Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

provide addInTimeZone function #225

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@
"import": "./esm/zonedTimeToUtc/index.js",
"require": "./zonedTimeToUtc/index.js"
},
"./addInTimeZone": {
"types": "./addInTimeZone/index.d.ts",
"import": "./esm/addInTimeZone/index.js",
"require": "./addInTimeZone/index.js"
},
"./fp": {
"types": "./fp/index.d.ts",
"import": "./esm/fp/index.js",
Expand Down Expand Up @@ -94,6 +99,11 @@
"types": "./fp/zonedTimeToUtc/index.d.ts",
"import": "./esm/fp/zonedTimeToUtc/index.js",
"require": "./fp/zonedTimeToUtc/index.js"
},
"./fp/addInTimeZone": {
"types": "./fp/addInTimeZone/index.d.ts",
"import": "./esm/fp/addInTimeZone/index.js",
"require": "./fp/addInTimeZone/index.js"
}
},
"scripts": {
Expand Down
64 changes: 64 additions & 0 deletions src/addInTimeZone/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import dateFnsAdd from 'date-fns/add/index.js'
import toDate from '../toDate/index.js'
import formatInTimeZone from '../formatInTimeZone/index.js'

/**
* @name addInTimeZone
* @category Common Helpers
* @summary Add the specified years, months, weeks, days, hours, minutes and seconds to the given date, in the given time zone.
*
* @description
* Add the specified years, months, weeks, days, hours, minutes and seconds to the given date, in the given time zone.
* The time zone can make a difference because of Daylight Savings Time.
* At 11pm Nov 4 2022 in LA, +1 day (= 11pm Nov 5) means +24 hrs.
* But at the same time in Halifax (3am Nov 5) +1 day (= 3am Nov 6) means +25 hrs,
* because the clocks fall back 1 hr at 2am.
*
* @param date - the date to be changed
* @param timeZone - the time zone to do the calculation in; can be an offset or IANA time zone
* @param duration - the object with years, months, weeks, days, hours, minutes and seconds to be added. Positive decimals will be rounded using `Math.floor`, decimals less than zero will be rounded using `Math.ceil`.
*
* | Key | Description |
* |----------------|------------------------------------|
* | years | Amount of years to be added |
* | months | Amount of months to be added |
* | weeks | Amount of weeks to be added |
* | days | Amount of days to be added |
* | hours | Amount of hours to be added |
* | minutes | Amount of minutes to be added |
* | seconds | Amount of seconds to be added |
*
* All values default to 0
*
* @returns the new date with the duration added
*
* @example
* // 11pm Nov 4 in LA === 3am Nov 5 in Halifax (UTC 2022-11-05T06:00Z).
* // In LA, +1 day === 11pm Nov 5 === +24 hours,
* // but in Halifax +1 day === 3am Nov 6 === +25 hours,
* // because the clocks fall back 1 hour (in both places) at 2am Nov 6.
* const HOUR = 60 * 60 * 1000;
* const start = new Date('2022-11-05T06:00Z');
* const resultLA = addInTimeZone(start, 'America/Los_Angeles', { days: 1 });
* const resultHalifax = addInTimeZone(start, 'America/Halifax', { days: 1 });
* console.log((resultLA.getTime() - start.getTime()) / HOUR); // => 24
* console.log((resultHalifax.getTime() - start.getTime()) / HOUR); // => 25
*/
export default function addInTimeZone(dirtyDate, timeZone, duration) {
const { years, months, weeks, days, hours, minutes, seconds } = duration

// separate date and time portions
const start = toDate(dirtyDate, { timeZone })
const ymd = formatInTimeZone(start, timeZone, 'yyyy-MM-dd')
const hms = formatInTimeZone(start, timeZone, 'HH:mm:ss.SSS')

// add days and larger units to the date portion to get the target day
const targetDay = dateFnsAdd(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 = toDate(newYmd + ' ' + hms, { timeZone })

// now add hours and smaller units
return dateFnsAdd(targetDayWithStartTime, { hours, minutes, seconds })
}
96 changes: 96 additions & 0 deletions src/addInTimeZone/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// @flow
/* eslint-env mocha */

import assert from 'power-assert'
import formatInTimeZone from '../formatInTimeZone'
import addInTimeZone from '.'

describe('addInTimeZone', () => {
const fmt = 'yyyy-MM-dd HH:mm:ss.SSS'

it('adds days to a date, with timezone awareness', () => {
// add a day to this time in Halifax and you cross a DST boundary; in LA you don't
const start = '2022-11-05T06:12:34.567Z'
const startTime = new Date(start).getTime()

// Halifax
assert(formatInTimeZone(start, 'America/Halifax', fmt) === '2022-11-05 03:12:34.567')
const oneDayLaterInHalifax = addInTimeZone(start, 'America/Halifax', { days: 1 })
// +25 hrs in Halifax because clock fell back at 2am
assert(oneDayLaterInHalifax.getTime() - startTime === 25 * 60 * 60 * 1000)
assert(
formatInTimeZone(oneDayLaterInHalifax, 'America/Halifax', fmt) === '2022-11-06 03:12:34.567'
)

// LA
assert(formatInTimeZone(start, 'America/Los_Angeles', fmt) === '2022-11-04 23:12:34.567')
const oneDayLaterInLA = addInTimeZone(start, 'America/Los_Angeles', { days: 1 })
// +24 hrs in LA because clock didn't fall back yet
assert(oneDayLaterInLA.getTime() - startTime === 24 * 60 * 60 * 1000)
assert(
formatInTimeZone(oneDayLaterInLA, 'America/Los_Angeles', fmt) === '2022-11-05 23:12:34.567'
)

// to sum up,
assert(oneDayLaterInHalifax.getTime() === oneDayLaterInLA.getTime() + 60 * 60 * 1000)
})

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
assert(formatInTimeZone(start, 'America/Halifax', fmt) === '2022-11-05 03:12:34.567')
const laterInHalifax = addInTimeZone(start, 'America/Halifax', { days: 1, hours: 3 })
// + 28 hrs
assert(laterInHalifax.getTime() - startTime === 28 * 60 * 60 * 1000)
// clock time is +3 hrs
assert(formatInTimeZone(laterInHalifax, 'America/Halifax', fmt) === '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
assert(formatInTimeZone(start, 'America/Los_Angeles', fmt) === '2022-11-04 23:12:34.567')
const laterInLA = addInTimeZone(start, 'America/Los_Angeles', { days: 1, hours: 3 })
// + 27 hrs
assert(laterInLA.getTime() - startTime === 27 * 60 * 60 * 1000)
// clock time is +2 hrs
assert(formatInTimeZone(laterInLA, 'America/Los_Angeles', fmt) === '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
assert(formatInTimeZone(start, 'America/Halifax', fmt) === '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
assert(oneDayLaterInHalifax.getTime() - startTime === (31 * 24 + 1) * 60 * 60 * 1000)
assert(
formatInTimeZone(oneDayLaterInHalifax, 'America/Halifax', fmt) === '2022-11-06 03:12:34.567'
)

// LA
assert(formatInTimeZone(start, 'America/Los_Angeles', fmt) === '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
assert(oneDayLaterInLA.getTime() - startTime === 31 * 24 * 60 * 60 * 1000)
assert(
formatInTimeZone(oneDayLaterInLA, 'America/Los_Angeles', fmt) === '2022-11-05 23:12:34.567'
)

// to sum up,
assert(oneDayLaterInHalifax.getTime() === oneDayLaterInLA.getTime() + 60 * 60 * 1000)
})
})
8 changes: 8 additions & 0 deletions src/fp/addInTimeZone/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// This file is generated automatically by `scripts/build/fp.js`. Please, don't change it.

import fn from '../../addInTimeZone/index.js'
import convertToFP from 'date-fns/fp/_lib/convertToFP/index.js'

var addInTimeZone = convertToFP(fn, 3)

export default addInTimeZone