temporal_rs: ECMAScript Temporal Implementation in Rust

repository·main·Indexed 42 days ago

https://github.com/boa-dev/temporal

A Rust implementation of the TC39 Temporal Builtin Proposal and ECMAScript Temporal specification. It provides robust, calendar-aware, and time-zone-aware date and time functionality through core types like PlainDate, ZonedDateTime, and Instant. The project includes the core temporal_rs library, a timezone_provider crate for managing TZif data, and temporal_capi for C/C++ FFI bindings.

Tokens
10K
Snippets
23
Records
48
Agent score
51%

What's inside temporal_rs

  1. Overview of temporal tools

    main
    The tools/ directory in the boa-dev/temporal repository contains several utility tools designed to support the development and maintenance of the temporal ecosystem. These tools handle tasks ranging from data generation to dependency verification and FFI binding generation.
  2. Overview of temporal_rs

    main
    temporal_rs is a Rust date/time library based on the ECMAScript Temporal specification. It provides support for calendrical calculations and time zones as first-class citizens. It is designed to be 100% test compliant and is used in engines like Boa, Kiesel, and V8 to implement Temporal built-ins.
  3. Available crates in the temporal project

    main

    The project is organized into several specialized crates:

    • temporal_rs: The core date/time library.
    • temporal_capi: A FFI crate providing bindings for C and C++.
    • timezone_provider: A crate for providing time zone data.
    • zoneinfo_rs: An experimental crate for compiling zoneinfo.
  4. Understand the purpose of temporal_capi

    main

    The temporal_capi crate provides the original API definitions used by Diplomat to generate FFI bindings. It is primarily used as a source for generating C++ and extern "C" APIs.

    Important Usage Warning:

    • Do not use this crate directly in Rust projects. The Rust APIs in this crate are internal definitions and are subject to change even within compatible semver versions.
    • Use language bindings for stability. If you need a stable API, use the generated C++ bindings or the extern "C" APIs. These are guaranteed to be stable within the same major semver version.
  5. When to use temporal_rs

    main

    Use temporal_rs if your project requires a calendar and time zone aware date/time API that aligns with the ECMAScript Temporal specification.

    Key use cases include:

    • Achieving web compatibility for date/time libraries.
    • Implementing the Temporal API in a JavaScript engine or other languages.
    • Handling internationalization needs for calendars other than the ISO / proleptic Gregorian calendar.
    • Using an API familiar to those who use the JavaScript Temporal built-in.
    • Projects requiring strict adherence to the ECMA-402 specification.
  6. Understand temporal_rs primitives

    main

    The library uses specific primitives to enforce invariants and handle ECMAScript compatibility:

    • FiniteF64: Provides an interface to translate between ECMAScript's number type and temporal_rs's strictly typed API.
    • IsoDate, IsoTime, IsoDateTime: Date and time primitives used to enforce specific record invariants.
    • EpochNanoseconds: A primitive for handling epoch-based time representation.
  7. How Core and Compiled implementations differ in temporal_rs

    main

    The temporal_rs library splits its Temporal builtins into two layers to serve different user needs:

    1. Core implementation: Designed for language engine implementers. It requires an explicit TimeZoneProvider (any type implementing the TimeZoneProvider trait) to be passed into methods to handle time zone data. This allows engines to source time zone data in their own preferred way.
    2. Compiled implementation: Designed for general-purpose users. It provides method wrappers around the core methods that use a default provider, removing the need for the user to manually manage time zone data. This implementation is only available when the compiled default feature flag is enabled.

    Use the Core implementation if you are building a language engine and need control over time zone data sourcing. Use the Compiled implementation for standard application development where you want a simplified API.

    // Core API requires a provider
    impl ZonedDateTime {
        pub fn day_with_provider(&self, provider: &(impl TimeZoneProvider + ?Sized)) -> TemporalResult<u8> {
            // ...
        }
    }
    
    // Compiled API (default feature) simplifies the call
    impl ZonedDateTime {
        pub fn day(&self) -> TemporalResult<u8> {
            // ...
        }
    }
  8. Comparison between temporal_rs and jiff

    main

    While jiff is inspired by Temporal, temporal_rs is designed to be a 100% compliant implementation of the Temporal specification.

    Key differences include:

    • Specification Compliance: temporal_rs is bound strictly to the specification behavior, whereas jiff may deviate from the spec for implementation convenience.
    • Time Zone Providers: temporal_rs uses a BYOP (Bring Your Own Provider) design, allowing engines to define their own time zone providers.
    • Clock Control: To support JavaScript engine integration, temporal_rs's Now does not ship with a built-in clock by default (controlled via feature flags), leaving clock implementation to the host engine.
    • Calendar Support: temporal_rs provides first-class support for all ECMA-402 defined calendars, whereas jiff primarily focuses on the ISO calendar.
  9. How time zone providers and traits work together

    main

    The timezone_provider crate uses a layered trait system to separate the concerns of identifier management and data resolution. This design prevents desynchronization between time zone identifiers (formatting) and the actual time zone data (resolution).

    Core Abstractions

    1. TimeZoneProvider: The primary interface used by temporal_rs. It acts as a high-level wrapper that combines identifier normalization and data resolution.
    2. TimeZoneNormalizer: A secondary trait responsible for fetching and formatting normalized and canonicalized time zone identifiers.
    3. TimeZoneResolver: A secondary trait responsible for resolving time zone data requests.

    Implementation Patterns

    While you can use TimeZoneNormalizer and TimeZoneResolver directly, the recommended approach is to implement these two secondary traits and then use the crate's default mechanism to create a TimeZoneProvider by mixing them together (often via a NormalizerAndResolver type). This ensures that your identifiers and data remain synchronized even as the time zone database updates.

  10. Understand the difference between 'fat' and 'slim' TZIF formats

    main

    The jiff_tzdb package (and potentially others) may use a different compiled tzif format than what is provided by the operating system.

    • Fat format (-b fat): Typically used by operating systems, containing transition times for a larger range.
    • Slim format (-b slim): The default setting for smaller embedded tzif files. It reduces file size by calculating transition times for a smaller range.

    Because the slim format covers a limited range, the system must be able to resolve the POSIX tz string to handle dates that fall outside the slim format's transition time coverage.

  11. How host system access and time zone providers work

    main

    While temporal_rs can be used in native Rust, it is designed for ECMAScript implementations. By default, it does not embed time zone data or access the host environment to remain lightweight and flexible for interpreters.

    To provide these capabilities, the library uses two main traits:

    1. HostHooks: For accessing the host system.
    2. TimeZoneProvider: For sourcing time zone data.

    Power User Pattern:

    • Any API with the suffix _with_provider allows you to supply a custom or specific TimeZoneProvider.
    • APIs without the _with_provider suffix will automatically use the default provider (if enabled via feature flags).

    If you are working without default features (e.g., in a constrained environment), you must manually provide a TimeZoneProvider to methods like time_zone_with_provider.

    use temporal_rs::{Instant, now::Now, host::EmptyHostSystem};
    use timezone_provider::tzif::CompiledTzdbProvider;
    
    let provider = CompiledTzdbProvider::default();
    
    // The empty host system is a system implementation HostHooks that always
    // returns the UNIX_EPOCH and the "+00:00" time zone.
    let now = Now::new(EmptyHostSystem);
    let time_zone = now.time_zone_with_provider(&provider).unwrap();
    assert_eq!(time_zone.identifier_with_provider(&provider).unwrap(), "+00:00");
    let now = Now::new(EmptyHostSystem);
    assert_eq!(now.instant(), Instant::try_new(0));
  12. Accessing date values with and without Context

    main

    The API distinguishes between standard operations and operations that require a Context (often needed by engine implementors to interact with the host environment, such as a JavaScript engine).

    To prevent the API from becoming cumbersome for non-engine users, functions that require a Context argument are explicitly labeled with a _with_context suffix. This allows standard users to call simple methods without managing context, while providing engine implementors a clear path to access necessary environmental data.

    // Standard usage for non-engine consumers
    let year_1 = Date<()>.year();
    
    // Usage for engine implementors requiring context
    let year_2 = Date<()>.year_with_context(&mut ());