Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/builtins/compiled/duration/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{
OffsetDisambiguation, RelativeTo, RoundingIncrement, RoundingMode, RoundingOptions, Unit,
},
partial::PartialDuration,
Calendar, DateDuration, PlainDate, TimeDuration, TimeZone, ZonedDateTime,
Calendar, DateDuration, PlainDate, TimeZone, ZonedDateTime,
};

use core::{num::NonZeroU32, str::FromStr};
Expand Down Expand Up @@ -373,7 +373,7 @@ fn basic_negative_expand_rounding() {

#[test]
fn rounding_to_fractional_day_tests() {
let twenty_five_hours = Duration::from(TimeDuration::new(25, 0, 0, 0, 0, 0).unwrap());
let twenty_five_hours = Duration::from_hours(25);
let options = RoundingOptions {
largest_unit: Some(Unit::Day),
smallest_unit: None,
Expand Down Expand Up @@ -485,7 +485,7 @@ fn basic_subtract_duration() {
// days-24-hours-relative-to-zoned-date-time.js
#[test]
fn round_relative_to_zoned_datetime() {
let duration = Duration::from(TimeDuration::new(25, 0, 0, 0, 0, 0).unwrap());
let duration = Duration::from_hours(25);
let zdt = ZonedDateTime::try_new(
1_000_000_000_000_000_000,
Calendar::default(),
Expand Down Expand Up @@ -609,7 +609,7 @@ fn balance_days_up_to_both_years_and_months() {
);
}

// relativeto-plaindate-add24hourdaystonormalizedtimeduration-out-of-range.js
// relativeto-plaindate-add24hourdaystoTimeDuration-out-of-range.js
#[test]
fn add_normalized_time_duration_out_of_range() {
let duration = Duration::from_partial_duration(PartialDuration {
Expand Down
69 changes: 22 additions & 47 deletions src/builtins/core/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,7 @@ use core::{cmp::Ordering, str::FromStr};
use icu_calendar::AnyCalendarKind;
use writeable::Writeable;

use super::{
duration::{normalized::NormalizedDurationRecord, TimeDuration},
PlainMonthDay, PlainYearMonth,
};
use super::{duration::normalized::InternalDurationRecord, PlainMonthDay, PlainYearMonth};
use tinystr::TinyAsciiStr;

// TODO (potentially): Bump era up to TinyAsciiStr<18> to accomodate
Expand Down Expand Up @@ -185,53 +182,31 @@ impl PlainDate {
Self { iso, calendar }
}

// Updated: 2025-08-03
/// Returns the date after adding the given duration to date.
///
/// Temporal Equivalent: 3.5.13 `AddDate ( calendar, plainDate, duration [ , options [ , dateAdd ] ] )`
/// 3.5.14 `AddDurationToDate`
///
/// More information:
///
/// - [AO specification](https://tc39.es/proposal-temporal/#sec-temporal-adddurationtodate)
#[inline]
pub(crate) fn add_date(
pub(crate) fn add_duration_to_date(
&self,
duration: &Duration,
overflow: Option<ArithmeticOverflow>,
) -> TemporalResult<Self> {
// 2. If options is not present, set options to undefined.
// 3. If operation is subtract, set duration to CreateNegatedTemporalDuration(duration).
// 4. Let dateDuration be ToDateDurationRecordWithoutTime(duration).
// TODO: Look into why this is fallible, and make some adjustments
let date_duration = duration.to_date_duration_record_without_time()?;
// 5. Let resolvedOptions be ? GetOptionsObject(options).
// 6. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
let overflow = overflow.unwrap_or(ArithmeticOverflow::Constrain);
// 3. If duration.[[Years]] ≠ 0, or duration.[[Months]] ≠ 0, or duration.[[Weeks]] ≠ 0, then
if duration.date().years != 0 || duration.date().months != 0 || duration.date().weeks != 0 {
// a. If dateAdd is not present, then
// i. Set dateAdd to unused.
// ii. If calendar is an Object, set dateAdd to ? GetMethod(calendar, "dateAdd").
// b. Return ? CalendarDateAdd(calendar, plainDate, duration, options, dateAdd).
return self
.calendar()
.date_add(&self.iso, duration.date(), overflow);
}

// 4. Let overflow be ? ToTemporalOverflow(options).
// 5. Let norm be NormalizeTimeDuration(duration.[[Hours]],
// duration.[[Minutes]], duration.[[Seconds]],
// duration.[[Milliseconds]], duration.[[Microseconds]],
// duration.[[Nanoseconds]]).
// 6. Let days be duration.[[Days]] + BalanceTimeDuration(norm,
// "day").[[Days]].
let days = duration
.days()
.checked_add(
TimeDuration::from_normalized(duration.time().to_normalized(), Unit::Day)?.0,
)
.ok_or(TemporalError::range())?;

// 7. Let result be ? AddISODate(plainDate.[[ISOYear]], plainDate.[[ISOMonth]], plainDate.[[ISODay]], 0, 0, 0, days, overflow).
let result = self
.iso
.add_date_duration(&DateDuration::new(0, 0, 0, days)?, overflow)?;

Self::try_new(
result.year,
result.month,
result.day,
self.calendar().clone(),
)
// 7. Let result be ? CalendarDateAdd(calendar, temporalDate.[[ISODate]], dateDuration, overflow).
// 8. Return ! CreateTemporalDate(result, calendar).
self.calendar()
.date_add(&self.iso, &date_duration, overflow)
}

/// Returns a duration representing the difference between the dates one and two.
Expand Down Expand Up @@ -298,7 +273,7 @@ impl PlainDate {
let result = self.internal_diff_date(other, resolved.largest_unit)?;

// 10. Let duration be ! CreateNormalizedDurationRecord(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], ZeroTimeDuration()).
let mut duration = NormalizedDurationRecord::from_date_duration(*result.date())?;
let mut duration = InternalDurationRecord::from_date_duration(result.date())?;
// 11. If settings.[[SmallestUnit]] is "day" and settings.[[RoundingIncrement]] = 1, let roundingGranularityIsNoop be true; else let roundingGranularityIsNoop be false.
let rounding_granularity_is_noop =
resolved.smallest_unit == Unit::Day && resolved.increment.get() == 1;
Expand All @@ -319,7 +294,7 @@ impl PlainDate {
resolved,
)?
}
let result = Duration::from_normalized(duration, Unit::Day)?;
let result = Duration::from_internal(duration, Unit::Day)?;
// 13. Return ! CreateTemporalDuration(sign × duration.[[Years]], sign × duration.[[Months]], sign × duration.[[Weeks]], sign × duration.[[Days]], 0, 0, 0, 0, 0, 0).
match op {
DifferenceOperation::Until => Ok(result),
Expand Down Expand Up @@ -520,7 +495,7 @@ impl PlainDate {
duration: &Duration,
overflow: Option<ArithmeticOverflow>,
) -> TemporalResult<Self> {
self.add_date(duration, overflow)
self.add_duration_to_date(duration, overflow)
}

#[inline]
Expand All @@ -530,7 +505,7 @@ impl PlainDate {
duration: &Duration,
overflow: Option<ArithmeticOverflow>,
) -> TemporalResult<Self> {
self.add_date(&duration.negated(), overflow)
self.add_duration_to_date(&duration.negated(), overflow)
}

#[inline]
Expand Down
79 changes: 44 additions & 35 deletions src/builtins/core/datetime.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! This module implements `DateTime` any directly related algorithms.

use super::{
duration::normalized::{NormalizedDurationRecord, NormalizedTimeDuration},
Duration, PartialTime, PlainDate, PlainTime, ZonedDateTime,
duration::normalized::InternalDurationRecord, Duration, PartialTime, PlainDate, PlainTime,
ZonedDateTime,
};
use crate::parsed_intermediates::ParsedDateTime;
use crate::{
Expand All @@ -19,7 +19,7 @@ use crate::{
parsers::IxdtfStringBuilder,
primitive::FiniteF64,
provider::{NeverProvider, TimeZoneProvider},
temporal_assert, MonthCode, TemporalError, TemporalResult, TimeZone,
MonthCode, TemporalError, TemporalResult, TimeZone,
};
use alloc::string::String;
use core::{cmp::Ordering, str::FromStr};
Expand Down Expand Up @@ -225,33 +225,25 @@ impl PlainDateTime {
overflow: Option<ArithmeticOverflow>,
) -> TemporalResult<Self> {
// SKIP: 1, 2, 3, 4
// 1. If operation is subtract, let sign be -1. Otherwise, let sign be 1.
// 2. Let duration be ? ToTemporalDurationRecord(temporalDurationLike).
// 3. Set options to ? GetOptionsObject(options).
// 4. Let calendarRec be ? CreateCalendarMethodsRecord(dateTime.[[Calendar]], « date-add »).

// 5. Let norm be NormalizeTimeDuration(sign × duration.[[Hours]], sign × duration.[[Minutes]], sign × duration.[[Seconds]], sign × duration.[[Milliseconds]], sign × duration.[[Microseconds]], sign × duration.[[Nanoseconds]]).
let norm = NormalizedTimeDuration::from_time_duration(duration.time());

// TODO: validate Constrain is default with all the recent changes.
// 6. Let result be ? AddDateTime(dateTime.[[ISOYear]], dateTime.[[ISOMonth]], dateTime.[[ISODay]], dateTime.[[ISOHour]], dateTime.[[ISOMinute]], dateTime.[[ISOSecond]], dateTime.[[ISOMillisecond]], dateTime.[[ISOMicrosecond]], dateTime.[[ISONanosecond]], calendarRec, sign × duration.[[Years]], sign × duration.[[Months]], sign × duration.[[Weeks]], sign × duration.[[Days]], norm, options).
let result =
self.iso
.add_date_duration(self.calendar().clone(), duration.date(), norm, overflow)?;

// 7. Assert: IsValidISODate(result.[[Year]], result.[[Month]], result.[[Day]]) is true.
// 8. Assert: IsValidTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]],
// result.[[Microsecond]], result.[[Nanosecond]]) is true.
temporal_assert!(
result.is_within_limits(),
"Assertion failed: the below datetime is not within valid limits:\n{:?}",
result
);

// 9. Return ? CreateTemporalDateTime(result.[[Year]], result.[[Month]], result.[[Day]], result.[[Hour]],
// result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]],
// result.[[Nanosecond]], dateTime.[[Calendar]]).
Ok(Self::new_unchecked(result, self.calendar.clone()))
// 5. Let internalDuration be ToInternalDurationRecordWith24HourDays(duration).
let internal_duration = InternalDurationRecord::from_duration_with_24_hour_days(duration)?;
// 6. Let timeResult be AddTime(dateTime.[[ISODateTime]].[[Time]], internalDuration.[[Time]]).
let (days, time_result) = self
.iso
.time
.add(internal_duration.normalized_time_duration());
// 7. Let dateDuration be ? AdjustDateDurationRecord(internalDuration.[[Date]], timeResult.[[Days]]).
let date_duration = internal_duration.date().adjust(days, None, None)?;
// 8. Let addedDate be ? CalendarDateAdd(dateTime.[[Calendar]], dateTime.[[ISODateTime]].[[ISODate]], dateDuration, overflow).
let added_date = self.calendar().date_add(
&self.iso.date,
&date_duration,
overflow.unwrap_or(ArithmeticOverflow::Constrain),
)?;
// 9. Let result be CombineISODateAndTimeRecord(addedDate, timeResult).
let result = IsoDateTime::new(added_date.iso, time_result)?;
// 10. Return ? CreateTemporalDateTime(result, dateTime.[[Calendar]]).
Ok(Self::new_unchecked(result, self.calendar().clone()))
}

/// Difference two `DateTime`s together.
Expand Down Expand Up @@ -284,7 +276,7 @@ impl PlainDateTime {
// Step 10-11.
let norm_record = self.diff_dt_with_rounding(other, options)?;

let result = Duration::from_normalized(norm_record, options.largest_unit)?;
let result = Duration::from_internal(norm_record, options.largest_unit)?;

// Step 12
match op {
Expand All @@ -300,12 +292,12 @@ impl PlainDateTime {
&self,
other: &Self,
options: ResolvedRoundingOptions,
) -> TemporalResult<NormalizedDurationRecord> {
) -> TemporalResult<InternalDurationRecord> {
// 1. If CompareISODateTime(y1, mon1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, d2, h2, min2, s2, ms2, mus2, ns2) = 0, then
if matches!(self.iso.cmp(&other.iso), Ordering::Equal) {
// a. Let durationRecord be CreateDurationRecord(0, 0, 0, 0, 0, 0, 0, 0, 0, 0).
// b. Return the Record { [[DurationRecord]]: durationRecord, [[Total]]: 0 }.
return Ok(NormalizedDurationRecord::default());
return Ok(InternalDurationRecord::default());
}
// 2. If ISODateTimeWithinLimits(isoDateTime1) is false or ISODateTimeWithinLimits(isoDateTime2) is false, throw a RangeError exception.
self.iso.check_validity()?;
Expand Down Expand Up @@ -1248,13 +1240,13 @@ mod tests {
PlainDateTime::try_new(2019, 10, 29, 10, 46, 38, 271, 986, 102, Calendar::default())
.unwrap();

let result = dt.subtract(&Duration::hour(12), None).unwrap();
let result = dt.subtract(&Duration::from_hours(12), None).unwrap();
assert_datetime(
result,
(2019, 10, tinystr!(4, "M10"), 28, 22, 46, 38, 271, 986, 102),
);

let result = dt.add(&Duration::hour(-12), None).unwrap();
let result = dt.add(&Duration::from_hours(-12), None).unwrap();
assert_datetime(
result,
(2019, 10, tinystr!(4, "M10"), 28, 22, 46, 38, 271, 986, 102),
Expand Down Expand Up @@ -1543,4 +1535,21 @@ mod tests {
"pads 4 decimal places to 9"
);
}

#[test]
fn datetime_add() {
use crate::{Duration, PlainDateTime};
use core::str::FromStr;

let dt = PlainDateTime::from_str("2024-01-15T12:00:00").unwrap();

let duration = Duration::from_str("P1M2DT3H4M").unwrap();

// Add duration
let later = dt.add(&duration, None).unwrap();
assert_eq!(later.month(), 2);
assert_eq!(later.day(), 17);
assert_eq!(later.hour(), 15);
assert_eq!(later.minute(), 4);
}
}
Loading
Loading