Temporal_rs is here! The datetime library powering Temporal in Boa, Kiesel, and V8
After 2 years of development, we're pleased to announce the
release of temporal_rs
. A calendar and time zone aware Rust date/time
library based on ECMAScript's Temporal API.
temporal_rs
is a highly conformant implementation of the Temporal API
in Rust that can be used in native Rust code or embedded into ECMAScript
engines / interpreters to support their implementations, which we first
announced in our
Temporal blog post, if you're
interested in learning more about small implementation details.
Currently, temporal_rs
v0.1 is being used by Boa, Kiesel,
V8, and Yavashark for their Temporal
implementations (more on that later) and is estimated to land unflagged in Chromium v144.
Why v0.1? Why not v1.0?
Right now the Temporal proposal is at Stage 3 (4 being the final stage) in the standards process. Although unlikely, we want to remain on a minor version to catch any changes which come in (mostly bug fixes) before the proposal reaches stage 4 and thus complete. We expect that to happen in Q1 2026.
Fear not! temporal_rs passes over 4000 specification tests, is stable and ready to use.
To celebrate the release of temporal_rs
, we'll cover a short
background of the Temporal implementation in Boa and why temporal_rs
was split into its own crate, we'll go over the library's general
design, and then we'll walk through a couple brief examples of using
temporal_rs
before finally talking about the FFI and engine adoption.
Some background and history
"Temporal is the single biggest addition to ECMAScript since ES6"
Jason Williams, TC39 Delegate
In this section, we'll reflect on the overall implementation, some general difficulties we had along with lessons learned.
The temporal implementation in Boa began over two years ago and culminated in an absolutely massive PR, #3277.
The PR itself stubbed out a lot of the methods, implemented some Duration and Instant functionality, and started the support for custom calendars. There were, however, 2 major takeaways from this PR:
- Temporal is a massive specification update, it's the single biggest addition to ECMAScript since ES6.
- There is a lot of room to potentially optimize Temporal if we do not deal with
JsValue
directly.
After a couple weeks, the question came up amongst maintainers of not just Boa, but also other engines: "could we separate the datetime logic and implementation off into a completely separate library?" Sure.
The first commit of then boa_temporal
occurred in PR
#3461, which moved the
majority of the existing functionality into a separate crate with the
primary concern at the time of being able to support custom calendars,
which was then ported into its
own repository later a
couple months later.
These early versions of temporal_rs
were vastly different than the 0.1
release version, and it can be easily seen with a short glance through
the first PR. temporal_rs
needed to support custom calendars and time
zones. In order to do this, each calendar was generic on a
CalendarProtocol
trait.
So to create a new date in the early versions of temporal_rs
, you
would have something like the following code:
use temporal_rs::{Date, Calendar, options::ArithmeticOverflow};
let date = Date::new_with_overflow(
2025,
9,
21,
Calendar::<()>::from_str("iso8601").unwrap(),
ArithmeticOverflow::Reject
).unwrap();
Luckily, custom calendars and time zones were removed from the
specification in the first half of 2024, so temporal_rs
was able to
remove that support, which greatly benefited the entire API. For
instance, here's the same code in the 0.1 version of temporal_rs
:
use temporal_rs::PlainDate;
let date = PlainDate::try_new_iso(2025, 9, 21).unwrap();
That was 2024 though, we're in September of 2025, so what's happened since the crate was initially split off from Boa over a year and a half ago? Well, plenty!
- In early 2024 the internal algorithm for
Duration
was overhauled in the specification, sotemporal_rs
had a complete rewrite ofDuration
. Duration
moved from usingf64
internally toFiniteF64
, and then to non-floating-point integers.- We moved from a large
TemporalFields
type to "Partial" objects, which better represent JavaScript property bags. - A good portion of missing method implementations were added as well.
- Internal utility methods were moved to the Neri-Schneider algorithms.
In general, the implementation was moving along at a pretty decent pace,
and would continue to do so well into roughly April of 2025 (mostly
helped along by a group of students from the University of Bergen who
began helping with the implementation in January 2025), but there were
two final hurdles: time zone data and ZonedDateTime
.
Time zones and time zone data are a topic for a completely different
blog post in the future. But suffice to say, it took a little bit of
time, and ZonedDateTime
was developed alongside the time zone data.
This work began in November 2024, by stubbing out the general support of
time zone data sourcing and ZonedDateTime
. Then, after almost 10
months of general work, the last major updates to time zone data
sourcing were merged at the beginning of September in PR
#537 and
#538. As a result, we
were finally able to stabilize temporal_rs
's API for a 0.1 release.
That's it for our brief background on temporal_rs
.
Date and time is hard, and there is a lot that goes into it, especially when it comes to calendars and time zones. But that's what makes it interesting!
Temporal API overview
The Temporal API focuses on a group of 8 date and time types, each of
which corresponds to a different aspect of date and time with varying
support for calendars and time zones, which are, unsurprisingly,
represented in temporal_rs
by the Calendar
and TimeZone
types.
Temporal type | Category | Calendar support | Time zone support |
---|---|---|---|
PlainDate | Calendar date | yes | no |
PlainTime | Wall-clock time | no | no |
PlainDateTime | Calendar date and wall-clock time | yes | no |
ZonedDateTime | Calendar date and exact time | yes | yes |
Instant | Exact time | no | no |
Duration | Time span | no | no |
PlainYearMonth | Calendar date | yes | no |
PlainMonthDay | Calendar date | yes | no |
There is also Now
, which provides access to the current host system
time. This can then be used to map the current Instant
to any of the
above Temporal types.
The types in the same categories will share similar APIs that are
related to that category. For instance, all types that support a
calendar date will have a with_calendar
method as well as calendar
date accessors. The exception being PlainYearMonth
and PlainMonthDay
which are missing their day and year, respectively ... for all intents
and purposes.
For a full view of the API, we recommend checking our documentation.
temporal_rs
design overview
temporal_rs
in general implements large portions of the specification
directly in the codebase. However, it does still have some dependencies,
which can be broken down into 4 main groups.
- Time zone data, for sourcing time zone data
- Calendrical calculations, for handling non-ISO calendar calculations
- RFC9557 parsing, for parsing of RFC9557's internet extended date/time format (IXDTF)
- Utilities
Notably, the dependencies that are highlighted in purple come from ICU4X. ICU4X is a phenomenal Rust project that takes a new approach to Unicode's ICU in order to make a new, more modular version of ICU.
While ICU4X provides the majority of the internationalization (i18n),
Unicode, and formatting focused functionality, temporal_rs
builds on
top of ICU4X to provide an ECMAScript compliant date/time API for both
native Rust and ECMAScript implementers.
Time zone data
While we plan to go into time zones in a completely separate post, one
of temporal_rs
's primary design decisions was to offer a way to
customize the source of time zone data, while also having an optional
default source for convenience. The time zone data sourcing
functionality is provided by timezone_provider
, a sister crate of
temporal_rs
that provides a project agnostic crate alongside default
trait implementations for sourcing time zone data.
We currently expose three types of provider implementations:
CompiledTzdbProvider
(current default), a provider that parses time zone data at runtime using data compiled into the binary.FsTzdbProvider
, a provider that parses time zone data at runtime using the file system time zone database (if it exists for that OS).ZoneInfo64TzdbProvider
, a provider using ICU's zoneinfo64 resource bundle.
We hope to have a zero copy compiled timezone provider available in the near future.
Using temporal_rs
Let's dive into using temporal_rs
from Rust.
Setup
First, add temporal_rs
as a dependency to your project using cargo:
cargo add temporal_rs
Or include the below in your project's Cargo.toml
.
temporal_rs = "0.1.0"
By default, temporal_rs
will use a compiled time zone data provider
that compiles the time zone data into the binary. If you prefer to use
the file system time zone database or a zoneinfo64 resource bundle, you
can disable the compiled time zone data by setting
default-features = false
; you can import your preferred provider from
the timezone_provider
crate, then provide it to any API that requires
a time zone provider.
For instance, to use the FsTzdbProvider
, your Cargo.toml
would look
like the following.
timezone_provider = { version = "0.0.17", features = ["tzif"] }
temporal_rs = { version = "0.1.0", default-features = false, features = ["sys"]}
The sys
feature for temporal_rs
enables the default implementation
for Now
, and the tzif
feature for timezone_provider
enables the
FsTzdbProvider
.
Please note: timezone_provider
is still considered unstable for the
near future.
Some examples
The below examples will be using temporal_rs
with the default
features.
Retrieve today's date
use temporal_rs::Temporal;
// We can easily retrieve today's date using `Temporal::now()`
let today = Temporal::now().plain_date_iso(None).unwrap()
Date operations available
Temporal provides a nice API for working with date and date/time via
PlainDate
and PlainDateTime
.
use std::convert::TryFrom;
use temporal_rs::{Calendar, Temporal, options::DifferenceSettings, partial::PartialDuration};
// We can get today's date
let today = Temporal::now().plain_date_iso(None).unwrap();
// We can also add a Duration.
let partial = PartialDuration::empty().with_days(1);
let tomorrow = today.add(&partial.try_into().unwrap(), None).unwrap();
// We can get the difference between two dates
let diff = today
.since(&tomorrow, DifferenceSettings::default())
.unwrap();
// We can change the calendar
let tomorrow_japanese = tomorrow.with_calendar(Calendar::JAPANESE);
// We can retrieve the calendar's RFC9557 string
println!("{tomorrow_japanese}"); // 2025-09-24[u-ca=japanese]
Working with dates and time zones
You can also easily work with dates and time zones with the
ZonedDateTime
type.
use temporal_rs::options::{DifferenceSettings, Disambiguation, OffsetDisambiguation, Unit};
use temporal_rs::{Calendar, Temporal, TimeZone, ZonedDateTime};
// We can parse a ZonedDateTime from utf8 bytes.
let zdt = ZonedDateTime::from_utf8(
b"2025-03-01T11:16:10Z[America/Chicago][u-ca=iso8601]",
Disambiguation::Compatible,
OffsetDisambiguation::Reject,
)
.unwrap();
// We can get the current ZonedDateTime
let today = Temporal::now().zoned_date_time_iso(None).unwrap();
// And we can easily get the difference between two `ZonedDateTime`s
let mut options = DifferenceSettings::default();
options.largest_unit = Some(Unit::Year);
let diff = today.since(&zdt, options).unwrap();
println!("{diff}"); // P6M23D
// We can change the calendar for the `ZonedDateTime`
let today_coptic = today.with_calendar(Calendar::COPTIC);
println!("{today_coptic}"); // 2025-09-24T10:36:56.914365368-05:00[America/Chicago][u-ca=coptic]
// We can also easily convert it into just the date.
let today_plain_date_coptic = today_coptic.to_plain_date();
println!("{today_coptic}"); // 2025-09-24[u-ca=coptic]
While we can extend these examples further, a more fun exercise for the reader would be to take a look at the Temporal cookbook, as it displays the utility of the Temporal API using JavaScript and all of these examples are now usable from Rust as well.
FFI and engine adoption
As previously stated, temporal_rs
is used in Boa, Kiesel, and V8.
There's just one thing, the latter of the two are ECMAScript
implementations written in Zig and C++, respectively, not Rust. This was
made possible through temporal_rs
's FFI crate temporal_capi
, which
provides C and C++ bindings to temporal_rs
.
The bindings are autogenerated via Diplomat, which is a project for generating FFI definitions for Rust libraries. In general, it's a really cool project and we would definitely recommend checking it out if you're looking to generate FFI bindings for other languages for your Rust library.
"Diplomat made the FFI code extremely easy: I basically wrote the entire temporal_capi FFI layer over the course of a couple PRs, each of which probably took me ~15 minutes each of relatively mindless "tab through docs, add API" work. Diplomat is really good at this type of thing."
Manishearth Rust Reddit Q&A:
There is some added benefits to offering C and C++ bindings beyond the classic: oh, let's (re)write it in Rust.
First, this approach allows other languages and engines to benefit from Rust's type system and memory safety guarantees without having to rewrite everything in Rust. It's a more modular and incremental approach that provides some level of flexibility.
Secondly, with how large the API is, temporal_rs
streamlines the
ability to adopt the Temporal API for any current and future
implementations, since any future updates can be done primarily in one
place and then released downstream. temporal_rs
plus the engine
specific integration code, while not a small amount of code, is a
relatively trivial amount of work in comparison to a from scratch
implementation.
Third, with adoption from multiple engines, temporal_rs
benefits via
external test coverage beyond the native Rust unit tests. For instance,
looking at the engines that offer conformance numbers (Boa, Kiesel, and
V8), each implementation is currently north of 95% conformance with V8
reaching the highest at around 99% conformance. There is still a small
disparity in conformance, but this can be explained by the absence of
some related features, i.e. Boa still hasn't completed its
Intl.DateTimeFormat
implementation yet, so it fails all ECMA402
toLocaleString
tests. Nonetheless, we can still be fairly confident in
the general correctness of temporal_rs
, and any potential bugs will
ideally be found and addressed fairly quickly.
In general, temporal_rs
is a pretty good reference case for setting up
a Rust library over FFI, being used in both a C++ and Zig codebase.
Conclusion
The 0.1 release of temporal_rs
is out. We expect the general API to
remain fairly stable moving forward, with any non-patch bumps being for
added features. Feel free to try it out, and provide feedback / file any
issues you come across. Although, we will make changes and semantic
versioning bumps based on feedback or the Temporal specification.
Our current plan is to have any remaining issues addressed and the API fully stable, in preparation for the "stabilization" of Temporal and its subsequent introduction to the ECMAScript specification. Once Temporal is "stabilized", we will move forward with a 1.0 release.
temporal_rs
started as an interesting experiment in creating an engine
agnostic library of the Temporal API that could also be usable as a
date/time library in native Rust code, but seeing the wide adoption
we've been getting from other engines, we can say that this project has
been a great success! And with any luck, we hope this library will find
its place in the Rust ecosystem as well.
Special thanks
We'd like to thank all the contributors to temporal_rs
for helping
it get to v0.1.
Thanks to the University of Bergen students who helped drive some of the major conformance push earlier this year.
Also, a huge thanks to all the Temporal champions for all their work on the specification, as well as the ICU4X project for their incredible ongoing work on calendars and all things i18n related.
Discussion
- Hackernews: https://news.ycombinator.com/item?id=45361826
- Reddit: https://www.reddit.com/r/rust/comments/1npffdx/temporal_rs_is_here_the_datetime_library_powering/
How can you support Boa?
Boa is an independent JavaScript engine implementing the ECMAScript specification, and we rely on the support of the community to keep it going. If you want to support us, you can do so by donating to our open collective. Proceeeds here go towards this very website, the domain name, and remunerating members of the team who have worked on the features released.
If financial contribution is not your strength, you can contribute by asking to be assigned to one of our open issues, and asking for mentoring if you don't know your way around the engine. Our contribution guide should help you here. If you are more used to working with JavaScript or frontend web development, we also welcome help to improve our web presence, either in our website, or in our testing representation page or benchmarks page. You can also contribute to our Criterion benchmark comparison GitHub action.
We are also looking to improve the documentation of the engine, both for developers of the engine itself and for users of the engine. Feel free to contact us in Matrix.