pr_01m47d24b0e6n91zwymwxg0vpx/crates/contracts/src/time.rs
| 1 | //! Timestamps. Everywhere in g1t, in storage and on the wire, a time is an |
| 2 | //! RFC 3339 string in UTC with millisecond precision, such as |
| 3 | //! `2026-10-02T05:16:19.000Z`. Strings in this one fixed format sort and |
| 4 | //! compare as text, so they need no parsing to be ordered. |
| 5 | |
| 6 | /// SQLite's expression for the current time in g1t's format. |
| 7 | pub const SQL_NOW: &str = "strftime('%Y-%m-%dT%H:%M:%fZ', 'now')"; |
| 8 | |
| 9 | /// SQLite's expression for a time `seconds` from now, in g1t's format. |
| 10 | pub fn sql_after(seconds: u64) -> String { |
| 11 | format!("strftime('%Y-%m-%dT%H:%M:%fZ', 'now', '+{seconds} seconds')") |
| 12 | } |
| 13 | |
| 14 | /// Milliseconds since the Unix epoch as an RFC 3339 UTC timestamp. |
| 15 | pub fn rfc3339(ms: u64) -> String { |
| 16 | let (seconds, millis) = (ms / 1000, ms % 1000); |
| 17 | let (days, rest) = (seconds / 86_400, seconds % 86_400); |
| 18 | let (hour, minute, second) = (rest / 3600, rest % 3600 / 60, rest % 60); |
| 19 | |
| 20 | // Days since the epoch to a calendar date, after Howard Hinnant's |
| 21 | // civil_from_days. The era arithmetic starts years in March. |
| 22 | let z = days as i64 + 719_468; |
| 23 | let era = z.div_euclid(146_097); |
| 24 | let day_of_era = z.rem_euclid(146_097); |
| 25 | let year_of_era = |
| 26 | (day_of_era - day_of_era / 1460 + day_of_era / 36_524 - day_of_era / 146_096) / 365; |
| 27 | let day_of_year = day_of_era - (365 * year_of_era + year_of_era / 4 - year_of_era / 100); |
| 28 | let shifted_month = (5 * day_of_year + 2) / 153; |
| 29 | let day = day_of_year - (153 * shifted_month + 2) / 5 + 1; |
| 30 | let month = if shifted_month < 10 { |
| 31 | shifted_month + 3 |
| 32 | } else { |
| 33 | shifted_month - 9 |
| 34 | }; |
| 35 | let year = year_of_era + era * 400 + i64::from(month <= 2); |
| 36 | |
| 37 | format!("{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}.{millis:03}Z") |
| 38 | } |
| 39 | |
| 40 | #[cfg(test)] |
| 41 | mod tests { |
| 42 | use super::*; |
| 43 | |
| 44 | #[test] |
| 45 | fn formats_known_instants() { |
| 46 | assert_eq!(rfc3339(0), "1970-01-01T00:00:00.000Z"); |
| 47 | assert_eq!(rfc3339(951_782_400_000), "2000-02-29T00:00:00.000Z"); |
| 48 | assert_eq!(rfc3339(1_790_918_179_123), "2026-10-02T05:16:19.123Z"); |
| 49 | assert_eq!(rfc3339(4_102_444_799_999), "2099-12-31T23:59:59.999Z"); |
| 50 | } |
| 51 | |
| 52 | #[test] |
| 53 | fn later_times_sort_later_as_text() { |
| 54 | let times: Vec<String> = [0, 999, 1000, 86_399_999, 86_400_000, 1_790_918_179_123] |
| 55 | .into_iter() |
| 56 | .map(rfc3339) |
| 57 | .collect(); |
| 58 | assert!(times.windows(2).all(|pair| pair[0] < pair[1])); |
| 59 | } |
| 60 | } |