HACKER Q&A
📣 solardev

Resources for learning/teaching time zone conversions and datetime math?


Does anyone have recommendations on tutorials, videos, lessons, etc. to better understand time zone conversions and datetime math and be able to teach it to a general audience (a mix of coders/managers)?

At work, we frequently have to deal with time zone conversions between a user's browser time, the timestamps in our backend databases, and the user's saved time zone in their profile.

Sometimes all three these can be different, and there are daylight savings time and leap year considerations too. This causes confusion for my coworkers and myself, and is the source of many small but gradually-accumulating bugs.

My coworkers have requested that we try to better understand and teach each other these intricacies, but it's a topic I very much struggle to explain and/or visualize. I've tried discussing it verbally, making paper timelines that you can move around and overlap, and writing some demonstration code about edge cases, but it's still not simple to grasp. Does anyone have recommendations for better teaching materials on this topic?

I'll provide some examples of the difficulties in a reply, but TLDR there are a lot of edge cases we have to account for, and I'm not at all sure I've accounted for them all. Many of us don't realize these are issues and so never code for them until there are already a bazillion entries in our databases, so it's harder to change now and we don't want to do it unless we're sure we can get a better implementation right.


  👤 solardev Accepted Answer ✓
OP here, providing those examples... sorry this is so long:

- Sometimes a user is traveling, and their browser time (which we don't really care about) will be different from their home time zone (which is what we want).

Workaround: We manually do the math to convert browser time to their home time zone, i.e. if their input is "Jan 1, 00:00", Javascript assumes that's in their browser time and saves it in memory as a UNIX epoch (milliseconds UTC). We have to then reverse that epoch time into an object representation (year, date, month), and create a new datetime using a helper library (Moment and/or Luxon) that's "Jan 1, 00:00 in the home time zone we fetched from the profile API, disregarding whatever the browser said".

- Most of our backend databases store data points as ISO 8601 timestamps, which use offsets (-07:00) and not IANA zones (America/Los_Angeles). These aren't always the same, for example because of Daylight Savings Time changes. For part of the year, Los Angeles time is -7 UTC, but the rest of the year it's -8 UTC. That means that with a given timestamp of 2020/01/01 00:00:00-07:00, we don't know whether that site's in Pacific Standard Time, Pacific Daylight Time, Mountain Standard Time during the winter, or Mountain Standard Time year-round because they're in Arizona (which doesn't observe daylight savings). To further complicate things, DST is something that lawmakers occasionally change on a regional or national basis, so a given time zone's DST schedule isn't fixed in stone but will change over the years.

Workaround: Mostly we just ignore this issue and pretend it doesn't exist and pray for the best. For the most part it seems OK, since ISO timestamps are basically milliseconds, and if we need to manually calculate a given range from browser time, we can convert that using their home time zone and request that. But that's a pretty ugly hack because it's never really clear when a stored timestamp's offset is meaningful vs when it's not. Some devs will forget that conversion step because they assume that an offset is the same as a time zone, but that breaks because of daylight savings time.

- Complicating the previous point, sometimes we do data aggregations by day or week, but right now those aggregations disregard local midnights and combine all datapoints from UTC midnight to UTC midnight.

Workaround: I don't think we have one right now, beyond hypothetically fetching both the target date and the days immediately before & after, then recombining the datapoints on the client side to form a 24-hour day local to the user's home time zone. Not nice =/

- Further complicating the above, some of our subsystems don't even store an offset, instead recording all their datetimes pegged to some other zone... sometimes UTC but not always, sometimes it's just in whatever the original developer's home time zone was.

Workaround: We have to manually convert these on a case-by-case basis. A lot of it lives as undocumented institutional knowledge and I worry these tribal secrets will be forgotten with turnover.

- Back on the frontend side, sometimes we also have to do duration math. Like a user might initially select Jan 1, 2020 to Mar 1, 2020, but then want to pan forward or back. But when they say "forward a month", does that mean +90 days? Do we need to account for Feb 29 since 2020 was a leap year? Do we add +3 months given whatever local definition of "month" that time zone has, like gaining/losing an hour from Daylight Savings Time?

Workaround: We use Luxon to help with this as much as possible (and it's tremendously beneficial), but we still have to make some UI decisions about whether to be consistent about the technical implementation of the selected period of time (+/- x milliseconds) or whether it's more important to abide by the user's intent (a "month" isn't really 30 days, is sometimes longer and shorter and varies both within a year and across years). Ultimately we try to guess the most "intuitive" behavior for any given pan interaction, but it causes discrepancies between the daily/7-day-/30-day/12-month views. The length of time selected will dynamically change based on what how granular a date range they're looking at. Sometimes this will cause missing datapoints (like when DST kills an hour), but those will "smooth out"/disappear if you zoom out enough to the weekly or monthly views.

---------------------

Phew. Those are just some of the examples I can remember off the top of my head. I'm sure there are other things I'm missing, but those are some of the subtle gotchas that we ran into. I'd super appreciate any resources you might know of that can help de-discombobulate this for us...