|
| 1 | +# Temporal_rs release v0.1 |
| 2 | + |
| 3 | +After almost 2+ years of development, we're pleased to announce the 0.1 release |
| 4 | +of `temporal_rs`. A calendar and time zone aware Rust date/time library based on |
| 5 | +ECMAScript's Temporal API. |
| 6 | + |
| 7 | +`temporal_rs` is a highly conformant implementation of the Temporal API in Rust that can be |
| 8 | +used in native Rust code or embedded into ECMAScript engines / interpreters to support their |
| 9 | +implementations. |
| 10 | + |
| 11 | +Currently, `temporal_rs` is being used by Boa, Kiesel, V8, and Yavashark for their Temporal |
| 12 | +implementations (but more on that later). |
| 13 | + |
| 14 | +### TODO: tentative post summary |
| 15 | + |
| 16 | +To celebrate the 0.1 release of `temporal_rs`, we'll cover a short background of the Temporal |
| 17 | +implementation in Boa and why `temporal_rs` was split into it's own crate, then we'll walk |
| 18 | +through a couple brief examples of using `temporal_rs`, and finally we'll talk about the |
| 19 | +FFI and engine adoption. |
| 20 | + |
| 21 | +## Some background and history |
| 22 | + |
| 23 | +In this section, we'll reflect on the overall implementation, some general difficulties |
| 24 | +we had along with lessons learned. |
| 25 | + |
| 26 | +The temporal implementation in Boa began over two years ago and culminated in a |
| 27 | +absolutely massive PR, [#3277](https://github.com/boa-dev/boa/pull/3277) (ASIDE from |
| 28 | +nekevss: mea culpa). |
| 29 | + |
| 30 | +The PR itself stubbed out a lot of the methods, implemented some Duration and Instant |
| 31 | +functionality, and started the support for custom calendars. There were, however, 2 |
| 32 | +major take aways from this PR: first, Temporal is a massive specification update; and |
| 33 | +second, there is a lot of room to potentially optimize Temporal if we did not deal with |
| 34 | +`JsValue` directly. |
| 35 | + |
| 36 | +After a couple weeks, the question came up amongst the maintainers: could we separate |
| 37 | +the Boa's implementation off into a completely separate library? Sure, why not. |
| 38 | + |
| 39 | +The first commit of then `boa_temporal` occurred in PR [#3461](https://github.com/boa-dev/boa/pull/3461), |
| 40 | +which moved the majority of the existing functionality into a separate crate with the |
| 41 | +primary concern at the time of being able to support custom calendars, which was then |
| 42 | +ported into it's [own repository later](https://github.com/boa-dev/temporal/pull/1) a |
| 43 | +couple months later. |
| 44 | + |
| 45 | +These early versions of `temporal_rs` were vastly different than the 0.1 release version, |
| 46 | +and it can be easily seen with a short glance through the first PR. `temporal_rs` needed |
| 47 | +to support custom calendars and time zones. In order to do this, each calendar was generic |
| 48 | +on a `CalendarProtocol` trait. |
| 49 | + |
| 50 | +So to create a new date in the early versions of `temporal_rs`, you would have something |
| 51 | +like the following code: |
| 52 | + |
| 53 | +```rust |
| 54 | +use temporal_rs::{Date, Calendar, options::ArithmeticOverflow}; |
| 55 | +let date = Date::new_with_overflow( |
| 56 | + 2025, |
| 57 | + 9, |
| 58 | + 21, |
| 59 | + Calendar::<()>::from_str("iso8601").unwrap(), |
| 60 | + ArithmeticOverflow::Reject |
| 61 | +).unwrap(); |
| 62 | +``` |
| 63 | + |
| 64 | +Luckily, custom calendar and time zone were removed from the specification. In the first |
| 65 | +half of 2024, so `temporal_rs` was able to remove that support, which greatly benefitted |
| 66 | +the entire API. For instance, here's the same code in the 0.1 version of `temporal_rs`: |
| 67 | + |
| 68 | +```rust |
| 69 | +use temporal_rs::PlainDate; |
| 70 | +let date = PlainDate::try_new_iso(2025, 9, 21).unwrap(); |
| 71 | +``` |
| 72 | + |
| 73 | +That was 2024 though, we're in September of 2025, so what's happened since the crate |
| 74 | +was initially split off from Boa over a year and a half ago. Well, plenty! In early 2024 |
| 75 | +the internal algorithm for `Duration` was overhauled in the specification, so `temporal_rs` |
| 76 | +had a complete rewrite of `Duration`. |
| 77 | + |
| 78 | +The general implementation was moving along as well, but there was one final hurdle: time |
| 79 | +zone data and `ZonedDateTime`. Time zones and time zone data are a topic for a completely |
| 80 | +different blog post in the future. But suffice to say, it took a little time, and the |
| 81 | +`ZonedDateTime` implementation developed alongside the development of time zone data. |
| 82 | + |
| 83 | +That's it for our brief background on `temporal_rs`. |
| 84 | + |
| 85 | +Date and time is hard, and there is a lot that goes into it, especially when it comes to |
| 86 | +calendars and time zones. But that also doesn't mean its not interesting. |
| 87 | + |
| 88 | +## Using `temporal_rs` |
| 89 | + |
| 90 | +Let's dive into using `temporal_rs` from Rust. |
| 91 | + |
| 92 | +### Setup |
| 93 | + |
| 94 | +First, add `temporal_rs` as a dependency. |
| 95 | + |
| 96 | +```bash |
| 97 | +cargo add temporal_rs |
| 98 | +``` |
| 99 | + |
| 100 | +By default, `temporal_rs` will use a compiled time zone data provider that compiles the |
| 101 | +time zone data into the binary. There are a few options that can be used for time zone |
| 102 | +providers: |
| 103 | + |
| 104 | +- CompiledTzdbProvider (current default), a provider that parses time zone data at |
| 105 | +runtime using data compiled into the binary. |
| 106 | +- FsTzdbProvider, a provider that parses time zone data at runtime using the file system time zone |
| 107 | +database (if it exists for that OS). |
| 108 | +- ZoneInfo64TzdbProvider, a provider using ICU's zoneinfo64 resource bundle. |
| 109 | + |
| 110 | +Currently, we do not have a way to select certain provider's via feature flag, but a provider |
| 111 | +can be selected by setting `no-default-features` and importing the provider from |
| 112 | +`timezone_provider` for the API's that require time zone data. |
| 113 | + |
| 114 | +### The Temporal API |
| 115 | + |
| 116 | +The Temporal API revolves around a group of 8 date and time types, each of which corresponds |
| 117 | +to a different aspect of date and time with varying support for `Calendar`s and `TimeZone`s. |
| 118 | + |
| 119 | +| Temporal type | Category | Calendar support | Time zone support | |
| 120 | +|----------------|--------------------------------------|--------------------|--------------------| |
| 121 | +| PlainDate | Calendar date | yes | no | |
| 122 | +| PlainTime | Wall-clock time | no | no | |
| 123 | +| PlainDateTime | Calendar date and wall-clock time | yes | no | |
| 124 | +| ZonedDateTime | Calendar date and exact time | yes | yes | |
| 125 | +| Instant | Exact time | no | no | |
| 126 | +| Duration | None | no | no | |
| 127 | +| PlainYearMonth | Calendar date | yes | no | |
| 128 | +| PlainMonthDay | Calendar date | yes | no | |
| 129 | + |
| 130 | +There is also the `Now`, which provides access to the current host system time. This can then |
| 131 | +be used to map the current `Instant` to any of the above Temporal types. |
| 132 | + |
| 133 | +The types in the same categories will share similar APIs that are related to that category. For instance, |
| 134 | +all types that support a calendar date will have a `with_calendar` method as well as calendar date |
| 135 | +accessors. The exception being PlainYearMonth and PlainMonthDay which are missing their day and |
| 136 | +year, respectively ... for all intents and purposes. |
| 137 | + |
| 138 | +### Some examples |
| 139 | + |
| 140 | +You retrieve the current date using the `Temporal` type. |
| 141 | + |
| 142 | +```rust |
| 143 | +use temporal_rs::Temporal; |
| 144 | +let today = Temporal::now().to_plain_date_iso(None).unwrap() |
| 145 | + |
| 146 | +``` |
| 147 | + |
| 148 | +With the current date, we can now do a variety of operations the date. |
| 149 | + |
| 150 | +```rust |
| 151 | +use temporal_rs::{Temporal, Calendar, partial::PartialDuration, options::DifferenceSettings}; |
| 152 | +let today = Temporal::now().to_plain_date_iso(None).unwrap(); |
| 153 | + |
| 154 | +// We can add a Duration. |
| 155 | +let partial = PartialDuration::empty().with_days(1); |
| 156 | +let tomorrow = today.add(Duration::from_partial_duration(partial).unwrap()).unwrap(); |
| 157 | + |
| 158 | +// We can get the difference two dates |
| 159 | +let diff = today.since(&tomorrow, DifferenceSettings::default()).unwrap(); |
| 160 | + |
| 161 | +// We can change the calendar |
| 162 | +let tomorrow_japanese = tomorrow.with_calendar(Calendar::JAPANESE); |
| 163 | + |
| 164 | +// We can retrieve the calendar's RFC9557 string |
| 165 | +let tomorrow_string = tomorrow_japanese.to_string(); |
| 166 | +``` |
| 167 | + |
| 168 | +We can also easily deal with a `ZonedDateTime` as well. |
| 169 | + |
| 170 | +```rust |
| 171 | +use temporal_rs::{ZonedDateTime, TimeZone, Temporal}; |
| 172 | +use temporal_rs::options::{Disambiguation, OffsetDisambiguation, DifferenceSettings}; |
| 173 | + |
| 174 | +// Parse a ZonedDateTime from utf8 bytes. |
| 175 | +let zdt = ZonedDateTime::from_utf8( |
| 176 | + b"2025-03-01T11:16:10Z[America/Chicago][u-ca=iso8601]", |
| 177 | + Disambiguation::Compatible, |
| 178 | + OffsetDisambiguation::Reject, |
| 179 | +).unwrap(); |
| 180 | + |
| 181 | +// Change the time zone. |
| 182 | +let zurich_zone = TimeZone::try_from_str("Europe/Zurich").unwrap(); |
| 183 | +let zdt_zurich = zdt.with_timezone(zurich_zone).unwrap(); |
| 184 | + |
| 185 | +// Or get the current ZonedDateTime |
| 186 | +let today = Temporal::now().zoned_date_time_iso(None).unwrap(); |
| 187 | + |
| 188 | +// Difference the two `ZonedDateTime`s |
| 189 | +let diff = today.since(&zdt, DifferenceSettings::default()).unwrap(); |
| 190 | + |
| 191 | +// Change the calendar |
| 192 | +let today_coptic = today.with_calendar(Calendar::COPTIC); |
| 193 | +``` |
| 194 | + |
| 195 | +While we can extend these examples further, a more fun exercise for the reader would be taking |
| 196 | +a look at the [Temporal cookbook](https://tc39.es/proposal-temporal/docs/cookbook.html) |
| 197 | +as it displays the utility of the Temporal API using JavaScript. |
| 198 | + |
| 199 | +## FFI and engine adoption |
| 200 | + |
| 201 | +As previously stated, `temporal_rs` is used in Boa, Kiesel, and V8. There's just one thing, |
| 202 | +the latter of the two are ECMAScript implementations written in Zig and C++, respectively, |
| 203 | +not Rust. This was made possible through `temporal_rs`'s sister crate `temporal_capi`, |
| 204 | +a FFI library that provides C and C++ bindings to `temporal_rs`. |
| 205 | + |
| 206 | +The bindings are autogenerated via [Diplomat](https://github.com/rust-diplomat/diplomat), which |
| 207 | +is a project for generating FFI definitions for Rust libraries. In general, it's a really cool |
| 208 | +project and would definitely recommend checking it out if you're looking to generate FFI |
| 209 | +bindings for other languages for your Rust library. |
| 210 | + |
| 211 | +There is some added benefits to offering C and C++ bindings beyond the classic: oh, let's |
| 212 | +(re)write it in Rust. |
| 213 | + |
| 214 | +First, this allows other languages and engines to benefit from Rust's type system and memory |
| 215 | +safety guarantees without having to rewrite everything in Rust. It's a more modular and |
| 216 | +incremental approach that provides some level of flexibility. |
| 217 | + |
| 218 | +Secondly, with how large the API is, `temporal_rs` streamlines the ability to adopt the |
| 219 | +Temporal API for any current and future implementations, alongside any future update |
| 220 | +that needs to be made can be done primarily in one place and then released downstream. While |
| 221 | +it's easy to say: "just use our library" to promote adoption. Seriously, just use the |
| 222 | +library. The Temporal API is massive from an implementation perspective and the glue |
| 223 | +code plus `temporal_rs` is relatively trivial in comparison to a fresh implementation. |
| 224 | + |
| 225 | +Third, with adoption from multiple engines, `temporal_rs` benefits via further test coverage |
| 226 | +beyond the native unit tests. For instance, of the engines that offer conformance numbers |
| 227 | +(Boa, Kiesel, and V8), all of them are currently north of 95% conformance with V8 reaching |
| 228 | +the highest at around 99% conformance. The conformance difference between the engines being |
| 229 | +due to the current implementation state of other features, i.e. Boa still hasn't completed |
| 230 | +its `Intl.DateTimeFormat` implementation yet so it fails all ECMA402 `toLocaleString` tests. |
| 231 | +As a result though, we can be fairly confident in the general correctness of `temporal_rs`, |
| 232 | +and any potential bugs will ideally be found and addressed fairly quickly. |
| 233 | + |
| 234 | +In general, `temporal_rs` is a pretty good test case with reference code for setting up |
| 235 | +a Rust library over FFI with usage in a C++ and Zig codebase, so that's really cool. |
| 236 | + |
| 237 | +## Conclusion |
| 238 | + |
| 239 | +The 0.1 release of `temporal_rs` is out. We expected the general API to remain fairly stable |
| 240 | +moving forward, with any non-patch bumps being for added features. Feel free to try it |
| 241 | +out, and provide feedback / file any issues you come across. |
| 242 | + |
| 243 | +`temporal_rs` started as an interesting experiment in creating an engine agnostic library |
| 244 | +of the Temporal API that could also be usable as a date/time library in native Rust code. |
| 245 | +We've seen pretty successful results from other engines adopting the library for use. And |
| 246 | +with any luck, it will also be useful for the Rust ecosystem as a whole. |
| 247 | + |
| 248 | +## Special thanks |
| 249 | + |
| 250 | +We'd like to thank all the contributors to `temporal_rs` for helping it get to 0.1. |
| 251 | + |
| 252 | +Thanks to the University of Bergen students who helped drive some of the major conformance |
| 253 | +push earlier this year. |
| 254 | + |
| 255 | +Also, a huge thanks to all the Temporal champions for all their work on the specification |
| 256 | +as well as the ICU4X project for their incredible ongoing work on calendars and all things |
| 257 | +i18n datetime related. |
| 258 | + |
0 commit comments