Skip to content

Instantly share code, notes, and snippets.

@jeremy-code
Created May 25, 2026 02:26
Show Gist options
  • Select an option

  • Save jeremy-code/a48be7de8476463436cf7a28b73512c5 to your computer and use it in GitHub Desktop.

Select an option

Save jeremy-code/a48be7de8476463436cf7a28b73512c5 to your computer and use it in GitHub Desktop.
Formatting and parsing with the Temporal API

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.

formatTemporal

Realistically, one should use Intl.DateTimeFormat, .toLocaleString, .toString() for formatting.

formatTemporal.ts

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);
};

Some notes

  1. "02-29" throws an error if the reference date is not a leap year, even though it is a valid TemporalPlainMonth
  2. Since the DateConstructor cannot be used to create a date using only month, day or hour, minutes, seconds, milliseconds: parse has to be used.
  3. date-fns doesn't support different calendars, unlike Temporal
  4. TZDate from @date-fns/tz/date is necessary unless you want to always create Temporal.ZonedDateTime in the local timezone (Temporal.Now.timeZoneId())
  5. date-fns's format function does not support named timezones (e.g. America/Indianapolis, Asia/Saigon, Europe/Paris, Antarctica/McMurdo)
  6. Date constructor ignores microsecond, nanosecond

parseTemporal

Realistically, one should probably be using the native options to parse standardized formats, or using RegEx for any specialized custom formats.

parseTemporal.ts

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}`);
}

Some notes

  1. parse from date-fns does not parse timezones. Per date-fns/date-fns#2642, this seems to be intended.
    • One can still use .withTimeZone to get the time in the correct timezone, or .withTimeZone(Temporal.Now.timeZoneId()) for the date in the local timezone
  2. Again, 02-29 will not work as a PlainYearMonth if the current UTC year is not a leap year
  3. There is a lot of excessive function overloads here
  4. Nanoseconds/microseconds parsing is not supported

Conclusion

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

  1. RFC 9557 - Instant, ZonedDateTime, PlainDate, PlainDateTime, PlainMonthDay, PlainTime, PlainYearMonth. Date.toString, Date.toISOString (Simplified ISO 8601), Date.toUTCString (RFC 7231) using Date.parse and Date.toTemporalInstant

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment