Skip to content

Behaviour of date libraries against Temporal

Each case below gives one input to Temporal, to GMT and to five libraries: date-fns, Luxon, Day.js, Moment.js and @internationalized/date. A library’s result is reduced to the form named in the case, then compared with Temporal’s result. The verdict is “Same” when the two are equal on every host, “Differs” when they are not, “Depends on the host zone” when the result changes with the host, and “Not supported” when the library has no such operation. A case Temporal has no call for is compared with a stated result, and the case says so.

A person runs pnpm matrix in scripts/behaviour-matrix/. Some libraries read the time zone of the machine they run on, so a result can depend on where the code runs. The runner therefore repeats every library call once under each of these host time zones: Asia/Kolkata, Europe/London, Australia/Sydney, America/Santiago. One has no daylight saving time, and the others observe it in the northern and southern hemispheres. This run used Node v26.12.1 on 2026-10-27.

A library whose result is the same on every host gets the verdict “Same” or “Differs”. A library whose result changes with the host zone gets the verdict “Depends on the host zone”, and its row lists the result of each host. It is never “Same”, because it would give a different answer on another machine. Temporal and GMT read no host zone, and the runner stops if either gives a different result on any host.

Every Temporal call ran twice, once on the polyfill and once on native Temporal, and the runner stops when the two disagree. The versions of the five libraries, and of the companion packages date-fns and Moment.js use for time zones, are named at the top of each library’s page under Migrate to Temporal with GMT.

GMT’s result is run from the source of this repository. Where GMT’s result differs from Temporal’s, the case says why.

M1. Add one calendar day across a DST gap

Input: 2024-03-30T02:30:00+01:00[Europe/Berlin], plus 1 day (02:30 does not exist on 2024-03-31). Compared as: local time and offset. Specification: AddZonedDateTime (6.5.5), DisambiguatePossibleEpochNanoseconds (11.1.12), RFC 5545 section 3.3.6.

LibraryCallReturnsCompared with TemporalUpstream issue
Temporalthe proposal's own call2024-03-31T03:30:00+02:00[Europe/Berlin]Referencenone
GMTaddZoned("2024-03-30T02:30:00+01:00[Europe/Berlin]", { days: 1 })2024-03-31T03:30:00+02:00[Europe/Berlin]Samenone
date-fnsaddDays(new TZDate(2024, 2, 30, 2, 30, "Europe/Berlin"), 1)Asia/Kolkata host: 2024-03-31T03:30:00.000+02:00Europe/London host: 2024-03-31T01:30:00.000+01:00Australia/Sydney host: 2024-03-31T03:30:00.000+02:00America/Santiago host: 2024-03-31T03:30:00.000+02:00Depends on the host zonenone
LuxonDateTime.fromISO("2024-03-30T02:30", { zone: "Europe/Berlin" }).plus({ days: 1 }).toISO()2024-03-31T03:30:00.000+02:00Samenone
Day.jsdayjs.tz("2024-03-30T02:30", "Europe/Berlin").add(1, "day").format()2024-03-31T02:30:00+01:00Differsiamkun/dayjs#1271, iamkun/dayjs#1260, iamkun/dayjs#1827
Moment.jsmoment.tz("2024-03-30T02:30", "Europe/Berlin").add(1, "day").format()2024-03-31T03:30:00+02:00Samenone
@internationalized/dateparseZonedDateTime("2024-03-30T02:30[Europe/Berlin]").add({ days: 1 }).toString()2024-03-31T03:30:00+02:00[Europe/Berlin]Samenone

M2. Add one calendar day, not 24 hours

Input: 2024-03-30T12:00:00+01:00[Europe/Berlin], plus 1 day (the day of 2024-03-31 has 23 hours). Compared as: local time and offset. Specification: AddZonedDateTime (6.5.5), hoursInDay (6.3.23), RFC 5545 section 3.3.6.

LibraryCallReturnsCompared with TemporalUpstream issue
Temporalthe proposal's own call2024-03-31T12:00:00+02:00[Europe/Berlin]Referencenone
GMTaddZoned("2024-03-30T12:00:00+01:00[Europe/Berlin]", { days: 1 })2024-03-31T12:00:00+02:00[Europe/Berlin]Samenone
date-fnsaddDays(new TZDate(2024, 2, 30, 12, 0, "Europe/Berlin"), 1)2024-03-31T12:00:00.000+02:00Samenone
LuxonDateTime.fromISO("2024-03-30T12:00", { zone: "Europe/Berlin" }).plus({ days: 1 }).toISO()2024-03-31T12:00:00.000+02:00Samenone
Day.jsdayjs.tz("2024-03-30T12:00", "Europe/Berlin").add(1, "day").format()2024-03-31T12:00:00+01:00Differsiamkun/dayjs#1271
Moment.jsmoment.tz("2024-03-30T12:00", "Europe/Berlin").add(1, "day").format()2024-03-31T12:00:00+02:00Samenone
@internationalized/dateparseZonedDateTime("2024-03-30T12:00[Europe/Berlin]").add({ days: 1 }).toString()2024-03-31T12:00:00+02:00[Europe/Berlin]Samenone

M3. Add one day into a repeated hour

Input: 2024-10-26T02:30:00+02:00[Europe/Berlin], plus 1 day (02:30 happens twice on 2024-10-27). Compared as: local time and offset. Specification: DisambiguatePossibleEpochNanoseconds (11.1.12), AddZonedDateTime (6.5.5).

LibraryCallReturnsCompared with TemporalUpstream issue
Temporalthe proposal's own call2024-10-27T02:30:00+02:00[Europe/Berlin]Referencenone
GMTaddZoned("2024-10-26T02:30:00+02:00[Europe/Berlin]", { days: 1 })2024-10-27T02:30:00+02:00[Europe/Berlin]Samenone
date-fnsaddDays(new TZDate(2024, 9, 26, 2, 30, "Europe/Berlin"), 1)Asia/Kolkata host: 2024-10-27T02:30:00.000+02:00Europe/London host: 2024-10-27T02:30:00.000+01:00Australia/Sydney host: 2024-10-27T02:30:00.000+02:00America/Santiago host: 2024-10-27T02:30:00.000+01:00Depends on the host zonenone
LuxonDateTime.fromISO("2024-10-26T02:30", { zone: "Europe/Berlin" }).plus({ days: 1 }).toISO()2024-10-27T02:30:00.000+02:00Samenone
Day.jsdayjs.tz("2024-10-26T02:30", "Europe/Berlin").add(1, "day").format()2024-10-27T02:30:00+02:00Samenone
Moment.jsmoment.tz("2024-10-26T02:30", "Europe/Berlin").add(1, "day").format()2024-10-27T02:30:00+02:00Samenone
@internationalized/dateparseZonedDateTime("2024-10-26T02:30[Europe/Berlin]").add({ days: 1 }).toString()2024-10-27T02:30:00+02:00[Europe/Berlin]Samenone

M3b. Two readings of 02:30 on the repeated-hour day: equal or not

Input: 2024-10-27T02:30:00+02:00[Europe/Berlin] and 2024-10-27T02:30:00+01:00[Europe/Berlin]. Compared as: equal (true or false). Specification: ZonedDateTime.prototype.equals (6.3.40).

LibraryCallReturnsCompared with TemporalUpstream issue
Temporalthe proposal's own callfalseReferencenone
GMTareZonedEqual("2024-10-27T02:30:00+02:00[Europe/Berlin]", "2024-10-27T02:30:00+01:00[Europe/Berlin]")falseSamenone
date-fnsisEqual(parseISO("2024-10-27T02:30:00+02:00"), parseISO("2024-10-27T02:30:00+01:00"))falseSamenone
LuxonDateTime.fromISO(a, { zone: "Europe/Berlin" }).equals(DateTime.fromISO(b, { zone: "Europe/Berlin" }))falseSamenone
Day.jsdayjs(a).isSame(dayjs(b))falseSamenone
Moment.jsmoment.parseZone(a).isSame(moment.parseZone(b))falseSamenone
@internationalized/dateparseZonedDateTime(a).compare(parseZonedDateTime(b)) === 0falseSamenone

M4. Resolve a wall time that does not exist

Input: 2024-03-31T02:30:00 in Europe/Berlin (the clocks skip from 02:00 to 03:00). Compared as: UTC instant. Specification: GetEpochNanosecondsFor (11.1.11), DisambiguatePossibleEpochNanoseconds (11.1.12), GetPossibleEpochNanoseconds (11.1.13).

LibraryCallReturnsCompared with TemporalUpstream issue
Temporalthe proposal's own call2024-03-31T01:30:00ZReferencenone
GMTresolveLocal("2024-03-31T02:30:00", "Europe/Berlin")2024-03-31T01:30:00ZSamenone
date-fnsnew TZDate(2024, 2, 31, 2, 30, "Europe/Berlin")Asia/Kolkata host: 2024-03-31T01:30:00.000+00:00Europe/London host: 2024-03-31T00:30:00.000+00:00Australia/Sydney host: 2024-03-31T01:30:00.000+00:00America/Santiago host: 2024-03-31T01:30:00.000+00:00Depends on the host zonenone
LuxonDateTime.fromISO("2024-03-31T02:30", { zone: "Europe/Berlin" }).toUTC().toISO()2024-03-31T01:30:00.000ZSamenone
Day.jsdayjs.tz("2024-03-31T02:30", "Europe/Berlin").utc().format()2024-03-31T01:30:00ZSamenone
Moment.jsmoment.tz("2024-03-31T02:30", "Europe/Berlin").utc().format()2024-03-31T01:30:00ZSamenone
@internationalized/dateparseZonedDateTime("2024-03-31T02:30[Europe/Berlin]").toAbsoluteString()2024-03-31T01:30:00.000ZSamenone

M4b. Reject a wall time that does not exist

Input: 2024-03-31T02:30:00 in Europe/Berlin, asking for an error instead of a shifted time. Compared as: UTC instant, or rejected. Specification: DisambiguatePossibleEpochNanoseconds (11.1.12), GetTemporalDisambiguationOption (13.7).

LibraryCallReturnsCompared with TemporalUpstream issue
Temporalthe proposal's own callrejectedReferencenone
GMTresolveLocal("2024-03-31T02:30:00", "Europe/Berlin", { disambiguation: "reject" })rejectedSamenone
date-fnsnoneTZDate always resolves a skipped wall time; it has no option to reject itNot supportednone
LuxonnonefromISO always resolves a skipped wall time; it has no option to reject itNot supportednone
Day.jsnonedayjs.tz always resolves a skipped wall time; it has no option to reject itNot supportednone
Moment.jsnonemoment.tz always resolves a skipped wall time; it has no option to reject itNot supportednone
@internationalized/dateparseZonedDateTime("2024-03-31T02:30[Europe/Berlin]", "reject")RangeError: No such absolute time foundSamenone

M5. Add one day across a 30-minute DST change

Input: 2024-10-05T02:15:00+10:30[Australia/Lord_Howe], plus 1 day (the clocks move 30 minutes on 2024-10-06). Compared as: local time and offset. Specification: AddZonedDateTime (6.5.5), hoursInDay (6.3.23).

LibraryCallReturnsCompared with TemporalUpstream issue
Temporalthe proposal's own call2024-10-06T02:45:00+11:00[Australia/Lord_Howe]Referencenone
GMTaddZoned("2024-10-05T02:15:00+10:30[Australia/Lord_Howe]", { days: 1 })2024-10-06T02:45:00+11:00[Australia/Lord_Howe]Samenone
date-fnsaddDays(new TZDate(2024, 9, 5, 2, 15, "Australia/Lord_Howe"), 1)Asia/Kolkata host: 2024-10-06T02:45:00.000+11:00Europe/London host: 2024-10-06T02:45:00.000+11:00Australia/Sydney host: 2024-10-06T03:15:00.000+11:00America/Santiago host: 2024-10-06T02:45:00.000+11:00Depends on the host zonenone
LuxonDateTime.fromISO("2024-10-05T02:15", { zone: "Australia/Lord_Howe" }).plus({ days: 1 }).toISO()2024-10-06T02:45:00.000+11:00Samenone
Day.jsdayjs.tz("2024-10-05T02:15", "Australia/Lord_Howe").add(1, "day").format()Asia/Kolkata host: 2024-10-06T02:15:00+10:30Europe/London host: 2024-10-06T02:15:00+10:30Australia/Sydney host: 2024-10-06T03:15:00+10:30America/Santiago host: 2024-10-06T02:15:00+10:30Depends on the host zonenone
Moment.jsmoment.tz("2024-10-05T02:15", "Australia/Lord_Howe").add(1, "day").format()2024-10-06T02:45:00+11:00Samenone
@internationalized/dateparseZonedDateTime("2024-10-05T02:15[Australia/Lord_Howe]").add({ days: 1 }).toString()2024-10-06T02:45:00+11:00[Australia/Lord_Howe]Samenone

M6. Start of a day whose midnight does not exist

Input: 2024-09-08T12:00:00-03:00[America/Santiago], start of day (the clocks skip from 00:00 to 01:00). Compared as: local time and offset. Specification: GetStartOfDay (11.1.14), startOfDay (6.3.45).

LibraryCallReturnsCompared with TemporalUpstream issue
Temporalthe proposal's own call2024-09-08T01:00:00-03:00[America/Santiago]Referencenone
GMTstartOfZoned("2024-09-08T12:00:00-03:00[America/Santiago]", "day")2024-09-08T01:00:00-03:00[America/Santiago]Samenone
date-fnsstartOfDay(new TZDate(2024, 8, 8, 12, 0, "America/Santiago"))2024-09-08T01:00:00.000-03:00Samenone
LuxonDateTime.fromISO("2024-09-08T12:00", { zone: "America/Santiago" }).startOf("day").toISO()2024-09-08T01:00:00.000-03:00Samenone
Day.jsdayjs.tz("2024-09-08T12:00", "America/Santiago").startOf("day").format()Asia/Kolkata host: 2024-09-08T00:00:00-04:00Europe/London host: 2024-09-08T00:00:00-04:00Australia/Sydney host: 2024-09-08T00:00:00-04:00America/Santiago host: 2024-09-08T01:00:00-03:00Depends on the host zoneiamkun/dayjs#1437, iamkun/dayjs#2553, iamkun/dayjs#2057
Moment.jsmoment.tz("2024-09-08T12:00", "America/Santiago").startOf("day").format()2024-09-08T01:00:00-03:00Samenone
@internationalized/datetoZoned(toCalendarDate(parseZonedDateTime("2024-09-08T12:00[America/Santiago]")), "America/Santiago")2024-09-08T01:00:00-03:00[America/Santiago]Samenone

M7. Add one month to January 31

Input: 2024-01-31, plus 1 month. Compared as: date. Specification: RegulateISODate (3.5.7), CalendarDateAdd (12.3.7).

LibraryCallReturnsCompared with TemporalUpstream issue
Temporalthe proposal's own call2024-02-29Referencenone
GMTaddDate("2024-01-31", { months: 1 })2024-02-29Samenone
date-fnsaddMonths(parseISO("2024-01-31"), 1)2024-02-29Samenone
LuxonDateTime.fromISO("2024-01-31").plus({ months: 1 }).toISODate()2024-02-29Samenone
Day.jsdayjs("2024-01-31").add(1, "month").format("YYYY-MM-DD")2024-02-29Samenone
Moment.jsmoment("2024-01-31").add(1, "month").format("YYYY-MM-DD")2024-02-29Samenone
@internationalized/dateparseDate("2024-01-31").add({ months: 1 }).toString()2024-02-29Samenone

M8. Whole months between January 31 and February 29

Input: 2024-01-31 until 2024-02-29. Compared as: whole months. Specification: CompareSurpasses (3.5.5), CalendarDateUntil (12.3.9).

LibraryCallReturnsCompared with TemporalUpstream issue
Temporalthe proposal's own call0Referencenone
GMTdiffDate("2024-01-31", "2024-02-29", "months")0Samenone
date-fnsdifferenceInMonths(parseISO("2024-02-29"), parseISO("2024-01-31"))1Differsnone
LuxonDateTime.fromISO("2024-02-29").diff(DateTime.fromISO("2024-01-31"), "months").months1Differsnone
Day.jsdayjs("2024-02-29").diff(dayjs("2024-01-31"), "month")1Differsnone
Moment.jsmoment("2024-02-29").diff(moment("2024-01-31"), "months")1Differsnone
@internationalized/datenoneit has no month difference; compare() returns a count of daysNot supportednone

M9. Parse a date whose year has one digit

Input: "2-12-12" read as an ISO 8601 date. Compared as: date, or rejected. Specification: ISO 8601 grammar (13.31), RFC 3339 section 5.6.

LibraryCallReturnsCompared with TemporalUpstream issue
Temporalthe proposal's own callrejectedReferencenone
GMTisValidDate("2-12-12")rejectedSamenone
date-fnsparseISO("2-12-12")rejectedSamenone
LuxonDateTime.fromISO("2-12-12")rejectedSamenone
Day.jsdayjs("2-12-12")2012-02-12Differsnone
Moment.jsmoment("2-12-12", moment.ISO_8601, true)rejectedSamenone
@internationalized/dateparseDate("2-12-12")Error: Invalid ISO 8601 date string: 2-12-12Samenone

M9d. Read a year of one digit with a pattern

Input: "2-12-12" read with the pattern year-month-day. Compared as: date, or rejected. Specification: UTS #35 Date Format Patterns, ISO 8601 grammar (13.31).

Temporal has no call for this. Each result is compared with the stated result, rejected.

Temporal reads no patterns, so the stated result is GMT's choice: yyyy is four digits, as the year is in the ISO 8601 grammar. UTS #35 recommends lenient pattern parsing, so a library that reads 2 as year 2 follows it. The rows show the difference and make no claim about which is right.

LibraryCallReturnsCompared with the stated resultUpstream issue
GMTparseDateWithPattern("2-12-12", "yyyy-MM-dd")rejectedSamenone
date-fnsparse("2-12-12", "yyyy-MM-dd", parseISO("2000-01-01"))0002-12-12Differsdate-fns/date-fns#942, date-fns/date-fns#1569
LuxonDateTime.fromFormat("2-12-12", "yyyy-MM-dd")rejectedSamenone
Day.jsdayjs("2-12-12", "YYYY-MM-DD", true)rejectedSamenone
Moment.jsmoment("2-12-12", "YYYY-MM-DD", true)rejectedSamenone
@internationalized/datenoneit has no pattern parser; parseDate reads ISO 8601 onlyNot supportednone

M9b. Parse a date that is not on the calendar

Input: "2024-02-30". Compared as: date, or rejected. Specification: IsValidDate (13.31.2), IsValidISODate (3.5.8).

LibraryCallReturnsCompared with TemporalUpstream issue
Temporalthe proposal's own callrejectedReferencenone
GMTisValidDate("2024-02-30")rejectedSamenone
date-fnsparseISO("2024-02-30")rejectedSamenone
LuxonDateTime.fromISO("2024-02-30")rejectedSamenone
Day.jsdayjs("2024-02-30")2024-03-01Differsnone
Moment.jsmoment("2024-02-30", moment.ISO_8601, true)rejectedSamenone
@internationalized/dateparseDate("2024-02-30")RangeError: Value out of range: 1 <= 30 <= 29Samenone

M9c. Parse a date in basic format

Input: "20240212". Compared as: date, or rejected. Specification: RFC 3339 section 5.6, ISO 8601 grammar (13.31).

GMT validators take the extended form of RFC 3339 section 5.6 only, so 20240212 is rejected on purpose. Temporal accepts the basic form.

LibraryCallReturnsCompared with TemporalUpstream issue
Temporalthe proposal's own call2024-02-12Referencenone
GMTisValidDate("20240212")rejectedDiffersnone
date-fnsparseISO("20240212")2024-02-12Samenone
LuxonDateTime.fromISO("20240212")2024-02-12Samenone
Day.jsdayjs("20240212")2024-02-12Samenone
Moment.jsmoment("20240212", moment.ISO_8601, true)2024-02-12Samenone
@internationalized/dateparseDate("20240212")Error: Invalid ISO 8601 date string: 20240212Differsnone

M10. Read a string with a bracketed time zone

Input: 2024-03-31T03:30:00+02:00[Europe/Berlin]. Compared as: UTC instant. Specification: RFC 9557 section 4.1, InterpretISODateTimeOffset (6.5.1).

LibraryCallReturnsCompared with TemporalUpstream issue
Temporalthe proposal's own call2024-03-31T01:30:00ZReferencenone
GMTconvertZonedToUtc("2024-03-31T03:30:00+02:00[Europe/Berlin]")2024-03-31T01:30:00ZSamenone
date-fnsparseISO("2024-03-31T03:30:00+02:00[Europe/Berlin]").toISOString()2024-03-31T03:30:00.000ZDiffersnone
LuxonDateTime.fromISO("2024-03-31T03:30:00+02:00[Europe/Berlin]").toUTC().toISO()2024-03-31T01:30:00.000ZSamenone
Day.jsdayjs("2024-03-31T03:30:00+02:00[Europe/Berlin]").toISOString()rejectedDiffersnone
Moment.jsmoment("2024-03-31T03:30:00+02:00[Europe/Berlin]", moment.ISO_8601, true)rejectedDiffersnone
@internationalized/dateparseZonedDateTime("2024-03-31T03:30:00+02:00[Europe/Berlin]").toAbsoluteString()2024-03-31T01:30:00.000ZSamenone

M10b. Time zone name after reading a bracketed time zone

Input: 2024-03-31T03:30:00+02:00[Europe/Berlin]. Compared as: time zone name. Specification: RFC 9557 section 4.1, ToTemporalZonedDateTime (6.5.2).

LibraryCallReturnsCompared with TemporalUpstream issue
Temporalthe proposal's own callEurope/BerlinReferencenone
GMTparseTimeZoneFromZoned("2024-03-31T03:30:00+02:00[Europe/Berlin]")Europe/BerlinSamenone
date-fnsnoneparseISO returns a Date; it keeps no time zone nameNot supportednone
LuxonDateTime.fromISO("2024-03-31T03:30:00+02:00[Europe/Berlin]", { setZone: true }).zoneNameEurope/BerlinSamenone
Day.jsnoneit rejects the bracketed string, so there is no time zone to readNot supportednone
Moment.jsnonestrict ISO 8601 parsing rejects the bracketed string, so there is no time zone to readNot supportednone
@internationalized/dateparseZonedDateTime("2024-03-31T03:30:00+02:00[Europe/Berlin]").timeZoneEurope/BerlinSamenone

M10c. Read a string with a calendar annotation

Input: 2024-03-31T03:30:00+02:00[Europe/Berlin][u-ca=japanese]. Compared as: accepted or rejected, and for an accepted string the UTC instant. Specification: RFC 9557 section 3.3, RFC 9557 section 4.1, ToTemporalZonedDateTime (6.5.2), ParseISODateTime (13.35).

GMT's validator reports whether the string is accepted. The run also reads the instant with convertZonedToUtc from the string without its annotation, which that function does not take.

LibraryCallReturnsCompared with TemporalUpstream issue
Temporalthe proposal's own callacceptedReferencenone
GMTisValidCalendarZonedDateTime("2024-03-31T03:30:00+02:00[Europe/Berlin][u-ca=japanese]")acceptedSamenone
date-fnsparseISO("2024-03-31T03:30:00+02:00[Europe/Berlin][u-ca=japanese]").toISOString()accepted, reads 2024-03-31T03:30:00ZDiffersnone
LuxonDateTime.fromISO("2024-03-31T03:30:00+02:00[Europe/Berlin][u-ca=japanese]").toUTC().toISO()rejectedDiffersmoment/luxon#1621
Day.jsdayjs("2024-03-31T03:30:00+02:00[Europe/Berlin][u-ca=japanese]").toISOString()rejectedDiffersnone
Moment.jsmoment("2024-03-31T03:30:00+02:00[Europe/Berlin][u-ca=japanese]", moment.ISO_8601, true).toISOString()rejectedDiffersnone
@internationalized/dateparseZonedDateTime("2024-03-31T03:30:00+02:00[Europe/Berlin][u-ca=japanese]").toAbsoluteString()RangeError: Invalid time zone specified: Europe/Berlin][u-ca=japaneseDiffersnone

M10d. Read a string whose offset does not match its time zone

Input: 2022-07-08T00:14:07+01:00[Europe/Paris] (Paris is at +02:00 on that date). Compared as: UTC instant, or rejected. Specification: RFC 9557 section 3.4, InterpretISODateTimeOffset (6.5.1), GetTemporalOffsetOption (13.9).

LibraryCallReturnsCompared with TemporalUpstream issue
Temporalthe proposal's own callrejectedReferencenone
GMTconvertZonedToUtc("2022-07-08T00:14:07+01:00[Europe/Paris]")rejectedSamenone
date-fnsparseISO("2022-07-08T00:14:07+01:00[Europe/Paris]").toISOString()2022-07-08T00:14:07.000ZDiffersnone
LuxonDateTime.fromISO("2022-07-08T00:14:07+01:00[Europe/Paris]").toUTC().toISO()2022-07-07T22:14:07.000ZDiffersnone
Day.jsnoneit rejects the bracketed stringNot supportednone
Moment.jsnonestrict ISO 8601 parsing rejects the bracketed stringNot supportednone
@internationalized/dateparseZonedDateTime("2022-07-08T00:14:07+01:00[Europe/Paris]").toAbsoluteString()Error: Offset +01:00 is invalid for 2022-07-08T00:14:07 in Europe/ParisSamenone

M11. Read an offset with minutes before UTC

Input: 2024-01-15T12:00:00-03:30. Compared as: UTC instant. Specification: RFC 3339 section 4.2, ParseDateTimeUTCOffset (14.6.11).

LibraryCallReturnsCompared with TemporalUpstream issue
Temporalthe proposal's own call2024-01-15T15:30:00ZReferencenone
GMTtoOffsetInstant("2024-01-15T12:00:00-03:30").instant2024-01-15T15:30:00ZSamenone
date-fnsparseISO("2024-01-15T12:00:00-03:30").toISOString()2024-01-15T15:30:00.000ZSamenone
LuxonDateTime.fromISO("2024-01-15T12:00:00-03:30").toUTC().toISO()2024-01-15T15:30:00.000ZSamenone
Day.jsdayjs("2024-01-15T12:00:00-03:30").toISOString()2024-01-15T15:30:00.000ZSamenone
Moment.jsmoment("2024-01-15T12:00:00-03:30").toISOString()2024-01-15T15:30:00.000ZSamenone
@internationalized/dateparseAbsolute("2024-01-15T12:00:00-03:30", "UTC").toAbsoluteString()2024-01-15T14:30:00.000ZDiffersadobe/react-spectrum#10809

M12. ISO week and week-year at a year boundary

Input: 2024-12-30. Compared as: ISO week-year and week. Specification: ISOWeekOfYear (12.3.18), ISO 8601-1 (week calendar).

LibraryCallReturnsCompared with TemporalUpstream issue
Temporalthe proposal's own call2025-W01Referencenone
GMTgetIsoWeekDate("2024-12-30")2025-W01Samenone
date-fnsgetISOWeekYear(parseISO("2024-12-30")), getISOWeek(parseISO("2024-12-30"))2025-W01Samenone
LuxonDateTime.fromISO("2024-12-30").weekYear, .weekNumber2025-W01Samenone
Day.jsdayjs("2024-12-30").isoWeekYear(), .isoWeek()2025-W01Samenone
Moment.jsmoment("2024-12-30").isoWeekYear(), .isoWeek()2025-W01Samenone
@internationalized/datenoneit has no ISO week callNot supportednone

M13. Time zone name kept for Etc/UTC

Input: 2024-01-01T00:00:00Z shown in Etc/UTC. Compared as: time zone name. Specification: Time Zone Identifiers (14.6.2).

LibraryCallReturnsCompared with TemporalUpstream issue
Temporalthe proposal's own callEtc/UTCReferencenone
GMTparseTimeZoneFromZoned(convertUtcToZoned("2024-01-01T00:00:00Z", "Etc/UTC"))Etc/UTCSamenone
date-fnsnew TZDate(parseISO("2024-01-01T00:00:00Z").getTime(), "Etc/UTC").timeZoneEtc/UTCSamenone
LuxonDateTime.fromISO("2024-01-01T00:00:00Z").setZone("Etc/UTC").zoneNameEtc/UTCSamenone
Day.jsnoneit has no public accessor for the zone name after tz()Not supportednone
Moment.jsmoment.tz("2024-01-01T00:00:00Z", "Etc/UTC").tz()Etc/UTCSamenone
@internationalized/dateparseAbsolute("2024-01-01T00:00:00Z", "Etc/UTC").timeZoneEtc/UTCSamenone

M13b. UTC and Etc/UTC: same time zone or not

Input: 2024-01-01T00:00:00+00:00[UTC] and 2024-01-01T00:00:00+00:00[Etc/UTC]. Compared as: equal (true or false). Specification: TimeZoneEquals (11.1.15), ZonedDateTime.prototype.equals (6.3.40).

LibraryCallReturnsCompared with TemporalUpstream issue
Temporalthe proposal's own calltrueReferencenone
GMTareZonedEqual("2024-01-01T00:00:00+00:00[UTC]", "2024-01-01T00:00:00+00:00[Etc/UTC]")trueSamenone
date-fnsnoneisEqual compares instants; no call compares time zone namesNot supportednone
LuxonDateTime.fromISO(s, { zone: "UTC" }).equals(DateTime.fromISO(s, { zone: "Etc/UTC" }))falseDiffersmoment/luxon#1358
Day.jsnoneisSame compares instants; no call compares time zone namesNot supportednone
Moment.jsnoneisSame compares instants; no call compares time zone namesNot supportednone
@internationalized/datenonecompare() orders instants; no call compares time zone namesNot supportednone

M13c. Europe/Kiev and Europe/Kyiv: same time zone or not

Input: 2024-01-01T00:00:00+02:00[Europe/Kiev] and 2024-01-01T00:00:00+02:00[Europe/Kyiv]. Compared as: equal (true or false). Specification: TimeZoneEquals (11.1.15).

LibraryCallReturnsCompared with TemporalUpstream issue
Temporalthe proposal's own calltrueReferencenone
GMTareZonedEqual("2024-01-01T00:00:00+02:00[Europe/Kiev]", "2024-01-01T00:00:00+02:00[Europe/Kyiv]")trueSamenone
date-fnsnoneisEqual compares instants; no call compares time zone namesNot supportednone
LuxonDateTime.fromISO(s, { zone: "Europe/Kiev" }).equals(DateTime.fromISO(s, { zone: "Europe/Kyiv" }))falseDiffersnone
Day.jsnoneisSame compares instants; no call compares time zone namesNot supportednone
Moment.jsnoneisSame compares instants; no call compares time zone namesNot supportednone
@internationalized/datenonecompare() orders instants; no call compares time zone namesNot supportednone

M14. Offset written for a UTC instant

Input: 2024-03-31T01:30:00Z, written back as ISO 8601 with each library's default writer. Compared as: the text written: the local time and offset, or Z. Specification: Temporal.Instant.prototype.toString (8.3.11), RFC 9557 section 2.

LibraryCallReturnsCompared with TemporalUpstream issue
Temporalthe proposal's own call2024-03-31T01:30:00ZReferencenone
GMTtoOffsetInstant("2024-03-31T01:30:00Z").instant2024-03-31T01:30:00ZSamenone
date-fnsformatISO(parseISO("2024-03-31T01:30:00Z"))Asia/Kolkata host: 2024-03-31T07:00:00+05:30Europe/London host: 2024-03-31T02:30:00+01:00Australia/Sydney host: 2024-03-31T12:30:00+11:00America/Santiago host: 2024-03-30T22:30:00-03:00Depends on the host zonedate-fns/date-fns#2151
LuxonDateTime.fromISO("2024-03-31T01:30:00Z").toISO()Asia/Kolkata host: 2024-03-31T07:00:00.000+05:30Europe/London host: 2024-03-31T02:30:00.000+01:00Australia/Sydney host: 2024-03-31T12:30:00.000+11:00America/Santiago host: 2024-03-30T22:30:00.000-03:00Depends on the host zonenone
Day.jsdayjs("2024-03-31T01:30:00Z").format()Asia/Kolkata host: 2024-03-31T07:00:00+05:30Europe/London host: 2024-03-31T02:30:00+01:00Australia/Sydney host: 2024-03-31T12:30:00+11:00America/Santiago host: 2024-03-30T22:30:00-03:00Depends on the host zonenone
Moment.jsmoment("2024-03-31T01:30:00Z").format()Asia/Kolkata host: 2024-03-31T07:00:00+05:30Europe/London host: 2024-03-31T02:30:00+01:00Australia/Sydney host: 2024-03-31T12:30:00+11:00America/Santiago host: 2024-03-30T22:30:00-03:00Depends on the host zonenone
@internationalized/dateparseAbsolute("2024-03-31T01:30:00Z", "UTC").toAbsoluteString()2024-03-31T01:30:00.000ZSamenone

M15. Add one business day with a Friday and Saturday weekend

Input: 2024-07-04 (a Thursday), plus 1 business day, weekend on Friday and Saturday (ISO weekdays 5 and 6). Compared as: date. Specification: ISO 8601-1 (week calendar), UTS #35 Week Data.

Temporal has no call for this. Each result is compared with the stated result, 2024-07-07.

LibraryCallReturnsCompared with the stated resultUpstream issue
GMTaddBusinessDays("2024-07-04", 1, { weekend: [5, 6], holidays: [], timeZone: "Asia/Riyadh" })2024-07-07Samenone
date-fnsnonethe weekend is fixed to Saturday and Sunday; addBusinessDays has no weekend optionNot supportednone
Luxonnoneit has no business-day callNot supportednone
Day.jsnoneit has no business-day callNot supportednone
Moment.jsnoneit has no business-day callNot supportednone
@internationalized/datenoneit has no business-day callNot supportednone

M16. Show the same instant in another time zone

Input: 2024-07-15T12:00:00+02:00[Europe/Berlin], shown in America/St_Johns. Compared as: local time and offset. Specification: withTimeZone (6.3.33).

LibraryCallReturnsCompared with TemporalUpstream issue
Temporalthe proposal's own call2024-07-15T07:30:00-02:30[America/St_Johns]Referencenone
GMTconvertZonedToZoned("2024-07-15T12:00:00+02:00[Europe/Berlin]", "America/St_Johns")2024-07-15T07:30:00-02:30[America/St_Johns]Samenone
date-fnsnew TZDate(parseISO("2024-07-15T12:00:00+02:00").getTime(), "America/St_Johns").toISOString()2024-07-15T07:30:00.000-02:30Samenone
LuxonDateTime.fromISO("2024-07-15T12:00:00+02:00").setZone("America/St_Johns").toISO()2024-07-15T07:30:00.000-02:30Samenone
Day.jsdayjs("2024-07-15T12:00:00+02:00").tz("America/St_Johns").format()2024-07-15T07:30:00-02:30Samenone
Moment.jsmoment.parseZone("2024-07-15T12:00:00+02:00").tz("America/St_Johns").format()2024-07-15T07:30:00-02:30Samenone
@internationalized/datetoTimeZone(parseZonedDateTime("2024-07-15T12:00:00+02:00[Europe/Berlin]"), "America/St_Johns")2024-07-15T07:30:00-02:30[America/St_Johns]Samenone

Code examples: MIT-0. Packages: MIT. Third-party data and components: their own licences, credited where they appear. Everything else:© 2026 NorthGuild, all rights reserved.