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
15use crate::{
16 error::{TemporalKind, TypeError},
17 fragment::Fragment,
18};
19
20#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
25pub struct Date {
26 days_since_epoch: i32,
29}
30
31impl Default for Date {
32 fn default() -> Self {
33 Self {
34 days_since_epoch: 0,
35 } }
37}
38
39impl Date {
41 #[inline]
43 pub fn is_leap_year(year: i32) -> bool {
44 (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
45 }
46
47 #[inline]
49 pub fn days_in_month(year: i32, month: u32) -> u32 {
50 match month {
51 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
52 4 | 6 | 9 | 11 => 30,
53 2 => {
54 if Self::is_leap_year(year) {
55 29
56 } else {
57 28
58 }
59 }
60 _ => 0,
61 }
62 }
63
64 fn ymd_to_days_since_epoch(year: i32, month: u32, day: u32) -> Option<i32> {
66 if month < 1 || month > 12 || day < 1 || day > Self::days_in_month(year, month) {
68 return None;
69 }
70
71 let (y, m) = if month <= 2 {
74 (year - 1, month as i32 + 9) } else {
76 (year, month as i32 - 3) };
78
79 let era = if y >= 0 {
80 y
81 } else {
82 y - 399
83 } / 400;
84 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;
88
89 Some(days)
90 }
91
92 fn days_since_epoch_to_ymd(days: i32) -> (i32, u32, u32) {
94 let days_since_ce = days + 719468;
96
97 let era = if days_since_ce >= 0 {
98 days_since_ce
99 } else {
100 days_since_ce - 146096
101 } / 146097;
102 let doe = days_since_ce - era * 146097; let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; let y = yoe + era * 400;
105 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 {
109 mp + 3
110 } else {
111 mp - 9
112 }; let year = if m <= 2 {
114 y + 1
115 } else {
116 y
117 };
118
119 (year as i32, m as u32, d as u32)
120 }
121}
122
123impl Date {
124 fn overflow_err(message: impl Into<String>) -> TypeError {
125 TypeError::Temporal {
126 kind: TemporalKind::DateOverflow {
127 message: message.into(),
128 },
129 message: "date overflow".to_string(),
130 fragment: Fragment::None,
131 }
132 }
133
134 pub fn new(year: i32, month: u32, day: u32) -> Option<Self> {
135 Self::ymd_to_days_since_epoch(year, month, day).map(|days_since_epoch| Self {
136 days_since_epoch,
137 })
138 }
139
140 pub fn from_ymd(year: i32, month: u32, day: u32) -> Result<Self, TypeError> {
141 Self::new(year, month, day)
142 .ok_or_else(|| Self::overflow_err(format!("invalid date: {}-{:02}-{:02}", year, month, day)))
143 }
144
145 pub fn today() -> Self {
146 let duration = SystemTime::now().duration_since(UNIX_EPOCH).expect("System time before Unix epoch");
147
148 let days = duration.as_secs() / 86400;
149 Self {
150 days_since_epoch: days as i32,
151 }
152 }
153
154 pub fn year(&self) -> i32 {
155 Self::days_since_epoch_to_ymd(self.days_since_epoch).0
156 }
157
158 pub fn month(&self) -> u32 {
159 Self::days_since_epoch_to_ymd(self.days_since_epoch).1
160 }
161
162 pub fn day(&self) -> u32 {
163 Self::days_since_epoch_to_ymd(self.days_since_epoch).2
164 }
165
166 pub fn to_days_since_epoch(&self) -> i32 {
168 self.days_since_epoch
169 }
170
171 pub fn from_days_since_epoch(days: i32) -> Option<Self> {
173 if days < -365_250_000 || days > 365_250_000 {
176 return None;
177 }
178 Some(Self {
179 days_since_epoch: days,
180 })
181 }
182}
183
184impl Display for Date {
185 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
186 let (year, month, day) = Self::days_since_epoch_to_ymd(self.days_since_epoch);
187 if year < 0 {
188 write!(f, "-{:04}-{:02}-{:02}", -year, month, day)
189 } else {
190 write!(f, "{:04}-{:02}-{:02}", year, month, day)
191 }
192 }
193}
194
195impl Serialize for Date {
197 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
198 where
199 S: Serializer,
200 {
201 serializer.serialize_str(&self.to_string())
202 }
203}
204
205struct DateVisitor;
206
207impl<'de> Visitor<'de> for DateVisitor {
208 type Value = Date;
209
210 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
211 formatter.write_str("a date in ISO 8601 format (YYYY-MM-DD)")
212 }
213
214 fn visit_str<E>(self, value: &str) -> Result<Date, E>
215 where
216 E: de::Error,
217 {
218 let parts: Vec<&str> = value.split('-').collect();
220
221 if parts.len() != 3 {
222 return Err(E::custom(format!("invalid date format: {}", value)));
223 }
224
225 let (year_str, month_str, day_str) = if parts[0].is_empty() && parts.len() == 4 {
227 (format!("-{}", parts[1]), parts[2], parts[3])
230 } else {
231 (parts[0].to_string(), parts[1], parts[2])
232 };
233
234 let year = year_str.parse::<i32>().map_err(|_| E::custom(format!("invalid year: {}", year_str)))?;
235 let month = month_str.parse::<u32>().map_err(|_| E::custom(format!("invalid month: {}", month_str)))?;
236 let day = day_str.parse::<u32>().map_err(|_| E::custom(format!("invalid day: {}", day_str)))?;
237
238 Date::new(year, month, day)
239 .ok_or_else(|| E::custom(format!("invalid date: {}-{:02}-{:02}", year, month, day)))
240 }
241}
242
243impl<'de> Deserialize<'de> for Date {
244 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
245 where
246 D: Deserializer<'de>,
247 {
248 deserializer.deserialize_str(DateVisitor)
249 }
250}
251
252#[cfg(test)]
253pub mod tests {
254 use std::fmt::Debug;
255
256 use serde_json::{from_str, to_string};
257
258 use super::*;
259 use crate::error::{TemporalKind, TypeError};
260
261 #[test]
262 fn test_date_display_standard_dates() {
263 let date = Date::new(2024, 3, 15).unwrap();
265 assert_eq!(format!("{}", date), "2024-03-15");
266
267 let date = Date::new(2000, 1, 1).unwrap();
268 assert_eq!(format!("{}", date), "2000-01-01");
269
270 let date = Date::new(1999, 12, 31).unwrap();
271 assert_eq!(format!("{}", date), "1999-12-31");
272 }
273
274 #[test]
275 fn test_date_display_edge_cases() {
276 let date = Date::new(1970, 1, 1).unwrap();
278 assert_eq!(format!("{}", date), "1970-01-01");
279
280 let date = Date::new(2024, 2, 29).unwrap();
282 assert_eq!(format!("{}", date), "2024-02-29");
283
284 let date = Date::new(2024, 1, 9).unwrap();
286 assert_eq!(format!("{}", date), "2024-01-09");
287
288 let date = Date::new(2024, 9, 1).unwrap();
289 assert_eq!(format!("{}", date), "2024-09-01");
290 }
291
292 #[test]
293 fn test_date_display_boundary_dates() {
294 let date = Date::new(1, 1, 1).unwrap();
296 assert_eq!(format!("{}", date), "0001-01-01");
297
298 let date = Date::new(9999, 12, 31).unwrap();
300 assert_eq!(format!("{}", date), "9999-12-31");
301
302 let date = Date::new(1900, 1, 1).unwrap();
304 assert_eq!(format!("{}", date), "1900-01-01");
305
306 let date = Date::new(2000, 1, 1).unwrap();
307 assert_eq!(format!("{}", date), "2000-01-01");
308
309 let date = Date::new(2100, 1, 1).unwrap();
310 assert_eq!(format!("{}", date), "2100-01-01");
311 }
312
313 #[test]
314 fn test_date_display_negative_years() {
315 let date = Date::new(0, 1, 1).unwrap();
317 assert_eq!(format!("{}", date), "0000-01-01");
318
319 let date = Date::new(-1, 1, 1).unwrap();
321 assert_eq!(format!("{}", date), "-0001-01-01");
322
323 let date = Date::new(-100, 12, 31).unwrap();
324 assert_eq!(format!("{}", date), "-0100-12-31");
325 }
326
327 #[test]
328 fn test_date_display_default() {
329 let date = Date::default();
330 assert_eq!(format!("{}", date), "1970-01-01");
331 }
332
333 #[test]
334 fn test_date_display_all_months() {
335 let months = [
336 (1, "01"),
337 (2, "02"),
338 (3, "03"),
339 (4, "04"),
340 (5, "05"),
341 (6, "06"),
342 (7, "07"),
343 (8, "08"),
344 (9, "09"),
345 (10, "10"),
346 (11, "11"),
347 (12, "12"),
348 ];
349
350 for (month, expected) in months {
351 let date = Date::new(2024, month, 15).unwrap();
352 assert_eq!(format!("{}", date), format!("2024-{}-15", expected));
353 }
354 }
355
356 #[test]
357 fn test_date_display_days_in_month() {
358 let test_cases = [
360 (2024, 1, 1, "2024-01-01"),
361 (2024, 1, 31, "2024-01-31"),
362 (2024, 2, 1, "2024-02-01"),
363 (2024, 2, 29, "2024-02-29"), (2024, 4, 1, "2024-04-01"),
365 (2024, 4, 30, "2024-04-30"),
366 (2024, 12, 1, "2024-12-01"),
367 (2024, 12, 31, "2024-12-31"),
368 ];
369
370 for (year, month, day, expected) in test_cases {
371 let date = Date::new(year, month, day).unwrap();
372 assert_eq!(format!("{}", date), expected);
373 }
374 }
375
376 #[test]
377 fn test_date_roundtrip() {
378 let test_dates = [
380 (1900, 1, 1),
381 (1970, 1, 1),
382 (2000, 2, 29), (2024, 12, 31),
384 (2100, 6, 15),
385 ];
386
387 for (year, month, day) in test_dates {
388 let date = Date::new(year, month, day).unwrap();
389 let days = date.to_days_since_epoch();
390 let recovered = Date::from_days_since_epoch(days).unwrap();
391
392 assert_eq!(date.year(), recovered.year());
393 assert_eq!(date.month(), recovered.month());
394 assert_eq!(date.day(), recovered.day());
395 }
396 }
397
398 #[test]
399 fn test_leap_year_detection() {
400 assert!(Date::is_leap_year(2000)); assert!(Date::is_leap_year(2024)); assert!(!Date::is_leap_year(1900)); assert!(!Date::is_leap_year(2023)); }
405
406 #[test]
407 fn test_invalid_dates() {
408 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()); }
415
416 #[test]
417 fn test_serde_roundtrip() {
418 let date = Date::new(2024, 3, 15).unwrap();
419 let json = to_string(&date).unwrap();
420 assert_eq!(json, "\"2024-03-15\"");
421
422 let recovered: Date = from_str(&json).unwrap();
423 assert_eq!(date, recovered);
424 }
425
426 fn assert_date_overflow<T: Debug>(result: Result<T, TypeError>) {
427 let err = result.expect_err("expected DateOverflow error");
428 match err {
429 TypeError::Temporal {
430 kind: TemporalKind::DateOverflow {
431 ..
432 },
433 ..
434 } => {}
435 other => panic!("expected DateOverflow, got: {:?}", other),
436 }
437 }
438
439 #[test]
440 fn test_from_ymd_invalid_month() {
441 assert_date_overflow(Date::from_ymd(2024, 0, 1));
442 assert_date_overflow(Date::from_ymd(2024, 13, 1));
443 }
444
445 #[test]
446 fn test_from_ymd_invalid_day() {
447 assert_date_overflow(Date::from_ymd(2024, 1, 0));
448 assert_date_overflow(Date::from_ymd(2024, 1, 32));
449 }
450
451 #[test]
452 fn test_from_ymd_non_leap_year() {
453 assert_date_overflow(Date::from_ymd(2023, 2, 29));
454 }
455}