From fd3f3ffe003b3608336e43a3a19ccd9db475fbde Mon Sep 17 00:00:00 2001 From: gosho-kazuya Date: Thu, 1 Oct 2020 03:19:21 -0700 Subject: [PATCH 1/4] use UTC as default, matrix test --- .github/workflows/test.yml | 10 +++++++--- package.json | 3 ++- src/CalendarEvent.test.ts | 41 +++++++++++++++++++++++++++++++++++++- src/CalendarEvent.ts | 7 ++++++- yarn.lock | 13 ++++++++---- 5 files changed, 64 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index edb5702..14d0886 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,11 +5,15 @@ on: push jobs: test: runs-on: ubuntu-latest + strategy: + matrix: + node: [8, 10, 12] + tz: ["Z", "UTC", "Asia/Tokyo", "Pacific/Pago_Pago", "America/Los_Angeles"] steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 12.x + node-version: ${{ matrix.node }} - uses: actions/cache@master with: path: | @@ -19,5 +23,5 @@ jobs: yarn install yarn tsc yarn test - # env: - # CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + env: + TZ: ${{ matrix.tz }} diff --git a/package.json b/package.json index 8a63f14..28c98ba 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "ava": "^3.12.1", "husky": "^4.3.0", "lint-staged": "^10.4.0", + "mockdate": "^3.0.2", "prettier": "^2.1.2", "rollup": "^2.28.2", "rollup-plugin-typescript2": "^0.27.3", @@ -31,6 +32,6 @@ "rrule": "^2" }, "dependencies": { - "dayjs": "^1.8.36" + "dayjs": "^1.9.1" } } diff --git a/src/CalendarEvent.test.ts b/src/CalendarEvent.test.ts index 8ae25c4..0886413 100644 --- a/src/CalendarEvent.test.ts +++ b/src/CalendarEvent.test.ts @@ -1,6 +1,14 @@ import { CalendarEvent } from './CalendarEvent' import { RRule } from 'rrule' import test from 'ava' +import MockDate from 'mockdate' + +const SUMMER = '2020-08-01T07:50:00Z' +const WINTER = '2020-02-01T07:50:00Z' + +test.afterEach(() => { + MockDate.reset() +}) test('CalendarEvent - init with hour and minute, occurences', (t) => { const event = new CalendarEvent({ @@ -24,10 +32,12 @@ test('CalendarEvent - init with hour and minute, occurences', (t) => { t.is(event.toText(), '10:00 AM to 10:30 AM every month on the 3rd Friday') + MockDate.set(SUMMER) t.deepEqual( event.occurences({ - between: [new Date('2020-09-01T00:00:00'), new Date('2020-12-31T00:00:00')], + between: [new Date('2020-05-01T00:00:00'), new Date('2020-12-31T00:00:00')], }), + // FIXME: is it broken? [ [new Date('2020-10-16T10:00:00Z'), new Date('2020-10-16T10:30:00Z')], [new Date('2020-11-20T10:00:00Z'), new Date('2020-11-20T10:30:00Z')], @@ -77,6 +87,8 @@ test('CalendarEvent - toText format & time zone', (t) => { }) test('CalendarEvent - toText format prev date timezone', (t) => { + MockDate.set(SUMMER) + const event = new CalendarEvent({ start: { dateTime: '2000-01-01T10:00:00Z', @@ -101,3 +113,30 @@ test('CalendarEvent - toText format prev date timezone', (t) => { '11:00 PM to the next day of 2:00 AM every day on Sunday, Thursday and every month on the 24th', ) }) + +test.only('CalendarEvent - summer time', (t) => { + const event = new CalendarEvent({ + start: { + hour: 10, + minute: 0, + tz: 'America/New_York', + }, + end: { + hour: 10, + minute: 30, + tz: 'America/New_York', + }, + recurrences: [ + new RRule({ + freq: RRule.DAILY, + }), + ], + }) + + // We expect same result even if summer time + MockDate.set(WINTER) + t.is(event.toText({ tz: 'America/New_York' }), '10:00 AM to 10:30 AM every day') + + MockDate.set(SUMMER) + t.is(event.toText({ tz: 'America/New_York' }), '10:00 AM to 10:30 AM every day') +}) diff --git a/src/CalendarEvent.ts b/src/CalendarEvent.ts index bb0949f..072c0c4 100644 --- a/src/CalendarEvent.ts +++ b/src/CalendarEvent.ts @@ -6,6 +6,8 @@ import timezone from 'dayjs/plugin/timezone' dayjs.extend(utc) dayjs.extend(timezone) +dayjs.tz.setDefault('UTC') + interface CalendarEventDef { dateTime?: Date | string hour?: number @@ -55,6 +57,8 @@ export class CalendarEvent { this.end = this.end.hour(end.hour!).minute(end.minute!) } + console.log(this.start) + this.recurrences = new RRuleSet() recurrences.forEach((r) => this.recurrences.rrule(r)) @@ -89,12 +93,13 @@ export class CalendarEvent { return this.recurrences.between(new Date(), until) } - return this.recurrences.between(new Date(), dayjs().add(1, 'month').toDate()) + return this.recurrences.between(new Date(), dayjs.utc().add(1, 'month').toDate()) } toText({ tz = 'UTC', joinDatesWith = ' and ', timeFormat = 'h:mm A' }: ToTextArgs = {}) { const start = dayjs.tz(this.start, tz) const end = dayjs.tz(this.end, tz) + const occurencesText = this.recurrences .clone() .rrules() diff --git a/yarn.lock b/yarn.lock index 29c4f11..c181110 100644 --- a/yarn.lock +++ b/yarn.lock @@ -604,10 +604,10 @@ date-time@^3.1.0: dependencies: time-zone "^1.0.0" -dayjs@^1.8.36: - version "1.8.36" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.8.36.tgz#be36e248467afabf8f5a86bae0de0cdceecced50" - integrity sha512-3VmRXEtw7RZKAf+4Tv1Ym9AGeo8r8+CjDi26x+7SYQil1UqtqdaokhzoEJohqlzt0m5kacJSDhJQkG/LWhpRBw== +dayjs@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.9.1.tgz#201a755f7db5103ed6de63ba93a984141c754541" + integrity sha512-01NCTBg8cuMJG1OQc6PR7T66+AFYiPwgDvdJmvJBn29NGzIG+DIFxPLNjHzwz3cpFIvG+NcwIjP9hSaPVoOaDg== debug@^4.1.1: version "4.2.0" @@ -1447,6 +1447,11 @@ minimist@^1.2.0: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +mockdate@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/mockdate/-/mockdate-3.0.2.tgz#a5a7bb5820da617747af424d7a4dcb22c6c03d79" + integrity sha512-ldfYSUW1ocqSHGTK6rrODUiqAFPGAg0xaHqYJ5tvj1hQyFsjuHpuWgWFTZWwDVlzougN/s2/mhDr8r5nY5xDpA== + ms@2.1.2, ms@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" From 8b3ed79da0baa0a94378c75fd42c1b61aeb98e92 Mon Sep 17 00:00:00 2001 From: gosho-kazuya Date: Thu, 1 Oct 2020 03:22:23 -0700 Subject: [PATCH 2/4] fix style, node > 8 --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 14d0886..93466dc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,13 +7,13 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [8, 10, 12] + node-version: [10, 12] tz: ["Z", "UTC", "Asia/Tokyo", "Pacific/Pago_Pago", "America/Los_Angeles"] steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: ${{ matrix.node }} + node-version: ${{ matrix.node-version }} - uses: actions/cache@master with: path: | From 6643da7416053bf0c0593c8e87f504fe9e0b499c Mon Sep 17 00:00:00 2001 From: gosho-kazuya Date: Thu, 1 Oct 2020 05:32:44 -0700 Subject: [PATCH 3/4] fix summer time bug, refactor --- src/CalendarEvent.test.ts | 10 +++++----- src/CalendarEvent.ts | 32 ++++++++++++++++++-------------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/CalendarEvent.test.ts b/src/CalendarEvent.test.ts index 0886413..fa93c70 100644 --- a/src/CalendarEvent.test.ts +++ b/src/CalendarEvent.test.ts @@ -119,12 +119,12 @@ test.only('CalendarEvent - summer time', (t) => { start: { hour: 10, minute: 0, - tz: 'America/New_York', + tz: 'America/Los_Angeles', }, end: { hour: 10, minute: 30, - tz: 'America/New_York', + tz: 'America/Los_Angeles', }, recurrences: [ new RRule({ @@ -134,9 +134,9 @@ test.only('CalendarEvent - summer time', (t) => { }) // We expect same result even if summer time - MockDate.set(WINTER) - t.is(event.toText({ tz: 'America/New_York' }), '10:00 AM to 10:30 AM every day') + // MockDate.set(WINTER) + // t.is(event.toText({ tz: 'America/Los_Angeles' }), '10:00 AM to 10:30 AM every day') MockDate.set(SUMMER) - t.is(event.toText({ tz: 'America/New_York' }), '10:00 AM to 10:30 AM every day') + t.is(event.toText({ tz: 'America/Los_Angeles' }), '10:00 AM to 10:30 AM every day') }) diff --git a/src/CalendarEvent.ts b/src/CalendarEvent.ts index 072c0c4..ad09771 100644 --- a/src/CalendarEvent.ts +++ b/src/CalendarEvent.ts @@ -34,31 +34,35 @@ interface OccurencesArgs { const INITIAL_DATE = new Date(Date.UTC(2000, 0, 15, 0, 0, 0, 0)) +function validateCalendarEventDef(e: CalendarEventDef, name: string) { + if ((e.hour === undefined || e.minute === undefined) && !e.dateTime) { + throw new Error(`invalid ${name} value. Either spcify dateTime or hour/minutes`) + } +} + +function hasMinuteHour(e: CalendarEventDef): e is { hour: number; minute: number } { + return Number.isInteger(e.hour) && Number.isInteger(e.minute) +} + export class CalendarEvent { private start: dayjs.Dayjs private end: dayjs.Dayjs private recurrences: RRuleSet constructor({ start, end, recurrences }: CalendarEventArgs) { - if ((start.hour === undefined || start.minute === undefined) && !start.dateTime) { - throw new Error('invalid "start" value. Either spcify dateTime or hour/minutes') - } - if ((end.hour === undefined || end.minute === undefined) && !end.dateTime) { - throw new Error('invalid "end" value. Either spcify dateTime or hour/minutes') - } + validateCalendarEventDef(start, 'start') + validateCalendarEventDef(end, 'end') this.start = dayjs.tz(start.dateTime || INITIAL_DATE, start.tz || 'UTC') - if (Number.isInteger(start.hour) && Number.isInteger(start.minute)) { - this.start = this.start.hour(start.hour!).minute(start.minute!) + if (hasMinuteHour(start)) { + this.start = this.start.hour(start.hour).minute(start.minute) } this.end = dayjs.tz(end.dateTime || INITIAL_DATE, end.tz || 'UTC') - if (Number.isInteger(end.hour) && Number.isInteger(end.minute)) { - this.end = this.end.hour(end.hour!).minute(end.minute!) + if (hasMinuteHour(end)) { + this.end = this.end.hour(end.hour).minute(end.minute) } - console.log(this.start) - this.recurrences = new RRuleSet() recurrences.forEach((r) => this.recurrences.rrule(r)) @@ -97,8 +101,8 @@ export class CalendarEvent { } toText({ tz = 'UTC', joinDatesWith = ' and ', timeFormat = 'h:mm A' }: ToTextArgs = {}) { - const start = dayjs.tz(this.start, tz) - const end = dayjs.tz(this.end, tz) + const start = this.start.tz(tz) + const end = this.end.tz(tz) const occurencesText = this.recurrences .clone() From 71325080fb9136e3bf363a178bd4ead0ecdcecbb Mon Sep 17 00:00:00 2001 From: gosho-kazuya Date: Thu, 1 Oct 2020 05:35:51 -0700 Subject: [PATCH 4/4] fix commentout --- src/CalendarEvent.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CalendarEvent.test.ts b/src/CalendarEvent.test.ts index fa93c70..402c94f 100644 --- a/src/CalendarEvent.test.ts +++ b/src/CalendarEvent.test.ts @@ -114,7 +114,7 @@ test('CalendarEvent - toText format prev date timezone', (t) => { ) }) -test.only('CalendarEvent - summer time', (t) => { +test('CalendarEvent - summer time', (t) => { const event = new CalendarEvent({ start: { hour: 10, @@ -134,8 +134,8 @@ test.only('CalendarEvent - summer time', (t) => { }) // We expect same result even if summer time - // MockDate.set(WINTER) - // t.is(event.toText({ tz: 'America/Los_Angeles' }), '10:00 AM to 10:30 AM every day') + MockDate.set(WINTER) + t.is(event.toText({ tz: 'America/Los_Angeles' }), '10:00 AM to 10:30 AM every day') MockDate.set(SUMMER) t.is(event.toText({ tz: 'America/Los_Angeles' }), '10:00 AM to 10:30 AM every day')