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