docs.rodeo

MDN Web Docs mirror

Temporal.ZonedDateTime

{{JSRef}} {{SeeCompatTable}} 

The Temporal.ZonedDateTime object represents a date and time with a time zone. It is fundamentally represented as a combination of an instant, a time zone, and a calendar system.

Description

A ZonedDateTime functions as a bridge between an exact time and a wall-clock time: it simultaneously represents an instant in history (like a {{jsxref("Temporal.Instant")}} ) and a local, wall-clock time (like a {{jsxref("Temporal.PlainDateTime")}} ). It does so by storing the instant, the time zone, and the calendar system. The time zone is used to convert between the instant and the local time, and the calendar system is used to interpret the local time.

ZonedDateTime is the only Temporal class that is time zone-aware. The addition of a time zone makes ZonedDateTime objects have important behavior differences from {{jsxref("Temporal.PlainDateTime")}}  objects. Namely, you can no longer assume that “the time 1 minute afterwards” is the same every day, or that a day has 24 hours. In the worst case, an entire day may be missing from the local calendar. Below, we offer a quick overview of time zones and offsets and how they affect conversion between UTC time and local time.

Time zones and offsets

All times in JavaScript have one golden standard: the UTC time, which increments continuously and uniformly as physical time progresses. By contrast, users are more interested in their local time, which is the time they read on their calendars and clocks. The process of converting between UTC time and local time involves a time zone offset, which is calculated as:

local time = UTC time + offset

For example, if the UTC time is 1970-01-01T00:00:00, and the offset is “-05:00”, then the local time is:

1970-01-01T00:00:00 + -05:00 = 1969-12-31T19:00:00

By suffixing this local time with the offset, thus expressing this time as “1969-12-31T19:00:00-05:00”, it can now be unambiguously understood as an instant in history.

To know the offset, we need two pieces of information, the time zone, and the instant. The time zone is a region on Earth where the same offset is used at all times. Two clocks in the same time zone will always show the same time simultaneously, but the offset is not necessarily constant: that is, these clocks’ times may change abruptly. This commonly happens during daylight saving time transitions, where the offset changes by an hour, which happens twice a year. Offsets can also change permanently due to political changes, e.g., a country switching time zones.

The time zones are stored in the IANA Time Zone Database. Each IANA time zone has:

As input, named identifiers are matched case-insensitively. Internally, they are stored in their preferred case, and non-primary identifiers will not be converted to their primary identifier.

[!NOTE] When setting the time zone name, you rarely want to set it to "UTC". ZonedDateTime is intended to be displayed to users, but no human lives in the “UTC” time zone. If you don’t know the time zone at construction time but know the wall-clock time, use a {{jsxref("Temporal.PlainDateTime")}} . If you know the exact instant, use a {{jsxref("Temporal.Instant")}} .

When a Temporal API accepts a time zone identifier, in addition to primary time zone identifiers and non-primary time zone identifiers, it also accepts an offset time zone identifier, which is in the same form as the offset, except subminute precision is not allowed. For example, +05:30, -08, +0600 are all valid offsets identifiers. Internally, offset identifiers are stored in the ±HH:mm form.

[!NOTE] Avoid using offset identifiers if there is a named time zone you can use instead. Even if a region has always used a single offset, it is better to use the named identifier to guard against future political changes to the offset.

If a region uses (or has used) multiple offsets, then using its named time zone is even more important. This is because Temporal.ZonedDateTime can use methods like add or with to create new instances at a different instant. If those derived instances correspond to an instant that uses a different offset (for example, after a Daylight Saving Time transition) then your calculations will have an incorrect local time. Using a named time zone ensures that local dates and times are always adjusted for the correct offset for that instant.

For convenience, when providing a time zone identifier to Temporal APIs such as Temporal.ZonedDateTime.prototype.withTimeZone() and the timeZoneId option of Temporal.ZonedDateTime.from(), you can provide it in a few other forms:

The IANA time zone database changes from time to time, usually to add new time zones in response to political changes. However, on rare occasions, IANA time zone identifiers are renamed to match updated English translation of a city name or to update outdated naming conventions. For example, here are a few notable name changes:

Current IANA primary identifier Old, now non-primary identifier
America/Argentina/Buenos_Aires America/Buenos_Aires
Asia/Kolkata Asia/Calcutta
Asia/Ho_Chi_Minh Asia/Saigon
Europe/Kyiv Europe/Kiev

Historically, these renames caused problems for programmers because the Unicode CLDR database (a library used by browsers rely on to supply time zone identifiers and data) did not follow IANA’s renaming for stability reasons. As a result, some browsers like Chrome and Safari reported CLDR’s outdated identifiers, while other browsers like Firefox overrode CLDR’s defaults and reported the up-to-date primary identifiers.

With the introduction of Temporal, this behavior is now more standardized:

Note that the attribution of primary identifiers preserves the country code: for example, the IANA database records Atlantic/Reykjavik as an alias for Africa/Abidjan, but because they correspond to different countries (Iceland and Côte d’Ivoire, respectively), they are treated as distinct primary identifiers.

This standardization applies outside of Temporal as well. For example, the timeZone option returned by {{jsxref("Intl/DateTimeFormat/resolvedOptions", "Intl.DateTimeFormat.prototype.resolvedOptions()")}}  is also never replaced with an alias, though browsers have traditionally canonicalized these identifiers prior to standardization by Temporal. On the other hand, {{jsxref("Intl/Locale/getTimeZones", "Intl.Locale.prototype.getTimeZones()")}}  and {{jsxref("Intl.supportedValuesOf()")}}  (timeZone option) will return the most up-to-date identifier, while some browsers used to return the old, non-primary identifier.

RFC 9557 format

ZonedDateTime objects can be serialized and parsed using the RFC 9557 format, an extension to the ISO 8601 / RFC 3339 format. The string has the following form (spaces are only for readability and should not be present in the actual string):

YYYY-MM-DD T HH:mm:ss.sssssssss Z/±HH:mm [time_zone_id] [u-ca=calendar_id]

As an input, other annotations in the [key=value] format are ignored, and they must not have the critical flag.

When serializing, you can configure the fractional second digits, whether to display the offset/time zone ID/calendar ID, and whether to add a critical flag for the annotations.

Ambiguity and gaps from local time to UTC time

Given a time zone, conversion from UTC to local time is straightforward: you first get the offset using the time zone name and the instant, then add the offset to the instant. The reverse is not true: conversion from local time to UTC time, without an explicit offset, is ambiguous, because one local time can correspond to zero, one, or many UTC times. Consider the most common cause: daylight saving time transitions. Take New York as an example. Its standard offset is UTC-5, but during DST, all clocks are set forward by an hour, so the offset becomes UTC-4. In the US, transitions happen at 2:00 AM local time, so consider these two transition days:

UTC time New York time
2024-03-10T06:58:00Z 2024-03-10T01:58:00-05:00
2024-03-10T06:59:00Z 2024-03-10T01:59:00-05:00
2024-03-10T07:00:00Z 2024-03-10T03:00:00-04:00
2024-11-03T05:58:00Z 2024-11-03T01:58:00-04:00
2024-11-03T05:59:00Z 2024-11-03T01:59:00-04:00
2024-11-03T06:00:00Z 2024-11-03T01:00:00-05:00

As you can see, in March, one hour disappeared from the local time, and in November, we have two hours that have the same wall-clock time. Suppose that we stored a PlainDateTime that says “2024-03-10T02:05:00”, and we want to interpret it in the America/New_York time zone, there will be no time that corresponds to it, while a PlainDateTime that says “2024-11-03T01:05:00” can correspond to two different instants.

When constructing a ZonedDateTime from a local time (using {{jsxref("Temporal/ZonedDateTime/from", "Temporal.ZonedDateTime.from()")}} , {{jsxref("Temporal/ZonedDateTime/with", "Temporal.ZonedDateTime.prototype.with()")}} , {{jsxref("Temporal/PlainDateTime/toZonedDateTime", "Temporal.PlainDateTime.prototype.toZonedDateTime()")}} ), the behavior for ambiguity and gaps is configurable via the disambiguation option:

There are several cases where there’s no ambiguity when constructing a ZonedDateTime:

Offset ambiguity

We already demonstrated how ambiguity may arise from interpreting a local time in a time zone, without providing an explicit offset. However, if you provide an explicit offset, then another conflict arises: between the offset as specified, and the offset as calculated from the time zone and the local time. This is an unavoidable real-world issue: if you store a time in the future, with an anticipated offset, then before that time comes, the time zone definition may have changed due to political reasons. For example, suppose in 2018, we set a reminder at the time 2019-12-23T12:00:00-02:00[America/Sao_Paulo] (which is a daylight saving time; Brazil is in the southern hemisphere, so it enters DST in October and exits in February). But before that time comes, in early 2019, Brazil decides to stop observing DST, so the real offset becomes -03:00. Should the reminder now still fire at noon (keeping the local time), or should it fire at 11:00 AM (keeping the exact time)?

When constructing a ZonedDateTime with {{jsxref("Temporal/ZonedDateTime/from", "Temporal.ZonedDateTime.from()")}}  or when updating it using the {{jsxref("Temporal/ZonedDateTime/with", "with()")}}  method, the behavior for offset ambiguity is configurable via the offset option:

Note that the Z offset does not mean +00:00; it is always considered valid regardless of the time zone. The time is interpreted as a UTC time, and the time zone identifier is then used to convert it to local time. In other words, Z enforces the same behavior as the ignore option and its results can never be ambiguous.

[!NOTE] Although {{jsxref("Temporal/Instant/from", "Temporal.Instant.from()")}}  also takes an RFC 9557 string in the same form, there is no ambiguity because it always ignores the time zone identifier and reads the offset only.

Constructor

Static methods

Instance properties

These properties are defined on Temporal.ZonedDateTime.prototype and shared by all Temporal.ZonedDateTime instances.

Instance methods

Specifications

{{Specifications}} 

Browser compatibility

{{Compat}} 

See also

In this article

View on MDN