Skip to content

Commit 433fc4d

Browse files
committed
Improve handling of boundless and unknown intervals
Add special case logic for boundless intervals (e.g., Interval[null, null]) and unknown intervals (e.g., Interval(null, null)
1 parent 5c28fd1 commit 433fc4d

9 files changed

Lines changed: 1508 additions & 1201 deletions

File tree

examples/browser/cql4browsers.js

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1856,19 +1856,22 @@ class Interval {
18561856
if (this.high != null && typeof this.high.copy === 'function') {
18571857
newHigh = this.high.copy();
18581858
}
1859-
return new Interval(newLow, newHigh, this.lowClosed, this.highClosed);
1859+
return new Interval(newLow, newHigh, this.lowClosed, this.highClosed, this.defaultPointType);
18601860
}
18611861
contains(item, precision) {
1862-
// These first two checks ensure correct handling of edge case where an item equals the closed boundary
1862+
if (item != null && item.isInterval) {
1863+
throw new Error('Argument to contains must be a point');
1864+
}
1865+
if (this.isBoundlessInterval) {
1866+
return true;
1867+
}
1868+
// Ensure correct handling of edge case where an item equals the closed boundary
18631869
if (this.lowClosed && this.low != null && cmp.equals(this.low, item)) {
18641870
return true;
18651871
}
18661872
if (this.highClosed && this.high != null && cmp.equals(this.high, item)) {
18671873
return true;
18681874
}
1869-
if (item != null && item.isInterval) {
1870-
throw new Error('Argument to contains must be a point');
1871-
}
18721875
let lowFn;
18731876
if (this.lowClosed && this.low == null) {
18741877
lowFn = () => true;
@@ -1907,6 +1910,12 @@ class Interval {
19071910
return logic_1.ThreeValuedLogic.and(this.includes(other, precision), logic_1.ThreeValuedLogic.not(other.includes(this, precision)));
19081911
}
19091912
includes(other, precision) {
1913+
if (this.isBoundlessInterval) {
1914+
return true;
1915+
}
1916+
else if (other?.isBoundlessInterval) {
1917+
return this.isUnknownInterval ? null : false;
1918+
}
19101919
if (other == null || !other.isInterval) {
19111920
return this.contains(other, precision);
19121921
}
@@ -1974,6 +1983,12 @@ class Interval {
19741983
if (other == null || !other.isInterval) {
19751984
throw new Error('Argument to union must be an interval');
19761985
}
1986+
if (this.isBoundlessInterval) {
1987+
return this.copy();
1988+
}
1989+
else if (other.isBoundlessInterval) {
1990+
return other.copy();
1991+
}
19771992
// Note that interval union is only defined if the arguments overlap or meet.
19781993
if (this.overlaps(other) || this.meets(other)) {
19791994
const [a, b] = [this.toClosed(), other.toClosed()];
@@ -2021,7 +2036,13 @@ class Interval {
20212036
if (other == null || !other.isInterval) {
20222037
throw new Error('Argument to union must be an interval');
20232038
}
2024-
// Note that interval union is only defined if the arguments overlap.
2039+
if (this.isBoundlessInterval) {
2040+
return other.copy();
2041+
}
2042+
else if (other.isBoundlessInterval) {
2043+
return this.copy();
2044+
}
2045+
// Note that interval intersect is only defined if the arguments overlap.
20252046
if (this.overlaps(other)) {
20262047
const [a, b] = [this.toClosed(), other.toClosed()];
20272048
let l, lc;
@@ -2164,6 +2185,9 @@ class Interval {
21642185
}
21652186
}
21662187
sameOrBefore(other, precision) {
2188+
if (this.isBoundlessInterval || other?.isBoundlessInterval) {
2189+
return this.equals(other);
2190+
}
21672191
if (this.end() == null || other == null || other.start() == null) {
21682192
return null;
21692193
}
@@ -2172,6 +2196,9 @@ class Interval {
21722196
}
21732197
}
21742198
sameOrAfter(other, precision) {
2199+
if (this.isBoundlessInterval || other?.isBoundlessInterval) {
2200+
return this.equals(other);
2201+
}
21752202
if (this.start() == null || other == null || other.end() == null) {
21762203
return null;
21772204
}
@@ -2181,6 +2208,12 @@ class Interval {
21812208
}
21822209
equals(other) {
21832210
if (other != null && other.isInterval) {
2211+
if (this.isBoundlessInterval) {
2212+
return other.isBoundlessInterval ? true : other.isUnknownInterval ? null : false;
2213+
}
2214+
else if (other.isBoundlessInterval) {
2215+
return this.isUnknownInterval ? null : false;
2216+
}
21842217
const [a, b] = [this.toClosed(), other.toClosed()];
21852218
return logic_1.ThreeValuedLogic.and(cmp.equals(a.low, b.low), cmp.equals(a.high, b.high));
21862219
}
@@ -2189,6 +2222,9 @@ class Interval {
21892222
}
21902223
}
21912224
after(other, precision) {
2225+
if (this.isBoundlessInterval || other?.isBoundlessInterval) {
2226+
return false;
2227+
}
21922228
const closed = this.toClosed();
21932229
// Meets spec, but not 100% correct (e.g., (null, 5] after [6, 10] --> null)
21942230
// Simple way to fix it: and w/ not overlaps
@@ -2200,6 +2236,9 @@ class Interval {
22002236
}
22012237
}
22022238
before(other, precision) {
2239+
if (this.isBoundlessInterval || other?.isBoundlessInterval) {
2240+
return false;
2241+
}
22032242
const closed = this.toClosed();
22042243
// Meets spec, but not 100% correct (e.g., (null, 5] after [6, 10] --> null)
22052244
// Simple way to fix it: and w/ not overlaps
@@ -2211,9 +2250,15 @@ class Interval {
22112250
}
22122251
}
22132252
meets(other, precision) {
2253+
if (this.isBoundlessInterval || other?.isBoundlessInterval) {
2254+
return false;
2255+
}
22142256
return logic_1.ThreeValuedLogic.or(this.meetsBefore(other, precision), this.meetsAfter(other, precision));
22152257
}
22162258
meetsAfter(other, precision) {
2259+
if (this.isBoundlessInterval || other?.isBoundlessInterval) {
2260+
return false;
2261+
}
22172262
try {
22182263
if (precision != null && this.low != null && this.low.isDateTime) {
22192264
return this.toClosed().low.sameAs(other.toClosed().high != null ? other.toClosed().high.add(1, precision) : null, precision);
@@ -2227,6 +2272,9 @@ class Interval {
22272272
}
22282273
}
22292274
meetsBefore(other, precision) {
2275+
if (this.isBoundlessInterval || other?.isBoundlessInterval) {
2276+
return false;
2277+
}
22302278
try {
22312279
if (precision != null && this.high != null && this.high.isDateTime) {
22322280
return this.toClosed().high.sameAs(other.toClosed().low != null ? other.toClosed().low.add(-1, precision) : null, precision);
@@ -2262,6 +2310,12 @@ class Interval {
22622310
return this.toClosed().high;
22632311
}
22642312
starts(other, precision) {
2313+
if (this.isBoundlessInterval) {
2314+
return other.isBoundlessInterval ? true : other.isUnknownInterval ? null : false;
2315+
}
2316+
else if (other?.isBoundlessInterval) {
2317+
return this.isUnknownInterval ? null : false;
2318+
}
22652319
let startEqual;
22662320
if (precision != null && this.low != null && this.low.isDateTime) {
22672321
startEqual = this.low.sameAs(other.low, precision);
@@ -2273,6 +2327,12 @@ class Interval {
22732327
return startEqual && endLessThanOrEqual;
22742328
}
22752329
ends(other, precision) {
2330+
if (this.isBoundlessInterval) {
2331+
return other.isBoundlessInterval ? true : other.isUnknownInterval ? null : false;
2332+
}
2333+
else if (other?.isBoundlessInterval) {
2334+
return this.isUnknownInterval ? null : false;
2335+
}
22762336
let endEqual;
22772337
const startGreaterThanOrEqual = cmp.greaterThanOrEquals(this.low, other.low, precision);
22782338
if (precision != null && (this.low != null ? this.low.isDateTime : undefined)) {

src/datatypes/interval.ts

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,20 +78,23 @@ export class Interval {
7878
newHigh = this.high.copy();
7979
}
8080

81-
return new Interval(newLow, newHigh, this.lowClosed, this.highClosed);
81+
return new Interval(newLow, newHigh, this.lowClosed, this.highClosed, this.defaultPointType);
8282
}
8383

8484
contains(item: any, precision?: any) {
85-
// These first two checks ensure correct handling of edge case where an item equals the closed boundary
85+
if (item != null && item.isInterval) {
86+
throw new Error('Argument to contains must be a point');
87+
}
88+
if (this.isBoundlessInterval) {
89+
return true;
90+
}
91+
// Ensure correct handling of edge case where an item equals the closed boundary
8692
if (this.lowClosed && this.low != null && cmp.equals(this.low, item)) {
8793
return true;
8894
}
8995
if (this.highClosed && this.high != null && cmp.equals(this.high, item)) {
9096
return true;
9197
}
92-
if (item != null && item.isInterval) {
93-
throw new Error('Argument to contains must be a point');
94-
}
9598
let lowFn;
9699
if (this.lowClosed && this.low == null) {
97100
lowFn = () => true;
@@ -137,6 +140,11 @@ export class Interval {
137140
}
138141

139142
includes(other: any, precision?: any) {
143+
if (this.isBoundlessInterval) {
144+
return true;
145+
} else if (other?.isBoundlessInterval) {
146+
return this.isUnknownInterval ? null : false;
147+
}
140148
if (other == null || !other.isInterval) {
141149
return this.contains(other, precision);
142150
}
@@ -216,6 +224,11 @@ export class Interval {
216224
if (other == null || !other.isInterval) {
217225
throw new Error('Argument to union must be an interval');
218226
}
227+
if (this.isBoundlessInterval) {
228+
return this.copy();
229+
} else if (other.isBoundlessInterval) {
230+
return other.copy();
231+
}
219232
// Note that interval union is only defined if the arguments overlap or meet.
220233
if (this.overlaps(other) || this.meets(other)) {
221234
const [a, b] = [this.toClosed(), other.toClosed()];
@@ -255,7 +268,12 @@ export class Interval {
255268
if (other == null || !other.isInterval) {
256269
throw new Error('Argument to union must be an interval');
257270
}
258-
// Note that interval union is only defined if the arguments overlap.
271+
if (this.isBoundlessInterval) {
272+
return other.copy();
273+
} else if (other.isBoundlessInterval) {
274+
return this.copy();
275+
}
276+
// Note that interval intersect is only defined if the arguments overlap.
259277
if (this.overlaps(other)) {
260278
const [a, b] = [this.toClosed(), other.toClosed()];
261279
let l, lc;
@@ -397,6 +415,9 @@ export class Interval {
397415
}
398416

399417
sameOrBefore(other: any, precision?: any) {
418+
if (this.isBoundlessInterval || other?.isBoundlessInterval) {
419+
return this.equals(other);
420+
}
400421
if (this.end() == null || other == null || other.start() == null) {
401422
return null;
402423
} else {
@@ -405,6 +426,9 @@ export class Interval {
405426
}
406427

407428
sameOrAfter(other: any, precision?: any) {
429+
if (this.isBoundlessInterval || other?.isBoundlessInterval) {
430+
return this.equals(other);
431+
}
408432
if (this.start() == null || other == null || other.end() == null) {
409433
return null;
410434
} else {
@@ -414,6 +438,11 @@ export class Interval {
414438

415439
equals(other: any) {
416440
if (other != null && other.isInterval) {
441+
if (this.isBoundlessInterval) {
442+
return other.isBoundlessInterval ? true : other.isUnknownInterval ? null : false;
443+
} else if (other.isBoundlessInterval) {
444+
return this.isUnknownInterval ? null : false;
445+
}
417446
const [a, b] = [this.toClosed(), other.toClosed()];
418447
return ThreeValuedLogic.and(cmp.equals(a.low, b.low), cmp.equals(a.high, b.high));
419448
} else {
@@ -422,6 +451,9 @@ export class Interval {
422451
}
423452

424453
after(other: any, precision?: any) {
454+
if (this.isBoundlessInterval || other?.isBoundlessInterval) {
455+
return false;
456+
}
425457
const closed = this.toClosed();
426458
// Meets spec, but not 100% correct (e.g., (null, 5] after [6, 10] --> null)
427459
// Simple way to fix it: and w/ not overlaps
@@ -433,6 +465,9 @@ export class Interval {
433465
}
434466

435467
before(other: any, precision?: any) {
468+
if (this.isBoundlessInterval || other?.isBoundlessInterval) {
469+
return false;
470+
}
436471
const closed = this.toClosed();
437472
// Meets spec, but not 100% correct (e.g., (null, 5] after [6, 10] --> null)
438473
// Simple way to fix it: and w/ not overlaps
@@ -444,13 +479,19 @@ export class Interval {
444479
}
445480

446481
meets(other: any, precision?: any) {
482+
if (this.isBoundlessInterval || other?.isBoundlessInterval) {
483+
return false;
484+
}
447485
return ThreeValuedLogic.or(
448486
this.meetsBefore(other, precision),
449487
this.meetsAfter(other, precision)
450488
);
451489
}
452490

453491
meetsAfter(other: any, precision?: any) {
492+
if (this.isBoundlessInterval || other?.isBoundlessInterval) {
493+
return false;
494+
}
454495
try {
455496
if (precision != null && this.low != null && this.low.isDateTime) {
456497
return this.toClosed().low.sameAs(
@@ -466,6 +507,9 @@ export class Interval {
466507
}
467508

468509
meetsBefore(other: any, precision?: any) {
510+
if (this.isBoundlessInterval || other?.isBoundlessInterval) {
511+
return false;
512+
}
469513
try {
470514
if (precision != null && this.high != null && this.high.isDateTime) {
471515
return this.toClosed().high.sameAs(
@@ -503,6 +547,11 @@ export class Interval {
503547
}
504548

505549
starts(other: any, precision?: any) {
550+
if (this.isBoundlessInterval) {
551+
return other.isBoundlessInterval ? true : other.isUnknownInterval ? null : false;
552+
} else if (other?.isBoundlessInterval) {
553+
return this.isUnknownInterval ? null : false;
554+
}
506555
let startEqual;
507556
if (precision != null && this.low != null && this.low.isDateTime) {
508557
startEqual = this.low.sameAs(other.low, precision);
@@ -514,6 +563,11 @@ export class Interval {
514563
}
515564

516565
ends(other: any, precision?: any) {
566+
if (this.isBoundlessInterval) {
567+
return other.isBoundlessInterval ? true : other.isUnknownInterval ? null : false;
568+
} else if (other?.isBoundlessInterval) {
569+
return this.isUnknownInterval ? null : false;
570+
}
517571
let endEqual;
518572
const startGreaterThanOrEqual = cmp.greaterThanOrEquals(this.low, other.low, precision);
519573
if (precision != null && (this.low != null ? this.low.isDateTime : undefined)) {

0 commit comments

Comments
 (0)