Skip to content

Commit 8b51d26

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 5bc96b7 commit 8b51d26

9 files changed

Lines changed: 1885 additions & 685 deletions

File tree

examples/browser/cql4browsers.js

Lines changed: 89 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1799,6 +1799,12 @@ class Interval {
17991799
get isInterval() {
18001800
return true;
18011801
}
1802+
get isBoundlessInterval() {
1803+
return this.low == null && this.lowClosed && this.high == null && this.highClosed;
1804+
}
1805+
get isUnknownInterval() {
1806+
return this.low == null && !this.lowClosed && this.high == null && !this.highClosed;
1807+
}
18021808
get pointType() {
18031809
let pointType = null;
18041810
const point = this.low != null ? this.low : this.high;
@@ -1835,19 +1841,22 @@ class Interval {
18351841
if (this.high != null && typeof this.high.copy === 'function') {
18361842
newHigh = this.high.copy();
18371843
}
1838-
return new Interval(newLow, newHigh, this.lowClosed, this.highClosed);
1844+
return new Interval(newLow, newHigh, this.lowClosed, this.highClosed, this.defaultPointType);
18391845
}
18401846
contains(item, precision) {
1841-
// These first two checks ensure correct handling of edge case where an item equals the closed boundary
1847+
if (item != null && item.isInterval) {
1848+
throw new Error('Argument to contains must be a point');
1849+
}
1850+
if (this.isBoundlessInterval) {
1851+
return true;
1852+
}
1853+
// Ensure correct handling of edge case where an item equals the closed boundary
18421854
if (this.lowClosed && this.low != null && cmp.equals(this.low, item)) {
18431855
return true;
18441856
}
18451857
if (this.highClosed && this.high != null && cmp.equals(this.high, item)) {
18461858
return true;
18471859
}
1848-
if (item != null && item.isInterval) {
1849-
throw new Error('Argument to contains must be a point');
1850-
}
18511860
let lowFn;
18521861
if (this.lowClosed && this.low == null) {
18531862
lowFn = () => true;
@@ -1877,6 +1886,12 @@ class Interval {
18771886
return logic_1.ThreeValuedLogic.and(this.includes(other, precision), logic_1.ThreeValuedLogic.not(other.includes(this, precision)));
18781887
}
18791888
includes(other, precision) {
1889+
if (this.isBoundlessInterval) {
1890+
return true;
1891+
}
1892+
else if (other === null || other === void 0 ? void 0 : other.isBoundlessInterval) {
1893+
return this.isUnknownInterval ? null : false;
1894+
}
18801895
if (other == null || !other.isInterval) {
18811896
return this.contains(other, precision);
18821897
}
@@ -1894,6 +1909,9 @@ class Interval {
18941909
}
18951910
}
18961911
overlaps(item, precision) {
1912+
if (this.isBoundlessInterval || (item === null || item === void 0 ? void 0 : item.isBoundlessInterval)) {
1913+
return true;
1914+
}
18971915
const closed = this.toClosed();
18981916
const [low, high] = (() => {
18991917
if (item != null && item.isInterval) {
@@ -1907,19 +1925,37 @@ class Interval {
19071925
return logic_1.ThreeValuedLogic.and(cmp.lessThanOrEquals(closed.low, high, precision), cmp.greaterThanOrEquals(closed.high, low, precision));
19081926
}
19091927
overlapsAfter(item, precision) {
1910-
const closed = this.toClosed();
19111928
const high = item != null && item.isInterval ? item.toClosed().high : item;
1929+
if (this.isBoundlessInterval) {
1930+
return cmp.lessThan(high, (0, math_1.maxValueForInstance)(high), precision);
1931+
}
1932+
else if (item === null || item === void 0 ? void 0 : item.isBoundlessInterval) {
1933+
return false;
1934+
}
1935+
const closed = this.toClosed();
19121936
return logic_1.ThreeValuedLogic.and(cmp.lessThanOrEquals(closed.low, high, precision), cmp.greaterThan(closed.high, high, precision));
19131937
}
19141938
overlapsBefore(item, precision) {
1915-
const closed = this.toClosed();
19161939
const low = item != null && item.isInterval ? item.toClosed().low : item;
1940+
if (this.isBoundlessInterval) {
1941+
return cmp.greaterThan(low, (0, math_1.minValueForInstance)(low), precision);
1942+
}
1943+
else if (item === null || item === void 0 ? void 0 : item.isBoundlessInterval) {
1944+
return false;
1945+
}
1946+
const closed = this.toClosed();
19171947
return logic_1.ThreeValuedLogic.and(cmp.lessThan(closed.low, low, precision), cmp.greaterThanOrEquals(closed.high, low, precision));
19181948
}
19191949
union(other) {
19201950
if (other == null || !other.isInterval) {
19211951
throw new Error('Argument to union must be an interval');
19221952
}
1953+
if (this.isBoundlessInterval) {
1954+
return this.copy();
1955+
}
1956+
else if (other.isBoundlessInterval) {
1957+
return other.copy();
1958+
}
19231959
// Note that interval union is only defined if the arguments overlap or meet.
19241960
if (this.overlaps(other) || this.meets(other)) {
19251961
const [a, b] = [this.toClosed(), other.toClosed()];
@@ -1967,7 +2003,13 @@ class Interval {
19672003
if (other == null || !other.isInterval) {
19682004
throw new Error('Argument to union must be an interval');
19692005
}
1970-
// Note that interval union is only defined if the arguments overlap.
2006+
if (this.isBoundlessInterval) {
2007+
return other.copy();
2008+
}
2009+
else if (other.isBoundlessInterval) {
2010+
return this.copy();
2011+
}
2012+
// Note that interval intersect is only defined if the arguments overlap.
19712013
if (this.overlaps(other)) {
19722014
const [a, b] = [this.toClosed(), other.toClosed()];
19732015
let l, lc;
@@ -2110,6 +2152,9 @@ class Interval {
21102152
}
21112153
}
21122154
sameOrBefore(other, precision) {
2155+
if (this.isBoundlessInterval || (other === null || other === void 0 ? void 0 : other.isBoundlessInterval)) {
2156+
return this.equals(other);
2157+
}
21132158
if (this.end() == null || other == null || other.start() == null) {
21142159
return null;
21152160
}
@@ -2118,6 +2163,9 @@ class Interval {
21182163
}
21192164
}
21202165
sameOrAfter(other, precision) {
2166+
if (this.isBoundlessInterval || (other === null || other === void 0 ? void 0 : other.isBoundlessInterval)) {
2167+
return this.equals(other);
2168+
}
21212169
if (this.start() == null || other == null || other.end() == null) {
21222170
return null;
21232171
}
@@ -2127,6 +2175,12 @@ class Interval {
21272175
}
21282176
equals(other) {
21292177
if (other != null && other.isInterval) {
2178+
if (this.isBoundlessInterval) {
2179+
return other.isBoundlessInterval ? true : other.isUnknownInterval ? null : false;
2180+
}
2181+
else if (other.isBoundlessInterval) {
2182+
return this.isUnknownInterval ? null : false;
2183+
}
21302184
const [a, b] = [this.toClosed(), other.toClosed()];
21312185
return logic_1.ThreeValuedLogic.and(cmp.equals(a.low, b.low), cmp.equals(a.high, b.high));
21322186
}
@@ -2135,6 +2189,9 @@ class Interval {
21352189
}
21362190
}
21372191
after(other, precision) {
2192+
if (this.isBoundlessInterval || (other === null || other === void 0 ? void 0 : other.isBoundlessInterval)) {
2193+
return false;
2194+
}
21382195
const closed = this.toClosed();
21392196
// Meets spec, but not 100% correct (e.g., (null, 5] after [6, 10] --> null)
21402197
// Simple way to fix it: and w/ not overlaps
@@ -2146,6 +2203,9 @@ class Interval {
21462203
}
21472204
}
21482205
before(other, precision) {
2206+
if (this.isBoundlessInterval || (other === null || other === void 0 ? void 0 : other.isBoundlessInterval)) {
2207+
return false;
2208+
}
21492209
const closed = this.toClosed();
21502210
// Meets spec, but not 100% correct (e.g., (null, 5] after [6, 10] --> null)
21512211
// Simple way to fix it: and w/ not overlaps
@@ -2157,9 +2217,15 @@ class Interval {
21572217
}
21582218
}
21592219
meets(other, precision) {
2220+
if (this.isBoundlessInterval || (other === null || other === void 0 ? void 0 : other.isBoundlessInterval)) {
2221+
return false;
2222+
}
21602223
return logic_1.ThreeValuedLogic.or(this.meetsBefore(other, precision), this.meetsAfter(other, precision));
21612224
}
21622225
meetsAfter(other, precision) {
2226+
if (this.isBoundlessInterval || (other === null || other === void 0 ? void 0 : other.isBoundlessInterval)) {
2227+
return false;
2228+
}
21632229
try {
21642230
if (precision != null && this.low != null && this.low.isDateTime) {
21652231
return this.toClosed().low.sameAs(other.toClosed().high != null ? other.toClosed().high.add(1, precision) : null, precision);
@@ -2173,6 +2239,9 @@ class Interval {
21732239
}
21742240
}
21752241
meetsBefore(other, precision) {
2242+
if (this.isBoundlessInterval || (other === null || other === void 0 ? void 0 : other.isBoundlessInterval)) {
2243+
return false;
2244+
}
21762245
try {
21772246
if (precision != null && this.high != null && this.high.isDateTime) {
21782247
return this.toClosed().high.sameAs(other.toClosed().low != null ? other.toClosed().low.add(-1, precision) : null, precision);
@@ -2208,6 +2277,12 @@ class Interval {
22082277
return this.toClosed().high;
22092278
}
22102279
starts(other, precision) {
2280+
if (this.isBoundlessInterval) {
2281+
return other.isBoundlessInterval ? true : other.isUnknownInterval ? null : false;
2282+
}
2283+
else if (other === null || other === void 0 ? void 0 : other.isBoundlessInterval) {
2284+
return this.isUnknownInterval ? null : false;
2285+
}
22112286
let startEqual;
22122287
if (precision != null && this.low != null && this.low.isDateTime) {
22132288
startEqual = this.low.sameAs(other.low, precision);
@@ -2219,6 +2294,12 @@ class Interval {
22192294
return startEqual && endLessThanOrEqual;
22202295
}
22212296
ends(other, precision) {
2297+
if (this.isBoundlessInterval) {
2298+
return other.isBoundlessInterval ? true : other.isUnknownInterval ? null : false;
2299+
}
2300+
else if (other === null || other === void 0 ? void 0 : other.isBoundlessInterval) {
2301+
return this.isUnknownInterval ? null : false;
2302+
}
22222303
let endEqual;
22232304
const startGreaterThanOrEqual = cmp.greaterThanOrEquals(this.low, other.low, precision);
22242305
if (precision != null && (this.low != null ? this.low.isDateTime : undefined)) {

0 commit comments

Comments
 (0)