diff --git a/src/datatypes/datetime.ts b/src/datatypes/datetime.ts index 335b0ef8..c066c517 100644 --- a/src/datatypes/datetime.ts +++ b/src/datatypes/datetime.ts @@ -289,7 +289,7 @@ export class DateTime extends AbstractDate { DateTime.Unit.MILLISECOND ]; - static parse(string: any) { + static parse(string: any, defaultTimezoneOffset?: Decimal | number | null) { if (string === null) { return null; } @@ -330,6 +330,8 @@ export class DateTime extends AbstractDate { args.push(matches[17] === '+' ? num : num * -1); } else if (matches[15] === 'Z') { args.push(0); + } else if (defaultTimezoneOffset != null) { + args.push(Decimal.from(defaultTimezoneOffset).toNumber()); } // @ts-ignore return new DateTime(...args); diff --git a/src/elm/type.ts b/src/elm/type.ts index ae95006b..cfbcc123 100644 --- a/src/elm/type.ts +++ b/src/elm/type.ts @@ -162,7 +162,7 @@ export class ToDateTime extends Expression { const timezoneOffset = ctx.getExecutionDateTime().timezoneOffset; return arg.getDateTime(timezoneOffset); } else { - return DateTime.parse(arg.toString()); + return DateTime.parse(arg.toString(), ctx.getExecutionDateTime().timezoneOffset); } } } diff --git a/test/datatypes/datetime-test.ts b/test/datatypes/datetime-test.ts index 85dea5ce..ebd62f11 100644 --- a/test/datatypes/datetime-test.ts +++ b/test/datatypes/datetime-test.ts @@ -146,6 +146,20 @@ describe('DateTime', () => { d.should.eql(new DateTime(2012, 10, 25, 12, 55, 14, 953, 0)); }); + it('should use the default timezone offset when the string has none', () => { + // use a quarter-hour offset that no real-world timezone has, so it can + // never coincide with the host machine's offset + const d = DateTime.parse('2012-10-25T12:55', -3.25); + d.timezoneOffset.should.equalDecimal(-3.25); + }); + + it('should ignore the default timezone offset when the string specifies one', () => { + let d = DateTime.parse('2012-10-25T12:55+05:30', -3.25); + d.timezoneOffset.should.equalDecimal(5.5); + d = DateTime.parse('2012-10-25T12:55Z', -3.25); + d.timezoneOffset.should.equalDecimal(0); + }); + it('should toString yyyy', () => { const d = new DateTime(2012); d.toString().should.eql('2012'); diff --git a/test/elm/convert/convert-test.ts b/test/elm/convert/convert-test.ts index ec396ae6..0c9134bc 100644 --- a/test/elm/convert/convert-test.ts +++ b/test/elm/convert/convert-test.ts @@ -6,6 +6,7 @@ import { DateTime } from '../../../src/datatypes/datetime'; import { Quantity } from '../../../src/datatypes/quantity'; import { Uncertainty } from '../../../src/datatypes/uncertainty'; import { Decimal } from '../../../src/datatypes/decimal'; +import { PatientContext } from '../../../src/runtime/context'; describe('FromString', () => { beforeEach(function () { @@ -92,6 +93,21 @@ describe('FromString', () => { date.isDateTime.should.equal(true); }); + it("should convert '2015-01-02' to DateTime using the execution timezone offset", async function () { + this.ctx = new PatientContext( + this.ctx.library, + this.ctx.patient, + this.ctx.codeService, + this.ctx.parameters, + // use a quarter-hour offset that no real-world timezone has, so it can + // never coincide with the host machine's offset + DateTime.fromJSDate(new Date(), -3.25) + ); + const date = await this.dateTimeStr.exec(this.ctx); + date.timezoneOffset.should.equalDecimal(-3.25); + date.isDateTime.should.equal(true); + }); + it("should convert '2015-01-02' to Date", async function () { const date = await this.dateStr.exec(this.ctx); date.year.should.equal(2015); diff --git a/test/spec-tests/cql/CqlTypeOperatorsTest.cql b/test/spec-tests/cql/CqlTypeOperatorsTest.cql index 4ae19f7c..71f04973 100644 --- a/test/spec-tests/cql/CqlTypeOperatorsTest.cql +++ b/test/spec-tests/cql/CqlTypeOperatorsTest.cql @@ -31,11 +31,9 @@ define "Convert": Tuple{ output: null }, "StringToDateTime": Tuple{ - skipped: 'Wrong answer (different offsets)' - /* expression: convert '2014-01-01' to DateTime, output: @2014-01-01T - */ }, + }, "StringToTime": Tuple{ expression: convert 'T14:30:00.0' to Time, output: @T14:30:00.000 @@ -79,23 +77,17 @@ define "ToConcept": Tuple{ define "ToDateTime": Tuple{ "ToDateTime1": Tuple{ - skipped: 'Wrong answer (different offsets)' - /* expression: ToDateTime('2014-01-01'), output: @2014-01-01T - */ }, + }, "ToDateTime2": Tuple{ - skipped: 'Wrong answer (different offsets)' - /* expression: ToDateTime('2014-01-01T12:05'), output: @2014-01-01T12:05 - */ }, + }, "ToDateTime3": Tuple{ - skipped: 'Wrong answer (different offsets)' - /* expression: ToDateTime('2014-01-01T12:05:05.955'), output: @2014-01-01T12:05:05.955 - */ }, + }, "ToDateTime4": Tuple{ expression: ToDateTime('2014-01-01T12:05:05.955+01:30'), output: @2014-01-01T12:05:05.955+01:30 diff --git a/test/spec-tests/cql/CqlTypeOperatorsTest.json b/test/spec-tests/cql/CqlTypeOperatorsTest.json index 68578b7b..8bfc3445 100644 --- a/test/spec-tests/cql/CqlTypeOperatorsTest.json +++ b/test/spec-tests/cql/CqlTypeOperatorsTest.json @@ -589,11 +589,20 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", + "name": "{urn:hl7-org:elm-types:r1}DateTime", + "annotation": [] + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}DateTime", "annotation": [] } } @@ -757,11 +766,20 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", + "name": "{urn:hl7-org:elm-types:r1}DateTime", + "annotation": [] + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}DateTime", "annotation": [] } } @@ -1012,11 +1030,20 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", + "name": "{urn:hl7-org:elm-types:r1}DateTime", + "annotation": [] + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}DateTime", "annotation": [] } } @@ -1024,13 +1051,46 @@ }, "element": [ { - "name": "skipped", + "name": "expression", "value": { - "type": "Literal", - "resultTypeName": "{urn:hl7-org:elm-types:r1}String", - "valueType": "{urn:hl7-org:elm-types:r1}String", - "value": "Wrong answer (different offsets)", - "annotation": [] + "type": "ToDateTime", + "resultTypeName": "{urn:hl7-org:elm-types:r1}DateTime", + "annotation": [], + "signature": [], + "operand": { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}String", + "valueType": "{urn:hl7-org:elm-types:r1}String", + "value": "2014-01-01", + "annotation": [] + } + } + }, + { + "name": "output", + "value": { + "type": "DateTime", + "resultTypeName": "{urn:hl7-org:elm-types:r1}DateTime", + "annotation": [], + "signature": [], + "year": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "2014", + "annotation": [] + }, + "month": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + }, + "day": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + } } } ] @@ -1909,11 +1969,20 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", + "name": "{urn:hl7-org:elm-types:r1}DateTime", + "annotation": [] + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}DateTime", "annotation": [] } } @@ -1928,11 +1997,20 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", + "name": "{urn:hl7-org:elm-types:r1}DateTime", + "annotation": [] + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}DateTime", "annotation": [] } } @@ -1947,11 +2025,20 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", + "name": "{urn:hl7-org:elm-types:r1}DateTime", + "annotation": [] + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}DateTime", "annotation": [] } } @@ -2143,11 +2230,20 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", + "name": "{urn:hl7-org:elm-types:r1}DateTime", + "annotation": [] + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}DateTime", "annotation": [] } } @@ -2162,11 +2258,20 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", + "name": "{urn:hl7-org:elm-types:r1}DateTime", + "annotation": [] + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}DateTime", "annotation": [] } } @@ -2181,11 +2286,20 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", + "name": "{urn:hl7-org:elm-types:r1}DateTime", + "annotation": [] + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}DateTime", "annotation": [] } } @@ -2373,11 +2487,20 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", + "name": "{urn:hl7-org:elm-types:r1}DateTime", + "annotation": [] + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}DateTime", "annotation": [] } } @@ -2385,13 +2508,46 @@ }, "element": [ { - "name": "skipped", + "name": "expression", "value": { - "type": "Literal", - "resultTypeName": "{urn:hl7-org:elm-types:r1}String", - "valueType": "{urn:hl7-org:elm-types:r1}String", - "value": "Wrong answer (different offsets)", - "annotation": [] + "type": "ToDateTime", + "resultTypeName": "{urn:hl7-org:elm-types:r1}DateTime", + "annotation": [], + "signature": [], + "operand": { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}String", + "valueType": "{urn:hl7-org:elm-types:r1}String", + "value": "2014-01-01", + "annotation": [] + } + } + }, + { + "name": "output", + "value": { + "type": "DateTime", + "resultTypeName": "{urn:hl7-org:elm-types:r1}DateTime", + "annotation": [], + "signature": [], + "year": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "2014", + "annotation": [] + }, + "month": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + }, + "day": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + } } } ] @@ -2407,11 +2563,20 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", + "name": "{urn:hl7-org:elm-types:r1}DateTime", + "annotation": [] + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}DateTime", "annotation": [] } } @@ -2419,13 +2584,58 @@ }, "element": [ { - "name": "skipped", + "name": "expression", "value": { - "type": "Literal", - "resultTypeName": "{urn:hl7-org:elm-types:r1}String", - "valueType": "{urn:hl7-org:elm-types:r1}String", - "value": "Wrong answer (different offsets)", - "annotation": [] + "type": "ToDateTime", + "resultTypeName": "{urn:hl7-org:elm-types:r1}DateTime", + "annotation": [], + "signature": [], + "operand": { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}String", + "valueType": "{urn:hl7-org:elm-types:r1}String", + "value": "2014-01-01T12:05", + "annotation": [] + } + } + }, + { + "name": "output", + "value": { + "type": "DateTime", + "resultTypeName": "{urn:hl7-org:elm-types:r1}DateTime", + "annotation": [], + "signature": [], + "year": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "2014", + "annotation": [] + }, + "month": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + }, + "day": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + }, + "hour": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "12", + "annotation": [] + }, + "minute": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "5", + "annotation": [] + } } } ] @@ -2441,11 +2651,20 @@ "annotation": [], "element": [ { - "name": "skipped", + "name": "expression", "annotation": [], "elementType": { "type": "NamedTypeSpecifier", - "name": "{urn:hl7-org:elm-types:r1}String", + "name": "{urn:hl7-org:elm-types:r1}DateTime", + "annotation": [] + } + }, + { + "name": "output", + "annotation": [], + "elementType": { + "type": "NamedTypeSpecifier", + "name": "{urn:hl7-org:elm-types:r1}DateTime", "annotation": [] } } @@ -2453,13 +2672,70 @@ }, "element": [ { - "name": "skipped", + "name": "expression", + "value": { + "type": "ToDateTime", + "resultTypeName": "{urn:hl7-org:elm-types:r1}DateTime", + "annotation": [], + "signature": [], + "operand": { + "type": "Literal", + "resultTypeName": "{urn:hl7-org:elm-types:r1}String", + "valueType": "{urn:hl7-org:elm-types:r1}String", + "value": "2014-01-01T12:05:05.955", + "annotation": [] + } + } + }, + { + "name": "output", "value": { - "type": "Literal", - "resultTypeName": "{urn:hl7-org:elm-types:r1}String", - "valueType": "{urn:hl7-org:elm-types:r1}String", - "value": "Wrong answer (different offsets)", - "annotation": [] + "type": "DateTime", + "resultTypeName": "{urn:hl7-org:elm-types:r1}DateTime", + "annotation": [], + "signature": [], + "year": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "2014", + "annotation": [] + }, + "month": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + }, + "day": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "1", + "annotation": [] + }, + "hour": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "12", + "annotation": [] + }, + "minute": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "5", + "annotation": [] + }, + "second": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "5", + "annotation": [] + }, + "millisecond": { + "type": "Literal", + "valueType": "{urn:hl7-org:elm-types:r1}Integer", + "value": "955", + "annotation": [] + } } } ] diff --git a/test/spec-tests/skip-list.txt b/test/spec-tests/skip-list.txt index c6fcfa92..f8fb8bac 100644 --- a/test/spec-tests/skip-list.txt +++ b/test/spec-tests/skip-list.txt @@ -71,10 +71,6 @@ CqlComparisonOperatorsTest.Equal.DateTimeEqNull Wrong answer (tr CqlIntervalOperatorsTest.Collapse.TestCollapseNull Wrong answer (Interval(null, null) vs null) CqlIntervalOperatorsTest.Expand.ExpandListWithNull Wrong answer (should be empty list due to removing nulls) -CqlTypeOperatorsTest.Convert.StringToDateTime Wrong answer (different offsets) -CqlTypeOperatorsTest.ToDateTime.ToDateTime1 Wrong answer (different offsets) -CqlTypeOperatorsTest.ToDateTime.ToDateTime2 Wrong answer (different offsets) -CqlTypeOperatorsTest.ToDateTime.ToDateTime3 Wrong answer (different offsets) ValueLiteralsAndSelectors.Decimal.Decimal10Pow28ToZeroOneStepDecimalMaxValue Wrong answer (null vs big number); intermediate value exceeds max Decimal ValueLiteralsAndSelectors.Decimal.DecimalPos10Pow28ToZeroOneStepDecimalMaxValue Wrong answer (null vs big number); intermediate value exceeds max Decimal