devela/phys/time/
unix.rs

1// devela::phys::time::unix
2//
3// # LINKS
4// - https://en.wikipedia.org/wiki/Unix_time
5// - https://doc.rust-lang.org/std/time/struct.SystemTime.html
6// - https://www.gnu.org/software/libc/manual/html_node/Getting-the-Time.html
7// - https://www.gnu.org/software/libc/manual/html_node/Time-Functions-Example.html
8//
9//! Unix time.
10//
11
12use crate::{
13    is_leap_year, Debug, Display, FmtResult, Formatter, Month, TimeSplit, TimeSplitYearSec,
14    TryFromIntError,
15};
16
17/// 64-bit Unix time, supporting negative values.
18///
19/// Stores the number of seconds relative to the Unix Epoch (`1970-01-01 00:00:00 UTC`).
20#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
21pub struct UnixTimeI64 {
22    /// The number of seconds relative the Unix Epoch.
23    pub seconds: i64,
24}
25
26/// 32-bit Unix time, supporting only non-negative values.
27///
28/// Stores the number of seconds since the Unix Epoch (`1970-01-01 00:00:00 UTC`).
29///
30/// It can represent time from `1970-01-01_00:00:00` to `2106-02-07_06:28:15`.
31#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
32pub struct UnixTimeU32 {
33    /// The number of seconds since the Unix Epoch.
34    pub seconds: u32,
35}
36
37impl UnixTimeI64 {
38    /// Returns a new `UnixTimeI64` from the given amount of seconds.
39    ///
40    /// # Examples
41    /// ```
42    /// # use devela::UnixTimeI64;
43    /// assert_eq!["1970-01-01_00:00:01", UnixTimeI64::new(1).to_string()];
44    /// assert_eq!["1969-12-31_23:59:59", UnixTimeI64::new(-1).to_string()];
45    /// assert_eq!["2038-01-19_03:14:07", UnixTimeI64::new(i32::MAX as i64).to_string()];
46    /// assert_eq!["2106-02-07_06:28:15", UnixTimeI64::new(u32::MAX as i64).to_string()];
47    /// assert_eq!["1833-11-24_17:31:45", UnixTimeI64::new(u32::MAX as i64 * -1).to_string()];
48    /// ```
49    pub fn new(seconds: i64) -> Self {
50        Self { seconds }
51    }
52
53    /// Returns a new `UnixTimeI64` anchored to the current second.
54    #[cfg(feature = "std")]
55    // all(not(feature = "std"), feature = "unsafe", feature = "libc")
56    #[cfg_attr(feature = "nightly_doc", doc(cfg(feature = "std")))]
57    // all(feature = "no_std", feature = "unsafe", feature = "libc")
58    pub fn now() -> Self {
59        Self { seconds: Self::unix_time_64() }
60    }
61
62    /// Splits the `UnixTimeI64` into `{ y, mo, d, h, m, s }`.
63    ///
64    /// # Examples
65    /// ```
66    /// # use devela::UnixTimeI64;
67    /// assert_eq![(1970, 1, 1, 0, 0, 1), UnixTimeI64::new(1).split().to_tuple_y_s().unwrap()];
68    /// assert_eq![(1969, 12, 31, 23, 59, 59),
69    ///     UnixTimeI64::new(-1).split().to_tuple_y_s().unwrap()];
70    /// ```
71    // 72b
72    pub const fn split(&self) -> TimeSplitYearSec<i32, u8, u8, u8, u8, u8> {
73        let seconds_per_minute: u32 = 60;
74        let minutes_per_hour: u32 = 60;
75        let hours_per_day: u32 = 24;
76        let days_per_year: u32 = 365;
77
78        let mut seconds_left = self.seconds.abs();
79        let mut year = if self.seconds >= 0 { 1970 } else { 1969 };
80        let mut leap = is_leap_year(year);
81
82        while seconds_left
83            >= (hours_per_day * minutes_per_hour * seconds_per_minute * days_per_year) as i64
84        {
85            leap = is_leap_year(year);
86            let days_in_year = if leap { 366 } else { 365 };
87            seconds_left -=
88                (hours_per_day * minutes_per_hour * seconds_per_minute * days_in_year) as i64;
89
90            if self.seconds >= 0 {
91                year += 1;
92            } else {
93                year -= 1;
94            }
95        }
96
97        let mut month = Month::January;
98        while seconds_left
99            >= (hours_per_day * minutes_per_hour * seconds_per_minute * month.len(leap) as u32)
100                as i64
101        {
102            seconds_left -=
103                (hours_per_day * minutes_per_hour * seconds_per_minute * month.len(leap) as u32)
104                    as i64;
105            month = month.next();
106        }
107
108        let day = (seconds_left / (hours_per_day * minutes_per_hour * seconds_per_minute) as i64)
109            as u8
110            + 1;
111        seconds_left %= (hours_per_day * minutes_per_hour * seconds_per_minute) as i64;
112
113        let hour = seconds_left / (minutes_per_hour * seconds_per_minute) as i64;
114        seconds_left %= (minutes_per_hour * seconds_per_minute) as i64;
115
116        let minute = seconds_left / seconds_per_minute as i64;
117        let second = seconds_left % seconds_per_minute as i64;
118
119        if self.seconds >= 0 {
120            TimeSplit::new_year_sec(
121                year,
122                month.number(),
123                day,
124                hour as u8,
125                minute as u8,
126                second as u8,
127            )
128        } else {
129            TimeSplit::new_year_sec(
130                year,
131                13 - month.number(),
132                Month::December.previous_nth(month.index()).len(leap) - day + 1,
133                23 - hour as u8,
134                59 - minute as u8,
135                60 - second as u8,
136            )
137        }
138    }
139}
140
141// private functions
142impl UnixTimeI64 {
143    // Returns the number of seconds since `1970-01-01 00:00:00 UTC`.
144    #[cfg(feature = "std")]
145    fn unix_time_64() -> i64 {
146        use crate::SystemTime;
147        SystemTime::now()
148            .duration_since(SystemTime::UNIX_EPOCH)
149            .unwrap()
150            .as_secs()
151            .min(i64::MAX as u64) as i64
152    }
153
154    // // Returns the number of seconds since 1970-01-01 00:00:00 UTC.
155    // //
156    // // Because of `u32` this will only work until `06:28:15 UTC on 07 February 2106`.
157    // #[cfg(all(not(feature = "std"), feature = "unsafe", feature = "libc"))]
158    // fn unix_time_64() -> i64 {
159    //     // https://docs.rs/libc/latest/libc/fn.time.html
160    //     #[allow(clippy::unnecessary_cast)] // could be i32 in other platforms?
161    //     // SAFETY: safe since we pass a null pointer and do not dereference anything.
162    //     unsafe {
163    //         libc::time(core::ptr::null_mut()) as i64
164    //     }
165    // }
166}
167
168impl UnixTimeU32 {
169    /// Returns a new `UnixTimeU32` from the given amount of seconds.
170    ///
171    /// # Examples
172    /// ```
173    /// # use devela::UnixTimeU32;
174    /// assert_eq!["1970-01-01_00:00:00", UnixTimeU32::new(0).to_string()];
175    /// assert_eq!["2106-02-07_06:28:15", UnixTimeU32::new(u32::MAX).to_string()];
176    /// ```
177    pub fn new(seconds: u32) -> Self {
178        Self { seconds }
179    }
180
181    /// Returns a new `UnixTimeU32` anchored to the current second.
182    #[cfg(feature = "std")]
183    // all(not(feature = "std"), feature = "unsafe", feature = "libc")
184    #[cfg_attr(feature = "nightly_doc", doc(cfg(feature = "std")))]
185    // all(feature = "no_std", feature = "unsafe", feature = "libc")
186    pub fn now() -> Self {
187        Self { seconds: Self::unix_time_32() }
188    }
189
190    /// Splits the `UnixTimeU32` into `{ y, mo, d, h, m, s }`.
191    ///
192    /// # Examples
193    /// ```
194    /// # use devela::UnixTimeU32;
195    /// assert_eq![(1970, 1, 1, 0, 0, 1), UnixTimeU32::new(1).split().to_tuple_y_s().unwrap()];
196    /// assert_eq![(2038, 1, 19, 3, 14, 7),
197    ///     UnixTimeU32::new(i32::MAX as u32).split().to_tuple_y_s().unwrap()];
198    /// ```
199    pub const fn split(&self) -> TimeSplitYearSec<u16, u8, u8, u8, u8, u8> {
200        let seconds_per_minute: u32 = 60;
201        let minutes_per_hour: u32 = 60;
202        let hours_per_day: u32 = 24;
203        let days_per_year: u32 = 365;
204
205        let mut seconds_left = self.seconds;
206        let mut year = 1970;
207        let mut leap = is_leap_year(year);
208
209        while seconds_left
210            >= (hours_per_day * minutes_per_hour * seconds_per_minute * days_per_year)
211        {
212            year += 1;
213            leap = is_leap_year(year);
214            let days_in_year = if leap { 366 } else { 365 };
215            seconds_left -= hours_per_day * minutes_per_hour * seconds_per_minute * days_in_year;
216        }
217
218        let mut month = Month::January;
219        while seconds_left
220            >= hours_per_day * minutes_per_hour * seconds_per_minute * month.len(leap) as u32
221        {
222            seconds_left -=
223                hours_per_day * minutes_per_hour * seconds_per_minute * month.len(leap) as u32;
224            month = month.next();
225        }
226
227        let day = (seconds_left / (hours_per_day * minutes_per_hour * seconds_per_minute)) + 1;
228        seconds_left %= hours_per_day * minutes_per_hour * seconds_per_minute;
229
230        let hour = seconds_left / (minutes_per_hour * seconds_per_minute);
231        seconds_left %= minutes_per_hour * seconds_per_minute;
232
233        let minute = seconds_left / seconds_per_minute;
234        let second = seconds_left % seconds_per_minute;
235
236        TimeSplit::new_year_sec(
237            year as u16,
238            month.number(),
239            day as u8,
240            hour as u8,
241            minute as u8,
242            second as u8,
243        )
244    }
245}
246
247// private functions
248impl UnixTimeU32 {
249    // Returns the number of seconds since `1970-01-01 00:00:00 UTC`.
250    //
251    // Because of `u32` this will only work until `06:28:15 UTC on 07 February 2106`.
252    #[cfg(feature = "std")]
253    fn unix_time_32() -> u32 {
254        use crate::SystemTime;
255        SystemTime::now()
256            .duration_since(SystemTime::UNIX_EPOCH)
257            .unwrap()
258            .as_secs()
259            .min(u32::MAX as u64) as u32
260    }
261
262    // // Returns the number of seconds since 1970-01-01 00:00:00 UTC.
263    // //
264    // // Because of `u32` this will only work until `06:28:15 UTC on 07 February 2106`.
265    // #[cfg(all(not(feature = "std"), feature = "unsafe", feature = "libc"))]
266    // fn unix_time_32() -> u32 {
267    //     // SAFETY: safe since we pass a null pointer and do not dereference anything.
268    //     unsafe { libc::time(core::ptr::null_mut()).clamp(0, u32::MAX as i64) as u32 }
269    // }
270}
271
272impl Display for UnixTimeI64 {
273    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult<()> {
274        let TimeSplit { y, mo, d, h, m, s, .. } = self.split();
275        write![f, "{y:04}-{mo:02}-{d:02}_{h:02}:{m:02}:{s:02}"]
276    }
277}
278impl Debug for UnixTimeI64 {
279    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult<()> {
280        let TimeSplit { y, mo, d, h, m, s, .. } = self.split();
281        write![f, "UnixTimeI64 {{ {y:04}-{mo:02}-{d:02}_{h:02}:{m:02}:{s:02} }}"]
282    }
283}
284
285impl Display for UnixTimeU32 {
286    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult<()> {
287        let TimeSplit { y, mo, d, h, m, s, .. } = self.split();
288        write![f, "{y:04}-{mo:02}-{d:02}_{h:02}:{m:02}:{s:02}"]
289    }
290}
291
292impl Debug for UnixTimeU32 {
293    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult<()> {
294        let TimeSplit { y, mo, d, h, m, s, .. } = self.split();
295        write![f, "UnixTimeU32 {{ {y:04}-{mo:02}-{d:02}_{h:02}:{m:02}:{s:02} }}"]
296    }
297}
298
299impl From<UnixTimeU32> for UnixTimeI64 {
300    fn from(ut: UnixTimeU32) -> UnixTimeI64 {
301        UnixTimeI64 { seconds: ut.seconds.into() }
302    }
303}
304
305impl TryFrom<UnixTimeI64> for UnixTimeU32 {
306    type Error = TryFromIntError;
307
308    fn try_from(ut: UnixTimeI64) -> Result<UnixTimeU32, Self::Error> {
309        Ok(UnixTimeU32 { seconds: u32::try_from(ut.seconds)? })
310    }
311}
312
313#[cfg(feature = "std")]
314mod std_impls {
315    #[cfg(all(feature = "cast", feature = "error"))]
316    use crate::{Cast, DataOverflow, UnixTimeU32};
317    use crate::{SystemTime, SystemTimeError, UnixTimeI64};
318
319    impl TryFrom<SystemTime> for UnixTimeI64 {
320        type Error = SystemTimeError;
321
322        fn try_from(time: SystemTime) -> Result<Self, Self::Error> {
323            let duration = time.duration_since(SystemTime::UNIX_EPOCH)?;
324            Ok(UnixTimeI64 {
325                seconds: duration.as_secs() as i64, // Assuming the range is acceptable for i64
326            })
327        }
328    }
329
330    #[cfg(all(feature = "cast", feature = "error"))]
331    #[cfg_attr(feature = "nightly_doc", doc(cfg(all(feature = "cast", feature = "error"))))]
332    impl TryFrom<SystemTime> for UnixTimeU32 {
333        type Error = crate::TimeError;
334
335        fn try_from(time: SystemTime) -> Result<Self, Self::Error> {
336            let since = time.duration_since(SystemTime::UNIX_EPOCH)?;
337            let seconds = u32::try_from(since.as_secs())
338                .map_err(|_| DataOverflow(Cast(since.as_secs()).checked_cast_to_usize().ok()))?;
339            Ok(UnixTimeU32 { seconds })
340        }
341    }
342
343    // impl From<SystemTime> for UnixTimeI64 {
344    //     fn from(time: SystemTime) -> Self {
345    //         match time.duration_since(SystemTime::UNIX_EPOCH) {
346    //             Ok(duration) => UnixTimeI64 { seconds: duration.as_secs() as i64 },
347    //             Err(e) => UnixTimeI64 { seconds: -(e.duration().as_secs() as i64) },
348    //         }
349    //     }
350    // }
351    // impl From<SystemTime> for UnixTimeU32 {
352    //     fn from(time: SystemTime) -> Self {
353    //         let duration = time.duration_since(SystemTime::UNIX_EPOCH)
354    //             .expect("Time is before Unix epoch, which is unsupported for `UnixTimeU32`.");
355    //         UnixTimeU32 {
356    //             seconds: duration.as_secs() as u32, // Safe cast, as u32 covers the valid range for UnixTimeU32
357    //         }
358    //     }
359    // }
360}
361
362// Implements From<primitive> for UnixTime*
363macro_rules! impl_from_prim {
364    // for many
365    ($ut:ty, $($prim:ty),+) => { $( impl_from_prim![@ $ut, $prim]; )+ };
366    (@ $ut:ty, $prim:ty) => {
367        impl From<$prim> for $ut {
368            fn from(seconds: $prim) -> $ut {
369                Self { seconds: seconds.into() }
370            }
371        }
372    };
373}
374impl_from_prim![UnixTimeI64, i64, i32, i16, i8, u32, u16, u8];
375impl_from_prim![UnixTimeU32, u32, u16, u8];
376
377// Implements TryFrom<primitive> for UnixTime*
378macro_rules! impl_try_from_prim {
379    ($ut:ty, $($prim:ty),+) => { $( impl_try_from_prim![@ $ut, $prim]; )+ };
380    (@ $ut:ty, $prim:ty) => {
381        impl TryFrom<$prim> for $ut {
382            type Error = TryFromIntError;
383            fn try_from(seconds: $prim) -> Result<$ut, Self::Error> {
384                Ok(Self { seconds: seconds.try_into()? })
385            }
386        }
387    };
388}
389impl_try_from_prim![UnixTimeI64, u64, u128, usize, i128, isize];
390#[rustfmt::skip]
391impl_try_from_prim![UnixTimeU32, u64, u128, usize, i8, i16, i32, i64, i128, isize];