@@ -10,72 +10,238 @@ crate has been externalized and is being used in other engines such as [V8](http
10
10
For more information on ` temporal_rs ` 's general position in the Rust
11
11
date/time library ecoystem, see our [ FAQ] ( ./docs/FAQ.md ) .
12
12
13
- ## Example usage
14
13
15
- Below are a few examples to give an overview of ` temporal_rs ` 's current
16
- API .
14
+ Temporal is an API for working with date and time in a calendar
15
+ and time zone aware manner .
17
16
18
- ### Convert from an ISO8601 ` PlainDate ` into a Japanese ` PlainDate ` .
17
+ temporal_rs is designed with ECMAScript implementations and general
18
+ purpose Rust usage in mind, meaning that temporal_rs can be used to implement
19
+ the Temporal built-ins in an ECMAScript implementation or generally
20
+ used as a date and time library in a Rust project.
21
+
22
+ temporal_rs is the primary library for the Temporal API implementation in Boa, Kiesel,
23
+ and V8. Each of these engines pass the large ECMAScript conformance test suite for
24
+ the specification.
25
+
26
+ ## Why use temporal_rs?
27
+
28
+ As previously mentioned, Temporal is an API for working with date and time in
29
+ a calendar and time zone aware manner. This means that calendar and time zone support
30
+ are first class in Temporal as well as in temporal_rs.
31
+
32
+ For instance, converting between calendars is as simple as providing the calendar as
33
+ shown below.
19
34
20
35
``` rust
21
36
use temporal_rs :: {PlainDate , Calendar };
22
37
use tinystr :: tinystr;
23
38
use core :: str :: FromStr ;
39
+
24
40
// Create a date with an ISO calendar
25
41
let iso8601_date = PlainDate :: try_new_iso (2025 , 3 , 3 ). unwrap ();
42
+
26
43
// Create a new date with the japanese calendar
27
- let japanese_date = iso8601_date . with_calendar (Calendar :: from_str ( " japanese " ) . unwrap ()) . unwrap ( );
44
+ let japanese_date = iso8601_date . with_calendar (Calendar :: JAPANESE );
28
45
assert_eq! (japanese_date . era (), Some (tinystr! (16 , " reiwa" )));
29
46
assert_eq! (japanese_date . era_year (), Some (7 ));
30
47
assert_eq! (japanese_date . month (), 3 )
31
48
```
32
49
33
- ### Create a ` PlainDateTime ` from a RFC9557 IXDTF string.
50
+ Beyond the general calendar use case, temporal_rs has robust support for
51
+ time zones which can generally by applied to a ` PlainDate ` via [ ` ZonedDateTime ` ] .
34
52
35
- For more information on the Internet Extended DateTime Format (IXDTF),
36
- see [ RFC9557 ] ( https://www.rfc-editor.org/rfc/rfc9557.txt ) .
53
+ ** Important Note: ** The below API is enabled with the
54
+ ` compiled_data ` feature flag .
37
55
38
56
``` rust
39
- use temporal_rs :: PlainDateTime ;
40
- use core :: str :: FromStr ;
57
+ use temporal_rs :: {ZonedDateTime , TimeZone };
58
+ use temporal_rs :: options :: {Disambiguation , OffsetDisambiguation };
59
+
60
+ let zdt = ZonedDateTime :: from_utf8 (
61
+ b " 2025-03-01T11:16:10Z[America/Chicago][u-ca=iso8601]" ,
62
+ Disambiguation :: Compatible ,
63
+ OffsetDisambiguation :: Reject
64
+ ). unwrap ();
65
+ assert_eq! (zdt . year (), 2025 );
66
+ assert_eq! (zdt . month (), 3 );
67
+ assert_eq! (zdt . day (), 1 );
68
+ // Using Z and a timezone projects a UTC datetime into the timezone.
69
+ assert_eq! (zdt . hour (), 5 );
70
+ assert_eq! (zdt . minute (), 16 );
71
+ assert_eq! (zdt . second (), 10 );
72
+
73
+ // You can also update a time zone easily.
74
+ let zurich_zone = TimeZone :: try_from_str (" Europe/Zurich" ). unwrap ();
75
+ let zdt_zurich = zdt . with_timezone (zurich_zone ). unwrap ();
76
+ assert_eq! (zdt_zurich . year (), 2025 );
77
+ assert_eq! (zdt_zurich . month (), 3 );
78
+ assert_eq! (zdt_zurich . day (), 1 );
79
+ assert_eq! (zdt_zurich . hour (), 12 );
80
+ assert_eq! (zdt_zurich . minute (), 16 );
81
+ assert_eq! (zdt_zurich . second (), 10 );
41
82
42
- let pdt = PlainDateTime :: from_str (" 2025-03-01T11:16:10[u-ca=gregory]" ). unwrap ();
43
- assert_eq! (pdt . calendar (). identifier (), " gregory" );
44
- assert_eq! (pdt . year (), 2025 );
45
- assert_eq! (pdt . month (), 3 );
46
- assert_eq! (pdt . day (), 1 );
47
- assert_eq! (pdt . hour (), 11 );
48
- assert_eq! (pdt . minute (), 16 );
49
- assert_eq! (pdt . second (), 10 );
50
83
```
51
84
52
- ### Create a ` ZonedDateTime ` for a RFC9557 IXDTF string.
85
+ ## Overview
86
+
87
+ temporal_rs provides 8 core types for working with date and time. The core types are:
53
88
54
- ** Important Note:** The below API is enabled with the ` compiled_data `
55
- feature flag.
89
+ - [ PlainDate]
90
+ - [ PlainTime]
91
+ - [ PlainDateTime]
92
+ - [ ZonedDateTime]
93
+ - [ Instant]
94
+ - [ Duration]
95
+ - [ PlainYearMonth]
96
+ - [ PlainMonthDay]
97
+
98
+ In addition to these types, there are the [ ` Calendar ` ] and [ ` TimeZone ` ] type that
99
+ support the calendars or time zones. The specific support for calendars and time
100
+ zones per type are as follows.
101
+
102
+ | Temporal type | Category | Calendar support | Time zone support |
103
+ | ----------------| --------------------------------------| --------------------| --------------------|
104
+ | PlainDate | Calendar date | yes | no |
105
+ | PlainTime | Wall-clock time | no | no |
106
+ | PlainDateTime | Calendar date and wall-clock time | yes | no |
107
+ | ZonedDateTime | Calendar date and exact time | yes | yes |
108
+ | Instant | Exact time | no | no |
109
+ | Duration | None | no | no |
110
+ | PlainYearMonth | Calendar date | yes | no |
111
+ | PlainMonthDay | Calendar date | yes | no |
112
+
113
+ There is also the [ ` Now ` ] [ now::Now ] , which provides access to the current host system
114
+ time. This can then be used to map to any of the above Temporal types.
115
+
116
+ ** Important Note:** the below example is only available with the ` sys ` and
117
+ ` compiled_data ` feature flag enabled.
56
118
57
119
``` rust
58
- use temporal_rs :: {ZonedDateTime , TimeZone };
59
- use temporal_rs :: options :: {Disambiguation , OffsetDisambiguation };
120
+ use core :: cmp :: Ordering ;
121
+ use temporal_rs :: {Temporal , Calendar , ZonedDateTime };
122
+ let current_instant = Temporal :: now (). instant (). unwrap ();
123
+ let current_zoned_date_time = Temporal :: now (). zoned_date_time_iso (None ). unwrap ();
124
+
125
+ /// Create a `ZonedDateTime` from the requested instant.
126
+ let zoned_date_time_from_instant = ZonedDateTime :: try_new (
127
+ current_instant . as_i128 (),
128
+ * current_zoned_date_time . time_zone (),
129
+ Calendar :: ISO ,
130
+ ). unwrap ();
131
+
132
+ // The two `ZonedDateTime` will be equal down to the second.
133
+ assert_eq! (current_zoned_date_time . year (), zoned_date_time_from_instant . year ());
134
+ assert_eq! (current_zoned_date_time . month (), zoned_date_time_from_instant . month ());
135
+ assert_eq! (current_zoned_date_time . day (), zoned_date_time_from_instant . day ());
136
+ assert_eq! (current_zoned_date_time . hour (), zoned_date_time_from_instant . hour ());
137
+ assert_eq! (current_zoned_date_time . minute (), zoned_date_time_from_instant . minute ());
138
+ assert_eq! (current_zoned_date_time . second (), zoned_date_time_from_instant . second ());
139
+
140
+ // The `Instant` reading that occurred first will be less than the ZonedDateTime
141
+ // reading
142
+ assert_eq! (
143
+ zoned_date_time_from_instant . compare_instant (& current_zoned_date_time ),
144
+ Ordering :: Less
145
+ );
146
+ ```
60
147
61
- let zdt = ZonedDateTime :: from_utf8 (b " 2025-03-01T11:16:10Z[America/Chicago][u-ca=iso8601]" , Disambiguation :: Compatible , OffsetDisambiguation :: Reject ). unwrap ();
62
- assert_eq! (zdt . year (). unwrap (), 2025 );
63
- assert_eq! (zdt . month (). unwrap (), 3 );
64
- assert_eq! (zdt . day (). unwrap (), 1 );
65
- assert_eq! (zdt . hour (). unwrap (), 11 );
66
- assert_eq! (zdt . minute (). unwrap (), 16 );
67
- assert_eq! (zdt . second (). unwrap (), 10 );
148
+ ## General design
68
149
69
- let zurich_zone = TimeZone :: try_from_str (" Europe/Zurich" ). unwrap ();
70
- let zdt_zurich = zdt . with_timezone (zurich_zone ). unwrap ();
71
- assert_eq! (zdt_zurich . year (). unwrap (), 2025 );
72
- assert_eq! (zdt_zurich . month (). unwrap (), 3 );
73
- assert_eq! (zdt_zurich . day (). unwrap (), 1 );
74
- assert_eq! (zdt_zurich . hour (). unwrap (), 18 );
75
- assert_eq! (zdt_zurich . minute (). unwrap (), 16 );
76
- assert_eq! (zdt_zurich . second (). unwrap (), 10 );
150
+ While temporal_rs can be used in native Rust programs, the library is -- first and
151
+ foremost -- designed for use in ECMAScript implementations. This is not to detract
152
+ from temporal_rs's use in a native Rust program, but it is important information to
153
+ understand in order to understand the library's architecture and general API design.
154
+
155
+ Without default feature flags, temporal_rs does not have with access to the host
156
+ environment and it does not embed any time zone data. This is important from an
157
+ interpreter perspective, because access to the host environment and time zone data
158
+ comes from the interpreter's agent, not from a dependency.
159
+
160
+ Instead, temporal_rs provides the [ ` HostHooks ` ] [ host::HostHooks ] and [ ` TimeZoneProvider ` ] [ provider::TimeZoneProvider ]
161
+ traits that can be implemented and provided as function arguments that temporal_rs will
162
+ use to access the host system or time zone data. temporal_rs also provides some baseline
163
+ implementations of the traits that can be selected from depending on application needs.
164
+
165
+ That being said, this does not mean that everyone must implement their own trait
166
+ implementations for that functionality to exist, but the APIs are there for power
167
+ users who may need a custom host system or time zone data implementation.
168
+
169
+ A default host system and time zone provider have been implemented and are automatically
170
+ active as default features.
171
+
172
+ ### A quick walkthrough
173
+
174
+ For instance, the examples thus far have been using the general usage Rust API with
175
+ the ` sys ` and ` compiled_data ` feature.
176
+
177
+ For instance, let's manually write our [ ` Now ` ] [ now::Now ] implementation instead of using
178
+ [ ` Temporal::now() ` ] with an empty host system implementation.
179
+
180
+ ``` rust
181
+ use temporal_rs :: {Instant , now :: Now , host :: EmptyHostSystem };
182
+
183
+ // The empty host system is a system implementation HostHooks that always
184
+ // returns the UNIX_EPOCH and the "+00:00" time zone.
185
+ let now = Now :: new (EmptyHostSystem );
186
+ let time_zone = now . time_zone (). unwrap ();
187
+ assert_eq! (time_zone . identifier (). unwrap (), " +00:00" );
188
+ let now = Now :: new (EmptyHostSystem );
189
+ assert_eq! (now . instant (), Instant :: try_new (0 ));
190
+ ```
191
+
192
+ However, even in our above example, we cheated a bit. We were still relying on the
193
+ ` compiled_data ` feature flag that provided time zone data for us. Let's try again,
194
+ but this time without the feature flag.
195
+
196
+ ``` rust
197
+ use temporal_rs :: {Instant , now :: Now , host :: EmptyHostSystem };
198
+ use timezone_provider :: tzif :: CompiledTzdbProvider ;
199
+
200
+ let provider = CompiledTzdbProvider :: default ();
201
+
202
+ // The empty host system is a system implementation HostHooks that always
203
+ // returns the UNIX_EPOCH and the "+00:00" time zone.
204
+ let now = Now :: new (EmptyHostSystem );
205
+ let time_zone = now . time_zone_with_provider (& provider ). unwrap ();
206
+ assert_eq! (time_zone . identifier_with_provider (& provider ). unwrap (), " +00:00" );
207
+
208
+ let now = Now :: new (EmptyHostSystem );
209
+ assert_eq! (now . instant (), Instant :: try_new (0 ));
77
210
```
78
211
212
+ Now -- pun only partially intended -- we've successfully written a no-default-features
213
+ example with temporal_rs!
214
+
215
+ ### What have we learned going over this all this?
216
+
217
+ First, any API that has the suffix ` _with_provider ` is a power user API for supplying
218
+ a custom or specific time zone data provider. Furthermore, any API that has a
219
+ ` _with_provider ` suffix will also have a version without the suffix that automagically
220
+ provides time zone data for you.
221
+
222
+ Finally, sourcing time zone data is a very scary (but fun!) business. If you're interested
223
+ in learning more, feel free to check out the ` timezone_provider ` crate!
224
+
225
+ With any luck, this also highlights the general design of temporal_rs. It provides a
226
+ general usage API that aligns with the Temporal specification while also being
227
+ flexible enough to provide an power user to take control of their host system access
228
+ and time zone data sourcing as needed.
229
+
230
+ ## Formatting
231
+
232
+ temporal_rs adheres to Temporal grammar, which is a strict version of
233
+ [ RFC9557's IXDTF] [ ixdtf ] . RFC9557 is an update to RFC3339 that adds
234
+ extensions to the format.
235
+
236
+ ## More information
237
+
238
+ [ ` Temporal ` ] [ proposal ] is the Stage 3 proposal for ECMAScript that
239
+ provides new JS objects and functions for working with dates and
240
+ times that fully supports time zones and non-gregorian calendars.
241
+
242
+ This library's primary development source is the Temporal
243
+ Proposal [ specification] [ spec ] .
244
+
79
245
## Temporal proposal
80
246
81
247
Relevant links and information regarding Temporal can be found below.
@@ -123,3 +289,7 @@ This project is licensed under the [Apache](./LICENSE-Apache) or
123
289
[ MIT] ( ./LICENSE-MIT ) licenses, at your option.
124
290
125
291
[ boa-repo ] : https://github.com/boa-dev/boa
292
+ [ ixdtf ] : https://www.rfc-editor.org/rfc/rfc9557.txt
293
+ [ proposal ] : https://github.com/tc39/proposal-temporal
294
+ [ spec ] : https://tc39.es/proposal-temporal/
295
+
0 commit comments