Skip to main content

sage_runtime/stdlib/
time.rs

1//! Time helper functions for the Sage standard library.
2
3use chrono::{DateTime, TimeZone, Utc};
4use std::time::{SystemTime, UNIX_EPOCH};
5
6/// Get the current time in milliseconds since Unix epoch.
7#[must_use]
8pub fn now_ms() -> i64 {
9    SystemTime::now()
10        .duration_since(UNIX_EPOCH)
11        .expect("time went backwards")
12        .as_millis() as i64
13}
14
15/// Get the current time in seconds since Unix epoch.
16#[must_use]
17pub fn now_s() -> i64 {
18    SystemTime::now()
19        .duration_since(UNIX_EPOCH)
20        .expect("time went backwards")
21        .as_secs() as i64
22}
23
24/// Format a Unix timestamp (in milliseconds) using the given format string.
25///
26/// Uses chrono format specifiers:
27/// - `%Y` = year (4 digits)
28/// - `%m` = month (01-12)
29/// - `%d` = day (01-31)
30/// - `%H` = hour (00-23)
31/// - `%M` = minute (00-59)
32/// - `%S` = second (00-59)
33/// - `%F` = ISO date (YYYY-MM-DD)
34/// - `%T` = ISO time (HH:MM:SS)
35#[must_use]
36pub fn format_timestamp(timestamp_ms: i64, format: &str) -> String {
37    let secs = timestamp_ms / 1000;
38    let nanos = ((timestamp_ms % 1000) * 1_000_000) as u32;
39    let dt: DateTime<Utc> = Utc.timestamp_opt(secs, nanos).unwrap();
40    dt.format(format).to_string()
41}
42
43/// Parse a string into a Unix timestamp (in milliseconds) using the given format.
44///
45/// Uses chrono format specifiers (see `format_timestamp`).
46pub fn parse_timestamp(s: &str, format: &str) -> Result<i64, String> {
47    let dt = DateTime::parse_from_str(s, format)
48        .or_else(|_| {
49            // Try parsing as UTC without timezone
50            chrono::NaiveDateTime::parse_from_str(s, format).map(|d| d.and_utc().fixed_offset())
51        })
52        .map_err(|e| {
53            format!(
54                "failed to parse timestamp '{}' with format '{}': {}",
55                s, format, e
56            )
57        })?;
58    Ok(dt.timestamp_millis())
59}
60
61// Time constants (in milliseconds)
62pub const MS_PER_SECOND: i64 = 1000;
63pub const MS_PER_MINUTE: i64 = 60_000;
64pub const MS_PER_HOUR: i64 = 3_600_000;
65pub const MS_PER_DAY: i64 = 86_400_000;
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn test_now_ms() {
73        let ms1 = now_ms();
74        // Ensure time has advanced
75        let ms2 = now_ms();
76        assert!(ms2 >= ms1);
77    }
78
79    #[test]
80    fn test_now_s() {
81        let s = now_s();
82        // Should be a reasonable timestamp
83        assert!(s > 1_700_000_000); // After 2023
84    }
85
86    #[test]
87    fn test_format_timestamp() {
88        // 2024-01-15 10:50:00 UTC
89        let ts = 1705315800000_i64;
90        assert_eq!(format_timestamp(ts, "%Y-%m-%d"), "2024-01-15");
91        assert_eq!(format_timestamp(ts, "%H:%M:%S"), "10:50:00");
92    }
93
94    #[test]
95    fn test_parse_timestamp() {
96        // Parse a date with timezone
97        let result = parse_timestamp("2024-01-15 10:50:00 +0000", "%Y-%m-%d %H:%M:%S %z");
98        assert!(result.is_ok());
99        assert_eq!(result.unwrap(), 1705315800000);
100    }
101
102    #[test]
103    fn test_constants() {
104        assert_eq!(MS_PER_SECOND, 1000);
105        assert_eq!(MS_PER_MINUTE, 60 * 1000);
106        assert_eq!(MS_PER_HOUR, 60 * 60 * 1000);
107        assert_eq!(MS_PER_DAY, 24 * 60 * 60 * 1000);
108    }
109}