temporal-polyfill

repository·main·Indexed 20 days ago

https://github.com/fullcalendar/temporal-polyfill

A spec-compliant polyfill for the JavaScript Temporal API. It provides multiple entrypoints including a global polyfill, a side-effect-free ponyfill, and a tree-shakeable function API for minimal bundle sizes. The monorepo also includes temporal-polyfill-codemod for migrating from the functional API to idiomatic Temporal objects, temporal-utils for convenience helpers like startOfMonth and endOfDay, and temporal-spec for standalone TypeScript type definitions.

Tokens
48.6K
Snippets
192
Records
227
Agent score
72%

What's inside temporal-polyfill

  1. What is included in temporal-spec

    main

    The package covers the full surface of the Temporal proposal:

    • Temporal namespace: Includes Instant, ZonedDateTime, PlainDate, PlainTime, PlainDateTime, PlainYearMonth, PlainMonthDay, Duration, Now, and all supporting *Like / options types.
    • Intl.DateTimeFormat augmentations: Enables format, formatToParts, formatRange, and formatRangeToParts to accept Temporal objects.
    • Date.prototype.toTemporalInstant: Provides a way to bridge legacy Date values into Temporal (exposed as a standalone toTemporalInstant function from the namespaced entry point).
  2. Understand the Calendar Record shape

    main

    In the tree-shakeable API, a Record is an opaque, branded handle used instead of a raw calendar ID string. It allows the API to remain modular and tree-shakeable while providing the necessary calendar behavior to date operations.

    Key characteristics:

    • It has no public calendar fields.
    • It is memoized.
    • toJSON() and valueOf() both return the exact calendar identifier string used to create the record.
    • In the standard Temporal API, this Record corresponds to the calendar identifier string (e.g., 'gregory').
    type Record = {
      toJSON(): string
      valueOf(): string
    }
  3. Understand the PlainYearMonth Record shape

    main

    A PlainYearMonth record is a plain object representing a year and a month. It follows this structure:

    type Record = {
      readonly calendarId: string
      readonly era: string | undefined
      readonly eraYear: number | undefined
      readonly year: number
      readonly month: number
      readonly monthCode: string
      toJSON(): string
      valueOf(): never
    }
  4. Understand the PlainDate Record Shape

    main

    The tree-shakeable API operates on Record objects. A PlainDate record represents a date without a time or time zone.

    Record Structure:

    • calendarId: string
    • era: string | undefined
    • eraYear: number | undefined
    • year: number
    • month: number
    • monthCode: string
    • day: number
    • toJSON(): returns a string representation
    • valueOf(): returns never
    type Record = {
      readonly calendarId: string
      readonly era: string | undefined
      readonly eraYear: number | undefined
      readonly year: number
      readonly month: number
      readonly monthCode: string
      readonly day: number
      toJSON(): string
      valueOf(): never
    }
  5. Understand the PlainTime Record shape

    main

    A PlainTime record in the tree-shakeable API is a plain object representing a time of day. It contains the following read-only numeric fields:

    • hour
    • minute
    • second
    • millisecond
    • microsecond
    • nanosecond

    It also includes toJSON() and valueOf() methods.

    type Record = {
      readonly hour: number
      readonly minute: number
      readonly second: number
      readonly millisecond: number
      readonly microsecond: number
      readonly nanosecond: number
      toJSON(): string
      valueOf(): never
    }
  6. Understand non-standard functions in the tree-shakeable API

    main

    The tree-shakeable API includes several non-standard functions that do not have a direct Temporal.* counterpart. These are categorized into three groups:

    1. Narrowed forms of standard methods: Functions like addDays (which is a restricted version of .add({ days })) or toBasicString (a restricted .toString()). These allow bundlers to use simpler, cheaper internal paths.
    2. Gaps the standard API doesn't fill: Operations missing from the Temporal spec, such as startOfYear on a ZonedDateTime, endOfMonth on a PlainDate, or roundToWeek.
    3. Compressed multi-step operations: Helpers that perform complex logic in one call, such as diffYears(a, b), which returns a floating-point total.

    Migration Path: When you transition to native Temporal using temporal-polyfill-codemod, standard functions map to native methods, while non-standard functions map to the temporal-utils package, which provides the same semantics for real Temporal objects.

  7. Understand the Duration Record shape

    main

    A Duration record is a plain object representing a span of time. It contains the following read-only numeric fields:

    • years, months, weeks, days
    • hours, minutes, seconds, milliseconds, microseconds, nanoseconds

    It also includes toJSON() and valueOf() methods.

    type Record = {
      readonly years: number
      readonly months: number
      readonly weeks: number
      readonly days: number
      readonly hours: number
      readonly minutes: number
      readonly seconds: number
      readonly milliseconds: number
      readonly microseconds: number
      readonly nanoseconds: number
      toJSON(): string
      valueOf(): never
    }
  8. What the fns-to-temporal transform migrates

    main

    The fns-to-temporal transform converts several patterns from the temporal-polyfill/fns API to standard Temporal syntax:

    Constructors and Methods

    Function calls are converted into constructors or instance methods:

    // Before
    import * as PlainDateFns from 'temporal-polyfill/fns/PlainDate'
    const date = PlainDateFns.create(2024, 5, 1)
    const next = PlainDateFns.addDays(date, 3)
    
    // After
    const date = new Temporal.PlainDate(2024, 5, 1)
    const next = date.add({ days: 3 })

    Calendar Records to IDs

    Calendar record objects are converted to string IDs where Temporal expects a string:

    // Before
    PlainDateFns.create(2024, 5, 1, CalendarFns.getBuddhist())
    // After
    new Temporal.PlainDate(2024, 5, 1, 'buddhist')

    Types and Type Guards

    • Types: Record and option types are rewritten to their Temporal equivalents (e.g., PlainDateRecord becomes Temporal.PlainDate). If no direct equivalent exists, it falls back to temporal-utils.
    • Type Guards: isRecord checks are converted to instanceof checks:
    // Before
    if (PlainDateFns.isRecord(value)) { /* ... */ }
    // After
    if (value instanceof Temporal.PlainDate) { /* ... */ }

    Note on temporal-utils

    If a function has no direct Temporal equivalent, the codemod rewrites it to use temporal-utils. The codemod will print a note for you to add this dependency to your package.json, but it will not edit your package.json automatically.

  9. Understand `fns-to-temporal` migration logic

    main

    The goal of the fns-to-temporal codemod is to replace tree-shakeable API records (which are incompatible with real Temporal objects) with direct calls to the global Temporal object.

    Transformation Strategy

    • Direct Rewrites: Simple, semantically equivalent calls are rewritten directly to the global Temporal API (e.g., Temporal.PlainDate.from()).
    • temporal-utils fallback: If a helper has no direct Temporal equivalent or if the arguments cannot be statically normalized (e.g., they are not object/string literals), the codemod rewrites the call to use temporal-utils.
    • Unsafe Cases: If the codemod cannot prove a usage is safe to rewrite, it leaves the code unchanged and emits a diagnostic. Examples of unsafe cases include:
      • Assigning a function to a variable: const fn = PlainDateFns.addDays
      • Dynamic property access: PlainDateFns[name](date, 3)
      • Destructuring: const { create } = PlainDateFns
      • Passing functions as predicates: values.filter(PlainDateFns.isRecord)

    Important: Manual Step for temporal-utils

    If the codemod introduces imports from temporal-utils, you must manually install the package in your affected project(s). The codemod will print a summary notification if this occurs.

  10. Understand the PlainDateTime Record Shape

    main

    The tree-shakeable API operates on a Record object rather than a class instance. This object represents a PlainDateTime and contains the following properties:

    • calendarId: string
    • era: string | undefined
    • eraYear: number | undefined
    • year: number
    • month: number
    • monthCode: string
    • day: number
    • hour: number
    • minute: number
    • second: number
    • millisecond: number
    • microsecond: number
    • nanosecond: number
    • toJSON(): returns a string
    • valueOf(): returns never
    type Record = {
      readonly calendarId: string
      readonly era: string | undefined
      readonly eraYear: number | undefined
      readonly year: number
      readonly month: number
      readonly monthCode: string
      readonly day: number
      readonly hour: number
      readonly minute: number
      readonly second: number
      readonly millisecond: number
      readonly microsecond: number
      readonly nanosecond: number
      toJSON(): string
      valueOf(): never
    }
  11. Round dates to calendar boundaries with rounding helpers

    main

    temporal-utils provides rounding helpers to return the closest start of a named calendar unit. These are useful because native Temporal .round() APIs do not cover every large calendar unit (like years or months) in a uniform way across all date-like types.

    Usage Guidelines:

    • Default Mode: If options or options.roundingMode is omitted, it defaults to 'halfExpand'.
    • Shorthand: You can pass a RoundingMode string directly as a shorthand for { roundingMode }.
    • Constraints: RoundingMathOptions must not include smallestUnit (the helper name defines the unit). For year, month, and week helpers, roundingIncrement must be omitted or set to 1.
    • Use these for manual code: roundToYear, roundToMonth, and roundToWeek.
    • Avoid these in manual code: roundToDay, roundToHour, roundToMinute, roundToSecond, roundToMillisecond, and roundToMicrosecond. These are exported primarily as codemod targets. For these units, use the native Temporal .round() method instead.
    import { roundToMonth, roundToWeek, roundToYear } from 'temporal-utils'
    
    const dateTime = Temporal.PlainDateTime.from('2024-07-20T12:30:00')
    
    roundToYear(dateTime).toString()
    // '2025-01-01T00:00:00'
    
    roundToMonth(dateTime, 'floor').toString()
    // '2024-07-01T00:00:00'
    
    roundToWeek(dateTime, { roundingMode: 'floor' }).toString()
    // '2024-07-15T00:00:00'