The difference between `gregory` and `iso8601` calendars in ECMAScript

other versions:

Temporal and Intl.DateTimeFormat can handle various calendars, such as the Gregorian calendar with Japanese regnal eras, East Asian lunisolar calendars, Hijri calendars, and the Hebrew calendar. They also support the iso8601 and gregory calendars, both of which represent the proleptic Gregorian calendar. So, what is the difference between iso8601 and gregory?

The short answer is: gregory is the real-world Gregorian calendar in the context of internationalization, while iso8601 is an "artificial" and "culturally neutral" Gregorian calendar modeled for computer usage.

iso8601 as a calendar

ISO 8601 is the origin of standard date and time formats for data exchange. However, the standard itself is not suitable for practical use as a reference: we cannot obtain the ISO standard document for free, the syntax definition is ambiguous, and it defines various formats that almost no one uses. The term "ISO 8601 format" roughly refers to formats defined in RFC 3339 and its successors.

https://www.rfc-editor.org/info/rfc3339/

https://ijmacd.github.io/rfc3339-iso8601/

The ISO 8601 format is based on the Gregorian calendar with astronomical year numbering (year 0 for 1 BC), so it does not depend on the concept of eras. ISO 8601 also defines formats based on weeks and days of the week. The start of the week is Monday, and days of the week are 1-indexed (1 for Monday, 7 for Sunday). The first week of the year is defined as the week that contains 4 January.

The calendar ID iso8601 comes from CLDR. I suspect it originally meant the Gregorian calendar with ISO 8601 week numbering, in contrast to gregory, which referred to the Gregorian calendar with locale-dependent week numbering.

https://github.com/unicode-org/cldr/commit/dd46a84dabf145428cdd7fbf9a350368f3055d78

As an aside, TC39 and the Unicode Consortium concluded that there was insufficient evidence for locale-dependent week numbering systems other than ISO 8601. Across the world, people who care about week numbers usually expect the ISO 8601 definition. While some people may use different week numbering systems for specific use cases, it is generally not something that should be determined by locale.

https://github.com/tc39/proposal-intl-locale-info/issues/86

iso8601 as a culturally neutral data model

In ECMAScript, the iso8601 calendar is defined as a culturally neutral calendar, while gregory is a Gregorian calendar closely tied to cultural contexts. I am not sure when this distinction was first established, but we can see many discussions and decisions that assume it in the meeting notes of TC39 and ECMA-402 (TG2), issues on date-related proposals, and so on.

Of course, the iso8601 calendar is based on the Gregorian calendar and anno Domini, so it inevitably has European and Christian origins. That being said, it is obvious that the ISO 8601 format and the calendar model behind it are widely used, even in non-Western and non-Christian regions. Therefore, I believe that it is practical and reasonable to treat the iso8601 calendar as neutral, even though it is not completely neutral.

Intl.DateTimeFormat

We can specify the calendar in Intl.DateTimeFormat. If no calendar is specified, Intl.DateTimeFormat selects the default calendar for the locale (gregory in most cases). gregory represents the standard Gregorian calendar used in the real world, so it includes the concept of eras, such as BC/BCE and AD/CE. You can display the era using the era option.

const formatter = new Intl.DateTimeFormat("en-US");
formatter.format(Temporal.PlainDate.from("2026-06-01"));
// returns a date format used in the US, such as "6/1/2026"
formatter.resolvedOptions().calendar;
// "gregory"

Temporal.PlainDate.from("-000020-01-01").toLocaleString("en-US", {
  era: "short",
  year: "numeric",
  month: "long",
  day: "numeric",
}); // "January 1, 21 BC"

You can format dates with the iso8601 calendar via Intl.DateTimeFormat, since the API supports all calendars specified in ECMAScript. However, there is no specification or consensus on what it means to "format dates in the iso8601 calendar". JavaScript engines tend to choose a pattern similar to the ISO 8601 format, but in general, the result is neither a machine-readable ISO 8601 string nor a natural human-readable format (use Temporal if you want the former). Specifying formatting options that contradict the ISO 8601 format is semantically contradictory, and the output can even be broken in such cases:

// A format similar to ISO 8601 with English month names (semantically conflicting)
new Date().toLocaleString("en-u-ca-iso8601", {
  year: "numeric",
  month: "long",
});
// "2026 July" in Chrome
// "2026 " in Node.js (!!!)

https://github.com/tc39/ecma402/issues/1036

https://github.com/nodejs/node/issues/63041

Because Intl.DateTimeFormat is designed for internationalization and produces localized output for humans, we should generally use gregory here rather than iso8601. We do not use the iso8601 calendar in daily life.

Temporal

iso8601 and gregory behave differently in Temporal. For example, the weekOfYear and yearOfWeek properties return the ISO week number for iso8601, but they are undefined for other calendars, including gregory.

const isoDate = Temporal.PlainDate.from("2024-12-31");
const gregorianDate = Temporal.PlainDate.from("2024-12-31[u-ca=gregory]");
// 31 Dec 2024 belongs to the first week of 2025
isoDate.weekOfYear; // 1
isoDate.yearOfWeek; // 2025
// `undefined` for other calendars
gregorianDate.weekOfYear; // undefined
gregorianDate.yearOfWeek; // undefined

The year property uses astronomical year numbering for both calendars. However, iso8601 does not have eras, whereas gregory defines two eras: bce and ce.

const isoDate = Temporal.PlainDate.from("-000020-01-01");
isoDate.year; // -20
// "iso8601" does not have eras
isoDate.era; // undefined
isoDate.eraYear; // undefined

const gregorianDate = Temporal.PlainDate.from("-000020-01-01[u-ca=gregory]");
gregorianDate.year; // -20
// "year -20" corresponds to 21 BC
gregorianDate.era; // "bce"
gregorianDate.eraYear; // 21

The most fundamental difference between iso8601 and gregory is that iso8601 is treated as a special calendar in Temporal, while gregory is not. Temporal places gregory in the same category as other cultural calendars like chinese (the traditional Chinese calendar) and hebrew (the Hebrew calendar).

ECMA-262 and ECMA-402

ECMA-262 is the core specification of JavaScript, defining almost all of its core language features. On the other hand, ECMA-402 defines internationalization features, primarily the APIs under the Intl namespace. The specification for Temporal spans both ECMA-262 and ECMA-402, because Temporal deals heavily with domains closely tied to internationalization, particularly time zones and calendars.

The behavior of the iso8601 calendar in Temporal is explicitly specified in ECMA-262 with no implementation-defined behavior. For example, the date and time arithmetic algorithms are fully defined in ECMA-262. In fact, iso8601 is the only calendar defined in ECMA-262; all other calendars are implementation-defined in ECMA-262, and gregory is not mentioned there at all.

ECMA-402 defines the list of calendars that conforming implementations must support, which includes gregory. A conforming ECMA-402 implementation cannot omit any of these calendars or add arbitrary extra ones.

This distinction reflects the underlying design principle: iso8601 is a neutral model, whereas gregory is the Gregorian calendar as used in the real world. ECMA-262, as the core language specification, aims to remain culturally neutral, and iso8601 is the only calendar that satisfies this requirement.

How iso8601 and gregory work in Temporal

While iso8601 is a very special calendar in Temporal's specification, gregory is treated just like any other calendar. This difference becomes apparent when using Temporal.PlainYearMonth and Temporal.PlainMonthDay, the two trickiest types in Temporal.

Dates can be converted between calendars, but isolated "year and month" or "month and day" values cannot. For example, Tamuz (M10) 5786 in the Hebrew calendar spans dates from 16 June to 14 July 2026, so there is no single corresponding "year and month" in the iso8601 calendar. This is why Temporal.PlainYearMonth and Temporal.PlainMonthDay do not have a withCalendar method.

2026-07[u-ca=hebrew] clearly makes no sense, whereas 2026-07 is a valid ISO 8601 string for July 2026 in the iso8601 calendar. A Temporal.PlainYearMonth representing Tamuz 5786 is serialized as an ISO date string representing the first day of that month (2026-06-16[u-ca=hebrew]). For consistency, Temporal.PlainYearMonth rejects 2026-07[u-ca=gregory] just as it rejects 2026-07[u-ca=hebrew]. For the same reason, both 07-01[u-ca=hebrew] and 07-01[u-ca=gregory] are invalid for Temporal.PlainMonthDay, whereas 07-01 is valid for 1 July in the iso8601 calendar.

Temporal.PlainYearMonth.from("2026-07");
// July 2026 in `iso8601`
Temporal.PlainMonthDay.from("07-01");
// 1st July in `iso8601`

Temporal.PlainYearMonth.from("2026-07[u-ca=gregory]");
Temporal.PlainMonthDay.from("07-01[u-ca=gregory]");
// parse error

You can create a Temporal.PlainMonthDay from a month number and day in iso8601, but you cannot do so for other calendars. You must provide either the year or the month code.

Temporal.PlainMonthDay.from({ month: 1, day: 1 });
// 1st January in `iso8601`

Temporal.PlainMonthDay.from({
  month: 1,
  day: 1,
  calendar: "gregory",
});
// error

Temporal.PlainMonthDay.from({
  year: 2026,
  month: 1,
  day: 1,
  calendar: "gregory",
});
// or...
Temporal.PlainMonthDay.from({
  monthCode: "M01",
  day: 1,
  calendar: "gregory",
});

This is because the month number for a given month can vary by year in calendars with leap months, such as the Hebrew and Chinese calendars. For example, the month of Tamuz in the Hebrew calendar is the 10th month in regular years, but the 11th month in leap years due to the insertion of an intercalary month (Adar I). When Temporal.PlainMonthDay.from receives a month number without year information, it throws an error for all calendars other than iso8601, rather than only throwing for calendars that have leap months. I guess this rule was chosen for consistency across non-ISO calendars.

Friction between Temporal and Intl.DateTimeFormat

Most developers will not need to use the gregory calendar directly in Temporal, so the differences described above rarely cause issues. However, friction does arise when formatting a Temporal.PlainYearMonth or Temporal.PlainMonthDay with toLocaleString or Intl.DateTimeFormat.

const formatter = new Intl.DateTimeFormat("en-GB");
formatter.format(Temporal.PlainDate.from("2026-07-01"));
// "01/07/2026"

formatter.format(Temporal.PlainYearMonth.from("2026-07"));
// error
Temporal.PlainYearMonth.from("2026-07").toLocaleString("en-GB");
// error

formatter.format(Temporal.PlainMonthDay.from("01-01"));
// error
Temporal.PlainMonthDay.from("01-01").toLocaleString("en-GB");
// error

Why does this happen?

While Temporal objects default to the iso8601 calendar, Intl.DateTimeFormat instances default to gregory for most locales.

new Intl.DateTimeFormat("ja").resolvedOptions().calendar;
// "gregory"

This mismatch does not cause an error when formatting a Temporal.PlainDate. Because iso8601 is the default and transparent calendar, the date in iso8601 is automatically converted to the calendar of the Intl.DateTimeFormat instance. This conversion applies to all calendars (not only for gregory), consistent with the principle that iso8601 is special while gregory is treated like any other calendar.

const date = Temporal.PlainDate.from("2026-07-01");
new Intl.DateTimeFormat("ja").format(date);
// "2026/7/1"
new Intl.DateTimeFormat("en-u-ca-hebrew").format(date);
// "16 Tamuz 5786"

However, because Temporal.PlainYearMonth and Temporal.PlainMonthDay are calendar-dependent and cannot be converted between calendars, a mismatch between the calendar on the Temporal object (iso8601) and the formatter (gregory) throws an error. One might argue that iso8601 and gregory are same and that JavaScript should simply overlook the difference, but such an exception would contradict the core design principle.

Formatting dates with the iso8601 calendar is usually not what developers want, so the proper workaround is to construct a Temporal.PlainYearMonth or Temporal.PlainMonthDay using the gregory calendar before formatting:

const pym = Temporal.PlainYearMonth.from("2026-07");
const pymGregory = Temporal.PlainYearMonth.from({
  year: pym.year,
  month: pym.month,
  calendar: "gregory",
});
new Intl.DateTimeFormat("ja").format(pymGregory);
// "2026/7"

Conclusion

  • iso8601: The Gregorian calendar as a culturally neutral data model, primarily used in Temporal.
  • gregory: The Gregorian calendar as used in the real world with cultural conventions and eras, primarily used in Intl.DateTimeFormat.