Working with time zones in javascript

https://medium.com/@toastui/handling-time-zone-in-javascript-547e67aa842d
  • Transfer


Recently, I was working on the task of adding time zones to the JS-library of the calendar, which my team is leading. I was well aware of the useless support for time zones in JavaScript, but I hoped that abstracting existing data objects would make it easy to solve most of the difficulties.

However, my dreams were ruined. When I delved into the task, I realized that in this language it is really difficult to work with time zones. Implementing something more complicated than simply formatting the time display and calculating the date using complex operations (calendar functions) was extremely difficult. I gained valuable experience solving this problem, and this entailed new difficulties.

In this article I want to discuss what I encountered and how I solved it. While I was writing the text, I realized that the cause of all adversity was my poor understanding of the very theme of time zones. In the light of this awareness, I suggest first to talk in detail about definitions and standards, and only then move on to JavaScript.

What is the time zone?


The time zone is a geographic region in which the uniform local time is used, set by the government of the country. Many countries entirely belong to some specific time zones, and in the territories of large states, like Russia and the United States, several time zones are used. It is curious that although China is also quite large, however, it adopted only one time zone. Sometimes this leads to strange situations when sunrise begins in the western part of the country around 10 am.

GMT, UTC and offset


GMT


Local South Korean time is denoted as GMT+09:00. GMT stands for Greenwich Mean Time (Greenwich Mean Time), that is, the time on the Royal Observatory clock in Greenwich, UK. It is located on the zero meridian. The GMT-system's radio signal began broadcasting on February 5, 1924, and it itself became a world standard on January 1, 1972.

UTC


Many people believe that GMT and UTC are the same thing, often using them as interchangeable systems. But this is a mistake. The UTC system appeared in 1972 as a way to compensate for the effect of the rotation of the Earth. The system is based on International Atomic Time (International Atomic Time), calculated from the frequency of electromagnetic oscillations of cesium atoms. In other words, UTC is a more accurate replacement for GMT. Although the real time difference between the two systems is very small, it is still better for software developers to rely on UTC.

An interesting fact: when UTC was still being developed, in English-speaking countries it was suggested to call it CUT (Coordinated Universal Time), and in French-speaking countries - TUC (Temps Universal Coordonn). However, none of the camps could not win, and the system agreed to call UTC, by letter of both the proposed options (C, T and U).

Bias


+09:00to UTC+09:00mean that the local time is 9 hours ahead of UTC-time standard. That is, when it is 9 pm in South Korea, it is noon in the UTC region. The difference between standard time and UTC-local called "offset", which is expressed as a positive or negative values: +09:00, -03:00etc...

In many countries it is accepted to give their time zones unique names. For example, the time zone of South Korea is called KST (Korea Standard Time), its offset is expressed as KST = UTC+09:00. However, the displacement is +09:00used not only by South Korea, but also by Japan, Indonesia and many other countries, therefore the connection between displacements and belts is expressed not as 1: 1, but as 1: N. The list of countries with offset +09:00is presented here .

Some offsets operate not only on the clock. For example, in North Korea, the standard time is +08:30, and in Australia in some regions are used +8:45, +09:30and +10:30.

The full list of UTC offsets is here .

Time zone! == offset?


As I already said, we use time zone names (KST, JST) with displacements interchangeably, not distinguishing them. But it will be wrong to consider the same time and the shift of a particular region. There are several reasons:

Summer time (DST)


In some countries, this term is unknown, but in many states there is a daylight saving time, mainly in Europe. For this, the international term DST is adopted - Daylight Saving Time. It means the transfer of hours in the summer period one hour ahead of the relative standard time.

For example, in California in the winter PST (Pacific Standard Time, Pacific Standard Time) is used, and in the summer PDT (Pacific Daylight Time, UTC-07:00) is used. In the United States and Canada, the term Pacific Time (PT, Pacific Time) applies to regions that use two time zones.

When does summer time begin and end? It all depends on the country. For example, in the United States and Canada until 2006, DST was used from 2 am on the first Sunday of April to 12 am on the last Sunday of October. And from 2007, summertime began to count from 2 am on the second Sunday of March to 2 am on the first Sunday of November. In Europe, in different countries, the progressive use of DST is practiced depending on each time zone.

Do time zones change?


Each country itself determines which time zones to use it, so the local time may vary for any political and / or economic reasons. For example, in the USA, the DST borders were changed in 2007, because George Bush initiated an energy policy in 2005. Egypt and Russia used to switch to summer time, but they refused from 2011.

In some cases, the government can change not only the order of daylight saving time, but also the standard time. For example, earlier on Samoa offset was used UTC-10:00, but then they switched UTC+14:00to reduce losses in trade due to the time difference with Australia and New Zealand. This decision led to the loss of the whole day from the life of the country - December 30, 2011, about which newspapers around the world wrote .

In the Netherlands, the shift has been applied since 1909 +0:19:32.13, since 1937 the country has moved to +00:20, and from 1940 to +01:00, since then, most of the time has not changed there.

Time Zone 1: N Offset


So, the time zone can have one or more offsets. What time is taken as the standard depends on the current political and / or economic reasons in a particular country.

In everyday life, this does not cause difficulties until you try to systematize this data on the basis of some rules. Imagine that you want to set a standard time on your smartphone using an offset. If you live in a region where daylight saving time is practiced, then the smartphone should know exactly when to go back and forth. That is, you need to establish the relationship between standard and summer time in the same time zone (for example, Pacific Time).

But this can not be done with a couple of simple rules. For example, when the beginning and the end of DST were changed in the USA in 2007, May 31, 2006 was supposed to use PDT ( -07:00) as the standard time , and March 31, 2007 - PST ( -08:00). It turns out that in order to refer to a specific time zone, you need to know the whole history of changing time zones or the date of changing the rules for daylight saving time.

You can say: "The time zone in New York is PST ( -08:00)." However, it is necessary to clarify: "The current time zone in New York is PST." At the same time for the exact implementation of the system you need to use an even more accurate expression. Forget the term "time zone". You have to say: “PST is now used as a standard time in New York.”

So what should we use instead of offset to determine the time zone of a particular region? The name of this region. More precisely, you should group into a single time zone those regions in which the same time goes to daylight saving time and standard time. Names like PT (Pacific Time) can be used, but they only combine current standard and summer time, and do not necessarily take into account all historical changes. Moreover, since PT is only available in the United States and Canada, you need to rely on established standards from reputable organizations to ensure the versatility of your software.

IANA Time Zone Database


I must confess that time zone information is rather a database, not a set of rules, because this information should contain all relevant historical changes. There are several standard databases designed for solving problems related to time zones. The most commonly used IANA Time Zone Database , and usually called its tz database (or tzdata). The database contains historical data on changes in standard time and DST across the globe. Moreover, it is organized so that you can check all the historical data and verify the accuracy of time since Unix time ( 1970.01/01 00:00:00). Although you can find information in the database until 1970, their accuracy is not guaranteed.

The naming convention uses the “region / place” rule. As the region is usually used the name of the continent or ocean (Asia, America, Pacific), and as a place - the names of major cities (Seoul, New York). The reason is that cities usually exist longer than countries. For example, the time zone of South Korea is Asia/Seoul, and Japan is Asia/Tokyo. Although both countries use the same offset UTC+09:00, their local time varied in different ways, so they were divided into different time zones.

The IANA database is run by many communities of developers and historians. Freshly-available historical data is immediately entered into it and current policies are updated, so that today the base can be considered the most reliable source. Moreover, it is used under the hood of many Unix-systems, including Linux and MacOS, as well as a number of popular programming languages, including Java and PHP.

Please note that Windows uses a Microsoft database . However, it is inaccurate in terms of historical data and is supported only by Microsoft itself. Therefore, the base is less reliable than the IANA base.

JavaScript and IANA base


The time zone-related functionality is implemented in JavaScript. By default, the language uses the current zone of the region (more precisely, the belt selected when installing the OS), and there is no way to change it. Moreover, even the specifications for the standard database in JavaScript are vague, and you will understand this yourself if you decide to deal with the specification for ES2015. About the local time zone and the availability of DST there are only a couple of vague statements. For example, DST is defined as: ECMAScript 2015 - Daylight Saving Time Adjustment .

This is an implementation-dependent algorithm that uses the best available time zone information to determine the Daylight SavingTA (t) setting for local time, which is calculated in milliseconds. The ECMAScript implementation should help best define the local summer time setting.

Looks like they just say: "Dude, try to make it work." Among other things, you have to solve the problem of compatibility with different browsers. You say, “What a mess!”, And then read the following line:

Note: We recommend that you use information from the IANA time zone database http://www.iana.org/time-zones/ in your implementations .

Yes. ECMA specifications give you the ball such a simple recommendation to use the IANA database, because JavaScript does not have a special standard database. As a result, different browsers use their own time zone operations to calculate time, which are often incompatible with each other. Later in ECMA for the international API, we added the option of using IANA data in ECMA-402 format Intl.DateTimeFormat. But this option is much less reliable than analogs in other programming languages.

Time Zone in Server-Client Environment


Consider a simple scenario: we need to define a time zone. Suppose we create a calendar that will process time information. When a user in the client environment enters the date and time in the registration window, the date is transferred to the server and stored in the database. Then the client receives from the server the date registered in the schedule for display on the screen.

Here you need to decide on something. What if some of the clients accessing the server are in different time zones? The event in the schedule, which is registered in Seoul on March 10, 2017 at 23.30, in New York should be displayed as March 10, 2017 at 9.30. In order for the server to serve clients from different time zones, the schedule stored on it must contain absolute values ​​that do not depend on the belt in any way. Each server has its own way of storing such values, this question is beyond the scope of the article, since everything depends on the particular server or database. In general, the date and time transmitted from the client to the server should be presented either as values ​​based on a single offset (usually UTC), or as values ​​containing information about the time zone of the client environment.

Typically, such data is transmitted in the form of Unix-time in UTC format or according to the ISO-8601 standard with offset information. If in our example we convert Seoul to Unix-time at 21.30 on March 10, 2017, we get an integer value 1489113000. And in the ISO-8601 format, a string value will be obtained 2017–03–10T11:30:00+09:00.

If you are using JavaScript in your browser environment, you must convert the entered value as described above, and then convert it back to match your custom time zone. We need to solve both of these problems. From the point of view of a programming language, the first operation is called “parsing,” and the second is “formatting.” Now let's see how this is done in javascript.

Even when you work with JS in a server environment using Node.js, you may need to parse the data received from the client. But since the time zones of servers and databases are usually synchronized, and formatting is imposed on clients, in a browser environment you need to determine several factors. Further I will explain with reference to the browser environment.

Javascript date object


Tasks involving work with a given or time are solved using an object Date. This is a native object defined in ECMAScript, like Arrayor Function. That is, it is, for the most part, implemented using native code like C ++. The API is well described in the MDN documentation . The object was greatly influenced by the java.util.Date class from Java, so it inherited some undesirable properties, such as the characteristics of changeable data and the month starting at zero ( month).

Under the hood, an object Date in JavaScript works with time using absolute values ​​in Unix-time format. But on the constructors and methods like features parse(), getHour(),setHour()and others affect the client's time zone (more precisely, the belt specified in the OS in which the browser is running). So if you create an object Datedirectly using the data entered by the user, then the local time zone of the client will be reflected in this data.

As I mentioned, JavaScript does not provide any way to arbitrarily change the time zone. Therefore, we will assume that we can directly use the value of the time zone specified in the browser.

Creating a Date object using user-entered data


Let's return to the first example. Suppose a user entered Seoul time at 11:30 am on March 11, 2017. This data is saved as five numbers: 2017, 2, 11, 11, and 30 — year, month, day, hour, and minute, respectively (since the month starts at 0, its the value should be 3–1 = 2). Using the constructor, you can easily create an object Date:

const d1 = newDate(2017, 2, 11, 11, 30);
d1.toString(); // Sat Mar 11 2017 11:30:00 GMT+0900 (KST)

If you look at the value returned d1.toString(), you will see that the absolute value of the created object is 11.00 on March 11, 2017, it is calculated using confusion +09:00(KST).

You can use in the constructor and string data. If you apply them to Date, the object will internally call Date.parse()and calculate the correct value. This feature supports the specifications of RFC2888 and ISO-8601 . But the MDN documentation for Date.parse () says that the value returned by this method depends on the browser, and the format of the string type can affect the exact final value. Therefore, it is better not to use this method. For example, in Safari and Internet Explorer a string value seems to 2015–10–12 12:00:00returnNaN, and in Chrome and Firefox returns the local time zone. In some situations, a UTC-based value is returned.

Creating a Date object using server data


Suppose you want to get data from a server. If they are in the form of numeric Unix time, then Dateyou can simply use a constructor to create an object . I have not mentioned that when the constructor Dategets one value as a single parameter, it calculates the Unix time value in milliseconds (note: JS processes Unix time in milliseconds. This means that the second value needs to be multiplied by 1000). When executing the following code, we will get the same value as in the previous example:

const d1 = newDate(1489199400000);
d1.toString(); // Sat Mar 11 2017 11:30:00 GMT+0900 (KST)

And if instead of Unix-time to use the string type ISO-8601? As I explained above, then the method Date.parse()becomes unreliable and it is better not to use it. However, starting with ECMAScript 5, you can Dateuse strings in the ISO-8601 format in the designer in Internet Explorer 9.0 and higher.

If you are not using the most recent browser, then make sure that there is a letter Zat the end of the values. Without it, your old browser can interpret the value based on local time, not UTC. Here is an example of using Internet Explorer 10:

const d1 = newDate('2017-03-11T11:30:00');
const d2 = newDate('2017-03-11T11:30:00Z');
d1.toString(); // "Sat Mar 11 11:30:00 UTC+0900 2017"
d2.toString(); // "Sat Mar 11 20:30:00 UTC+0900 2017"

According to the specification, in both cases the same value should be obtained. But they are different. In a later browser, the values ​​will be the same. To avoid this problem, always add Zat the end of the line if there is no information about the time zone.

Creating a date to send to the server


Now you can use the previously created Date, freely retrieve or add time based on local time zones. Only at the end of processing do not forget to convert the data to the previous format before returning to the server.

If this is Unix time, you can use the method getTime()(do not forget about milliseconds).

const d1 = newDate(2017, 2, 11, 11, 30);
d1.getTime(); // 1489199400000

What about the string value in ISO-8601 format? As I said, Internet Explorer 9.0 and above support ECMAScript 5, and later versions support ISO-8601. Therefore, using the methods toISOString()or toJSON()you can create a string in ISO-8601 ( toJSON()can be used for recursive calls with JSON.stringify()and others). Both methods give the same result, except for the cases of processing incorrect data:

const d1 = newDate(2017, 2, 11, 11, 30);
d1.toISOString(); // "2017-03-11T02:30:00.000Z"
d1.toJSON();      // "2017-03-11T02:30:00.000Z"const d2 = newDate('Hello');
d2.toISOString(); // Error: Invalid Date
d2.toJSON();      // null

To create a string in UTC format, you can use the toGMTString()or methods toUTCString(). This will result in a value that meets the requirements of RFC-1123 .

The object Dateincludes toString(), toLocaleString()and their extension methods. But there is little benefit from them, since they are mainly used to return rows based on the local time zone, and the return values ​​depend on the browser and OS.

Changing the local time zone


As you can see, JS has some support for time zones. And if you need in the application to change the local belt without taking into account the value specified in the OS? Or if you need to display several time zones simultaneously in one application? As I have said several times, JS does not allow you to manually change the local time zone. The only solution would be to add or remove an offset from the date for which you already know the offset value of the desired belt. But don't be upset. Perhaps there is another way.

Let's return to our example with Seoul. Let this time zone be specified in the browser. The user enters Seoul time at 11.30 on March 11, 2017 and wants to see it as the local New York time. The server sends the date in Unix-time in milliseconds and notifies that the New York offset is-05:00. Now you can convert the data if you know the local time zone offset.

In this scenario, you can use the method getTimeZoneOffset(). This is the only API in JavaScript that allows you to get information about the local time zone. The method returns the current zone offset value in minutes:

const seoul = newDate(1489199400000);
seoul.getTimeZoneOffset(); // -540

The value -540means that the time zone is 540 minutes ahead of the target. Note the minus, although the Seoul offset contains a plus ( +09:00). I do not know why, but it is displayed that way. If we use this method to calculate the offset for New York, we get 60 * 5 = 300. Transform the difference 840in milliseconds and create a new object Date. Now we will use its method getXXto convert the value to the format you need. Create a simple formatting function to compare the results:

functionformatDate(date) {
   return date.getFullYear() + '/' +
      (date.getMonth() + 1) + '/' +
      date.getDate() + ' ' +
      date.getHours() + ':' + 
      date.getMinutes();
   }
const seoul = newDate(1489199400000);
const ny = newDate(1489199400000 - (840 * 60 * 1000));
formatDate(seoul);  // 2017/3/11 11:30
formatDate(ny);     // 2017/3/10 21:30

formatDate()shows the correct date and time according to the time zone difference between Seoul and New York. Looks like we found a simple solution. Is it possible to convert to the local time zone if we know the offset of the desired region? Unfortunately not. I have already said that time zone data is a database that stores all historical changes in offsets. So, to get the correct time zone value, you need to know the offset to the desired date (not the current one).

Local time zone conversion problem


If you work a little more with the example above, you will soon run into a problem. Suppose a user wants to check the local time in New York, and then change the date from 11 to 15. If you use the method setDate()from the object Date, you can change the date without affecting the other values.

ny.setDate(15);
formatDate(ny);   // 2017/3/15 21:30

It looks simple, but there is a hidden trap. What happens if you need to send data back to the server? They have changed, so you can not use methods getTime()or getISOString(). Therefore, it is necessary to do the inverse transformation before sending data to the server.

const time = ny.getTime() + (840 * 60 * 1000);  // 1489631400000

Some may wonder why I added the use of transformed data, because you still have to convert them back before returning. After all, you can process the data without conversion and temporarily create the converted object Dateonly during formatting? Not. If the object is Date based on the Seoul time to change the date from the 11th to the 15th, then 4 days are added ( 24 * 4 * 60 * 60 * 1000). But in the case of the local New York time, the date changed from 10th to 15th, that is, 5 days was added ( 24* 5 * 60 * 60 * 1000). So to get an accurate result, you need to calculate data based on local offset.

The problems do not end there. You will not get the desired value simply by adding or subtracting the offset. Since for New York, summer time begins on March 12, the offset for March 15, 2017 is equal to-04:00and not -05:00. And when you do the inverse transformation, you need to add 780 minutes, which is 60 minutes less than before.

const time = ny.getTime() + (780 * 60 * 1000);  // 1489627800000

On the other hand, if the user's local time zone refers to New York and you need to know the time in Seoul, then the unnecessary use of summer time will lead to another problem.

Simply put, you cannot use the offset alone to perform accurate operations based on the selected time zone. If we put together everything that we have already discussed, it will become clear that there are still difficulties with summer time. To get the exact value, you need a database that contains the entire history of changing offsets, like the same IANA database .

To solve this problem, you will have to store the entire database, and when extracting the date or time from the objectDatefind this date and the corresponding offset, and then convert the value according to the process described above. In theory, all this is realizable. But in fact, you have to spend too much energy, not to mention testing the reliability of the converted data. Although not in a hurry to get upset. So far, we have discussed only some of the problems of JS and how to solve them. Now you can use a good library.

Moment Timezone


Moment is a mature JavaScript library that has almost become the standard. It provides various APIs for dates and their formatting, and many users find the library stable and reliable. There is also the Moment Timezone extension module , which solves the problems discussed above. It contains IANA database data for accurate calculation of offsets and provides a set of APIs that you can use to change and format time zones.

I will not talk in detail about the use of the library or its structure. Just show you how it can be used to easily solve the problems described above. For details, send the documentation .

So, use the Moment Timezone:

const seoul = moment(1489199400000).tz('Asia/Seoul');
const ny = moment(1489199400000).tz('America/New_York');
seoul.format(); // 2017-03-11T11:30:00+09:00
ny.format();    // 2017-03-10T21:30:00-05:00
seoul.date(15).format();  // 2017-03-15T11:30:00+09:00
ny.date(15).format();     // 2017-03-15T21:30:00-04:00

The offset seoulremains the same, and the offset nyhas changed from -05:00to -04:00. And if you use the function format(), you can get a string in ISO-8601 format, in which the offset is neatly applied. It turns out much simpler than the solution given by me above.

Conclusion


We discussed the time zone APIs supported in JavaScript and their associated complexity. If you do not need to manually change your local time zone, you can implement the necessary functions even using basic APIs that involve using Internet Explorer 9 and above. If you need to change manually, then everything becomes very complicated. In a region without daylight saving time and occasional changes in the time zone, you can partially implement the necessary functionality by transforming the data with getTimezoneOffset(). But if you need full time zone support, don't do it from scratch. Better use a library like the Moment Timezone.

I tried to do everything on my own, but I could not, which is not surprising. After numerous attempts I can give advice: use the library. Starting to write an article, I did not know what to say in conclusion, but now I know. I do not recommend using third-party libraries blindly, without knowing the features of JavaScript supported by them and possible pitfalls. As always, choose the right tool for your situation.

useful links



Also popular now: