1use crate::util::t;
25
26#[inline]
30pub(crate) const fn is_leap_year(year: i16) -> bool {
31 let d = if year % 25 != 0 { 4 } else { 16 };
33 (year % d) == 0
34}
35
36#[inline]
38pub(crate) const fn days_in_month(year: i16, month: i8) -> i8 {
39 if month == 2 {
41 if is_leap_year(year) {
42 29
43 } else {
44 28
45 }
46 } else {
47 30 | (month ^ month >> 3)
48 }
49}
50
51#[inline(always)]
57#[allow(non_upper_case_globals, non_snake_case)]
58pub(crate) fn to_unix_epoch_day(year: i16, month: i8, day: i8) -> i32 {
59 const s: u32 = 82;
60 const K: u32 = 719468 + 146097 * s;
61 const L: u32 = 400 * s;
62
63 let year = year as u32;
64 let month = month as u32;
65 let day = day as u32;
66
67 let J = month <= 2;
68 let Y = year.wrapping_add(L).wrapping_sub(J as u32);
69 let M = if J { month + 12 } else { month };
70 let D = day - 1;
71 let C = Y / 100;
72
73 let y_star = 1461 * Y / 4 - C + C / 4;
74 let m_star = (979 * M - 2919) / 32;
75 let N = y_star + m_star + D;
76
77 let N_U = N.wrapping_sub(K);
78 N_U as i32
79}
80
81#[inline(always)]
87#[allow(non_upper_case_globals, non_snake_case)]
88pub(crate) fn from_unix_epoch_day(days: i32) -> (i16, i8, i8) {
89 const s: u32 = 82;
90 const K: u32 = 719468 + 146097 * s;
91 const L: u32 = 400 * s;
92
93 let N_U = days as u32;
94 let N = N_U.wrapping_add(K);
95
96 let N_1 = 4 * N + 3;
97 let C = N_1 / 146097;
98 let N_C = (N_1 % 146097) / 4;
99
100 let N_2 = 4 * N_C + 3;
101 let P_2 = 2939745 * u64::from(N_2);
102 let Z = (P_2 / 4294967296) as u32;
103 let N_Y = (P_2 % 4294967296) as u32 / 2939745 / 4;
104 let Y = 100 * C + Z;
105
106 let N_3 = 2141 * N_Y + 197913;
107 let M = N_3 / 65536;
108 let D = (N_3 % 65536) / 2141;
109
110 let J = N_Y >= 306;
111 let year = Y.wrapping_sub(L).wrapping_add(J as u32) as i16;
112 let month = (if J { M - 12 } else { M }) as i8;
113 let day = (D + 1) as i8;
114 (year, month, day)
115}
116
117#[inline(always)]
119pub(crate) fn to_day_nanosecond(
120 hour: i8,
121 minute: i8,
122 second: i8,
123 subsec: i32,
124) -> i64 {
125 let mut nanos: i64 = 0;
126 nanos += i64::from(hour) * t::NANOS_PER_HOUR.value();
127 nanos += i64::from(minute) * t::NANOS_PER_MINUTE.value();
128 nanos += i64::from(second) * t::NANOS_PER_SECOND.value();
129 nanos += i64::from(subsec);
130 nanos
131}
132
133#[inline(always)]
135pub(crate) fn from_day_nanosecond(mut nanos: i64) -> (i8, i8, i8, i32) {
136 let (mut hour, mut minute, mut second, mut subsec) = (0, 0, 0, 0);
137 if nanos != 0 {
138 hour = (nanos / t::NANOS_PER_HOUR.value()) as i8;
139 nanos %= t::NANOS_PER_HOUR.value();
140 if nanos != 0 {
141 minute = (nanos / t::NANOS_PER_MINUTE.value()) as i8;
142 nanos %= t::NANOS_PER_MINUTE.value();
143 if nanos != 0 {
144 second = (nanos / t::NANOS_PER_SECOND.value()) as i8;
145 subsec = (nanos % t::NANOS_PER_SECOND.value()) as i32;
146 }
147 }
148 }
149 (hour, minute, second, subsec)
150}
151
152#[inline(always)]
154pub(crate) fn timestamp_to_datetime_zulu(
155 mut secs: i64,
156 mut subsec: i32,
157 offset: i32,
158) -> (i16, i8, i8, i8, i8, i8, i32) {
159 secs += i64::from(offset);
160 let mut days = secs.div_euclid(86_400) as i32;
161 secs = secs.rem_euclid(86_400);
162 if subsec < 0 {
163 if secs > 0 {
164 secs -= 1;
165 subsec += t::NANOS_PER_SECOND.value() as i32;
166 } else {
167 days -= 1;
168 secs += 86_399;
169 subsec += 1_000_000_000;
170 }
171 }
172
173 let (year, month, day) = from_unix_epoch_day(days);
174 let hour = (secs / t::SECONDS_PER_HOUR.value()) as i8;
175 secs %= t::SECONDS_PER_HOUR.value();
176 let minute = (secs / t::SECONDS_PER_MINUTE.value()) as i8;
177 let second = (secs % t::SECONDS_PER_MINUTE.value()) as i8;
178 (year, month, day, hour, minute, second, subsec)
179}
180
181#[inline(always)]
183pub(crate) fn datetime_zulu_to_timestamp(
184 year: i16,
185 month: i8,
186 day: i8,
187 hour: i8,
188 minute: i8,
189 second: i8,
190 mut subsec: i32,
191 offset: i32,
192) -> (i64, i32) {
193 let day = to_unix_epoch_day(year, month, day);
194 let mut secs = i64::from(day) * t::SECONDS_PER_CIVIL_DAY.value();
195 secs += i64::from(hour) * t::SECONDS_PER_HOUR.value();
196 secs += i64::from(minute) * t::SECONDS_PER_MINUTE.value();
197 secs += i64::from(second);
198 secs -= i64::from(offset);
199 if day < 0 && subsec != 0 {
200 secs += 1;
201 subsec -= 1_000_000_000;
202 }
203 (secs, subsec)
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn t_is_leap_year() {
212 assert!(is_leap_year(2024));
213 assert!(!is_leap_year(2023));
214 assert!(!is_leap_year(2025));
215 assert!(is_leap_year(2000));
216 assert!(!is_leap_year(1900));
217 assert!(!is_leap_year(1800));
218 assert!(!is_leap_year(1700));
219 assert!(is_leap_year(1600));
220 assert!(is_leap_year(0));
221 assert!(!is_leap_year(-1));
222 assert!(!is_leap_year(-2));
223 assert!(!is_leap_year(-3));
224 assert!(is_leap_year(-4));
225 assert!(!is_leap_year(-100));
226 assert!(!is_leap_year(-200));
227 assert!(!is_leap_year(-300));
228 assert!(is_leap_year(400));
229 assert!(!is_leap_year(9999));
230 assert!(!is_leap_year(-9999));
231 }
232
233 #[test]
234 fn t_days_in_month() {
235 assert_eq!(28, days_in_month(-9999, 2));
236 }
237
238 }