explorations, interests & opinions of a mind

Understanding Time Zone

Building a mental model around Time Zone

Published: Feb 2024

Introduction

How do you deal with Time Zone as an application developer? Search on the web? Like most topics on the web, you might encounter a variety of sources, ranging from incorrect, to search junk, to somewhat useful. Likely you would still come out of it, longing for a structure.

In this article, we will create a mental model of Time Zone. Learn how advice like Always save timestamps as UTC may not work for your situation. Look at how to apply the mental model in your application design. We will look at falsehoods, only to learn to ignore them.

To approach this topic, first, we will summarize the historical context of the Time Zone. Next, we will build the mental model. And finally, we will look at applying this model. I know you would rather have answers (as would I), but this last part will be about helping you ask better questions. It would help you make better choices for your application. But first, let's start with context.

Why Time Zone?

The history of Time Zone begins with the history of tracking time. In turn, tracking time is influenced by celestial motion, history, geopolitics and more. If you are aware that Time Zone works over UTC, that it defines offset for regions identified by Time Zone ID, and that many Time Zones have rules to adjust their offsets for seasons, all of which can change based on geopolitics, managed as a dataset at eggert/tz etc, then you can skip to: How we use Time Zone?. Or else, the rest of the section will go through a brief historical context, to help you understand the same. Few themes that reappear in this section:

  • Time and related concepts were invented in response to our needs for coordination. And evolved into their current form as our needs for coordination evolved.

  • Much of how we track time is a result of historical happenstance.

  • The universe doesn't care about our convenience, but still we got lucky.

Be advised, this section is full of rabbit holes of topics to dive into :) There are expandable sections called Curiosity; raising questions to help you appreciate the context behind the current choices.

We start with celestial mechanics visible to us.

Sun, Moon, Stars and us

The rhythms of life on Earth are roughly synchronized with

  1. Its spin
  2. Its rotation around the Sun and
  3. Moon's rotation around it

Like being awake/active and asleep/recovering, seasons, crop cycles, Tides. Our biology has adapted to these cycles.

These celestial mechanisms lead to an important part of how we track time: Calendars.

Calendars

6000BC

The Earth's spin is what we experience as a day. The number of times the earth spins while rotating around the Sun is around 365.2421. The number of days that the Moon does a complete cycle around the Earth is 29.53. Notice how these are not whole numbers, and Earth's cycle (365.2421) is also not a whole multiple of the Moon's cycle (29.53). These cycles are slightly out of sync with each other. And these are not stable. Earth's spin is slowing, slowly. The moon is, slowly, moving away from Earth. Surprisingly, Earths motion around the Sun seems to be relatively stable. Still, these rhythms are roughly in sync, for us to consider them as subdivisions. Days grouped into months, and months grouped into years. We came up with calendars to track these.

Calendars were used as early as 6000BC. Of course, history is a lot more complicated. Different calendars, with different days, months, years. As humanity got more connected (first Julian and later) the Gregorian calendar became the norm. A historical happenstance.

Curiosity

Calendars helped us sync activities across days, months, and years. What about within a day? That's where clocks come in.

Clocks

1500BC

Clocks also have a long history. Many civilizations developed clocks like water, sand, candle clock, sundials. And used them to divide the day into smaller parts. By the 14th century, monasteries were using Turret Clocks for syncing activities like prayers. Towns started to have clocks in town squares to coordinate activities within them. These clocks were mostly tracking the local position of the Sun. E.g. it wouldn't matter if two towns 200km apart had time differing by 7 min. Towns had their own local tracking of time. Meanwhile, clocks were getting more precise and portable, driven by the use-case of accurate time for sea navigation.

Somewhere along this journey, 24-hour clocks became prevalent. Note that, like the Gregorian calendar, the 24-hour clock was something we converged on. A historical happenstance.

Curiosity

As clocks improved, we started synchronizing our daily lives based on clock time (Sun became secondary). With this context, the world was getting more connected. Which meant a need for syncing/coordinating across different regions.

Time Zones

1850s

It began around the 1850s, when trains started to appear as a mode of public transport. Soon, people could travel between cities. Now people travelling across the city would have to sync their clocks with the city clock. A 7 min difference in clocks between two stations 200km apart, would have lead to confusion. Especially with trains needing to synchronize their movements. So, we (mostly countries with railways) came up with a system to have the same time for most cities within a country/region. This was essentially a scaled up version of a town's local time. Time for the whole region was based on the Sun's rhythm at a place in the region.

Soon, international travel and communication became frequent enough to standardize on how the clock times across countries are related to one another. With the context of the Gregorian Calendar and 24-hour days, as the world got more connected, Time Zones were invented. We came up with Time Zones to standardize offsets used by countries. Two things were needed for this. One, a timeline that all countries agreed to declare their offset against. This is (GMT and later) UTC. And second, offsets from UTC (which we will get to shortly).

UTC

1880s

To sync time between different clocks, we required a reference timeline. GMT was this timeline. GMT was based on the mean of solar time, as measured from Greenwich. Around the 1970s, we started using atomic clocks, which were good enough to keep time better than solar measurements. UTC (Coordinated Universal Time) is derived from a statistical aggregate of some 450 atomic clocks. UTC became the base of most of our time infrastructure. UTC labels the timeline in terms of the Gregorian calendar and 24-hour clock.

Offsets

With a reference timeline, countries would declare offset for their regions. Here the standardization helped with making the offsets more practical. India's offset around 1940 was 5:21:10. Imagine trying to set up an online meeting with that offset in mind. With standardization, India's offset is now 5:30. For synchronization across the world, 5:00 or 6:00 might have been more convenient, but 5:30 probably works better for most of the population in India for their synchronization with the Sun's apparent movement?

Curiosity

Standardized offsets helped with collaboration/syncing across regions. Unfortunately, the story doesn't end here.

DST

1918

Our planet goes around its star (the Sun), while spinning at a tilt of 23.4°. This tilt leads to differences in both duration and intensity of Sunlight, through our year-long cycle around the Sun. As people started synchronizing their life with clocks (instead of the Sun), we started noticing that the Sun was still out, while the clock said night. DST was/is an attempt to help people synchronize with more sunlight in the evening.

Consider the year 2023 in Los Ángeles (America/Los_Angeles). On Sunday, 12 Mar 2023, 2:00am, clocks shifted forward by an hour. In other words, clocks went from 1:59am to 3:00am. There was no 2:30 that day. This also means evening timings are shifted 1 hour earlier, relative to the previous day. The sunset in Los Ángeles, which was around 6pm on 11th March, happened at 5pm on 12th March. And on Sunday, 5 Nov 2023, 02:00am, clocks shifted backward by an hour, from 1:59am to 1:00am. With the sunset, which happened around 6pm on the 4th, now happening at around 5pm on the 5th. And, clocks read 1:30am twice, that day.

DST is the reason for most shifts in offsets. It impacts more people, than other non-DST offset changes. Many of the developed countries implement DST. Which means, every year, people in these countries experience offset shift, twice.

Time Zone went south

By now you should have a sense that Time Zone is a compromise around nature's rhythm and us syncing our activities. DST is one part of that compromise. There are some extreme examples of this compromise. North Pole and South Pole. For some part of the year, the South Pole has 24 hours of daylight, and the North Pole has 24 hours of darkness. Visa versa for the other parts of the year. Research stations near the South Pole (Antarctica) often use their country's local time. And every so often, whatever is convenient.

Curiosity

It's Geopolitical

Now that we have covered the context for Time Zone (and DST), let's emphasize the role of geopolitics in this. We standardized on reference time. And we standardized the process of getting local time. That is, local time should be defined as an offset to the standard reference time (UTC). But the offsets themselves are decided by the governments. It's a geopolitical concern. If a region splits into two geopolitical regions, it might create a new zone and a new offset. If the people in power of a geopolitical region, decide on changing the region's offset, everybody outside would have to respect the rule to get local time for that region. A DST rule could change. Countries might have disputes over borders). Or have internal disputes. Some people might have to deal with different Time Zones for different activities in their lives. Etc. In other words, we agree on the process of getting clock time (get offset and add to UTC), but the offset itself can change with time.

For all these reasons, it makes sense to consider the Time Zone as an offset followed by a set of clocks (instead of a region), called civil time. Below is a visualization of recorded offset and DST changes in the Time Zone. We still show Time Zones by region, since it's the common use-case. It only shows changes since 1970, as the data is more reliable since then. Hover over a region to see the number of changes. Click on the region to open up the relevant part of the tzdb, which has offset rules and comments.

With this context, let's understand how Time Zones are used.

How we use Time Zone

We will only consider Time Zones from an application development perspective. There are four broad use cases.

  1. Get civil time (official time followed by a region) from UTC (UTC → CT).
  2. Vice versa (CT → UTC) for a particular region
  3. Convert between civil times (CT1 → CT2).
  4. Do arithmetic like UTC + Duration or CT + Duration.

Let's start with the straightforward case: UTC → CT.

UTC → CT

Previously, we saw that civil time is an offset added to UTC (thanks to standardization efforts). So, if we can get an offset for a region, we can add it to UTC to get the civil time. Here is a representation of this simple model. We will build on it as we add more details.

The first problem we face is the geopolitical nature of regions. Time Zone could have been defined for geographical boundaries. But as the definition of a region can change (or, be contested), it makes sense that we have an indirection: Time Zone ID. Time Zone ID usually refers to a region, but could be used independent of it. Your browser reports UTC as your Time Zone ID, likely set in your operating system. The list of Time Zones along with their details is maintained at (tzdb) eggert/tz provided by IANA. The visualization we saw before is built on this data. Another one is being maintained by Microsoft, for its OS. We are going to assume IANA tzdb for this article.

So, instead of region, we use Time Zone ID, which represents a region (or occasionally a set of clocks in a region). Given the Time Zone and the UTC timestamp, we want to derive the civil time. As mentioned before, Time Zones themselves are a geopolitical concern. This means offsets can change with time. These changes are also maintained at eggert/tz. But it has a bigger implication. To get the offset, we also need to pass in reference time (because the offset could have been changed after or before that). So, our new model becomes: pass Time Zone and UTC, to get the offset for a region (which uses tzdb) and then add the offset and UTC to get the civil time.

What about DST? DST is like a planned offset change, that applies every year. These are recorded as rules in tzdb. E.g., based on the rule, the time might shift forward or backward (usually by an hour, but could be more or less). For practical purposes, one can consider them to be offset changes. So, our mental model doesn't change.

CT → UTC

This sounds simple enough, and it is until we consider the reverse. Converting from civil time to UTC (CT → UTC). This conversion would have been straightforward had the offsets remained unchanged. Unfortunately, as we saw earlier, the offsets change, and many regions follow DST. These offset changes lead to a shift in local time. Either forwards or backwards. Let's look at the implications visually.

UTC is always moving forward. The timeline for civil time can have gaps and overlaps. This leads to three possibilities when trying to convert local time to UTC.

  1. If there are no offset changes, civil time maps to UTC. This is the first case in the visual.
  2. A civil time could be invalid (no corresponding UTC time). This could happen if civil time is user input. The second case in visual.
  3. Civil time could map to two UTC times. This is the third case in the visual.

In more formal language, the function (UTC → CT) is neither one-to-one, nor onto. In other words, its inverse (CT → UTC) is not a function. But, in practice, we can assume that the inverse (CT → UTC) would lead to 0, 1 or 2 UTC timestamps. With 1 being the most common case. This is important in how we deal with Time Zone arithmetic.

The cases of ambiguity and invalidity of CT → UTC is why people suggest keeping time in UTC. We will add more nuance to the topic in the next section.

CT1 → CT2

We looked at UTC → CT and CT → UTC. CT1 → CT2 could be achieved by first doing CT1 → UTC and then UTC → CT2. In other words, first convert from the source Time Zone to UTC, and then from UTC to the target Time Zone.

UTC, CT + Duration

These are operations like adding a month to the time, or adding a day to the time, or adding 300 hours to the time. Arithmetic becomes more complicated if we want to accommodate what is intuitive to us. When we say same day next month, we usually mean same day from the start of next month, irrespective of whether the current month has 28, 29, 30 or 31 days. Similarly, arithmetic around years should adjust for leap years. Intuitive calendar arithmetic (day, week, month, and year) is common in libraries. Complicated scenarios like the end of next month might be supported by helper functions in the library. Most date-time libraries would support these kinds of calendrical arithmetic.

But when it comes to timezone and time within a day, implementations vary widely. UTC + Duration is somewhat straightforward. There is no offset. We don't need to worry about offset changes. Most libraries could handle UTC + Duration consitenctly. CT + Duration is more complicated. We will only consider how Time Zone affects these calculations. E.g. the intent of adding a day to a timestamp might be to get the same time next day. But how should one accommodate for a DST or an offset change within that time period? There are a few ways in which software libraries around Time Zone handle this. Remember the three cases? Time Zone can stay same (simplest case). And the other two are: Time Zone offset can move forward, or backward. Let's consider concrete examples. You are in Europe/Copenhagen Time Zone. Let's say you want to add a day to 2019-03-30 3am. Unfortunately the offset changed from +1 to +2 on 2019-03-31 2am (civil time). Intuitively by a day you probably meant, same time next day. So adding a day to the time above should result to 2019-03-31 3am. But if we blindly add 24 hours to 2019-03-30 3am, the result would be 2019-03-31 4am. To get the intuitive result, time has to be adjusted. Temporal.ZonedDateTime.add explains these adjustments. Different Time Zone libraries handle these cases differently. Here are the behaviors I have come across.

  1. Doesn't support it: Noda Time - The library would support other operations like UTC → CT, CT → UTC and UTC + Duration. You would have to do civil clock time arithmetic using those.

  2. Does arithmetic in UTC: Elixir DateTime - Converts the civil clock time to UTC, does arithmetic and converts the result back to civil time. This means it doesn't adjust for Time Zone shifts. This is the case of 2019-03-31 4am in example above. Time between start and end will always be 24 hours. Note that because the resulting time is a mapping from UTC → CT, it will never be invalid.

  3. Does whats intuitive: Luxon, Temporal - This is the case where the arithmetic above would result in 2019-03-31 3am. When there are two possibilities, Luxon picks the source timestamp offset. To do explicit operation, you can add duration in hours.

  4. Does not support? day.js - The implementation does not adjust for Time Zone offset changes. In dayjs case, likely, it's not supported by the library.

How does the logic in Luxon work?

So, if you are working with date time and Time Zone arithmetic, you have to evaluate the library you are using. There are basically two cases the library has to accommodate for. The case where offset shift forward. And the one where it shifts back. I have listed three of these cases. Parse these cases with your library and add a day, an hour etc to understand what the library supports.

tzcivil timeUTCoffset fromoffset to
Pacific/Apia2011-12-29 23:59:59.9999-10+14
Europe/Zurich2023-03-26 01:59:59.9999+2+3
Europe/Zurich2023-10-27 02:59:59.9999-3+2

For Luxon, Temporal, DayJS here is a Obsersable playground. Dotnetfiddle playground for NodaTime.

Date operations gotcha

Timezone arithmetic leads to the nuance that if you are doing date arithmetic with this abstraction, sometimes you might not get what you expect. E.g. if you consider midnight as day start, and you want to find day + 1, but offset changes within that duration. Depending on what your library chooses, you might get the same day or next. If you don't care about time component, and only want to manipulate date, prefer using date operations (if your library supports it) or use UTC. Except in rare case like 24 hour shift (like Somoa), UTC date calculations should give you the right answer.

We have only covered offset changes in this section. Other kinds of changes, although rare, are still possible. Like, new region, or zone aliasing etc. If Time Zone is critical for you application, I would suggest spending some time understanding and keeping track of changes.

Application design concerns

With the mental model in mind, while designing applications, we could think of time from two perspectives.

  1. Users intent.
  2. Whether the event is in past or future.

Let's consider the simpler case. Past events. This is where you record timestamp for when something happened in the past. For example, time of birth, or when a purchase was made. Recording them as UTC timestamp makes sense. But also, depending on what the user is expecting, you might want to keep a record of timezone. E.g for a global app, to understand local daily rhythms, it would have to differentiate events based on the region the event happened in. For many applications, you can save users timezone in their profile configuration and convert UTC timestamps to civil time, while rendering the UI. but what if user travels and uses your service? It's a decision you have to make, based on your application and users expectations.

Next, future events. Timestamps for future events are more complicated. Because the offset could change (as we discussed earlier). For future events, it's important to understand if the user is expecting civil time. For example, if you are a school, you have to respect their civil time. Schools might have 8am as their opening time. And might keep it through DST changes (in other words, restaurants open at 8am based on civil time). Again, you have to think carefully about your applications and users expectations. And if your user expects local time, you might even have to save the time within day, for the Time Zone, and do arithmetic to get what user is expecting.

Few cases

Let's look at a few cases to get some familiarity. We will assume DST of 1 hour, where applicable.

  • Restaurant opening time: You run a real time food delivery app. Customers can order from restaurant based on their operating time. Your region has DST. So, every year, once time moves forward, and once, it moves back. Should your restaurant operating time change based on DST? There is no straight forward answer. It depends on restaurants. Likely, most restaurants would continue to operate the same civil hours, in DST also. And software should implement what restaurants implement in real world.

  • Calendar app: A user wants to create a recurring calendar event. Should the event consider civil time or ignore offset changes (in which case the during DST, event would be scheduled 1 hour earlier)? Intuitively, it makes sense to follow civil time. But what if the event is shared with users in other Time Zones? Let's consider another case, where user wants to create a one off future event. But before the event, government decides to change the offset (or move a future DST earlier). Still, civil time might make sense. Unless the event is shared with people in other Time Zones, in which case, it might make sense to use the UTC time of the future event. From my understanding, Google Calendar saves one-off events in UTC, and uses event timezone for recurring events.

  • Counting streak: Let's say you want to track and show study streak (days of study) for students. The students might want these to be counted based on civil time. What happens with offset changes? Since we only care about days, this could just be calendar calculations (no need for Time Zone concerns), except for rare cases like Somoa moving forward by 24 hours in dec 2011. Most date time libraries don't handle this case. Likely, the effort of supporting this case, might not be worth the returns. Github UTC.

Implementation concerns

With the understanding of how Time Zone affects your application, here are some hints to implementation details:

  1. Storing time
  2. Transferring time (serialization)
  3. Time conversions
  4. Updating tzdb

I have only given pointers for these concerns. There are context specific concerns to get into. Before discussing them, we first have to look at the timeline that most systems use in practice: Unix time.

Unix time and leap seconds

Most computing systems don't use UTC directly. That's where Unix timestamp comes in. Unix timestamp is number of seconds since 00:00:00 UTC on January 1, 1970 (the Unix epoch), skipping the leap seconds. Most systems keep track of time as Unix timestamp (or some similar epoch representation). But they also sync time with UTC (remember UTC is timeline that everyone agrees on. Unfortunately, one can't figure out UTC locally. We have build quite complicated infrastructure to disperse UTC (this includes GPS Satellites). Since Unix time doesn't have the concept of leap seconds, when those happen in UTC, the machine has to do something with it. Common approach was to repeat/skip a second, based on leap second. More recent (and better) approach is to spread the leap second across the day. E.g. each second is just a little longer/shorter.).

What does ignoring leap seconds give us? Ease of calculations. Number of seconds in a day is always 60×60×2460\times60\times24. But it ignores leap seconds. If you really need precise calculations, like exact number of seconds between two timestamps. you would have to find a way to include leap seconds. You could consider TAI. If you have to record the leap seconds in timestamps and keep track of them in time arithmetic, I wish you luck.

For storage, Unix time representation has different trade offs compare to the other common representation of time, ISO8601. Unix time is a representation of UTC (which avoids wrong interpretation), but is not quite human friendly. On the other hand, ISO8601 is human readable but could be interpreted wrongly, and both have slightly different sorting characteristics.

How does this affect time zone. Mostly, it doesn't. Instead of using offset with UTC, offset is applied on Unix time. This leads to leap seconds being ignored in Civil time also. Most libraries around time and Time Zone use Unix time or some similar timeline. Which means, you as a application developer likely won't have to deal with leap seconds. Now, let's get back to implementation concerns.

Storing time

Here mostly you have to understand what information is needed for users intent (or whatever you decided based on that), and how to store that in your persistence layer. Information might be UTC, for most cases. Time Zone (remember to use IANA code) for few cases. And if you are dealing with future recurring times, or future civil time, etc, you might have to consider saving that information also. Or if your use case only deals with date, may be save just the date (without time)

Transfer time

You might have to serialize time to transfer or export it. It's best practice to do so in standardized format like ISO8601. Unfortunately, the standard doesn't include Time Zone id. It supports offset, but by now you should know, offsets can change for a time zone. Different libraries have adopted different conventions. I would suggest following Date and Time on the Internet: Timestamps with additional information which seems to be followed by Temporal and suggested by w3c.

Time conversions

We have already seen that libraries have differences in how they handle certain Time Zone operations. I would suggest to first understand the operations your application might need, and then evaluate libraries for those use cases. I do think the approach used by Luxon and Temporal should work for most cases.

Also, be careful with using time (which includes date), when all you need is date operations. If application only needs Date arithmetic, you could ignore Time Zone (except for the rate cases where Time Zone changes could make a date invalid (Somoa 2011), or repeat (hasn't happened yet)). If you want to support Somoa, you have to get the time zone database and a library which supports arithmetic over time zone.

Updating tzdb

Since the Time Zone database could be updated, for applications dealing with Time Zone, you would have to consider how to update the tzdb periodically. Browsers ship this with their regular updates. Many libraries support updating of the tzdb, e.g Elixir tz, Noda Time.

That covers most of what I have figured out. But I can't end this post without covering the often shared topic around Time Zone - fallacies.

Shall we fallacy?

Somehow it's fashionable to share fallacies around time and related topics. May be we could have implemented better systems, but it shouldn't stop us from building better understanding of current systems. I would rather, people share a mental model which, even if wrong, might lead to more learning (and better mental models) for readers. Dealing with Time Zone (and time) is not intuitive. But complaining about it is not the solution. Anyhow, you can expand the section below to understand how a mental model might be useful to look at, and promptly ignore fallacies.

Fallacies map to mental model

Abolish Time Zone?

A few suggest this in discussions around Time Zone. Why is this not practical? Consider how this would be implemented. All the institutions (banks, government offices, schools, shops, restaurants) would have to change their timings. Population of all nations would have to learn completely new way of talking about their day time. All written references of civil time (novel charting a characters day with day starting at 0), would read strange to population which has a different hour for starting of day. I think Time Zone is here to stay. We might make it less annoying, by abolishing DST.

Parting suggestions

Consider Time Zone as a geopolitical thing. Like most geopolitical concerns, use a database to get the details and very few assumptions for working with Time Zones. If you are doing Time Zone arithmetic, I would suggest avoiding it if possible, and if not, evaluate the library options before picking one.

Use fallacies to update your mental model. But otherwise, ignore them.

Acknowledgment

Thanks to Saneef, Anantha Kumaran, Dhruva Sagar for reviewing the draft. And especially Arjun Karande for giving much needed critical feedback, and push to care.

...