diff --git a/README.md b/README.md index 9d6d78a..5965507 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,45 @@ -# rrule-contrib +# rrule-duration -[![npm version](https://badge.fury.io/js/rrule-contrib.svg)](https://badge.fury.io/js/rrule-contrib) +[![npm version](https://badge.fury.io/js/rrule-duration.svg)](https://badge.fury.io/js/rrule-duration) -[RRule](https://github.com/jakubroztocil/rrule) related utilities. +Add duration feature and time zone support to [RRule](https://github.com/jakubroztocil/rrule). + +# Features + +- Create events with duration and represent it with RRule +- Time zone support, even separate start/end time +- Dump duration as natural language # Install ``` -npm install --save rrule rrule-contrib +npm install --save rrule rrule-duration ``` Or if you use Yarn: ``` -yarn add rrule rrule-contrib +yarn add rrule rrule-duration ``` -# Usage - -Currently the following classes are exported: - -- CalendarEvent - -TODO: add more functionality. - -## `CalendarEvent` +# Basic Usage `CalendarEvent` class is intended to use `RRule` with calendar events. Basically an `event` is represented by `start`, `end` and `RRule`. Let's see an example event. ```typescript import { RRule } from 'rrule' -import { CalendarEvent } from 'rrule-contrib' +import { CalendarEvent } from 'rrule-duration' const event = new CalendarEvent({ start: { hour: 10, minute: 0, + tz: 'UTC', }, end: { - hour: 13, + hour: 22, minute: 30, + tz: 'Asia/Tokyo', }, recurrences: [ new RRule({ @@ -72,6 +72,10 @@ It will return the following array: ], ``` +Note it includes start and end in plain `Date` type. + +# Natural Language + You can also get event text as natural language (currently only in English) text. ```typescript @@ -79,7 +83,7 @@ event.toText() // => 10:00 AM to 13:30 AM every day on Monday, Friday and every month on the 25th ``` -**Time zone support** +# Time Zone Support The most powerful feature of `CalendarEvent` is strong time zone support. We, Remotehour, provides our service globally so this feature has been developed. In the above example, you can pass a `tz` option to `toText` method. diff --git a/package.json b/package.json index 93a79c4..02a8f8b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "rrule-contrib", - "version": "0.0.4", - "description": "RRule related utilities with timezone NLP support", + "name": "rrule-duration", + "version": "0.0.5", + "description": "RRule related utility with timezone NLP support", "repository": "git@github.com:remotehour/rrule-duration", "author": "gosho-kazuya ", "license": "MIT", diff --git a/src/CalendarEvent.test.ts b/src/CalendarEvent.test.ts index 402c94f..ae297f1 100644 --- a/src/CalendarEvent.test.ts +++ b/src/CalendarEvent.test.ts @@ -46,6 +46,44 @@ test('CalendarEvent - init with hour and minute, occurences', (t) => { ) }) +test('CalendarEvent - different time zone between start and end', (t) => { + const event = new CalendarEvent({ + start: { + hour: 10, + minute: 0, + tz: 'UTC', + }, + end: { + hour: 19, + minute: 30, + tz: 'Asia/Tokyo', + }, + recurrences: [ + new RRule({ + freq: RRule.MONTHLY, + dtstart: new Date('2020-09-27T09:08:24.000Z'), + bymonthday: [], + byweekday: [RRule.FR.nth(3)], + }), + ], + }) + + 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-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')], + [new Date('2020-12-18T10:00:00Z'), new Date('2020-12-18T10:30:00Z')], + ], + ) +}) + test('CalendarEvent - toText format & time zone', (t) => { const event = new CalendarEvent({ start: { diff --git a/src/CalendarEvent.ts b/src/CalendarEvent.ts index ad09771..eab9a02 100644 --- a/src/CalendarEvent.ts +++ b/src/CalendarEvent.ts @@ -40,10 +40,27 @@ function validateCalendarEventDef(e: CalendarEventDef, name: string) { } } -function hasMinuteHour(e: CalendarEventDef): e is { hour: number; minute: number } { +function hasMinuteHour(e: CalendarEventDef): e is { hour: number; minute: number; tz?: string } { return Number.isInteger(e.hour) && Number.isInteger(e.minute) } +// TODO: Better implementation? +function getOffsetFromTimeZone(d: dayjs.Dayjs) { + try { + const diffString = d.format('Z') + if (!diffString.includes('+')) { + return 0 + } + const [hour, minute] = diffString + .replace('+', '') + .split(':') + .map((x) => parseInt(x)) + return -1 * (hour * 60 + minute) + } catch { + return 0 + } +} + export class CalendarEvent { private start: dayjs.Dayjs private end: dayjs.Dayjs @@ -74,6 +91,7 @@ export class CalendarEvent { .utc(date) .hour(this.start.hour()) .minute(this.start.minute()) + .add(getOffsetFromTimeZone(this.start), 'minute') .second(0) .millisecond(0) .toDate() @@ -81,6 +99,7 @@ export class CalendarEvent { .utc(date) .hour(this.end.hour()) .minute(this.end.minute()) + .add(getOffsetFromTimeZone(this.end), 'minute') .second(0) .millisecond(0) .toDate()