chrono_lite/
lib.rs

1#![no_std]
2#![allow(clippy::needless_return)]
3
4//! Tiny libc-based [`chrono`](https://docs.rs/chrono) replacement
5
6use core::ffi::{c_int, c_long, c_char};
7#[cfg(unix)] use core::mem::MaybeUninit;
8
9/// 64-bit signed integer that represents a Unix timestamp
10#[allow(non_camel_case_types)]
11pub type time_t = i64;
12
13/// Broken-down time structure
14#[repr(C)]
15#[derive(Default, Debug, Clone, Copy)]
16pub struct Tm {
17    /// [0, 60] Seconds
18    pub tm_sec: c_int,
19    /// [0, 59] Minutes
20    pub tm_min: c_int,
21    /// [0, 23] Hour
22    pub tm_hour: c_int,
23    /// [1, 31] Day of the month
24    pub tm_mday: c_int,
25    /// [0, 11] Month (January = 0)
26    pub tm_mon: c_int,
27    /// Year minus 1900
28    pub tm_year: c_int,
29    /// [0, 6] Day of the week (Sunday = 0)
30    pub tm_wday: c_int,
31    /// [0, 365] Day of the year (Jan/01 = 0)
32    pub tm_yday: c_int,
33    /// Daylight savings flag
34    pub tm_isdst: c_int,
35    /// Seconds East of UTC
36    pub tm_gmtoff: c_long,
37    /// Timezone abbreviation
38    pub tm_zone: *const c_char,
39}
40
41mod sys {
42    use super::{Tm, time_t};
43    unsafe extern "C" {
44        pub fn time(tloc: *mut time_t) -> time_t;
45        #[cfg(windows)]
46        pub fn gmtime(timep: *const time_t) -> *mut Tm;
47        #[cfg(unix)]
48        pub fn gmtime_r(timep: *const time_t, result: *mut Tm) -> *mut Tm;
49        #[cfg(windows)]
50        pub fn localtime(timep: *const time_t) -> *mut Tm;
51        #[cfg(unix)]
52        pub fn localtime_r(timep: *const time_t, result: *mut Tm) -> *mut Tm;
53        #[cfg(windows)]
54        pub fn mkgmtime(timeptr: *mut Tm) -> time_t;
55        #[cfg(unix)]
56        pub fn timegm(tm: *mut Tm) -> time_t;
57    }
58}
59
60/// Retrieves current system time as a Unix timestamp
61pub fn time() -> time_t {
62    unsafe { sys::time(core::ptr::null_mut()) }
63}
64
65/// Converts provided unix timestamp into broken-down [`Tm`] struct, using GMT timezone
66///
67/// Returns None on overflows, or (on windows) when timestamp is larger than `23:59:59, December 31, 3000, UTC`
68pub fn gmtime(time: time_t) -> Option<Tm> {
69    #[cfg(windows)] unsafe {
70        let ret = sys::gmtime(&time as *const time_t);
71        if ret.is_null() { return None; }
72        return Some(*ret);
73    }
74
75    #[cfg(unix)] unsafe {
76        let mut tm = MaybeUninit::zeroed();
77        let ret = sys::gmtime_r(&time as *const time_t, tm.as_mut_ptr());
78        if ret.is_null() { return None; }
79        return Some(tm.assume_init());
80    };
81}
82
83/// Same as [`gmtime`], but uses local timezone
84pub fn localtime(time: time_t) -> Option<Tm> {
85    #[cfg(windows)] unsafe {
86        let ret = sys::localtime(&time as *const time_t);
87        if ret.is_null() { return None; }
88        return Some(*ret);
89    }
90
91    #[cfg(unix)] unsafe {
92        let mut tm = MaybeUninit::zeroed();
93        let ret = sys::localtime_r(&time as *const time_t, tm.as_mut_ptr());
94        if ret.is_null() { return None; }
95        return Some(tm.assume_init());
96    };
97}
98
99/// Convert broken-down [`Tm`] into a unix timestamp
100///
101/// Returns `None` on overflow, or (on windows) when it is out of range.
102/// Range: from midnight, January 1, 1970, UTC to 23:59:59, December 31, 3000, UTC
103pub fn timegm(mut tm: Tm) -> Option<time_t> {
104    let ret;
105    #[cfg(windows)] unsafe {
106        ret = sys::mkgmtime(&mut tm as *mut Tm);
107    }
108
109    #[cfg(unix)] unsafe {
110        ret = sys::timegm(&mut tm as *mut Tm);
111    }
112
113    if ret == -1 { return None; }
114    Some(ret)
115}
116
117#[cfg(test)]
118mod test {
119    use super::*;
120
121    #[test]
122    fn test_gmtime() {
123        let tm = gmtime(1740607859).unwrap();
124        // 1740607859 = Wed 26 Feb 2025 22:10:59 GMT
125        assert_eq!(3, tm.tm_wday);   // Wed
126        assert_eq!(26, tm.tm_mday);
127        assert_eq!(1, tm.tm_mon);    // 26 Feb
128        assert_eq!(125, tm.tm_year); // 2025
129        assert_eq!(22, tm.tm_hour);
130        assert_eq!(10, tm.tm_min);
131        assert_eq!(59, tm.tm_sec);   // 22:10:59
132    }
133
134    #[test]
135    fn test_timegm() {
136        let tm = Tm {
137            tm_year: 2026 - 1900,
138            tm_mon: 0,
139            tm_mday: 3,
140            tm_hour: 23,
141            tm_min: 6,
142            tm_sec: 46,
143            ..Default::default()
144        };
145        assert_eq!(1767481606, timegm(tm).unwrap());
146    }
147}