Skip to content

Commit af4da33

Browse files
committed
Improve handling of null / uncertain boundaries
1 parent 0feb777 commit af4da33

2 files changed

Lines changed: 86 additions & 7 deletions

File tree

src/datatypes/interval.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import {
2323
import { MIN_FLOAT_VALUE } from '../util/limits';
2424
import { Quantity } from './quantity';
2525

26+
// TODO: Replace all build.fhir.org URL references with stable references once CQL 2.0 is pulished
27+
2628
export class Interval {
2729
constructor(
2830
public low: any,
@@ -308,10 +310,15 @@ export class Interval {
308310
this.pointType === ELM_DATETIME_TYPE ||
309311
this.pointType === ELM_TIME_TYPE
310312
) {
311-
const boundaries = [this.low, this.high, other.low, other.high];
312-
const leastPreciseBoundary = boundaries.reduce((least, boundary) =>
313-
boundary?.isLessPrecise(least) ? boundary : least
313+
const boundaries = [this.low, this.high, other.low, other.high].flatMap(boundary =>
314+
boundary?.isUncertainty ? [boundary.low, boundary.high] : [boundary]
314315
);
316+
const leastPreciseBoundary = boundaries
317+
.filter(boundary => boundary != null)
318+
.reduce(
319+
(least, boundary) => (least == null || boundary.isLessPrecise(least) ? boundary : least),
320+
null
321+
);
315322
precision = leastPreciseBoundary?.getPrecision();
316323
}
317324

@@ -850,16 +857,16 @@ function getQuantityInstanceForMinMax(ivl?: Interval): Quantity | undefined {
850857
function performConversionIfNecessary(left: Interval, right: Interval) {
851858
if (left.pointType === ELM_DATE_TYPE && right.pointType === ELM_DATETIME_TYPE) {
852859
left = new Interval(
853-
left.low?.getDateTime(),
854-
left.high?.getDateTime(),
860+
convertDateBoundaryToDateTime(left.low),
861+
convertDateBoundaryToDateTime(left.high),
855862
left.lowClosed,
856863
left.highClosed,
857864
ELM_DATETIME_TYPE
858865
);
859866
} else if (left.pointType === ELM_DATETIME_TYPE && right.pointType === ELM_DATE_TYPE) {
860867
right = new Interval(
861-
right.low?.getDateTime(),
862-
right.high?.getDateTime(),
868+
convertDateBoundaryToDateTime(right.low),
869+
convertDateBoundaryToDateTime(right.high),
863870
right.lowClosed,
864871
right.highClosed,
865872
ELM_DATETIME_TYPE
@@ -868,6 +875,14 @@ function performConversionIfNecessary(left: Interval, right: Interval) {
868875
return [left, right];
869876
}
870877

878+
function convertDateBoundaryToDateTime(boundary: any) {
879+
const convert = (value: any) => value?.getDateTime() ?? value;
880+
if (boundary?.isUncertainty) {
881+
return new Uncertainty(convert(boundary.low), convert(boundary.high));
882+
}
883+
return convert(boundary);
884+
}
885+
871886
function lowestUncertainty(x: any, y: any) {
872887
if (!x?.isUncertainty) {
873888
x = new Uncertainty(x);

test/datatypes/interval-test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1416,6 +1416,46 @@ describe('DateTimeInterval', () => {
14161416
dateTimeIntervalWithBoundlessLow.sameAs(dateIntervalWithBoundlessLow).should.be.true();
14171417
});
14181418

1419+
it('should convert uncertain Date interval boundaries to DateTime', () => {
1420+
const dateLow = Date.parse('2012-01-01');
1421+
const dateHigh = Date.parse('2012-12-31');
1422+
const dateTimeLow = DateTime.parse('2012-01-01');
1423+
const dateTimeHigh = DateTime.parse('2012-12-31');
1424+
const dateIntervalWithUncertainLow = new Interval(
1425+
new Uncertainty(dateLow),
1426+
dateHigh,
1427+
true,
1428+
true,
1429+
ELM_DATE_TYPE
1430+
);
1431+
const dateTimeIntervalWithUncertainLow = new Interval(
1432+
new Uncertainty(dateTimeLow),
1433+
dateTimeHigh,
1434+
true,
1435+
true,
1436+
ELM_DATETIME_TYPE
1437+
);
1438+
const dateIntervalWithUncertainHigh = new Interval(
1439+
dateLow,
1440+
new Uncertainty(dateHigh),
1441+
true,
1442+
true,
1443+
ELM_DATE_TYPE
1444+
);
1445+
const dateTimeIntervalWithUncertainHigh = new Interval(
1446+
dateTimeLow,
1447+
new Uncertainty(dateTimeHigh),
1448+
true,
1449+
true,
1450+
ELM_DATETIME_TYPE
1451+
);
1452+
1453+
dateIntervalWithUncertainLow.sameAs(dateTimeIntervalWithUncertainLow).should.be.true();
1454+
dateTimeIntervalWithUncertainLow.sameAs(dateIntervalWithUncertainLow).should.be.true();
1455+
dateIntervalWithUncertainHigh.sameAs(dateTimeIntervalWithUncertainHigh).should.be.true();
1456+
dateTimeIntervalWithUncertainHigh.sameAs(dateIntervalWithUncertainHigh).should.be.true();
1457+
});
1458+
14191459
it('should properly handle boundless and unknown intervals', () => {
14201460
boundlessInterval(ELM_DATETIME_TYPE)
14211461
.sameAs(boundlessInterval(ELM_DATETIME_TYPE))
@@ -1920,6 +1960,30 @@ describe('DateTimeInterval', () => {
19201960
.should.equalInterval(new Interval(x.toMinute.high, y.toDay.high, false, true));
19211961
});
19221962

1963+
it('should handle null temporal boundaries when determining precision', () => {
1964+
const a = new Interval(null, DateTime.parse('2012-12-31'));
1965+
const b = new Interval(DateTime.parse('2012-06-01'), DateTime.parse('2013-01-01'));
1966+
a.except(b).should.equalInterval(
1967+
new Interval(null, DateTime.parse('2012-06-01'), true, false)
1968+
);
1969+
});
1970+
1971+
it('should handle uncertain temporal boundaries when determining precision', () => {
1972+
const a = new Interval(
1973+
new Uncertainty(DateTime.parse('2012-01-01'), DateTime.parse('2012-01-31')),
1974+
DateTime.parse('2012-12-31')
1975+
);
1976+
const b = new Interval(DateTime.parse('2012-06-01'), DateTime.parse('2013-01-01'));
1977+
a.except(b).should.equalInterval(
1978+
new Interval(
1979+
new Uncertainty(DateTime.parse('2012-01-01'), DateTime.parse('2012-01-31')),
1980+
DateTime.parse('2012-06-01'),
1981+
true,
1982+
false
1983+
)
1984+
);
1985+
});
1986+
19231987
it('should throw when the argument is a point', () => {
19241988
should(() => d.all2012.closed.except(DateTime.parse('2012-07-01T00:00:00.0'))).throw(Error);
19251989
});

0 commit comments

Comments
 (0)