date-fns-tz
date-fns-tz copied to clipboard
formatInTimeZone gives wrong results around DST
This issue covers 2 problems in 1:
- formatInTimeZone gives wrong results around DST
- formatInTimeZone gives inconsistent results around DST depending on host time zone
Testing code:
describe('Europe/Paris', function () {
var tests = [
{
name: 'summer time CEST',
date: '2021-08-01T00:30:00Z',
expected: '2021-08-01T02:30:00+02:00',
},
{
name: 'winter time CET',
date: '2021-01-01T01:30:00Z',
expected: '2021-01-01T02:30:00+01:00',
},
{
name: 'before DST changeover (CEST to CET)',
date: '2021-10-31T00:30:00Z',
expected: '2021-10-31T02:30:00+02:00',
},
{
name: 'after DST changeover (CEST to CET)',
date: '2021-10-31T01:30:00Z',
expected: '2021-10-31T02:30:00+01:00',
},
{
name: 'before DST changeover (CET to CEST)',
date: '2021-03-28T00:30:00Z',
expected: '2021-03-28T01:30:00+01:00',
},
{
name: 'after DST changeover (CET to CEST)',
date: '2021-03-28T01:30:00Z',
expected: '2021-03-28T03:30:00+02:00',
},
]
tests.forEach(({ name, date, expected }) => {
it(`${name}: ${date}`, function () {
var timeZone = 'Europe/Paris'
var result = formatInTimeZone(date, timeZone, "yyyy-MM-dd'T'HH:mm:ssxxx")
assert(result === expected)
assert(new Date(result).getTime() === new Date(date).getTime()) // Equivalent to previous assertion
})
})
Testing results:
- first with host TZ set to Europe/Paris,
- then with host TZ set to UTC
$ cmd.exe '/c' 'tzutil /g'
Romance Standard Time
$ yarn test
SUMMARY:
✔ 5 tests completed
✖ 1 test failed
FAILED TESTS:
Europe/Paris
✖ after DST changeover (CEST to CET): 2021-10-31T01:30:00Z
Chrome 98.0.4758 (Windows 10.0.0)
AssertionError: # src\formatInTimeZone\test.js:64
assert(result === expected)
| | |
| | "2021-10-31T02:30:00+01:00"
| false
"2021-10-31T02:30:00+02:00"
--- [string] expected
+++ [string] result
@@ -14,12 +14,12 @@
:30:00+0
-1
+2
:00
$ cmd.exe '/c' 'tzutil /s UTC'
$ cmd.exe '/c' 'tzutil /g'
UTC
$ yarn test
SUMMARY:
✔ 4 tests completed
✖ 2 tests failed
FAILED TESTS:
Europe/Paris
✖ before DST changeover (CEST to CET): 2021-10-31T00:30:00Z
Chrome 98.0.4758 (Windows 10.0.0)
AssertionError: # src\formatInTimeZone\test.js:64
assert(result === expected)
| | |
| | "2021-10-31T02:30:00+02:00"
| false
"2021-10-31T02:30:00+01:00"
--- [string] expected
+++ [string] result
@@ -14,12 +14,12 @@
:30:00+0
-2
+1
:00
✖ before DST changeover (CET to CEST): 2021-03-28T00:30:00Z
Chrome 98.0.4758 (Windows 10.0.0)
AssertionError: # src\formatInTimeZone\test.js:64
assert(result === expected)
| | |
| | "2021-03-28T01:30:00+01:00"
| false
"2021-03-28T01:30:00+02:00"
--- [string] expected
+++ [string] result
@@ -14,12 +14,12 @@
:30:00+0
-1
+2
:00
Moreover, and maybe related, these tests in getTimezoneOffset/test.js
are wrong by design.
DST changeover happens respectively at 02:00 (AEST to AEDT) and 03:00 (AEDT to AEST) LOCAL TIME and not UTC time.
describe('near DST changeover (AEST to AEDT)', function () {
it('one day before', function () {
var date = new Date('2020-10-04T00:45:00.000Z')
assert.equal(getTimezoneOffset('Australia/Melbourne', date), 10 * hours)
})
it('15 minutes before', function () {
var date = new Date('2020-10-04T01:45:00.000Z')
assert.equal(getTimezoneOffset('Australia/Melbourne', date), 10 * hours)
})
it('15 minutes after', function () {
var date = new Date('2020-10-04T03:15:00.000Z')
assert.equal(getTimezoneOffset('Australia/Melbourne', date), 11 * hours)
})
})
I assume it's the same issue but I saw this trying to format some parsed dates as UTC. You can see the issue even if the input dates are UTC already as shown below. The output depends on my local timezone (Europe/Berlin).
describe('formatInTimeZone', () => {
test.each([
{ input: '2023-03-26T00:11:00.000Z', expected: '2023-03-26 00:11:00 UTC' },
{ input: '2023-03-26T01:11:01.000Z', expected: '2023-03-26 01:11:01 UTC' },
{ input: '2023-03-26T02:11:02.000Z', expected: '2023-03-26 02:11:02 UTC' },
{ input: '2023-03-26T03:11:03.000Z', expected: '2023-03-26 03:11:03 UTC' },
])('formatInTimeZone($input) == $expected', ({ input, expected }) => {
expect(formatInTimeZone(input, 'UTC', 'yyyy-MM-dd HH:mm:ss zzz')).toEqual(
expected,
)
})
})
foo
✓ formatInTimeZone(2023-03-26T00:11:00.000Z) == 2023-03-26 00:11:00 UTC (16 ms)
✓ formatInTimeZone(2023-03-26T01:11:01.000Z) == 2023-03-26 01:11:01 UTC (1 ms)
✕ formatInTimeZone(2023-03-26T02:11:02.000Z) == 2023-03-26 02:11:02 UTC (4 ms)
✓ formatInTimeZone(2023-03-26T03:11:03.000Z) == 2023-03-26 03:11:03 UTC (1 ms)
● formatInTimeZone › formatInTimeZone(2023-03-26T02:11:02.000Z) == 2023-03-26 02:11:02 UTC
expect(received).toEqual(expected) // deep equality
Expected: "2023-03-26 02:11:02 UTC"
Received: "2023-03-26 03:11:02 UTC"
30 | { input: '2023-03-26T03:11:03.000Z', expected: '2023-03-26 03:11:03 UTC' },
31 | ])('formatInTimeZone($input) == $expected', ({ input, expected }) => {
> 32 | expect(formatInTimeZone(input, 'UTC', 'yyyy-MM-dd HH:mm:ss zzz')).toEqual(
| ^
33 | expected,
34 | )
35 | })
at redacted.test.tsx:32:71
This change should fix it: https://github.com/marnusw/date-fns-tz/pull/247
This change should fix it: #247
It doesn't pass the test cases I provided above.
This change should fix it: #247
It doesn't pass the test cases I provided above.
That's because I'm not touching getTimezoneOffset
, I'm fixing formatInTimeZone
, and the fix makes the tests from the description of this issue pass. Feel free to open another PR to fix the issues with getTimezoneOffset
.