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

dayjs.tz is not idempotent with dates in DST when it's not currently DST #1805

Open
scottschup opened this issue Feb 12, 2022 · 11 comments
Open

Comments

@scottschup
Copy link

scottschup commented Feb 12, 2022

Describe the bug
Given:

  • it is currently Standard Time (i.e. not Daylight Saving Time)
  • the date being manipulated is in Daylight Saving Time

When Dayjs#tz is called more than once on a Dayjs object, the time moves back 1 hour each time Dayjs#tz is called.

> dayjs('2020-08-08T00:00:00.000Z').toString()
//=> 'Sat, 08 Aug 2020 00:00:00 GMT'

> dayjs('2020-08-08T00:00:00.000Z').tz('America/Chicago').toString()
//=> 'Fri, 07 Aug 2020 23:00:00 GMT'

> dayjs('2020-08-08T00:00:00.000Z').tz('America/Chicago').tz('America/Chicago').toString()
//=> 'Fri, 07 Aug 2020 22:00:00 GMT'

> dayjs('2020-08-08T00:00:00.000Z').tz('America/Chicago').tz('America/Chicago').tz('America/Chicago').toString()
//=> 'Fri, 07 Aug 2020 21:00:00 GMT'

The result is the same regardless of what timezones are used, or whether a timezone arg is passed in at all:

> dayjs('2020-08-08T00:00:00.001Z').tz('UTC').tz().tz('UTC').toString()
//=> 'Fri, 07 Aug 2020 21:00:00 GMT'

> dayjs('2020-08-08T00:00:00.001Z').tz('America/Chicago').tz('UTC').tz().toString()
//=> 'Fri, 07 Aug 2020 21:00:00 GMT'

> dayjs('2020-08-08T00:00:00.001Z').tz().tz().tz().toString()
//=> 'Fri, 07 Aug 2020 21:00:00 GMT'

It's February and I'm in Chicago, so I've only been able to confirm this issue occurs during CST with a date in CDT. I suspect that the opposite will be true in a few weeks (that calling Dayjs#tz on a CST time during CDT will result in a one-hour shift the other way).

And I saw several open issue tickets that are likely related or the result of the same underlying problem:

Expected behavior
Applying a timezone should not change the underlying time.

Information

  • Day.js v1.10.7
  • OS: iOS 12.1
  • Time zone: GMT-06:00 (Central Standard Time)
@sulkaharo
Copy link

I filed a ticket (#1801) some days ago on the same topic and am wondering if I should try to PR a fix. The blocker I have against doing that is, I read the timezone plugin and I'm not 100% sure I understand the logic as it's implemented. Wonder if the right way to get started is to PR additions to https://github.com/iamkun/dayjs/blob/dev/test/timezone.test.js and add tests that fail.

@sulkaharo
Copy link

Reading the code of the UTC and Timezone plugins, the UTC plugin doesn't set the time zone of the date object (just flags $u=true), while the Timezone plugin uses $x.$timezone to determine the time zone, so there is no interaction between the two that I can see. If you initialise a date using dayjs.utc(number), dayjs.isUTC() return true for the object and the date string is in UTC format, but assigning a new time zone using dayjs.tz() will ignore the UTC flag and not detect the time zone format and thus consider the time is in local time of the computer running the code.

@SkaceKamen
Copy link

This bug is also related:
#1803

@snoozbuster
Copy link

+1. The tests I wrote to catch this started failing when the west coast switched off daylight savings time. Specifically, if you install the plugins utc, and tz, and define these functions:

const toUtcTime = time => dayjs.utc(time).tz('UTC')
const toLocalTime = time => dayjs.utc(time).tz()

then, you have the following behavior:

> const timestampUtc = "2021-09-30T20:08:35+00:00"
> toUtcTime(toLocalTime(timestampUtc)).format() === timestampUtc
false
> toUtcTime(toLocalTime(timestampUtc).format()).format() === timestampUtc
true
> 

The internal state of the dayjs object does not match its format string, as evidenced by the second line. If you don't re-use the dayjs objects and format them between each step, it works as expected.

The really lame part is, I don't even need the timezone plugin in my application. The only reason I included it is so that I could use dayjs.tz.setDefault() in my tests to (theoretically) get consistent results and assertions. Without including it, I have no idea how I would force the timezone that is considered "local time" for testing purposes.

@sulkaharo
Copy link

@iamkun can you confirm is the library is still being maintained? Based on the commit history you've touched dayjs logic last time 7 months ago and there doesn't seem to be another developer on board maintaining dayjs, resulting in a long backlog of pull requests and issues. If you've burned out or don't have time for this library, let's find a solution to getting the development back on track.

@snoozbuster
Copy link

@sulkaharo I ended up switching to Luxon in my project since it looks like this one is no longer active (and luxon’s docs are much better to boot). I don’t know if that’s an option for you but it may be worth considering.

@scottschup
Copy link
Author

scottschup commented Feb 21, 2022

@sulkaharo I ended up switching to Luxon in my project since it looks like this one is no longer active (and luxon’s docs are much better to boot). I don’t know if that’s an option for you but it may be worth considering.

I'm about to go that route too. I found a workaround for this issue, but now anytime I enter a date that's not in the same Daylight Saving period as the current date, I get a "Too many re-renders" error from the MaterialUI DatePicker component, but no indication of what's causing it other than it's the DatePicker that's re-rendering too many times.

@scottschup
Copy link
Author

I did end up switching over to Luxon and life is so much better now. Using it, it's clear that the creator built it with timezones in mind from the start rather than adding support for them later as an after-thought.

https://moment.github.io/luxon/#/why

@Serg-Mois
Copy link

Faced the same problem. @iamkun I see you returned, please check that ASAP, or at least provide any workaround.

@justingolden21
Copy link

+1 to what @sulkaharo said

@iamkun if all of the PRs and issues and noise around this project is too much, we should find a way to keep the development of this project going, since there are a lot of capable and willing people and it's a widely used (and awesome) library.

@Travis-Enright
Copy link

For those who need reliable behavior and cannot switch off of DayJS, thankfully this issue is easy to work around.
This approach relies on the fact that multiple .tz() calls to the same timezone will stack up the offset.
All we do here is measure the offset over multiple calls, and then add that offset back to the original.

function tz (date, timezone) {
  const once = date.tz(timezone)
  const twice = once.tz(timezone)
  const offset = once.diff(twice, 'minute')
  return once.add(offset, 'minute')
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants