Software localization

A Guide to Date and Time Localization

We shed light on the intricacies of global dates and times, covering formatting, time zones, regional calendars, and more.
React l10n and i18n blog post featured image | Phrase

We often think of internationalization (i18n) and localization (l10n) as translating text, ignoring all-too-important i18n aspects like date and time localization. Yet dates and times are represented in very different ways across geographies, and localizing them correctly is crucial to making users feel at home in our apps. This guide aims to shed some light on the intricacies of global dates and times. We’ll cover formatting, working with time zones, understanding regional calendars, and taking a look at date and time picker UI.

🗒️ While the examples in this guide target the browser and are written in JavaScript, the guide is intentionally tech-agnostic — there should be an equivalent to each concept in your platform and programming language of choice.

🗒️ Internationalization (i18n) and localization (l10n) allow us to make our apps available in different languages and to different regions, often for more profit. If you’re new to i18n and l10n, check out our guide to internationalization.

Formatting dates and times

How do we present dates and times to someone depending on where they hail from the world? We have to answer the second question first and determine the user’s locale. This will often be their system locale: If a user sets her operating system language to French and region to Canada, the default locale of an app presented to this user is likely fr-CA (French Canada).

🗒️ A locale defines a language, a region, and sometimes more. Locales typically use IETF BCP 47 language tags, like en for English, fr for French, and es for Spanish. Adding a region with the ISO Alpha-2 code (e.g., BH for Bahrain, CN for China, US for the United States) is recommended for accurate date and number localization. So a complete locale might look like en-US for American English or zh-CN for Chinese as used in China.

🔗 Explore more language tags on Wikipedia and find country codes through the ISO’s search tool.

🗒️ Date and time formats are region-specific, so it’s important to use qualified locales when formatting dates e.g. use en-US, not en.

With our essential background in place, the straightforward approach to formatting localized dates and times involves creating a date object and then utilizing a native date formatting library. Let’s take a look at an example in the browser using the native JavaScript Intl.DateTimeFormat formatter.

const date = new Date("2024-01-27T14:00:00Z");
const formatter = new Intl.DateTimeFormat();

formatter.format(date);
// => 1/27/2024Code language: JavaScript (javascript)

Our date represents 2024-01-27 2:00 PM in UTC (more on time zones a bit later). We create an Intl.DateTimeFormat without specifying any options, which means we want default date formatting for the default locale.

The format() call spits out “1/27/2024” when en-US (English America) is determined to be the default locale, respecting the American default date format of Month/Day/Year.

On my machine, however, the same code outputs “2024-01-27”. This is because my operating system is set to en-CA (English Canada), and Year-Month-Day is the default Canadian date format.

🗒️ Intl.DateTimeFormat is available natively in all modern browsers and Node.js.

Default formats

When we don’t provide specific formatting options to a date formatting library, we often get the default format for a given locale. Here are some default date formats for various locales:

const date = new Date("2024-01-27T14:00:00Z");

// We use `date.toLocaleDateString("en-US")` for
// the examples below. This is a shortcut for
// `new Intl.DateTimeFormat("en-US").format(date)`
// See 🗒️ below for more info.

// English-America
date.toLocaleDateString("en-US");
// => "1/27/2024"

// Arabic-Egypt
date.toLocaleDateString("ar-EG");
// => "٢٧‏/١‏/٢٠٢٤"

// Hindi-India
date.toLocaleDateString("hi-IN");
// => "27/1/2024"

// Russian-Russia
date.toLocaleDateString("ru-RU");
// => "27.01.2024"

// Chinese-China
date.toLocaleDateString("zh-CN");
// => "2024/1/27"

// Japanese-Japan
date.toLocaleDateString("jp-JP");
// => "2024-01-27"Code language: JavaScript (javascript)

🗒️ Date.toLocaleDateString() uses Intl.DateTimeFormat under the hood when it’s available. Read more on the Date MDN entry.

“What about times?” you might be asking. Of course, we can often use native libraries to format localized times as well. Here are some default time formats:

// Date object with time of 2 PM UTC
const date = new Date("2024-01-27T14:00:00Z");

// The below calls to
// `date.ToLocaleTimeString(...)` are equivalent to
// `new Intl.DateTimeFormat("en-US", {
//    timeZone: "UTC",
//    timeStyle: "medium",
//  }).format(date)`
// See 🗒️ below for more info.

// English-America
date.toLocaleTimeString("en-US", { timeZone: "UTC" });
// => "2:00:00 PM"

// Arabic-Egypt
date.toLocaleTimeString("ar-EG", { timeZone: "UTC" });
// => "٢:٠٠:٠٠ م"

// Hindi-India
date.toLocaleTimeString("hi-IN", { timeZone: "UTC" });
// => "2:00:00 pm"

// Russian-Russia
date.toLocaleTimeString("ru-RU", { timeZone: "UTC" });
// => "14:00:00"

// Chinese-China
date.toLocaleTimeString("zh-CN", { timeZone: "UTC" });
// => "14:00:00"

// Japanese-Japan
date.toLocaleTimeString("jp-JP", { timeZone: "UTC" });
// => "2:00:00 p.m."Code language: JavaScript (javascript)

🗒️ Just like Date.toLocaleDateString(), Date.toLocaleTimeString() uses Intl.DateTimeFormat under the hood when it’s available. Read more on the Date MDN entry.

Note that in the above examples, we set the time zone to UTC using the timeZone option. This is to maintain a consistent output for demonstration purposes. If we omit the option, the system time zone will be used. I’m currently in the UTC +1 time zone, so the en-US example would output the following on my machine: “3:00:00 PM”.

🗒️ Again, we’ll tackle time zones in a bit more detail a bit later.

Preset formats

Beyond default formats, we often want to control the length of the date and time output. We can achieve this with preset formats that many native formatting libraries offer. In the JavaScript Intl.DateTimeFormat options, these are dateStyle and timeStyle. If you’re working on a different platform, there are likely equivalents in that platform’s formatting libraries.

  • dateStyle — can be "full", "long", "medium", or "short”.
  • timeStyle — can be "full", "long", "medium", or "short”.

Let’s see what these look like in action:

const date = new Date("2024-01-27T14:00:00Z");

const shortOptions = {
  timeZone: "UTC",
  dateStyle: "short",
  timeStyle: "short",
};

// English-America
new Intl.DateTimeFormat("en-US", shortOptions)
  .format(date);
// => "1/27/24, 2:00 PM"

// Arabic-Egypt
new Intl.DateTimeFormat("ar-EG", shortOptions)
  .format(date);
// => "٢٧‏/١‏/٢٠٢٤، ٢:٠٠ م"

// Chinese-China
new Intl.DateTimeFormat("zh-CN", shortOptions)
  .format(date);
// => "2024/1/27 14:00"

const fullOptions = {
  timeZone: "UTC",
  dateStyle: "full",
  timeStyle: "full",
};

// English-America
new Intl.DateTimeFormat("en-US", fullOptions)
  .format(date);
// => "Saturday, January 27, 2024 at 2:00:00 PM Coordinated Universal Time"

// Arabic-Egypt
new Intl.DateTimeFormat("ar-EG", fullOptions)
  .format(date);
// => "السبت، ٢٧ يناير ٢٠٢٤ في ٢:٠٠:٠٠ م التوقيت العالمي المنسق"

// Chinese-China
new Intl.DateTimeFormat("zh-CN", fullOptions).format(date);
// => "2024年1月27日星期六 协调世界时 14:00:00"Code language: JavaScript (javascript)

Preset options often conform to the conventions of the given locale. We can just set the length of the format we want and rely on the formatting library to handle the per-locale details.

Custom formats

Complete control over our date and time formatting is achieved via granular format specifiers. For example, we might want to show only the year of a given date or force 24-hour time representation. Any date formatting library worth its salt will allow us this level of customization. Here are examples using JavaScript’s Intl.DateTimeFormat:

const date = new Date("2024-01-27T14:00:00Z");

const options = {
  timeZone: "UTC",
  timeZoneName: "short",
  year: "2-digit",
  month: "narrow",
  weekday: "short",
  day: "numeric",
  hourCycle: "h24",
  hour: "numeric",
  minute: "numeric",
};

// English-America
date.toLocaleDateString("en-US", options);
// => "Sat, J 27, 24, 14:00 UTC"

// Arabic-Egypt
date.toLocaleDateString("ar-EG", options);
// => "السبت، ٢٧ ي ٢٤، ١٤:٠٠ UTC"

// Hindi-India
date.toLocaleDateString("hi-IN", options);
// => "शनि, 27 ज 24, 14:00 UTC"

// Russian-Russia
date.toLocaleDateString("ru-RU", options);
// => "сб, 27 Я 24 г., 14:00 UTC"

// Chinese-China
date.toLocaleDateString("zh-CN", options);
// => "24年1月27日周六 UTC 14:00"

// Japanese-Japan
date.toLocaleDateString("jp-JP", options);
// => "Sat, J 27, 24, 14:00 UTC"Code language: JavaScript (javascript)

🔗 Check out all the formatting options provided by Intl.DateTimeFormat on the MDN documentation.

🗒️ You may have noticed that localizing dates and times often relies on local number systems. The previous Arabic example uses both Eastern Arabic numerals and Arabic script, “السبت، ٢٧ ي ٢٤، ١٤:٠٠ UTC”. Modern systems and their formatting libraries often take care of all of this for us, but it’s good to bear in mind.

🔗 Our Concise Guide to Number Localization covers numeral systems, currency, and more.

🗒️ To represent written days of the week, months, etc. we of course have to use the language and script of the target locale. Again, something to be mindful of.

Time zones

We usually stick to UTC or the server’s time zone for keeping track of times on the back end. We can then convert the date during formatting to match the user’s local time zone on the front end. The key here is knowing the source and destination time zones so we can nail the conversion.

🗒️ Heads up if you’re working with full-stack frameworks like Next.js or SvelteKit — mismatched time zones between server-side rendering and client-side can lead to those pesky hydration errors. One fix is to keep your time zone consistent on both the server and client side by setting it once in a shared config.

When working with time zones, we often use a set of standards, primarily UTC, IANA, and ISO 8601. Let’s go over these briefly.

UTC

UTC (Coordinated Universal Time) is the modern standard for global civil time, refining GMT (Greenwich Mean Time) with atomic clock precision alongside Earth’s rotation. Time zones are represented as offsets from UTC. For example, UTC-05:00 is equivalent to Eastern Standard Time (EST).

🔗 Dive deeper into UTC’s specifics on its Wikipedia page.

IANA

The Internet Assigned Numbers Authority (IANA) handles crucial Internet roles like IP addressing, DNS management, and protocol IDs to keep the net running smoothly. Its Time Zone Database tracks global time zones, including daylight saving changes and UTC offsets, updated for political shifts. Time zones use an Area/Location format, like America/New_York, and have abbreviations such as EST (Eastern Standard Time).

🔗 Dive into the details on the tz database’s Wikipedia page.

🔗 The Wikipedia List of tz database time zones is an excellent lookup.

ISO 8601

We’ve been using the ISO 8601 format for dates, like 2024-02-23T15:45:00Z, which includes the date followed by T and the time in 24-hour format. Z indicates UTC, but you can specify other time zones with an offset, like +03:00 e.g. 2024-02-23T15:45:00+03:00.

🗒️ ISO 8601 is a common standard, but your source date can be formatted in a variety of ways. Make sure you know the format when parsing.

🔗 Learn more on Wikipedia’s ISO 8601 page.

Code examples

Let’s see how these standards work together when localizing dates and times. Here are examples in JavaScript using Intl.DateTimeFormat:

// Note how the `Date` object knows how to parse
// the ISO 8601 format. The following datetime
// includes 3:45 PM in the UTC +01:00 time
// zone.
const date = new Date("2024-02-27T15:45:00+01:00");

date.toLocaleString("en-US", {
  // Convert to target IANA `timeZone` when
  // formatting.
  timeZone: "America/New_York",
  dateStyle: "medium",
  timeStyle: "full",
});
// => "Feb 27, 2024, 9:45:00 AM Eastern Standard Time"

date.toLocaleString("en-US", {
  // Use the abbreviated time zone; this 
  // affects conversion as well as formatting. 
  timeZone: "EST",
  dateStyle: "medium",
  timeStyle: "full",
});
// => "Feb 27, 2024, 9:45:00 AM GMT-05:00"

// Format in Hindi-India and convert to 
// the Indian time zone.
date.toLocaleString("hi-IN", {
  timeZone: "Asia/Kolkata",
  dateStyle: "medium",
  timeStyle: "full",
});
// => "27 फ़र॰ 2024, 8:15:00 pm भारतीय मानक समय"

// Chinese-China in the Shanghai time zone.
date.toLocaleString("zh-CH", {
  timeZone: "Asia/Shanghai",
  dateStyle: "medium",
  timeStyle: "full",
})
// => "2024年2月27日 中国标准时间 22:45:00"Code language: JavaScript (javascript)

Calendars

While the Gregorian calendar — Jan, Feb, etc. —  is ubiquitous, there are many regional and cultural calendars used all over the world. This is important when localizing, since some calendars, like the Islamic, Persian, and Buddhist calendars are used on a day-to-day basis. Indeed, certain regions use a non-Gregorian calendar as the default.

Some of these calendars serve cultural purposes. Some are solar, others are lunar, and each has its starting year.

A listing of months in the Gregorian calendar. Source: Wikipedia.
A listing of months in the Gregorian calendar. Source: Wikipedia.
A listing of months in the Buddhist calendar. Source: Wikipedia.
A listing of months in the Buddhist calendar. Source: Wikipedia.

Here are some examples of regions that use non-Gregorian calendars as their default:

const date = new Date("2024-01-27T14:00:00Z");

// Arabic Saudi-Arabia;
// converts to the Islamic Hijri calendar.
date.toLocaleDateString("ar-SA");
// => "١٥‏/٧‏/١٤٤٥ هـ"

// We can provide the calendar as a
// manual override formatting option.
date.toLocaleDateString("en-US", { calendar: "islamic" })
// => "7/16/1445 AH"

// Persian-Iran;
// converts to the Persian calendar.
date.toLocaleDateString("fa-IR");
// => "۱۴۰۲/۱۱/۷"

date.toLocaleDateString("en-US", { calendar: "persian" });
// => "11/7/1402 AP"

// Thai-Thailand;
// converts to the Buddhist calendar.
date.toLocaleDateString("th-TH");
// => "27/1/2567"Code language: JavaScript (javascript)

Date and time pickers

We’ve been looking at formatting output so far. Let’s switch to input. To make our users’ lives easier, we often present them with a date picker when ask them for date input. On the web, we can use the date, time and datetime-local inputs.

<input type="date">Code language: HTML, XML (xml)

 

The Firefox browser date picker.
The Firefox browser date picker.

🔗 Learn more from the MDN date input entry.

<input type="time">Code language: HTML, XML (xml)
The Arc (Chromium-based) browser time picker.
The Arc (Chromium-based) browser time picker.

🔗 Learn more from the MDN time input entry.

To combine both date and time, can use the the datetime-local input.

<input type="datetime-local">Code language: HTML, XML (xml)

 

The Arc browser datetime-local picker.
The Arc browser datetime-local picker.

🔗 Learn more from the MDN datetime-local input entry.

Unfortunately, the native browser pickers seem stuck on Gregorian calendars, with no manual or automatic localization switching to other calendars. To get truly localizable date pickers in our web apps, we have to roll our own or use a library like react-dates (for React).

The react-dates date picker showing the Persian calendar.
The react-dates date picker showing the Persian calendar.

 

If you’re working on an operating system platform, like Android or iOS, you’ll find their native date pickers automatically localize their numeral systems, text labels, and layout direction per the user’s language. They might still largely stick to the Gregorian calendar, however.

The native Android date picker localized for Arabic users.
The native Android date picker localized for Arabic users.
The native Android time picker localized for Arabic users.
The native Android time picker localized for Arabic users.

🔗 How to Localize Date and Time Formats in Android is a deep dive into the subject.

Wrapping up: Key takeaways in date and time localization

As we conclude our exploration of localizing dates and times, we hope that you can see that this aspect of localization is more than a mere afterthought — it’s a cornerstone of creating truly global and inclusive applications.

Here are the key takeaways from this guide:

  • Determine the user’s locale: Identifying the user’s locale is the first step in presenting dates and times that reflect their cultural and regional standards.
  • Understand locale definitions: Recognize that a locale encompasses language, region, and sometimes additional nuances, influencing how dates and times should be formatted.
  • Use native formatting libraries: Leverage native date formatting libraries, which automatically adjust to the user’s locale.
  • Utilize default and custom formats: Whether adopting default formats or customizing them for complete control, be mindful of the locale’s conventions for representing dates and times (again libraries help a lot here).
  • Account for time zones: Ensure that dates and times are correctly adjusted for the user’s location.
  • Explore calendar diversity: Study the regions where you’re serving your app and see if they use local non-Gregorian calendars.
  • Prefer accessible date and time pickers: Choose date and time pickers that support localization, at least showing numeral systems and writing in your user’s language and region. Ideally, show them their local default calendar.

Alright, we hope you’ve enjoyed this guide to date and time localization and learned a thing or two. Happy coding.