From af2351304d481aaa33139c7c9b1be630fc5cc614 Mon Sep 17 00:00:00 2001 From: jshor Date: Sat, 26 Sep 2020 00:14:12 -0400 Subject: [PATCH] refactor: removes ability to specify time as strings BREAKING CHANGE: Removes ability to pass `start`, `end`, or `recurrence.end` as strings. They must now be passed as Date objects. --- src/CalendarBase.ts | 8 ++-- src/GoogleCalendar.ts | 4 +- src/ICalendar.ts | 4 +- src/OutlookCalendar.ts | 4 +- src/YahooCalendar.ts | 8 ++-- src/__tests__/CalendarBase.spec.ts | 3 +- src/__tests__/GoogleCalendar.spec.ts | 4 +- src/__tests__/ICalendar.spec.ts | 4 +- src/__tests__/OutlookCalendar.spec.ts | 12 ++--- src/__tests__/YahooCalendar.spec.ts | 69 ++++++--------------------- src/interfaces/ICalendarBase.ts | 13 +++-- src/interfaces/IOptions.ts | 5 +- src/interfaces/IRecurrence.ts | 2 +- src/utils/__tests__/ics.spec.ts | 4 +- src/utils/__tests__/time.spec.ts | 32 ++++++++++++- src/utils/ics.ts | 2 +- src/utils/time.ts | 50 +++++-------------- 17 files changed, 96 insertions(+), 132 deletions(-) diff --git a/src/CalendarBase.ts b/src/CalendarBase.ts index e37c0993..69161356 100644 --- a/src/CalendarBase.ts +++ b/src/CalendarBase.ts @@ -84,10 +84,10 @@ class CalendarBase implements ICalendarBase { */ setTimestamps (options: IOptions): void { this.allday = !options.end - this.start = time.parseDate(options.start) + this.start = options.start - if (this.end) { - this.end = time.parseDate(options.end) + if (options.end) { + this.end = options.end } else { // if allday is specified, make the end date exactly 1 day from the start date this.end = time.incrementDate(this.start, 1) @@ -96,7 +96,7 @@ class CalendarBase implements ICalendarBase { this.recurrence = options.recurrence if (this.recurrence && this.recurrence.end) { - this.recurrence.end = time.parseDate(this.recurrence.end) + this.recurrence.end = this.recurrence.end } } } diff --git a/src/GoogleCalendar.ts b/src/GoogleCalendar.ts index 45255c57..2f98efc4 100644 --- a/src/GoogleCalendar.ts +++ b/src/GoogleCalendar.ts @@ -34,8 +34,8 @@ export default class GoogleCalendar extends CalendarBase { details: this.description, location: this.location, dates: [ - time.formatTimestampDate(this.start, timestampFormat), - time.formatTimestampDate(this.end, timestampFormat) + time.formatDate(this.start, timestampFormat), + time.formatDate(this.end, timestampFormat) ].join('/') } diff --git a/src/ICalendar.ts b/src/ICalendar.ts index f24147d9..a181d77c 100644 --- a/src/ICalendar.ts +++ b/src/ICalendar.ts @@ -36,8 +36,8 @@ export default class ICalendar extends CalendarBase { const event = [ 'CLASS:PUBLIC', `DESCRIPTION:${description}`, - `DTSTART:${time.formatTimestampDate(this.start, FORMAT.FULL)}`, - `DTEND:${time.formatTimestampDate(this.end, FORMAT.FULL)}`, + `DTSTART:${time.formatDate(this.start, FORMAT.FULL)}`, + `DTEND:${time.formatDate(this.end, FORMAT.FULL)}`, `LOCATION:${location}`, `SUMMARY:${summary}`, 'TRANSP:TRANSPARENT' diff --git a/src/OutlookCalendar.ts b/src/OutlookCalendar.ts index 7a7c74a8..20834319 100644 --- a/src/OutlookCalendar.ts +++ b/src/OutlookCalendar.ts @@ -33,8 +33,8 @@ export default class OutlookCalendar extends CalendarBase { const params: Record = { rru: 'addevent', - startdt: time.formatTimestampDate(this.start, timestampFormat), - enddt: time.formatTimestampDate(this.end, timestampFormat), + startdt: time.formatDate(this.start, timestampFormat), + enddt: time.formatDate(this.end, timestampFormat), subject: this.title, body: this.description, location: this.location, diff --git a/src/YahooCalendar.ts b/src/YahooCalendar.ts index 858483e8..42ec7db0 100644 --- a/src/YahooCalendar.ts +++ b/src/YahooCalendar.ts @@ -159,13 +159,13 @@ export default class YahooCalendar extends CalendarBase { if (this.allday) { params.dur = 'allday' - params.st = time.formatTimestampDate(this.start, FORMAT.DATE) + params.st = time.formatDate(this.start, FORMAT.DATE) } else { - params.st = time.formatTimestampDate(this.start, FORMAT.FULL) + params.st = time.formatDate(this.start, FORMAT.FULL) if (this.getHoursDuration(this.start.getTime(), this.end.getTime()) > 99) { // Yahoo only supports up to 99 hours, so we are forced to specify the end time instead of the duration - params.et = time.formatTimestampDate(this.end, FORMAT.FULL) + params.et = time.formatDate(this.end, FORMAT.FULL) } else { // we prefer specifying duration in lieu of end time, because apparently Yahoo's end time is buggy w.r.t. timezones params.dur = this.getDuration(this.start.getTime(), this.end.getTime()) @@ -181,7 +181,7 @@ export default class YahooCalendar extends CalendarBase { const days = this.getRecurrenceLengthDays(this.recurrence) const rend = time.incrementDate(this.end, Math.ceil(days)) - params.REND = time.formatTimestampDate(rend, FORMAT.DATE) + params.REND = time.formatDate(rend, FORMAT.DATE) } } diff --git a/src/__tests__/CalendarBase.spec.ts b/src/__tests__/CalendarBase.spec.ts index 2ebe7700..23640d71 100644 --- a/src/__tests__/CalendarBase.spec.ts +++ b/src/__tests__/CalendarBase.spec.ts @@ -113,8 +113,7 @@ describe('Calendar Base', () => { it('should set allday to true', () => { calendarObj.setTimestamps({ - start: new Date('2019-03-23T17:00:00.000'), - end: '' + start: new Date('2019-03-23T17:00:00.000') }) expect(calendarObj.allday).toBe(true) diff --git a/src/__tests__/GoogleCalendar.spec.ts b/src/__tests__/GoogleCalendar.spec.ts index a9fe0556..1ee65080 100644 --- a/src/__tests__/GoogleCalendar.spec.ts +++ b/src/__tests__/GoogleCalendar.spec.ts @@ -59,9 +59,9 @@ describe('GoogleCalendar', () => { const paramsObj = queryString.parse(result.split('?')[1]) const expectedDates = `${ - time.formatTimestampDate(testObj.start, FORMAT.DATE) + time.formatDate(testObj.start, FORMAT.DATE) }/${ - time.formatTimestampDate(testObj.end, FORMAT.DATE) + time.formatDate(testObj.end, FORMAT.DATE) }` expect(paramsObj.dates).toBe(expectedDates) diff --git a/src/__tests__/ICalendar.spec.ts b/src/__tests__/ICalendar.spec.ts index 794dc5bc..29a0e095 100644 --- a/src/__tests__/ICalendar.spec.ts +++ b/src/__tests__/ICalendar.spec.ts @@ -103,8 +103,8 @@ describe('ICalendar', () => { 'BEGIN:VEVENT', 'CLASS:PUBLIC', `DESCRIPTION:${baseOpts.description}`, - `DTSTART:${time.formatTimestampString(new Date(baseOpts.start), FORMAT.FULL)}`, - `DTEND:${time.formatTimestampString(new Date(baseOpts.end), FORMAT.FULL)}`, + `DTSTART:${time.formatDate(new Date(baseOpts.start), FORMAT.FULL)}`, + `DTEND:${time.formatDate(new Date(baseOpts.end), FORMAT.FULL)}`, `LOCATION:${baseOpts.location}`, `SUMMARY:${baseOpts.title}`, 'TRANSP:TRANSPARENT', diff --git a/src/__tests__/OutlookCalendar.spec.ts b/src/__tests__/OutlookCalendar.spec.ts index d2f1e59e..bb7bc568 100644 --- a/src/__tests__/OutlookCalendar.spec.ts +++ b/src/__tests__/OutlookCalendar.spec.ts @@ -11,13 +11,13 @@ describe('Outlook Calendar', () => { title: 'Fun Party', description: 'BYOB', location: 'New York', - start: '2019-07-04T19:00:00.000' + start: new Date('2019-07-04T19:00:00.000') })).toBeInstanceOf(CalendarBase) }) describe('render()', () => { const testOpts: IOptions = { - start: '2019-03-23T17:00:00.000' + start: new Date('2019-03-23T17:00:00.000') } beforeEach(() => { @@ -45,8 +45,8 @@ describe('Outlook Calendar', () => { expect(paramsObj).toMatchObject({ path: '/calendar/action/compose', rru: 'addevent', - startdt: time.formatTimestampDate(obj.start, FORMAT.OUTLOOK_FULL), - enddt: time.formatTimestampDate(obj.end, FORMAT.OUTLOOK_FULL), + startdt: time.formatDate(obj.start, FORMAT.OUTLOOK_FULL), + enddt: time.formatDate(obj.end, FORMAT.OUTLOOK_FULL), subject: testOpts.title, body: testOpts.description, location: testOpts.location, @@ -67,8 +67,8 @@ describe('Outlook Calendar', () => { expect(paramsObj).toMatchObject({ path: '/calendar/action/compose', rru: 'addevent', - startdt: time.formatTimestampDate(obj.start, FORMAT.OUTLOOK_FULL), - enddt: time.formatTimestampDate(obj.end, FORMAT.OUTLOOK_FULL), + startdt: time.formatDate(obj.start, FORMAT.OUTLOOK_FULL), + enddt: time.formatDate(obj.end, FORMAT.OUTLOOK_FULL), subject: testOpts.title, body: testOpts.description, location: testOpts.location, diff --git a/src/__tests__/YahooCalendar.spec.ts b/src/__tests__/YahooCalendar.spec.ts index 3e5e4b86..32d25f6c 100644 --- a/src/__tests__/YahooCalendar.spec.ts +++ b/src/__tests__/YahooCalendar.spec.ts @@ -8,15 +8,6 @@ import IOptions from '../interfaces/IOptions' const { FREQUENCY: { DAILY, WEEKLY, MONTHLY } } = RECURRENCE -<<<<<<< HEAD -======= -const yahooFreqMap = { - [DAILY]: 'Dy', - [WEEKLY]: 'Wk', - [MONTHLY]: 'Mh', - [YEARLY]: 'Yr' -} ->>>>>>> 878ccf7... refactor(ts): adds ESLint, TypeDoc describe('YahooCalendar', () => { let testOpts: IOptions @@ -74,17 +65,8 @@ describe('YahooCalendar', () => { expect(obj.getFrequency(FREQUENCY.WEEKLY)).toEqual('Wk') }) -<<<<<<< HEAD it('should default to daily recurrences', () => { expect(obj.getFrequency(FREQUENCY.DAILY)).toEqual('Dy') -======= - for (const frequency of [ DAILY, WEEKLY, MONTHLY, YEARLY, 'foobar' ]) { - const result = obj.getFrequency({ frequency }) - const expected = yahooFreqMap[frequency] || yahooFreqMap[WEEKLY] - expect(result).toBe(expected) - } - }) ->>>>>>> 878ccf7... refactor(ts): adds ESLint, TypeDoc }) }) @@ -239,7 +221,7 @@ describe('YahooCalendar', () => { title: 'Fun Party', description: 'BYOB', location: 'New York', - start: '2019-07-04T19:00:00.000' + start: new Date('2019-07-04T19:00:00.000') } describe('no recurrence', () => { @@ -255,7 +237,7 @@ describe('YahooCalendar', () => { desc: 'BYOB', in_loc: 'New York', dur: 'allday', - st: time.formatTimestampString(testOpts.start, FORMAT.DATE) + st: time.formatDate(obj.start, FORMAT.DATE) } expect(params).toMatchObject(expectedObj) expect(expectedObj).toMatchObject(params) @@ -264,7 +246,7 @@ describe('YahooCalendar', () => { describe('recurrence', () => { it('should format the query string with start and end times containing only their dates', () => { - const recurrenceEnd = '2019-07-10T19:00:00.000' + const recurrenceEnd = new Date('2019-07-10T19:00:00.000') const obj = new YahooCalendar({ ...testOpts, recurrence: { @@ -283,15 +265,9 @@ describe('YahooCalendar', () => { desc: 'BYOB', in_loc: 'New York', dur: 'allday', -<<<<<<< HEAD - st: time.formatTimestampString(testOpts.start, FORMAT.FULL), - RPAT: '01Dy', - REND: time.formatTimestampString(recurrenceEnd, FORMAT.FULL), -======= - st: time.formatTimestampString(testOpts.start, 'YYYYMMDD'), + st: time.formatDate(testOpts.start, FORMAT.DATE), RPAT: '01Dy', - REND: time.formatTimestampString(recurrenceEnd, 'YYYYMMDD') ->>>>>>> 878ccf7... refactor(ts): adds ESLint, TypeDoc + REND: time.formatDate(recurrenceEnd, FORMAT.DATE) } expect(params).toMatchObject(expectedObj) }) @@ -303,8 +279,8 @@ describe('YahooCalendar', () => { title: 'Fun Party', description: 'BYOB', location: 'New York', - start: '2019-07-04T19:00:00.000', - end: '2019-07-04T21:00:00.000' // two-hour long event (19h to 21h) + start: new Date('2019-07-04T19:00:00.000'), + end: new Date('2019-07-04T21:00:00.000') // two-hour long event (19h to 21h) } describe('no recurrence', () => { @@ -320,11 +296,7 @@ describe('YahooCalendar', () => { title: 'Fun Party', desc: 'BYOB', in_loc: 'New York', -<<<<<<< HEAD - st: time.formatTimestampString(testOpts.start, FORMAT.FULL), -======= - st: time.formatTimestampString(testOpts.start, 'YYYYMMDDThhmmss'), ->>>>>>> 878ccf7... refactor(ts): adds ESLint, TypeDoc + st: time.formatDate(testOpts.start, FORMAT.FULL), dur: '0200' } expect(params).toMatchObject(expectedObj) @@ -334,8 +306,8 @@ describe('YahooCalendar', () => { describe('when the duration of the event spans longer than 99 hours', () => { it('should format the query string with the time parameters in start/end timestamps', () => { - const start = '2019-07-04T19:00:00.000' - const end = '2019-07-08T23:00:00.000' // one-hundred-hour long event, (four days, three hours) + const start = new Date('2019-07-04T19:00:00.000') + const end = new Date('2019-07-08T23:00:00.000') // one-hundred-hour long event, (four days, three hours) const obj = new YahooCalendar({ ...testOpts, start, end }) const result = obj.render() @@ -347,13 +319,8 @@ describe('YahooCalendar', () => { title: 'Fun Party', desc: 'BYOB', in_loc: 'New York', -<<<<<<< HEAD - st: time.formatTimestampString(start, FORMAT.FULL), - et: time.formatTimestampString(end, FORMAT.FULL) -======= - st: time.formatTimestampString(start, 'YYYYMMDDThhmmss'), - et: time.formatTimestampString(end, 'YYYYMMDDThhmmss') ->>>>>>> 878ccf7... refactor(ts): adds ESLint, TypeDoc + st: time.formatDate(start, FORMAT.FULL), + et: time.formatDate(end, FORMAT.FULL) } expect(params).toMatchObject(expectedObj) expect(expectedObj).toMatchObject(params) @@ -363,7 +330,7 @@ describe('YahooCalendar', () => { describe('recurrence', () => { it('should format the query string with RPAT and REND params', () => { - const recurrenceEnd = '2019-07-10T19:00:00.000' + const recurrenceEnd = new Date('2019-07-10T19:00:00.000') const obj = new YahooCalendar({ ...testOpts, recurrence: { @@ -382,15 +349,9 @@ describe('YahooCalendar', () => { desc: 'BYOB', in_loc: 'New York', dur: '0200', -<<<<<<< HEAD - st: time.formatTimestampString(testOpts.start, FORMAT.FULL), - RPAT: '01Dy', - REND: time.formatTimestampString(recurrenceEnd, FORMAT.DATE) -======= - st: time.formatTimestampString(testOpts.start, 'YYYYMMDDThhmmss'), + st: time.formatDate(testOpts.start, FORMAT.FULL), RPAT: '01Dy', - REND: time.formatTimestampString(recurrenceEnd, 'YYYYMMDD') ->>>>>>> 878ccf7... refactor(ts): adds ESLint, TypeDoc + REND: time.formatDate(recurrenceEnd, FORMAT.DATE) } expect(params).toMatchObject(expectedObj) }) diff --git a/src/interfaces/ICalendarBase.ts b/src/interfaces/ICalendarBase.ts index 2a6597f9..1cc4abd1 100644 --- a/src/interfaces/ICalendarBase.ts +++ b/src/interfaces/ICalendarBase.ts @@ -4,11 +4,16 @@ import IRecurrence from './IRecurrence' * CalendarBase properties */ export default interface ICalendarBase { - allday: boolean + /** The event description. */ description: string + /** The event title (i.e., summary). */ + title: string + /** A summary description of the event location. */ location: string - start: Date | string - end?: Date | string + /** The event start timestamp. */ + start: Date + /** The event end timestamp. For all-day events, this field should be omitted. */ + end: Date + /** The recurrence of an event is how often the event is supposed to occur. See {@link IRecurrence}. */ recurrence?: IRecurrence - title: string } diff --git a/src/interfaces/IOptions.ts b/src/interfaces/IOptions.ts index 747d6f0f..99ae3653 100644 --- a/src/interfaces/IOptions.ts +++ b/src/interfaces/IOptions.ts @@ -11,10 +11,9 @@ export default interface IOptions { /** A summary description of the event location. */ location?: string /** The event start timestamp. */ - start: string | Date - /** The event end timestamp. For all-day events, this field should be omitted. */ - end?: string | Date + start: Date /** The event end timestamp. For all-day events, this field should be omitted. */ + end?: Date /** The recurrence of an event is how often the event is supposed to occur. See {@link IRecurrence}. */ recurrence?: IRecurrence } diff --git a/src/interfaces/IRecurrence.ts b/src/interfaces/IRecurrence.ts index dbd1c872..07e16e0d 100644 --- a/src/interfaces/IRecurrence.ts +++ b/src/interfaces/IRecurrence.ts @@ -9,7 +9,7 @@ export default interface IRecurrence { /** The maximum number of times the event should repeat. */ count?: number /** The latest date that this event may occur on. */ - end?: Date | string + end?: Date /** The day of the week to denote when the the week starts on. */ weekstart?: string /** The days of the week that the event should occur on. */ diff --git a/src/utils/__tests__/ics.spec.ts b/src/utils/__tests__/ics.spec.ts index c7df34b8..00bb8f81 100644 --- a/src/utils/__tests__/ics.spec.ts +++ b/src/utils/__tests__/ics.spec.ts @@ -93,7 +93,7 @@ describe('IcsUtil', () => { interval: 1, count: 5, weekstart: 'MO', - end: '2019-05-02', + end: new Date('2019-05-02'), weekdays: 'MO', monthdays: '5' } @@ -104,7 +104,7 @@ describe('IcsUtil', () => { 'WKST=MO', 'BYDAY=MO', 'BYMONTHDAY=5', - `UNTIL=${time.formatTimestampString(recurrence.end, 'YYYYMMDDThhmmss')}` + `UNTIL=${time.formatDate(recurrence.end, 'YYYYMMDDThhmmss')}` ].join(';') const actualRrule = ics.getRrule(recurrence) diff --git a/src/utils/__tests__/time.spec.ts b/src/utils/__tests__/time.spec.ts index 88678bc8..c59078d6 100644 --- a/src/utils/__tests__/time.spec.ts +++ b/src/utils/__tests__/time.spec.ts @@ -1,4 +1,5 @@ <<<<<<< HEAD +<<<<<<< HEAD ======= // import * as moment from 'moment' >>>>>>> 878ccf7... refactor(ts): adds ESLint, TypeDoc @@ -20,6 +21,12 @@ describe('time util', () => { }) }) +======= +import { FORMAT } from '../../constants' +import time from '../time' + +describe('time utils', () => { +>>>>>>> d8c964c... refactor: removes ability to specify time as strings describe('getTimeCreated()', () => { beforeEach(() => { jest.useFakeTimers() @@ -29,12 +36,33 @@ describe('time util', () => { jest.useRealTimers() }) +<<<<<<< HEAD // xit('should get the current time in date time format', () => { // const now = new moment() // const expectedOutput = now.format(FORMAT.DATE) +======= + it('should get the current time in date time format', () => { + expect(time.getTimeCreated()).toBe(time.formatDate(new Date(), FORMAT.DATE)) + }) + }) - // expect(getTimeCreated()).toBe(expectedOutput) - // }) + describe('formatDate()', () => { + it('should format the timestamp with the given formats', () => { + const timestamp = '2019-03-23T17:00:00-03:00' + const actualDateOutput = time.formatDate(new Date(timestamp), FORMAT.DATE) + + expect(actualDateOutput).toBe('20190323') + }) + }) + + describe('incrementDate()', () => { + it('should increment the date by one day', () => { + const incrementedDate = time.incrementDate(new Date('2020-04-01'), 1) + const formattedDate = time.formatDate(incrementedDate, 'YYYY-MM-DD') +>>>>>>> d8c964c... refactor: removes ability to specify time as strings + + expect(formattedDate).toEqual('2020-04-02') + }) }) }) diff --git a/src/utils/ics.ts b/src/utils/ics.ts index 58b3c35c..dde4f008 100644 --- a/src/utils/ics.ts +++ b/src/utils/ics.ts @@ -76,7 +76,7 @@ const getRrule = (recurrence: IRecurrence): string => { } if (recurrence.end) { - rrule.UNTIL = time.formatTimestampString(recurrence.end, 'YYYYMMDDThhmmss') + rrule.UNTIL = time.formatDate(recurrence.end, 'YYYYMMDDThhmmss') } return data.toIcsParamString(rrule) diff --git a/src/utils/time.ts b/src/utils/time.ts index 812f0ab6..b4d2d52d 100644 --- a/src/utils/time.ts +++ b/src/utils/time.ts @@ -1,5 +1,4 @@ import { FORMAT } from '../constants' -import warn from './warn' /** * Adds a leading zero to a single-digit string and returns a two-digit string. @@ -19,8 +18,13 @@ const addLeadingZero = (n: number | string): string => { * @param {string} format * @returns {string} */ +<<<<<<< HEAD const formatTimestampDate = (d: Date = new Date(), format: string): string => { const dateValues = { +======= +const formatDate = (d: Date = new Date(), format: string): string => { + const dateValues: Record = { +>>>>>>> d8c964c... refactor: removes ability to specify time as strings YYYY: d.getUTCFullYear(), MM: addLeadingZero(d.getUTCMonth() + 1), DD: addLeadingZero(d.getUTCDate()), @@ -36,40 +40,13 @@ const formatTimestampDate = (d: Date = new Date(), format: string): string => { }, format) } -/** - * Parses the given string as a JS Date() object. - * - * @param {string} str - * @returns {date | string} date - * @returns {Date} - */ -const parseDate = (date: Date | string) => { - if (typeof date === 'string') { - warn('Passing in `date` as a string') - return new Date(date) - } - - return date -} - -/** - * Formats the given timestamp. - * - * @param {string} time - * @param {string} format - momentjs format - * @returns {string} - */ -const formatTimestampString = (str: string, format: string): string => { - return formatTimestampDate(parseDate(str), format) -} - /** * Returns the current timestamp. * * @returns {string} */ const getTimeCreated = (): string => { - return formatTimestampDate(new Date(), FORMAT.DATE) + return formatDate(new Date(), FORMAT.DATE) } /** @@ -77,26 +54,21 @@ const getTimeCreated = (): string => { * This will account for edge cases, such as leap years. * * @param {Date} dateInput - date to increment - * @param {number} increment - number of days + * @param {number} days - number of days * @returns {Date} */ -const incrementDate = (dateInput: Date | string, increment: number): Date => { - const additionalTime = increment * 86400000 +const incrementDate = (dateInput: Date, days: number): Date => { + const additionalTime = days * 86400000 const newDate = new Date() - const curTime = typeof dateInput === 'string' - ? new Date(dateInput) - : dateInput - newDate.setTime(curTime.getTime() + additionalTime) + newDate.setTime(dateInput.getTime() + additionalTime) return newDate } export default { addLeadingZero, - formatTimestampDate, - parseDate, - formatTimestampString, + formatDate, getTimeCreated, incrementDate }