Skip to content

Commit a97cdac

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 9877d3f commit a97cdac

9 files changed

Lines changed: 1624 additions & 1367 deletions

File tree

examples/browser/cql4browsers.js

Lines changed: 182 additions & 172 deletions
Large diffs are not rendered by default.

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;
@@ -125,6 +128,11 @@ export class Interval {
125128
}
126129

127130
includes(other: any, precision?: any) {
131+
if (this.isBoundlessInterval) {
132+
return true;
133+
} else if (other?.isBoundlessInterval) {
134+
return this.isUnknownInterval ? null : false;
135+
}
128136
if (other == null || !other.isInterval) {
129137
return this.contains(other, precision);
130138
}
@@ -204,6 +212,11 @@ export class Interval {
204212
if (other == null || !other.isInterval) {
205213
throw new Error('Argument to union must be an interval');
206214
}
215+
if (this.isBoundlessInterval) {
216+
return this.copy();
217+
} else if (other.isBoundlessInterval) {
218+
return other.copy();
219+
}
207220
// Note that interval union is only defined if the arguments overlap or meet.
208221
if (this.overlaps(other) || this.meets(other)) {
209222
const [a, b] = [this.toClosed(), other.toClosed()];
@@ -243,7 +256,12 @@ export class Interval {
243256
if (other == null || !other.isInterval) {
244257
throw new Error('Argument to union must be an interval');
245258
}
246-
// Note that interval union is only defined if the arguments overlap.
259+
if (this.isBoundlessInterval) {
260+
return other.copy();
261+
} else if (other.isBoundlessInterval) {
262+
return this.copy();
263+
}
264+
// Note that interval intersect is only defined if the arguments overlap.
247265
if (this.overlaps(other)) {
248266
const [a, b] = [this.toClosed(), other.toClosed()];
249267
let l, lc;
@@ -385,6 +403,9 @@ export class Interval {
385403
}
386404

387405
sameOrBefore(other: any, precision?: any) {
406+
if (this.isBoundlessInterval || other?.isBoundlessInterval) {
407+
return this.equals(other);
408+
}
388409
if (this.end() == null || other == null || other.start() == null) {
389410
return null;
390411
} else {
@@ -393,6 +414,9 @@ export class Interval {
393414
}
394415

395416
sameOrAfter(other: any, precision?: any) {
417+
if (this.isBoundlessInterval || other?.isBoundlessInterval) {
418+
return this.equals(other);
419+
}
396420
if (this.start() == null || other == null || other.end() == null) {
397421
return null;
398422
} else {
@@ -402,6 +426,11 @@ export class Interval {
402426

403427
equals(other: any) {
404428
if (other != null && other.isInterval) {
429+
if (this.isBoundlessInterval) {
430+
return other.isBoundlessInterval ? true : other.isUnknownInterval ? null : false;
431+
} else if (other.isBoundlessInterval) {
432+
return this.isUnknownInterval ? null : false;
433+
}
405434
const [a, b] = [this.toClosed(), other.toClosed()];
406435
return ThreeValuedLogic.and(cmp.equals(a.low, b.low), cmp.equals(a.high, b.high));
407436
} else {
@@ -410,6 +439,9 @@ export class Interval {
410439
}
411440

412441
after(other: any, precision?: any) {
442+
if (this.isBoundlessInterval || other?.isBoundlessInterval) {
443+
return false;
444+
}
413445
const closed = this.toClosed();
414446
// Meets spec, but not 100% correct (e.g., (null, 5] after [6, 10] --> null)
415447
// Simple way to fix it: and w/ not overlaps
@@ -421,6 +453,9 @@ export class Interval {
421453
}
422454

423455
before(other: any, precision?: any) {
456+
if (this.isBoundlessInterval || other?.isBoundlessInterval) {
457+
return false;
458+
}
424459
const closed = this.toClosed();
425460
// Meets spec, but not 100% correct (e.g., (null, 5] after [6, 10] --> null)
426461
// Simple way to fix it: and w/ not overlaps
@@ -432,13 +467,19 @@ export class Interval {
432467
}
433468

434469
meets(other: any, precision?: any) {
470+
if (this.isBoundlessInterval || other?.isBoundlessInterval) {
471+
return false;
472+
}
435473
return ThreeValuedLogic.or(
436474
this.meetsBefore(other, precision),
437475
this.meetsAfter(other, precision)
438476
);
439477
}
440478

441479
meetsAfter(other: any, precision?: any) {
480+
if (this.isBoundlessInterval || other?.isBoundlessInterval) {
481+
return false;
482+
}
442483
try {
443484
if (precision != null && this.low != null && this.low.isDateTime) {
444485
return this.toClosed().low.sameAs(
@@ -454,6 +495,9 @@ export class Interval {
454495
}
455496

456497
meetsBefore(other: any, precision?: any) {
498+
if (this.isBoundlessInterval || other?.isBoundlessInterval) {
499+
return false;
500+
}
457501
try {
458502
if (precision != null && this.high != null && this.high.isDateTime) {
459503
return this.toClosed().high.sameAs(
@@ -491,6 +535,11 @@ export class Interval {
491535
}
492536

493537
starts(other: any, precision?: any) {
538+
if (this.isBoundlessInterval) {
539+
return other.isBoundlessInterval ? true : other.isUnknownInterval ? null : false;
540+
} else if (other?.isBoundlessInterval) {
541+
return this.isUnknownInterval ? null : false;
542+
}
494543
let startEqual;
495544
if (precision != null && this.low != null && this.low.isDateTime) {
496545
startEqual = this.low.sameAs(other.low, precision);
@@ -502,6 +551,11 @@ export class Interval {
502551
}
503552

504553
ends(other: any, precision?: any) {
554+
if (this.isBoundlessInterval) {
555+
return other.isBoundlessInterval ? true : other.isUnknownInterval ? null : false;
556+
} else if (other?.isBoundlessInterval) {
557+
return this.isUnknownInterval ? null : false;
558+
}
505559
let endEqual;
506560
const startGreaterThanOrEqual = cmp.greaterThanOrEquals(this.low, other.low, precision);
507561
if (precision != null && (this.low != null ? this.low.isDateTime : undefined)) {

0 commit comments

Comments
 (0)