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

fix: account for daylight savings time [LIBS-490] #1345

Merged
merged 2 commits into from
Apr 11, 2023

Conversation

tomzemp
Copy link
Member

@tomzemp tomzemp commented Mar 31, 2023

Implements LIBS-490


Key features

  1. fixes useTimeZoneConversion hook to adjust serverOffset relative to date

Description

useTimeZoneConversion hook assumes a constant offset between client and server time zones but this offset will change throughout the year depending on whether either client or server time zone observes daylight savings time.


Checklist

  • Have written Documentation
    • This PR changes the internal implementation of the hook; the exported functions are more correct than before, but there is no change in
  • Has tests coverage
    • test has been added covering a check for client: UTC/ server: Europe/Oslo during summer time (complements existing check during standard time)

Known issues

A) I have updated the hook to calculate the offset except when either a) the client and server time zones are the same or b) an initial attempt at calculating the offset fails (e.g. could be due to some problem with the time zone information). The calculation of the offset isn't expensive, but we could also check if either client of server time zone observes daylight savings time, and if not memoize the offset calculation. I haven't done this as it feels a bit hacky (e.g. see this suggested approach on StackOverflow for determining whether time zone uses daylight savings time)

B) Since JavaScript only allows us to initialize a date in the client time zone, I can't think of a way to (easily) calculate the offset as of a given server time. E.g. if we get the string 2020-10-01T12:00:00 we can calculate the offset from that client time, but cannot calculate from that server time. This will lead to some inaccuracies around the point when time shifts from standard to summer time (or vice versa). For example, if we have server time zone: UTC, client time zone: Europe/Oslo and want to figure out the client equivalent of 2023-03-23T01:00:00; the server offset at this point should be 2 hours, but if we calculate at the point it is 1am for the browser, the offset would be 1 hour (because at that point, summer time won't be implemented). So we end up with a couple of hours during which point the offset is incorrectly calculated.

I thought to address this issue it might make sense to calculate the offset from a client perspective and then test that the resulting client date when put into server time zone string (with toLocaleString) is correct. If not, the offset could be guessed at within ±2 hours (accounting for daylight savings time simultaneously occurring in both northern/southern hemispheres) until one presumably finds the correct one. I don't really know this would be worth it though (and also feels hacky)

Another issue that occurs to me with this is that because we allow server time zone to be a local time zone that can observe daylight savings time, we can end up in situations in which our server time differential is not unambiguously mappable to UTC (or a server time). For example, if we have server time zone: Europe/Oslo and the timestamp 2023-10-29T02:30 this could be either 2023-10-29T01:30 UTC or 2023-10-29T00:30` UTC (that is we have apparent repeat timestamps when moving from summer to standard time)

As a whole, I think I lean to being okay with this few hours of non-exactness in our system around daylight savings time (until temporal standard is introduced which should fix this). I don't think we're using dates in such a mission critical way that this will be problematic? (This isn't obviously ideal, but it seems like even if we invest time to fixing the frontend with more logic, it will still be imperfect?)

@tomzemp tomzemp marked this pull request as ready for review April 3, 2023 09:33
Copy link
Contributor

@HendrikThePendric HendrikThePendric left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for writing such an extensive explanation. I don't think I would have been able to evaluate this PR without it 👍.

I agree that the current implementation is good enough and that we should not spend more time on this. We can revisit this when we add some sort of Temporal based solution, which is probably going to be part of a bigger effort of adding some sort of date/time conversion/localisation provider to the app-shell.

Copy link
Contributor

@kabaros kabaros left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM .. and the compromises are very sensible. One question is regarding the tests: are we mocking the client's timezone somewhere? otherwise I guess these tests could fall if I run them locally on a different timezone..

@tomzemp
Copy link
Member Author

tomzemp commented Apr 4, 2023

One question is regarding the tests: are we mocking the client's timezone somewhere? otherwise I guess these tests could fall if I run them locally on a different timezone..

@kabaros: For these tests, we're setting the client time zone to be UTC in the test script with an environment variable (https://github.com/dhis2/app-runtime/blob/master/services/config/package.json#L37), so they should work as long as you run them with yarn test and don't directly run using d2-app-scripts (I've tested running when my system time set to different time zones)

Ideally, when writing the tests, I was hoping that I could mock different client time zones within the tests, so that we could test a greater combination of client/server time zones. It doesn't seem, however, like jest or JavaScript will let one change the time zone after one has set it (I'm going off random blogposts, stack overflow posts, because jest documentation doesn't seem to mention time zones). There do seem to be some libraries (e.g. timezone-mock) that exist for helping with this, but then when I was reading up on approaches, it seems like most of them are mocking time zones by mocking the Date object implementation using offsets...which then made me wonder if it was worth it here given the logic of the hook is extending the Date object with offsets?

I don't really have a sense either way; I guess my preference would be to merge this fix and then make another ticket to look into improving these tests more?

@kabaros
Copy link
Contributor

kabaros commented Apr 4, 2023

I achieved something within the same lines by spying on Intl.DateTimeFormat here .. maybe that could work as an alternative for the env variable (not 100% sure if it let you change it after setting it up though, but I think it should be possible).

In all cases, I think setting the env variable is good as well .. I was just curious to know how you were ensuring that the tests are deterministic.

@tomzemp
Copy link
Member Author

tomzemp commented Apr 5, 2023

I achieved something within the same lines by spying on Intl.DateTimeFormat here .. maybe that could work as an alternative for the env variable (not 100% sure if it let you change it after setting it up though, but I think it should be possible).

In all cases, I think setting the env variable is good as well .. I was just curious to know how you were ensuring that the tests are deterministic.

Got you. Thanks @kabaros ! That's a a good idea for spying on Intl.DateTimeFormat; mostly the implementation here doesn't expressly use information about the client time zone, but now it won't calculate an offset if the server and client time zones are the same, so I've added in an additional test (with the Intl.DateTimeFormat for spying) for ensuring no offset where both server/client time is the same.

Going to go ahead and merge this PR by end of day. 🙏

@tomzemp tomzemp merged commit fb00533 into master Apr 11, 2023
@tomzemp tomzemp deleted the LIBS-490/useTimeZoneConversion-dst branch April 11, 2023 08:20
dhis2-bot added a commit that referenced this pull request Apr 11, 2023
## [3.9.1](v3.9.0...v3.9.1) (2023-04-11)

### Bug Fixes

* account for daylight savings time [LIBS-490] ([06eaa5d](06eaa5d))
* account for daylight savings time [LIBS-490] [#1345](#1345) ([fb00533](fb00533))
* add test for when time zones are the same [LIBS-490] ([7911f8b](7911f8b))
@dhis2-bot
Copy link
Contributor

amcgee pushed a commit that referenced this pull request Aug 22, 2023
* account for daylight savings time [LIBS-490] ([06eaa5d](06eaa5d))
* account for daylight savings time [LIBS-490] [#1345](#1345) ([fb00533](fb00533))
* add test for when time zones are the same [LIBS-490] ([7911f8b](7911f8b))
dhis2-bot added a commit that referenced this pull request Aug 22, 2023
# [3.10.0-alpha.3](v3.10.0-alpha.2...v3.10.0-alpha.3) (2023-08-22)

### Bug Fixes

* **connection-status:** responsiveness to online events [LIBS-497] ([#1348](#1348)) ([91a3d4d](91a3d4d))
* **types:** add generic result type to oncomplete param ([#1350](#1350)) ([a069603](a069603))
* [DHIS2] Type generic T = QueryResult to useDataQuery ([#1297](#1297)) ([7c5c083](7c5c083))
* account for daylight savings time [LIBS-490] ([06eaa5d](06eaa5d))
* account for daylight savings time [LIBS-490] [#1345](#1345) ([fb00533](fb00533))
* add test for when time zones are the same [LIBS-490] ([7911f8b](7911f8b))
@dhis2-bot
Copy link
Contributor

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

Successfully merging this pull request may close these issues.

4 participants