Skip to content

Commit c999bb3

Browse files
authored
Update duration rounding to new algorithms (#65)
~~Currently a WIP.~~ This closes #19 outside of anything related to ZonedDateTime, which I'd prefer to consider separately due to the reliance on time zones. This PR updates duration rounding to the new duration rounding algorithms. As it's a bit of a larger PR, I thought I would create a draft in case anyone wants to take a look early. Overall, this draft is fairly close to complete, but there are still at least a couple bugs around the actually rounding that I'm working through.
1 parent c658ac7 commit c999bb3

File tree

15 files changed

+1584
-1467
lines changed

15 files changed

+1584
-1467
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
`Temporal` is a calendar and timezone aware date/time library that is currently being designed and proposed as a new
66
builtin to the `ECMAScript` specification.
77

8-
This crate is an implementation of `Temporal` in Rust. While initially developed for the `Boa`, the crate has been externalized
8+
This crate is an implementation of `Temporal` in Rust. While initially developed for `Boa`, the crate has been externalized
99
as we intended to make an engine agnostic and general usage implementation of `Temporal` and its algorithms.
1010

1111
## Temporal Proposal

src/components/date.rs

Lines changed: 62 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,20 @@ use crate::{
88
duration::DateDuration,
99
DateTime, Duration,
1010
},
11-
iso::{IsoDate, IsoDateSlots, IsoDateTime},
11+
iso::{IsoDate, IsoDateSlots, IsoDateTime, IsoTime},
1212
options::{
13-
ArithmeticOverflow, RelativeTo, RoundingIncrement, TemporalRoundingMode, TemporalUnit,
13+
ArithmeticOverflow, DifferenceOperation, DifferenceSettings, ResolvedRoundingOptions,
14+
TemporalUnit,
1415
},
1516
parsers::parse_date_time,
1617
TemporalError, TemporalFields, TemporalResult, TemporalUnwrap,
1718
};
1819
use std::str::FromStr;
1920

20-
use super::{duration::TimeDuration, MonthDay, Time, YearMonth};
21+
use super::{
22+
duration::{normalized::NormalizedDurationRecord, TimeDuration},
23+
MonthDay, Time, YearMonth,
24+
};
2125

2226
/// The native Rust implementation of `Temporal.PlainDate`.
2327
#[non_exhaustive]
@@ -37,14 +41,6 @@ impl Date {
3741
Self { iso, calendar }
3842
}
3943

40-
#[inline]
41-
/// Returns a new moved date and the days associated with that adjustment
42-
pub(crate) fn move_relative_date(&self, duration: &Duration) -> TemporalResult<(Self, f64)> {
43-
let new_date = self.add_date(duration, None)?;
44-
let days = f64::from(self.days_until(&new_date));
45-
Ok((new_date, days))
46-
}
47-
4844
/// Returns the date after adding the given duration to date.
4945
///
5046
/// Temporal Equivalent: 3.5.13 `AddDate ( calendar, plainDate, duration [ , options [ , dateAdd ] ] )`
@@ -112,20 +108,15 @@ impl Date {
112108
}
113109

114110
/// Equivalent: DifferenceTemporalPlainDate
115-
#[allow(clippy::too_many_arguments)]
116111
pub(crate) fn diff_date(
117112
&self,
118-
op: bool,
113+
op: DifferenceOperation,
119114
other: &Self,
120-
rounding_mode: Option<TemporalRoundingMode>,
121-
rounding_increment: Option<RoundingIncrement>,
122-
largest_unit: Option<TemporalUnit>,
123-
smallest_unit: Option<TemporalUnit>,
115+
settings: DifferenceSettings,
124116
) -> TemporalResult<Duration> {
125117
// 1. If operation is SINCE, let sign be -1. Otherwise, let sign be 1.
126118
// 2. Set other to ? ToTemporalDate(other).
127119

128-
// TODO(improvement): Implement `PartialEq` for `TemporalCalendar`
129120
// 3. If ? CalendarEquals(temporalDate.[[Calendar]], other.[[Calendar]]) is false, throw a RangeError exception.
130121
if self.calendar().identifier()? != other.calendar().identifier()? {
131122
return Err(TemporalError::range()
@@ -134,20 +125,12 @@ impl Date {
134125

135126
// 4. Let resolvedOptions be ? SnapshotOwnProperties(? GetOptionsObject(options), null).
136127
// 5. Let settings be ? GetDifferenceSettings(operation, resolvedOptions, DATE, « », "day", "day").
137-
let rounding_increment = rounding_increment.unwrap_or_default();
138-
let (sign, rounding_mode) = if op {
139-
(
140-
-1.0,
141-
rounding_mode
142-
.unwrap_or(TemporalRoundingMode::Trunc)
143-
.negate(),
144-
)
145-
} else {
146-
(1.0, rounding_mode.unwrap_or(TemporalRoundingMode::Trunc))
147-
};
148-
let smallest_unit = smallest_unit.unwrap_or(TemporalUnit::Day);
149-
// Use the defaultlargestunit which is max smallestlargestdefault and smallestunit
150-
let largest_unit = largest_unit.unwrap_or(smallest_unit.max(TemporalUnit::Day));
128+
let (sign, resolved) = ResolvedRoundingOptions::from_diff_settings(
129+
settings,
130+
op,
131+
TemporalUnit::Day,
132+
TemporalUnit::Day,
133+
)?;
151134

152135
// 6. If temporalDate.[[ISOYear]] = other.[[ISOYear]], and temporalDate.[[ISOMonth]] = other.[[ISOMonth]],
153136
// and temporalDate.[[ISODay]] = other.[[ISODay]], then
@@ -159,62 +142,39 @@ impl Date {
159142
// 7. Let calendarRec be ? CreateCalendarMethodsRecord(temporalDate.[[Calendar]], « DATE-ADD, DATE-UNTIL »).
160143
// 8. Perform ! CreateDataPropertyOrThrow(resolvedOptions, "largestUnit", settings.[[LargestUnit]]).
161144
// 9. Let result be ? DifferenceDate(calendarRec, temporalDate, other, resolvedOptions).
162-
let result = self.internal_diff_date(other, largest_unit)?;
163-
164-
// 10. If settings.[[SmallestUnit]] is "day" and settings.[[RoundingIncrement]] = 1,
165-
// let roundingGranularityIsNoop be true; else let roundingGranularityIsNoop be false.
166-
let is_noop =
167-
smallest_unit == TemporalUnit::Day && rounding_increment == RoundingIncrement::ONE;
168-
169-
// 12. Return ! CreateTemporalDuration(sign × result.[[Years]], sign × result.[[Months]], sign × result.[[Weeks]], sign × result.[[Days]], 0, 0, 0, 0, 0, 0).
170-
if is_noop {
171-
return Duration::new(
172-
result.years() * sign,
173-
result.months() * sign,
174-
result.weeks() * sign,
175-
result.days() * sign,
176-
0.0,
177-
0.0,
178-
0.0,
179-
0.0,
180-
0.0,
181-
0.0,
145+
let result = self.internal_diff_date(other, resolved.largest_unit)?;
146+
147+
// 10. Let duration be ! CreateNormalizedDurationRecord(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], ZeroTimeDuration()).
148+
let duration = NormalizedDurationRecord::from_date_duration(*result.date())?;
149+
// 11. If settings.[[SmallestUnit]] is "day" and settings.[[RoundingIncrement]] = 1, let roundingGranularityIsNoop be true; else let roundingGranularityIsNoop be false.
150+
let rounding_granularity_is_noop =
151+
resolved.smallest_unit == TemporalUnit::Day && resolved.increment.get() == 1;
152+
// 12. If roundingGranularityIsNoop is false, then
153+
let date_duration = if !rounding_granularity_is_noop {
154+
// a. Let destEpochNs be GetUTCEpochNanoseconds(other.[[ISOYear]], other.[[ISOMonth]], other.[[ISODay]], 0, 0, 0, 0, 0, 0).
155+
let dest_epoch_ns = other.iso.as_nanoseconds().temporal_unwrap()?;
156+
// b. Let dateTime be ISO Date-Time Record { [[Year]]: temporalDate.[[ISOYear]], [[Month]]: temporalDate.[[ISOMonth]], [[Day]]: temporalDate.[[ISODay]], [[Hour]]: 0, [[Minute]]: 0, [[Second]]: 0, [[Millisecond]]: 0, [[Microsecond]]: 0, [[Nanosecond]]: 0 }.
157+
let dt = DateTime::new_unchecked(
158+
IsoDateTime::new_unchecked(self.iso, IsoTime::default()),
159+
self.calendar.clone(),
182160
);
183-
}
161+
// c. Set duration to ? RoundRelativeDuration(duration, destEpochNs, dateTime, calendarRec, unset, settings.[[LargestUnit]], settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]).
162+
*duration
163+
.round_relative_duration(dest_epoch_ns, &dt, None, resolved)?
164+
.0
165+
.date()
166+
} else {
167+
duration.date()
168+
};
184169

185-
// 11. If roundingGranularityIsNoop is false, then
186-
// a. Let roundRecord be ? RoundDuration(result.[[Years]], result.[[Months]], result.[[Weeks]],
187-
// result.[[Days]], ZeroTimeDuration(), settings.[[RoundingIncrement]], settings.[[SmallestUnit]],
188-
// settings.[[RoundingMode]], temporalDate, calendarRec).
189-
// TODO: Look into simplifying round_internal's parameters.
190-
let round_record = result.round_internal(
191-
rounding_increment,
192-
smallest_unit,
193-
rounding_mode,
194-
&RelativeTo {
195-
zdt: None,
196-
date: Some(self),
197-
},
198-
None,
199-
)?;
200-
// b. Let roundResult be roundRecord.[[NormalizedDuration]].
201-
let round_result = round_record.0 .0 .0;
202-
// c. Set result to ? BalanceDateDurationRelative(roundResult.[[Years]], roundResult.[[Months]], roundResult.[[Weeks]],
203-
// roundResult.[[Days]], settings.[[LargestUnit]], settings.[[SmallestUnit]], temporalDate, calendarRec).
204-
let result = round_result.balance_relative(largest_unit, smallest_unit, Some(self))?;
205-
206-
Duration::new(
207-
result.years * sign,
208-
result.months * sign,
209-
result.weeks * sign,
210-
result.days * sign,
211-
0.0,
212-
0.0,
213-
0.0,
214-
0.0,
215-
0.0,
216-
0.0,
217-
)
170+
let sign = f64::from(sign as i8);
171+
// 13. Return ! CreateTemporalDuration(sign × duration.[[Years]], sign × duration.[[Months]], sign × duration.[[Weeks]], sign × duration.[[Days]], 0, 0, 0, 0, 0, 0).
172+
Ok(Duration::from_date_duration(&DateDuration::new(
173+
date_duration.years * sign,
174+
date_duration.months * sign,
175+
date_duration.weeks * sign,
176+
date_duration.days * sign,
177+
)?))
218178
}
219179
}
220180

@@ -303,40 +263,12 @@ impl Date {
303263
self.add_date(&duration.negated(), overflow)
304264
}
305265

306-
pub fn until(
307-
&self,
308-
other: &Self,
309-
rounding_mode: Option<TemporalRoundingMode>,
310-
rounding_increment: Option<RoundingIncrement>,
311-
smallest_unit: Option<TemporalUnit>,
312-
largest_unit: Option<TemporalUnit>,
313-
) -> TemporalResult<Duration> {
314-
self.diff_date(
315-
false,
316-
other,
317-
rounding_mode,
318-
rounding_increment,
319-
smallest_unit,
320-
largest_unit,
321-
)
266+
pub fn until(&self, other: &Self, settings: DifferenceSettings) -> TemporalResult<Duration> {
267+
self.diff_date(DifferenceOperation::Until, other, settings)
322268
}
323269

324-
pub fn since(
325-
&self,
326-
other: &Self,
327-
rounding_mode: Option<TemporalRoundingMode>,
328-
rounding_increment: Option<RoundingIncrement>,
329-
smallest_unit: Option<TemporalUnit>,
330-
largest_unit: Option<TemporalUnit>,
331-
) -> TemporalResult<Duration> {
332-
self.diff_date(
333-
true,
334-
other,
335-
rounding_mode,
336-
rounding_increment,
337-
smallest_unit,
338-
largest_unit,
339-
)
270+
pub fn since(&self, other: &Self, settings: DifferenceSettings) -> TemporalResult<Duration> {
271+
self.diff_date(DifferenceOperation::Since, other, settings)
340272
}
341273
}
342274

@@ -582,23 +514,31 @@ mod tests {
582514
fn simple_date_until() {
583515
let earlier = Date::from_str("1969-07-24").unwrap();
584516
let later = Date::from_str("1969-10-05").unwrap();
585-
let result = earlier.until(&later, None, None, None, None).unwrap();
517+
let result = earlier
518+
.until(&later, DifferenceSettings::default())
519+
.unwrap();
586520
assert_eq!(result.days(), 73.0,);
587521

588522
let later = Date::from_str("1996-03-03").unwrap();
589-
let result = earlier.until(&later, None, None, None, None).unwrap();
523+
let result = earlier
524+
.until(&later, DifferenceSettings::default())
525+
.unwrap();
590526
assert_eq!(result.days(), 9719.0,);
591527
}
592528

593529
#[test]
594530
fn simple_date_since() {
595531
let earlier = Date::from_str("1969-07-24").unwrap();
596532
let later = Date::from_str("1969-10-05").unwrap();
597-
let result = later.since(&earlier, None, None, None, None).unwrap();
533+
let result = later
534+
.since(&earlier, DifferenceSettings::default())
535+
.unwrap();
598536
assert_eq!(result.days(), 73.0,);
599537

600538
let later = Date::from_str("1996-03-03").unwrap();
601-
let result = later.since(&earlier, None, None, None, None).unwrap();
539+
let result = later
540+
.since(&earlier, DifferenceSettings::default())
541+
.unwrap();
602542
assert_eq!(result.days(), 9719.0,);
603543
}
604544

0 commit comments

Comments
 (0)