import {describe, it, expect, beforeEach} from 'vitest'; import {ZonedDateTime} from '@/'; import importer from '@clarify/zone-data/importer'; import {TimeZone} from '@clarify/zone-data'; let newYorkFormatter = new Intl.DateTimeFormat('nb-no', { timeStyle: 'long', dateStyle: 'short', timeZone: 'America/New_York', }); let calcuttaFormatter = new Intl.DateTimeFormat('nb-no', { timeStyle: 'long', dateStyle: 'short', timeZone: 'Asia/Calcutta', }); let osloFormatter = new Intl.DateTimeFormat('nb-no', { timeStyle: 'long', dateStyle: 'short', timeZone: 'Europe/Oslo', }); let londonFormatter = new Intl.DateTimeFormat('nb-no', { timeStyle: 'long', dateStyle: 'short', timeZone: 'Europe/London', }); let katmanduFormatter = new Intl.DateTimeFormat('nb-no', { timeStyle: 'long', dateStyle: 'short', timeZone: 'Asia/Katmandu', }); let guyanaFormatter = new Intl.DateTimeFormat('nb-no', { timeStyle: 'long', dateStyle: 'short', timeZone: 'America/Guyana', }); type TestCase = [ timeZone: keyof typeof importer, timestamp: number, expected: string, startOfMonth: string, startOfDay: string, startOfHour: string, ]; let testCases: TestCase[] = [ [ 'America/Guyana', 176096700000 - 276800 * 1000, '28.07.1975, 19:06:40 GMT-3:45', '01.07.1975, 00:00:00 GMT-3:45', '28.07.1975, 00:00:00 GMT-3:45', '28.07.1975, 19:00:00 GMT-3:45', ], [ 'America/Guyana', Date.parse('1975-07-28T18:51:40.000Z'), '28.07.1975, 15:06:40 GMT-3:45', '01.07.1975, 00:00:00 GMT-3:45', '28.07.1975, 00:00:00 GMT-3:45', '28.07.1975, 15:00:00 GMT-3:45', ], [ 'America/Guyana', Date.parse('1975-07-28T02:51:40.000Z'), '27.07.1975, 23:06:40 GMT-3:45', '01.07.1975, 00:00:00 GMT-3:45', '27.07.1975, 00:00:00 GMT-3:45', '27.07.1975, 23:00:00 GMT-3:45', ], [ 'Europe/Oslo', 13828356e5 - 1000, '27.10.2013, 02:59:59 CEST', '01.10.2013, 00:00:00 CEST', '27.10.2013, 00:00:00 CEST', '27.10.2013, 02:00:00 CEST', ], [ 'Europe/Oslo', 13828356e5 + 1000, '27.10.2013, 02:00:01 CET', '01.10.2013, 00:00:00 CEST', '27.10.2013, 00:00:00 CEST', '27.10.2013, 02:00:00 CET', ], [ 'Europe/Oslo', 1396141200000 - 1000, '30.03.2014, 01:59:59 CET', '01.03.2014, 00:00:00 CET', '30.03.2014, 00:00:00 CET', '30.03.2014, 01:00:00 CET', ], [ 'Europe/Oslo', 1396141200000 + 1000, '30.03.2014, 03:00:01 CEST', '01.03.2014, 00:00:00 CET', '30.03.2014, 00:00:00 CET', '30.03.2014, 03:00:00 CEST', ], [ 'Europe/London', 13828356e5 - 1, '27.10.2013, 01:59:59 GMT+1', '01.10.2013, 00:00:00 GMT+1', '27.10.2013, 00:00:00 GMT+1', '27.10.2013, 01:00:00 GMT+1', ], [ 'America/New_York', 1667714400000 - 1000, '06.11.2022, 01:59:59 EDT', '01.11.2022, 00:00:00 EDT', '06.11.2022, 00:00:00 EDT', '06.11.2022, 01:00:00 EDT', ], [ 'America/New_York', 1667714400000 + 1000, '06.11.2022, 01:00:01 EST', '01.11.2022, 00:00:00 EDT', '06.11.2022, 00:00:00 EDT', '06.11.2022, 01:00:00 EST', ], [ 'America/New_York', 1678604400000 - 1000, '12.03.2023, 01:59:59 EST', '01.03.2023, 00:00:00 EST', '12.03.2023, 00:00:00 EST', '12.03.2023, 01:00:00 EST', ], [ 'America/New_York', 1678604400000 + 1000, '12.03.2023, 03:00:01 EDT', '01.03.2023, 00:00:00 EST', '12.03.2023, 00:00:00 EST', '12.03.2023, 03:00:00 EDT', ], [ 'Asia/Katmandu', Date.UTC(2023, 0, 12, 20, 54, 0), '13.01.2023, 02:39:00 GMT+5:45', '01.01.2023, 00:00:00 GMT+5:45', '13.01.2023, 00:00:00 GMT+5:45', '13.01.2023, 02:00:00 GMT+5:45', ], [ 'Asia/Katmandu', Date.UTC(2023, 0, 12, 20, 44, 0), '13.01.2023, 02:29:00 GMT+5:45', '01.01.2023, 00:00:00 GMT+5:45', '13.01.2023, 00:00:00 GMT+5:45', '13.01.2023, 02:00:00 GMT+5:45', ], ]; it.each(testCases)( 'format timezone %s at timestamp %i', async (timeZone, timestamp, expected, startOfMonth, startOfDay, startOfHour) => { await importer[timeZone](); let formatter = new Intl.DateTimeFormat('nb-no', { timeStyle: 'long', dateStyle: 'short', timeZone: timeZone, }); expect(formatter.format(new Date(timestamp))).equal(expected); let startOfDayDate = new ZonedDateTime(timestamp, timeZone); startOfDayDate = startOfDayDate.startOfDay(); expect(formatter.format(new Date(startOfDayDate.epochMilliseconds)), 'start of day').equal(startOfDay); let startOfHourDate = new ZonedDateTime(timestamp, timeZone); startOfHourDate = startOfHourDate.with({ minute: 0, second: 0, millisecond: 0, }); expect(formatter.format(new Date(startOfHourDate.epochMilliseconds)), 'start of hour').equal(startOfHour); let startOfMonthDate = new ZonedDateTime(timestamp, timeZone); startOfMonthDate = startOfMonthDate.with({ day: 1, hour: 0, minute: 0, second: 0, millisecond: 0, }); expect(formatter.format(new Date(startOfMonthDate.epochMilliseconds)), 'start of month').equal(startOfMonth); }, ); describe('ZonedDateTime', () => { describe('getOffset', () => { let timeZone: TimeZone; beforeEach(() => { timeZone = new TimeZone('Europe/Early', [3600, 7200, null], [60, 180, 0]); }); it('before first offset', async () => { let data = new ZonedDateTime(1800, timeZone); expect(data.offsetMilliseconds).equal(-3600000); }); it('at first offset', async () => { let next = new ZonedDateTime(3600, timeZone); expect(next.offsetMilliseconds).equal(-10800000); }); it('at second offset', async () => { let after = new ZonedDateTime(7200, timeZone); expect(after.offsetMilliseconds).equal(0); }); it('after offset', async () => { let after = new ZonedDateTime(9000, timeZone); expect(after.offsetMilliseconds).equal(0); }); }); it('invokes API', async () => { let timeZone = await importer['America/New_York'](); let date = new ZonedDateTime(Date.parse('2023-03-10T20:39:53.990Z'), timeZone); date.add({days: 5}); expect(true).equal(true); }); it('start of day (DTS end)', async () => { let timeZone = await importer['America/New_York'](); let date = new ZonedDateTime(Date.parse('2023-03-12T20:39:53.990Z'), timeZone); date = date.startOfDay(); expect(true).equal(true); }); describe('startOfHour', async () => { it('-3:75', async () => { let date = new ZonedDateTime(176096700000 - 1, 'America/Guyana'); expect(guyanaFormatter.format(new Date(date.epochMilliseconds))).equal('31.07.1975, 23:59:59 GMT-3:45'); date = date.with({ minute: 0, second: 0, millisecond: 0, }); expect(guyanaFormatter.format(new Date(date.epochMilliseconds))).equal('31.07.1975, 23:00:00 GMT-3:45'); }); it('-3:75 twice', async () => { let date = new ZonedDateTime(176096700000 - 2760000, 'America/Guyana'); expect(guyanaFormatter.format(new Date(date.epochMilliseconds))).equal('31.07.1975, 23:14:00 GMT-3:45'); date = date.with({ minute: 0, second: 0, millisecond: 0, }); expect(guyanaFormatter.format(new Date(date.epochMilliseconds))).equal('31.07.1975, 23:00:00 GMT-3:45'); }); it('+0545', async () => { let timeZone = await importer['Asia/Katmandu'](); let date = new ZonedDateTime(Date.UTC(2023, 0, 12, 20, 54, 0), timeZone); expect(katmanduFormatter.format(new Date(date.epochMilliseconds))).equal('13.01.2023, 02:39:00 GMT+5:45'); date = date.with({ minute: 0, second: 0, millisecond: 0, }); expect(katmanduFormatter.format(new Date(date.epochMilliseconds))).equal('13.01.2023, 02:00:00 GMT+5:45'); }); it('+0545 before cut off', async () => { let timeZone = await importer['Asia/Katmandu'](); let date = new ZonedDateTime(Date.UTC(2023, 0, 12, 20, 44, 0), timeZone); expect(katmanduFormatter.format(new Date(date.epochMilliseconds))).equal('13.01.2023, 02:29:00 GMT+5:45'); date = date.with({ minute: 0, second: 0, millisecond: 0, }); expect(katmanduFormatter.format(new Date(date.epochMilliseconds))).equal('13.01.2023, 02:00:00 GMT+5:45'); }); it('works when offset % 60 !== 0', async () => { let timeZone = await importer['Asia/Calcutta'](); let date = new ZonedDateTime(Date.UTC(2023, 0, 12, 20, 54, 0), timeZone); date = date.with({ minute: 0, second: 0, millisecond: 0, }); expect(calcuttaFormatter.format(new Date(date.epochMilliseconds))).equal('13.01.2023, 02:00:00 GMT+5:30'); }); it('works when offset % 60 !== 0', async () => { let timeZone = await importer['Asia/Calcutta'](); let rawDate = new Date(Date.UTC(2023, 0, 12, 20, 29, 0)); expect(calcuttaFormatter.format(rawDate)).equal('13.01.2023, 01:59:00 GMT+5:30'); let date = new ZonedDateTime(Date.UTC(2023, 0, 12, 20, 29, 0), timeZone); date = date.with({ minute: 0, second: 0, millisecond: 0, }); expect(calcuttaFormatter.format(new Date(date.epochMilliseconds))).equal('13.01.2023, 01:00:00 GMT+5:30'); }); it('works in local time zone', async () => { let timeZone = await importer['Europe/Oslo'](); let date = new ZonedDateTime(13828356e5 - 1, timeZone); date = date.with({ minute: 0, second: 0, millisecond: 0, }); expect(osloFormatter.format(new Date(date.epochMilliseconds))).equal('27.10.2013, 02:00:00 CEST'); }); it('works in london time zone', async () => { let timeZone = await importer['Europe/London'](); let date = new ZonedDateTime(13828356e5 - 1, timeZone); date = date.with({ minute: 0, second: 0, millisecond: 0, }); expect(londonFormatter.format(new Date(date.epochMilliseconds))).equal('27.10.2013, 01:00:00 GMT+1'); }); it('works in london time zone', async () => { let timeZone = await importer['Europe/London'](); let date = new ZonedDateTime(13828356e5 + 3600 - 1, timeZone); date = date.with({ minute: 0, second: 0, millisecond: 0, }); expect(londonFormatter.format(new Date(date.epochMilliseconds))).equal('27.10.2013, 01:00:00 GMT'); }); it('works in local time zone', async () => { let timeZone = await importer['America/New_York'](); let date = new ZonedDateTime(13828356e5 - 1, timeZone); date = date.with({ minute: 0, second: 0, millisecond: 0, }); expect(newYorkFormatter.format(new Date(date.epochMilliseconds))).equal('26.10.2013, 20:00:00 EDT'); }); }); describe('startOfYear', async () => { it('works when offset % 60 !== 0', async () => { let timeZone = await importer['Asia/Calcutta'](); let date = new ZonedDateTime(Date.UTC(2023, 5, 12, 20, 54, 0), timeZone); date = date.with({ month: 1, day: 1, hour: 0, minute: 0, second: 0, millisecond: 0, }); expect(calcuttaFormatter.format(new Date(date.epochMilliseconds))).equal('01.01.2023, 00:00:00 GMT+5:30'); }); it('works in local time zone', async () => { let timeZone = await importer['Europe/Oslo'](); let date = new ZonedDateTime(13828356e5, timeZone); date = date.with({ month: 1, day: 1, hour: 0, minute: 0, second: 0, millisecond: 0, }); expect(osloFormatter.format(new Date(date.epochMilliseconds))).equal('01.01.2013, 00:00:00 CET'); }); it('works in local time zone at 1st of month', async () => { let timeZone = await importer['Europe/Oslo'](); let date = new ZonedDateTime(Date.UTC(2022, 11, 31, 23, 0, 0), timeZone); date = date.with({ month: 1, day: 1, hour: 0, minute: 0, second: 0, millisecond: 0, }); expect(osloFormatter.format(new Date(date.epochMilliseconds))).equal('01.01.2023, 00:00:00 CET'); }); it('works in new york time zone at exact 1st of month', async () => { let timeZone = await importer['America/New_York'](); let date = new ZonedDateTime(Date.UTC(2023, 0, 1, 5, 0, 0), timeZone); date = date.with({ month: 1, day: 1, hour: 0, minute: 0, second: 0, millisecond: 0, }); expect(newYorkFormatter.format(new Date(date.epochMilliseconds))).equal('01.01.2023, 00:00:00 EST'); }); it('works in new york time zone', async () => { let timeZone = await importer['America/New_York'](); let date = new ZonedDateTime(13834584e5, timeZone); date = date.with({ month: 1, day: 1, hour: 0, minute: 0, second: 0, millisecond: 0, }); expect(newYorkFormatter.format(new Date(date.epochMilliseconds))).equal('01.01.2013, 00:00:00 EST'); }); it('works in london time zone', async () => { let timeZone = await importer['Europe/London'](); let date = new ZonedDateTime(13828356e5, timeZone); expect(londonFormatter.format(new Date(date.epochMilliseconds))).equal('27.10.2013, 01:00:00 GMT'); date = date.with({ month: 1, day: 1, hour: 0, minute: 0, second: 0, millisecond: 0, }); expect(londonFormatter.format(new Date(date.epochMilliseconds))).equal('01.01.2013, 00:00:00 GMT'); }); it('works in london time zone', async () => { let timeZone = await importer['Europe/London'](); let date = new ZonedDateTime(13961412e5, timeZone); date = date.with({ month: 1, day: 1, hour: 0, minute: 0, second: 0, millisecond: 0, }); expect(londonFormatter.format(new Date(date.epochMilliseconds))).equal('01.01.2014, 00:00:00 GMT'); }); }); describe('startOfMonth', async () => { it('works when offset % 60 !== 0', async () => { let timeZone = await importer['Asia/Calcutta'](); let date = new ZonedDateTime(Date.UTC(2023, 0, 12, 20, 54, 0), timeZone); date = date.with({ day: 1, hour: 0, minute: 0, second: 0, millisecond: 0, }); expect(calcuttaFormatter.format(new Date(date.epochMilliseconds))).equal('01.01.2023, 00:00:00 GMT+5:30'); }); it('works in local time zone', async () => { let timeZone = await importer['Europe/Oslo'](); let date = new ZonedDateTime(13828356e5, timeZone); date = date.with({ day: 1, hour: 0, minute: 0, second: 0, millisecond: 0, }); expect(osloFormatter.format(new Date(date.epochMilliseconds))).equal('01.10.2013, 00:00:00 CEST'); }); it('works in local time zone at 1st of month', async () => { let timeZone = await importer['Europe/Oslo'](); let date = new ZonedDateTime(Date.UTC(2023, 4, 1, 18, 0, 0), timeZone); date = date.with({ day: 1, hour: 0, minute: 0, second: 0, millisecond: 0, }); expect(osloFormatter.format(new Date(date.epochMilliseconds))).equal('01.05.2023, 00:00:00 CEST'); }); it('works in local time zone at 1st of month', async () => { let timeZone = await importer['Europe/Oslo'](); let date = new ZonedDateTime(Date.UTC(2023, 3, 30, 22, 0, 0), timeZone); date = date.with({ day: 1, hour: 0, minute: 0, second: 0, millisecond: 0, }); expect(osloFormatter.format(new Date(date.epochMilliseconds))).equal('01.05.2023, 00:00:00 CEST'); }); it('works in new york time zone at 1st of month', async () => { let timeZone = await importer['America/New_York'](); let date = new ZonedDateTime(Date.UTC(2023, 4, 1, 13, 0, 0), timeZone); date = date.with({ day: 1, hour: 0, minute: 0, second: 0, millisecond: 0, }); expect(newYorkFormatter.format(new Date(date.epochMilliseconds))).equal('01.05.2023, 00:00:00 EDT'); }); it('works in new york time zone at exact 1st of month', async () => { let timeZone = await importer['America/New_York'](); let date = new ZonedDateTime(Date.UTC(2023, 4, 1, 4, 0, 0), timeZone); date = date.with({ day: 1, hour: 0, minute: 0, second: 0, millisecond: 0, }); expect(newYorkFormatter.format(new Date(date.epochMilliseconds))).equal('01.05.2023, 00:00:00 EDT'); }); it('works in new york time zone', async () => { let timeZone = await importer['America/New_York'](); let date = new ZonedDateTime(13834584e5, timeZone); date = date.with({ day: 1, hour: 0, minute: 0, second: 0, millisecond: 0, }); expect(newYorkFormatter.format(new Date(date.epochMilliseconds))).equal('01.11.2013, 00:00:00 EDT'); }); // 13828356e5, 13961412e5 it('works in london time zone', async () => { let timeZone = await importer['Europe/London'](); let date = new ZonedDateTime(13828356e5, timeZone); expect(londonFormatter.format(new Date(date.epochMilliseconds))).equal('27.10.2013, 01:00:00 GMT'); date = date.with({ day: 1, hour: 0, minute: 0, second: 0, millisecond: 0, }); expect(londonFormatter.format(new Date(date.epochMilliseconds))).equal('01.10.2013, 00:00:00 GMT+1'); }); it('works in london time zone', async () => { let timeZone = await importer['Europe/London'](); let date = new ZonedDateTime(13961412e5, timeZone); date = date.with({ day: 1, hour: 0, minute: 0, second: 0, millisecond: 0, }); expect(londonFormatter.format(new Date(date.epochMilliseconds))).equal('01.03.2014, 00:00:00 GMT'); }); }); describe('.dayOfYear', async () => { let timeZone!: TimeZone; beforeEach(async () => { timeZone = await importer['Europe/Oslo'](); }); it('first day of year', async () => { let date = new ZonedDateTime(Date.parse('2021-12-31T23:00:00.000Z'), timeZone); expect(date.dayOfYear).equal(1); }); it('around daylight saving', async () => { let preZonedDateTime = new ZonedDateTime(Date.parse('2022-03-27T00:00:00.000Z'), timeZone); expect(preZonedDateTime.dayOfYear).equal(86); let postZonedDateTime = new ZonedDateTime(Date.parse('2022-03-27T01:00:00.000Z'), timeZone); expect(postZonedDateTime.dayOfYear).equal(86); let dayAfterZonedDateTime = new ZonedDateTime(Date.parse('2022-03-27T22:00:00.000Z'), timeZone); expect(dayAfterZonedDateTime.dayOfYear).equal(87); }); it('around daylight saving end', async () => { let preZonedDateTime = new ZonedDateTime(Date.parse('2022-10-30T00:00:00.000Z'), timeZone); expect(preZonedDateTime.dayOfYear).equal(303); let postZonedDateTime = new ZonedDateTime(Date.parse('2022-10-30T01:00:00.000Z'), timeZone); expect(postZonedDateTime.dayOfYear).equal(303); let dayAfterZonedDateTime = new ZonedDateTime(Date.parse('2022-10-30T23:00:00.000Z'), timeZone); expect(dayAfterZonedDateTime.dayOfYear).equal(304); }); }); });