///|
/// A span of time, such as "2 hours and 30 minutes" or "3 years, 2 months".
///
/// A duration is not anchored to any point on the timeline. Its date
/// components (years, months, weeks, days) and time components (hours through
/// nanoseconds) are kept separate because the length of a year or month
/// depends on where it starts, so converting between the two groups requires a
/// reference point.
///
/// Every non-zero component of a valid duration shares the same sign.
///
/// Reference:
pub struct Duration {
years : Int64
months : Int64
weeks : Int64
days : Int64
hours : Int64
minutes : Int64
seconds : Int64
milliseconds : Int64
microseconds : @int128.Int128
nanoseconds : @int128.Int128
} derive(Eq)
///|
/// A duration of zero length.
pub let duration_zero : Duration = {
years: 0,
months: 0,
weeks: 0,
days: 0,
hours: 0,
minutes: 0,
seconds: 0,
milliseconds: 0,
microseconds: @int128.zero,
nanoseconds: @int128.zero,
}
///|
pub impl Default for Duration with fn default() {
duration_zero
}
///|
pub impl Show for Duration with fn output(self, logger) {
// The default options never round, so this cannot fail.
logger.write_string(
try! self.to_string_with_options(ToStringRoundingOptions::default()),
)
}
///|
pub impl Debug for Duration with fn to_repr(self) {
Repr::Repr(self.to_string())
}
///|
/// `CreateTemporalDuration`: creates a validated duration.
///
/// Raises a `RangeError` when the components disagree in sign or the total
/// magnitude is not representable.
///
/// ```mbt check
/// test {
/// let d = @temporal.Duration::of(weeks=2, days=3)
/// inspect(d, content="P2W3D")
/// }
/// ```
pub fn Duration::new(
years : Int64,
months : Int64,
weeks : Int64,
days : Int64,
hours : Int64,
minutes : Int64,
seconds : Int64,
milliseconds : Int64,
microseconds : @int128.Int128,
nanoseconds : @int128.Int128,
) -> Duration raise TemporalError {
if !is_valid_duration(
years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds,
nanoseconds,
) {
raise RangeError("duration is not valid")
}
{
years,
months,
weeks,
days,
hours,
minutes,
seconds,
milliseconds,
microseconds,
nanoseconds,
}
}
///|
/// Creates a duration from date and time components given as `Int`.
///
/// This is the ergonomic form for the common case; use [`Duration::new`] when
/// a component needs the full `Int64` or 128-bit range.
///
/// ```mbt check
/// test {
/// let d = @temporal.Duration::of(hours=2, minutes=30)
/// inspect(d, content="PT2H30M")
/// }
/// ```
pub fn Duration::of(
years? : Int64 = 0,
months? : Int64 = 0,
weeks? : Int64 = 0,
days? : Int64 = 0,
hours? : Int64 = 0,
minutes? : Int64 = 0,
seconds? : Int64 = 0,
milliseconds? : Int64 = 0,
microseconds? : Int64 = 0,
nanoseconds? : Int64 = 0,
) -> Duration raise TemporalError {
Duration::new(
years,
months,
weeks,
days,
hours,
minutes,
seconds,
milliseconds,
@int128.of_int64(microseconds),
@int128.of_int64(nanoseconds),
)
}
///|
/// Creates a duration with only date components.
pub fn Duration::from_date_duration(
date : DateDuration,
) -> Duration raise TemporalError {
Duration::new(
date.years,
date.months,
date.weeks,
date.days,
0L,
0L,
0L,
0L,
@int128.zero,
@int128.zero,
)
}
///|
/// Returns the years component.
pub fn Duration::years(self : Duration) -> Int64 {
self.years
}
///|
/// Returns the months component.
pub fn Duration::months(self : Duration) -> Int64 {
self.months
}
///|
/// Returns the weeks component.
pub fn Duration::weeks(self : Duration) -> Int64 {
self.weeks
}
///|
/// Returns the days component.
pub fn Duration::days(self : Duration) -> Int64 {
self.days
}
///|
/// Returns the hours component.
pub fn Duration::hours(self : Duration) -> Int64 {
self.hours
}
///|
/// Returns the minutes component.
pub fn Duration::minutes(self : Duration) -> Int64 {
self.minutes
}
///|
/// Returns the seconds component.
pub fn Duration::seconds(self : Duration) -> Int64 {
self.seconds
}
///|
/// Returns the milliseconds component.
pub fn Duration::milliseconds(self : Duration) -> Int64 {
self.milliseconds
}
///|
/// Returns the microseconds component.
pub fn Duration::microseconds(self : Duration) -> @int128.Int128 {
self.microseconds
}
///|
/// Returns the nanoseconds component.
pub fn Duration::nanoseconds(self : Duration) -> @int128.Int128 {
self.nanoseconds
}
///|
/// `DurationSign`: the sign shared by every non-zero component.
///
/// ```mbt check
/// test {
/// inspect(@temporal.Duration::of(days=-1).sign(), content="-1")
/// inspect(@temporal.duration_zero.sign(), content="0")
/// }
/// ```
pub fn Duration::sign(self : Duration) -> Sign {
duration_sign(self.fields_signum())
}
///|
/// Returns the sign of each component, in descending magnitude order.
fn Duration::fields_signum(self : Duration) -> Array[Int64] {
[
self.years.compare(0L).to_int64(),
self.months.compare(0L).to_int64(),
self.weeks.compare(0L).to_int64(),
self.days.compare(0L).to_int64(),
self.hours.compare(0L).to_int64(),
self.minutes.compare(0L).to_int64(),
self.seconds.compare(0L).to_int64(),
self.milliseconds.compare(0L).to_int64(),
self.microseconds.signum().to_int64(),
self.nanoseconds.signum().to_int64(),
]
}
///|
/// Returns whether every component is zero.
pub fn Duration::is_zero(self : Duration) -> Bool {
self.sign() == Zero
}
///|
/// Returns the duration with every component negated.
///
/// ```mbt check
/// test {
/// inspect(@temporal.Duration::of(days=1, hours=2).negated(), content="-P1DT2H")
/// }
/// ```
pub fn Duration::negated(self : Duration) -> Duration {
{
years: -self.years,
months: -self.months,
weeks: -self.weeks,
days: -self.days,
hours: -self.hours,
minutes: -self.minutes,
seconds: -self.seconds,
milliseconds: -self.milliseconds,
microseconds: self.microseconds.neg(),
nanoseconds: self.nanoseconds.neg(),
}
}
///|
/// Returns the duration with every component made non-negative.
pub fn Duration::abs(self : Duration) -> Duration {
{
years: self.years.abs(),
months: self.months.abs(),
weeks: self.weeks.abs(),
days: self.days.abs(),
hours: self.hours.abs(),
minutes: self.minutes.abs(),
seconds: self.seconds.abs(),
milliseconds: self.milliseconds.abs(),
microseconds: self.microseconds.abs(),
nanoseconds: self.nanoseconds.abs(),
}
}
///|
/// Returns the date components as a [`DateDuration`].
///
/// This is lossy: any time components are dropped.
pub fn Duration::date_duration(self : Duration) -> DateDuration {
DateDuration::new_unchecked(self.years, self.months, self.weeks, self.days)
}
///|
/// Returns whether the duration has any non-zero time component.
fn Duration::has_time_component(self : Duration) -> Bool {
self.hours != 0L ||
self.minutes != 0L ||
self.seconds != 0L ||
self.milliseconds != 0L ||
!self.microseconds.is_zero() ||
!self.nanoseconds.is_zero()
}
///|
/// `DefaultTemporalLargestUnit`: the largest unit with a non-zero component.
fn Duration::default_largest_unit(self : Duration) -> DateTimeUnit {
let signums = self.fields_signum()
for i, v in signums {
if v != 0L {
return unit_from_table_index(i)
}
}
Nanosecond
}
///|
/// `ToInternalDurationRecord`: splits the duration into calendar and
/// normalized time parts.
fn Duration::to_internal(self : Duration) -> InternalDurationRecord {
InternalDurationRecord::combine(
self.date_duration(),
TimeDuration::from_duration(self),
)
}
///|
/// `TemporalDurationFromInternal`: expands a normalized time duration back
/// into components no larger than `largest_unit`.
fn Duration::from_internal(
record : InternalDurationRecord,
largest_unit : DateTimeUnit,
) -> Duration raise TemporalError {
let sign = record.time.sign().to_multiplier().to_int64()
let mut nanoseconds = record.time.nanoseconds().abs()
let mut days = 0L
let mut hours = 0L
let mut minutes = 0L
let mut seconds = 0L
let mut milliseconds = 0L
let mut microseconds = @int128.zero
// Each arm carries the split one unit further up than the arm below it.
if largest_unit >= Microsecond {
let (q, r) = nanoseconds.div_rem_euclid(i128_thousand)
microseconds = q
nanoseconds = r
}
if largest_unit >= Millisecond {
let (q, r) = microseconds.div_rem_euclid(i128_thousand)
milliseconds = q.to_int64_saturating()
microseconds = r
}
if largest_unit >= Second {
let (q, r) = div_mod(milliseconds, 1000L)
seconds = q
milliseconds = r
}
if largest_unit >= Minute {
let (q, r) = div_mod(seconds, 60L)
minutes = q
seconds = r
}
if largest_unit >= Hour {
let (q, r) = div_mod(minutes, 60L)
hours = q
minutes = r
}
if largest_unit >= Day {
let (q, r) = div_mod(hours, 24L)
days = q
hours = r
}
Duration::new(
record.date.years,
record.date.months,
record.date.weeks,
record.date.days + days * sign,
hours * sign,
minutes * sign,
seconds * sign,
milliseconds * sign,
microseconds.mul(@int128.of_int64(sign)),
nanoseconds.mul(@int128.of_int64(sign)),
)
}
///|
/// `ToDateDurationRecordWithoutTime`: keeps the calendar components and
/// truncates the time part to whole days.
fn Duration::to_date_duration_record_without_time(
self : Duration,
) -> DateDuration raise TemporalError {
InternalDurationRecord::from_duration_with_24_hour_days(self).to_date_duration_record_without_time()
}
///|
/// Adds two durations.
///
/// Neither operand may have a calendar component, because combining years or
/// months requires a reference point; use `PlainDate::add` or
/// `ZonedDateTime::add` for that.
///
/// ```mbt check
/// test {
/// let a = @temporal.Duration::of(minutes=45)
/// let b = @temporal.Duration::of(hours=1)
/// inspect(a.add(b), content="PT1H45M")
/// }
/// ```
pub fn Duration::add(
self : Duration,
other : Duration,
) -> Duration raise TemporalError {
let largest_unit = self
.default_largest_unit()
.larger(other.default_largest_unit())
// Adding calendar units would need a reference date to resolve their length.
if largest_unit.is_calendar_unit() {
raise RangeError(
"adding durations with years, months or weeks requires a relativeTo reference",
)
}
let combined = InternalDurationRecord::from_duration_with_24_hour_days(self).time.add(
InternalDurationRecord::from_duration_with_24_hour_days(other).time,
)
Duration::from_internal(
InternalDurationRecord::combine(DateDuration::default(), combined),
largest_unit,
)
}
///|
/// Subtracts `other` from this duration.
///
/// The same calendar-component restriction as [`Duration::add`] applies.
pub fn Duration::subtract(
self : Duration,
other : Duration,
) -> Duration raise TemporalError {
self.add(other.negated())
}