practicaltimestamp/
unix_timestamp.rs

1use super::{
2    result,
3    util,
4};
5
6#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
7pub struct UnixTimestamp(i64);
8
9impl UnixTimestamp {
10    pub const MIN: UnixTimestamp = Self::new(0);
11    pub const MAX: UnixTimestamp = Self::new(253_402_300_800);
12
13    const fn new(value: i64) -> Self {
14        Self(value)
15    }
16
17    #[cfg(feature = "std")]
18    pub fn now() -> Self {
19        super::std_support::system_time_now()
20    }
21
22    pub const fn checked_from_unix_timestamp(timestamp: i64) -> Option<Self> {
23        Self::from_unix_timestamp(timestamp).ok()
24    }
25
26    pub const fn from_unix_timestamp(timestamp: i64) -> result::TimestampResult {
27        if util::is_supported_unix_timestamp(timestamp) {
28            result::TimestampResult::TimestampOk(Self::new(timestamp))
29        } else {
30            result::TimestampResult::OverflowErr(timestamp)
31        }
32    }
33
34    pub const fn unix_timestamp(self) -> i64 {
35        self.0
36    }
37
38    pub const fn midnight(self) -> Self {
39        Self::new(self.unix_timestamp() - self.seconds_since_midnight())
40    }
41
42    pub const fn seconds_since_midnight(self) -> i64 {
43        (self.unix_timestamp() as u64 % util::SECONDS_PER_DAY as u64) as i64
44    }
45    
46    pub const fn checked_add(self, seconds: i64) -> Option<Self> {
47        let timestamp = self.unix_timestamp().wrapping_add(seconds);
48        Self::checked_from_unix_timestamp(timestamp)
49    }
50
51    pub const fn checked_sub(self, seconds: i64) -> Option<Self> {
52        let timestamp = self.unix_timestamp().wrapping_sub(seconds);
53        Self::checked_from_unix_timestamp(timestamp)
54    }
55
56    pub const fn saturating_add(self, seconds: i64) -> Self {
57        let timestamp = self.unix_timestamp().saturating_add(seconds); // MSRV 1.47
58        Self::from_unix_timestamp(timestamp).unwrap()
59    }
60
61    pub const fn saturating_sub(self, seconds: i64) -> Self {
62        let timestamp = self.unix_timestamp().saturating_sub(seconds); // MSRV 1.47
63        Self::from_unix_timestamp(timestamp).unwrap()
64    }
65
66    pub const fn checked_from_year_month_day(year: u16, month: u8, day: u8) -> Option<Self> {
67        if util::is_valid_year_month_day(year, month, day) {
68            Self::from_year_month_day(year, month, day).ok()
69        } else {
70            None
71        }
72    }
73
74    // Only valid for dates greater than or equal to 0000-1-1
75    // [section 2.2.1](https://www.researchgate.net/publication/316558298_Date_Algorithms)
76    pub const fn from_year_month_day(year: u16, month: u8, day: u8) -> result::TimestampResult {
77        let (adj_year, adj_month, day) = if month < 3 {
78            (year as i32 + 399, month as i32 + 12, day as i32)
79        } else {
80            (year as i32 + 400, month as i32, day as i32)
81        };
82        // f = (153 * adj_month - 457) / 5
83        let f = (979 * adj_month - 2_918) >> 5;
84        let julian_day_number = day + f + 365 * adj_year + adj_year / 4 - adj_year / 100 + adj_year / 400 + 1_575_022;
85        Self::from_julian_day_number(julian_day_number)
86    }
87
88    // Only valid for dates greater than or equal to 0000-1-1
89    // [section 3.2.1/3.3.1](https://www.researchgate.net/publication/316558298_Date_Algorithms)
90    pub const fn to_year_month_day(self) -> (u16, u8, u8) {
91        let julian_day_number = self.julian_day_number() as u32;
92        let z = julian_day_number - 1_575_022;
93        let h = 100 * z - 25;
94        let a = h / 3_652_425;
95        let b = a - a / 4;
96        let adj_year = (100 * b + h) / 36_525;
97        let c = b + z - 365 * adj_year - adj_year / 4;
98        // adj_month = (5 * c + 456) / 153
99        let adj_month = (535 * c + 48_950) >> 14;
100        // f = (153 * adj_month - 457) / 5
101        let f = (979 * adj_month - 2_918) >> 5;
102        let day = c - f;
103        let (year, month) = if adj_month > 12 {
104            (adj_year - 399, adj_month - 12)
105        } else {
106            (adj_year - 400, adj_month)
107        };
108        (year as u16, month as u8, day as u8)
109    }
110
111    pub const fn checked_from_year_ordinal(year: u16, ordinal: u16) -> Option<Self> {
112        if util::is_valid_year_ordinal(year, ordinal) {
113            Self::from_year_ordinal(year, ordinal).ok()
114        } else {
115            None
116        }
117    }
118
119    // I derived this algorithm based on the doy_from_month equation
120    // [Computing day-of-year...](http://howardhinnant.github.io/date_algorithms.html#days_from_civil)
121    pub const fn from_year_ordinal(year: u16, ordinal: u16) -> result::TimestampResult {
122        let ordinal = ordinal as u64;
123        let last_day_of_february = 59 + util::is_leap_year(year) as u64;
124        // adj_month = (10 * adj_ordinal - 5) / 306
125        let (adj_ordinal, adj_month, month) = if ordinal > last_day_of_february {
126            let adj_ordinal = ordinal - last_day_of_february;
127            let adj_month = (1_071 * adj_ordinal - 535) >> 15;
128            (adj_ordinal, adj_month, (adj_month + 3) as u8)
129        } else {
130            let adj_ordinal = ordinal + 306;
131            let adj_month = (1_071 * adj_ordinal - 535) >> 15;
132            (adj_ordinal, adj_month, (adj_month - 9) as u8)
133        };
134        // f = (306 * adj_month + 5) / 10
135        let f = (979 * adj_month + 16) >> 5;
136        let day = adj_ordinal - f;
137        Self::from_year_month_day(year, month, day as u8)
138    }
139
140    // [Eliminating the Lookup Table](https://blog.reverberate.org/2020/05/12/optimizing-date-algorithms.html)
141    pub const fn to_year_ordinal(self) -> (u16, u16) {
142        let (year, month, day) = self.to_year_month_day();
143        let (month, day) = (month as u64, day as u64);
144        // f = (306 * adj_month + 5) / 10
145        let ordinal = if month >= 3 {
146            ((979 * (month - 3) + 16) >> 5) + day + 59 + util::is_leap_year(year) as u64
147        } else {
148            ((979 * (month + 9) + 16) >> 5) + day - 306
149        };
150        (year, ordinal as u16)
151    }
152
153    pub const fn checked_from_julian_day_number(julian_day_number: i32) -> Option<Self> {
154        Self::from_julian_day_number(julian_day_number).ok()
155    }
156
157    pub const fn from_julian_day_number(julian_day_number: i32) -> result::TimestampResult {
158        let timestamp = (julian_day_number as i64 - util::UNIX_EPOCH_JULIAN_DAY_NUMBER as i64) * util::SECONDS_PER_DAY;
159        Self::from_unix_timestamp(timestamp)
160    }
161    
162    pub const fn julian_day_number(self) -> i32 {
163        (self.unix_timestamp() as u64 / util::SECONDS_PER_DAY as u64) as i32 + util::UNIX_EPOCH_JULIAN_DAY_NUMBER
164    }
165
166    pub const fn weekday(self) -> util::Weekday {
167        // (days_since_epoch + 3) % 7
168        let adj_days = self.unix_timestamp() as u64 / util::SECONDS_PER_DAY as u64 + 3;
169        let wd = adj_days - (((adj_days * 613_566_757) >> 32) * 7);
170        util::Weekday::new(wd)
171    }
172}