Time is an illusion. — Albert Einstein

Recently, I encountered some problems in dealing with time internationalization while developing a front-end project that needed to complete the internationalization solution. So I spent a little bit of time researching, and I got this article. However, since there are numerous articles on the web about Date objects in JavaScript, this article is not a guide to using Date objects in JavaScript, but focuses solely on front-end time internationalization.

Start with time zones

When it comes to dealing with time, UTC is an inescapable name. Coordinated Universal Time is the current Universal Time standard. Timekeeping is based on atomic clocks, but it is not equivalent to TAI. TAI does not count leap seconds, but UTC inserts leap seconds from time to time, so the discrepancy between UTC and TAI is growing. UTC is also close to GMT (Greenwich Mean Time), but not exactly equivalent. Most people have probably noticed that GMT has become less common in recent years. This is because GMT timing is based on the Earth’s rotation, which is irregular and slowing down, and has been largely replaced by UTC.

JavaScript’s Date implementation does not deal with leap seconds. In fact, Unix/POSIX timestamps do not take leap seconds into account at all due to the unpredictability of leap second additions. The Unix timestamp repeats for one second when a leap second occurs. This also means that it is possible for one timestamp to correspond to two points in time.

Since UTC is standard, we sometimes use UTC+/ -n to express a time zone. That’s easy to understand, but it’s not accurate. The Asia/Shanghai time zone used in China is mostly represented by UTC+8, but the Europe/London time zone used in the UK is not represented by a UTC+N — Europe/London equals UTC+1 in summer due to daylight saving time. In winter it’s UTC/GMT.

A time zone’s offset from UTC does not have to be a full hour. For example, Asia/Yangon is currently UTC+6:30, while Australia/Eucla currently has a fantastic OFFSET of UTC+8:45.

The existence of DAYLIGHT saving time means that time is not continuous, and the time difference between time zones is not fixed, and we don’t use fixed time difference to deal with time, which is easy to realize. But what is not easy to realize is that time zones also contain information about their history. China does not currently implement daylight saving time, so we can safely use UTC+8 to represent China’s time zone? You may have noticed that I used most of the words in the previous paragraph when describing the Asia/Shanghai time zone. Asia/Shanghai time zone has historically practiced daylight saving time, so Asia/Shanghai can be represented in UTC+9 for part of the time period.

new Date('1988-04-18 00:00:00')
// Mon Apr 18 1988 00:00:00 GMT+0900
Copy the code

Daylight saving time is messy enough, but it’s actually messier than you might think — some Muslim countries switch to DAYLIGHT saving time four times a year (it’s temporarily cancelled when Ramadan begins), and some countries use chaotic 15/30 minutes of daylight saving time instead of the usual one hour.

Don’t always start your day based on 00:00. Some countries use 07:00-1:00 daylight saving time, which means the next minute of 23:59 could be 1:00.

In fact, although there are only 24 hours in a day, there are currently (2021.10) over 300 time zones in use. Each time zone contains its own specific history. While some time zones look consistent today, they all contain different histories. Time zones also make history. For political, economic or other reasons, some time zones adjust their deviations from UTC (Samoa once switched from UTC-10 to UTC+14, causing the country to lose an entire day on December 30, 2011), or switch to/remove DAYLIGHT saving time, or even redivide one time zone into two. Therefore, in order to process each time zone correctly, we need a database to store the time zone change information. Fortunately, we already have someone to do the work for us. Most * NIx systems and numerous open source projects today use the IANA TZ Database, which contains information about time zone changes since Unix timestamp 0. Of course, this database also contains a lot of information about time zone changes prior to Unix timestamp 0, but there is no guarantee that this information is accurate. The IANA Time zone database is regularly updated to reflect historical time zone changes caused by new time zone changes and newly discovered historical facts.

Windows does not use the IANA time zone database. Microsoft maintains a time-zone database for Windows itself, which can sometimes cause times that are legal on one system to be illegal on another.

Since we cannot use a UTC offset to represent a time zone, we have to define a standard name for each time zone. Generally, we use < continent >/< city > to name a time zone. The cities are generally the most populated in the time zone. Thus, we can express the prevailing time zone in China as Asia/Shanghai. Some time zones also have their own nicknames, such as Pacific Standard Time (PST) and Coordinated Universal Time (UTC).

Time zone names are used for cities rather than countries because countries usually change much more quickly than cities.

A city is not the smallest unit of time zone. There are many cities in multiple time zones at the same time, and even Australia has an airport with runways in different time zones.

Dealing with time zones is difficult

One day a few months ago, Custard posted this message on his Telegram channel:

You’re right, the problem is caused by the difference between the time zone and the UTC offset. The Asia/Shanghai time zone used daylight saving time circa 1940 and 1986, and the switch caused an hour to come and go. 2021-03-28 01:30:00 is not valid in Europe/London time zone. When daylight saving time is enabled in the UK on March 28, 2021-03-28 01:30:00 is invalid in Europe/London time zone. On the same day when daylight saving time is cancelled, there will be another hour of repetition. For example, if daylight saving time is cancelled in 2021.10.31 in the UK, 2:00 will jump back to 1:00 again, causing 2021-10-31 01:30:00 to correspond to two time points in Europe/London time zone. In the case of the ice cream, 1988-04-10 00:46:50 falls within an hour that was lost due to daylight saving time, so the system considers the time string invalid and refuses to parse it.

You may notice that in history on April 10, 1980 the Asia/Shanghai time zone was actually removed from the hour of 1:00-2:00 instead of 0:00-1:00. The deeper reason for the above problem is that in IANA TZDB 2018A and earlier versions, IANA set wrong daylight saving time rules due to lack of historical data. The rules set the daylight saving time boundary at 0:00-1:00, which caused the above problem. IANA updated its database as the community found more accurate historical facts. The above problem was resolved by updating the system’s time zone database.

Let’s think about another case. A Brazilian user of your app saved a future time in 2018 at 2022-01-1512:00 (which would have been DAYLIGHT saving time by then). Unfortunately, your app saved the time as a formatted time string at that time. 2022-01-15 12:00: The Unix timestamp has changed and is no longer accurate. To handle this string correctly, you need to do different things with reference to the time the string was generated (or the UTC offset calculated at the time of generation). Therefore, applications should avoid using strings to transfer and store time in the first place and use Unix timestamps instead. If you have to use strings to store time, try:

  • Using UTC to describe time, you never know what will happen to your local time zone in the future
  • If you want to describe the time in local time, be sure to include the current UTC offset

Time zone history presents problems that are often unexpected and far more than imagined. In fact, time zone history data is very detailed, numerous and inconsistent across devices, and there is no simple and uniform way to deal with it. When you need to handle time zones rigorously, you might want to have a unified time zone database embedded in your application, but such a solution presents problems on the front end:

  • Too large. Moment.js had designed a compact TZDB representation, but the entire file still reached 180+KB despite being as compressed as possible. This is not acceptable in performance-first Web applications
  • Continuous updates are required. Time zone data changes all the time, and the time zone data in the application needs to be updated as soon as possible, which incurs additional maintenance costs

ES6 brings us the Intl namespace. Here, the JavaScript runtime provides a lot of time-dependent internationalization capabilities. Thus, it is possible to handle time zones accurately without using additional data, but it is not perfect:

  • The ends are not uniform. The time zone data provided by the browser is subject to the browser version and system version. The latest time zone update may not be quickly reflected on all devices
  • Implementation complexity.JavaScriptDateThe poor design of the object makes it difficult to implement sound time zone handling, andIntlObject instantiation performance in namespaces is expensive and requires additional optimization

There are a number of useful internationalization-related methods in the Intl namespace that deserve another article.

In real development, this is a trade-off. The current mainstream JavaScript time-handling libraries have moved to browser-built methods and Polyfill to ensure cross-end consistency when needed. In this article, we will try to implement basic time internationalization processing without using third-party libraries. In addition, there are some details to be noted, such as the need to use Unix timestamps to properly exchange times on both sides.

Time zone conversion

Date in JavaScript does not exclude time zone information — in fact, the Date object must represent the current time zone. By trying:

new Date('1970-01-01T00:00:00Z')
// Thu Jan 01 1970 08:00:00 GMT+0800
Copy the code

You know that the JavaScript runtime actually knows the current time zone and will convert the time from another time zone to the current time zone when needed. So how do you convert local time to time in another time zone? From Date’s point of view, this doesn’t work because we can’t set the time zone of a Date object. We can “cheat” by adding/subtracting the corresponding time difference to the Date object’s time. The Date object will still think it is in its local time zone, but this will make it appear correct. But we run into the problem mentioned above: the time difference between time zones is not fixed, and it is difficult to calculate correctly without additional data.

Well, ES6 expanded the Date based on the Intl namespace. The prototype. ToLocaleString () method, which make it acceptable time zone parameter and format the time in the specified time zone. If you search a search engine for how to convert time zones using JavaScript, chances are you’ll find something like this on StackOverflow:

const convertTimeZone = (date, timeZone) = > {
    return new Date(date.toLocaleString('en-US', { timeZone }))
}

const now = new Date(a)// Wed Oct 13 2021 01:00:00 GMT+0800
convertTimeZone(now, 'Europe/London') // Tue Oct 12 2021 18:00:00 GMT+0800
Copy the code

Understandably, our locale with en-us requires the JavaScript runtime to format the time in the time zone we specify, and then reparse the time string into a time object. TimeZone here is the IANA TZDB timeZone name for things like Asia/Shanghai. This string does need to be supplied, but this is the only data we need to prepare ourselves! As long as you provide the time zone name, the browser automatically calculates the correct time without us having to do it ourselves.

For time zone names, you might consider using @vvo/ TZDB. This is a JSON export that claims to be an automatically updated IANA TZDB and has been used by several large projects. You can export all time zone names from this package.

This looks like a good idea, right? But really, it has two problems:

  • Specified locale and time zonetoLocaleString()Virtually every call creates a new one in the JavaScript runtimeIntl.DateTimeFormatObject (more on that later), which carries a high performance overhead (in Node 14, a single instantiation increases memory usage in V8 by approximately 46.3Kb. But this is in line with expectations. See moreV8 Issue. Therefore, in the case of intensive calls, you need to consider calculating and caching the time difference, and updating it after a certain amount of time or when needed
  • usetoLocaleString()And useen-USThe default time format for locale formatting is similar to10/13/2021, 1:00:00 AM. This can be correctly parsed by most browsers,But this is non-canonical, and different browsers may produce different results. You can also customize the format (see below)Intl.DateTimeFormat), but still cannot construct a canonical string

Therefore, a better solution would be to build a reusable formatter to avoid the overhead of repeatedly creating intl.datetimeFormat, and to manually construct a time string that conforms to the specification and reparse it into a Date object.

const timeZoneConverter = (timeZone) = > {
    // Create a DateTimeFormat object for reuse with the same target time zone
    Since the time zone attribute must be specified when the DateTimeFormat object is created, we can only reuse the formatter for the same time zone
    const formatter = new Intl.DateTimeFormat('zh-CN', {
        year: 'numeric'.month: '2-digit'.day: '2-digit'.hour: '2-digit'.minute: '2-digit'.second: '2-digit'.hour12: false,
        timeZone
    })
    return {
        // Provide the conver method to convert the supplied Date object to the specified time zone
        convert (date) {
            // the zh-cn locale returns a string similar to 1970/01/01 00:00:00
            The ISO 8601 standard format time string similar to 1970-01-01t00:00:00 is constructed with the substitution character and is correctly parsed
            return new Date(formatter.format(date).replace(/\//g.The '-').replace(' '.'T').trim())
        }
    }
}

const toLondonTime = timeZoneConverter('Europe/London') // This object is reusable for the same time zone

const now = new Date(a)// Wed Oct 13 2021 01:00:00 GMT+0800
toLondonTime.convert(now) // Tue Oct 12 2021 18:00:00 GMT+0800
Copy the code

The current zh-CN locale generates a format string similar to 1970/01/01 00:00:00. This format is currently consistent across ends, but since the specification does not specify a time format, this format may change in the future. It would be better to use the formatToParts() method (more on that later) to get the parts of the time string and manually concatenate the string in standard format, but in this case replace would have better performance.

Now, try switching to the same time zone 1000 times, reducing the time from toLocaleString() 1.5 seconds to 0.04 seconds. Although the code was a bit longer, the rewrite gave us a performance increase of more than 20 times in the best case.

It’s important to note that while this may seem like the final solution, it’s still not perfect. There are two main problems:

  • When intensive conversions to different zones are required, performance remains poor and difficult to optimize further because formatters cannot be reused
  • Due to theIntl.DateTimeFormatFormatting milliseconds is not supported, and milliseconds will be lost during the formatting of the string, resulting in a final result that may differ up to 999ms from the expected result, requiring additional processing. For example, to calculate the time difference, we might write:
const calcTimeDiff = (date, converter) = > {
    const secDate = date - date.getMilliseconds() // Remove the milliseconds to avoid accuracy differences before and after conversion
    return converter.convert(new Date(secDate), tzName) - secDate
}

calcTimeDiff(new Date(), timeZoneConverter('Europe/London')) / / - 25200000
Copy the code

Anyway, after a lot of fiddling we managed to get the time zone switch right. Are you ready to format the time string next? But first, let’s talk about language, words and regions.

The language area is not clear

How to express Chinese in computer?

“It’s not easy,” you might say, “use zh.”

What about simplified Chinese?

“Useful – CN. You might say the answer.

So how can simplified Chinese used in Singapore be distinguished from simplified Chinese used in mainland China?

HMM… Good question.

To correctly distinguish between simplified Chinese characters, we need to go back to the definition. In fact, “internationalization” is not just a language translation, internationalization includes a whole set of localization solutions for each region. To accurately represent an internationalization scheme, we actually need to identify at least three attributes: Language, Script, and Locale.

  • Language usually refers to the language of sound. Different languages have their own pronunciation rules, which make it difficult to communicate with each other. For example, Both Chinese and English are languages
  • Characters correspond to the writing style of a language, and the same language may have multiple writing schemes. For example, there are mainly simplified and traditional Chinese writing schemes
  • Region refers to the region for which internationalization is oriented. The same language and characters may have different usage habits in different regions. For example, Both Singapore and Mainland China use simplified Chinese, but there are some differences in the wording habits of the two places

Only with these three attributes can we properly define an internationalization scheme (or locale). Of course, there are many other attributes that more accurately represent a locale, but language, text, and locale are usually sufficient.

Therefore, based on BCP 47, we can know:

Cmn-hans-cn = Mandarin Chinese - Simplified - Mainland China CMN-Hans-SG = Mandarin Chinese - Simplified - Singapore CMN-hant-TW = Mandarin Chinese - Traditional - Taiwan Yue-hant-hk = Cantonese - Traditional - Hong KongCopy the code

Wait, what is all this? And what is BCP 47? BCP is a “best current practice” document published by the IETF, and BCP 47 is a collection of ISO’s and memos related to internationalization, as well as the de facto standard for expressing locale Settings currently used by HTML and ECMAScript. The locale tag defined by BCP 47 is actually quite complex, but the format in the example above is sufficient for most simple use cases. In short, to express a locale, we use the language [-text][-locale] format, and both text and locale are optional. The specific code for each part is also defined in BCP 47. Among them:

  • The language uses two-letter codes defined in ISO 639-1 (for Example, In Chinese)zhEnglish foren) orISO 639-2/3The three-letter code defined (for example, Putonghua iscmnEnglish foreng), usually lowercase
  • Text using theISO 15924A defined four-letter code, usually capitalized. For example, simplified Chinese isHansThe traditional Chinese isHant
  • The region is usually usedISO 3166-1A two-letter code defined, usually in uppercase, as mainland China isCNAnd in the UK forGB

The relationship between ISO 639-1/2/3 is actually: ISO 639-1 is the earliest specification that uses two letters to represent languages, but there are too many languages to be represented by two codes alone. As a result, ISO 639-2 and 3 were subsequently revised to include three letters for more languages. Usually the 639-1 code and isO-2/3 code have a one-to-many relationship. For example, Chinese zh is actually the macrolanguage of Chinese mandarin CMN, and there are also dozens of languages using zh as the macrolanguage, such as wuu (Chinese wu language), hak (Chinese hakka language), yue (Chinese cantonese) and so on. The specification should now use ISO 639-2/3 code instead of ISO 639-1, but it is still quite common and perfectly acceptable to specify languages using ISO 639-1 due to historical resistance and the need for such detailed classification in real requirements. In addition, specifically, we define the unspecified language as UND in ISO 639-3.

Therefore, the correct answer in BCP 47 to the two questions at the beginning of this section is:

En = Chinese CMN = Chinese Mandarin zh-Hans = Chinese - simplified Chinese cmN-Hans = Chinese Mandarin - simplified ChineseCopy the code

Zh-cn actually refers to the Chinese language used in mainland China, as well as the traditional Chinese language used in mainland China. However, since most of the time only one language will be used in a region, in many cases we can ignore the word and use zh-CN (or cn-CN) to refer to simplified Mandarin Chinese in mainland China — after all, there is very little use of traditional and non-Mandarin Chinese in mainland China for most businesses.

In fact, locale names like zh-hans and zh-hant are already marked redundant, so use only locale names like zh-cn or cmN-hans-cn when possible. A list of all locale names can be found at IANA.

Now we can define a locale exactly. But we still have a few small needs. For example, we want to use lunar calendar to represent dates in cmN-Hans-CN locale, but obviously the representation we defined above does not meet this requirement. Fortunately, Unicode provides a U extension for BCP 47. A more detailed variant can be expressed by adding -u-[option] after the locale name. So we have:

Cmn-hans-cn-u-ca-chinese = Mandarin Chinese - Simplified Chinese Mainland - U - Calendar - Chinese Lunar Calendar JPN-jpan-jp-U-ca-Japanese = Japanese - Kanji/Hiragana/Katakana - Japanese - U - Calendar - Japanese Calendar Cmn-hans-cn-u-nu-hansfin = Mandarin Chinese - Simplified Chinese Mainland - U - Numbers - Simplified Uppercase numbersCopy the code

Specific options for extensions can be found on the Unicode web site. Multiple U extensions can also be connected — so we can even write the crazy locale-name of cmN-Hans-CN-U-ca-Chinese-nu-Hansfin. Of course, you can now see what this locale means.

Different regions may have different calendar usage habits. For example, China needs to use lunar calendar and Thailand needs to use Buddhist calendar. We can specify different calendars through U extension. However, most of the time we will use the standard ISO 8601 calendar (Gregory), which is the only calendar supported by JavaScript Date objects.

You can use the BCP47 Language Subtag Lookup tool to quickly check whether your BCP47 region tags are valid.

Finally we can correctly express a locale that perfectly fits our needs. Next, let’s start formatting the time.

Formatting time

I know this one!

const formatDate(date) => {
    return `${date.getFullYear()}-The ${`${date.getMonth() + 1}`.padStart(2.'0')}-The ${`${date.getDate()}`.padStart(2.'0')} The ${`${date.getHours()}`.padStart(2.'0')}:The ${`${date.getMinutes()}`.padStart(2.'0')}:The ${`${date.getSeconds()}`.padStart(2.'0')}`
}

formatDate(new Date()) / / the 2021-10-13 01:00:00
Copy the code

That’s it… ? Regardless of how hard the formatting code is to read, although the date format above is common internationally, not all regions are used to this way of representing dates. For example, english-speaking countries are used to adding weeks to dates in many cases, while Arabic-speaking countries are used to using Arabic numerals in some cases (and arabic-Indian numerals are very common); For example, in The United States, the date notation is month-day-year, while in the United Kingdom, the date notation is day-month-year… The differences in time representation conventions between regions are so great that it is difficult to format a date correctly and internationally in a simple way.

Fortunately, ES6 paved the way for us. Remember intl.dateTimeformat? We use it to instantiate a date formatter and internationalize dates.

Let’s get straight to the example:

const options = {
    year: 'numeric'.month: 'short'.day: 'numeric'.weekday: 'long'
}
const now = new Date(a)const enUSFormatter = new Intl.DateTimeFormat('en-US', options)

const zhCNFormatter = new Intl.DateTimeFormat('zh-CN', options)
const zhCNAltFormatter = new Intl.DateTimeFormat('zh-CN-u-ca-chinese', options)
const zhCNAlt2Formatter = new Intl.DateTimeFormat('zh-CN-u-ca-roc-nu-hansfin', options)

const jaFormatter = new Intl.DateTimeFormat('ja', options)
const jaAltFormatter = new Intl.DateTimeFormat('ja-JP-u-ca-japanese', options)

const arEGFormatter = new Intl.DateTimeFormat('ar-EG', options)

enUSFormatter.format(now) // Wednesday, Oct 13, 2021

zhCNFormatter.format(now) // Wednesday 13 October 2021
zhCNAltFormatter.format(now) // Wednesday, September 8, 2021
zhCNAlt2Formatter.format(now) Wednesday, October 13, republic of China

jaFormatter.format(now) // October 13, 2021
jaAltFormatter.format(now) // October 13, reihwa 3

arEGFormatter.format(now) // free button أ ع free ء، ١٣ أكتو ع ع ع free ء، ١٣ أكتو ع ٢٠ doctor
Copy the code

We use ISO 639-1 codes for languages here because of the fact that ISO 639-1 codes are more common and generic. In most JavaScript runtimes that support intl.dateTimeFormat we can also use ISO 639-2/3 code to represent the language (but actually fallback to the corresponding ISO 639-1 code).

You can also replace the use of the U extension in the locale name by setting the Calendar and numberingSystem properties in options. This is also recommended.

This is straightforward enough that we can initialize a formatter by specifying locale and formatting options, and later format a Date object using the format method of the formatter object. The formatting options are actually very flexible, not just dates, but times can be flexibly formatted, and there are a lot of combinations to choose from. We won’t explain each option in detail here, but you can visit the MDN documentation to learn more.

As mentioned earlier, intl.dateTimeFormat cannot format milliseconds.

It is important to note, however, that JavaScript runtime does not necessarily support all locales, nor does it support all formatting options. In unsupported cases, intl.dateTimeFormat by default quiesce fallback to the best matching support, so you may need to do extra checking when dealing with unusual locales or options. You can use the Intl. DateTimeFormat. SupportedLocalesOf () static method to judge whether the current runtime support for the specified locale, You can also call the resolvedOptions() method on the object after instantiating the formatter to check that the run-time parsing results are as expected.

new Intl.DateTimeFormat('yue-Hant-CN').resolvedOptions()
// {locale: 'zh-cn ', calendar:' Gregory '... }
// Fallback to zh-CN is inconsistent with yue-CN's expectation
Copy the code

In addition, as you can see, the JavaScript runtime for text used in date formatting in various languages has been built in for us. Therefore, we can even take advantage of these internationalization features to reduce the number of strings that need to be translated for our app — the fewer translations we pack into the app, the smaller the app will be — for example, to get names for seven days of the week:

const getWeekdayNames = (locale) = > {
     // Based on a fixed date, 1970.1.1 is selected here
     // Cannot use 0 because the Unix timestamp 0 has different dates in different time zones
    const base = new Date(1970.0.1).getTime()
    const formatter = new Intl.DateTimeFormat(locale, { weekday: 'short' })
    return Array.from({ length: 7 }, (_, day) = > (
        formatter.format(new Date(base + 3600000 * 24 * (-4 + day))) // 1970.1.1 is Thursday
    ))
}

getWeekdayNames('en-US') // ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
getWeekdayNames('zh-CN') // [' Sunday ', 'Monday ',' Tuesday ', 'Wednesday ',' Thursday ', 'Friday ',' Saturday ']
getWeekdayNames('ja') // [' sun ', 'moon ',' fire ', 'water ',' wood ', 'earth ']
getWeekdayNames('ar-EG') / / / 'an ل أ ح د', 'an ل an ث ن ي ن', 'an ل ث ل an ث an ء', 'an ل أ ر ب ع an ء', 'an ل خ م ي س', 'an ل ج م ع ة', 'an ل س ب ت']
Copy the code

Of course, if you still don’t like the format provided by the runtime, we have the formatToParts() method mentioned above. Here’s a simple example:

new Intl.DateTimeFormat('zh-CN', {
    year: 'numeric'.month: 'short'.day: 'numeric'.weekday: 'long'.hour: 'numeric'.minute: 'numeric'.second: 'numeric',
}).formatToParts(new Date())
/ / /
// { type: 'year', value: '2021' },
// {type: 'literal', value: 'year'},
// { type: 'month', value: '10' },
//     { type: 'literal', value: '月' },
// { type: 'day', value: '13' },
// {type: 'literal', value: 'date'},
//     { type: 'weekday', value: '星期三' },
// { type: 'literal', value: ' ' },
// {type: 'dayPeriod', value: 'morning'},
// { type: 'hour', value: '1' },
// { type: 'literal', value: ':' },
// { type: 'minute', value: '00' },
// { type: 'literal', value: ':' },
// { type: 'second', value: '00' }
// ]
Copy the code

You can then parse the array yourself to construct the time format you want. Finally, we can use intl.relativeTimeFormat to format relative dates. Of course we won’t go into the details of the API here, but you can refer to the MDN documentation. Let’s jump to a simple example:

const getRelativeTime = (num, unit, locale) = > {
    return new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }).format(num, unit)
}

getRelativeTime(-3.'day'.'en-US') // 3 days ago
getRelativeTime(-1.'day'.'zh-CN') / / yesterday.
getRelativeTime(0.'second'.'zh-CN') / / now,
getRelativeTime(3.'hour'.'ja') // 3 After time
Copy the code

Intl.RelativeTimeFormat is a relatively late entry into the standard, so browsers have poor support and may need to Polyfill. However, the API is now supported in the latest versions of major browsers (2021.10).

In the future

I hope the time zone switching part of this article will be outdated soon – this is not nonsense, as the Temporal proposal of TC39 is now in Stage 3. The Temporal proposal defines a new, time-zone friendly Temporal namespace that is expected to be standards-based and eventually used in production environments in the near future. Temporal defines the complete processing of time zone, time period and calendar rules, and has a simple API. At that point, JavaScript time zone handling won’t be so painful anymore. Since the Temporal proposal has not yet entered the standard and the API is not stable, we cannot use it in the production environment, but we can look at a simple example to feel the power of this API.

const zonedDateTime = Temporal.ZonedDateTime.from({
  timeZone: 'America/Los_Angeles'.year: 1995.month: 12.day: 7.hour: 3.minute: 24.second: 30.millisecond: 0.microsecond: 3.nanosecond: 500.calendar: 'iso8601'
}) / / the 1995-12-07 T03:13. 0000035-08:00 America/Los_Angeles
Copy the code

If you want to start using Temporal right away, Polyfill is available now.

The time zone problem will not go away, however, nor will it be easy for regional customs to merge. Time internationalization is extremely complex, and time internationalization in the front end still deserves our careful attention.