Skip to content

Commit 0f88fc8

Browse files
committed
1 parent 214a25b commit 0f88fc8

10 files changed

Lines changed: 4629 additions & 408 deletions

File tree

examples/browser/cql4browsers.js

Lines changed: 251 additions & 386 deletions
Large diffs are not rendered by default.

src/datatypes/datetime.ts

Lines changed: 68 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,6 +1005,32 @@ export class DateTime extends AbstractDate {
10051005
}
10061006
return reduced;
10071007
}
1008+
1009+
highBoundary(precision: number | null = null) {
1010+
const precisionMap = this.isTime() ? PRECISION_TIME_VALUE_MAP : PRECISION_DATETIME_VALUE_MAP;
1011+
const unit = precisionMap.get(precision ?? (this.isTime() ? 9 : 17));
1012+
if (unit == null) {
1013+
return null;
1014+
}
1015+
1016+
const boundary = DateTime.fromLuxonDateTime(this.toLuxonUncertainty().high);
1017+
return this.isTime()
1018+
? boundary.getTime().reducedPrecision(unit)
1019+
: boundary.reducedPrecision(unit);
1020+
}
1021+
1022+
lowBoundary(precision: number | null = null) {
1023+
const precisionMap = this.isTime() ? PRECISION_TIME_VALUE_MAP : PRECISION_DATETIME_VALUE_MAP;
1024+
const unit = precisionMap.get(precision ?? (this.isTime() ? 9 : 17));
1025+
if (unit == null) {
1026+
return null;
1027+
}
1028+
1029+
const boundary = DateTime.fromLuxonDateTime(this.toLuxonUncertainty().low);
1030+
return this.isTime()
1031+
? boundary.getTime().reducedPrecision(unit)
1032+
: boundary.reducedPrecision(unit);
1033+
}
10081034
}
10091035

10101036
export class Date extends AbstractDate {
@@ -1228,6 +1254,24 @@ export class Date extends AbstractDate {
12281254
}
12291255
return reduced;
12301256
}
1257+
1258+
highBoundary(precision: number | null = null) {
1259+
const unit = PRECISION_DATETIME_VALUE_MAP.get(precision ?? 8);
1260+
if (unit == null || !Date.FIELDS.includes(unit)) {
1261+
return null;
1262+
}
1263+
1264+
return Date.fromLuxonDateTime(this.toLuxonUncertainty().high).reducedPrecision(unit);
1265+
}
1266+
1267+
lowBoundary(precision: number | null = null) {
1268+
const unit = PRECISION_DATETIME_VALUE_MAP.get(precision ?? 8);
1269+
if (unit == null || !Date.FIELDS.includes(unit)) {
1270+
return null;
1271+
}
1272+
1273+
return Date.fromLuxonDateTime(this.toLuxonUncertainty().low).reducedPrecision(unit);
1274+
}
12311275
}
12321276

12331277
export const MIN_DATETIME_VALUE = DateTime.parse(MIN_DATETIME_VALUE_STRING);
@@ -1237,26 +1281,30 @@ export const MAX_DATE_VALUE = Date.parse(MAX_DATE_VALUE_STRING);
12371281
export const MIN_TIME_VALUE = DateTime.parse(MIN_TIME_VALUE_STRING)?.getTime();
12381282
export const MAX_TIME_VALUE = DateTime.parse(MAX_TIME_VALUE_STRING)?.getTime();
12391283

1240-
const DATETIME_PRECISION_VALUE_MAP = (() => {
1241-
const dtpvMap = new Map();
1242-
dtpvMap.set(DateTime.Unit.YEAR, 4);
1243-
dtpvMap.set(DateTime.Unit.MONTH, 6);
1244-
dtpvMap.set(DateTime.Unit.DAY, 8);
1245-
dtpvMap.set(DateTime.Unit.HOUR, 10);
1246-
dtpvMap.set(DateTime.Unit.MINUTE, 12);
1247-
dtpvMap.set(DateTime.Unit.SECOND, 14);
1248-
dtpvMap.set(DateTime.Unit.MILLISECOND, 17);
1249-
return dtpvMap;
1250-
})();
1251-
1252-
const TIME_PRECISION_VALUE_MAP = (() => {
1253-
const tpvMap = new Map();
1254-
tpvMap.set(DateTime.Unit.HOUR, 2);
1255-
tpvMap.set(DateTime.Unit.MINUTE, 4);
1256-
tpvMap.set(DateTime.Unit.SECOND, 6);
1257-
tpvMap.set(DateTime.Unit.MILLISECOND, 9);
1258-
return tpvMap;
1259-
})();
1284+
const DATETIME_PRECISION_VALUE_MAP = new Map<string | null, number>([
1285+
[DateTime.Unit.YEAR, 4],
1286+
[DateTime.Unit.MONTH, 6],
1287+
[DateTime.Unit.DAY, 8],
1288+
[DateTime.Unit.HOUR, 10],
1289+
[DateTime.Unit.MINUTE, 12],
1290+
[DateTime.Unit.SECOND, 14],
1291+
[DateTime.Unit.MILLISECOND, 17]
1292+
]);
1293+
1294+
const PRECISION_DATETIME_VALUE_MAP = invertMap(DATETIME_PRECISION_VALUE_MAP);
1295+
1296+
const TIME_PRECISION_VALUE_MAP = new Map<string | null, number>([
1297+
[DateTime.Unit.HOUR, 2],
1298+
[DateTime.Unit.MINUTE, 4],
1299+
[DateTime.Unit.SECOND, 6],
1300+
[DateTime.Unit.MILLISECOND, 9]
1301+
]);
1302+
1303+
const PRECISION_TIME_VALUE_MAP = invertMap(TIME_PRECISION_VALUE_MAP);
1304+
1305+
function invertMap<K, V>(map: Map<K, V>): Map<V, K> {
1306+
return new Map([...map.entries()].map(([k, v]) => [v, k]));
1307+
}
12601308

12611309
function compareWithDefaultResult(a: any, b: any, defaultResult: any) {
12621310
// return false there is a type mismatch

src/elm/aggregate.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,10 @@ export class Mode extends AggregateExpression {
249249
}
250250

251251
type StatisticType =
252-
'standard_deviation' | 'population_deviation' | 'standard_variance' | 'population_variance';
252+
| 'standard_deviation'
253+
| 'population_deviation'
254+
| 'standard_variance'
255+
| 'population_variance';
253256

254257
export class StdDev extends AggregateExpression {
255258
// TODO: This should be a derived class of an abstract base class 'Statistic'

src/elm/arithmetic.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Uncertainty } from '../datatypes/uncertainty';
55
import { Context } from '../runtime/context';
66
import { build } from './builder';
77
import {
8+
Date as CQLDate,
89
DateTime,
910
MAX_DATE_VALUE,
1011
MAX_DATETIME_VALUE,
@@ -469,6 +470,69 @@ export class MaxValue extends Expression {
469470
}
470471
}
471472

473+
function decimalBoundary(value: number, precision: number | null, boundary: 'high' | 'low') {
474+
precision ??= 8;
475+
if (precision < 0 || precision > 8) {
476+
return null;
477+
}
478+
479+
const [whole = '0', decimalPart = ''] = value.toString().split('.');
480+
// we can't do much w/ decimals that are using scientific notation
481+
if (decimalPart.includes('e')) {
482+
return value;
483+
}
484+
485+
const filledDecimalPart = decimalPart
486+
.padEnd(precision, boundary === 'high' ? '9' : '0')
487+
.slice(0, precision);
488+
489+
return Number(`${whole}.${filledDecimalPart}`);
490+
}
491+
492+
function boundary(
493+
value: CQLDate | DateTime | number,
494+
precision: number | null,
495+
boundary: 'high' | 'low'
496+
) {
497+
if (value == null) {
498+
return null;
499+
}
500+
501+
if (typeof value === 'number') {
502+
return decimalBoundary(value, precision, boundary);
503+
}
504+
505+
if (boundary === 'high') {
506+
return value.highBoundary?.(precision);
507+
} else if (boundary === 'low') {
508+
return value.lowBoundary?.(precision);
509+
}
510+
511+
return null;
512+
}
513+
514+
export class HighBoundary extends Expression {
515+
constructor(json: any) {
516+
super(json);
517+
}
518+
519+
async exec(ctx: Context) {
520+
const [value, precision] = await this.execArgs(ctx);
521+
return boundary(value, precision, 'high');
522+
}
523+
}
524+
525+
export class LowBoundary extends Expression {
526+
constructor(json: any) {
527+
super(json);
528+
}
529+
530+
async exec(ctx: Context) {
531+
const [value, precision] = await this.execArgs(ctx);
532+
return boundary(value, precision, 'low');
533+
}
534+
}
535+
472536
export class Successor extends Expression {
473537
constructor(json: any) {
474538
super(json);

test/datatypes/date-test.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,72 @@ describe('Date.reducedPrecision', () => {
868868
});
869869
});
870870

871+
describe('Date.highBoundary', () => {
872+
it('should return the greatest possible Date boundary for each supported precision', () => {
873+
const date = Date.parse('2014-02');
874+
date.highBoundary(4).should.eql(new Date(2014));
875+
date.highBoundary(6).should.eql(new Date(2014, 2));
876+
date.highBoundary(8).should.eql(new Date(2014, 2, 28));
877+
});
878+
879+
it('should return the greatest possible Date boundary for minimally specified Date', () => {
880+
const date = Date.parse('2014');
881+
date.highBoundary(4).should.eql(new Date(2014));
882+
date.highBoundary(6).should.eql(new Date(2014, 12));
883+
date.highBoundary(8).should.eql(new Date(2014, 12, 31));
884+
});
885+
886+
it('should return the greatest possible Date boundary for fully specified Date', () => {
887+
const date = Date.parse('2014-05-17');
888+
date.highBoundary(4).should.eql(new Date(2014));
889+
date.highBoundary(6).should.eql(new Date(2014, 5));
890+
date.highBoundary(8).should.eql(new Date(2014, 5, 17));
891+
});
892+
893+
it('should use the default precision when no precision is provided', () => {
894+
Date.parse('2014')
895+
.highBoundary()
896+
.should.eql(new Date(2014, 12, 31));
897+
});
898+
899+
it('should return null when the precision is unsupported', () => {
900+
should(Date.parse('2014').highBoundary(10)).be.null();
901+
});
902+
});
903+
904+
describe('Date.lowBoundary', () => {
905+
it('should return the least possible Date boundary for each supported precision', () => {
906+
const date = Date.parse('2014-02');
907+
date.lowBoundary(4).should.eql(new Date(2014));
908+
date.lowBoundary(6).should.eql(new Date(2014, 2));
909+
date.lowBoundary(8).should.eql(new Date(2014, 2, 1));
910+
});
911+
912+
it('should return the least possible Date boundary for minimally specified Date', () => {
913+
const date = Date.parse('2014');
914+
date.lowBoundary(4).should.eql(new Date(2014));
915+
date.lowBoundary(6).should.eql(new Date(2014, 1));
916+
date.lowBoundary(8).should.eql(new Date(2014, 1, 1));
917+
});
918+
919+
it('should return the least possible Date boundary for fully specified Date', () => {
920+
const date = Date.parse('2014-05-17');
921+
date.lowBoundary(4).should.eql(new Date(2014));
922+
date.lowBoundary(6).should.eql(new Date(2014, 5));
923+
date.lowBoundary(8).should.eql(new Date(2014, 5, 17));
924+
});
925+
926+
it('should use the default precision when no precision is provided', () => {
927+
Date.parse('2014')
928+
.lowBoundary()
929+
.should.eql(new Date(2014, 1, 1));
930+
});
931+
932+
it('should return null when the precision is unsupported', () => {
933+
should(Date.parse('2014').lowBoundary(10)).be.null();
934+
});
935+
});
936+
871937
describe('Date.getPrecisionValue', () => {
872938
it('should properly get precision value for years', () => {
873939
Date.parse('2012').getPrecisionValue().should.equal(4);

0 commit comments

Comments
 (0)