///|
/// A point in time, UTC, with nanosecond precision. The caller reads the clock
/// and gives "now" as an `Instant`; the library never reads a clock.
pub struct Instant {
priv seconds : Int64
/// Nanoseconds after `seconds`, from 0 to 999 999 999.
priv nanos : Int
} derive(Eq, Compare, Debug)
///|
pub extend Instant with Eq::{equal, not_equal}
///|
pub extend Instant with Compare::{compare, op_lt, op_le, op_ge, op_gt}
///|
pub extend Instant with @debug.Debug::{to_repr}
///|
/// The instant `seconds` after 1970-01-01T00:00:00Z. Every `Int64` is valid.
pub fn Instant::from_unix_seconds(seconds : Int64) -> Instant {
{ seconds, nanos: 0 }
}
///|
/// The instant `millis` milliseconds after 1970-01-01T00:00:00Z, as an SCT
/// gives it, with the millisecond part kept.
fn Instant::from_unix_millis(millis : Int64) -> Instant {
let seconds = millis / 1000L
let rest = (millis % 1000L).to_int()
if rest < 0 {
{ seconds: seconds - 1L, nanos: (rest + 1000) * 1_000_000 }
} else {
{ seconds, nanos: rest * 1_000_000 }
}
}
///|
/// Whole seconds since 1970-01-01T00:00:00Z, rounded down.
pub fn Instant::unix_seconds(self : Instant) -> Int64 {
self.seconds
}
///|
pub impl Show for Instant with output(self, logger) {
logger.write_string(self.unix_seconds().to_string())
}
///|
pub extend Instant with Show::{output, to_string}
///|
/// Read a protobuf JSON timestamp, for example `2022-12-31T23:59:59.999Z`:
/// RFC 3339 with up to nine fraction digits and a `Z` or `+hh:mm` offset.
fn parse_timestamp(text : String) -> Instant? {
fn number(start : Int, count : Int) -> Int? {
guard start + count <= text.length() else { return None }
let mut value = 0
for i in start..<(start + count) {
let code = text[i].to_int()
guard code >= '0'.to_int() && code <= '9'.to_int() else { return None }
value = value * 10 + (code - '0'.to_int())
}
Some(value)
}
fn char_at(at : Int, expected : Char) -> Bool {
at < text.length() && text[at].to_int() == expected.to_int()
}
guard number(0, 4) is Some(year) &&
char_at(4, '-') &&
number(5, 2) is Some(month) &&
char_at(7, '-') &&
number(8, 2) is Some(day) &&
char_at(10, 'T') &&
number(11, 2) is Some(hour) &&
char_at(13, ':') &&
number(14, 2) is Some(minute) &&
char_at(16, ':') &&
number(17, 2) is Some(second) else {
return None
}
guard year >= 1 &&
month >= 1 &&
month <= 12 &&
day >= 1 &&
day <= @der.days_in_month(year, month) &&
hour <= 23 &&
minute <= 59 &&
second <= 59 else {
return None
}
let mut at = 19
let mut fraction = 0
if char_at(at, '.') {
at += 1
let mut digits = 0
while at < text.length() &&
text[at].to_int() >= '0'.to_int() &&
text[at].to_int() <= '9'.to_int() {
guard digits < 9 else { return None }
fraction = fraction * 10 + (text[at].to_int() - '0'.to_int())
digits += 1
at += 1
}
guard digits > 0 else { return None }
for _ in digits..<9 {
fraction = fraction * 10
}
}
let offset = if char_at(at, 'Z') && at + 1 == text.length() {
0
} else if (char_at(at, '+') || char_at(at, '-')) &&
at + 6 == text.length() &&
char_at(at + 3, ':') &&
number(at + 1, 2) is Some(hours) &&
number(at + 4, 2) is Some(minutes) &&
hours <= 23 &&
minutes <= 59 {
let size = hours * 3600 + minutes * 60
if char_at(at, '+') {
size
} else {
-size
}
} else {
return None
}
let seconds = @der.unix_seconds(year, month, day, hour, minute, second) -
offset.to_int64()
Some({ seconds, nanos: fraction })
}