Skip to content

Instantly share code, notes, and snippets.

@dr-skot
Last active February 6, 2023 17:59
Show Gist options
  • Save dr-skot/df3af7a3118bbaea9efa07a97c8b7ae2 to your computer and use it in GitHub Desktop.
Save dr-skot/df3af7a3118bbaea9efa07a97c8b7ae2 to your computer and use it in GitHub Desktop.
addInTimeZone: timezone-aware version of the date-fns `add` function
import addInTimeZone from './addInTimeZone';
import { formatInTimeZone } from 'date-fns-tz';
it('adds days + hours to a date, with timezone awareness', () => {
// add a day plus 3 hours to this time, and you'll cross a DST boundary
// in both Halifax and LA
const start = '2022-11-05T06:12:34.567Z';
const startTime = new Date(start).getTime();
// but in Halifax you cross it adding the day; in LA you cross it adding hours
// i.e. in Halifax you add a 25-hr day plus 3 hrs = 28 hrs, and clock time is +3 hrs
// in LA you add a 24-hr day plus 3 hrs = 27 hrs, and clock time is +2 hrs
// Halifax: 11/05 3:12am
// + 1 day = 11/06 3:12am (25 hrs because clock fell back at 2am)
// + 3 hrs = 11/06 6:12am
// total 28 hrs
expect(formatInTimeZone(start, 'America/Halifax', fmt)).toBe('2022-11-05 03:12:34.567');
const laterInHalifax = addInTimeZone(start, 'America/Halifax', { days: 1, hours: 3 });
// + 28 hrs
expect(laterInHalifax.getTime() - startTime).toBe(28 * 60 * 60 * 1000);
// clock time is +3 hrs
expect(formatInTimeZone(laterInHalifax, 'America/Halifax', fmt)).toBe(
'2022-11-06 06:12:34.567'
);
// LA: 11/04 11:12pm
// + 1 day = 11/05 11:12pm (24 hrs because clock didn't fall back yet)
// + 3 hrs = 11/06 1:12am (fall back at 2am)
// total 27 hrs
expect(formatInTimeZone(start, 'America/Los_Angeles', fmt)).toBe('2022-11-04 23:12:34.567');
const laterInLA = addInTimeZone(start, 'America/Los_Angeles', { days: 1, hours: 3 });
// + 27 hrs
expect(laterInLA.getTime() - startTime).toBe(27 * 60 * 60 * 1000);
// clock time is +2 hrs
expect(formatInTimeZone(laterInLA, 'America/Los_Angeles', fmt)).toBe('2022-11-06 01:12:34.567');
});
it('adds months with timezone awareness', () => {
// add a month to this time in Halifax and you cross a DST boundary; in LA you don't
const start = '2022-10-06T06:12:34.567Z';
const startTime = new Date(start).getTime();
// Halifax
expect(formatInTimeZone(start, 'America/Halifax', fmt)).toBe('2022-10-06 03:12:34.567');
const oneDayLaterInHalifax = addInTimeZone(start, 'America/Halifax', { months: 1 });
// +31 days + 1 hr in Halifax, because clock fell back at 2am
expect(oneDayLaterInHalifax.getTime() - startTime).toBe((31 * 24 + 1) * 60 * 60 * 1000);
expect(formatInTimeZone(oneDayLaterInHalifax, 'America/Halifax', fmt)).toBe(
'2022-11-06 03:12:34.567'
);
// LA
expect(formatInTimeZone(start, 'America/Los_Angeles', fmt)).toBe('2022-10-05 23:12:34.567');
const oneDayLaterInLA = addInTimeZone(start, 'America/Los_Angeles', { months: 1 });
// +31 days + 0 hrs in LA, because clock didn't fall back yet
expect(oneDayLaterInLA.getTime() - startTime).toBe(31 * 24 * 60 * 60 * 1000);
expect(formatInTimeZone(oneDayLaterInLA, 'America/Los_Angeles', fmt)).toBe(
'2022-11-05 23:12:34.567'
);
// to sum up,
expect(oneDayLaterInHalifax.getTime()).toBe(oneDayLaterInLA.getTime() + 60 * 60 * 1000);
});
});
import * as dateFns from 'date-fns';
import dateFnsTz from 'date-fns-tz';
export function addInTimeZone(
dirtyDate: Date | string | number,
timeZone: string,
duration: dateFns.Duration
) {
const { years, months, weeks, days, hours, minutes, seconds } = duration;
// separate date and time portions
const start = dateFnsTz.toDate(dirtyDate, { timeZone });
const ymd = dateFnsTz.formatInTimeZone(start, timeZone, 'yyyy-MM-dd');
const hms = dateFnsTz.formatInTimeZone(start, timeZone, 'HH:mm:ss.SSS');
// add days and larger units to the date portion to get the target day
const targetDay = dateFns.add(new Date(ymd), { years, months, weeks, days });
const newYmd = targetDay.toISOString().slice(0, 10);
// combine the new date portion with the original time portion
const targetDayWithStartTime = dateFnsTz.toDate(newYmd + ' ' + hms, { timeZone });
// now add hours and smaller units
return dateFns.add(targetDayWithStartTime, { hours, minutes, seconds });
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment