1const MILLIS_PER_SECOND: i64 = 1_000;
14const MILLIS_PER_MINUTE: i64 = 60 * MILLIS_PER_SECOND;
15const MILLIS_PER_HOUR: i64 = 60 * MILLIS_PER_MINUTE;
16const MILLIS_PER_DAY: i64 = 24 * MILLIS_PER_HOUR;
17
18const DAYS_IN_MONTH: [i64; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
20
21pub fn datetime_now() -> i64 {
28 std::time::SystemTime::now()
29 .duration_since(std::time::UNIX_EPOCH)
30 .unwrap_or_default()
31 .as_millis() as i64
32}
33
34pub fn datetime_from_epoch(millis: i64) -> i64 {
36 millis
37}
38
39pub fn datetime_from_parts(year: i64, month: i64, day: i64, hour: i64, min: i64, sec: i64) -> i64 {
42 let days = days_from_civil(year, month, day);
43 days * MILLIS_PER_DAY + hour * MILLIS_PER_HOUR + min * MILLIS_PER_MINUTE + sec * MILLIS_PER_SECOND
44}
45
46pub fn datetime_year(millis: i64) -> i64 {
52 let (y, _, _) = civil_from_days(millis.div_euclid(MILLIS_PER_DAY));
53 y
54}
55
56pub fn datetime_month(millis: i64) -> i64 {
58 let (_, m, _) = civil_from_days(millis.div_euclid(MILLIS_PER_DAY));
59 m
60}
61
62pub fn datetime_day(millis: i64) -> i64 {
64 let (_, _, d) = civil_from_days(millis.div_euclid(MILLIS_PER_DAY));
65 d
66}
67
68pub fn datetime_hour(millis: i64) -> i64 {
70 let day_millis = millis.rem_euclid(MILLIS_PER_DAY);
71 day_millis / MILLIS_PER_HOUR
72}
73
74pub fn datetime_minute(millis: i64) -> i64 {
76 let day_millis = millis.rem_euclid(MILLIS_PER_DAY);
77 (day_millis % MILLIS_PER_HOUR) / MILLIS_PER_MINUTE
78}
79
80pub fn datetime_second(millis: i64) -> i64 {
82 let day_millis = millis.rem_euclid(MILLIS_PER_DAY);
83 (day_millis % MILLIS_PER_MINUTE) / MILLIS_PER_SECOND
84}
85
86pub fn datetime_diff(a: i64, b: i64) -> i64 {
92 a - b
93}
94
95pub fn datetime_add_millis(dt: i64, millis: i64) -> i64 {
97 dt + millis
98}
99
100pub fn datetime_format(millis: i64) -> String {
106 let days = millis.div_euclid(MILLIS_PER_DAY);
107 let (year, month, day) = civil_from_days(days);
108 let day_millis = millis.rem_euclid(MILLIS_PER_DAY);
109 let hour = day_millis / MILLIS_PER_HOUR;
110 let minute = (day_millis % MILLIS_PER_HOUR) / MILLIS_PER_MINUTE;
111 let second = (day_millis % MILLIS_PER_MINUTE) / MILLIS_PER_SECOND;
112 format!(
113 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
114 year, month, day, hour, minute, second
115 )
116}
117
118fn is_leap_year(year: i64) -> bool {
124 (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
125}
126
127fn days_from_civil(year: i64, month: i64, day: i64) -> i64 {
130 let (y, m) = if month <= 2 {
132 (year - 1, month + 9)
133 } else {
134 (year, month - 3)
135 };
136 let era = y.div_euclid(400);
137 let yoe = y.rem_euclid(400); let doy = (153 * m + 2) / 5 + day - 1; let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; era * 146097 + doe - 719468 }
142
143fn civil_from_days(days: i64) -> (i64, i64, i64) {
145 let z = days + 719468;
146 let era = z.div_euclid(146097);
147 let doe = z.rem_euclid(146097); let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; let y = yoe + era * 400;
150 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); let mp = (5 * doy + 2) / 153; let d = doy - (153 * mp + 2) / 5 + 1; let m = if mp < 10 { mp + 3 } else { mp - 9 };
154 let y = if m <= 2 { y + 1 } else { y };
155 (y, m, d)
156}
157
158pub fn days_in_month(year: i64, month: i64) -> i64 {
160 if month == 2 && is_leap_year(year) {
161 29
162 } else if month >= 1 && month <= 12 {
163 DAYS_IN_MONTH[(month - 1) as usize]
164 } else {
165 0
166 }
167}
168
169#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn test_epoch_origin() {
179 let dt = datetime_from_parts(1970, 1, 1, 0, 0, 0);
181 assert_eq!(dt, 0);
182 }
183
184 #[test]
185 fn test_known_date() {
186 let dt = datetime_from_parts(2000, 1, 1, 0, 0, 0);
188 assert_eq!(datetime_year(dt), 2000);
189 assert_eq!(datetime_month(dt), 1);
190 assert_eq!(datetime_day(dt), 1);
191 assert_eq!(datetime_hour(dt), 0);
192 }
193
194 #[test]
195 fn test_extraction_roundtrip() {
196 let dt = datetime_from_parts(2024, 6, 15, 14, 30, 45);
197 assert_eq!(datetime_year(dt), 2024);
198 assert_eq!(datetime_month(dt), 6);
199 assert_eq!(datetime_day(dt), 15);
200 assert_eq!(datetime_hour(dt), 14);
201 assert_eq!(datetime_minute(dt), 30);
202 assert_eq!(datetime_second(dt), 45);
203 }
204
205 #[test]
206 fn test_leap_year() {
207 assert!(is_leap_year(2000));
208 assert!(is_leap_year(2024));
209 assert!(!is_leap_year(1900));
210 assert!(!is_leap_year(2023));
211 }
212
213 #[test]
214 fn test_days_in_feb_leap() {
215 assert_eq!(days_in_month(2024, 2), 29);
216 assert_eq!(days_in_month(2023, 2), 28);
217 }
218
219 #[test]
220 fn test_format_iso8601() {
221 let dt = datetime_from_parts(2024, 3, 14, 9, 26, 53);
222 let s = datetime_format(dt);
223 assert_eq!(s, "2024-03-14T09:26:53Z");
224 }
225
226 #[test]
227 fn test_diff() {
228 let a = datetime_from_parts(2024, 1, 2, 0, 0, 0);
229 let b = datetime_from_parts(2024, 1, 1, 0, 0, 0);
230 assert_eq!(datetime_diff(a, b), MILLIS_PER_DAY);
231 }
232
233 #[test]
234 fn test_add_millis() {
235 let dt = datetime_from_parts(2024, 1, 1, 0, 0, 0);
236 let dt2 = datetime_add_millis(dt, MILLIS_PER_HOUR);
237 assert_eq!(datetime_hour(dt2), 1);
238 }
239
240 #[test]
241 fn test_format_epoch() {
242 assert_eq!(datetime_format(0), "1970-01-01T00:00:00Z");
243 }
244
245 #[test]
246 fn test_determinism() {
247 let a = datetime_from_parts(2024, 12, 31, 23, 59, 59);
249 let b = datetime_from_parts(2024, 12, 31, 23, 59, 59);
250 assert_eq!(a, b);
251 assert_eq!(datetime_format(a), datetime_format(b));
252 }
253}