Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
4916f2b
WIP of improved CQL Decimal
dehall Aug 12, 2026
c943426
WIP of better CQL Decimal support, checkpoint 2
dehall Aug 19, 2026
9b3d4bc
CQL Decimal improvements, checkpoint 3
dehall Aug 20, 2026
e47383f
fix package-lock
dehall Aug 20, 2026
b77728e
actually fix package-lock
dehall Aug 20, 2026
ffc8949
Skip interval expand tests for now
dehall Aug 25, 2026
fbc1f98
fix test-server
dehall Aug 25, 2026
fdf0c52
clean up some TODOs
dehall Aug 26, 2026
d66fad5
remove now-unnecessary param in successor/predecessor
dehall Aug 26, 2026
cb3c83f
additional cleanup and fixes
dehall Aug 26, 2026
8993bf8
additional cleanup
dehall Aug 26, 2026
b7cb9e6
new tests added by codex
dehall Aug 26, 2026
1c075ff
one more round of cleanup
dehall Aug 26, 2026
95ad9ed
Low-hanging fruit to get interval tests passing
dehall Aug 27, 2026
8c25d28
just a couple more tests
dehall Aug 27, 2026
b3eb56d
more cleanup on manual review
dehall Aug 27, 2026
a887e94
one more
dehall Aug 27, 2026
1525212
forgot to save this file
dehall Aug 27, 2026
240f95b
update some comments
dehall Aug 27, 2026
ae241c0
Update spec-tests to the latest, plus some minor supporting changes t…
dehall Sep 3, 2026
bb8ac72
Use Decimal equals in DateTime.isUTC
dehall Sep 8, 2026
ffa7d8f
Add scale field and support to Decimal class
dehall Sep 8, 2026
5c570a0
remove resultTypeName from generic number math
dehall Sep 9, 2026
42b6246
reintroduce numeric integer check
dehall Sep 9, 2026
2965dc1
Remove conversions that aren't supported in ELM, and clarify Decimal.…
dehall Sep 9, 2026
e987616
use Decimal comparison methods for uncertainty boundaries
dehall Sep 9, 2026
bd9772b
update UCUM conversion logic to handle special cases and decimal scale
dehall Sep 9, 2026
43aaa72
sum Quantities by value so values are only normalized once at the end
dehall Sep 9, 2026
ad82f42
cleanup aggregate file; use explicit lambda functions to avoid risk o…
dehall Sep 9, 2026
293f03a
Power operator always returns Decimal
dehall Sep 9, 2026
b3555d2
remove/clarify calls to Quantity arithmetic
dehall Sep 9, 2026
7511707
regenerate spec-tests with latest
dehall Sep 9, 2026
986e48d
replace limitDecimalPrecision with finalizeNumericResult
dehall Sep 9, 2026
c2befb6
update interval Expand with latest understanding
dehall Sep 9, 2026
31f1439
light cleanup
dehall Sep 9, 2026
d849e96
update equalDecimal to take all numeric types + string
dehall Sep 9, 2026
708ec6a
update tests to be explicit about decimal scale where necessary
dehall Sep 9, 2026
945238b
update comment to point to reported issue
dehall Sep 9, 2026
9e20582
rework Decimal tests to cover all methods
dehall Sep 9, 2026
4e5c81f
remove redundant Decimal.from() in should.equalDecimal calls
dehall Sep 9, 2026
4a7f549
bump dependency, add csv-parse override to pass npm audit
dehall Sep 9, 2026
b3a28d8
quick test cleanup
dehall Sep 9, 2026
f189a0d
add number type option to Date.getDateTime parameter
dehall Sep 9, 2026
405bd94
make edge case tests more clearly edge cases
dehall Sep 9, 2026
603a5c8
create Decimal.truncatedDivideBy to ensure result is not rounded prio…
dehall Sep 10, 2026
0cf5efd
Add Decimal.truncateToBigInt as Long counterpart to Integer Decimal.t…
dehall Sep 10, 2026
802d918
Add Decimal.nthRoot to try to preserve exact values where possible
dehall Sep 10, 2026
e81f5c0
Make Decimal.round parameter optional; unspecified or null = 0
dehall Sep 10, 2026
2b1e025
Make sure aggregate operations like Mode, Except, etc, use value equa…
dehall Sep 10, 2026
7615e7f
More interval expand examples
dehall Sep 10, 2026
991acc5
remove Quantity do(+,-,/) helper functions. Point existing tests at M…
dehall Sep 11, 2026
7574ce1
simplify units logic, remove division factor
dehall Sep 11, 2026
8d7354a
remove Decimal.truncated, export TRUNCATE_TO_PRECISION rounding mode
dehall Sep 15, 2026
06e8c84
update cql-tests to the latest
dehall Sep 15, 2026
2a4927b
Return only one value from Mode, even in case of ties
dehall Sep 15, 2026
04891c8
update skip-list per feedback
dehall Sep 15, 2026
fc2f1dd
add more invalid-scale tests
dehall Sep 15, 2026
8abf173
add tests for testing equals vs equivalence semantics in list operations
dehall Sep 15, 2026
5d325a8
prettier
dehall Sep 15, 2026
3dbc096
add note explaining decimal scale logic is based on testing, rather t…
dehall Sep 15, 2026
a061f94
update package-lock for example projects
dehall Sep 15, 2026
02952ae
Regenerate lockfiles one more time
dehall Sep 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/browser/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions examples/node/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions examples/typescript/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

671 changes: 366 additions & 305 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,15 @@
},
"dependencies": {
"@lhncbc/ucum-lhc": "^7.1.9",
"decimal.js": "^10.6.0",
"immutable": "^5.1.6",
"luxon": "^3.7.2"
},
"overrides": {
"mocha": {
"serialize-javascript": "^7.0.3"
}
},
"csv-parse": "^7.0.2"
},
"main": "lib/cql",
"types": "lib/cql.d.ts",
Expand Down
3 changes: 3 additions & 0 deletions src/cql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
Concept,
Date,
DateTime,
Decimal,
Interval,
Quantity,
Ratio,
Expand Down Expand Up @@ -54,6 +55,7 @@ export {
Concept,
Date,
DateTime,
Decimal,
Interval,
Quantity,
Ratio,
Expand Down Expand Up @@ -81,6 +83,7 @@ export default {
Concept,
Date,
DateTime,
Decimal,
Interval,
Quantity,
Ratio,
Expand Down
1 change: 1 addition & 0 deletions src/datatypes/datatypes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './bigint';
export * from './decimal';
export * from './logic';
export * from './clinical';
export * from './uncertainty';
Expand Down
47 changes: 31 additions & 16 deletions src/datatypes/datetime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {
MIN_DATETIME_VALUE_STRING,
MIN_TIME_VALUE_STRING
} from '../util/limits';
import { Decimal } from './decimal';
import { equals } from '../util/comparison';

// It's easiest and most performant to organize formats by length of the supported strings.
// This way we can test strings only against the formats that have a chance of working.
Expand Down Expand Up @@ -529,7 +531,7 @@ export class DateTime extends AbstractDate {
minute: number | null;
second: number | null;
millisecond: number | null;
timezoneOffset: number | null;
timezoneOffset: Decimal | null;

static readonly Unit = {
YEAR: 'year',
Expand Down Expand Up @@ -599,13 +601,19 @@ export class DateTime extends AbstractDate {
}

// TODO: Note: using the jsDate type causes issues, fix later
static fromJSDate(date: any, timezoneOffset?: any) {
static fromJSDate(date: any, timezoneOffset?: number | string | Decimal) {
//This is from a JS Date, not a CQL Date
if (date instanceof DateTime) {
return date;
}
if (timezoneOffset != null) {
date = new jsDate(date.getTime() + timezoneOffset * 60 * 60 * 1000);
let tzOffset: number;
if (timezoneOffset instanceof Decimal) {
tzOffset = timezoneOffset.toNumber();
} else {
tzOffset = +timezoneOffset;
}
date = new jsDate(date.getTime() + tzOffset * 60 * 60 * 1000);
return new DateTime(
date.getUTCFullYear(),
date.getUTCMonth() + 1,
Expand All @@ -614,7 +622,7 @@ export class DateTime extends AbstractDate {
date.getUTCMinutes(),
date.getUTCSeconds(),
date.getUTCMilliseconds(),
timezoneOffset
tzOffset
);
} else {
return new DateTime(
Expand All @@ -641,7 +649,7 @@ export class DateTime extends AbstractDate {
luxonDT.minute,
luxonDT.second,
luxonDT.millisecond,
luxonDT.offset / 60
Decimal.from(luxonDT.offset / 60)
);
}

Expand All @@ -653,7 +661,7 @@ export class DateTime extends AbstractDate {
minute: number | null = null,
second: number | null = null,
millisecond: number | null = null,
timezoneOffset?: number | null
timezoneOffset?: Decimal | number | null
) {
// from the spec: If no timezone is specified, the timezone of the evaluation request timestamp is used.
// NOTE: timezoneOffset will be explicitly null for the Time overload, whereas
Expand All @@ -664,9 +672,11 @@ export class DateTime extends AbstractDate {
this.second = second;
this.millisecond = millisecond;
if (timezoneOffset === undefined) {
this.timezoneOffset = (new jsDate().getTimezoneOffset() / 60) * -1;
this.timezoneOffset = Decimal.from((new jsDate().getTimezoneOffset() / 60) * -1);
} else if (timezoneOffset === null) {
this.timezoneOffset = null;
Comment thread
cmoesel marked this conversation as resolved.
} else {
this.timezoneOffset = timezoneOffset;
this.timezoneOffset = Decimal.from(timezoneOffset);
}
}

Expand Down Expand Up @@ -819,7 +829,7 @@ export class DateTime extends AbstractDate {

isUTC() {
// A timezoneOffset of 0 indicates UTC time.
return !this.timezoneOffset;
return this.timezoneOffset?.equals(0);
}

getPrecision() {
Expand Down Expand Up @@ -869,7 +879,7 @@ export class DateTime extends AbstractDate {
toLuxonDateTime() {
const offsetMins =
this.timezoneOffset != null
? this.timezoneOffset * 60
? this.timezoneOffset.toNumber() * 60
: new jsDate().getTimezoneOffset() * -1;
return LuxonDateTime.fromObject(
{
Expand Down Expand Up @@ -958,10 +968,11 @@ export class DateTime extends AbstractDate {
}

if (str.indexOf('T') !== -1 && this.timezoneOffset != null) {
str += this.timezoneOffset < 0 ? '-' : '+';
const offsetHours = Math.floor(Math.abs(this.timezoneOffset));
const tzOffset = this.timezoneOffset.toNumber();
str += tzOffset < 0 ? '-' : '+';
const offsetHours = Math.floor(Math.abs(tzOffset));
str += String(offsetHours).padStart(2, '0');
const offsetMin = (Math.abs(this.timezoneOffset) - offsetHours) * 60;
const offsetMin = (Math.abs(tzOffset) - offsetHours) * 60;
str += ':' + String(offsetMin).padStart(2, '0');
}

Expand Down Expand Up @@ -1202,7 +1213,7 @@ export class Date extends AbstractDate {
return str;
}

getDateTime(timeZoneOffset?: number | null) {
getDateTime(timeZoneOffset?: Decimal | number | null) {
// from the spec: the result will be a DateTime with the time components unspecified,
// except for the timezone offset, which will be set to the timezone offset of the evaluation
// request timestamp. (this last part is achieved by passing in the timeZoneOffset from the context)
Expand Down Expand Up @@ -1264,7 +1275,11 @@ function compareWithDefaultResult(a: any, b: any, defaultResult: any) {
}

// make a copy of other in the correct timezone offset if they don't match.
if (a.timezoneOffset !== b.timezoneOffset) {
const differentTZ =
a.timeZoneOffset == null
? b.timezoneOffset != null
: !a.timezoneOffset.equals(b.timezoneOffset);
if (differentTZ) {
b = b.convertToTimezoneOffset(a.timezoneOffset);
}

Expand All @@ -1286,7 +1301,7 @@ function compareWithDefaultResult(a: any, b: any, defaultResult: any) {
}

// if they are different then return with false
if (a[field] !== b[field]) {
if (!equals(a[field], b[field])) {
return false;
}

Expand Down
Loading
Loading