The Temporal API is very nearly baseline with support in 67.52% of browsers including beta opt-in support in Safari and support in Node.js 26. Of course, the polyfills @js-temporal/polyfill and temporal-polyfill also exist.
One of the larger points of contention is the parsing and formatting support in the Temporal API. Regarding parsing, in addition to object literals or epochNanoseconds/epochMilliseconds, there is currently support for parsing RFC 9557, JavaScript.toString(), ISO 8601, RFC 7231.1 While there originally was a proposal for a Temporal.parse API, it has since been abandoned based on the claim that "A type-spanning "parse anything" API goes against [Temporal's] strongly-typed model." Regarding formatting, in addition to RFC 9557 support with .toString(), there is additional locale formatting support with .toLocaleString or Intl.DateTimeFormat.
However, there does not seem to exist formatting or parsing support using string tokens like that of date-fns (based on Unicode TR35) or that of Luxon or Day.js.
There does exist a temporal-parse library but it has not been updated in 4 years.
Regarding formatting, there does exist a proposal for stable formatting currently in Stage 2. Philip Chimento, one of the champions of the Temporal API, seemingly has advocated for a userland library to handle this functionality, though he does not know of any that exist.
Out of curiosity, I wanted to try using date-fns's format and parse functions to attempt to get format/parse support with Temporal. Unfortunately, I found a good number of problems that I think make either unsalvagable, but here's my work for future reference.
Realistically, one should use Intl.DateTimeFormat, .toLocaleString, .toString() for formatting.
const formatTemporal = (
temporalDate: TemporalInstance,
formatStr: string,
options?: FormatOptions,
) => {
// Could be shortened using parseISO from date-fns, but here for explicitness
const date =
temporalDate instanceof Temporal.Instant
? new Date(temporalDate.epochMilliseconds)
: temporalDate instanceof Temporal.ZonedDateTime
? new TZDate(temporalDate.epochMilliseconds, temporalDate.timeZoneId)
: temporalDate instanceof Temporal.PlainDateTime
? new Date(
temporalDate.year,
temporalDate.month - 1,
temporalDate.day,
temporalDate.hour,
temporalDate.minute,
temporalDate.second,
temporalDate.millisecond,
)
: temporalDate instanceof Temporal.PlainDate
? new Date(
temporalDate.year,
temporalDate.month - 1,
temporalDate.day,
)
: temporalDate instanceof Temporal.PlainYearMonth
? new Date(temporalDate.year, temporalDate.month - 1)
: temporalDate instanceof Temporal.PlainMonthDay
? parse(
temporalDate.toString({ calendarName: "never" }),
"MM-dd",
new Date(),
)
: parse(
temporalDate.toString({ fractionalSecondDigits: 3 }),
"HH:mm:ss.SSS",
new Date(),
);
return format(date, formatStr, options);
};- "02-29" throws an error if the reference date is not a leap year, even though it is a valid TemporalPlainMonth
- Since the DateConstructor cannot be used to create a date using only month, day or hour, minutes, seconds, milliseconds: parse has to be used.
- date-fns doesn't support different calendars, unlike Temporal
- TZDate from @date-fns/tz/date is necessary unless you want to always create Temporal.ZonedDateTime in the local timezone (Temporal.Now.timeZoneId())
- date-fns's format function does not support named timezones (e.g. America/Indianapolis, Asia/Saigon, Europe/Paris, Antarctica/McMurdo)
- Date constructor ignores microsecond, nanosecond
Realistically, one should probably be using the native options to parse standardized formats, or using RegEx for any specialized custom formats.
function parseTemporal(
dateStr: string,
formatStr: string,
output: keyof Output,
options?: ParseOptions<TZDate>,
): Output[keyof Output] {
const date = parse(
dateStr,
formatStr,
new TZDate(new Date(), "UTC"),
options,
);
console.log(date.timeZone);
const instant = Temporal.Instant.fromEpochMilliseconds(date.getTime());
if (output === "instant") {
return instant;
}
const zonedDateTime = instant.toZonedDateTimeISO(date.timeZone ?? "UTC");
if (output === "zonedDateTime") {
return zonedDateTime;
} else if (output === "plainDateTime") {
return zonedDateTime.toPlainDateTime();
} else if (output === "plainTime") {
return zonedDateTime.toPlainTime();
}
const plainDate = zonedDateTime.toPlainDate();
if (output === "plainDate") {
return plainDate;
} else if (output === "plainYearMonth") {
return plainDate.toPlainYearMonth();
} else if (output === "plainMonthDay") {
return plainDate.toPlainMonthDay();
}
throw new Error(`parseTemporal: unexpected output, ${output}`);
}parsefrom date-fns does not parse timezones. Per date-fns/date-fns#2642, this seems to be intended.- One can still use
.withTimeZoneto get the time in the correct timezone, or.withTimeZone(Temporal.Now.timeZoneId())for the date in the local timezone
- One can still use
- Again, 02-29 will not work as a
PlainYearMonthif the current UTC year is not a leap year - There is a lot of excessive function overloads here
- Nanoseconds/microseconds parsing is not supported
Formatting is probably the least useful of the two, given the variety of options avaliable. Parsing however, may or may not be useful if you are not working with timezones and/or calendars.
Footnotes
-
RFC 9557 - Instant, ZonedDateTime, PlainDate, PlainDateTime, PlainMonthDay, PlainTime, PlainYearMonth.
Date.toString,Date.toISOString(Simplified ISO 8601),Date.toUTCString(RFC 7231) usingDate.parseandDate.toTemporalInstant↩