///|
/// A calendar date paired with a wall-clock time, with no time zone.
pub struct IsoDateTime {
/// The date part.
date : IsoDate
/// The time part.
time : IsoTime
} derive(Eq, Compare, Debug)
///|
pub impl Show for IsoDateTime with fn output(self, logger) {
logger.write_string("\{self.date}T\{self.time}")
}
///|
/// Creates a date-time without checking that it is within range.
fn IsoDateTime::new_unchecked(date : IsoDate, time : IsoTime) -> IsoDateTime {
{ date, time }
}
///|
/// Creates a date-time, rejecting one outside the range Temporal supports.
fn IsoDateTime::new(
date : IsoDate,
time : IsoTime,
) -> IsoDateTime raise TemporalError {
if !iso_datetime_within_limits(date, time) {
raise RangeError("date-time is outside the ISO date-time limits")
}
{ date, time }
}
///|
/// Returns the date part.
pub fn IsoDateTime::date(self : IsoDateTime) -> IsoDate {
self.date
}
///|
/// Returns the time part.
pub fn IsoDateTime::time(self : IsoDateTime) -> IsoTime {
self.time
}
///|
/// Raises unless the date-time is within the supported range.
fn IsoDateTime::check_within_limits(
self : IsoDateTime,
) -> Unit raise TemporalError {
if !iso_datetime_within_limits(self.date, self.time) {
raise RangeError("date-time is outside the ISO date-time limits")
}
}
///|
/// `ISODateTimeWithinLimits`.
///
/// The bound is the instant range widened by one day at each end, because a
/// wall-clock time may sit up to a day outside the instant range and still be
/// reachable once a time zone offset is applied.
fn iso_datetime_within_limits(date : IsoDate, time : IsoTime) -> Bool {
if epoch_days_from_gregorian_date(date.year, date.month, date.day).abs() >
MAX_EPOCH_DAYS {
return false
}
let ns = utc_epoch_nanoseconds(date, time)
let max = ns_max_instant.add(i128_ns_per_day)
let min = ns_min_instant.sub(i128_ns_per_day)
min.compare(ns) < 0 && max.compare(ns) > 0
}
///|
/// The epoch nanoseconds of a date and time read as UTC.
fn utc_epoch_nanoseconds(date : IsoDate, time : IsoTime) -> @int128.Int128 {
let epoch_ms = epoch_days_to_epoch_ms(
date.to_epoch_days(),
time.to_epoch_ms(),
)
@int128.of_int64(epoch_ms)
.mul(i128_million)
.add(@int128.of_int(time.microsecond * 1000 + time.nanosecond))
}
///|
/// Returns the epoch nanoseconds of this date-time read as UTC.
fn IsoDateTime::as_nanoseconds(self : IsoDateTime) -> @int128.Int128 {
utc_epoch_nanoseconds(self.date, self.time)
}
///|
/// `GetISOPartsFromEpoch`: splits epoch nanoseconds into a date and time,
/// after shifting by `offset` nanoseconds.
fn IsoDateTime::from_epoch_nanoseconds(
epoch_nanoseconds : @int128.Int128,
offset : Int64,
) -> IsoDateTime {
// Split off the sub-millisecond part first so the bulk of the arithmetic can
// happen in the 64-bit millisecond domain.
let remainder_nanos = epoch_nanoseconds.rem_euclid(i128_million)
let epoch_millis = epoch_nanoseconds
.sub(remainder_nanos)
.div_euclid(i128_million)
.to_int64_saturating()
let (year, month, day) = ymd_from_epoch_milliseconds(epoch_millis)
let hour = rem_euclid(div_euclid(epoch_millis, MS_PER_HOUR), 24L)
let minute = rem_euclid(div_euclid(epoch_millis, MS_PER_MINUTE), 60L)
let second = rem_euclid(div_euclid(epoch_millis, 1000L), 60L)
let millis = rem_euclid(epoch_millis, 1000L)
let micros = remainder_nanos.div_euclid(i128_thousand)
let nanos = remainder_nanos.rem_euclid(i128_thousand)
IsoDateTime::balance(
year,
month,
day.to_int64(),
hour,
minute,
second,
millis,
micros,
nanos.add(@int128.of_int64(offset)),
)
}
///|
/// Balances unbalanced date and time fields into a valid date-time.
fn IsoDateTime::balance(
year : Int,
month : Int,
day : Int64,
hour : Int64,
minute : Int64,
second : Int64,
millisecond : Int64,
microsecond : @int128.Int128,
nanosecond : @int128.Int128,
) -> IsoDateTime {
let (overflow_days, time) = IsoTime::balance(
hour, minute, second, millisecond, microsecond, nanosecond,
)
let epoch_days = iso_date_to_epoch_days(year, month, 1) +
day -
1L +
overflow_days
let (year, month, day) = ymd_from_epoch_days(epoch_days)
IsoDateTime::new_unchecked(IsoDate::new_unchecked(year, month, day), time)
}
///|
/// `RoundISODateTime`: rounds the time part, carrying any whole days into the
/// date.
fn IsoDateTime::round(
self : IsoDateTime,
options : ResolvedRoundingOptions,
) -> IsoDateTime raise TemporalError {
let (rounded_days, rounded_time) = self.time.round(options)
let date = IsoDate::try_balance(
self.date.year,
self.date.month,
self.date.day.to_int64() + rounded_days,
)
IsoDateTime::new(date, rounded_time)
}
///|
/// `DifferenceISODateTime`: the duration from this date-time to `other`.
fn IsoDateTime::diff(
self : IsoDateTime,
other : IsoDateTime,
largest_unit : DateTimeUnit,
) -> InternalDurationRecord raise TemporalError {
let time_duration = self.time.diff(other.time)
let time_sign = time_duration.sign().to_int()
let date_sign = other.date.compare(self.date).compare(0)
// When the time part runs opposite to the date part, borrow a day from the
// date so both parts end up with the same sign.
let (adjusted_date, time_duration) = if time_sign == -date_sign {
(
IsoDate::balance(
other.date.year,
other.date.month,
other.date.day + time_sign,
),
time_duration.add_days(-time_sign.to_int64()),
)
} else {
(other.date, time_duration)
}
let date_largest_unit = largest_unit.larger(Day)
let date_diff = self.date.diff_iso_date(adjusted_date, date_largest_unit)
// If the caller asked for a time unit, the whole date difference has to be
// folded back into the time part.
let (days, time_duration) = if largest_unit == date_largest_unit {
(date_diff.days, time_duration)
} else {
(0L, time_duration.add_days(date_diff.days))
}
InternalDurationRecord::new(
DateDuration::new_unchecked(
date_diff.years,
date_diff.months,
date_diff.weeks,
days,
),
time_duration,
)
}