///|
fn decimal(source : String) -> Int raise InputError {
if source == "" {
raise InputError("empty decimal")
}
let mut value = 0
for c in source {
if c < '0' || c > '9' {
raise InputError("invalid decimal")
}
value = value * 10 + c.to_int() - 48
}
value
}
///|
pub fn timestamp(value : Value) -> Double raise {
if value is Number(n) {
return num(Number(n))
}
let text = str(value)
if text.length() < 20 {
raise InputError("timestamp must be RFC3339")
}
let year = decimal(text.substring(start=0, end=4))
let month = decimal(text.substring(start=5, end=7))
let day = decimal(text.substring(start=8, end=10))
let hour = decimal(text.substring(start=11, end=13))
let minute = decimal(text.substring(start=14, end=16))
let second = decimal(text.substring(start=17, end=19))
if text[4] != 45 ||
text[7] != 45 ||
text[10] != 84 ||
text[13] != 58 ||
text[16] != 58 {
raise InputError("invalid timestamp separators")
}
if year < 1 ||
month < 1 ||
month > 12 ||
hour > 23 ||
minute > 59 ||
second > 59 {
raise InputError("timestamp component out of range")
}
let lengths = [
31,
if year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) {
29
} else {
28
},
31,
30,
31,
30,
31,
31,
30,
31,
30,
31,
]
if day < 1 || day > lengths[month - 1] {
raise InputError("invalid calendar day")
}
let adjusted = year - (if month <= 2 { 1 } else { 0 })
let era = adjusted / 400
let yoe = adjusted - era * 400
let m = month + (if month > 2 { -3 } else { 9 })
let doy = (153 * m + 2) / 5 + day - 1
let days = era * 146097 + yoe * 365 + yoe / 4 - yoe / 100 + doy - 719468
let mut pos = 19
let mut fraction = 0.0
if text[pos] == 46 {
let start = pos
pos += 1
while pos < text.length() && text[pos] >= 48 && text[pos] <= 57 {
pos += 1
}
if pos == start + 1 {
raise InputError("empty timestamp fraction")
}
fraction = num(parse_value("0" + text.substring(start~, end=pos)))
}
let mut offset = 0
let suffix = text.substring(start=pos)
if suffix != "Z" {
if suffix.length() != 6 ||
(suffix[0] != 43 && suffix[0] != 45) ||
suffix[3] != 58 {
raise InputError("invalid timestamp timezone")
}
let h = decimal(suffix.substring(start=1, end=3))
let m = decimal(suffix.substring(start=4, end=6))
if h > 23 || m > 59 {
raise InputError("timezone out of range")
}
offset = (h * 60 + m) * 60 * (if suffix[0] == 43 { 1 } else { -1 })
}
days.to_double() * 86400.0 +
(hour * 3600 + minute * 60 + second - offset).to_double() +
fraction
}