///|
/// The time portion of a duration, held as a single count of nanoseconds.
///
/// Time components can be normalized against one another because their
/// lengths are fixed, so the whole time part collapses to one integer. The
/// magnitude never exceeds [`max_time_duration`].
///
/// Spec:
struct TimeDuration(@int128.Int128) derive(Eq, Compare, Debug)
///|
/// A zero time duration.
let time_duration_zero : TimeDuration = TimeDuration(@int128.zero)
///|
/// Returns the total nanoseconds.
fn TimeDuration::nanoseconds(self : TimeDuration) -> @int128.Int128 {
self.0
}
///|
/// `TimeDurationFromComponents`: builds a time duration from signed
/// components, preserving each component's own sign.
fn TimeDuration::from_components(
hours : Int64,
minutes : Int64,
seconds : Int64,
milliseconds : Int64,
microseconds : @int128.Int128,
nanoseconds : @int128.Int128,
) -> TimeDuration {
let total = @int128.of_int64(hours)
.mul(@int128.of_int64(NS_PER_HOUR))
.add(@int128.of_int64(minutes).mul(@int128.of_int64(NS_PER_MINUTE)))
.add(@int128.of_int64(seconds).mul(i128_billion))
.add(@int128.of_int64(milliseconds).mul(i128_million))
.add(microseconds.mul(i128_thousand))
.add(nanoseconds)
TimeDuration(total)
}
///|
/// `NormalizeTimeDuration`: collapses a `Duration`'s time components.
fn TimeDuration::from_duration(duration : Duration) -> TimeDuration {
TimeDuration::from_components(
duration.hours,
duration.minutes,
duration.seconds,
duration.milliseconds,
duration.microseconds,
duration.nanoseconds,
)
}
///|
/// `TimeDurationFromEpochNanosecondsDifference`.
fn TimeDuration::from_nanosecond_difference(
one : @int128.Int128,
two : @int128.Int128,
) -> TimeDuration raise TemporalError {
TimeDuration::checked(one.sub(two))
}
///|
/// Wraps a nanosecond count, rejecting values beyond [`max_time_duration`].
fn TimeDuration::checked(
nanoseconds : @int128.Int128,
) -> TimeDuration raise TemporalError {
if nanoseconds.abs().compare(max_time_duration) > 0 {
raise RangeError("TimeDuration exceeds maxTimeDuration")
}
TimeDuration(nanoseconds)
}
///|
/// `Add24HourDaysToTimeDuration`.
fn TimeDuration::add_days(
self : TimeDuration,
days : Int64,
) -> TimeDuration raise TemporalError {
TimeDuration::checked(self.0.add(@int128.of_int64(days).mul(i128_ns_per_day)))
}
///|
/// `AddTimeDuration`.
fn TimeDuration::add(
self : TimeDuration,
other : TimeDuration,
) -> TimeDuration raise TemporalError {
TimeDuration::checked(self.0.add(other.0))
}
///|
/// Subtracts another time duration.
fn TimeDuration::sub(
self : TimeDuration,
other : TimeDuration,
) -> TimeDuration raise TemporalError {
TimeDuration::checked(self.0.sub(other.0))
}
///|
/// Returns the negated time duration.
fn TimeDuration::negated(self : TimeDuration) -> TimeDuration {
TimeDuration(self.0.neg())
}
///|
/// `TimeDurationSign`.
fn TimeDuration::sign(self : TimeDuration) -> Sign {
Sign::of_int(self.0.signum())
}
///|
/// Returns the whole seconds, truncated toward zero.
fn TimeDuration::seconds(self : TimeDuration) -> Int64 {
self.0.div(i128_billion).to_int64_saturating()
}
///|
/// Returns the sub-second nanoseconds, carrying the duration's sign.
fn TimeDuration::subseconds(self : TimeDuration) -> Int {
self.0.rem(i128_billion).to_int64_saturating().to_int()
}
///|
/// `RoundTimeDuration`: rounds to a multiple of the resolved smallest unit.
fn TimeDuration::round(
self : TimeDuration,
options : ResolvedRoundingOptions,
) -> TimeDuration raise TemporalError {
let divisor = temporal_unwrap(
options.smallest_unit.as_nanoseconds(),
"smallest unit must be a time unit",
)
let increment = @int128.of_int(options.increment.get()).mul(
@int128.of_int64(divisor),
)
self.round_to_increment(increment, options.rounding_mode)
}
///|
/// Rounds to a multiple of `increment` nanoseconds.
fn TimeDuration::round_to_increment(
self : TimeDuration,
increment : @int128.Int128,
mode : RoundingMode,
) -> TimeDuration raise TemporalError {
let rounded = IncrementRounder::from_signed_num(self.0, increment).round(mode)
TimeDuration::checked(rounded)
}
///|
/// Rounds to a whole number of days, returning the day count.
fn TimeDuration::round_to_fractional_days(
self : TimeDuration,
increment : RoundingIncrement,
mode : RoundingMode,
) -> Int64 raise TemporalError {
let adjusted = @int128.of_int(increment.get()).mul(i128_ns_per_day)
let rounded = IncrementRounder::from_signed_num(self.0, adjusted).round(mode)
rounded.div(i128_ns_per_day).to_int64_saturating()
}
///|
/// Divides by a nanosecond count, truncating toward zero.
fn TimeDuration::truncated_divide(
self : TimeDuration,
divisor : Int64,
) -> Int64 {
self.0.div(@int128.of_int64(divisor)).to_int64_saturating()
}
///|
/// `TotalTimeDuration`: the duration expressed as a fractional count of
/// `unit`.
fn TimeDuration::total(
self : TimeDuration,
unit : DateTimeUnit,
) -> Double raise TemporalError {
let divisor = temporal_unwrap(
unit.as_nanoseconds(),
"unit must have a fixed length",
)
exact_ratio_to_double(self.0, @int128.of_int64(divisor))
}
///|
/// Converts the exact rational `numerator / denominator` to the nearest
/// `Double`, with ties resolved to even.
///
/// Temporal totals routinely exceed `2^53`, so dividing after converting each
/// side to a `Double` would round twice and drift. Instead the quotient is
/// computed in 128-bit integer arithmetic with enough spare bits that a single
/// final rounding decides the result, with the discarded remainder folded in
/// as a sticky bit.
fn exact_ratio_to_double(
numerator : @int128.Int128,
denominator : @int128.Int128,
) -> Double {
if numerator.is_zero() {
return 0.0
}
let negative = numerator.is_negative() != denominator.is_negative()
let n = numerator.abs()
let d = denominator.abs()
// Scale so the quotient carries at least 55 significant bits: 53 for the
// mantissa, one to decide the rounding, and one for the sticky bit.
let shift = @cmp.maximum(0, 55 - (n.bit_length() - d.bit_length()))
let (quotient, remainder) = n.shl(shift).div_rem(d)
// Fold "there was a remainder" into the lowest bit. The quotient has far
// more than 53 bits here, so bit 0 is well below the mantissa and cannot
// disturb anything but the tie decision.
let sticky = if remainder.is_zero() { @int128.zero } else { @int128.one }
let adjusted = quotient.land(@int128.one.neg()).lor(sticky)
let magnitude = adjusted.to_double() / two_pow_double(shift)
if negative {
-magnitude
} else {
magnitude
}
}
///|
/// Returns `2^exp` as a `Double` for non-negative `exp`.
fn two_pow_double(exp : Int) -> Double {
let mut result = 1.0
for _ in 0..
struct InternalDurationRecord {
date : DateDuration
time : TimeDuration
} derive(Debug)
///|
/// `CombineDateAndTimeDuration`, without the sign agreement check.
fn InternalDurationRecord::combine(
date : DateDuration,
time : TimeDuration,
) -> InternalDurationRecord {
{ date, time }
}
///|
/// `CreateNormalizedDurationRecord`: combines the two parts, requiring that
/// they agree in sign when both are non-zero.
fn InternalDurationRecord::new(
date : DateDuration,
time : TimeDuration,
) -> InternalDurationRecord raise TemporalError {
if date.sign() != Zero && time.sign() != Zero && date.sign() != time.sign() {
raise RangeError(
"DateDuration and TimeDuration must agree in sign when both are non-zero",
)
}
{ date, time }
}
///|
/// Builds a record with an empty time part.
fn InternalDurationRecord::from_date_duration(
date : DateDuration,
) -> InternalDurationRecord raise TemporalError {
InternalDurationRecord::new(date, time_duration_zero)
}
///|
/// A record with both parts empty.
let internal_duration_zero : InternalDurationRecord = {
date: DateDuration::default(),
time: time_duration_zero,
}
///|
/// `ToInternalDurationRecordWith24HourDays`: folds the duration's days into
/// the time part, treating each as exactly 24 hours.
fn InternalDurationRecord::from_duration_with_24_hour_days(
duration : Duration,
) -> InternalDurationRecord raise TemporalError {
let time = TimeDuration::from_duration(duration).add_days(duration.days)
let date = DateDuration::new_unchecked(
duration.years,
duration.months,
duration.weeks,
0L,
)
InternalDurationRecord::new(date, time)
}
///|
/// `ToDateDurationRecordWithoutTime`: truncates the time part to whole days.
fn InternalDurationRecord::to_date_duration_record_without_time(
self : InternalDurationRecord,
) -> DateDuration raise TemporalError {
let days = self.time.nanoseconds().div(i128_ns_per_day).to_int64_saturating()
DateDuration::new(self.date.years, self.date.months, self.date.weeks, days)
}
///|
/// The sign of the record: the date part's sign if non-zero, else the time
/// part's.
fn InternalDurationRecord::sign(self : InternalDurationRecord) -> Sign {
let date_sign = self.date.sign()
if date_sign == Zero {
self.time.sign()
} else {
date_sign
}
}