Working with time zones sometimes becomes confusing, especially when you have just started to work on a timezone-dependent application, and you never had this experience before. The Timezone world is very unstable. Every year, there are a bunch of time zone changes that take place on Earth, and regular dev doesn’t think about it (and actually shouldn’t). But for a better understanding of how this time zone world exists and how it is related to the Javascript Date object, I want to go through a couple of things here. Below, I have multiple examples you’ve probably never thought about. Bare with me; you will find something new for yourself.
If you want to follow all the examples from this article, you only need the Console and Sensors tabs in Chrome. If you don’t know how to find Sensors functionality, then use this hotkey within Chrome Dev Tools:
ctrl + shift + p
- Windows
⌘ + shift + p
- Mac
Then type “Show Sensors” - you will get this option:
When you hit enter, you get the Sensors tab, where you will be able to change your location, timezone, and locale.
I want to discuss two things, first is what timestamp is and second, what do those UTC/GMT, CST, CDT, America/Chicago, etc mean? Why do all these exist? How do those work with Javascript Date object? I’ll try to make it clear to you.
Without further ado, let’s get started.
Let’s Imagine that you are an astronaut circling the Earth at ISS, at some point you decided to run your stopwatch, which measures milliseconds (Javascript uses milliseconds under the hood of a Date object). From the time you run a stopwatch, you never stop it. So number of milliseconds is growing with time. For every point in time, you have some number of milliseconds passed. No matter where you are living on Earth, which continent, country, city, or street - the point in time on this stopwatch is equal for everybody. It sounds pretty obvious, but I found that it confuses a lot of people. Consider this stopwatch run as a timestamp. In other words, every state in time of the Earth has a number. Ok, now we have constantly growing milliseconds, but it’s not very human-friendly. You can’t tell someone let’s meet somewhere in the future at 1893456000000 ms.
This is where time zones start to take place. This date and time when our imaginary astronaut has run the stopwatch is 01 January 1970 at midnight (you’ve probably heard about Unix Epoch).
Below is a little example with 3 different timestamps and corresponding dates just for visualization.
Ok, we know that at 0 ms we have the date 01 January 1970 00:00:00, but the other question is where exactly? The answer is below in the screenshot. This line which crosses part of the planet is called UTC (or UTC+0). So at 0ms all the locations which lay on this line have 01 Jan 1970 midnight.
If you want to see exact locations, you can find them here
Below, I’ll be explaining more about UTC abbreviation.
The most important point here is to understand that behind the Javascript’s Date object is a timestamp. All below return timestamp (number).
Number(new Date());
new Date().valueOf();
new Date()[Symbol.toPrimitive]('number');
But don’t do this in the code because there is more right way to get the current timestamp from the Date object, which is:
new Date().getTime();
Ok, now that we know what a timestamp is in Javascript, let’s talk more about time zones and how they work with the Javascript Date object.
First, let’s consider 3 different groups of time zones you’ve probably come across.
UTC
and GMT
are used interchangeably. Even if it is not very true, in our (not scientific) case let’s consider that they are the same.CST
, EST
, CDT
, MST
, EST
, etc)America/Chicago
, Europe/Brussels
, etc)
All these types represent some geographical areas, but they have different properties and behavior.
I’ve mentioned UTC in the Timestamp section a little bit, that we calculate our stopwatch at UTC+0 geographical locations. Let’s dive into the UTC abbreviation more. Consider UTC as the baseline for all other timezone types. It’s just a split of the Earth into 24 equal pieces 15° each (24hrs * 15° = 360°). In other words, Earth rotates 15° every 1 hour. It rotates full circle for 24 hours. Each of these 15° pieces is mimicked with UTC names, from UTC-11 to UTC+12 (including UTC+0), which in total is 24.
I found a good picture which illustrates what I’ve just said.
Look above and below the map (black horizontal lines). Above you can see degrees, below you can see hours. So you can use UTC these two ways.
Wait a minute you would probably say but what about UTC-12 or UTC+14 which do exist for some parts of the planet, they are out of bound of -11 to +12. And this confused me at first, too. But the answer is easy, you should just understand that UTC can be used in 2 different contexts. First, I’ve just explained to you regarding equal physical pieces of the Earth and second is just time offset.
But what does it mean to be time offset? Let’s take a look at what’s residing under UTC-12. You will see such a name as the U.S. Outlying Islands, so instead of leaving those islands in UTC+12, there was just a political decision to follow UTC-12 time to be consistent with other parts of the country. If those islands wouldn’t be part of the US we would likely have seen different offsets.
The same story with UTC+14. The Line Islands before 1994 were in the UTC-10 timezone, but the local government decided to shift to the UTC+14 timezone to be consistent with all other parts of the republic.
The other reason can be Daylight Saving time when you shift time, for example, from UTC+12 to UTC+13.
Javascript allows you to set even more extreme offsets:
new Date("2023-07-15T10:00:00.000-23:59")
Or
new Date("2023-07-15T10:00:00.000+23:59")
Trying to use ±24 offset returns an invalid Date because it doesn’t make sense since 24 just makes a full circle around the Earth and returns you back to the UTC+0.
It looks funny that we have 2 locations on Earth where the time between them is more than 24h. Like these two (26 hours apart):
If you want to play with UTC to get more understanding of how it works, you can find an interactive map on the web like this:
I mentioned above that UTC is a base of the Date object in the Javascript. It means that when you create a new Date object, it takes the current UTC+0 time and adds up the timezone offset of the user location to finally get the user local time.
For example, type this date in your browser console:
new Date('2023-07-15T13:00:00')
You will get something like this:
Sat Jul 15 2023 13:00:00 GMT-0500 (Central Daylight Time)
Depending on where you are, results can differ by offset. In my case, it’s GMT-0500 (minus 5 hours). Remember that here, we use GMT and UTC interchangeably.
Calculation:
I like to think about Date object creation from such string in this way (and probably this is how it works behind the scenes):
Javascript sees this string 2023-07-15T13:00:00, parses it, and calculates the UTC timestamp for this date (converts string to timestamp). It returns the same timestamp as this static method with specified arguments:
Date.UTC(2023, 6, 15, 13, 0, 0, 0); // returns 1689426000000
Then Javascript observes an environment where code is run and checks the timezone offset from UTC+0.
new Date('2023-07-15').getTimezoneOffset();
+300 minutes in my case (it can be with a minus sign as well depending on where you are)
Then it adds +300 minutes (or +18000000 ms) to the 1689426000000 timestamp which we got on step 1.
Then, it creates a Date from this timestamp:
new Date(18000000 + 1689426000000);
You can check that you are getting the same results from these 2 Date objects creation (your results can be different because of the offset part):
Or in other words, this equation is true:
( new Date('2023-07-15T13:00:00').getTime()
-
new Date('2023-07-15').getTimezoneOffset() * 60 * 1000 )
===
Date.UTC(2023, 6, 15, 13, 0, 0, 0)
<Local Date Timestamp> - <UTC offset> === <UTC+0 Timestamp>
The reason why I’m putting date string as an argument to here:
new Date('2023-07-15').getTimezoneOffset() // 2023-07-15 argument is set
is that the place where you are may experience daylight saving time (and overall historical changes have an impact on what you can get), so to run the method this way new Date().getTimezoneOffset()
can give you different results based on where you are and the date and time you are running this function.
Ok, now let’s talk about timezone names like CST (Central Standard Time). Consider such abbreviations as other names for UTC offsets. It’s not very convenient for people to talk about time zones in the UTC language. For example, if someone asks you, “What timezone are you in?” without an abbreviated timezone, you would answer something like “UTC-6”. It would work, but it’s not very comfortable. So people decided to create such abbreviations like in the title of this section. Single Abbreviated timezone name can have only a single UTC offset, but not vice versa.
For example, following abbreviations are sticking to a specific offset.
But UTC offset itself can have many abbreviations.
Pay close attention when you are using such names. For example, CST can be either Central Standard Time, China Standard Time, or Cuba Standard Time. So try not to create a Date object this way:
new Date('15 Jul 2023 13:00:00 CST')
Because it’s ambiguous (and it is not in the spec).
In my case, when I run this command in Chrome
new Date('15 Jul 2023 13:00:00 CST')
it treats CST as Central Standard Time. And it’s very likely it does work the same way in Chrome no matter where you are. But it doesn’t guarantee that it will work the same way forever.
I tried to set up a current location in China.
But anyway, it sets offset as 360 minutes or 6 hours (UTC-06)
(new Date('15 Jul 2023 13:00:00 CST').getTime()
-
Date.UTC(2023, 6, 15, 13, 0, 0, 0)) / 1000 / 60
So, even if you are located in China, Chrome treats CST as American Central Standard Time, not China Standard Time. My initial assumption was wrong that the browser will use offset based on the user location if you use such names that have multiple translations.
The other way to check offset (probably more visually easier) is doing the following:
You see that returned dates are the same, which means that in this case
CST === -06:00
Another property of the Abbreviated time zones is that it relates to UTC offset, not UTC geographical split. For example, China is scattered across almost five UTC 15° slices (or five 5-hour-long areas), but their local authorities have decided to match only a single UTC+8 offset. So, China Standard Time is equal to UTC+8 offset (not a UTC slice).
This is the only timezone type that has historical data of the timezone of any (inhabited) locations. Time zones above, like UTC or CST/CDT, are just names for the specific offsets. Yes, they apply to specific regions of the planet, but they don’t save any historical data. For example, CST is applied during winter time for some regions, and then during summer time, CST is not applied but CDT. If any location decides not to use Daylight saving from some point in time, then they will have just CST throughout the year. Or if the location's government decides to move from one timezone to another, like the example below when Kentucky’s Wayne County in the US decided to switch from CST to EST (it happened 17 August 2000). But timezone data before this point in time will be lost. You can’t extract this information just from the CST name. This is where the IANA timezone comes into rescue.
Just imagine that internally, in the browser, you have historical data like this:
IANA name |
Offset |
Active from |
Active to |
---|---|---|---|
America/Kentucky/Monticello |
UTC-06 (CST) |
01 Jan 1970 |
16 Aug 2000 |
America/Kentucky/Monticello |
UTC-05 (EST) |
17 Aug 2000 |
Current |
Also, you don’t need to think about Daylight saving time when you reference to IANA name; it already observes it instead of you.
To understand it better, let’s consider some examples. You will see the power of the IANA timezone along with interesting timezone change examples.
If you want to get your current IANA timezone, you can type this command in the console:
Intl.DateTimeFormat().resolvedOptions().timeZone
Question: Can the Timezone of the geographical area be changed?
Answer: Yes.
History:
Let’s explore this example when a geographical area has changed its timezone. Wayne County in Kentucky state in the US on 17 August 2000, moved from the Central Time Zone to the Eastern Time Zone. You can read more about it here.
For my example I’ve taken the city of Monticello from Wayne County. It has the IANA timezone America/Kentucky/Monticello
Since this change took place in 2000 then, let’s see what date and time we will get in 1999 (before the change) and in 2001 (after the change)
Tech part (proof):
(new Date(Date.UTC(1999,6,15,13,0,0,0))
.toLocaleString('en-US', {timeZone: 'America/Kentucky/Monticello'}))
Returns 7/15/1999, 8:00:00 AM
(new Date(Date.UTC(2001,6,15,13,0,0,0))
.toLocaleString('en-US', {timeZone: 'America/Kentucky/Monticello'}))
Returns 7/15/2001, 9:00:00 AM
So, in 2000 it returns 1 hour earlier than in 2001 for the same timestamp.
Those two invocations differ only in the passed year to the Date.UTC static method. So it proves that the IANA timezone tracks a history.
Question: Is geographical area always attached to the single IANA timezone?
Answer: No. Actually, Example #1 can prove it as well, but I decided to take another example.
History:
Let’s explore this example I found in the IANA log here.
There is a quote from the link:
The northern edge of Chihuahua resumes US DST rules. New zone America/Ciudad_Juarez for the western half, which rejoins -07 from 2022-11-30.
It means that the whole of Chihuahua in Mexico before the end of 2022 had lived under the same timezone rules. They had a timezone named America/Ojinaga
. However, their local government had decided that the northern part of the Chihuahua would shift time by 1 hour during DST. What it means for IANA is that they couldn’t use the same name America/Ojinaga
for the whole region. They didn’t have any other choice except to create a new name America/Ciudad_Juarez
in that case.
If to summarize shortly, Chihuahua before 2022 had a single IANA name America/Ojinaga
, after 2022 Chihuahua was split into two halves. The first lives under an old name America/Ojinaga
, and the other one lives under the America/Ciudad_Juarez
timezone name.
Tech part (proof):
This time I want to show you how the browser is syncing with the IANA database when a new timezone change event is happening. So, we know that the event happened on November 30, 2022. Then let’s run Chrome version 106 (released September 27, 2022), which is before Chihuahua split, and then run Chrome version 118 (released October 4, 2023). 118 - because currently, I just have this version installed, and it’s far in the future from the event that took place.
Run this:
new Date(Date.UTC(2023, 6, 15, 13, 0, 0, 0))
.toLocaleString('en-US', {timeZone: 'America/Ojinaga'})
Chrome version 106 returns 7/15/2023, 7:00:00 AM
Chrome version 118 returns 7/15/2023, 8:00:00 AM
So same code, same browser, same system time, but different results based on Chrome versions.
Btw running this timezone on version 106 throws an error:
new Date(Date.UTC(2023, 6, 15, 13, 0, 0, 0))
.toLocaleString('en-US', {timeZone: 'America/Ciudad_Juarez'})
Invalid time zone specified: America/Ciudad_Juarez
because it wasn’t added for the time being.
Question: What if we shift OS system time, will it follow timezone historical changes?
Answer: Yes.
History: On 31 December 1994, Line Islands (Republic of Kiribati) changed the timezone, they shifted time to the whole day (from UTC-10 to UTC+14). Why did they do that is better to read on the web, but it is an interesting case that we can use for the sake of example with the time zones.
Tech part (proof):
I took a city with the name London, no, not the one you are thinking about but the one on Kiribati island - it’s part of a group of islands.
If you are curious, then this city is located here.
Add a new Location in the Chrome sensors. You can use the settings I’ve set.
Latitude: 1.984623759310528
Longitude: -157.47449242454888
Timezone ID: Pacific/Kiritimati
Locale: en-US
Ok, we know that in 1995, everything had changed for that location. So let’s try to check it.
But now, we will not change the browser version (actually, it doesn’t make sense; specifically, Chrome didn’t exist at that time). Let’s change the system time (OS time) to somewhere in the middle of 1994, then check what Date object we will get, then shift the system time again to 1996 and do the same procedure.
I’m on a Mac, so I go to the System Settings → General → Date & Time → Change the date and time.
Ok, this is what the Date object will return for 1994:
After changing the year to 1996:
You see that in 1996, for the same system date, we are getting a different day, Jul 16 instead of Jul 15.
The same effect we can get by using timeZone property of the toLocaleString method
From all these above: