我想在 Node.js 后端应用程序中使用 UTC 日期,但是,我需要能够在本地/用户指定的时区中设置时间(小时和分钟)。
我正在寻找纯 JS 或使用
dayjs
的解决方案。我不是在寻找使用 moment
的解决方案。
看起来使用
dayjs
我可以很容易地解决这个问题,但是,我找不到一种方法来完成这个任务。
dayjs.utc()
或使用 dayjs.tz(someDate, 'Etc/UTC')
来使用 UTC 时区。
使用
dayjs.utc()
时,我无法为任何东西使用/指定其他时区,因此我找不到方法来告诉dayjs
我想在特定(非UTC)时区设置小时/分钟。
使用
dayjs.tz()
时,我仍然无法定义要设置为特定日期的时间时区。
我的区域设置时区是
Europe/Slovakia
(CEST = UTC+02,带 DST,CET = UTC+1,不带 DST),但是,我希望它适用于任何时区。
// Expected outcome
// old: 2022-10-29T10:00:00.000Z
// new time: 10h 15m CEST
// new: 2022-10-29T08:15:00.000Z
// Plain JS
const now = new Date('2022-10-29T10:00:00.000Z')
const hours = 10
const minutes = 15
now.setHours(10)
now.setMinutes(15)
// As my default timezone is `Europe/Bratislava`, it seems to work as expected
console.log(now)
// Output: 2022-10-29T08:15:00.000Z
// However, it won't work with timezones other than my local timezone
更新:我在这个答案中发布了一个工作功能。
以下功能似乎适用于大多数测试用例,但是,对于我已知的 6 4 个案例,它失败了(非常感谢任何帮助):
now
在双小时之前为 DST,newDate
在双小时期间为 ST;now
双小时期间为 DST,双小时期间 newDate
ST;now
双小时内为 ST,双小时内 newDate
为 DST;now
在两小时后为 ST,newDate
在两小时内为 DST;now
,跳过的小时中的 ST 中的 newDate
;now
,跳过一小时后的 ST 中为
newDate
。
是指将夏令时更改为标准时间造成的情况,即将时钟调慢一小时(例如,凌晨3点到凌晨2点→双时是在02:00:00.000
和
02:59:59.999
之间,这两种情况都会发生在夏令时和标准时间) .
/**
* Set time provided in a timezone
*
* @param {Date} [dto.date = new Date()] Date object to work with
* @param {number} [dto.time.h = 0] Hour to set
* @param {number} [dto.time.m = 0] Minute to set
* @param {number} [dto.time.s = 0] Second to set
* @param {number} [dto.time.ms = 0] Millisecond to set
* @param {string} [dto.timezone = 'Europe/Bratislava'] Timezone of `dto.time`
*
* @return {Date} Date object
*/
function setLocalTime(dto = {
date: new Date(),
// TODO: Rename the property to `{h, m, s, ms}`.
time: {h: 0, m: 0, ms: 0, s: 0},
timezone: 'Europe/Bratislava'
}) {
const defaultTime = {h: 0, m: 0, ms: 0, s: 0}
const defaultTimeKeys = Object.keys(defaultTime)
// src: https://stackoverflow.com/a/44118363/3408342
if (!Intl || !Intl.DateTimeFormat().resolvedOptions().timeZone) {
throw new Error('`Intl` API is not available or it does not contain a list of timezone identifiers in this environment')
}
if (!(dto.date instanceof Date)) {
throw Error('`date` must be a `Date` object.')
}
try {
Intl.DateTimeFormat(undefined, {timeZone: dto.timezone})
} catch (e) {
throw Error('`timezone` must be a valid IANA timezone.')
}
if (
typeof dto.time !== 'undefined'
&& typeof dto.time !== 'object'
&& dto.time instanceof Object
&& Object.keys(dto.time).every(v => defaultTimeKeys.indexOf(v) !== -1)
) {
throw Error('`time` must be an object of `{h: number, m: number, s: number, ms: number}` format, where numbers should be valid time values.')
}
dto.time = Object.assign({}, defaultTime, dto.time)
const getTimezoneOffsetHours = ({date, localisedDate, returnNumber, timezone}) => {
let offsetString
if (localisedDate) {
offsetString = localisedDate.find(i => i.type === 'timeZoneName').value.match(/[\d+:-]+$/)?.[0]
} else {
offsetString = new Intl
.DateTimeFormat('en-GB', {timeZone: timezone, timeZoneName: 'longOffset'})
.formatToParts(date)
.find(i => i.type === 'timeZoneName').value.match(/[\d+:-]+$/)?.[0]
}
return returnNumber ? offsetString.split(':').reduce((a, c) => /^[+-]/.test(c) ? +c * 60 : a + +c, 0) : offsetString
}
const pad = (n, len) => `00${n}`.slice(-len)
let [datePart, offset] = dto.date.toLocaleDateString('sv', {
timeZone: dto.timezone,
timeZoneName: 'longOffset'
}).split(/ GMT|\//)
offset = offset.replace(String.fromCharCode(8722), '-')
const newDateWithoutOffset = `${datePart}T${pad(dto.time.h || 0, 2)}:${pad(dto.time.m || 0, 2)}:${pad(dto.time.s || 0, 2)}.${pad(dto.time.ms || 0, 3)}`
let newDate = new Date(`${newDateWithoutOffset}${offset}`)
const newDateTimezoneOffsetHours = getTimezoneOffsetHours({date: newDate, timezone: dto.timezone})
// Check if timezones of `dto.date` and `newDate` match; if not, use the new timezone to re-create `newDate`
newDate = newDateTimezoneOffsetHours === offset
? newDate
: new Date(`${newDateWithoutOffset}${newDateTimezoneOffsetHours}`)
if (dto.time.h !== +new Intl.DateTimeFormat('en-GB', {hour: 'numeric', timeZone: dto.timezone}).formatToParts(newDate)?.[0].value) {
newDate = new Date('')
}
return newDate
}
const timezoneIana = 'Europe/Bratislava'
const tests = [
{
expString: '30/10/2022, 01:55:00 GMT+02:00',
now: new Date('2022-10-29T23:56:12.006Z'),
testName: '[DST to ST] `now` in DST before double hour, `newDate` in DST before double hour',
time: {h: 1, m: 55}
},
{
expString: '30/10/2022, 02:55:00 GMT+02:00',
now: new Date('2022-10-29T23:56:12.006Z'),
testName: '[DST to ST] `now` in DST before double hour, `newDate` in DST during double hour',
time: {h: 2, m: 55}
},
// FIXME
{
expString: '30/10/2022, 02:55:00 GMT+01:00',
now: new Date('2022-10-29T23:56:12.006Z'),
testName: '[DST to ST] `now` in DST before double hour, `newDate` in ST during double hour',
time: {h: 2, m: 55}
},
{
expString: '30/10/2022, 03:55:00 GMT+01:00',
now: new Date('2022-10-29T23:56:12.006Z'),
testName: '[DST to ST] `now` in DST before double hour, `newDate` in ST after double hour',
time: {h: 3, m: 55}
},
{
expString: '30/10/2022, 01:55:00 GMT+02:00',
now: new Date('2022-10-30T00:56:12.006Z'),
testName: '[DST to ST] `now` in DST during double hour, `newDate` in DST before double hour',
time: {h: 1, m: 55}
},
{
expString: '30/10/2022, 02:55:00 GMT+02:00',
now: new Date('2022-10-30T00:56:12.006Z'),
testName: '[DST to ST] `now` in DST during double hour, `newDate` in DST during double hour',
time: {h: 2, m: 55}
},
// FIXME
{
expString: '30/10/2022, 02:55:00 GMT+01:00',
now: new Date('2022-10-30T00:56:12.006Z'),
testName: '[DST to ST] `now` in DST during double hour, `newDate` in ST during double hour',
time: {h: 2, m: 55}
},
{
expString: '30/10/2022, 03:55:00 GMT+01:00',
now: new Date('2022-10-30T00:56:12.006Z'),
testName: '[DST to ST] `now` in DST during double hour, `newDate` in ST after double hour',
time: {h: 3, m: 55}
},
{
expString: '30/10/2022, 01:55:00 GMT+02:00',
now: new Date('2022-10-30T01:56:12.006Z'),
testName: '[DST to ST] `now` in ST during double hour, `newDate` in DST before double hour',
time: {h: 1, m: 55}
},
// FIXME
{
expString: '30/10/2022, 02:55:00 GMT+02:00',
now: new Date('2022-10-30T01:56:12.006Z'),
testName: '[DST to ST] `now` in ST during double hour, `newDate` in DST during double hour',
time: {h: 2, m: 55}
},
{
expString: '30/10/2022, 02:55:00 GMT+01:00',
now: new Date('2022-10-30T01:56:12.006Z'),
testName: '[DST to ST] `now` in ST during double hour, `newDate` in ST during double hour',
time: {h: 2, m: 55}
},
{
expString: '30/10/2022, 03:55:00 GMT+01:00',
now: new Date('2022-10-30T01:56:12.006Z'),
testName: '[DST to ST] `now` in ST during double hour, `newDate` in ST after double hour',
time: {h: 3, m: 55}
},
{
expString: '30/10/2022, 01:55:00 GMT+02:00',
now: new Date('2022-10-30T02:56:12.006Z'),
testName: '[DST to ST] `now` in ST after double hour, `newDate` in DST before double hour',
time: {h: 1, m: 55}
},
// FIXME
{
expString: '30/10/2022, 02:55:00 GMT+02:00',
now: new Date('2022-10-30T02:56:12.006Z'),
testName: '[DST to ST] `now` in ST after double hour, `newDate` in DST during double hour',
time: {h: 2, m: 55}
},
{
expString: '30/10/2022, 02:55:00 GMT+01:00',
now: new Date('2022-10-30T02:56:12.006Z'),
testName: '[DST to ST] `now` in ST after double hour, `newDate` in ST during double hour',
time: {h: 2, m: 55}
},
{
expString: '30/10/2022, 03:55:00 GMT+01:00',
now: new Date('2022-10-30T02:56:12.006Z'),
testName: '[DST to ST] `now` in ST after double hour, `newDate` in ST after double hour',
time: {h: 3, m: 55}
},
{
expString: '26/03/2023, 01:55:00 GMT+01:00',
now: new Date('2023-03-26T00:56:12.006Z'),
testName: '[ST to DST] `now` in ST before skipped hour, `newDate` in ST before skipped hour',
time: {h: 1, m: 55}
},
// FIXME
{
expString: 'Invalid Date',
now: new Date('2023-03-26T00:56:12.006Z'),
testName: '[ST to DST] `now` in ST before skipped hour, `newDate` in ST in skipped hour',
time: {h: 2, m: 55}
},
{
expString: '26/03/2023, 03:55:00 GMT+02:00',
now: new Date('2023-03-26T00:56:12.006Z'),
testName: '[ST to DST] `now` in ST before skipped hour, `newDate` in DST after skipped hour',
time: {h: 3, m: 55}
},
{
expString: '26/03/2023, 01:55:00 GMT+01:00',
now: new Date('2023-03-26T01:56:12.006Z'),
testName: '[ST to DST] `now` in DST after skipped hour, `newDate` in ST before skipped hour',
time: {h: 1, m: 55}
},
// FIXME
{
expString: 'Invalid Date',
now: new Date('2023-03-26T01:56:12.006Z'),
testName: '[ST to DST] `now` in DST after skipped hour, `newDate` in ST in skipped hour',
time: {h: 2, m: 55}
},
{
expString: '26/03/2023, 03:55:00 GMT+02:00',
now: new Date('2023-03-26T01:56:12.006Z'),
testName: '[ST to DST] `now` in DST after skipped hour, `newDate` in DST after skipped hour',
time: {h: 3, m: 55}
}
// TODO: Add a test of a date in DST and ST on a day on which there is no timezone change (two tests in total, one for DST and another for ST).
]
const results = tests.map(t => {
const newDate = setLocalTime({date: t.now, time: t.time, timezone: timezoneIana})
const newDateString = newDate.toLocaleString('en-GB', {timeZone: timezoneIana, timeZoneName: 'longOffset'})
const testResult = newDateString === t.expString
if (testResult) {
console.log(testResult, `: ${t.testName} : ${newDateString}`)
} else {
console.log(testResult, `: ${t.testName} : ${newDateString} :`, {newDate, newDateString, test: t})
}
return testResult
}).reduce((a, c, i) => {
if (c) {
a.passed++
} else {
a.failed++
a.failedTestIds.push(i)
}
return a
}, {failed: 0, failedTestIds: [], passed: 0})
console.log(results)
它没有解决初始日期和结果跨越偏移量(可能是 DST)的问题,只是代码少了一点。你真的应该使用一个合适的库。
例如
function setLocalTime(
date = new Date(),
time = {h: 0, m: 0, s: 0, ms: 0},
tz = 'Europe/Bratislava') {
let {h, m, s, ms} = time;
let z = (n,len) => ('00'+n).slice(-len);
let [datePart, offset] = date.toLocaleDateString('sv', {
timeZone:tz, timeZoneName:'longOffset'
}).split(' GMT');
let timestamp = `${datePart}T` +
`${z(h||0,2)}:${z(m||0,2)}:${z(s||0,2)}.${z(ms||0,3)}` +
`${offset.replace(String.fromCharCode(8722),'-')}`;
return new Date(timestamp);
}
let tz, d;
tz = 'Europe/Bratislava';
d = setLocalTime(new Date(), {h: 15, m: 30, s: 0, ms: 0}, tz);
console.log('UTC: ' + d.toISOString());
console.log(tz + ': ' + d.toLocaleString('en-gb',{timeZone: tz, timeZoneName: 'long'}));
tz = 'America/New_York';
d = setLocalTime(new Date(), {h: 15, m: 30, s: 0, ms: 0}, tz);
console.log('UTC: '+ d.toISOString());
console.log(tz + ': ' + d.toLocaleString('en-gb',{timeZone: tz, timeZoneName: 'long'}));
在 Safari 中,负偏移量中的“−”字符(字符代码 8722)无法正确解析,因此将其替换为连字符。此外,
timeZoneName:'longOffset'
可能还没有得到广泛支持。
Date
对象通常期望本地(浏览器)时间中的时间定义并将其存储为 UTC 时间。您可以显式设置 使用
.setUTC<time part>()
方法获取 UTC 时区的时间。您可以使用参数为 hours
的 .setUTCHours()
函数设置时区 GMT+2 的时间 hours-2
。
const d=new Date(),hours=10,minutes=40;
d.setUTCHours(hours-2);d.setUTCMinutes(minutes);
console.log("GMT/UTC:",d); // UTC time ("Z")
console.log("New York:",d.toLocaleString("en-US",{timeZone:"America/New_York"})); // local time US
console.log("Berlin:",d.toLocaleString("de-DE",{timeZone:"Europe/Berlin"})); // local time Germany (GMT+2)
方法
.setUTCMinutes()
和
setMinutes()
在大多数情况下会达到相同的结果。一个值得注意的例外是时区“亚洲/加尔各答”,它将应用 30 分钟的偏移。Date
对象并且您想要更改其时间(但不是特定时区的日期,无论该时间的 UTC 日期如何) ,您可以为该函数提供该
Date
对象、所需时间、时区以及您是否喜欢使用新时区(可选)(见下文)。我必须添加 preferNewTimezone
参数,因为所谓的
双小时(非 UTC 时区中的小时,由于将时钟调慢一小时而出现两次),因为它们无法输出日期(包括时区)偏移)我预料到了。 我并不是说它很快也不完美,但是它确实有效。 :)
我在
Europe/Bratislava
时区创建了 52 个此函数的测试。他们都通过了。我认为它适用于任何时区。如果没有,并且您发现问题或有改进/优化,我想听听。
/**
* Set time provided in a timezone
*
* @param {Date} [dto.date = new Date()] Date object to work with
* @param {boolean} [dto.preferNewTimezone = false] Whether to prefer new timezone for double hours
* @param {number} [dto.time.h = 0] Hour to set
* @param {number} [dto.time.m = 0] Minute to set
* @param {number} [dto.time.s = 0] Second to set
* @param {number} [dto.time.ms = 0] Millisecond to set
* @param {string} [dto.timezone = 'Europe/Bratislava'] Timezone of `dto.time`
*
* @return {Date} Date object
*/
function setLocalTime(dto = {
date: new Date(),
preferNewTimezone: false,
time: {h: 0, m: 0, ms: 0, s: 0},
timezone: 'Europe/Bratislava'
}) {
const defaultTime = {h: 0, m: 0, ms: 0, s: 0}
const defaultTimeKeys = Object.keys(defaultTime)
// src: https://stackoverflow.com/a/44118363/3408342
if (!Intl || !Intl.DateTimeFormat().resolvedOptions().timeZone) {
throw new Error('`Intl` API is not available or it does not contain a list of timezone identifiers in this environment')
}
if (!(dto.date instanceof Date)) {
throw Error('`date` must be a `Date` object.')
}
try {
Intl.DateTimeFormat(undefined, {timeZone: dto.timezone})
} catch (e) {
throw Error('`timezone` must be a valid IANA timezone.')
}
if (
typeof dto.time !== 'undefined'
&& typeof dto.time !== 'object'
&& dto.time instanceof Object
&& Object.keys(dto.time).every(v => defaultTimeKeys.includes(v))
) {
throw Error('`time` must be an object of `{h: number, m: number, s: number, ms: number}` format, where numbers should be valid time values.')
}
dto.time = Object.assign({}, defaultTime, dto.time)
/**
* Whether a date falls in double hour in a particular timezone
*
* @param {Date} dto.date Date
* @param {string} dto.timezone IANA timezone
* @return {boolean} `true` if the specified Date falls in double hour in a particular timezone, `false` otherwise
*/
const isInDoubleHour = (dto = {date: new Date(), timezone: 'Europe/Bratislava'}) => {
// Get hour in `dto.timezone` of timezones an hour before and after `dto.date`
const hourBeforeHour = +new Intl.DateTimeFormat('en-GB', {hour: 'numeric', timeZone: dto.timezone}).formatToParts(new Date(new Date(dto.date).setUTCHours(dto.date.getUTCHours() - 1)))?.[0].value
const hourDateHour = +new Intl.DateTimeFormat('en-GB', {hour: 'numeric', timeZone: dto.timezone}).formatToParts(dto.date)?.[0].value
const hourAfterHour = +new Intl.DateTimeFormat('en-GB', {hour: 'numeric', timeZone: dto.timezone}).formatToParts(new Date(new Date(dto.date).setUTCHours(dto.date.getUTCHours() + 1)))?.[0].value
return hourBeforeHour === hourDateHour || hourAfterHour === hourDateHour
}
const getTimezoneOffsetHours = ({date, localisedDate, returnNumber, timezone}) => {
let offsetString
if (localisedDate) {
offsetString = localisedDate.find(i => i.type === 'timeZoneName').value.match(/[\d+:-]+$/)?.[0]
} else {
offsetString = new Intl
.DateTimeFormat('en-GB', {timeZone: timezone, timeZoneName: 'longOffset'})
.formatToParts(date)
.find(i => i.type === 'timeZoneName').value.match(/[\d+:-]+$/)?.[0]
}
return returnNumber ? offsetString.split(':').reduce((a, c) => /^[+-]/.test(c) ? +c * 60 : a + +c, 0) : offsetString
}
/**
* Pad a number with zeros from left to a required length
*
* @param {number} n Number
* @param {number} len Length
* @return {string} Padded number
*/
const pad = (n, len) => `00${n}`.slice(-len)
let [datePart, offset] = dto.date.toLocaleDateString('sv', {
timeZone: dto.timezone,
timeZoneName: 'longOffset'
}).split(/ GMT|\//)
offset = offset.replace(String.fromCharCode(8722), '-')
const newDateWithoutOffset = `${datePart}T${pad(dto.time.h || 0, 2)}:${pad(dto.time.m || 0, 2)}:${pad(dto.time.s || 0, 2)}.${pad(dto.time.ms || 0, 3)}`
let newDate = new Date(`${newDateWithoutOffset}${offset}`)
const newDateTimezoneOffsetHours = getTimezoneOffsetHours({date: newDate, timezone: dto.timezone})
// Check if timezones of `dto.date` and `newDate` match; if not, use the new timezone to re-create `newDate`
newDate = newDateTimezoneOffsetHours === offset
? newDate
: new Date(`${newDateWithoutOffset}${newDateTimezoneOffsetHours}`)
// Invalidate the date in `newDate` when the hour defined by user is not the same as the hour of `newDate` formatted in the user-defined timezone
if (dto.time.h !== +new Intl.DateTimeFormat('en-GB', {hour: 'numeric', timeZone: dto.timezone}).formatToParts(newDate)?.[0].value) {
newDate = new Date('')
}
// Check if the user prefers using the new timezone when `newDate` is in double hour
newDate = dto.preferNewTimezone && !isNaN(newDate) && isInDoubleHour({date: newDate, timezone: dto.timezone})
? new Date(`${newDateWithoutOffset}${getTimezoneOffsetHours({date: new Date(new Date(newDate).setUTCHours(dto.date.getUTCHours() + 1)), timezone: dto.timezone})}`)
: newDate
return newDate
}
const timezoneIana = 'Europe/Bratislava'
const tests = [
{
expString: '30/10/2022, 01:55:00 GMT+02:00',
now: new Date('2022-10-29T23:56:12.006Z'),
preferNewTimezone: false,
testName: '[DST to ST] `now` in DST before double hour, `newDate` in DST before double hour [don\'t prefer new timezone]',
time: {h: 1, m: 55}
},
{
expString: '30/10/2022, 02:55:00 GMT+02:00',
now: new Date('2022-10-29T23:56:12.006Z'),
preferNewTimezone: false,
testName: '[DST to ST] `now` in DST before double hour, `newDate` in DST during double hour [don\'t prefer new timezone]',
time: {h: 2, m: 55}
},
{
expString: '30/10/2022, 02:55:00 GMT+02:00',
now: new Date('2022-10-29T23:56:12.006Z'),
preferNewTimezone: false,
testName: '[DST to ST] `now` in DST before double hour, `newDate` in DST during double hour [don\'t prefer new timezone]',
time: {h: 2, m: 55}
},
{
expString: '30/10/2022, 03:55:00 GMT+01:00',
now: new Date('2022-10-29T23:56:12.006Z'),
preferNewTimezone: false,
testName: '[DST to ST] `now` in DST before double hour, `newDate` in ST after double hour [don\'t prefer new timezone]',
time: {h: 3, m: 55}
},
{
expString: '30/10/2022, 01:55:00 GMT+02:00',
now: new Date('2022-10-30T00:56:12.006Z'),
preferNewTimezone: false,
testName: '[DST to ST] `now` in DST during double hour, `newDate` in DST before double hour [don\'t prefer new timezone]',
time: {h: 1, m: 55}
},
{
expString: '30/10/2022, 02:55:00 GMT+02:00',
now: new Date('2022-10-30T00:56:12.006Z'),
preferNewTimezone: false,
testName: '[DST to ST] `now` in DST during double hour, `newDate` in DST during double hour [don\'t prefer new timezone]',
time: {h: 2, m: 55}
},
{
expString: '30/10/2022, 02:55:00 GMT+02:00',
now: new Date('2022-10-30T00:56:12.006Z'),
preferNewTimezone: false,
testName: '[DST to ST] `now` in DST during double hour, `newDate` in DST during double hour [don\'t prefer new timezone]',
time: {h: 2, m: 55}
},
{
expString: '30/10/2022, 03:55:00 GMT+01:00',
now: new Date('2022-10-30T00:56:12.006Z'),
preferNewTimezone: false,
testName: '[DST to ST] `now` in DST during double hour, `newDate` in ST after double hour [don\'t prefer new timezone]',
time: {h: 3, m: 55}
},
{
expString: '30/10/2022, 01:55:00 GMT+02:00',
now: new Date('2022-10-30T01:56:12.006Z'),
preferNewTimezone: false,
testName: '[DST to ST] `now` in ST during double hour, `newDate` in DST before double hour [don\'t prefer new timezone]',
time: {h: 1, m: 55}
},
{
expString: '30/10/2022, 02:55:00 GMT+01:00',
now: new Date('2022-10-30T01:56:12.006Z'),
preferNewTimezone: false,
testName: '[DST to ST] `now` in ST during double hour, `newDate` in ST during double hour [don\'t prefer new timezone]',
time: {h: 2, m: 55}
},
{
expString: '30/10/2022, 02:55:00 GMT+01:00',
now: new Date('2022-10-30T01:56:12.006Z'),
preferNewTimezone: false,
testName: '[DST to ST] `now` in ST during double hour, `newDate` in ST during double hour [don\'t prefer new timezone]',
time: {h: 2, m: 55}
},
{
expString: '30/10/2022, 03:55:00 GMT+01:00',
now: new Date('2022-10-30T01:56:12.006Z'),
preferNewTimezone: false,
testName: '[DST to ST] `now` in ST during double hour, `newDate` in ST after double hour [don\'t prefer new timezone]',
time: {h: 3, m: 55}
},
{
expString: '30/10/2022, 01:55:00 GMT+02:00',
now: new Date('2022-10-30T02:56:12.006Z'),
preferNewTimezone: false,
testName: '[DST to ST] `now` in ST after double hour, `newDate` in DST before double hour [don\'t prefer new timezone]',
time: {h: 1, m: 55}
},
{
expString: '30/10/2022, 02:55:00 GMT+01:00',
now: new Date('2022-10-30T02:56:12.006Z'),
preferNewTimezone: false,
testName: '[DST to ST] `now` in ST after double hour, `newDate` in ST during double hour [don\'t prefer new timezone]',
time: {h: 2, m: 55}
},
{
expString: '30/10/2022, 02:55:00 GMT+01:00',
now: new Date('2022-10-30T02:56:12.006Z'),
preferNewTimezone: false,
testName: '[DST to ST] `now` in ST after double hour, `newDate` in ST during double hour [don\'t prefer new timezone]',
time: {h: 2, m: 55}
},
{
expString: '30/10/2022, 03:55:00 GMT+01:00',
now: new Date('2022-10-30T02:56:12.006Z'),
preferNewTimezone: false,
testName: '[DST to ST] `now` in ST after double hour, `newDate` in ST after double hour [don\'t prefer new timezone]',
time: {h: 3, m: 55}
},
{
expString: '26/03/2023, 01:55:00 GMT+01:00',
now: new Date('2023-03-26T00:56:12.006Z'),
preferNewTimezone: false,
testName: '[ST to DST] `now` in ST before skipped hour, `newDate` in ST before skipped hour [don\'t prefer new timezone]',
time: {h: 1, m: 55}
},
{
expString: 'Invalid Date',
now: new Date('2023-03-26T00:56:12.006Z'),
preferNewTimezone: false,
testName: '[ST to DST] `now` in ST before skipped hour, `newDate` in ST in skipped hour [don\'t prefer new timezone]',
time: {h: 2, m: 55}
},
{
expString: '26/03/2023, 03:55:00 GMT+02:00',
now: new Date('2023-03-26T00:56:12.006Z'),
preferNewTimezone: false,
testName: '[ST to DST] `now` in ST before skipped hour, `newDate` in DST after skipped hour [don\'t prefer new timezone]',
time: {h: 3, m: 55}
},
{
expString: '26/03/2023, 01:55:00 GMT+01:00',
now: new Date('2023-03-26T01:56:12.006Z'),
preferNewTimezone: false,
testName: '[ST to DST] `now` in DST after skipped hour, `newDate` in ST before skipped hour [don\'t prefer new timezone]',
time: {h: 1, m: 55}
},
{
expString: 'Invalid Date',
now: new Date('2023-03-26T01:56:12.006Z'),
preferNewTimezone: false,
testName: '[ST to DST] `now` in DST after skipped hour, `newDate` in ST in skipped hour [don\'t prefer new timezone]',
time: {h: 2, m: 55}
},
{
expString: '26/03/2023, 03:55:00 GMT+02:00',
now: new Date('2023-03-26T01:56:12.006Z'),
preferNewTimezone: false,
testName: '[ST to DST] `now` in DST after skipped hour, `newDate` in DST after skipped hour [don\'t prefer new timezone]',
time: {h: 3, m: 55}
},
{
expString: '30/10/2022, 01:55:00 GMT+02:00',
now: new Date('2022-10-29T23:56:12.006Z'),
preferNewTimezone: true,
testName: '[DST to ST] `now` in DST before double hour, `newDate` in DST before double hour [prefer new timezone] ',
time: {h: 1, m: 55}
},
{
expString: '30/10/2022, 02:55:00 GMT+01:00',
now: new Date('2022-10-29T23:56:12.006Z'),
preferNewTimezone: true,
testName: '[DST to ST] `now` in DST before double hour, `newDate` in ST during double hour [prefer new timezone] ',
time: {h: 2, m: 55}
},
{
expString: '30/10/2022, 02:55:00 GMT+01:00',
now: new Date('2022-10-29T23:56:12.006Z'),
preferNewTimezone: true,
testName: '[DST to ST] `now` in DST before double hour, `newDate` in ST during double hour [prefer new timezone] ',
time: {h: 2, m: 55}
},
{
expString: '30/10/2022, 03:55:00 GMT+01:00',
now: new Date('2022-10-29T23:56:12.006Z'),
preferNewTimezone: true,
testName: '[DST to ST] `now` in DST before double hour, `newDate` in ST after double hour [prefer new timezone] ',
time: {h: 3, m: 55}
},
{
expString: '30/10/2022, 01:55:00 GMT+02:00',
now: new Date('2022-10-30T00:56:12.006Z'),
preferNewTimezone: true,
testName: '[DST to ST] `now` in DST during double hour, `newDate` in DST before double hour [prefer new timezone] ',
time: {h: 1, m: 55}
},
{
expString: '30/10/2022, 02:55:00 GMT+01:00',
now: new Date('2022-10-30T00:56:12.006Z'),
preferNewTimezone: true,
testName: '[DST to ST] `now` in DST during double hour, `newDate` in ST during double hour [prefer new timezone] ',
time: {h: 2, m: 55}
},
{
expString: '30/10/2022, 02:55:00 GMT+01:00',
now: new Date('2022-10-30T00:56:12.006Z'),
preferNewTimezone: true,
testName: '[DST to ST] `now` in DST during double hour, `newDate` in ST during double hour [prefer new timezone] ',
time: {h: 2, m: 55}
},
{
expString: '30/10/2022, 03:55:00 GMT+01:00',
now: new Date('2022-10-30T00:56:12.006Z'),
preferNewTimezone: true,
testName: '[DST to ST] `now` in DST during double hour, `newDate` in ST after double hour [prefer new timezone] ',
time: {h: 3, m: 55}
},
{
expString: '30/10/2022, 01:55:00 GMT+02:00',
now: new Date('2022-10-30T01:56:12.006Z'),
preferNewTimezone: true,
testName: '[DST to ST] `now` in ST during double hour, `newDate` in DST before double hour [prefer new timezone] ',
time: {h: 1, m: 55}
},
{
expString: '30/10/2022, 02:55:00 GMT+01:00',
now: new Date('2022-10-30T01:56:12.006Z'),
preferNewTimezone: true,
testName: '[DST to ST] `now` in ST during double hour, `newDate` in ST during double hour [prefer new timezone] ',
time: {h: 2, m: 55}
},
{
expString: '30/10/2022, 02:55:00 GMT+01:00',
now: new Date('2022-10-30T01:56:12.006Z'),
preferNewTimezone: true,
testName: '[DST to ST] `now` in ST during double hour, `newDate` in ST during double hour [prefer new timezone] ',
time: {h: 2, m: 55}
},
{
expString: '30/10/2022, 03:55:00 GMT+01:00',
now: new Date('2022-10-30T01:56:12.006Z'),
preferNewTimezone: true,
testName: '[DST to ST] `now` in ST during double hour, `newDate` in ST after double hour [prefer new timezone] ',
time: {h: 3, m: 55}
},
{
expString: '30/10/2022, 01:55:00 GMT+02:00',
now: new Date('2022-10-30T02:56:12.006Z'),
preferNewTimezone: true,
testName: '[DST to ST] `now` in ST after double hour, `newDate` in DST before double hour [prefer new timezone] ',
time: {h: 1, m: 55}
},
{
expString: '30/10/2022, 02:55:00 GMT+01:00',
now: new Date('2022-10-30T02:56:12.006Z'),
preferNewTimezone: true,
testName: '[DST to ST] `now` in ST after double hour, `newDate` in ST during double hour [prefer new timezone] ',
time: {h: 2, m: 55}
},
{
expString: '30/10/2022, 02:55:00 GMT+01:00',
now: new Date('2022-10-30T02:56:12.006Z'),
preferNewTimezone: true,
testName: '[DST to ST] `now` in ST after double hour, `newDate` in ST during double hour [prefer new timezone] ',
time: {h: 2, m: 55}
},
{
expString: '30/10/2022, 03:55:00 GMT+01:00',
now: new Date('2022-10-30T02:56:12.006Z'),
preferNewTimezone: true,
testName: '[DST to ST] `now` in ST after double hour, `newDate` in ST after double hour [prefer new timezone] ',
time: {h: 3, m: 55}
},
{
expString: '26/03/2023, 01:55:00 GMT+01:00',
now: new Date('2023-03-26T00:56:12.006Z'),
preferNewTimezone: true,
testName: '[ST to DST] `now` in ST before skipped hour, `newDate` in ST before skipped hour [prefer new timezone] ',
time: {h: 1, m: 55}
},
{
expString: 'Invalid Date',
now: new Date('2023-03-26T00:56:12.006Z'),
preferNewTimezone: true,
testName: '[ST to DST] `now` in ST before skipped hour, `newDate` in ST in skipped hour [prefer new timezone] ',
time: {h: 2, m: 55}
},
{
expString: '26/03/2023, 03:55:00 GMT+02:00',
now: new Date('2023-03-26T00:56:12.006Z'),
preferNewTimezone: true,
testName: '[ST to DST] `now` in ST before skipped hour, `newDate` in DST after skipped hour [prefer new timezone] ',
time: {h: 3, m: 55}
},
{
expString: '26/03/2023, 01:55:00 GMT+01:00',
now: new Date('2023-03-26T01:56:12.006Z'),
preferNewTimezone: true,
testName: '[ST to DST] `now` in DST after skipped hour, `newDate` in ST before skipped hour [prefer new timezone] ',
time: {h: 1, m: 55}
},
{
expString: 'Invalid Date',
now: new Date('2023-03-26T01:56:12.006Z'),
preferNewTimezone: true,
testName: '[ST to DST] `now` in DST after skipped hour, `newDate` in ST in skipped hour [prefer new timezone] ',
time: {h: 2, m: 55}
},
{
expString: '26/03/2023, 03:55:00 GMT+02:00',
now: new Date('2023-03-26T01:56:12.006Z'),
preferNewTimezone: true,
testName: '[ST to DST] `now` in DST after skipped hour, `newDate` in DST after skipped hour [prefer new timezone] ',
time: {h: 3, m: 55}
},
{
expString: '01/01/2023, 03:55:00 GMT+01:00',
now: new Date('2023-01-01T10:00:00.000Z'),
preferNewTimezone: true,
testName: '[ST] `now` in ST, `newDate` in ST before `now` [prefer new timezone] ',
time: {h: 3, m: 55}
},
{
expString: '01/01/2023, 12:00:00 GMT+01:00',
now: new Date('2023-01-01T10:00:00.000Z'),
preferNewTimezone: true,
testName: '[ST] `now` in ST, `newDate` in ST after `now` [prefer new timezone] ',
time: {h: 12}
},
{
expString: '01/07/2023, 03:55:00 GMT+02:00',
now: new Date('2023-07-01T10:00:00.000Z'),
preferNewTimezone: true,
testName: '[ST] `now` in DST, `newDate` in DST before `now` [prefer new timezone] ',
time: {h: 3, m: 55}
},
{
expString: '01/07/2023, 12:00:00 GMT+02:00',
now: new Date('2023-07-01T10:00:00.000Z'),
preferNewTimezone: true,
testName: '[ST] `now` in DST, `newDate` in DST after `now` [prefer new timezone] ',
time: {h: 12}
},
{
expString: '01/01/2023, 03:55:00 GMT+01:00',
now: new Date('2023-01-01T10:00:00.000Z'),
preferNewTimezone: false,
testName: '[ST] `now` in ST, `newDate` in ST before `now` [don\'t prefer new timezone]',
time: {h: 3, m: 55}
},
{
expString: '01/01/2023, 12:00:00 GMT+01:00',
now: new Date('2023-01-01T10:00:00.000Z'),
preferNewTimezone: false,
testName: '[ST] `now` in ST, `newDate` in ST after `now` [don\'t prefer new timezone]',
time: {h: 12}
},
{
expString: '01/07/2023, 03:55:00 GMT+02:00',
now: new Date('2023-07-01T10:00:00.000Z'),
preferNewTimezone: false,
testName: '[ST] `now` in DST, `newDate` in DST before `now` [don\'t prefer new timezone]',
time: {h: 3, m: 55}
},
{
expString: '01/07/2023, 12:00:00 GMT+02:00',
now: new Date('2023-07-01T10:00:00.000Z'),
preferNewTimezone: false,
testName: '[ST] `now` in DST, `newDate` in DST after `now` [don\'t prefer new timezone]',
time: {h: 12}
}
]
const results = tests.map(t => {
const newDate = setLocalTime({date: t.now, preferNewTimezone: t.preferNewTimezone, time: t.time, timezone: timezoneIana})
const newDateString = newDate.toLocaleString('en-GB', {timeZone: timezoneIana, timeZoneName: 'longOffset'})
const testResult = newDateString === t.expString
if (testResult) {
console.log(testResult, `: ${t.testName} : ${newDateString}`)
} else {
console.log(testResult, `: ${t.testName} : ${newDateString} :`, {newDate, newDateString, test: t})
}
return testResult
}).reduce((a, c, i) => {
if (c) {
a.passed++
} else {
a.failed++
a.failedTestIds.push(i)
}
return a
}, {failed: 0, failedTestIds: [], passed: 0})
console.log(results)