diff options
Diffstat (limited to 'date-fns/test/dst')
-rw-r--r-- | date-fns/test/dst/addBusinessDays/basic.js | 18 | ||||
-rw-r--r-- | date-fns/test/dst/eachDayOfInterval/basic.js | 22 | ||||
-rw-r--r-- | date-fns/test/dst/formatDistanceStrict/cairo.ts | 15 | ||||
-rw-r--r-- | date-fns/test/dst/formatDistanceStrict/melbourne.ts | 16 | ||||
-rw-r--r-- | date-fns/test/dst/parseISO/basic.js | 30 | ||||
-rw-r--r-- | date-fns/test/dst/parseISO/samoa.js | 16 | ||||
-rw-r--r-- | date-fns/test/dst/parseISO/sydney.js | 58 | ||||
-rw-r--r-- | date-fns/test/dst/tzOffsetTransitions.ts | 143 |
8 files changed, 318 insertions, 0 deletions
diff --git a/date-fns/test/dst/addBusinessDays/basic.js b/date-fns/test/dst/addBusinessDays/basic.js new file mode 100644 index 0000000..c55a686 --- /dev/null +++ b/date-fns/test/dst/addBusinessDays/basic.js @@ -0,0 +1,18 @@ +// This is basic DST test for addBusinessDays + +import assert from 'assert' +import addBusinessDays from '../../../src/addBusinessDays' + +if (process.env.TZ !== 'America/Santiago') + throw new Error('The test must be run with TZ=America/Santiago') + +if (parseInt(process.version.match(/^v(\d+)\./)[1]) < 10) + throw new Error('The test must be run on Node.js version >= 10') + +console.log(addBusinessDays(new Date(2014, 8 /* Sep */, 1), 10).toString()) + +assert.deepEqual( + // new Date(2014, 8, 7) is the DST day + addBusinessDays(new Date(2014, 8 /* Sep */, 1), 10).toString(), + 'Mon Sep 15 2014 00:00:00 GMT-0300 (Chile Summer Time)' +) diff --git a/date-fns/test/dst/eachDayOfInterval/basic.js b/date-fns/test/dst/eachDayOfInterval/basic.js new file mode 100644 index 0000000..8525a2b --- /dev/null +++ b/date-fns/test/dst/eachDayOfInterval/basic.js @@ -0,0 +1,22 @@ +// This is basic DST test for eachDayOfInterval + +import eachDayOfInterval from '../../../src/eachDayOfInterval' +import assert from 'assert' + +if (process.env.TZ !== 'Asia/Damascus') + throw new Error('The test must be run with TZ=Asia/Damascus') + +if (parseInt(process.version.match(/^v(\d+)\./)[1]) < 10) + throw new Error('The test must be run on Node.js version >= 10') + +assert.deepEqual( + eachDayOfInterval({ + start: new Date(2020, 2, 26), + end: new Date(2020, 2, 28) + }).map(d => d.toString()), + [ + 'Thu Mar 26 2020 00:00:00 GMT+0200 (Eastern European Standard Time)', + 'Fri Mar 27 2020 01:00:00 GMT+0300 (Eastern European Summer Time)', + 'Sat Mar 28 2020 00:00:00 GMT+0300 (Eastern European Summer Time)' + ] +) diff --git a/date-fns/test/dst/formatDistanceStrict/cairo.ts b/date-fns/test/dst/formatDistanceStrict/cairo.ts new file mode 100644 index 0000000..c9bd691 --- /dev/null +++ b/date-fns/test/dst/formatDistanceStrict/cairo.ts @@ -0,0 +1,15 @@ +// This is DST test for formatDistanceStrict in the Cairo timezone + +import formatDistanceStrict from '../../../src/formatDistanceStrict' +import assert from 'assert' + +if (process.env.TZ !== 'Africa/Cairo') + throw new Error('The test must be run with TZ=Africa/Cairo') + +assert.equal( + formatDistanceStrict( + new Date(1986, 3, 4, 10, 32, 0), + new Date(1986, 4, 4, 10, 32, 0) + ), + '1 month' +) diff --git a/date-fns/test/dst/formatDistanceStrict/melbourne.ts b/date-fns/test/dst/formatDistanceStrict/melbourne.ts new file mode 100644 index 0000000..aee04da --- /dev/null +++ b/date-fns/test/dst/formatDistanceStrict/melbourne.ts @@ -0,0 +1,16 @@ +// This is DST test for formatDistanceStrict in the Melbourne timezone + +import formatDistanceStrict from '../../../src/formatDistanceStrict' +import parseISO from '../../../src/parseISO' +import assert from 'assert' + +if (process.env.TZ !== 'Australia/Melbourne') + throw new Error('The test must be run with TZ=Australia/Melbourne') + +assert.equal( + formatDistanceStrict( + parseISO('2020-04-05T01:00:00+11:00'), + parseISO('2020-04-05T03:00:00+10:00') + ), + '3 hours' +) diff --git a/date-fns/test/dst/parseISO/basic.js b/date-fns/test/dst/parseISO/basic.js new file mode 100644 index 0000000..665af35 --- /dev/null +++ b/date-fns/test/dst/parseISO/basic.js @@ -0,0 +1,30 @@ +// This is basic DST test for parseISO + +import parseISO from '../../../src/parseISO' +import assert from 'assert' + +if (process.env.TZ !== 'America/Sao_Paulo') + throw new Error('The test must be run with TZ=America/Sao_Paulo') + +if (parseInt(process.version.match(/^v(\d+)\./)[1]) < 10) + throw new Error('The test must be run on Node.js version >= 10') + +// Test DST start edge +assert.equal(parseISO('2018-11-03').getDate(), 3) +assert.equal(parseISO('2018-11-04').getDate(), 4) // DST start +assert.equal(parseISO('2018-11-05').getDate(), 5) + +// Test DST end edge +assert.equal(parseISO('2019-02-15').getDate(), 15) +assert.equal(parseISO('2019-02-16').getDate(), 16) // DST end +assert.equal(parseISO('2019-02-17').getDate(), 17) + +// Test creation of nonexistent time +assert.equal( + parseISO('2018-11-04T00:00').toString(), + 'Sun Nov 04 2018 01:00:00 GMT-0200 (Brasilia Summer Time)' +) +assert.equal( + parseISO('2018-11-04T00:30').toString(), + 'Sun Nov 04 2018 01:30:00 GMT-0200 (Brasilia Summer Time)' +) diff --git a/date-fns/test/dst/parseISO/samoa.js b/date-fns/test/dst/parseISO/samoa.js new file mode 100644 index 0000000..40d224f --- /dev/null +++ b/date-fns/test/dst/parseISO/samoa.js @@ -0,0 +1,16 @@ +// This is an edge case DST test for parseISO + +import parseISO from '../../../src/parseISO' +import assert from 'assert' + +if (process.env.TZ !== 'Pacific/Apia') + throw new Error('The test must be run with TZ=Pacific/Apia') + +if (parseInt(process.version.match(/^v(\d+)\./)[1]) < 10) + throw new Error('The test must be run on Node.js version >= 10') + +assert.equal(parseISO('2011-12-30').getDate(), 31) +assert.equal( + parseISO('2011-12-30T03:30').toString(), + 'Sat Dec 31 2011 03:30:00 GMT+1400 (Apia Daylight Time)' +) diff --git a/date-fns/test/dst/parseISO/sydney.js b/date-fns/test/dst/parseISO/sydney.js new file mode 100644 index 0000000..3b733c0 --- /dev/null +++ b/date-fns/test/dst/parseISO/sydney.js @@ -0,0 +1,58 @@ +// This is basic DST test for parseISO + +import parseISO from '../../../src/parseISO' +import assert from 'assert' + +if (process.env.TZ !== 'Australia/Sydney') + throw new Error('The test must be run with TZ=Australia/Sydney') + +if (parseInt(process.version.match(/^v(\d+)\./)[1]) < 10) + throw new Error('The test must be run on Node.js version >= 10') + +// Test DST start edge +assert.equal(parseISO('2019-10-06').getDate(), 6) // DST start +assert.equal(parseISO('2019-10-07').getDate(), 7) +assert.equal( + parseISO('2019-10-06T01:00:00').toString(), + 'Sun Oct 06 2019 01:00:00 GMT+1000 (Australian Eastern Standard Time)' +) +assert.equal( + parseISO('2019-10-06T02:00:00').toString(), + 'Sun Oct 06 2019 03:00:00 GMT+1100 (Australian Eastern Daylight Time)' +) + +assert.equal( + parseISO('2019-10-06T05:00:00').toString(), + 'Sun Oct 06 2019 05:00:00 GMT+1100 (Australian Eastern Daylight Time)' +) + +// Test DST end edge +assert.equal(parseISO('2019-04-06').getDate(), 6) +assert.equal(parseISO('2019-04-07').getDate(), 7) // DST end +assert.equal( + parseISO('2019-04-06T11:00:00').toString(), + 'Sat Apr 06 2019 11:00:00 GMT+1100 (Australian Eastern Daylight Time)' +) +assert.equal( + parseISO('2019-04-07T11:00:00').toString(), + 'Sun Apr 07 2019 11:00:00 GMT+1000 (Australian Eastern Standard Time)' +) + +assert.equal( + parseISO('2019-04-07T00:00:00').toString(), + 'Sun Apr 07 2019 00:00:00 GMT+1100 (Australian Eastern Daylight Time)' +) + +// test edge cases for months, years +assert.equal( + parseISO('2020-01-01T00:00:00').toString(), + 'Wed Jan 01 2020 00:00:00 GMT+1100 (Australian Eastern Daylight Time)' +) +assert.equal( + parseISO('2019-12-31T23:59:59').toString(), + 'Tue Dec 31 2019 23:59:59 GMT+1100 (Australian Eastern Daylight Time)' +) +assert.equal( + parseISO('2020-02-29T23:59:59').toString(), + 'Sat Feb 29 2020 23:59:59 GMT+1100 (Australian Eastern Daylight Time)' +) diff --git a/date-fns/test/dst/tzOffsetTransitions.ts b/date-fns/test/dst/tzOffsetTransitions.ts new file mode 100644 index 0000000..3e5521b --- /dev/null +++ b/date-fns/test/dst/tzOffsetTransitions.ts @@ -0,0 +1,143 @@ +type PartialInterval = { + start: Date | undefined + end: Date | undefined +} + +/** + * Fetch the start and end of DST for the local time + * zone in a given year. + * We'll assume that DST start & end are the first + * forward and the last back transitions in the year, + * except transitions in Jan or Dec which are likely + * to be permanent TZ changes rather than DST changes. + * @param {number} year + * @returns object with two Date-valued properties: + * - `start` is the first instant of DST in the Spring, + * or undefined if there's no DST in this year. + * - `end` is the first instant of standard time + * in the Fall, or undefined if there's no DST in + * this year. + */ +export function getDstTransitions(year: number): PartialInterval { + const result: PartialInterval = { + start: undefined, + end: undefined + } + const transitions = getTzOffsetTransitions(year) + for (let i = 0; i < transitions.length; i++) { + const t = transitions[i] + const month = t.date.getMonth() + if (month > 0 && month < 11) { + if (t.type === 'forward') result.start = t.date + if (t.type === 'back' && !result.end) result.end = t.date + } + } + return result +} + +function isValidDate(date: unknown): date is Date { + return date instanceof Date && !isNaN(date.getTime()) +} + +const MINUTE = 1000 * 60 + +function firstTickInLocalDay(date: Date): Date { + const dateNumber = date.getDate() + let prev = date + let d = date + do { + prev = d + d = new Date(d.getTime() - MINUTE) + } while (dateNumber === d.getDate()) + return prev +} + +function fiveMinutesLater(date: Date): Date { + return new Date(date.getTime() + 5 * MINUTE) +} + +function oneDayLater(date: Date): Date { + const d = new Date(date) + d.setDate(d.getDate() + 1) + return firstTickInLocalDay(d) +} + +function previousTickTimezoneOffset(date: Date): number { + const d = new Date(date.getTime() - 1) + return d.getTimezoneOffset() +} + +/** + * Fetch all timezone-offset transitions in a given + * year. These are almost always DST transitions, + * but sometimes there are non-DST changes, e.g. + * when a country changes its time zone + * @param {number} year + * @returns array of objects, each with the following + * propeerties: + * - `date` - a `Date` representing the first instant + * when the new timezone offset is effective. + * - `type` - either `forward` for skippnig time like + * the Spring transition to DST. + * - `before` - the timezone offset before the tranition. + * For example, the UTC-0400 offset will return -240. + * To match how times are displayed in ISO 8601 format, + * the sign of this value is reversed from the return + * value of `Date.getTimezoneOffset`. + * - `after` - the timezone offset after the tranition. + * Examples and caveats are the same as `before`. + + */ +export function getTzOffsetTransitions(year: number) { + // start at the end of the previous day + let date = firstTickInLocalDay(new Date(year, 0, 1)) + if (!isValidDate(date)) { + throw new Error('Invalid Date') + } + let baseTzOffset = previousTickTimezoneOffset(date) + const transitions = [] + do { + let tzOffset = date.getTimezoneOffset() + if (baseTzOffset !== tzOffset) { + if (tzOffset !== previousTickTimezoneOffset(date)) { + // Transition is the first tick of a local day. + transitions.push({ + date: date, + type: tzOffset < baseTzOffset ? 'forward' : 'back', + before: -baseTzOffset, + after: -tzOffset + }) + baseTzOffset = tzOffset + } else { + // transition was not at the start of the day, so it must have happened + // yesterday. Back up one day and find the minute where it happened. + let transitionDate = new Date(date.getTime()) + transitionDate.setDate(transitionDate.getDate() - 1) + + // Iterate through each 5 mins of the day until we find a transition. + // TODO: this could be optimized to search hours then minutes or by or + // by using a binary search. + const dayNumber = transitionDate.getDate() + while ( + isValidDate(transitionDate) && + transitionDate.getDate() === dayNumber + ) { + tzOffset = transitionDate.getTimezoneOffset() + if (baseTzOffset !== tzOffset) { + transitions.push({ + date: transitionDate, + type: tzOffset < baseTzOffset ? 'forward' : 'back', + before: -baseTzOffset, + after: -tzOffset + }) + baseTzOffset = tzOffset + break // assuming only 1 transition per day + } + transitionDate = fiveMinutesLater(transitionDate) + } + } + } + date = oneDayLater(date) + } while (date.getFullYear() === year) + return transitions +} |