Skip to content

Commit 6b7fd1d

Browse files
nekevssManishearth
andauthored
Update library introduction documentation and the project README.md (#564)
Co-authored-by: Manish Goregaokar <[email protected]>
1 parent ab98813 commit 6b7fd1d

File tree

4 files changed

+390
-128
lines changed

4 files changed

+390
-128
lines changed

README.md

Lines changed: 208 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -10,72 +10,238 @@ crate has been externalized and is being used in other engines such as [V8](http
1010
For more information on `temporal_rs`'s general position in the Rust
1111
date/time library ecoystem, see our [FAQ](./docs/FAQ.md).
1212

13-
## Example usage
1413

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.
1716

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.
1934

2035
```rust
2136
use temporal_rs::{PlainDate, Calendar};
2237
use tinystr::tinystr;
2338
use core::str::FromStr;
39+
2440
// Create a date with an ISO calendar
2541
let iso8601_date = PlainDate::try_new_iso(2025, 3, 3).unwrap();
42+
2643
// 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);
2845
assert_eq!(japanese_date.era(), Some(tinystr!(16, "reiwa")));
2946
assert_eq!(japanese_date.era_year(), Some(7));
3047
assert_eq!(japanese_date.month(), 3)
3148
```
3249

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`].
3452

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.
3755

3856
```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);
4182

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);
5083
```
5184

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:
5388

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.
56118

57119
```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+
```
60147

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
68149

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));
77210
```
78211

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+
79245
## Temporal proposal
80246

81247
Relevant links and information regarding Temporal can be found below.
@@ -123,3 +289,7 @@ This project is licensed under the [Apache](./LICENSE-Apache) or
123289
[MIT](./LICENSE-MIT) licenses, at your option.
124290

125291
[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+

docs/TZDB.md

Lines changed: 0 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -9,65 +9,6 @@ time zones that observe DST.
99
Further testing still needs to be done for time zones without any DST
1010
transition / potentially historically abnormal edge cases.
1111

12-
## Empty case (AKA Spring forward)
13-
14-
// New York STD -> DST transition (2017-3-12)
15-
// Record direction: DST -> STD
16-
Search time: Seconds(1489285800)
17-
Unresolved under LocalTimeTypeRecord { utoff: Seconds(-18000), is_dst: false, idx: 8 }
18-
Unresolved over LocalTimeTypeRecord { utoff: Seconds(-14400), is_dst: true, idx: 4 }
19-
previous over Seconds(10873800)
20-
current diff Seconds(-16200)
21-
Resolved under LocalTimeTypeRecord { utoff: Seconds(-18000), is_dst: false, idx: 8 }
22-
Resolved over LocalTimeTypeRecord { utoff: Seconds(-14400), is_dst: true, idx: 4 }
23-
Range: Seconds(-18000)..Seconds(-14400)
24-
Diff value: Seconds(-16200)
25-
Contains Result: true
26-
Result: []
27-
28-
// Sydney STD -> DST transition (2017-10-1)
29-
// Record direction: STD -> DST
30-
Search time: Seconds(1506825000)
31-
Unresolved under LocalTimeTypeRecord { utoff: Seconds(39600), is_dst: true, idx: 4 }
32-
Unresolved over LocalTimeTypeRecord { utoff: Seconds(36000), is_dst: false, idx: 9 }
33-
previous over Seconds(15762600)
34-
current diff Seconds(37800)
35-
Resolved under LocalTimeTypeRecord { utoff: Seconds(36000), is_dst: false, idx: 9 }
36-
Resolved over LocalTimeTypeRecord { utoff: Seconds(39600), is_dst: true, idx: 4 }
37-
Range: Seconds(36000)..Seconds(39600)
38-
Diff value: Seconds(37800)
39-
Contains Result: true
40-
Result: []
41-
42-
43-
## Duplicate case (AKA Spring backward)
44-
45-
// New York DST -> STD transition (2017-11-5)
46-
Search time: Seconds(1509845400)
47-
Unresolved under LocalTimeTypeRecord { utoff: Seconds(-14400), is_dst: true, idx: 4 }
48-
Unresolved over LocalTimeTypeRecord { utoff: Seconds(-18000), is_dst: false, idx: 8 }
49-
previous over Seconds(20543400)
50-
current diff Seconds(-16200)
51-
Resolved under LocalTimeTypeRecord { utoff: Seconds(-14400), is_dst: true, idx: 4 }
52-
Resolved over LocalTimeTypeRecord { utoff: Seconds(-18000), is_dst: false, idx: 8 }
53-
Range: Seconds(-18000)..Seconds(-14400)
54-
Diff value: Seconds(-16200)
55-
Contains Result: true
56-
Result: [LocalTimeTypeRecord { utoff: Seconds(-14400), is_dst: true, idx: 4 }, LocalTimeTypeRecord { utoff: Seconds(-18000), is_dst: false, idx: 8 }]
57-
58-
// Sydney DST -> STD transition (2017-4-2)
59-
Search time: Seconds(1491100200)
60-
Unresolved under LocalTimeTypeRecord { utoff: Seconds(36000), is_dst: false, idx: 9 }
61-
Unresolved over LocalTimeTypeRecord { utoff: Seconds(39600), is_dst: true, idx: 4 }
62-
previous over Seconds(15762600)
63-
current diff Seconds(37800)
64-
Resolved under LocalTimeTypeRecord { utoff: Seconds(39600), is_dst: true, idx: 4 }
65-
Resolved over LocalTimeTypeRecord { utoff: Seconds(36000), is_dst: false, idx: 9 }
66-
Range: Seconds(36000)..Seconds(39600)
67-
Diff value: Seconds(37800)
68-
Contains Result: true
69-
Result: [LocalTimeTypeRecord { utoff: Seconds(39600), is_dst: true, idx: 4 }, LocalTimeTypeRecord { utoff: Seconds(36000), is_dst: false, idx: 9 }]
70-
7112
## Slim format testing
7213

7314
`jiff_tzdb` and potentially others use a different compiled `tzif`

src/builtins/core/calendar/types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ const MONTH_THIRTEEN: TinyAsciiStr<4> = tinystr!(4, "M13");
392392
// TODO: Handle instances where month values may be outside of valid
393393
// bounds. In other words, it is totally possible for a value to be
394394
// passed in that is { month: 300 } with overflow::constrain.
395-
/// MonthCode struct v2
395+
/// A MonthCode identifier
396396
#[derive(Debug, Clone, Copy, PartialEq)]
397397
pub struct MonthCode(pub(crate) TinyAsciiStr<4>);
398398

0 commit comments

Comments
 (0)