///|
/// A date, time or timestamp field that is out of range.
pub(all) suberror DateTimeError {
InvalidDate(String)
InvalidTime(String)
} derive(Debug, Eq)
///|
/// A calendar date (proleptic Gregorian).
pub struct Date {
year : Int
month : Int
day : Int
} derive(Debug)
///|
fn is_leap_year(year : Int) -> Bool {
(year % 4 == 0 && year % 100 != 0) || year % 400 == 0
}
///|
fn days_in_month(year : Int, month : Int) -> Int {
match month {
2 => if is_leap_year(year) { 29 } else { 28 }
4 | 6 | 9 | 11 => 30
_ => 31
}
}
///|
pub fn Date::new(
year~ : Int,
month~ : Int,
day~ : Int,
) -> Date raise DateTimeError {
// The range of Java's LocalDate, which partiql-lang-kotlin uses; the
// extended conformance tests have years such as -1 and 19998.
if year < -999999999 || year > 999999999 {
raise InvalidDate("year \{year} is not in -999999999..999999999")
}
if month < 1 || month > 12 {
raise InvalidDate("month \{month} is not in 1..12")
}
if day < 1 || day > days_in_month(year, month) {
raise InvalidDate("day \{day} is not valid for \{year}-\{month}")
}
{ year, month, day, }
}
///|
/// A time of day. `offset` is minutes east of UTC; `None` means WITHOUT TIME
/// ZONE. `precision` is the number of fractional-second digits (TIME(p)).
pub struct Time {
hour : Int
minute : Int
second : Int
nanos : Int
precision : Int
offset : Int?
} derive(Debug)
///|
fn pow10(n : Int) -> Int {
let mut result = 1
for _ in 0.. Time raise DateTimeError {
if hour < 0 || hour > 23 {
raise InvalidTime("hour \{hour} is not in 0..23")
}
if minute < 0 || minute > 59 {
raise InvalidTime("minute \{minute} is not in 0..59")
}
if second < 0 || second > 59 {
raise InvalidTime("second \{second} is not in 0..59")
}
if precision < 0 || precision > 9 {
raise InvalidTime("precision \{precision} is not in 0..9")
}
if nanos < 0 || nanos > 999999999 {
raise InvalidTime("nanos \{nanos} is not in 0..999999999")
}
if nanos % pow10(9 - precision) != 0 {
raise InvalidTime("nanos \{nanos} needs more than \{precision} digits")
}
if offset is Some(minutes) && (minutes < -1439 || minutes > 1439) {
raise InvalidTime("offset \{minutes} is not in -1439..1439")
}
{ hour, minute, second, nanos, precision, offset, }
}
///|
/// A date and a time. The time's offset is the timestamp's offset.
pub struct Timestamp {
date : Date
time : Time
} derive(Debug)
///|
pub fn Timestamp::new(date : Date, time : Time) -> Timestamp {
{ date, time, }
}
///|
/// Days since 1970-01-01 (Howard Hinnant's days_from_civil).
fn days_from_civil(year : Int, month : Int, day : Int) -> Int64 {
let y = (if month <= 2 { year - 1 } else { year }).to_int64()
let era = (if y >= 0L { y } else { y - 399L }) / 400L
let yoe = y - era * 400L
let m = month.to_int64()
let doy = (153L * (if m > 2L { m - 3L } else { m + 9L }) + 2L) / 5L +
day.to_int64() -
1L
let doe = yoe * 365L + yoe / 4L - yoe / 100L + doy
era * 146097L + doe - 719468L
}
///|
fn Time::seconds_of_day(self : Time) -> Int64 {
(self.hour * 3600 + self.minute * 60 + self.second).to_int64()
}
///|
fn Time::offset_seconds(self : Time) -> Int64 {
match self.offset {
Some(minutes) => (minutes * 60).to_int64()
None => 0L
}
}
///|
/// Fields as written, ignoring the offset.
fn Time::local_key(self : Time) -> (Int64, Int) {
(self.seconds_of_day(), self.nanos)
}
///|
/// UTC seconds of day, modulo 86,400.
fn Time::utc_key(self : Time) -> (Int64, Int) {
let s = (self.seconds_of_day() - self.offset_seconds()) % 86400L
(if s < 0L { s + 86400L } else { s }, self.nanos)
}
///|
fn Timestamp::local_key(self : Timestamp) -> (Int64, Int) {
let days = days_from_civil(self.date.year, self.date.month, self.date.day)
(days * 86400L + self.time.seconds_of_day(), self.time.nanos)
}
///|
/// Seconds since 1970-01-01T00:00:00Z and nanos.
fn Timestamp::utc_key(self : Timestamp) -> (Int64, Int) {
let (seconds, nanos) = self.local_key()
(seconds - self.time.offset_seconds(), nanos)
}