///|
/// An exact point on the timeline, as a count of nanoseconds since the Unix
/// epoch.
///
/// An instant carries no calendar and no time zone; it is the same moment
/// everywhere. The representable range is ±10^8 days around the epoch, the
/// same range ECMAScript `Date` covers.
///
/// Reference:
pub struct Instant {
epoch_nanoseconds : @int128.Int128
} derive(Eq, Compare)
///|
pub impl Show for Instant with fn output(self, logger) {
logger.write_string(
try! self.to_string_with_options(
ToStringRoundingOptions::default(),
None,
utc_only_provider,
),
)
}
///|
pub impl Debug for Instant with fn to_repr(self) {
Repr::Repr(self.to_string())
}
///|
/// The Unix epoch, 1970-01-01T00:00:00Z.
pub let instant_epoch : Instant = { epoch_nanoseconds: @int128.zero }
///|
/// Creates an instant from nanoseconds since the epoch.
///
/// ```mbt check
/// test {
/// let instant = @temporal.Instant::from_epoch_nanoseconds(
/// @int128.of_int64(1740827770000000000L),
/// )
/// inspect(instant, content="2025-03-01T11:16:10Z")
/// }
/// ```
pub fn Instant::from_epoch_nanoseconds(
epoch_nanoseconds : @int128.Int128,
) -> Instant raise TemporalError {
if !is_valid_epoch_nanoseconds(epoch_nanoseconds) {
raise RangeError("instant is outside the representable epoch range")
}
{ epoch_nanoseconds, }
}
///|
/// Creates an instant from milliseconds since the epoch.
pub fn Instant::from_epoch_milliseconds(
epoch_milliseconds : Int64,
) -> Instant raise TemporalError {
Instant::from_epoch_nanoseconds(
@int128.of_int64(epoch_milliseconds).mul(i128_million),
)
}
///|
/// Returns whether the count is inside the representable instant range.
pub fn is_valid_epoch_nanoseconds(nanoseconds : @int128.Int128) -> Bool {
ns_min_instant.compare(nanoseconds) <= 0 &&
nanoseconds.compare(ns_max_instant) <= 0
}
///|
/// Returns the nanoseconds since the epoch.
pub fn Instant::epoch_nanoseconds(self : Instant) -> @int128.Int128 {
self.epoch_nanoseconds
}
///|
/// Returns the milliseconds since the epoch, rounded toward negative infinity.
pub fn Instant::epoch_milliseconds(self : Instant) -> Int64 {
self.epoch_nanoseconds.div_euclid(i128_million).to_int64_saturating()
}
///|
/// Adds a duration.
///
/// The duration may only have time components: an instant has no calendar, so
/// years, months, weeks and days have no defined length against it.
pub fn Instant::add(
self : Instant,
duration : Duration,
) -> Instant raise TemporalError {
if duration.years != 0L ||
duration.months != 0L ||
duration.weeks != 0L ||
duration.days != 0L {
raise RangeError(
"an Instant can only be shifted by time units, not calendar units",
)
}
Instant::from_epoch_nanoseconds(
self.epoch_nanoseconds.add(
TimeDuration::from_duration(duration).nanoseconds(),
),
)
}
///|
/// Subtracts a duration.
pub fn Instant::subtract(
self : Instant,
duration : Duration,
) -> Instant raise TemporalError {
self.add(duration.negated())
}
///|
/// `DifferenceTemporalInstant`.
fn Instant::diff_instant(
self : Instant,
operation : DifferenceOperation,
other : Instant,
settings : DifferenceSettings,
) -> Duration raise TemporalError {
let resolved = ResolvedRoundingOptions::from_diff_settings(
settings,
operation,
UnitGroup::Time,
Second,
Nanosecond,
)
if resolved.largest_unit.is_calendar_unit() || resolved.largest_unit is Day {
raise RangeError(
"the largest unit for an Instant difference must be a time unit",
)
}
let mut time = TimeDuration::from_nanosecond_difference(
other.epoch_nanoseconds,
self.epoch_nanoseconds,
)
if !resolved.is_noop() {
time = time.round(resolved)
}
let result = Duration::from_internal(
InternalDurationRecord::combine(DateDuration::default(), time),
resolved.largest_unit,
)
match operation {
Until => result
Since => result.negated()
}
}
///|
/// Returns the duration from this instant until `other`.
pub fn Instant::until(
self : Instant,
other : Instant,
settings? : DifferenceSettings = DifferenceSettings::default(),
) -> Duration raise TemporalError {
self.diff_instant(Until, other, settings)
}
///|
/// Returns the duration from `other` until this instant.
pub fn Instant::since(
self : Instant,
other : Instant,
settings? : DifferenceSettings = DifferenceSettings::default(),
) -> Duration raise TemporalError {
self.diff_instant(Since, other, settings)
}
///|
/// Rounds the instant to a multiple of the given unit.
///
/// Rounding is done as though the instant were positive, so that moments
/// before and after the epoch round in the same direction.
pub fn Instant::round(
self : Instant,
options : RoundingOptions,
) -> Instant raise TemporalError {
let resolved = ResolvedRoundingOptions::from_instant_options(options)
let length = temporal_unwrap(
resolved.smallest_unit.as_nanoseconds(),
"time unit length",
)
let increment = @int128.of_int64(length).mul(
@int128.of_int(resolved.increment.get()),
)
let rounded = IncrementRounder::from_signed_num(
self.epoch_nanoseconds,
increment,
).round_as_if_positive(resolved.rounding_mode)
Instant::from_epoch_nanoseconds(rounded)
}
///|
/// Converts to a `ZonedDateTime` in the given time zone.
pub fn[P : TimeZoneProvider] Instant::to_zoned_date_time(
self : Instant,
time_zone : TimeZone,
provider : P,
calendar? : Calendar = Calendar::ISO,
) -> ZonedDateTime raise TemporalError {
ZonedDateTime::try_new(self.epoch_nanoseconds, time_zone, provider, calendar~)
}
///|
/// Parses an instant from an RFC 9557 string, which must carry a UTC offset.
///
/// ```mbt check
/// test {
/// inspect(
/// @temporal.Instant::of_string("2025-03-01T11:16:10+01:00"),
/// content="2025-03-01T10:16:10Z",
/// )
/// }
/// ```
pub fn Instant::of_string(source : String) -> Instant raise TemporalError {
let (date, time, offset) = parse_instant_string(source)
let iso_date = IsoDate::new_with_overflow(
date.year,
date.month,
date.day,
Reject,
)
let iso = IsoDateTime::new_unchecked(iso_date, iso_time_of_parsed(time))
let offset_nanoseconds = match offset {
ZDesignator => 0L
NumericOffset(value) => value.nanoseconds()
}
Instant::from_epoch_nanoseconds(
iso.as_nanoseconds().sub(@int128.of_int64(offset_nanoseconds)),
)
}
///|
/// Renders the instant in RFC 9557 form.
///
/// Without a time zone the instant is rendered in UTC with a `Z` suffix;
/// with one, it is rendered as a local time with that zone's offset.
pub fn[P : TimeZoneProvider] Instant::to_string_with_options(
self : Instant,
options : ToStringRoundingOptions,
time_zone : TimeZone?,
provider : P,
) -> String raise TemporalError {
let resolved = options.resolve()
let rounding = ResolvedRoundingOptions::from_to_string_options(resolved)
let length = temporal_unwrap(
rounding.smallest_unit.as_nanoseconds(),
"time unit length",
)
let increment = @int128.of_int64(length).mul(
@int128.of_int(rounding.increment.get()),
)
let rounded = IncrementRounder::from_signed_num(
self.epoch_nanoseconds,
increment,
).round_as_if_positive(rounding.rounding_mode)
let offset_nanoseconds = match time_zone {
None => 0L
Some(zone) => zone.offset_nanoseconds_for(rounded, provider)
}
let iso = IsoDateTime::from_epoch_nanoseconds(rounded, offset_nanoseconds)
let buf = StringBuilder::new()
write_date(buf, iso.date.year, iso.date.month, iso.date.day)
buf.write_string("T")
write_time(
buf,
iso.time.hour,
iso.time.minute,
iso.time.second,
iso.time.subsecond_nanoseconds(),
resolved.precision,
true,
)
match time_zone {
None => buf.write_string("Z")
Some(_) => buf.write_string(UtcOffset(offset_nanoseconds).to_string())
}
buf.to_string()
}
///|
/// Renders the instant in UTC, as `YYYY-MM-DDTHH:MM:SSZ`.
pub fn Instant::to_string(self : Instant) -> String {
try! self.to_string_with_options(
ToStringRoundingOptions::default(),
None,
utc_only_provider,
)
}
///|
/// `DifferenceInstant`: the rounded difference between two instants, with the
/// rounding options already resolved.
fn Instant::diff_internal(
self : Instant,
other : Instant,
options : ResolvedRoundingOptions,
) -> InternalDurationRecord raise TemporalError {
let mut time = TimeDuration::from_nanosecond_difference(
other.epoch_nanoseconds,
self.epoch_nanoseconds,
)
if !options.is_noop() {
time = time.round(options)
}
InternalDurationRecord::combine(DateDuration::default(), time)
}