Skip to content

Commit cb10eec

Browse files
authored
Implement ZonedDateTime::since and ZonedDateTime::until (#170)
This PR implements the `since` and `until` methods for `ZonedDateTime`. It also has a host of various bug fixes that I came across from implementing the methods in Boa and running the test suite. As a result, each method has a ~80% conformance rating (since 72/90 and until 70/88).
1 parent c614682 commit cb10eec

File tree

11 files changed

+216
-80
lines changed

11 files changed

+216
-80
lines changed

src/components/calendar.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use crate::{
1717
},
1818
iso::IsoDate,
1919
options::{ArithmeticOverflow, TemporalUnit},
20+
parsers::parse_allowed_calendar_formats,
2021
TemporalError, TemporalResult,
2122
};
2223

@@ -200,8 +201,13 @@ impl Calendar {
200201
impl FromStr for Calendar {
201202
type Err = TemporalError;
202203

203-
// 13.39 ParseTemporalCalendarString ( string )
204+
// 13.34 ParseTemporalCalendarString ( string )
204205
fn from_str(s: &str) -> Result<Self, Self::Err> {
206+
if let Some(s) = parse_allowed_calendar_formats(s) {
207+
return s
208+
.map(Calendar::from_utf8)
209+
.unwrap_or(Ok(Calendar::default()));
210+
}
205211
Calendar::from_utf8(s.as_bytes())
206212
}
207213
}

src/components/date.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ use crate::{
55
iso::{IsoDate, IsoDateTime, IsoTime},
66
options::{
77
ArithmeticOverflow, DifferenceOperation, DifferenceSettings, DisplayCalendar,
8-
ResolvedRoundingOptions, TemporalUnit,
8+
ResolvedRoundingOptions, TemporalUnit, UnitGroup,
99
},
1010
parsers::{parse_date_time, IxdtfStringBuilder},
1111
primitive::FiniteF64,
12-
Sign, TemporalError, TemporalResult, TemporalUnwrap, TimeZone,
12+
TemporalError, TemporalResult, TemporalUnwrap, TimeZone,
1313
};
1414
use alloc::{format, string::String};
1515
use core::str::FromStr;
@@ -260,9 +260,10 @@ impl PlainDate {
260260

261261
// 4. Let resolvedOptions be ? SnapshotOwnProperties(? GetOptionsObject(options), null).
262262
// 5. Let settings be ? GetDifferenceSettings(operation, resolvedOptions, DATE, « », "day", "day").
263-
let (sign, resolved) = ResolvedRoundingOptions::from_diff_settings(
263+
let resolved = ResolvedRoundingOptions::from_diff_settings(
264264
settings,
265265
op,
266+
UnitGroup::Date,
266267
TemporalUnit::Day,
267268
TemporalUnit::Day,
268269
)?;
@@ -303,9 +304,9 @@ impl PlainDate {
303304
}
304305
let result = Duration::from_normalized(duration, TemporalUnit::Day)?;
305306
// 13. Return ! CreateTemporalDuration(sign × duration.[[Years]], sign × duration.[[Months]], sign × duration.[[Weeks]], sign × duration.[[Days]], 0, 0, 0, 0, 0, 0).
306-
match sign {
307-
Sign::Positive | Sign::Zero => Ok(result),
308-
Sign::Negative => Ok(result.negated()),
307+
match op {
308+
DifferenceOperation::Until => Ok(result),
309+
DifferenceOperation::Since => Ok(result.negated()),
309310
}
310311
}
311312
}

src/components/datetime.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ use crate::{
55
iso::{IsoDate, IsoDateTime, IsoTime},
66
options::{
77
ArithmeticOverflow, DifferenceOperation, DifferenceSettings, DisplayCalendar,
8-
ResolvedRoundingOptions, RoundingOptions, TemporalUnit, ToStringRoundingOptions,
8+
ResolvedRoundingOptions, RoundingOptions, TemporalUnit, ToStringRoundingOptions, UnitGroup,
99
},
1010
parsers::{parse_date_time, IxdtfStringBuilder},
11-
temporal_assert, Sign, TemporalError, TemporalResult, TemporalUnwrap, TimeZone,
11+
temporal_assert, TemporalError, TemporalResult, TemporalUnwrap, TimeZone,
1212
};
1313
use alloc::string::String;
1414
use core::{cmp::Ordering, str::FromStr};
@@ -131,9 +131,10 @@ impl PlainDateTime {
131131
}
132132

133133
// 5. Let settings be ? GetDifferenceSettings(operation, resolvedOptions, datetime, « », "nanosecond", "day").
134-
let (sign, options) = ResolvedRoundingOptions::from_diff_settings(
134+
let options = ResolvedRoundingOptions::from_diff_settings(
135135
settings,
136136
op,
137+
UnitGroup::DateTime,
137138
TemporalUnit::Day,
138139
TemporalUnit::Nanosecond,
139140
)?;
@@ -149,9 +150,9 @@ impl PlainDateTime {
149150
let result = Duration::from_normalized(norm_record, options.largest_unit)?;
150151

151152
// Step 12
152-
match sign {
153-
Sign::Positive | Sign::Zero => Ok(result),
154-
Sign::Negative => Ok(result.negated()),
153+
match op {
154+
DifferenceOperation::Until => Ok(result),
155+
DifferenceOperation::Since => Ok(result.negated()),
155156
}
156157
}
157158

src/components/duration/normalized.rs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -256,8 +256,9 @@ impl NormalizedDurationRecord {
256256
/// Equivalent: `CreateNormalizedDurationRecord` & `CombineDateAndNormalizedTimeDuration`.
257257
pub(crate) fn new(date: DateDuration, norm: NormalizedTimeDuration) -> TemporalResult<Self> {
258258
if date.sign() != Sign::Zero && norm.sign() != Sign::Zero && date.sign() != norm.sign() {
259-
return Err(TemporalError::range()
260-
.with_message("DateDuration and NormalizedTimeDuration must agree."));
259+
return Err(TemporalError::range().with_message(
260+
"DateDuration and NormalizedTimeDuration must agree if both are not zero.",
261+
));
261262
}
262263
Ok(Self { date, norm })
263264
}
@@ -778,18 +779,18 @@ impl NormalizedDurationRecord {
778779
// 4. Let largestUnitIndex be the ordinal index of the row of Table 22 whose "Singular" column contains largestUnit.
779780
// 5. Let smallestUnitIndex be the ordinal index of the row of Table 22 whose "Singular" column contains smallestUnit.
780781
// 6. Let unitIndex be smallestUnitIndex - 1.
781-
let mut unit = smallest_unit + 1;
782+
let mut smallest_unit = smallest_unit + 1;
782783
// 7. Let done be false.
783784
// 8. Repeat, while unitIndex ≤ largestUnitIndex and done is false,
784-
while unit != TemporalUnit::Auto && unit <= largest_unit {
785+
while smallest_unit != TemporalUnit::Auto && largest_unit < smallest_unit {
785786
// a. Let unit be the value in the "Singular" column of Table 22 in the row whose ordinal index is unitIndex.
786787
// b. If unit is not "week", or largestUnit is "week", then
787-
if unit == TemporalUnit::Week || largest_unit != TemporalUnit::Week {
788-
unit = unit + 1;
788+
if smallest_unit == TemporalUnit::Week || largest_unit != TemporalUnit::Week {
789+
smallest_unit = smallest_unit + 1;
789790
continue;
790791
}
791792

792-
let end_duration = match unit {
793+
let end_duration = match smallest_unit {
793794
// i. If unit is "year", then
794795
TemporalUnit::Year => {
795796
// 1. Let years be duration.[[Years]] + sign.
@@ -887,7 +888,7 @@ impl NormalizedDurationRecord {
887888
break;
888889
}
889890
// c. Set unitIndex to unitIndex - 1.
890-
unit = unit + 1;
891+
smallest_unit = smallest_unit + 1;
891892
}
892893

893894
Ok(duration)

src/components/instant.rs

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ use crate::{
1010
iso::{IsoDate, IsoDateTime, IsoTime},
1111
options::{
1212
ArithmeticOverflow, DifferenceOperation, DifferenceSettings, DisplayOffset,
13-
ResolvedRoundingOptions, RoundingOptions, TemporalUnit, ToStringRoundingOptions,
13+
ResolvedRoundingOptions, RoundingOptions, TemporalUnit, ToStringRoundingOptions, UnitGroup,
1414
},
1515
parsers::{parse_instant, IxdtfStringBuilder},
1616
primitive::FiniteF64,
1717
rounding::{IncrementRounder, Round},
18-
Sign, TemporalError, TemporalResult, TemporalUnwrap, TimeZone, NS_MAX_INSTANT,
18+
TemporalError, TemporalResult, TemporalUnwrap, TimeZone, NS_MAX_INSTANT,
1919
};
2020

2121
use ixdtf::parsers::records::UtcOffsetRecordOrZ;
@@ -24,11 +24,12 @@ use num_traits::FromPrimitive;
2424
use super::{
2525
duration::normalized::{NormalizedDurationRecord, NormalizedTimeDuration},
2626
timezone::TimeZoneProvider,
27+
DateDuration,
2728
};
2829

29-
const NANOSECONDS_PER_SECOND: f64 = 1e9;
30-
const NANOSECONDS_PER_MINUTE: f64 = 60f64 * NANOSECONDS_PER_SECOND;
31-
const NANOSECONDS_PER_HOUR: f64 = 60f64 * NANOSECONDS_PER_MINUTE;
30+
const NANOSECONDS_PER_SECOND: i128 = 1_000_000_000;
31+
const NANOSECONDS_PER_MINUTE: i128 = 60 * NANOSECONDS_PER_SECOND;
32+
const NANOSECONDS_PER_HOUR: i128 = 60 * NANOSECONDS_PER_MINUTE;
3233

3334
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
3435
pub struct EpochNanoseconds(pub(crate) i128);
@@ -83,19 +84,14 @@ impl Instant {
8384
// TODO: Update to `i128`?
8485
/// Adds a `TimeDuration` to the current `Instant`.
8586
///
86-
/// Temporal-Proposal equivalent: `AddDurationToOrSubtractDurationFrom`.
87+
/// Temporal-Proposal equivalent: `AddInstant`.
8788
pub(crate) fn add_to_instant(&self, duration: &TimeDuration) -> TemporalResult<Self> {
88-
let current_nanos = self.epoch_nanoseconds() as f64;
89-
let result = current_nanos
90-
+ duration.nanoseconds.0
91-
+ (duration.microseconds.0 * 1000f64)
92-
+ (duration.milliseconds.0 * 1_000_000f64)
93-
+ (duration.seconds.0 * NANOSECONDS_PER_SECOND)
94-
+ (duration.minutes.0 * NANOSECONDS_PER_MINUTE)
95-
+ (duration.hours.0 * NANOSECONDS_PER_HOUR);
89+
let norm = NormalizedTimeDuration::from_time_duration(duration);
90+
let result = self.epoch_nanoseconds() + norm.0;
9691
Ok(Self::from(EpochNanoseconds::try_from(result)?))
9792
}
9893

94+
/// `temporal_rs` equivalent of `DifferenceInstant`
9995
pub(crate) fn diff_instant_internal(
10096
&self,
10197
other: &Self,
@@ -104,7 +100,10 @@ impl Instant {
104100
let diff =
105101
NormalizedTimeDuration::from_nanosecond_difference(other.as_i128(), self.as_i128())?;
106102
let (round_record, _) = diff.round(FiniteF64::default(), resolved_options)?;
107-
Ok(round_record)
103+
NormalizedDurationRecord::new(
104+
DateDuration::default(),
105+
round_record.normalized_time_duration(),
106+
)
108107
}
109108

110109
// TODO: Add test for `diff_instant`.
@@ -121,9 +120,10 @@ impl Instant {
121120
// 2. Set other to ? ToTemporalInstant(other).
122121
// 3. Let resolvedOptions be ? SnapshotOwnProperties(? GetOptionsObject(options), null).
123122
// 4. Let settings be ? GetDifferenceSettings(operation, resolvedOptions, time, « », "nanosecond", "second").
124-
let (sign, resolved_options) = ResolvedRoundingOptions::from_diff_settings(
123+
let resolved_options = ResolvedRoundingOptions::from_diff_settings(
125124
options,
126125
op,
126+
UnitGroup::Time,
127127
TemporalUnit::Second,
128128
TemporalUnit::Nanosecond,
129129
)?;
@@ -138,9 +138,9 @@ impl Instant {
138138
// 6. Let norm be diffRecord.[[NormalizedTimeDuration]].
139139
// 7. Let result be ! BalanceTimeDuration(norm, settings.[[LargestUnit]]).
140140
// 8. Return ! CreateTemporalDuration(0, 0, 0, 0, sign × result.[[Hours]], sign × result.[[Minutes]], sign × result.[[Seconds]], sign × result.[[Milliseconds]], sign × result.[[Microseconds]], sign × result.[[Nanoseconds]]).
141-
match sign {
142-
Sign::Positive | Sign::Zero => Ok(result),
143-
Sign::Negative => Ok(result.negated()),
141+
match op {
142+
DifferenceOperation::Until => Ok(result),
143+
DifferenceOperation::Since => Ok(result.negated()),
144144
}
145145
}
146146

@@ -338,12 +338,12 @@ impl FromStr for Instant {
338338
// Find the offset
339339
let offset = match ixdtf_record.offset {
340340
UtcOffsetRecordOrZ::Offset(offset) => {
341-
f64::from(offset.hour) * NANOSECONDS_PER_HOUR
342-
+ f64::from(offset.minute) * NANOSECONDS_PER_MINUTE
343-
+ f64::from(offset.second) * NANOSECONDS_PER_SECOND
344-
+ f64::from(offset.nanosecond)
341+
offset.hour as i128 * NANOSECONDS_PER_HOUR
342+
+ i128::from(offset.minute) * NANOSECONDS_PER_MINUTE
343+
+ i128::from(offset.second) * NANOSECONDS_PER_SECOND
344+
+ i128::from(offset.nanosecond)
345345
}
346-
UtcOffsetRecordOrZ::Z => 0.0,
346+
UtcOffsetRecordOrZ::Z => 0,
347347
};
348348
let nanoseconds = IsoDateTime::new_unchecked(iso_date, iso_time)
349349
.as_nanoseconds()

src/components/time.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ use crate::{
55
iso::IsoTime,
66
options::{
77
ArithmeticOverflow, DifferenceOperation, DifferenceSettings, ResolvedRoundingOptions,
8-
RoundingIncrement, TemporalRoundingMode, TemporalUnit, ToStringRoundingOptions,
8+
RoundingIncrement, TemporalRoundingMode, TemporalUnit, ToStringRoundingOptions, UnitGroup,
99
},
1010
parsers::{parse_time, IxdtfStringBuilder},
1111
primitive::FiniteF64,
12-
Sign, TemporalError, TemporalResult,
12+
TemporalError, TemporalResult,
1313
};
1414
use alloc::string::String;
1515
use core::str::FromStr;
@@ -124,9 +124,10 @@ impl PlainTime {
124124
// 2. Set other to ? ToTemporalTime(other).
125125
// 3. Let resolvedOptions be ? SnapshotOwnProperties(? GetOptionsObject(options), null).
126126
// 4. Let settings be ? GetDifferenceSettings(operation, resolvedOptions, TIME, « », "nanosecond", "hour").
127-
let (sign, resolved) = ResolvedRoundingOptions::from_diff_settings(
127+
let resolved = ResolvedRoundingOptions::from_diff_settings(
128128
settings,
129129
op,
130+
UnitGroup::Time,
130131
TemporalUnit::Hour,
131132
TemporalUnit::Nanosecond,
132133
)?;
@@ -151,9 +152,9 @@ impl PlainTime {
151152
let result = TimeDuration::from_normalized(normalized_time, resolved.largest_unit)?.1;
152153

153154
// 8. Return ! CreateTemporalDuration(0, 0, 0, 0, sign × result.[[Hours]], sign × result.[[Minutes]], sign × result.[[Seconds]], sign × result.[[Milliseconds]], sign × result.[[Microseconds]], sign × result.[[Nanoseconds]]).
154-
match sign {
155-
Sign::Positive | Sign::Zero => Ok(Duration::from(result)),
156-
Sign::Negative => Ok(Duration::from(result.negated())),
155+
match op {
156+
DifferenceOperation::Until => Ok(Duration::from(result)),
157+
DifferenceOperation::Since => Ok(Duration::from(result.negated())),
157158
}
158159
}
159160
}

0 commit comments

Comments
 (0)