Skip to content

Commit

Permalink
[time] Default to system timezone in toZDT if none is explicitely s…
Browse files Browse the repository at this point in the history
…et (#307)

Fixes #306.

During DST changes, any ISO8601 String or Item or DateTimeType or Java ZonedDateTime
that is parsed by `time.toZDT()` lack the ZoneId.
However, it is this ZoneId which tells Joda when DST changes occur so when
`toToday()` is called, the time is not adjusted to account for DST.

This defaults to the system timezone if no timezone is explicitely set.

----

Also-by: Florian Hotze <[email protected]>
Signed-off-by: Richard Koshak <[email protected]>
  • Loading branch information
rkoshak authored Dec 2, 2023
1 parent 2bab261 commit 1f325bd
Show file tree
Hide file tree
Showing 7 changed files with 51 additions and 15 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,7 @@ The following rules are used during the conversion:
| `"kk:mm[:ss][ ]a"` (12 hour time) | today's date with the time indicated, the space between the time and am/pm and seconds are optional | `time.toZDT('1:23:45 PM');` |
| [ISO 8601 Duration](https://en.wikipedia.org/wiki/ISO_8601#Durations) String | added to `now` | `time.toZDT('PT1H4M6.789S');` |
If no time zone is explicitly set, the system default time zone is used.
When a type or string that cannot be handled is encountered, an error is thrown.
#### `toToday()`
Expand Down
21 changes: 15 additions & 6 deletions test/time.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const time = require('../time');
const { ZonedDateTime } = require('@js-joda/core');
const { ZonedDateTime, ZoneId } = require('@js-joda/core');

jest.mock('../items', () => ({
Item: new Object() // eslint-disable-line no-new-object
Expand Down Expand Up @@ -67,13 +67,9 @@ describe('time.js', () => {
});
});

describe('parses ISO DateTime with zone offset and/or time/zone', () => {
describe('parses ISO DateTime with zone offset and timezone', () => {
it.each([
['YYYY-MM-DDThh:mm:ss.f+HH:mm[SYSTEM]', '2016-03-18T12:38:23.561+01:00[SYSTEM]'],
['YYYY-MM-DDThh:mm:ss.f+HH:mm', '2016-03-18T12:38:23.561+01:00'],
['YYYY-MM-DDThh:mm:ss.f-HH:mm', '2016-03-18T12:38:23.56-04:30'],
['YYYY-MM-DDThh:mm:ss.f+HHmm', '2016-03-18T12:38:23.561+0100'],
['YYYY-MM-DDThh:mm:ss.f-HHmm', '2016-03-18T12:38:23.561-0430'],
['YYYY-MM-DDThh:mm:ssZ', '2022-12-24T18:30:35Z'],
['YYYY-MM-DDThh:mm:ss+HH:mm[timezone]', '2017-02-04T17:01:15.846+01:00[Europe/Paris]'],
['YYYY-MM-DDThh:mm:ss+HH:mm[timezone]', '2016-03-18T06:38:23.561-05:00[UTC-05:00]']
Expand All @@ -82,6 +78,19 @@ describe('time.js', () => {
});
});

describe('parses ISO DateTime with zone offset and without time zone and defaults to SYSTEM timezone', () => {
it.each([
['YYYY-MM-DDThh:mm:ss.f+HH:mm', '2016-03-18T12:38:23.561+01:00'],
['YYYY-MM-DDThh:mm:ss.f-HH:mm', '2016-03-18T12:38:23.56-04:30'],
['YYYY-MM-DDThh:mm:ss.f+HHmm', '2016-03-18T12:38:23.561+0100'],
['YYYY-MM-DDThh:mm:ss.f-HHmm', '2016-03-18T12:38:23.561-0430']
])('accepts correct pattern %s', (pattern, isoStr) => {
const zdt = parseISO8601(isoStr);
expect(zdt).toBeInstanceOf(ZonedDateTime);
expect(zdt.zone()).toBe(ZoneId.SYSTEM);
});
});

describe('rejects wrong ISO pattern', () => {
it.each([
['hh:mm:ss,f', '18:00:00,4654'],
Expand Down
12 changes: 7 additions & 5 deletions time.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,15 @@ function _parseISO8601 (isoStr) {
LOCAL_DATE: /^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])$/, // YYYY-MM-DD
LOCAL_TIME: /^\d{2}:\d{2}(:\d{2})?(\.\d+)?$/, // hh:mm or hh:mm:ss or hh:mm:ss.f
LOCAL_DATE_TIME: /^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T\d{2}:\d{2}(:\d{2})?(\.\d+)?$/, // LOCAL_DATE and LOCAL_TIME connected with "T"
ISO_8160_FULL: /^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9])(:[0-5][0-9])?(\.\d+)?(Z|[+-]\d{2}(:\d{2})?)/
ISO_8601_FULL: /^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9])(:[0-5][0-9])?(\.\d+)?(Z|[+-]\d{2}(:\d{2})?\[.*\])/, // with Zone ID
ISO_8601_OFFSET: /^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9])(:[0-5][0-9])?(\.\d+)?(Z|[+-]\d{2}(:\d{2})$)/ // offset only
};
switch (true) {
case REGEX.LOCAL_DATE.test(isoStr): return time.ZonedDateTime.of(time.LocalDate.parse(isoStr), time.LocalTime.MIDNIGHT, time.ZoneId.SYSTEM);
case REGEX.LOCAL_TIME.test(isoStr): return time.ZonedDateTime.of(time.LocalDate.now(), time.LocalTime.parse(isoStr), time.ZoneId.SYSTEM);
case REGEX.LOCAL_DATE_TIME.test(isoStr): return time.ZonedDateTime.of(time.LocalDateTime.parse(isoStr), time.ZoneId.SYSTEM);
case REGEX.ISO_8160_FULL.test(isoStr): return time.ZonedDateTime.parse(isoStr);
case REGEX.ISO_8601_FULL.test(isoStr): return time.ZonedDateTime.parse(isoStr);
case REGEX.ISO_8601_OFFSET.test(isoStr): return time.ZonedDateTime.parse(isoStr).withZoneSameLocal(time.ZoneId.SYSTEM);
}
return null;
}
Expand Down Expand Up @@ -165,7 +167,7 @@ function _convertItem (item) {
} else if (item.rawState instanceof StringType) { // String type Items
return _parseString(item.state);
} else if (item.rawState instanceof DateTimeType) { // DateTime Items
return utils.javaZDTToJsZDT(item.rawState.getZonedDateTime());
return utils.javaZDTToJsZDTWithDefaultZoneSystem(item.rawState.getZonedDateTime());
} else if (item.rawState instanceof QuantityType) { // Number:Time type Items
return _addQuantityType(item.rawState);
} else {
Expand Down Expand Up @@ -230,12 +232,12 @@ function toZDT (when) {

// Java ZDT
if (when instanceof javaZDT) {
return utils.javaZDTToJsZDT(when);
return utils.javaZDTToJsZDTWithDefaultZoneSystem(when);
}

// DateTimeType, extract the javaZDT and convert to time.ZDT
if (when instanceof DateTimeType) {
return utils.javaZDTToJsZDT(when.getZonedDateTime());
return utils.javaZDTToJsZDTWithDefaultZoneSystem(when.getZonedDateTime());
}

// Quantity
Expand Down
2 changes: 1 addition & 1 deletion types/time.d.ts.map

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

10 changes: 9 additions & 1 deletion types/utils.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,21 @@ export function isJsInstanceOfJava(instance: any, type: JavaClass): boolean;
*/
export function javaInstantToJsInstant(instant: JavaInstant): time.Instant;
/**
* Convert Java ZonedDateTime to JS-Joda.
* Convert Java ZonedDateTime to JS-Joda ZonedDateTime.
*
* @memberOf utils
* @param {JavaZonedDateTime} zdt {@link https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/ZonedDateTime.html java.time.ZonedDateTime}
* @returns {time.ZonedDateTime} {@link https://js-joda.github.io/js-joda/class/packages/core/src/ZonedDateTime.js~ZonedDateTime.html JS-Joda ZonedDateTime}
*/
export function javaZDTToJsZDT(zdt: JavaZonedDateTime): time.ZonedDateTime;
/**
* Convert Java ZonedDateTime to JS-Joda ZonedDateTime and default to SYSTEM timezone if not explicitly set.
*
* @memberOf utils
* @param {JavaZonedDateTime} zdt {@link https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/ZonedDateTime.html java.time.ZonedDateTime}
* @returns {time.ZonedDateTime} {@link https://js-joda.github.io/js-joda/class/packages/core/src/ZonedDateTime.js~ZonedDateTime.html JS-Joda ZonedDateTime}
*/
export function javaZDTToJsZDTWithDefaultZoneSystem(zdt: JavaZonedDateTime): time.ZonedDateTime;
/**
* openHAB JavaScript library version
*
Expand Down
2 changes: 1 addition & 1 deletion types/utils.d.ts.map

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

18 changes: 17 additions & 1 deletion utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ function javaInstantToJsInstant (instant) {
}

/**
* Convert Java ZonedDateTime to JS-Joda.
* Convert Java ZonedDateTime to JS-Joda ZonedDateTime.
*
* @memberOf utils
* @param {JavaZonedDateTime} zdt {@link https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/ZonedDateTime.html java.time.ZonedDateTime}
Expand All @@ -240,6 +240,21 @@ function javaZDTToJsZDT (zdt) {
return time.ZonedDateTime.ofInstant(instant, zone);
}

/**
* Convert Java ZonedDateTime to JS-Joda ZonedDateTime and default to SYSTEM timezone if not explicitly set.
*
* @memberOf utils
* @param {JavaZonedDateTime} zdt {@link https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/ZonedDateTime.html java.time.ZonedDateTime}
* @returns {time.ZonedDateTime} {@link https://js-joda.github.io/js-joda/class/packages/core/src/ZonedDateTime.js~ZonedDateTime.html JS-Joda ZonedDateTime}
*/
function javaZDTToJsZDTWithDefaultZoneSystem (zdt) {
const jsZDT = javaZDTToJsZDT(zdt);
if (/^[+-]*/.test(jsZDT.zone().toString())) {
return jsZDT.withZoneSameLocal(time.ZoneId.SYSTEM);
}
return jsZDT;
}

module.exports = {
jsSetToJavaSet,
jsArrayToJavaSet,
Expand All @@ -254,5 +269,6 @@ module.exports = {
isJsInstanceOfJava,
javaInstantToJsInstant,
javaZDTToJsZDT,
javaZDTToJsZDTWithDefaultZoneSystem,
OPENHAB_JS_VERSION: VERSION
};

0 comments on commit 1f325bd

Please sign in to comment.