1use crate::constants::{
2 FOUR_YEARS_DAYS, LEAP_YEAR_DAYS, LEAP_YEAR_MONTH_DAYS, MONTH_DAYS, MONTHS_IN_YEAR,
3 REGULAR_YEAR_DAYS, REGULAR_YEAR_MONTH_DAYS, SECONDS_IN_DAY, THREE_REGULAR_YEAR_DAYS,
4 UNIX_EPOCH_YEAR,
5};
6use crate::date_error::{DateError, DateErrorKind};
7use crate::utils::date_util::{leap_year, month_days, month_index};
8use crate::utils::{crossplatform_util, date_util};
9use core::{cmp, fmt};
10use std::cmp::Ordering;
11use std::str::FromStr;
12
13#[derive(Copy, Clone, Eq, PartialEq)]
14#[cfg_attr(feature = "serde-struct", derive(serde::Serialize, serde::Deserialize))]
15pub struct Date {
16 pub year: u64,
17 pub month: u64,
18 pub day: u64,
19}
20
21#[cfg(all(feature = "serde", not(feature = "serde-struct")))]
23impl serde::Serialize for Date {
24 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
25 where
26 S: serde::Serializer,
27 {
28 let seconds = self.to_seconds_from_unix_epoch(false);
30 serializer.serialize_u64(seconds)
31 }
32}
33
34#[cfg(all(feature = "serde", not(feature = "serde-struct")))]
35impl<'de> serde::Deserialize<'de> for Date {
36 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
37 where
38 D: serde::Deserializer<'de>,
39 {
40 let seconds = u64::deserialize(deserializer)?;
41 let (date, _) = Date::from_seconds_since_unix_epoch(seconds);
42 Ok(date)
43 }
44}
45
46impl Date {
47 pub fn new(year: u64, month: u64, day: u64) -> Self {
48 let date = Date { year, month, day };
49 date
50 }
51
52 #[inline]
53 pub fn leap_year(&self) -> bool {
54 date_util::leap_year(self.year)
55 }
56
57 #[inline]
58 pub fn recent_leap_year(&self) -> u64 {
59 date_util::recent_leap_year(self.year)
60 }
61
62 #[inline]
63 pub fn next_leap_year(&self) -> u64 {
64 date_util::next_leap_year(self.year)
65 }
66
67 pub fn year_day(&self) -> u64 {
68 let is_leap = self.leap_year();
69 let month_days = if is_leap {
70 &LEAP_YEAR_MONTH_DAYS
71 } else {
72 ®ULAR_YEAR_MONTH_DAYS
73 };
74
75 let mut days: u64 = 0;
76 for i in 0..(self.month_index()) {
77 days += month_days[i];
78 }
79 days + self.day
80 }
81
82 pub fn days_to_next_year(&self) -> u64 {
83 let total = if self.leap_year() { 366u64 } else { 365 };
84 total - self.year_day()
85 }
86
87 #[inline]
88 pub fn month_index(&self) -> usize {
89 date_util::month_index(self.month)
90 }
91
92 pub fn valid(&self) -> bool {
93 if self.month < 1 || self.month > 12 || self.day < 1 {
94 return false;
95 }
96 let max_day = MONTH_DAYS[self.month_index()];
97 self.day <= max_day || (self.month == 2 && self.leap_year() && self.day == 29)
98 }
99
100 pub fn from_ms_dos_date(mut ms_dos_date: u16) -> Self {
101 let day = ms_dos_date & 0x1f;
102 ms_dos_date >>= 5;
103 let month = ms_dos_date & 0xf;
104 ms_dos_date >>= 4;
105 Date::new(ms_dos_date as u64 + 1980, month as u64, day as u64)
106 }
107
108 pub fn add_days(&self, days: u64) -> Self {
109 let mut result_year = self.year;
110 let mut result_month = self.month;
111 let mut result_day = self.day + days;
112
113 let mut is_leap = date_util::leap_year(result_year);
114 let mut month_days = if is_leap {
115 &LEAP_YEAR_MONTH_DAYS
116 } else {
117 ®ULAR_YEAR_MONTH_DAYS
118 };
119
120 loop {
121 let days_in_current_month = month_days[(result_month.saturating_sub(1)) as usize];
122
123 if result_day <= days_in_current_month {
124 break;
125 }
126
127 result_day -= days_in_current_month;
128 result_month += 1;
129
130 if result_month > 12 {
131 result_month = 1;
132 result_year += 1;
133 is_leap = date_util::leap_year(result_year);
134 month_days = if is_leap {
135 &LEAP_YEAR_MONTH_DAYS
136 } else {
137 ®ULAR_YEAR_MONTH_DAYS
138 };
139 }
140 }
141
142 Date::new(result_year, result_month, result_day)
143 }
144
145 pub fn from_seconds_since_unix_epoch(seconds: u64) -> (Self, u64) {
146 let days = seconds / SECONDS_IN_DAY;
147 (
148 Date::new(UNIX_EPOCH_YEAR, 1, 1).add_days(days),
149 seconds % SECONDS_IN_DAY,
150 )
151 }
152
153 pub fn to_seconds_from_unix_epoch(self, included: bool) -> u64 {
154 let days = self.to_days() - Date::new(UNIX_EPOCH_YEAR, 1, 1).to_days();
155 (days + included as u64) * SECONDS_IN_DAY
156 }
157
158 pub fn today() -> Self {
159 let (date, _) = Self::from_seconds_since_unix_epoch(crossplatform_util::now_seconds());
160 date
161 }
162
163 pub fn quarter(&self) -> usize {
164 (self.month_index() / 3) + 1
165 }
166
167 pub fn add_months(&self, months: i64) -> Self {
168 let months = (self.year as i64 * 12 + self.month_index() as i64 + months) as u64;
169 let month = months % MONTHS_IN_YEAR + 1;
170 let year = months / MONTHS_IN_YEAR;
171 let day = cmp::min(
172 date_util::month_days(month, date_util::leap_year(year)),
173 self.day,
174 );
175 Date { year, month, day }
176 }
177
178 pub fn sub_months(&self, months: i64) -> Self {
179 self.add_months(-months)
180 }
181
182 pub fn is_month_last_day(&self) -> bool {
183 self.day == date_util::month_days(self.month, self.leap_year())
184 }
185
186 pub fn month_last_day(&self) -> Self {
187 Date {
188 year: self.year,
189 month: self.month,
190 day: date_util::month_days(self.month, self.leap_year()),
191 }
192 }
193
194 pub fn sub_days(&self, days: u64) -> Self {
195 let mut result_year = self.year;
196 let mut result_month = self.month;
197 let mut result_day = self.day;
198
199 let mut is_leap = date_util::leap_year(result_year);
200 let mut month_days = if is_leap {
201 &LEAP_YEAR_MONTH_DAYS
202 } else {
203 ®ULAR_YEAR_MONTH_DAYS
204 };
205
206 let mut remaining_days = days;
207 while remaining_days > 0 {
208 if result_day > remaining_days {
209 result_day -= remaining_days;
210 break;
211 }
212
213 remaining_days -= result_day;
214 result_month -= 1;
215
216 if result_month == 0 {
217 result_month = 12;
218 result_year -= 1;
219 is_leap = date_util::leap_year(result_year);
220 month_days = if is_leap {
221 &LEAP_YEAR_MONTH_DAYS
222 } else {
223 ®ULAR_YEAR_MONTH_DAYS
224 };
225 }
226
227 result_day = month_days[(result_month.saturating_sub(1)) as usize];
228 }
229
230 Date::new(result_year, result_month, result_day)
231 }
232
233 pub fn to_days(&self) -> u64 {
238 let year_elapsed = self.year - 1;
239 let leap_years = year_elapsed / 4;
240 let regular_years = year_elapsed - leap_years;
241
242 let base_days = leap_years * LEAP_YEAR_DAYS + regular_years * REGULAR_YEAR_DAYS;
243 let is_leap = self.leap_year();
244 let month_days = if is_leap {
245 &LEAP_YEAR_MONTH_DAYS
246 } else {
247 ®ULAR_YEAR_MONTH_DAYS
248 };
249
250 let mut days = 0;
251 for i in 0..(self.month_index()) {
252 days += month_days[i];
253 }
254
255 base_days + days + self.day
256 }
257
258 pub fn from_days(days: u64) -> Self {
259 let days = days - 1;
260 let quarters = days / FOUR_YEARS_DAYS;
261 let days = days % FOUR_YEARS_DAYS;
262 let (years, mut days) = if days / THREE_REGULAR_YEAR_DAYS == 0 {
263 (days / REGULAR_YEAR_DAYS, days % REGULAR_YEAR_DAYS)
264 } else {
265 (3, days % THREE_REGULAR_YEAR_DAYS)
266 };
267 let year = (quarters << 2) + years + 1;
268
269 let is_leap = date_util::leap_year(year);
270 let month_days = if is_leap {
271 &LEAP_YEAR_MONTH_DAYS
272 } else {
273 ®ULAR_YEAR_MONTH_DAYS
274 };
275
276 let mut month = 1;
277 for &days_in_month in month_days {
278 if days < days_in_month {
279 break;
280 }
281 days -= days_in_month;
282 month += 1;
283 }
284 Date::new(year, month, days + 1)
285 }
286
287 #[inline]
292 fn weekday(&self) -> u8 {
293 (self.to_days() % 7) as u8
294 }
295
296 pub fn is_monday(&self) -> bool {
297 self.weekday() == 2
298 }
299
300 pub fn is_tuesday(&self) -> bool {
301 self.weekday() == 3
302 }
303
304 pub fn is_wednesday(&self) -> bool {
305 self.weekday() == 4
306 }
307
308 pub fn is_thursday(&self) -> bool {
309 self.weekday() == 5
310 }
311
312 pub fn is_friday(&self) -> bool {
313 self.weekday() == 6
314 }
315
316 pub fn is_saturday(&self) -> bool {
317 self.weekday() == 0
318 }
319
320 pub fn is_sunday(&self) -> bool {
321 self.weekday() == 1
322 }
323
324 pub fn is_weekend(&self) -> bool {
325 self.weekday() < 2
326 }
327
328 pub fn is_week_day(&self) -> bool {
329 self.weekday() > 1
330 }
331
332 pub fn normalize(&self) -> Date {
333 let month_index = month_index(self.month) as u64;
334 let year = month_index / 12 + self.year;
335 let month = month_index as u64 % 12 + 1;
336 let mut result = Self::new(year, month, self.day);
337 let max_days = month_days(month, leap_year(year));
338 if max_days < result.day {
339 let add = result.day - max_days;
340 result.day = max_days;
341 result = result.add_days(add);
342 }
343 result
344 }
345}
346
347impl std::ops::Sub for Date {
348 type Output = u64;
349
350 fn sub(self, rhs: Self) -> Self::Output {
351 self.to_days() - rhs.to_days()
352 }
353}
354
355impl fmt::Display for Date {
356 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
357 write!(f, "{}-{:02}-{:02}", self.year, self.month, self.day)
358 }
359}
360
361impl fmt::Debug for Date {
362 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
363 fmt::Display::fmt(self, f)
364 }
365}
366
367impl FromStr for Date {
368 type Err = DateError;
369
370 fn from_str(date_str: &str) -> Result<Self, Self::Err> {
371 let bytes = date_str.as_bytes();
372 if bytes.len() != 10 || bytes[4] != b'-' || bytes[7] != b'-' {
373 return Err(DateErrorKind::WrongDateStringFormat.into());
374 }
375
376 let year = parse_digits(&bytes[0..4])?;
377 let month = parse_digits(&bytes[5..7])?;
378 let day = parse_digits(&bytes[8..10])?;
379
380 Ok(Date::new(year, month, day))
381 }
382}
383
384#[inline]
385fn parse_digits(bytes: &[u8]) -> Result<u64, DateError> {
386 let mut result = 0u64;
387 for &byte in bytes {
388 if byte < b'0' || byte > b'9' {
389 return Err(DateErrorKind::WrongDateStringFormat.into());
390 }
391 result = result * 10 + (byte - b'0') as u64;
392 }
393 Ok(result)
394}
395
396impl Ord for Date {
397 fn cmp(&self, other: &Self) -> Ordering {
398 if self.year != other.year {
399 return self.year.cmp(&other.year);
400 }
401 if self.month != other.month {
402 return self.month.cmp(&other.month);
403 }
404 self.day.cmp(&other.day)
405 }
406}
407
408impl PartialOrd for Date {
409 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
410 Some(self.cmp(other))
411 }
412}
413
414#[cfg(test)]
415mod tests {
416 use super::*;
417
418 #[test]
419 fn test_date_year_day() {
420 let date = Date::new(2020, 1, 1);
421 assert_eq!(date.year_day(), 1);
422 let date = Date::new(2018, 2, 28);
423 assert_eq!(date.year_day(), 59);
424 let date = Date::new(2016, 12, 31);
425 assert_eq!(date.year_day(), 366);
426 }
427
428 #[test]
429 fn test_date_sub() {
430 let date_1 = Date::new(2020, 1, 1);
431 let date_2 = Date::new(2019, 12, 31);
432 assert_eq!(date_1 - date_2, 1);
433 let date_1 = Date::new(2020, 1, 1);
434 let date_2 = Date::new(2016, 1, 1);
435 assert_eq!(date_1 - date_2, 1461);
436 let date_1 = Date::new(2020, 1, 1);
437 let date_2 = Date::new(2016, 3, 1);
438 assert_eq!(date_1 - date_2, 1401);
439 let date_1 = Date::new(2020, 3, 4);
440 let date_2 = Date::new(2016, 3, 1);
441 assert_eq!(date_1 - date_2, 1464);
442 let date_1 = Date::new(2021, 3, 2);
443 let date_2 = Date::new(2019, 12, 31);
444 assert_eq!(date_1 - date_2, 427);
445 let date_1 = Date::new(2021, 3, 2);
446 let date_2 = Date::new(2020, 12, 31);
447 assert_eq!(date_1 - date_2, 61);
448 let date_1 = Date::new(2021, 3, 2);
449 let date_2 = Date::new(2020, 1, 15);
450 assert_eq!(date_1 - date_2, 412);
451 let date_1 = Date::new(2020, 12, 31);
452 let date_2 = Date::new(2020, 1, 1);
453 assert_eq!(date_1 - date_2, 365);
454 }
455
456 #[test]
457 fn test_date_from_str() -> Result<(), DateError> {
458 assert_eq!(Date::from_str("2020-02-29")?, Date::new(2020, 2, 29));
459 Ok(())
460 }
461
462 #[test]
463 fn test_add_days() {
464 assert_eq!(Date::new(2019, 12, 31).add_days(1), Date::new(2020, 1, 1));
465 assert_eq!(
466 Date::new(2019, 12, 31).add_days(3753),
467 Date::new(2030, 4, 10)
468 );
469 assert_eq!(Date::new(2019, 2, 28).add_days(365), Date::new(2020, 2, 28));
470 assert_eq!(Date::new(2019, 2, 28).add_days(366), Date::new(2020, 2, 29));
471 assert_eq!(Date::new(2019, 3, 1).add_days(366), Date::new(2020, 3, 1));
472 assert_eq!(Date::new(2018, 1, 1).add_days(1198), Date::new(2021, 4, 13));
473 }
474
475 #[test]
476 fn test_ms_dos_date() {
477 assert_eq!(Date::from_ms_dos_date(0x354b), Date::new(2006, 10, 11));
478 }
479
480 #[test]
481 fn test_date_cmp() {
482 assert!(Date::new(2019, 12, 31) < Date::new(2020, 1, 1));
483 assert!(Date::new(2020, 2, 1) > Date::new(2020, 1, 31));
484 assert!(Date::new(2020, 3, 31) > Date::new(2020, 3, 30));
485 assert_eq!(Date::new(2020, 1, 1), Date::new(2020, 1, 1));
486 }
487
488 #[test]
489 fn test_add_months() {
490 assert_eq!(
491 Date::new(2019, 12, 31).add_months(2),
492 Date::new(2020, 2, 29)
493 );
494 assert_eq!(
495 Date::new(2019, 12, 31).add_months(26),
496 Date::new(2022, 2, 28)
497 );
498 assert_eq!(
499 Date::new(2019, 12, 31).add_months(1),
500 Date::new(2020, 1, 31)
501 );
502 assert_eq!(
503 Date::new(2020, 2, 29).add_months(-2),
504 Date::new(2019, 12, 29)
505 );
506 }
507
508 #[test]
509 fn test_is_month_last_day() {
510 assert!(Date::new(2019, 12, 31).is_month_last_day());
511 assert!(!Date::new(2019, 12, 30).is_month_last_day());
512 assert!(Date::new(2019, 2, 28).is_month_last_day());
513 assert!(!Date::new(2020, 2, 28).is_month_last_day());
514 assert!(Date::new(2020, 2, 29).is_month_last_day());
515 }
516
517 #[test]
518 fn test_month_last_day() {
519 assert_eq!(
520 Date::new(2019, 2, 2).month_last_day(),
521 Date::new(2019, 2, 28)
522 );
523 assert_eq!(
524 Date::new(2020, 2, 2).month_last_day(),
525 Date::new(2020, 2, 29)
526 );
527 }
528
529 #[test]
530 fn test_to_seconds_from_unix_epoch() {
531 assert_eq!(Date::new(1970, 1, 1).to_seconds_from_unix_epoch(false), 0);
532 assert_eq!(
533 Date::new(1970, 1, 1).to_seconds_from_unix_epoch(true),
534 SECONDS_IN_DAY
535 );
536 }
537
538 #[test]
539 fn test_to_days() {
540 assert_eq!(Date::new(1, 1, 1).to_days(), 1);
541 assert_eq!(Date::new(1, 12, 31).to_days(), 365);
542 assert_eq!(Date::new(4, 2, 29).to_days(), 1155);
543 assert_eq!(Date::new(5, 1, 1).to_days(), 1462);
544 }
545
546 #[test]
547 fn test_from_days() {
548 assert_eq!(Date::from_days(1), Date::new(1, 1, 1));
549 assert_eq!(Date::from_days(365), Date::new(1, 12, 31));
550 assert_eq!(Date::from_days(1155), Date::new(4, 2, 29));
551 assert_eq!(Date::from_days(1462), Date::new(5, 1, 1));
552 }
553
554 #[test]
555 fn test_is_monday() {
556 assert_eq!(Date::new(2021, 8, 2).is_monday(), true);
557 assert_eq!(Date::new(2021, 8, 3).is_monday(), false);
558 }
559
560 #[test]
561 fn test_is_tuesday() {
562 assert_eq!(Date::new(2021, 8, 3).is_tuesday(), true);
563 assert_eq!(Date::new(2021, 8, 4).is_tuesday(), false);
564 }
565
566 #[test]
567 fn test_is_wednesday() {
568 assert_eq!(Date::new(2021, 8, 4).is_wednesday(), true);
569 assert_eq!(Date::new(2021, 8, 5).is_wednesday(), false);
570 }
571
572 #[test]
573 fn test_is_thursday() {
574 assert_eq!(Date::new(2021, 8, 5).is_thursday(), true);
575 assert_eq!(Date::new(2021, 8, 6).is_thursday(), false);
576 }
577
578 #[test]
579 fn test_is_friday() {
580 assert_eq!(Date::new(2021, 8, 6).is_friday(), true);
581 assert_eq!(Date::new(2021, 8, 7).is_friday(), false);
582 }
583
584 #[test]
585 fn test_is_saturday() {
586 assert_eq!(Date::new(2021, 8, 7).is_saturday(), true);
587 assert_eq!(Date::new(2021, 8, 8).is_saturday(), false);
588 }
589
590 #[test]
591 fn test_is_sunday() {
592 assert_eq!(Date::new(2021, 8, 8).is_sunday(), true);
593 assert_eq!(Date::new(2021, 8, 9).is_sunday(), false);
594 }
595
596 #[test]
597 fn test_add_sub_days() {
598 let a = Date::new(2020, 12, 31);
599 assert_eq!(a.add_days(1).sub_days(1), a);
600 }
601
602 #[test]
603 fn test_sub_months() {
604 assert_eq!(
605 Date::new(2020, 2, 29).sub_months(2),
606 Date::new(2019, 12, 29)
607 );
608 assert_eq!(Date::new(2020, 4, 30).sub_months(2), Date::new(2020, 2, 29));
609 assert_eq!(
610 Date::new(2022, 2, 28).sub_months(26),
611 Date::new(2019, 12, 28)
612 );
613 assert_eq!(
614 Date::new(2020, 1, 31).sub_months(1),
615 Date::new(2019, 12, 31)
616 );
617 }
618
619 #[test]
620 fn test_normalize() {
621 assert_eq!(Date::new(2020, 49, 32).normalize(), Date::new(2024, 2, 1));
622 assert_eq!(Date::new(2020, 49, 60).normalize(), Date::new(2024, 2, 29));
623 assert_eq!(Date::new(2020, 49, 61).normalize(), Date::new(2024, 3, 1));
624 }
625
626 #[test]
627 fn test_date_validation() {
628 assert!(Date::new(2020, 2, 29).valid()); assert!(Date::new(2021, 2, 28).valid()); assert!(Date::new(2020, 12, 31).valid());
631 assert!(Date::new(2020, 1, 1).valid());
632
633 assert!(!Date::new(2021, 2, 29).valid()); assert!(!Date::new(2020, 2, 30).valid()); assert!(!Date::new(2020, 4, 31).valid()); assert!(!Date::new(2020, 6, 31).valid()); assert!(!Date::new(2020, 9, 31).valid()); assert!(!Date::new(2020, 11, 31).valid()); assert!(!Date::new(2020, 0, 1).valid()); assert!(!Date::new(2020, 13, 1).valid()); assert!(!Date::new(2020, 1, 0).valid()); assert!(!Date::new(2020, 1, 32).valid()); }
644
645 #[test]
646 fn test_date_from_str_invalid() {
647 assert!("invalid".parse::<Date>().is_err());
648 assert!("2020".parse::<Date>().is_err());
649 assert!("2020-13".parse::<Date>().is_err());
650 assert!("not-a-date".parse::<Date>().is_err());
651 assert!("2020/01/01".parse::<Date>().is_err());
652 }
653
654 #[test]
655 fn test_edge_cases() {
656 let date = Date::new(1, 1, 1);
657 assert!(date.valid());
658 assert_eq!(date.to_days(), 1);
659
660 let date = Date::new(9999, 12, 31);
661 assert!(date.valid());
662
663 assert!(Date::new(2000, 1, 1).leap_year());
664 assert!(!Date::new(1900, 1, 1).leap_year());
665 assert!(Date::new(2004, 1, 1).leap_year());
666 assert!(!Date::new(2001, 1, 1).leap_year());
667 }
668
669 #[test]
670 fn test_quarter_calculation() {
671 assert_eq!(Date::new(2020, 1, 1).quarter(), 1);
672 assert_eq!(Date::new(2020, 3, 31).quarter(), 1);
673 assert_eq!(Date::new(2020, 4, 1).quarter(), 2);
674 assert_eq!(Date::new(2020, 6, 30).quarter(), 2);
675 assert_eq!(Date::new(2020, 7, 1).quarter(), 3);
676 assert_eq!(Date::new(2020, 9, 30).quarter(), 3);
677 assert_eq!(Date::new(2020, 10, 1).quarter(), 4);
678 assert_eq!(Date::new(2020, 12, 31).quarter(), 4);
679 }
680
681 #[test]
682 fn test_weekend_weekday() {
683 assert!(Date::new(2021, 8, 7).is_weekend());
684 assert!(Date::new(2021, 8, 8).is_weekend());
685 assert!(!Date::new(2021, 8, 9).is_weekend());
686 assert!(Date::new(2021, 8, 9).is_week_day());
687 assert!(Date::new(2021, 8, 10).is_week_day());
688 assert!(!Date::new(2021, 8, 7).is_week_day());
689 assert!(!Date::new(2021, 8, 8).is_week_day());
690 }
691
692 #[test]
693 fn test_days_to_next_year() {
694 assert_eq!(Date::new(2020, 1, 1).days_to_next_year(), 365);
695 assert_eq!(Date::new(2021, 1, 1).days_to_next_year(), 364);
696 assert_eq!(Date::new(2020, 12, 31).days_to_next_year(), 0);
697 assert_eq!(Date::new(2020, 6, 15).days_to_next_year(), 199);
698 }
699
700 #[test]
701 fn test_year_day() {
702 assert_eq!(Date::new(2020, 1, 1).year_day(), 1);
703 assert_eq!(Date::new(2020, 1, 31).year_day(), 31);
704 assert_eq!(Date::new(2020, 2, 1).year_day(), 32);
705 assert_eq!(Date::new(2020, 2, 29).year_day(), 60);
706 assert_eq!(Date::new(2021, 2, 28).year_day(), 59);
707 assert_eq!(Date::new(2020, 12, 31).year_day(), 366);
708 assert_eq!(Date::new(2021, 12, 31).year_day(), 365);
709 }
710
711 #[cfg(feature = "serde")]
712 mod serde_tests {
713 use super::*;
714 use serde_json;
715
716 #[test]
717 #[cfg(not(feature = "serde-struct"))]
718 fn test_serde_unix_epoch() {
719 let date = Date::new(1970, 1, 1);
720 let json = serde_json::to_string(&date).unwrap();
721 assert_eq!(json, "0");
722 let deserialized: Date = serde_json::from_str(&json).unwrap();
723 assert_eq!(deserialized, date);
724
725 let date = Date::new(2024, 1, 15);
726 let json = serde_json::to_string(&date).unwrap();
727 let deserialized: Date = serde_json::from_str(&json).unwrap();
728 assert_eq!(deserialized, date);
729
730 let date = Date::new(2020, 1, 1);
732 let expected_seconds = date.to_seconds_from_unix_epoch(false);
733 let json = serde_json::to_string(&date).unwrap();
734 assert_eq!(json, expected_seconds.to_string());
735 let deserialized: Date = serde_json::from_str(&json).unwrap();
736 assert_eq!(deserialized, date);
737 }
738
739 #[test]
740 #[cfg(feature = "serde-struct")]
741 fn test_serde_struct() {
742 let date = Date::new(2024, 1, 15);
743 let json = serde_json::to_string(&date).unwrap();
744 assert!(json.contains("\"year\":2024"));
745 assert!(json.contains("\"month\":1"));
746 assert!(json.contains("\"day\":15"));
747 let deserialized: Date = serde_json::from_str(&json).unwrap();
748 assert_eq!(deserialized, date);
749
750 let date = Date::new(1970, 1, 1);
751 let json = serde_json::to_string(&date).unwrap();
752 let deserialized: Date = serde_json::from_str(&json).unwrap();
753 assert_eq!(deserialized, date);
754
755 let date = Date::new(2020, 2, 29); let json = serde_json::to_string(&date).unwrap();
757 let deserialized: Date = serde_json::from_str(&json).unwrap();
758 assert_eq!(deserialized, date);
759 }
760
761 #[test]
762 fn test_serde_roundtrip() {
763 let dates = vec![
764 Date::new(1970, 1, 1),
765 Date::new(2020, 1, 1),
766 Date::new(2024, 1, 15),
767 Date::new(2020, 2, 29),
768 Date::new(2021, 12, 31),
769 ];
770
771 for date in dates {
772 let json = serde_json::to_string(&date).unwrap();
773 let deserialized: Date = serde_json::from_str(&json).unwrap();
774 assert_eq!(deserialized, date, "Failed roundtrip for date: {}", date);
775 }
776 }
777 }
778}