1pub fn decompose_unix_timestamp(unix_timestamp: i64) -> (u16, u8, u8, u8, u8, u8) {
2 const SECONDS_IN_MINUTE: i64 = 60;
4 const SECONDS_IN_HOUR: i64 = 60 * SECONDS_IN_MINUTE;
5 const SECONDS_IN_DAY: i64 = 24 * SECONDS_IN_HOUR;
6 const DAYS_IN_YEAR: i64 = 365;
7 const DAYS_IN_LEAP_YEAR: i64 = 366;
8
9 let mut year: i64 = 1970;
11 let mut days_since_epoch = unix_timestamp.div_euclid(SECONDS_IN_DAY);
12 let mut remaining_seconds = unix_timestamp.rem_euclid(SECONDS_IN_DAY);
13
14 while days_since_epoch
16 >= if is_leap_year(year) {
17 DAYS_IN_LEAP_YEAR
18 } else {
19 DAYS_IN_YEAR
20 }
21 {
22 days_since_epoch -= if is_leap_year(year) {
23 DAYS_IN_LEAP_YEAR
24 } else {
25 DAYS_IN_YEAR
26 };
27 year += 1;
28 }
29
30 while days_since_epoch < 0 {
31 year -= 1;
32 days_since_epoch += if is_leap_year(year) {
33 DAYS_IN_LEAP_YEAR
34 } else {
35 DAYS_IN_YEAR
36 };
37 }
38
39 let mut month = 0;
41 while days_since_epoch >= days_in_month(year, month) {
42 days_since_epoch -= days_in_month(year, month);
43 month += 1;
44 }
45
46 let day = days_since_epoch + 1;
48
49 let hour = remaining_seconds / SECONDS_IN_HOUR;
51 remaining_seconds %= SECONDS_IN_HOUR;
52 let minute = remaining_seconds / SECONDS_IN_MINUTE;
53 let second = remaining_seconds % SECONDS_IN_MINUTE;
54
55 (
56 year as u16,
57 month as u8 + 1,
58 day as u8,
59 hour as u8,
60 minute as u8,
61 second as u8,
62 )
63}
64
65pub fn is_leap_year(year: i64) -> bool {
66 (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
67}
68
69pub fn days_in_month(year: i64, month: usize) -> i64 {
70 const DAYS_IN_MONTH: [i64; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
72
73 if month == 1 && is_leap_year(year) {
74 29
76 } else {
77 DAYS_IN_MONTH[month]
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84
85 #[test]
86 fn unix_epoch_is_correct() {
87 assert_eq!(decompose_unix_timestamp(0), (1970, 1, 1, 0, 0, 0));
88 }
89
90 #[test]
91 fn one_second_before_epoch_is_correct() {
92 assert_eq!(decompose_unix_timestamp(-1), (1969, 12, 31, 23, 59, 59));
93 }
94
95 #[test]
96 fn one_day_before_epoch_is_correct() {
97 assert_eq!(decompose_unix_timestamp(-86_400), (1969, 12, 31, 0, 0, 0));
98 }
99
100 #[test]
101 fn leap_day_2024_is_correct() {
102 assert_eq!(
104 decompose_unix_timestamp(1_709_164_800),
105 (2024, 2, 29, 0, 0, 0)
106 );
107 }
108
109 #[test]
110 fn leap_year_rules_are_correct() {
111 assert!(is_leap_year(2000));
112 assert!(!is_leap_year(1900));
113 assert!(is_leap_year(2024));
114 assert!(!is_leap_year(2023));
115 }
116
117 #[test]
118 fn february_days_are_correct() {
119 assert_eq!(days_in_month(2024, 1), 29);
120 assert_eq!(days_in_month(2023, 1), 28);
121 }
122}