///|
/// A wall-clock time, as the `[[ISOHour]]` through `[[ISONanosecond]]`
/// internal slots.
///
/// The fields are declared in descending significance so that the derived
/// comparison orders times chronologically.
pub struct IsoTime {
/// The hour, in `0 ..= 23`.
hour : Int
/// The minute, in `0 ..= 59`.
minute : Int
/// The second, in `0 ..= 59`. Temporal has no leap seconds.
second : Int
/// The millisecond, in `0 ..= 999`.
millisecond : Int
/// The microsecond within the millisecond, in `0 ..= 999`.
microsecond : Int
/// The nanosecond within the microsecond, in `0 ..= 999`.
nanosecond : Int
} derive(Eq, Compare, Debug)
///|
pub impl Show for IsoTime with fn output(self, logger) {
let buf = StringBuilder::new()
write_time(
buf,
self.hour,
self.minute,
self.second,
self.subsecond_nanoseconds(),
Auto,
true,
)
logger.write_string(buf.to_string())
}
///|
/// Midnight, `00:00:00`.
pub let iso_time_midnight : IsoTime = {
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
microsecond: 0,
nanosecond: 0,
}
///|
/// Noon, `12:00:00`.
///
/// Range checks use noon as the reference time so that a date at either end of
/// the supported range is judged by its middle rather than its edge.
let iso_time_noon : IsoTime = { ..iso_time_midnight, hour: 12 }
///|
/// Returns the hour.
pub fn IsoTime::hour(self : IsoTime) -> Int {
self.hour
}
///|
/// Returns the minute.
pub fn IsoTime::minute(self : IsoTime) -> Int {
self.minute
}
///|
/// Returns the second.
pub fn IsoTime::second(self : IsoTime) -> Int {
self.second
}
///|
/// Returns the millisecond.
pub fn IsoTime::millisecond(self : IsoTime) -> Int {
self.millisecond
}
///|
/// Returns the microsecond within the millisecond.
pub fn IsoTime::microsecond(self : IsoTime) -> Int {
self.microsecond
}
///|
/// Returns the nanosecond within the microsecond.
pub fn IsoTime::nanosecond(self : IsoTime) -> Int {
self.nanosecond
}
///|
/// Returns the whole sub-second part as a nanosecond count, in `0 ..= 999999999`.
fn IsoTime::subsecond_nanoseconds(self : IsoTime) -> Int {
self.millisecond * 1_000_000 + self.microsecond * 1_000 + self.nanosecond
}
///|
/// `RegulateTime`: creates a time, clamping or rejecting out-of-range fields.
fn IsoTime::new_with_overflow(
hour : Int,
minute : Int,
second : Int,
millisecond : Int,
microsecond : Int,
nanosecond : Int,
overflow : Overflow,
) -> IsoTime raise TemporalError {
match overflow {
Constrain =>
{
hour: hour.max(0).min(23),
minute: minute.max(0).min(59),
second: second.max(0).min(59),
millisecond: millisecond.max(0).min(999),
microsecond: microsecond.max(0).min(999),
nanosecond: nanosecond.max(0).min(999),
}
Reject => {
if !is_valid_iso_time(
hour, minute, second, millisecond, microsecond, nanosecond,
) {
raise RangeError("time fields are out of range")
}
{ hour, minute, second, millisecond, microsecond, nanosecond }
}
}
}
///|
/// Returns whether the components name a valid wall-clock time.
pub fn is_valid_iso_time(
hour : Int,
minute : Int,
second : Int,
millisecond : Int,
microsecond : Int,
nanosecond : Int,
) -> Bool {
hour >= 0 &&
hour <= 23 &&
minute >= 0 &&
minute <= 59 &&
second >= 0 &&
second <= 59 &&
millisecond >= 0 &&
millisecond <= 999 &&
microsecond >= 0 &&
microsecond <= 999 &&
nanosecond >= 0 &&
nanosecond <= 999
}
///|
/// `BalanceTime`: carries out-of-range components upward, returning the number
/// of whole days that overflowed along with the balanced time.
///
/// The sub-second inputs are 128-bit because a normalized time duration can
/// contribute more nanoseconds than an `Int64` holds.
fn IsoTime::balance(
hour : Int64,
minute : Int64,
second : Int64,
millisecond : Int64,
microsecond : @int128.Int128,
nanosecond : @int128.Int128,
) -> (Int64, IsoTime) {
let (carry, nanosecond) = nanosecond.div_rem_euclid(i128_thousand)
let microsecond = microsecond.add(carry)
let (carry, microsecond) = microsecond.div_rem_euclid(i128_thousand)
// From here the values are bounded by the carries above, so 64-bit
// arithmetic suffices for the rest of the cascade.
let millisecond = millisecond + carry.to_int64_saturating()
let (carry, millisecond) = div_mod(millisecond, 1000L)
let (carry, second) = div_mod(second + carry, 60L)
let (carry, minute) = div_mod(minute + carry, 60L)
let (days, hour) = div_mod(hour + carry, 24L)
(
days,
{
hour: hour.to_int(),
minute: minute.to_int(),
second: second.to_int(),
millisecond: millisecond.to_int(),
microsecond: microsecond.to_int64_saturating().to_int(),
nanosecond: nanosecond.to_int64_saturating().to_int(),
},
)
}
///|
/// `DifferenceTime`: the signed time duration from this time to `other`.
fn IsoTime::diff(self : IsoTime, other : IsoTime) -> TimeDuration {
TimeDuration::from_components(
(other.hour - self.hour).to_int64(),
(other.minute - self.minute).to_int64(),
(other.second - self.second).to_int64(),
(other.millisecond - self.millisecond).to_int64(),
@int128.of_int(other.microsecond - self.microsecond),
@int128.of_int(other.nanosecond - self.nanosecond),
)
}
///|
/// `AddTime`: adds a normalized time duration, returning the day overflow.
fn IsoTime::add(self : IsoTime, duration : TimeDuration) -> (Int64, IsoTime) {
IsoTime::balance(
self.hour.to_int64(),
self.minute.to_int64(),
self.second.to_int64() + duration.seconds(),
self.millisecond.to_int64(),
@int128.of_int(self.microsecond),
@int128.of_int(self.nanosecond + duration.subseconds()),
)
}
///|
/// `IsoTimeToEpochMs`: milliseconds since midnight.
fn IsoTime::to_epoch_ms(self : IsoTime) -> Int64 {
self.hour.to_int64() * MS_PER_HOUR +
self.minute.to_int64() * MS_PER_MINUTE +
self.second.to_int64() * 1000L +
self.millisecond.to_int64()
}
///|
/// `RoundTime`: rounds to a multiple of the resolved smallest unit, returning
/// the whole days that rounding carried out of the time.
fn IsoTime::round(
self : IsoTime,
options : ResolvedRoundingOptions,
) -> (Int64, IsoTime) raise TemporalError {
// The quantity being rounded is everything strictly below the smallest
// unit's own place, expressed in nanoseconds.
let quantity = match options.smallest_unit {
Day | Hour => @int128.of_int64(self.to_nanoseconds_since_midnight())
Minute =>
@int128.of_int64(
self.to_nanoseconds_since_midnight() -
self.hour.to_int64() * NS_PER_HOUR,
)
Second =>
@int128.of_int64(
self.second.to_int64() * NS_PER_SECOND +
self.subsecond_nanoseconds().to_int64(),
)
Millisecond => @int128.of_int(self.subsecond_nanoseconds())
Microsecond => @int128.of_int(self.microsecond * 1000 + self.nanosecond)
Nanosecond => @int128.of_int(self.nanosecond)
_ => raise RangeError("invalid smallestUnit value for time rounding")
}
let length = temporal_unwrap(
options.smallest_unit.as_nanoseconds(),
"time unit length",
)
let increment = @int128.of_int(options.increment.get()).mul(
@int128.of_int64(length),
)
let rounded = IncrementRounder::from_signed_num(quantity, increment)
.round(options.rounding_mode)
.div(@int128.of_int64(length))
.to_int64_saturating()
// Rebuild the time from the fields above the smallest unit plus the rounded
// count of that unit, letting `balance` carry any overflow.
match options.smallest_unit {
Day => (rounded, iso_time_midnight)
Hour => IsoTime::balance(rounded, 0L, 0L, 0L, @int128.zero, @int128.zero)
Minute =>
IsoTime::balance(
self.hour.to_int64(),
rounded,
0L,
0L,
@int128.zero,
@int128.zero,
)
Second =>
IsoTime::balance(
self.hour.to_int64(),
self.minute.to_int64(),
rounded,
0L,
@int128.zero,
@int128.zero,
)
Millisecond =>
IsoTime::balance(
self.hour.to_int64(),
self.minute.to_int64(),
self.second.to_int64(),
rounded,
@int128.zero,
@int128.zero,
)
Microsecond =>
IsoTime::balance(
self.hour.to_int64(),
self.minute.to_int64(),
self.second.to_int64(),
self.millisecond.to_int64(),
@int128.of_int64(rounded),
@int128.zero,
)
Nanosecond =>
IsoTime::balance(
self.hour.to_int64(),
self.minute.to_int64(),
self.second.to_int64(),
self.millisecond.to_int64(),
@int128.of_int(self.microsecond),
@int128.of_int64(rounded),
)
_ => raise AssertError("unreachable smallest unit in time rounding")
}
}
///|
/// Returns the nanoseconds elapsed since midnight.
fn IsoTime::to_nanoseconds_since_midnight(self : IsoTime) -> Int64 {
self.hour.to_int64() * NS_PER_HOUR +
self.minute.to_int64() * NS_PER_MINUTE +
self.second.to_int64() * NS_PER_SECOND +
self.subsecond_nanoseconds().to_int64()
}