1use crate::time::TimeZone;
2
3#[derive(core::fmt::Debug)]
13pub struct DateTime {
14 pub year: u32,
16 pub month: u8,
18 pub day: u8,
20 pub hour: u8,
22 pub minute: u8,
24 pub second: u8,
26 pub millisecond: u16,
28 pub timezone: TimeZone,
30}
31
32const MILLISECONDS_PER_SECOND: u64 = 1000;
33const SECONDS_PER_MINUTE: u64 = 60;
34const MINUTES_PER_HOUR: i32 = 60;
35const HOURS_PER_DAY: i32 = 24;
36const DAYS_PER_YEAR: u64 = 365;
37const DAYS_PER_LEAP_YEAR: u64 = 366;
38
39const MONTHS: [[u8; 12]; 2] = [
40 [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], ];
43
44impl DateTime {
45 #[inline]
47 pub fn from_unix_secs(timestamp: u64, timezone: TimeZone) -> DateTime {
48 return Self::from_unix_millis(timestamp * 1000, timezone);
49 }
50
51 pub fn from_unix_millis(timestamp: u64, timezone: TimeZone) -> DateTime {
53 let mut milliseconds = timestamp;
54 let mut seconds = milliseconds / MILLISECONDS_PER_SECOND;
55 let mut minutes = (seconds / SECONDS_PER_MINUTE) as i32;
56 let mut hours = minutes / MINUTES_PER_HOUR;
57 let mut days = hours / HOURS_PER_DAY;
58
59 milliseconds %= MILLISECONDS_PER_SECOND;
60 seconds %= SECONDS_PER_MINUTE;
61 minutes %= MINUTES_PER_HOUR;
62 hours %= HOURS_PER_DAY;
63 hours += timezone.get_offset() / 3600;
64 if hours < 0 {
65 hours += HOURS_PER_DAY as i32;
66 days -= 1;
67 } else if hours >= HOURS_PER_DAY as i32 {
68 hours -= HOURS_PER_DAY as i32;
69 days += 1;
70 }
71
72 let mut years = 1970;
73 while days
74 >= if is_leap_year(years) {
75 DAYS_PER_LEAP_YEAR
76 } else {
77 DAYS_PER_YEAR
78 } as i32
79 {
80 days -= if is_leap_year(years) {
81 DAYS_PER_LEAP_YEAR
82 } else {
83 DAYS_PER_YEAR
84 } as i32;
85 years += 1;
86 }
87
88 let mut month = 0;
89 while days >= MONTHS[is_leap_year(years) as usize][month as usize] as i32 {
90 days -= MONTHS[is_leap_year(years) as usize][month as usize] as i32;
91 month += 1;
92 }
93
94 DateTime {
95 year: years,
96 month: month + 1,
97 day: days as u8 + 1,
98 hour: hours as u8,
99 minute: minutes as u8,
100 second: (seconds % SECONDS_PER_MINUTE) as u8,
101 millisecond: milliseconds as u16,
102 timezone,
103 }
104 }
105}
106
107fn is_leap_year(year: u32) -> bool {
109 (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
110}
111
112#[cfg(test)]
113mod tests {
114 use crate::time::{DateTime, TimeZone};
115
116 #[test]
117 fn from_unix_secs_works() {
118 let datetime = DateTime::from_unix_secs(1704067199, TimeZone::UTC);
120 assert_eq!(datetime.year, 2023, "year: {}", datetime.year);
121 assert_eq!(datetime.month, 12, "month: {}", datetime.month);
122 assert_eq!(datetime.day, 31, "day: {}", datetime.day);
123 assert_eq!(datetime.hour, 23, "hour: {}", datetime.hour);
124 assert_eq!(datetime.minute, 59, "minute: {}", datetime.minute);
125 assert_eq!(datetime.second, 59, "second: {}", datetime.second);
126 assert_eq!(
127 datetime.millisecond, 0,
128 "millisecond: {}",
129 datetime.millisecond
130 );
131 assert_eq!(
132 datetime.timezone.get_offset(),
133 TimeZone::UTC.get_offset(),
134 "timezone: {:?}",
135 datetime.timezone.get_offset()
136 );
137 }
138
139 #[test]
140 fn from_unix_millis_works() {
141 let datetime = DateTime::from_unix_millis(1704067199998, TimeZone::AsiaShanghai);
143 assert_eq!(datetime.year, 2024, "year: {}", datetime.year);
144 assert_eq!(datetime.month, 1, "month: {}", datetime.month);
145 assert_eq!(datetime.day, 1, "day: {}", datetime.day);
146 assert_eq!(datetime.hour, 7, "hour: {}", datetime.hour);
147 assert_eq!(datetime.minute, 59, "minute: {}", datetime.minute);
148 assert_eq!(datetime.second, 59, "second: {}", datetime.second);
149 assert_eq!(
150 datetime.millisecond, 998,
151 "millisecond: {}",
152 datetime.millisecond
153 );
154 assert_eq!(
155 datetime.timezone.get_offset(),
156 TimeZone::AsiaShanghai.get_offset(),
157 "timezone: {:?}",
158 datetime.timezone.get_offset()
159 );
160 }
161}