Skip to content

Commit 44ed454

Browse files
authored
Implement DateTime diffing methods Until and Since (#83)
This implements diffing for `DateTime` and the corresponding `since` and `until` methods. TODO: add basic unit tests for the methods.
1 parent 6a621b7 commit 44ed454

File tree

3 files changed

+142
-3
lines changed

3 files changed

+142
-3
lines changed

src/components/date.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,8 @@ impl Date {
249249
other.iso.to_epoch_days() - self.iso.to_epoch_days()
250250
}
251251

252+
#[inline]
253+
/// Adds a `Duration` to the current `Date`
252254
pub fn add(
253255
&self,
254256
duration: &Duration,
@@ -257,6 +259,8 @@ impl Date {
257259
self.add_date(duration, overflow)
258260
}
259261

262+
#[inline]
263+
/// Subtracts a `Duration` to the current `Date`
260264
pub fn subtract(
261265
&self,
262266
duration: &Duration,
@@ -265,10 +269,14 @@ impl Date {
265269
self.add_date(&duration.negated(), overflow)
266270
}
267271

272+
#[inline]
273+
/// Returns a `Duration` representing the time from this `Date` until the other `Date`.
268274
pub fn until(&self, other: &Self, settings: DifferenceSettings) -> TemporalResult<Duration> {
269275
self.diff_date(DifferenceOperation::Until, other, settings)
270276
}
271277

278+
#[inline]
279+
/// Returns a `Duration` representing the time passed from this `Date` since the other `Date`.
272280
pub fn since(&self, other: &Self, settings: DifferenceSettings) -> TemporalResult<Duration> {
273281
self.diff_date(DifferenceOperation::Since, other, settings)
274282
}

src/components/datetime.rs

Lines changed: 113 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
use crate::{
44
components::{calendar::Calendar, duration::TimeDuration, Instant},
55
iso::{IsoDate, IsoDateSlots, IsoDateTime, IsoTime},
6-
options::{ArithmeticOverflow, ResolvedRoundingOptions, TemporalUnit},
6+
options::{
7+
ArithmeticOverflow, DifferenceOperation, DifferenceSettings, ResolvedRoundingOptions,
8+
TemporalUnit,
9+
},
710
parsers::parse_date_time,
8-
temporal_assert, TemporalError, TemporalResult, TemporalUnwrap,
11+
temporal_assert, Sign, TemporalError, TemporalResult, TemporalUnwrap,
912
};
1013

1114
use std::{cmp::Ordering, str::FromStr};
@@ -89,6 +92,42 @@ impl DateTime {
8992
Ok(Self::new_unchecked(result, self.calendar.clone()))
9093
}
9194

95+
/// Difference two `DateTime`s together.
96+
pub(crate) fn diff(
97+
&self,
98+
op: DifferenceOperation,
99+
other: &Self,
100+
settings: DifferenceSettings,
101+
) -> TemporalResult<Duration> {
102+
// 3. If ? CalendarEquals(dateTime.[[Calendar]], other.[[Calendar]]) is false, throw a RangeError exception.
103+
if self.calendar != other.calendar {
104+
return Err(TemporalError::range()
105+
.with_message("Calendar must be the same when diffing two DateTimes"));
106+
}
107+
108+
// 5. Let settings be ? GetDifferenceSettings(operation, resolvedOptions, datetime, « », "nanosecond", "day").
109+
let (sign, options) = ResolvedRoundingOptions::from_diff_settings(
110+
settings,
111+
op,
112+
TemporalUnit::Day,
113+
TemporalUnit::Nanosecond,
114+
)?;
115+
116+
// Step 7-8 combined.
117+
if self.iso == other.iso {
118+
return Ok(Duration::default());
119+
}
120+
121+
// Step 10-11.
122+
let (result, _) = self.diff_dt_with_rounding(other, options)?;
123+
124+
// Step 12
125+
match sign {
126+
Sign::Positive | Sign::Zero => Ok(result),
127+
Sign::Negative => Ok(result.negated()),
128+
}
129+
}
130+
92131
// TODO: Figure out whether to handle resolvedOptions
93132
// 5.5.12 DifferencePlainDateTimeWithRounding ( y1, mon1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, d2, h2, min2, s2, ms2,
94133
// mus2, ns2, calendarRec, largestUnit, roundingIncrement, smallestUnit, roundingMode, resolvedOptions )
@@ -381,6 +420,7 @@ impl DateTime {
381420
}
382421

383422
#[inline]
423+
/// Adds a `Duration` to the current `DateTime`.
384424
pub fn add(
385425
&self,
386426
duration: &Duration,
@@ -390,13 +430,26 @@ impl DateTime {
390430
}
391431

392432
#[inline]
433+
/// Subtracts a `Duration` to the current `DateTime`.
393434
pub fn subtract(
394435
&self,
395436
duration: &Duration,
396437
overflow: Option<ArithmeticOverflow>,
397438
) -> TemporalResult<Self> {
398439
self.add_or_subtract_duration(&duration.negated(), overflow)
399440
}
441+
442+
#[inline]
443+
/// Returns a `Duration` representing the period of time from this `DateTime` until the other `DateTime`.
444+
pub fn until(&self, other: &Self, settings: DifferenceSettings) -> TemporalResult<Duration> {
445+
self.diff(DifferenceOperation::Until, other, settings)
446+
}
447+
448+
#[inline]
449+
/// Returns a `Duration` representing the period of time from this `DateTime` since the other `DateTime`.
450+
pub fn since(&self, other: &Self, settings: DifferenceSettings) -> TemporalResult<Duration> {
451+
self.diff(DifferenceOperation::Since, other, settings)
452+
}
400453
}
401454

402455
// ==== Trait impls ====
@@ -464,6 +517,7 @@ mod tests {
464517
use crate::{
465518
components::{calendar::Calendar, duration::DateDuration, Duration},
466519
iso::{IsoDate, IsoTime},
520+
options::{DifferenceSettings, RoundingIncrement, TemporalRoundingMode, TemporalUnit},
467521
};
468522

469523
use super::DateTime;
@@ -575,4 +629,61 @@ mod tests {
575629
}
576630
);
577631
}
632+
633+
fn create_diff_setting(
634+
smallest: TemporalUnit,
635+
increment: u32,
636+
rounding_mode: TemporalRoundingMode,
637+
) -> DifferenceSettings {
638+
DifferenceSettings {
639+
largest_unit: None,
640+
smallest_unit: Some(smallest),
641+
increment: Some(RoundingIncrement::try_new(increment).unwrap()),
642+
rounding_mode: Some(rounding_mode),
643+
}
644+
}
645+
646+
#[test]
647+
fn dt_until_basic() {
648+
let earlier =
649+
DateTime::new(2019, 1, 8, 8, 22, 36, 123, 456, 789, Calendar::default()).unwrap();
650+
let later =
651+
DateTime::new(2021, 9, 7, 12, 39, 40, 987, 654, 321, Calendar::default()).unwrap();
652+
653+
let settings = create_diff_setting(TemporalUnit::Hour, 3, TemporalRoundingMode::HalfExpand);
654+
let result = earlier.until(&later, settings).unwrap();
655+
656+
assert_eq!(result.days(), 973.0);
657+
assert_eq!(result.hours(), 3.0);
658+
659+
let settings =
660+
create_diff_setting(TemporalUnit::Minute, 30, TemporalRoundingMode::HalfExpand);
661+
let result = earlier.until(&later, settings).unwrap();
662+
663+
assert_eq!(result.days(), 973.0);
664+
assert_eq!(result.hours(), 4.0);
665+
assert_eq!(result.minutes(), 30.0);
666+
}
667+
668+
#[test]
669+
fn dt_since_basic() {
670+
let earlier =
671+
DateTime::new(2019, 1, 8, 8, 22, 36, 123, 456, 789, Calendar::default()).unwrap();
672+
let later =
673+
DateTime::new(2021, 9, 7, 12, 39, 40, 987, 654, 321, Calendar::default()).unwrap();
674+
675+
let settings = create_diff_setting(TemporalUnit::Hour, 3, TemporalRoundingMode::HalfExpand);
676+
let result = later.since(&earlier, settings).unwrap();
677+
678+
assert_eq!(result.days(), 973.0);
679+
assert_eq!(result.hours(), 3.0);
680+
681+
let settings =
682+
create_diff_setting(TemporalUnit::Minute, 30, TemporalRoundingMode::HalfExpand);
683+
let result = later.since(&earlier, settings).unwrap();
684+
685+
assert_eq!(result.days(), 973.0);
686+
assert_eq!(result.hours(), 4.0);
687+
assert_eq!(result.minutes(), 30.0);
688+
}
578689
}

src/rounding.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ impl Roundable for i128 {
100100
}
101101

102102
fn compare_remainder(dividend: Self, divisor: Self) -> Option<Ordering> {
103-
Some(dividend.rem_euclid(divisor).cmp(&divisor.div_euclid(2)))
103+
Some((dividend.abs() % divisor).cmp(&(divisor / 2)))
104104
}
105105

106106
fn is_even_cardinal(dividend: Self, divisor: Self) -> bool {
@@ -284,6 +284,14 @@ mod tests {
284284
.unwrap()
285285
.round(TemporalRoundingMode::Floor);
286286
assert_eq!(result, -10);
287+
288+
let result = IncrementRounder::<i128>::from_potentially_negative_parts(
289+
-14,
290+
NonZeroU128::new(3).unwrap(),
291+
)
292+
.unwrap()
293+
.round(TemporalRoundingMode::HalfExpand);
294+
assert_eq!(result, -15);
287295
}
288296

289297
#[test]
@@ -304,4 +312,16 @@ mod tests {
304312
.round(TemporalRoundingMode::Floor);
305313
assert_eq!(result, -9);
306314
}
315+
316+
#[test]
317+
fn dt_since_basic_rounding() {
318+
let result = IncrementRounder::<i128>::from_potentially_negative_parts(
319+
-84082624864197532,
320+
NonZeroU128::new(1800000000000).unwrap(),
321+
)
322+
.unwrap()
323+
.round(TemporalRoundingMode::HalfExpand);
324+
325+
assert_eq!(result, -84083400000000000);
326+
}
307327
}

0 commit comments

Comments
 (0)