datetime.oak
← Standard library
See on GitHub ↗
{
default: default
map: map
take: take
slice: slice
merge: merge
} := import('std')
{
endsWith?: endsWith?
contains?: strContains?
indexOf: strIndexOf
padStart: padStart
padEnd: padEnd
split: split
} := import('str')
{
round: round
} := import('math')
{
format: fmtFormat
} := import('fmt')
LeapDay := 31 + 28
SecondsPerDay := 86400
DaysPer4Years := 365 * 4 + 1
DaysPer100Years := 25 * DaysPer4Years - 1
DaysPer400Years := DaysPer100Years * 4 + 1
ZeroYear := 1
DaysFrom1To1970 := DaysPer400Years * 5 - 365 * 31 - 8
DaysBeforeMonth := [
_
0
31
31 + 28
31 + 28 + 31
31 + 28 + 31 + 30
31 + 28 + 31 + 30 + 31
31 + 28 + 31 + 30 + 31 + 30
31 + 28 + 31 + 30 + 31 + 30 + 31
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31
]
fn leap?(year) year % 4 = 0 & (year % 100 != 0 | year % 400 = 0)
fn _describeDate(t) {
d := int((t - t % SecondsPerDay) / SecondsPerDay) + DaysFrom1To1970
if t < 0 & t % 86400 != 0 -> d <- d - 1
n400 := int(d / DaysPer400Years)
d := d - DaysPer400Years * n400
n100 := int(d / DaysPer100Years)
n100 := n100 - int(n100 / 4)
d := d - DaysPer100Years * n100
n4 := int(d / DaysPer4Years)
d := d - DaysPer4Years * n4
n := int(d / 365)
n := n - int(n / 4)
d := d - 365 * n
year := ZeroYear +
400 * n400 +
100 * n100 +
4 * n4 +
n
month := 0
day := d
leapYear? := leap?(year)
if {
leapYear? & day = LeapDay -> {
month <- 2
day <- 29
}
_ -> {
if leapYear? & day > LeapDay -> day <- day - 1
fn subMonth(m) if day < DaysBeforeMonth.(m + 1) {
true -> m
_ -> subMonth(m + 1)
}
month <- subMonth(1)
day <- day - DaysBeforeMonth.(month) + 1
}
}
{
year: year
month: month
day: day
}
}
fn _describeClock(t) {
rem := t % SecondsPerDay
if rem < 0 -> rem <- rem + SecondsPerDay
hour := int(rem / 3600)
rem := rem % 3600
minute := int(rem / 60)
{
hour: hour
minute: minute
second: rem % 60
}
}
fn describe(t) merge(
_describeDate(t)
_describeClock(t)
)
fn timestamp(desc) {
{
year: year
month: month
day: day
hour: hour
minute: minute
second: second
} := desc
leapYear? := leap?(year)
year := year - ZeroYear
n400 := int(year / 400), year := year % 400
n100 := int(year / 100), year := year % 100
n4 := int(year / 4), year := year % 4
daysYearToDate := if leapYear? {
true -> if {
month = 1
month = 2 & day < 29 -> DaysBeforeMonth.(month) + day - 1
month = 2 & day = 29 -> 59
_ -> DaysBeforeMonth.(month) + day
}
_ -> DaysBeforeMonth.(month) + day - 1
}
daysFrom1 := DaysPer400Years * n400 +
DaysPer100Years * n100 +
DaysPer4Years * n4 +
365 * year +
daysYearToDate
daysFrom1970 := daysFrom1 - DaysFrom1To1970
daysFrom1970 * SecondsPerDay +
3600 * hour +
60 * minute +
second
}
fn format(t, tzOffset) {
tzOffset := default(tzOffset, 0)
{
year: year
month: month
day: day
hour: hour
minute: minute
second: second
} := describe(t + tzOffset * 60)
'{{0}}-{{1}}-{{2}}T{{3}}:{{4}}:{{5}}{{6}}{{7}}' |> fmtFormat(
if {
year > 9999 -> year |> string() |> padStart(6, '0')
year < 0 -> '-' << -year |> string() |> padStart(6, '0')
_ -> year |> string() |> padStart(4, '0')
}
month |> string() |> padStart(2, '0')
day |> string() |> padStart(2, '0')
hour |> string() |> padStart(2, '0')
minute |> string() |> padStart(2, '0')
second |> int() |> string() |> padStart(2, '0')
if millis := round((second * 1000) % 1000) {
0 -> ''
_ -> '.' + millis |> string()
}
if {
tzOffset = 0 -> 'Z'
tzOffset > 0 -> '+' << '{{0}}:{{1}}' |> fmtFormat(
string(int(tzOffset / 60)) |> padStart(2, '0')
string(tzOffset % 60) |> padStart(2, '0')
)
_ -> '-' << '{{0}}:{{1}}' |> fmtFormat(
string(int(-tzOffset / 60)) |> padStart(2, '0')
string(-tzOffset % 60) |> padStart(2, '0')
)
}
)
}
fn _parseTZOffset(offsetString) if [hh, mm] := offsetString |> split(':') |> map(int) {
[], [_], [?, _], [_, ?] -> ?
_ -> hh * 60 + mm
}
fn parse(s) if [date, clock] := s |> split('T') {
[], [_]
[?, _], [_, ?] -> ?
_ -> if [year, month, day] := date |> split('-') |> map(int) {
[], [_], [_, _]
[?, _, _], [_, ?, _], [_, _, ?] -> ?
_ -> if [hour, minute, second] := clock |> take(8) |> split(':') |> map(int) {
[], [_], [_, _]
[?, _, _], [_, ?, _], [_, _, ?] -> ?
_ -> {
[_, maybeMillis] := clock |> split('.') |> map(fn(s) s |> take(3)) |> map(int)
tzOffset := if {
clock |> strContains?('+') ->
_parseTZOffset(clock |> slice(clock |> strIndexOf('+') + 1))
clock |> strContains?('-') -> if parsed :=
_parseTZOffset(clock |> slice(clock |> strIndexOf('-') + 1)) {
? -> ?
_ -> -parsed
}
_ -> 0
}
if tzOffset != ? -> {
year: year
month: month
day: day
hour: hour
minute: minute
second: second + (maybeMillis |> default(0)) / 1000
tzOffset: tzOffset
}
}
}
}
}