1use std::{
5 fmt::{Display, Formatter},
6 time::{SystemTime, UNIX_EPOCH},
7};
8
9use serde::{
10 Deserialize, Deserializer, Serialize, Serializer,
11 de::{self, Visitor},
12};
13
14#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
19pub struct Date {
20 days_since_epoch: i32,
23}
24
25impl Default for Date {
26 fn default() -> Self {
27 Self {
28 days_since_epoch: 0,
29 } }
31}
32
33impl Date {
35 #[inline]
37 fn is_leap_year(year: i32) -> bool {
38 (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
39 }
40
41 #[inline]
43 fn days_in_month(year: i32, month: u32) -> u32 {
44 match month {
45 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
46 4 | 6 | 9 | 11 => 30,
47 2 => {
48 if Self::is_leap_year(year) {
49 29
50 } else {
51 28
52 }
53 }
54 _ => 0,
55 }
56 }
57
58 fn ymd_to_days_since_epoch(year: i32, month: u32, day: u32) -> Option<i32> {
60 if month < 1 || month > 12 || day < 1 || day > Self::days_in_month(year, month) {
62 return None;
63 }
64
65 let (y, m) = if month <= 2 {
68 (year - 1, month as i32 + 9) } else {
70 (year, month as i32 - 3) };
72
73 let era = if y >= 0 {
74 y
75 } else {
76 y - 399
77 } / 400;
78 let yoe = y - era * 400; let doy = (153 * m + 2) / 5 + day as i32 - 1; let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; let days = era * 146097 + doe - 719468;
82
83 Some(days)
84 }
85
86 fn days_since_epoch_to_ymd(days: i32) -> (i32, u32, u32) {
88 let days_since_ce = days + 719468;
90
91 let era = if days_since_ce >= 0 {
92 days_since_ce
93 } else {
94 days_since_ce - 146096
95 } / 146097;
96 let doe = days_since_ce - era * 146097; let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; let y = yoe + era * 400;
99 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 {
103 mp + 3
104 } else {
105 mp - 9
106 }; let year = if m <= 2 {
108 y + 1
109 } else {
110 y
111 };
112
113 (year as i32, m as u32, d as u32)
114 }
115}
116
117impl Date {
118 pub fn new(year: i32, month: u32, day: u32) -> Option<Self> {
119 Self::ymd_to_days_since_epoch(year, month, day).map(|days_since_epoch| Self {
120 days_since_epoch,
121 })
122 }
123
124 pub fn from_ymd(year: i32, month: u32, day: u32) -> Result<Self, String> {
125 Self::new(year, month, day).ok_or_else(|| format!("Invalid date: {}-{:02}-{:02}", year, month, day))
126 }
127
128 pub fn today() -> Self {
129 let duration = SystemTime::now().duration_since(UNIX_EPOCH).expect("System time before Unix epoch");
130
131 let days = duration.as_secs() / 86400;
132 Self {
133 days_since_epoch: days as i32,
134 }
135 }
136
137 pub fn year(&self) -> i32 {
138 Self::days_since_epoch_to_ymd(self.days_since_epoch).0
139 }
140
141 pub fn month(&self) -> u32 {
142 Self::days_since_epoch_to_ymd(self.days_since_epoch).1
143 }
144
145 pub fn day(&self) -> u32 {
146 Self::days_since_epoch_to_ymd(self.days_since_epoch).2
147 }
148
149 pub fn to_days_since_epoch(&self) -> i32 {
151 self.days_since_epoch
152 }
153
154 pub fn from_days_since_epoch(days: i32) -> Option<Self> {
156 if days < -365_250_000 || days > 365_250_000 {
159 return None;
160 }
161 Some(Self {
162 days_since_epoch: days,
163 })
164 }
165}
166
167impl Display for Date {
168 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
169 let (year, month, day) = Self::days_since_epoch_to_ymd(self.days_since_epoch);
170 if year < 0 {
171 write!(f, "-{:04}-{:02}-{:02}", -year, month, day)
172 } else {
173 write!(f, "{:04}-{:02}-{:02}", year, month, day)
174 }
175 }
176}
177
178impl Serialize for Date {
180 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
181 where
182 S: Serializer,
183 {
184 serializer.serialize_str(&self.to_string())
185 }
186}
187
188struct DateVisitor;
189
190impl<'de> Visitor<'de> for DateVisitor {
191 type Value = Date;
192
193 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
194 formatter.write_str("a date in ISO 8601 format (YYYY-MM-DD)")
195 }
196
197 fn visit_str<E>(self, value: &str) -> Result<Date, E>
198 where
199 E: de::Error,
200 {
201 let parts: Vec<&str> = value.split('-').collect();
203
204 if parts.len() != 3 {
205 return Err(E::custom(format!("invalid date format: {}", value)));
206 }
207
208 let (year_str, month_str, day_str) = if parts[0].is_empty() && parts.len() == 4 {
210 (format!("-{}", parts[1]), parts[2], parts[3])
213 } else {
214 (parts[0].to_string(), parts[1], parts[2])
215 };
216
217 let year = year_str.parse::<i32>().map_err(|_| E::custom(format!("invalid year: {}", year_str)))?;
218 let month = month_str.parse::<u32>().map_err(|_| E::custom(format!("invalid month: {}", month_str)))?;
219 let day = day_str.parse::<u32>().map_err(|_| E::custom(format!("invalid day: {}", day_str)))?;
220
221 Date::new(year, month, day)
222 .ok_or_else(|| E::custom(format!("invalid date: {}-{:02}-{:02}", year, month, day)))
223 }
224}
225
226impl<'de> Deserialize<'de> for Date {
227 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
228 where
229 D: Deserializer<'de>,
230 {
231 deserializer.deserialize_str(DateVisitor)
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238
239 #[test]
240 fn test_date_display_standard_dates() {
241 let date = Date::new(2024, 3, 15).unwrap();
243 assert_eq!(format!("{}", date), "2024-03-15");
244
245 let date = Date::new(2000, 1, 1).unwrap();
246 assert_eq!(format!("{}", date), "2000-01-01");
247
248 let date = Date::new(1999, 12, 31).unwrap();
249 assert_eq!(format!("{}", date), "1999-12-31");
250 }
251
252 #[test]
253 fn test_date_display_edge_cases() {
254 let date = Date::new(1970, 1, 1).unwrap();
256 assert_eq!(format!("{}", date), "1970-01-01");
257
258 let date = Date::new(2024, 2, 29).unwrap();
260 assert_eq!(format!("{}", date), "2024-02-29");
261
262 let date = Date::new(2024, 1, 9).unwrap();
264 assert_eq!(format!("{}", date), "2024-01-09");
265
266 let date = Date::new(2024, 9, 1).unwrap();
267 assert_eq!(format!("{}", date), "2024-09-01");
268 }
269
270 #[test]
271 fn test_date_display_boundary_dates() {
272 let date = Date::new(1, 1, 1).unwrap();
274 assert_eq!(format!("{}", date), "0001-01-01");
275
276 let date = Date::new(9999, 12, 31).unwrap();
278 assert_eq!(format!("{}", date), "9999-12-31");
279
280 let date = Date::new(1900, 1, 1).unwrap();
282 assert_eq!(format!("{}", date), "1900-01-01");
283
284 let date = Date::new(2000, 1, 1).unwrap();
285 assert_eq!(format!("{}", date), "2000-01-01");
286
287 let date = Date::new(2100, 1, 1).unwrap();
288 assert_eq!(format!("{}", date), "2100-01-01");
289 }
290
291 #[test]
292 fn test_date_display_negative_years() {
293 let date = Date::new(0, 1, 1).unwrap();
295 assert_eq!(format!("{}", date), "0000-01-01");
296
297 let date = Date::new(-1, 1, 1).unwrap();
299 assert_eq!(format!("{}", date), "-0001-01-01");
300
301 let date = Date::new(-100, 12, 31).unwrap();
302 assert_eq!(format!("{}", date), "-0100-12-31");
303 }
304
305 #[test]
306 fn test_date_display_default() {
307 let date = Date::default();
308 assert_eq!(format!("{}", date), "1970-01-01");
309 }
310
311 #[test]
312 fn test_date_display_all_months() {
313 let months = [
314 (1, "01"),
315 (2, "02"),
316 (3, "03"),
317 (4, "04"),
318 (5, "05"),
319 (6, "06"),
320 (7, "07"),
321 (8, "08"),
322 (9, "09"),
323 (10, "10"),
324 (11, "11"),
325 (12, "12"),
326 ];
327
328 for (month, expected) in months {
329 let date = Date::new(2024, month, 15).unwrap();
330 assert_eq!(format!("{}", date), format!("2024-{}-15", expected));
331 }
332 }
333
334 #[test]
335 fn test_date_display_days_in_month() {
336 let test_cases = [
338 (2024, 1, 1, "2024-01-01"),
339 (2024, 1, 31, "2024-01-31"),
340 (2024, 2, 1, "2024-02-01"),
341 (2024, 2, 29, "2024-02-29"), (2024, 4, 1, "2024-04-01"),
343 (2024, 4, 30, "2024-04-30"),
344 (2024, 12, 1, "2024-12-01"),
345 (2024, 12, 31, "2024-12-31"),
346 ];
347
348 for (year, month, day, expected) in test_cases {
349 let date = Date::new(year, month, day).unwrap();
350 assert_eq!(format!("{}", date), expected);
351 }
352 }
353
354 #[test]
355 fn test_date_roundtrip() {
356 let test_dates = [
358 (1900, 1, 1),
359 (1970, 1, 1),
360 (2000, 2, 29), (2024, 12, 31),
362 (2100, 6, 15),
363 ];
364
365 for (year, month, day) in test_dates {
366 let date = Date::new(year, month, day).unwrap();
367 let days = date.to_days_since_epoch();
368 let recovered = Date::from_days_since_epoch(days).unwrap();
369
370 assert_eq!(date.year(), recovered.year());
371 assert_eq!(date.month(), recovered.month());
372 assert_eq!(date.day(), recovered.day());
373 }
374 }
375
376 #[test]
377 fn test_leap_year_detection() {
378 assert!(Date::is_leap_year(2000)); assert!(Date::is_leap_year(2024)); assert!(!Date::is_leap_year(1900)); assert!(!Date::is_leap_year(2023)); }
383
384 #[test]
385 fn test_invalid_dates() {
386 assert!(Date::new(2024, 0, 1).is_none()); assert!(Date::new(2024, 13, 1).is_none()); assert!(Date::new(2024, 1, 0).is_none()); assert!(Date::new(2024, 1, 32).is_none()); assert!(Date::new(2023, 2, 29).is_none()); assert!(Date::new(2024, 4, 31).is_none()); }
393
394 #[test]
395 fn test_serde_roundtrip() {
396 let date = Date::new(2024, 3, 15).unwrap();
397 let json = serde_json::to_string(&date).unwrap();
398 assert_eq!(json, "\"2024-03-15\"");
399
400 let recovered: Date = serde_json::from_str(&json).unwrap();
401 assert_eq!(date, recovered);
402 }
403}