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