diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..121531a --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +*.min.js diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..a78a01e --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "printWidth": 80, + "tabWidth": 2, + "singleQuote": false, + "bracketSpacing": true, + "semi": true +} diff --git a/README.md b/README.md index d532eb1..fe645fe 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ # [xtz.js](https://github.com/therootcompany/tz.js) -A fast, lightweight, zero-dependency library to -translate between Time Zones and UTC with native -`Intl.DateTimeFormat` in ~100 LoC. For Node.js & Browsers. +A fast, lightweight, zero-dependency library to translate between Time Zones and UTC with native `Intl.DateTimeFormat` +in ~100 LoC. For Node.js & Browsers. XTZ is a poor man's Temporal polyfill, but just for time zones. @@ -50,14 +49,14 @@ utcDate.toISOString(); # Features -- [x] Translate a UTC time to a Time Zone -- [x] Translate a Zoned time to UTC -- [x] Handles **Daylight Savings**, Weird Time Zones, etc... - - [x] Well-tested `npm run test` -- [x] Lightweight (No deps) - - 5kb Source + Comments - - 2.5kb Minified - - <1kb `gzip`d +- [x] Translate a UTC time to a Time Zone +- [x] Translate a Zoned time to UTC +- [x] Handles **Daylight Savings**, Weird Time Zones, etc... + - [x] Well-tested `npm run test` +- [x] Lightweight (No deps) + - 5kb Source + Comments + - 2.5kb Minified + - <1kb `gzip`d Compatible with Browsers, and Node.js. @@ -83,10 +82,10 @@ var TZ = require("xtz"); # API -- `toTimeZone(utcDate, timeZone)` -- `toTimeZoneISOString(isoString, timeZone)` -- `toUTC(dtString, timeZone)` -- `toUTCISOString(dtString, timeZone)` +- `toTimeZone(utcDate, timeZone)` +- `toTimeZoneISOString(isoString, timeZone)` +- `toUTC(dtString, timeZone)` +- `toUTCISOString(dtString, timeZone)` ## `toTimeZone(utcDate, timeZone)` @@ -115,8 +114,8 @@ You can also use a date object with an absolute UTC time: ```js var tzDate = TZ.toTimeZone( - new Date("2021-11-07T08:15:59.000Z"), - "America/New_York" + new Date("2021-11-07T08:15:59.000Z"), + "America/New_York" ); ``` @@ -155,12 +154,13 @@ Or our bespoke date object: var utcDate = TZ.toUTC("2021-11-07 03:15:59.000", "America/New_York"); ``` -You can also use a date object as the source time, but the date's UTC time will be treated as **_relative to time zone_** rather than absolute (this is a workaround for JavaScript's lack of bi-directional timezone support). +You can also use a date object as the source time, but the date's UTC time will be treated as **_relative to time +zone_** rather than absolute (this is a workaround for JavaScript's lack of bi-directional timezone support). ```js var utcDate = TZ.toUTC( - new Date("2021-11-07T03:15:59.000Z"), - "America/New_York" + new Date("2021-11-07T03:15:59.000Z"), + "America/New_York" ); ``` @@ -173,30 +173,32 @@ utcDate.toISOString(); > In 2021 Daylight Savings (in the US) > -> - begins at 2am on March 14th -> - ends at 2am on November 7th +> - begins at 2am on March 14th +> - ends at 2am on November 7th > > See . Q: What happens in March when 2am is skipped? -- A: Although 2am is not a valid time, rather than throwing an error this library will resolve to 1am instead, which is an hour early in real ("tick-tock" or "monotonic") time. - ```js - var utcDate = TZ.toUTC("2021-03-14 02:15:59.000", "America/New_York"); - utcDate.toISOString(); - // "2021-03-14T02:15:59.000-0400" - // (same as "2021-03-14T01:15:59.000-0500") - ``` +- A: Although 2am is not a valid time, rather than throwing an error this library will resolve to 1am instead, which + is an hour early in real ("tick-tock" or "monotonic") time. + ```js + var utcDate = TZ.toUTC("2021-03-14 02:15:59.000", "America/New_York"); + utcDate.toISOString(); + // "2021-03-14T02:15:59.000-0400" + // (same as "2021-03-14T01:15:59.000-0500") + ``` Q: What happens in November when 2am happens twice? -- A: Although both 2ams are distinguishable with ISO offset times, only the first can be resolved from a local time with this library. - ```js - var utcDate = TZ.toUTC("2021-11-07 01:15:59.000", "America/New_York"); - utcDate.toISOString(); - // "2021-11-07T01:15:59.000-0400", same as "2021-11-07T05:15:59.000Z" - // (an hour before the 2nd 2am at "2021-11-07T01:15:59.000-0500") - ``` +- A: Although both 2ams are distinguishable with ISO offset times, only the first can be resolved from a local time + with this library. + ```js + var utcDate = TZ.toUTC("2021-11-07 01:15:59.000", "America/New_York"); + utcDate.toISOString(); + // "2021-11-07T01:15:59.000-0400", same as "2021-11-07T05:15:59.000Z" + // (an hour before the 2nd 2am at "2021-11-07T01:15:59.000-0500") + ``` # List of Time Zones diff --git a/examples.js b/examples.js index 6fbaa0f..6ed7b43 100644 --- a/examples.js +++ b/examples.js @@ -1,89 +1,81 @@ var XTZ; (function () { - "use strict"; + "use strict"; - if (!XTZ) { - try { - XTZ = require("xtz"); - } catch (e) { - XTZ = require("./xtz.js"); - } + if (!XTZ) { + try { + XTZ = require("xtz"); + } catch (e) { + XTZ = require("./xtz.js"); } + } - var TZ = XTZ; - var tzDate; + var TZ = XTZ; + var tzDate; - // - // UTC-absolute time translated to a Time Zone - // - function demo1() { - console.info("What's the UTC equivalent of 8:15am in New York?"); - console.info(); + // + // UTC-absolute time translated to a Time Zone + // + function demo1() { + console.info("What's the UTC equivalent of 8:15am in New York?"); + console.info(); - console.info("\t// during daylight savings"); - console.info( - `\tXTZ.toUTC("2021-03-14 08:15:59.000", "America/New_York")` - ); - console.info(`\ttzDate.toISOString()`); - tzDate = XTZ.toUTC("2021-03-14 08:15:59.000", "America/New_York"); - console.info( - "\t" + tzDate.toISOString(), - "// same as", - new Date(tzDate.toISOString()).toISOString() - ); - console.info(); + console.info("\t// during daylight savings"); + console.info(`\tXTZ.toUTC("2021-03-14 08:15:59.000", "America/New_York")`); + console.info(`\ttzDate.toISOString()`); + tzDate = XTZ.toUTC("2021-03-14 08:15:59.000", "America/New_York"); + console.info( + "\t" + tzDate.toISOString(), + "// same as", + new Date(tzDate.toISOString()).toISOString() + ); + console.info(); - console.info("\t// during standard time"); - console.info( - `\tXTZ.toUTC("2021-11-07 08:15:59.000", "America/New_York")` - ); - console.info(`\ttzDate.toISOString()`); - tzDate = XTZ.toUTC("2021-11-07 08:15:59.000", "America/New_York"); - console.info( - "\t" + tzDate.toISOString(), - "// same as", - new Date(tzDate.toISOString()).toISOString() - ); - console.info(); - } + console.info("\t// during standard time"); + console.info(`\tXTZ.toUTC("2021-11-07 08:15:59.000", "America/New_York")`); + console.info(`\ttzDate.toISOString()`); + tzDate = XTZ.toUTC("2021-11-07 08:15:59.000", "America/New_York"); + console.info( + "\t" + tzDate.toISOString(), + "// same as", + new Date(tzDate.toISOString()).toISOString() + ); + console.info(); + } - // - // Time Zone-relative time translated to UTC - // - function demo2() { - console.info( - "What time is it in New York at 8:15am on March 14th UTC?" - ); - console.info(); + // + // Time Zone-relative time translated to UTC + // + function demo2() { + console.info("What time is it in New York at 8:15am on March 14th UTC?"); + console.info(); - console.info("\t// during daylight savings"); - console.info( - `\tXTZ.toTimeZone("2021-03-14T08:15:59.000Z", "America/New_York")` - ); - console.info(`\ttzDate.toISOString()`); - tzDate = XTZ.toTimeZone("2021-03-14T08:15:59.000Z", "America/New_York"); - console.info( - "\t" + tzDate.toISOString(), - "// same as", - new Date(tzDate.toISOString()).toISOString() - ); - console.info(); + console.info("\t// during daylight savings"); + console.info( + `\tXTZ.toTimeZone("2021-03-14T08:15:59.000Z", "America/New_York")` + ); + console.info(`\ttzDate.toISOString()`); + tzDate = XTZ.toTimeZone("2021-03-14T08:15:59.000Z", "America/New_York"); + console.info( + "\t" + tzDate.toISOString(), + "// same as", + new Date(tzDate.toISOString()).toISOString() + ); + console.info(); - console.info("\t// during standard time"); - console.info( - `\tXTZ.toUTC("2021-11-07T08:15:59.000Z", "America/New_York")` - ); - console.info(`\ttzDate.toISOString()`); - tzDate = XTZ.toUTC("2021-11-07T08:15:59.000Z", "America/New_York"); - console.info( - "\t" + tzDate.toISOString(), - "// same as", - new Date(tzDate.toISOString()).toISOString() - ); - console.info(); - } + console.info("\t// during standard time"); + console.info(`\tXTZ.toUTC("2021-11-07T08:15:59.000Z", "America/New_York")`); + console.info(`\ttzDate.toISOString()`); + tzDate = XTZ.toUTC("2021-11-07T08:15:59.000Z", "America/New_York"); + console.info( + "\t" + tzDate.toISOString(), + "// same as", + new Date(tzDate.toISOString()).toISOString() + ); + console.info(); + } - demo1(); - demo2(); + demo1(); + demo2(); })(); diff --git a/test.js b/test.js index a939353..acd4ea7 100644 --- a/test.js +++ b/test.js @@ -3,314 +3,314 @@ var TZ = require("./"); function testUtcToTz(t) { - var result = TZ.toTimeZone.apply(TZ, t.inputs).toISOString(); - if (t.result !== result) { - throw new Error( - `Invalid UTC to TZ conversion for ${t.desc}:\n` + - `\tExpected: ${t.result}\n` + - `\tActual: ${result}\n` - ); - } + var result = TZ.toTimeZone.apply(TZ, t.inputs).toISOString(); + if (t.result !== result) { + throw new Error( + `Invalid UTC to TZ conversion for ${t.desc}:\n` + + `\tExpected: ${t.result}\n` + + `\tActual: ${result}\n` + ); + } } function testTzToUtc(t) { - var result = TZ.toUTC.apply(TZ, t.inputs); - if (t.result !== result.toISOString()) { - console.log(result); - throw new Error( - `Invalid TZ to UTC conversion for ${t.desc}:\n` + - `\tExpected: ${t.result}\n` + - `\tActual: ${result.toISOString()}\n` - ); - } + var result = TZ.toUTC.apply(TZ, t.inputs); + if (t.result !== result.toISOString()) { + console.log(result); + throw new Error( + `Invalid TZ to UTC conversion for ${t.desc}:\n` + + `\tExpected: ${t.result}\n` + + `\tActual: ${result.toISOString()}\n` + ); + } } // At this real UTC time, what does the timezone translate it to? [ - // - // Start-of-DST Tests - // + // + // Start-of-DST Tests + // - // [Start] - // What time is '2021-03-14 01:15:59.000 in New York' in UTC? // 2021-03-14 06:15:59.000 - // // 2021-03-14T01:15:59.000-0500 - // What time is '2021-03-14 02:15:59.000 in New York' in UTC? // 2021-03-14 07:15:59.000 - // // 2021-03-14T03:15:59.000-0400 - // What time is '2021-03-14 03:15:59.000 in New York' in UTC? // 2021-03-14 07:15:59.000 - // // 2021-03-14T03:15:59.000-0400 - // What time is '2021-03-14 04:15:59.000 in New York' in UTC? // 2021-03-14 08:15:59.000 - // // 2021-03-14T04:15:59.000-0400 - // [End] + // [Start] + // What time is '2021-03-14 01:15:59.000 in New York' in UTC? // 2021-03-14 06:15:59.000 + // // 2021-03-14T01:15:59.000-0500 + // What time is '2021-03-14 02:15:59.000 in New York' in UTC? // 2021-03-14 07:15:59.000 + // // 2021-03-14T03:15:59.000-0400 + // What time is '2021-03-14 03:15:59.000 in New York' in UTC? // 2021-03-14 07:15:59.000 + // // 2021-03-14T03:15:59.000-0400 + // What time is '2021-03-14 04:15:59.000 in New York' in UTC? // 2021-03-14 08:15:59.000 + // // 2021-03-14T04:15:59.000-0400 + // [End] - // 12:15am NY -0500 => -0400 - { - desc: "UTC to 12:15am NY EST", - inputs: ["2021-03-14T05:15:59.000Z", "America/New_York"], - result: "2021-03-14T00:15:59.000-0500", - }, - { - desc: "UTC to 12:15am NY EST (2)", - inputs: ["2021-03-14T00:15:59.000-0500", "America/New_York"], - result: "2021-03-14T00:15:59.000-0500", - }, - // 1:15am NY (non-DST) - { - desc: "UTC to 1:15am NY EST", - inputs: ["2021-03-14T06:15:59.000Z", "America/New_York"], - result: "2021-03-14T01:15:59.000-0500", - }, - { - desc: "UTC to 1:15am NY EST (2)", - inputs: ["2021-03-14T01:15:59.000-0500", "America/New_York"], - result: "2021-03-14T01:15:59.000-0500", - }, + // 12:15am NY -0500 => -0400 + { + desc: "UTC to 12:15am NY EST", + inputs: ["2021-03-14T05:15:59.000Z", "America/New_York"], + result: "2021-03-14T00:15:59.000-0500", + }, + { + desc: "UTC to 12:15am NY EST (2)", + inputs: ["2021-03-14T00:15:59.000-0500", "America/New_York"], + result: "2021-03-14T00:15:59.000-0500", + }, + // 1:15am NY (non-DST) + { + desc: "UTC to 1:15am NY EST", + inputs: ["2021-03-14T06:15:59.000Z", "America/New_York"], + result: "2021-03-14T01:15:59.000-0500", + }, + { + desc: "UTC to 1:15am NY EST (2)", + inputs: ["2021-03-14T01:15:59.000-0500", "America/New_York"], + result: "2021-03-14T01:15:59.000-0500", + }, - // NOTE: Can't 2:15am NY, because it does not exist (skipped by DST) + // NOTE: Can't 2:15am NY, because it does not exist (skipped by DST) - // 3:15am NY (DST) - { - desc: "UTC to 3:15am NY EDT", - inputs: ["2021-03-14T07:15:59.000Z", "America/New_York"], - result: "2021-03-14T03:15:59.000-0400", - }, - { - desc: "UTC to 3:15am NY EDT (2)", - inputs: ["2021-03-14T03:15:59.000-0400", "America/New_York"], - result: "2021-03-14T03:15:59.000-0400", - }, - // 4:15am NY - { - desc: "UTC to 4:15am NY EDT", - inputs: ["2021-03-14T08:15:59.000Z", "America/New_York"], - result: "2021-03-14T04:15:59.000-0400", - }, - { - desc: "UTC to 4:15am NY EDT (2)", - inputs: ["2021-03-14T04:15:59.000-0400", "America/New_York"], - result: "2021-03-14T04:15:59.000-0400", - }, + // 3:15am NY (DST) + { + desc: "UTC to 3:15am NY EDT", + inputs: ["2021-03-14T07:15:59.000Z", "America/New_York"], + result: "2021-03-14T03:15:59.000-0400", + }, + { + desc: "UTC to 3:15am NY EDT (2)", + inputs: ["2021-03-14T03:15:59.000-0400", "America/New_York"], + result: "2021-03-14T03:15:59.000-0400", + }, + // 4:15am NY + { + desc: "UTC to 4:15am NY EDT", + inputs: ["2021-03-14T08:15:59.000Z", "America/New_York"], + result: "2021-03-14T04:15:59.000-0400", + }, + { + desc: "UTC to 4:15am NY EDT (2)", + inputs: ["2021-03-14T04:15:59.000-0400", "America/New_York"], + result: "2021-03-14T04:15:59.000-0400", + }, - // - // End-of-DST Tests - // + // + // End-of-DST Tests + // - // [Start] - // What time is '2021-11-07 01:15:59.000 in New York' in UTC? // 2021-11-07 05:15:59.000 - // // 2021-11-07T01:15:59.000-0400 - // // 2021-11-07 06:15:59.000 - // // 2021-11-07T01:15:59.000-0500 - // What time is '2021-11-07 02:15:59.000 in New York' in UTC? // 2021-11-07 07:15:59.000 - // // 2021-11-07T02:15:59.000-0500 - // What time is '2021-11-07 03:15:59.000 in New York' in UTC? // 2021-11-07 08:15:59.000 - // [End] + // [Start] + // What time is '2021-11-07 01:15:59.000 in New York' in UTC? // 2021-11-07 05:15:59.000 + // // 2021-11-07T01:15:59.000-0400 + // // 2021-11-07 06:15:59.000 + // // 2021-11-07T01:15:59.000-0500 + // What time is '2021-11-07 02:15:59.000 in New York' in UTC? // 2021-11-07 07:15:59.000 + // // 2021-11-07T02:15:59.000-0500 + // What time is '2021-11-07 03:15:59.000 in New York' in UTC? // 2021-11-07 08:15:59.000 + // [End] - // 12:15am NY -0400 => -0500 - { - desc: "UTC to 2021 Nov 7, 12:15am NY EDT", - inputs: ["2021-11-07T04:15:59.000Z", "America/New_York"], - result: "2021-11-07T00:15:59.000-0400", - }, - { - desc: "UTC to 2021 Nov 7, 12:15am NY EDT (2)", - inputs: ["2021-11-07T00:15:59.000-0400", "America/New_York"], - result: "2021-11-07T00:15:59.000-0400", - }, - // 1:15am NY (DST) -0400 - // NOTE: 1:15am happens TWICE (with different offsets) - { - desc: "UTC to 2021 Nov 7, 1:15am NY EDT", - inputs: ["2021-11-07T05:15:59.000Z", "America/New_York"], - result: "2021-11-07T01:15:59.000-0400", - }, - { - desc: "UTC to 2021 Nov 7, 1:15am NY EDT (2)", - inputs: ["2021-11-07T01:15:59.000-0400", "America/New_York"], - result: "2021-11-07T01:15:59.000-0400", - }, - // 1:15am NY (non-DST) -0500 - { - desc: "UTC to 2021 Nov 7, 1:15am NY EST", - inputs: ["2021-11-07T06:15:59.000Z", "America/New_York"], - result: "2021-11-07T01:15:59.000-0500", - }, - { - desc: "UTC to 2021 Nov 7, 1:15am NY EST (2)", - inputs: ["2021-11-07T01:15:59.000-0500", "America/New_York"], - result: "2021-11-07T01:15:59.000-0500", - }, - // 2:15am NY -0500 - { - desc: "UTC to 2021 Nov 7, 2:15am NY EST", - inputs: ["2021-11-07T07:15:59.000Z", "America/New_York"], - result: "2021-11-07T02:15:59.000-0500", - }, - { - desc: "UTC to 2021 Nov 7, 2:15am NY EST (2)", - inputs: ["2021-11-07T02:15:59.000-0500", "America/New_York"], - result: "2021-11-07T02:15:59.000-0500", - }, - // 3:15am NY - { - desc: "UTC to 2021 Nov 7, 3:15am NY EST", - inputs: ["2021-11-07T08:15:59.000Z", "America/New_York"], - result: "2021-11-07T03:15:59.000-0500", - }, - { - desc: "UTC to 2021 Nov 7, 3:15am NY EST (2)", - inputs: ["2021-11-07T03:15:59.000-0500", "America/New_York"], - result: "2021-11-07T03:15:59.000-0500", - }, + // 12:15am NY -0400 => -0500 + { + desc: "UTC to 2021 Nov 7, 12:15am NY EDT", + inputs: ["2021-11-07T04:15:59.000Z", "America/New_York"], + result: "2021-11-07T00:15:59.000-0400", + }, + { + desc: "UTC to 2021 Nov 7, 12:15am NY EDT (2)", + inputs: ["2021-11-07T00:15:59.000-0400", "America/New_York"], + result: "2021-11-07T00:15:59.000-0400", + }, + // 1:15am NY (DST) -0400 + // NOTE: 1:15am happens TWICE (with different offsets) + { + desc: "UTC to 2021 Nov 7, 1:15am NY EDT", + inputs: ["2021-11-07T05:15:59.000Z", "America/New_York"], + result: "2021-11-07T01:15:59.000-0400", + }, + { + desc: "UTC to 2021 Nov 7, 1:15am NY EDT (2)", + inputs: ["2021-11-07T01:15:59.000-0400", "America/New_York"], + result: "2021-11-07T01:15:59.000-0400", + }, + // 1:15am NY (non-DST) -0500 + { + desc: "UTC to 2021 Nov 7, 1:15am NY EST", + inputs: ["2021-11-07T06:15:59.000Z", "America/New_York"], + result: "2021-11-07T01:15:59.000-0500", + }, + { + desc: "UTC to 2021 Nov 7, 1:15am NY EST (2)", + inputs: ["2021-11-07T01:15:59.000-0500", "America/New_York"], + result: "2021-11-07T01:15:59.000-0500", + }, + // 2:15am NY -0500 + { + desc: "UTC to 2021 Nov 7, 2:15am NY EST", + inputs: ["2021-11-07T07:15:59.000Z", "America/New_York"], + result: "2021-11-07T02:15:59.000-0500", + }, + { + desc: "UTC to 2021 Nov 7, 2:15am NY EST (2)", + inputs: ["2021-11-07T02:15:59.000-0500", "America/New_York"], + result: "2021-11-07T02:15:59.000-0500", + }, + // 3:15am NY + { + desc: "UTC to 2021 Nov 7, 3:15am NY EST", + inputs: ["2021-11-07T08:15:59.000Z", "America/New_York"], + result: "2021-11-07T03:15:59.000-0500", + }, + { + desc: "UTC to 2021 Nov 7, 3:15am NY EST (2)", + inputs: ["2021-11-07T03:15:59.000-0500", "America/New_York"], + result: "2021-11-07T03:15:59.000-0500", + }, - // - // Positive Offset Test - // + // + // Positive Offset Test + // - // Colombo +0530 (not DST) - { - desc: "UTC to Asia/Colombo (1)", - inputs: ["2021-03-14T08:15:59.000Z", "Asia/Colombo"], - result: "2021-03-14T13:45:59.000+0530", - }, - { - desc: "UTC to Asia/Colombo (2)", - inputs: ["2021-03-14T13:45:59.000+0530", "Asia/Colombo"], - result: "2021-03-14T13:45:59.000+0530", - }, - { - desc: "UTC to Asia/Colombo (3)", - inputs: ["2021-11-07T08:15:59.000Z", "Asia/Colombo"], - result: "2021-11-07T13:45:59.000+0530", - }, - { - desc: "UTC to Asia/Colombo (4)", - inputs: ["2021-11-07T13:45:59.000+0530", "Asia/Colombo"], - result: "2021-11-07T13:45:59.000+0530", - }, + // Colombo +0530 (not DST) + { + desc: "UTC to Asia/Colombo (1)", + inputs: ["2021-03-14T08:15:59.000Z", "Asia/Colombo"], + result: "2021-03-14T13:45:59.000+0530", + }, + { + desc: "UTC to Asia/Colombo (2)", + inputs: ["2021-03-14T13:45:59.000+0530", "Asia/Colombo"], + result: "2021-03-14T13:45:59.000+0530", + }, + { + desc: "UTC to Asia/Colombo (3)", + inputs: ["2021-11-07T08:15:59.000Z", "Asia/Colombo"], + result: "2021-11-07T13:45:59.000+0530", + }, + { + desc: "UTC to Asia/Colombo (4)", + inputs: ["2021-11-07T13:45:59.000+0530", "Asia/Colombo"], + result: "2021-11-07T13:45:59.000+0530", + }, ].forEach(testUtcToTz); console.info("Pass: UTC to TZ for America/New_York and Asia/Colombo"); [ - // - // Start-of-DST Tests - // + // + // Start-of-DST Tests + // - // [Start] - // What time is '2021-03-14 01:15:59.000 in New York' in UTC? // 2021-03-14 06:15:59.000 - // // 2021-03-14T01:15:59.000-0500 - // What time is '2021-03-14 02:15:59.000 in New York' in UTC? // 2021-03-14 07:15:59.000 - // // 2021-03-14T03:15:59.000-0400 - // What time is '2021-03-14 03:15:59.000 in New York' in UTC? // 2021-03-14 07:15:59.000 - // // 2021-03-14T03:15:59.000-0400 - // What time is '2021-03-14 04:15:59.000 in New York' in UTC? // 2021-03-14 08:15:59.000 - // // 2021-03-14T04:15:59.000-0400 - // [End] + // [Start] + // What time is '2021-03-14 01:15:59.000 in New York' in UTC? // 2021-03-14 06:15:59.000 + // // 2021-03-14T01:15:59.000-0500 + // What time is '2021-03-14 02:15:59.000 in New York' in UTC? // 2021-03-14 07:15:59.000 + // // 2021-03-14T03:15:59.000-0400 + // What time is '2021-03-14 03:15:59.000 in New York' in UTC? // 2021-03-14 07:15:59.000 + // // 2021-03-14T03:15:59.000-0400 + // What time is '2021-03-14 04:15:59.000 in New York' in UTC? // 2021-03-14 08:15:59.000 + // // 2021-03-14T04:15:59.000-0400 + // [End] - // 12:15am NY -0500 => -0400 - { - // 2021-03-14T05:15:59.000Z - desc: "2021 Mar 14, 12:15am NY EST to UTC", - inputs: ["2021-03-14 00:15:59.000", "America/New_York"], - result: "2021-03-14T00:15:59.000-0500", - }, - // 1:15am NY (non-DST) - { - // 2021-03-14T06:15:59.000Z - desc: "2021 Mar 14, 1:15am NY EST to UTC", - inputs: ["2021-03-14 01:15:59.000", "America/New_York"], - result: "2021-03-14T01:15:59.000-0500", - }, + // 12:15am NY -0500 => -0400 + { + // 2021-03-14T05:15:59.000Z + desc: "2021 Mar 14, 12:15am NY EST to UTC", + inputs: ["2021-03-14 00:15:59.000", "America/New_York"], + result: "2021-03-14T00:15:59.000-0500", + }, + // 1:15am NY (non-DST) + { + // 2021-03-14T06:15:59.000Z + desc: "2021 Mar 14, 1:15am NY EST to UTC", + inputs: ["2021-03-14 01:15:59.000", "America/New_York"], + result: "2021-03-14T01:15:59.000-0500", + }, - // NOTE: Can't 2:15am NY, because it does not exist (skipped by DST) - // This test is here to document the "undefined" behavior - { - // Both 2021-03-14T06:15:59.000Z and 2021-03-14T07:15:59.000Z - // would be reasonable substitutions, I think - desc: "2021 Mar 14, 2:15am NY ExT to UTC", - inputs: ["2021-03-14 02:15:59.000", "America/New_York"], - //result: "2021-03-14T01:15:59.000-0500", // 2021-03-14T06:15:59.000Z - result: "2021-03-14T02:15:59.000-0400", // 2021-03-14T06:15:59.000Z - //result: "2021-03-14T02:15:59.000-0500", // 2021-03-14T07:15:59.000Z - }, + // NOTE: Can't 2:15am NY, because it does not exist (skipped by DST) + // This test is here to document the "undefined" behavior + { + // Both 2021-03-14T06:15:59.000Z and 2021-03-14T07:15:59.000Z + // would be reasonable substitutions, I think + desc: "2021 Mar 14, 2:15am NY ExT to UTC", + inputs: ["2021-03-14 02:15:59.000", "America/New_York"], + //result: "2021-03-14T01:15:59.000-0500", // 2021-03-14T06:15:59.000Z + result: "2021-03-14T02:15:59.000-0400", // 2021-03-14T06:15:59.000Z + //result: "2021-03-14T02:15:59.000-0500", // 2021-03-14T07:15:59.000Z + }, - // 3:15am NY (DST) - { - // 2021-03-14T07:15:59.000Z - desc: "2021 Mar 14, 3:15am NY EDT to UTC", - inputs: ["2021-03-14 03:15:59.000", "America/New_York"], - result: "2021-03-14T03:15:59.000-0400", - }, - // 4:15am NY - { - // 2021-03-14T08:15:59.000Z - desc: "2021 Mar 14, 4:15am NY EDT to UTC", - inputs: ["2021-03-14 04:15:59.000", "America/New_York"], - result: "2021-03-14T04:15:59.000-0400", - }, + // 3:15am NY (DST) + { + // 2021-03-14T07:15:59.000Z + desc: "2021 Mar 14, 3:15am NY EDT to UTC", + inputs: ["2021-03-14 03:15:59.000", "America/New_York"], + result: "2021-03-14T03:15:59.000-0400", + }, + // 4:15am NY + { + // 2021-03-14T08:15:59.000Z + desc: "2021 Mar 14, 4:15am NY EDT to UTC", + inputs: ["2021-03-14 04:15:59.000", "America/New_York"], + result: "2021-03-14T04:15:59.000-0400", + }, - // - // End-of-DST Tests - // + // + // End-of-DST Tests + // - // [Start] - // What time is '2021-11-07 01:15:59.000 in New York' in UTC? // 2021-11-07 05:15:59.000 - // // 2021-11-07T01:15:59.000-0400 - // // 2021-11-07 06:15:59.000 - // // 2021-11-07T01:15:59.000-0500 - // What time is '2021-11-07 02:15:59.000 in New York' in UTC? // 2021-11-07 07:15:59.000 - // // 2021-11-07T02:15:59.000-0500 - // What time is '2021-11-07 03:15:59.000 in New York' in UTC? // 2021-11-07 08:15:59.000 - // [End] + // [Start] + // What time is '2021-11-07 01:15:59.000 in New York' in UTC? // 2021-11-07 05:15:59.000 + // // 2021-11-07T01:15:59.000-0400 + // // 2021-11-07 06:15:59.000 + // // 2021-11-07T01:15:59.000-0500 + // What time is '2021-11-07 02:15:59.000 in New York' in UTC? // 2021-11-07 07:15:59.000 + // // 2021-11-07T02:15:59.000-0500 + // What time is '2021-11-07 03:15:59.000 in New York' in UTC? // 2021-11-07 08:15:59.000 + // [End] - // 12:15am NY -0400 => -0500 - { - // 2021-11-07T04:15:59.000Z - desc: "2021 Nov 7, 12:15am NY EDT to UTC", - inputs: ["2021-11-07 00:15:59.000", "America/New_York"], - result: "2021-11-07T00:15:59.000-0400", - }, + // 12:15am NY -0400 => -0500 + { + // 2021-11-07T04:15:59.000Z + desc: "2021 Nov 7, 12:15am NY EDT to UTC", + inputs: ["2021-11-07 00:15:59.000", "America/New_York"], + result: "2021-11-07T00:15:59.000-0400", + }, - // 1:15am NY (DST) -0400 - // NOTE: 1:15am happens TWICE (with different offsets), so we skip one - { - // ==> 2021-11-07T05:15:59.000Z - // [Skip] 2021-11-07T06:15:59.000Z - desc: "2021 Nov 7, 1:15am NY ExT to UTC", - inputs: ["2021-11-07 01:15:59.000", "America/New_York"], - result: "2021-11-07T01:15:59.000-0400", // 2021-11-07T05:15:59.000Z - //result: "2021-11-07T01:15:59.000-0500", // 2021-11-07T06:15:59.000Z - }, + // 1:15am NY (DST) -0400 + // NOTE: 1:15am happens TWICE (with different offsets), so we skip one + { + // ==> 2021-11-07T05:15:59.000Z + // [Skip] 2021-11-07T06:15:59.000Z + desc: "2021 Nov 7, 1:15am NY ExT to UTC", + inputs: ["2021-11-07 01:15:59.000", "America/New_York"], + result: "2021-11-07T01:15:59.000-0400", // 2021-11-07T05:15:59.000Z + //result: "2021-11-07T01:15:59.000-0500", // 2021-11-07T06:15:59.000Z + }, - // 2:15am NY -0500 - { - // 2021-11-07T07:15:59.000Z - desc: "2021 Nov 7, 2:15am NY EST to UTC", - inputs: ["2021-11-07 02:15:59.000", "America/New_York"], - result: "2021-11-07T02:15:59.000-0500", - }, - // 3:15am NY - { - // 2021-11-07T08:15:59.000Z - desc: "2021 Nov 7, 3:15am NY EST to UTC", - inputs: ["2021-11-07 03:15:59.000", "America/New_York"], - result: "2021-11-07T03:15:59.000-0500", - }, + // 2:15am NY -0500 + { + // 2021-11-07T07:15:59.000Z + desc: "2021 Nov 7, 2:15am NY EST to UTC", + inputs: ["2021-11-07 02:15:59.000", "America/New_York"], + result: "2021-11-07T02:15:59.000-0500", + }, + // 3:15am NY + { + // 2021-11-07T08:15:59.000Z + desc: "2021 Nov 7, 3:15am NY EST to UTC", + inputs: ["2021-11-07 03:15:59.000", "America/New_York"], + result: "2021-11-07T03:15:59.000-0500", + }, - // - // Positive Offset Test - // + // + // Positive Offset Test + // - // Colombo +0530 (not DST) - { - // 2021-03-14T08:15:59.000Z - desc: "Asia/Colombo to UTC (1)", - inputs: ["2021-03-14 13:45:59.000", "Asia/Colombo"], - result: "2021-03-14T13:45:59.000+0530", - }, - { - // 2021-03-14T08:15:59.000Z - desc: "Asia/Colombo to UTC (2)", - inputs: ["2021-11-07 13:45:59.000", "Asia/Colombo"], - result: "2021-11-07T13:45:59.000+0530", - }, + // Colombo +0530 (not DST) + { + // 2021-03-14T08:15:59.000Z + desc: "Asia/Colombo to UTC (1)", + inputs: ["2021-03-14 13:45:59.000", "Asia/Colombo"], + result: "2021-03-14T13:45:59.000+0530", + }, + { + // 2021-03-14T08:15:59.000Z + desc: "Asia/Colombo to UTC (2)", + inputs: ["2021-11-07 13:45:59.000", "Asia/Colombo"], + result: "2021-11-07T13:45:59.000+0530", + }, ].forEach(testTzToUtc); console.info("Pass: TZ to UTC for America/New_York and Asia/Colombo"); diff --git a/xtz.js b/xtz.js index e9d0895..57d9743 100644 --- a/xtz.js +++ b/xtz.js @@ -1,182 +1,179 @@ var XTZ; (function () { - "use strict"; + "use strict"; - function toTimeZone(date, timeZone) { - // ISO string or existing date object - date = new Date(date); - var options = { - timeZone: timeZone, - year: "numeric", - month: "numeric", - day: "numeric", - hour12: false, - hour: "numeric", - minute: "numeric", - second: "numeric", - fractionalSecondDigits: 3, - }; - - var tzOptions = Object.assign({ timeZoneName: "long" }, options); - - // Every country uses the same year and months, right? - var formater = new Intl.DateTimeFormat("default", tzOptions); - var parts = formater.formatToParts(date); - - var whole = {}; - parts.forEach(function (part) { - var val = part.value; - switch (part.type) { - case "literal": - // ignore separators and whitespace characters - return; - case "timeZoneName": - // keep as is - it's a string - break; - case "month": - // months are 0-indexed for new Date() - val = parseInt(val, 10) - 1; - break; - case "hour": - // because sometimes 24 is used instead of 0, make 24 0 - val = parseInt(val, 10) % 24; - break; - case "fractionalSecond": - // fractionalSecond is a dumb name - should be millisecond - whole.millisecond = parseInt(val, 10); - return; - default: - val = parseInt(val, 10); - } - // whole.month = 0; - whole[part.type] = val; - }); - - whole.timeZone = timeZone; - whole.offset = getOffset(date, whole); - whole.toISOString = _toOffsetISOString; - return whole; - } - - function toTimeZoneISOString(date, timeZone) { - var whole = toTimeZone(date, timeZone); - return toOffsetISOString(whole); - } - - function _toOffsetISOString() { - return toOffsetISOString(this); - } - - function getOffset(utcDate, tzD2) { - var tzDate = new Date(toOffsetISOString(tzD2)); - var diff = - Math.round(tzDate.valueOf() - utcDate.valueOf()) / (60 * 1000); - return diff; - } - - function p2(x) { - return String(x).padStart(2, "0"); - } - - function p3(x) { - return String(x).padStart(3, "0"); - } - - function formatOffset(minutes) { - if (!minutes) { - return "Z"; - } - - var h = Math.floor(Math.abs(minutes) / 60); - var m = Math.abs(minutes) % 60; - var offset = ""; - if (minutes > 0) { - offset = "+"; - } else if (minutes < 0) { - offset = "-"; - } - - // +0500, -0730 - return ( - offset + - h.toString().padStart(2, "0") + - m.toString().padStart(2, "0") - ); - } - - function toOffsetISOString(d) { - var offset = formatOffset(d.offset); - return ( - `${d.year}-${p2(d.month + 1)}-${p2(d.day)}` + - `T${p2(d.hour)}:${p2(d.minute)}:${p2(d.second)}.${p3( - d.millisecond - )}${offset}` - ); - } - - function toUTC(dt, tz) { - if ("string" === typeof dt) { - // Either of these formats should work: - // 2021-03-14 01:15:59 - // 2021-03-14T01:15:59Z - dt = dt - .replace("T", " ") - .replace("Z", "") - .replace(" ", "T") - .replace(/$/, "Z"); - } - var utcDate = new Date(dt); - var tzD2 = toTimeZone(utcDate, tz); - var offset = tzD2.offset; - tzD2.offset = ""; - - var deltaDate = new Date(utcDate); - deltaDate.setUTCMinutes(deltaDate.getUTCMinutes() - offset); - var tzD3 = toTimeZone(deltaDate, tz); - - if ( - tzD3.hour === utcDate.getUTCHours() && - tzD3.minute === utcDate.getUTCMinutes() - ) { - return tzD3; - } - - var diff = tzD3.offset - offset; - var h = Math.floor(Math.abs(diff) / 60); - var m = Math.abs(diff) % 60; - var sign = Math.abs(diff) / diff; - tzD3.hour -= h * sign; - tzD3.minute -= m * sign; - - return tzD3; - } - - function toUTCISOString(date, timeZone) { - var whole = toUTC(date, timeZone); - return toOffsetISOString(whole); - } - - XTZ = { - // bespoke date => - // 2021-11-07T3:15:59-0500 - toOffsetISOString: toOffsetISOString, - - // -240 => -0400 - formatOffset: formatOffset, - - // [ "2021-11-07T08:15:59Z", "America/New_York" ] - // => "2021-11-07T03:15:59-0500" // 2021-11-07 03:15:59 - toTimeZone: toTimeZone, - toTimeZoneISOString: toTimeZoneISOString, - - // [ "2021-11-07 03:15:59", "America/New_York" ] - // => "2021-11-07T03:15:59-0500" // 2021-11-07T08:15:59Z - toUTC: toUTC, - toUTCISOString: toUTCISOString, + function toTimeZone(date, timeZone) { + // ISO string or existing date object + date = new Date(date); + var options = { + timeZone: timeZone, + year: "numeric", + month: "numeric", + day: "numeric", + hour12: false, + hour: "numeric", + minute: "numeric", + second: "numeric", + fractionalSecondDigits: 3, }; - if ("undefined" != typeof module && module.exports) { - module.exports = XTZ; + var tzOptions = Object.assign({ timeZoneName: "long" }, options); + + // Every country uses the same year and months, right? + var formater = new Intl.DateTimeFormat("default", tzOptions); + var parts = formater.formatToParts(date); + + var whole = {}; + parts.forEach(function (part) { + var val = part.value; + switch (part.type) { + case "literal": + // ignore separators and whitespace characters + return; + case "timeZoneName": + // keep as is - it's a string + break; + case "month": + // months are 0-indexed for new Date() + val = parseInt(val, 10) - 1; + break; + case "hour": + // because sometimes 24 is used instead of 0, make 24 0 + val = parseInt(val, 10) % 24; + break; + case "fractionalSecond": + // fractionalSecond is a dumb name - should be millisecond + whole.millisecond = parseInt(val, 10); + return; + default: + val = parseInt(val, 10); + } + // whole.month = 0; + whole[part.type] = val; + }); + + whole.timeZone = timeZone; + whole.offset = getOffset(date, whole); + whole.toISOString = _toOffsetISOString; + return whole; + } + + function toTimeZoneISOString(date, timeZone) { + var whole = toTimeZone(date, timeZone); + return toOffsetISOString(whole); + } + + function _toOffsetISOString() { + return toOffsetISOString(this); + } + + function getOffset(utcDate, tzD2) { + var tzDate = new Date(toOffsetISOString(tzD2)); + var diff = Math.round(tzDate.valueOf() - utcDate.valueOf()) / (60 * 1000); + return diff; + } + + function p2(x) { + return String(x).padStart(2, "0"); + } + + function p3(x) { + return String(x).padStart(3, "0"); + } + + function formatOffset(minutes) { + if (!minutes) { + return "Z"; } -}()); + + var h = Math.floor(Math.abs(minutes) / 60); + var m = Math.abs(minutes) % 60; + var offset = ""; + if (minutes > 0) { + offset = "+"; + } else if (minutes < 0) { + offset = "-"; + } + + // +0500, -0730 + return ( + offset + h.toString().padStart(2, "0") + m.toString().padStart(2, "0") + ); + } + + function toOffsetISOString(d) { + var offset = formatOffset(d.offset); + return ( + `${d.year}-${p2(d.month + 1)}-${p2(d.day)}` + + `T${p2(d.hour)}:${p2(d.minute)}:${p2(d.second)}.${p3( + d.millisecond + )}${offset}` + ); + } + + function toUTC(dt, tz) { + if ("string" === typeof dt) { + // Either of these formats should work: + // 2021-03-14 01:15:59 + // 2021-03-14T01:15:59Z + dt = dt + .replace("T", " ") + .replace("Z", "") + .replace(" ", "T") + .replace(/$/, "Z"); + } + var utcDate = new Date(dt); + var tzD2 = toTimeZone(utcDate, tz); + var offset = tzD2.offset; + tzD2.offset = ""; + + var deltaDate = new Date(utcDate); + deltaDate.setUTCMinutes(deltaDate.getUTCMinutes() - offset); + var tzD3 = toTimeZone(deltaDate, tz); + + if ( + tzD3.hour === utcDate.getUTCHours() && + tzD3.minute === utcDate.getUTCMinutes() + ) { + return tzD3; + } + + var diff = tzD3.offset - offset; + var h = Math.floor(Math.abs(diff) / 60); + var m = Math.abs(diff) % 60; + var sign = Math.abs(diff) / diff; + tzD3.hour -= h * sign; + tzD3.minute -= m * sign; + + return tzD3; + } + + function toUTCISOString(date, timeZone) { + var whole = toUTC(date, timeZone); + return toOffsetISOString(whole); + } + + XTZ = { + // bespoke date => + // 2021-11-07T3:15:59-0500 + toOffsetISOString: toOffsetISOString, + + // -240 => -0400 + formatOffset: formatOffset, + + // [ "2021-11-07T08:15:59Z", "America/New_York" ] + // => "2021-11-07T03:15:59-0500" // 2021-11-07 03:15:59 + toTimeZone: toTimeZone, + toTimeZoneISOString: toTimeZoneISOString, + + // [ "2021-11-07 03:15:59", "America/New_York" ] + // => "2021-11-07T03:15:59-0500" // 2021-11-07T08:15:59Z + toUTC: toUTC, + toUTCISOString: toUTCISOString, + }; + + if ("undefined" != typeof module && module.exports) { + module.exports = XTZ; + } +})();