simple_datetime_rs/
date.rs

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// Unix epoch serialization (default when serde is enabled without serde-struct)
22#[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        // Serialize as seconds since unix epoch (at midnight)
29        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            &REGULAR_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            &REGULAR_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                    &REGULAR_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            &REGULAR_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                    &REGULAR_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    /*
234        The function does not take "Adoption of the Gregorian calendar https://en.wikipedia.org/wiki/Adoption_of_the_Gregorian_calendar"
235        and similar things into consideration.
236    */
237    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            &REGULAR_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            &REGULAR_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    /*
288        The function does not take "Adoption of the Gregorian calendar https://en.wikipedia.org/wiki/Adoption_of_the_Gregorian_calendar"
289        and similar things into consideration.
290    */
291    #[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()); // Leap year
629        assert!(Date::new(2021, 2, 28).valid()); // Non-leap year
630        assert!(Date::new(2020, 12, 31).valid());
631        assert!(Date::new(2020, 1, 1).valid());
632
633        assert!(!Date::new(2021, 2, 29).valid()); // Feb 29 in non-leap year
634        assert!(!Date::new(2020, 2, 30).valid()); // Feb 30
635        assert!(!Date::new(2020, 4, 31).valid()); // Apr 31
636        assert!(!Date::new(2020, 6, 31).valid()); // Jun 31
637        assert!(!Date::new(2020, 9, 31).valid()); // Sep 31
638        assert!(!Date::new(2020, 11, 31).valid()); // Nov 31
639        assert!(!Date::new(2020, 0, 1).valid()); // Month 0
640        assert!(!Date::new(2020, 13, 1).valid()); // Month 13
641        assert!(!Date::new(2020, 1, 0).valid()); // Day 0
642        assert!(!Date::new(2020, 1, 32).valid()); // Day 32
643    }
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            // Test unix epoch date
731            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); // Leap year
756            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}