Skip to main content

elph_core/utils/
time.rs

1//! UTC timestamp helpers.
2
3/// Returns the current UTC timestamp in RFC 3339 format.
4pub fn utc_rfc3339_now() -> String {
5    let duration = std::time::SystemTime::now()
6        .duration_since(std::time::UNIX_EPOCH)
7        .expect("system clock before unix epoch");
8    format_rfc3339(duration.as_secs(), duration.subsec_nanos())
9}
10
11fn format_rfc3339(secs: u64, nanos: u32) -> String {
12    let (year, month, day, hour, minute, second) = unix_secs_to_utc(secs);
13    if nanos == 0 {
14        return format!("{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}Z");
15    }
16
17    format!(
18        "{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}.{nanos:09}Z",
19        nanos = nanos
20    )
21}
22
23fn unix_secs_to_utc(secs: u64) -> (u32, u32, u32, u32, u32, u32) {
24    let days = secs / 86_400;
25    let rem = secs % 86_400;
26    let hour = (rem / 3_600) as u32;
27    let minute = ((rem % 3_600) / 60) as u32;
28    let second = (rem % 60) as u32;
29
30    let mut year = 1970u32;
31    let mut day_of_year = days;
32
33    loop {
34        let days_in_year = if is_leap_year(year) { 366 } else { 365 };
35        if day_of_year < days_in_year {
36            break;
37        }
38        day_of_year -= days_in_year;
39        year += 1;
40    }
41
42    let mut month = 1u32;
43    for days_in_month in month_lengths(year) {
44        if day_of_year < days_in_month {
45            break;
46        }
47        day_of_year -= days_in_month;
48        month += 1;
49    }
50
51    (year, month, day_of_year as u32 + 1, hour, minute, second)
52}
53
54fn is_leap_year(year: u32) -> bool {
55    (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
56}
57
58fn month_lengths(year: u32) -> [u64; 12] {
59    [
60        31,
61        if is_leap_year(year) { 29 } else { 28 },
62        31,
63        30,
64        31,
65        30,
66        31,
67        31,
68        30,
69        31,
70        30,
71        31,
72    ]
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn utc_rfc3339_now_ends_with_z() {
81        let stamp = utc_rfc3339_now();
82        assert!(stamp.ends_with('Z'));
83        assert!(stamp.contains('T'));
84    }
85}