use super::funejson::Value; use super::dates_add_days::epoch_day_from_iso; fn num(b: &[u8], from: usize, to: usize) -> i64 { b[from..to].iter().fold(0, |n, d| n * 10 + i64::from(d - b'0')) } /// An RFC 3339 date-time to Unix seconds, parsed by position. /// /// # Panics /// Panics on anything that is not an RFC 3339 date-time with a zone, or on /// a date that does not exist. pub fn iso_to_unix(timestamp: &str) -> i64 { let bad = || -> ! { panic!( "\"{}\" is not an RFC 3339 timestamp such as 2026-09-26T12:00:00Z", timestamp ) }; // Bytes: every accepted character is ASCII, so a multi-byte character // fails a check wherever it sits, as it does in the other languages. let t = timestamp.as_bytes(); if t.len() < 20 { bad(); } for i in [0, 1, 2, 3, 5, 6, 8, 9, 11, 12, 14, 15, 17, 18] { if !t[i].is_ascii_digit() { bad(); } } if t[4] != b'-' || t[7] != b'-' || (t[10] != b'T' && t[10] != b't') || t[13] != b':' || t[16] != b':' { bad(); } let mut i = 19; if t[i] == b'.' { i += 1; let start = i; while i < t.len() && t[i].is_ascii_digit() { i += 1; } if i == start { bad(); } } let offset: i64; if i < t.len() && (t[i] == b'Z' || t[i] == b'z') && i + 1 == t.len() { offset = 0; } else if i < t.len() && (t[i] == b'+' || t[i] == b'-') && i + 6 == t.len() && t[i + 1].is_ascii_digit() && t[i + 2].is_ascii_digit() && t[i + 3] == b':' && t[i + 4].is_ascii_digit() && t[i + 5].is_ascii_digit() { let oh = num(t, i + 1, i + 3); let om = num(t, i + 4, i + 6); if oh > 23 || om > 59 { bad(); } offset = if t[i] == b'+' { 1 } else { -1 } * (oh * 3600 + om * 60); } else { bad(); } let h = num(t, 11, 13); let m = num(t, 14, 16); let s = num(t, 17, 19); if h > 23 || m > 59 || s > 59 { bad(); } epoch_day_from_iso(×tamp[0..10]) * 86400 + h * 3600 + m * 60 + s - offset } pub fn fune_vector(args: &[Value]) -> Value { Value::Int(iso_to_unix(args[0].as_str())) }