simple_datetime_rs/
date.rs

1use std::str::FromStr;
2use core::{fmt, cmp};
3use crate::date_error::{DateError, DateErrorKind};
4use crate::constants::{MONTH_DAYS, SECONDS_IN_DAY, FOUR_YEARS_DAYS, LEAP_YEAR_DAYS, REGULAR_YEAR_DAYS, UNIX_EPOCH_YEAR, MONTHS_IN_YEAR, THREE_REGULAR_YEAR_DAYS, REGULAR_YEAR_MONTH_DAYS, LEAP_YEAR_MONTH_DAYS};
5use crate::utils::{crossplatform_util, date_util};
6use std::cmp::Ordering;
7use crate::utils::date_util::{leap_year, month_days, month_index};
8
9#[derive(Copy, Clone, Eq, PartialEq)]
10pub struct Date {
11    pub year:u64,
12    pub month:u64,
13    pub day:u64,
14}
15
16impl Date {
17    pub fn new(year:u64, month:u64, day:u64) -> Self{
18        let date = Date{year, month, day};
19        date
20    }
21
22    #[inline]
23    pub fn leap_year(&self) -> bool {
24        date_util::leap_year(self.year)
25    }
26
27    #[inline]
28    pub fn recent_leap_year(&self) -> u64 {
29        date_util::recent_leap_year(self.year)
30    }
31
32    #[inline]
33    pub fn next_leap_year(&self) -> u64 {
34        date_util::next_leap_year(self.year)
35    }
36
37    pub fn year_day(&self) -> u64 {
38        let is_leap = self.leap_year();
39        let month_days = if is_leap { &LEAP_YEAR_MONTH_DAYS } else { &REGULAR_YEAR_MONTH_DAYS };
40        
41        let mut days:u64 = 0;
42        for i in 0..(self.month_index()) {
43            days += month_days[i];
44        }
45        days + self.day
46    }
47
48    pub fn days_to_next_year(&self) -> u64 {
49        let total = if self.leap_year() {366u64} else {365};
50        total - self.year_day()
51    }
52
53    #[inline]
54    pub fn month_index(&self) -> usize {
55        date_util::month_index(self.month)
56    }
57
58    pub fn valid(&self) -> bool {
59        if self.month < 1 || self.month > 12 || self.day < 1 {
60            return false;
61        }
62        let max_day = MONTH_DAYS[self.month_index()];
63        self.day <= max_day || (self.month == 2 && self.leap_year() && self.day == 29)
64    }
65
66    pub fn from_ms_dos_date(mut ms_dos_date:u16) -> Self {
67        let day = ms_dos_date & 0x1f;
68        ms_dos_date >>= 5;
69        let month = ms_dos_date & 0xf;
70        ms_dos_date >>= 4;
71        Date::new(ms_dos_date as u64 + 1980, month as u64, day as u64)
72    }
73
74    pub fn add_days(&self, days:u64) -> Self{
75        let mut result_year = self.year;
76        let mut result_month = self.month;
77        let mut result_day = self.day + days;
78        
79        let mut is_leap = date_util::leap_year(result_year);
80        let mut month_days = if is_leap { &LEAP_YEAR_MONTH_DAYS } else { &REGULAR_YEAR_MONTH_DAYS };
81        
82        loop {
83            let days_in_current_month = month_days[(result_month.saturating_sub(1)) as usize];
84            
85            if result_day <= days_in_current_month {
86                break;
87            }
88            
89            result_day -= days_in_current_month;
90            result_month += 1;
91            
92            if result_month > 12 {
93                result_month = 1;
94                result_year += 1;
95                is_leap = date_util::leap_year(result_year);
96                month_days = if is_leap { &LEAP_YEAR_MONTH_DAYS } else { &REGULAR_YEAR_MONTH_DAYS };
97            }
98        }
99        
100        Date::new(result_year, result_month, result_day)
101    }
102
103    pub fn from_seconds_since_unix_epoch(seconds:u64) -> (Self, u64) {
104        let days = seconds / SECONDS_IN_DAY;
105        (Date::new(UNIX_EPOCH_YEAR,1, 1).add_days(days), seconds % SECONDS_IN_DAY)
106    }
107
108    pub fn to_seconds_from_unix_epoch(self, included:bool) -> u64 {
109        let days = self.to_days() - Date::new(UNIX_EPOCH_YEAR,1, 1).to_days();
110        (days + included as u64) * SECONDS_IN_DAY
111    }
112
113    pub fn today() -> Self {
114        let (date, _) = Self::from_seconds_since_unix_epoch(crossplatform_util::now_seconds());
115        date
116    }
117
118    pub fn quarter(&self) -> usize {
119        (self.month_index() / 3) + 1
120    }
121
122    pub fn add_months(&self, months:i64) -> Self {
123        let months = (self.year as i64 * 12 + self.month_index() as i64 + months) as u64;
124        let month = months % MONTHS_IN_YEAR + 1;
125        let year = months / MONTHS_IN_YEAR;
126        let day = cmp::min(date_util::month_days(month, date_util::leap_year(year)), self.day);
127        Date {
128            year,
129            month,
130            day
131        }
132    }
133
134    pub fn sub_months(&self, months:i64) -> Self {
135        self.add_months(-months)
136    }
137
138    pub fn is_month_last_day(&self) -> bool {
139        self.day == date_util::month_days(self.month, self.leap_year())
140    }
141
142    pub fn month_last_day(&self) -> Self {
143        Date {
144            year: self.year,
145            month: self.month,
146            day: date_util::month_days(self.month, self.leap_year())
147        }
148    }
149
150    pub fn sub_days(&self, days:u64) -> Self {
151        let mut result_year = self.year;
152        let mut result_month = self.month;
153        let mut result_day = self.day;
154        
155        let mut is_leap = date_util::leap_year(result_year);
156        let mut month_days = if is_leap { &LEAP_YEAR_MONTH_DAYS } else { &REGULAR_YEAR_MONTH_DAYS };
157        
158        let mut remaining_days = days;
159        while remaining_days > 0 {
160            if result_day > remaining_days {
161                result_day -= remaining_days;
162                break;
163            }
164            
165            remaining_days -= result_day;
166            result_month -= 1;
167            
168            if result_month == 0 {
169                result_month = 12;
170                result_year -= 1;
171                is_leap = date_util::leap_year(result_year);
172                month_days = if is_leap { &LEAP_YEAR_MONTH_DAYS } else { &REGULAR_YEAR_MONTH_DAYS };
173            }
174            
175            result_day = month_days[(result_month.saturating_sub(1)) as usize];
176        }
177        
178        Date::new(result_year, result_month, result_day)
179    }
180
181    /*
182        The function does not take "Adoption of the Gregorian calendar https://en.wikipedia.org/wiki/Adoption_of_the_Gregorian_calendar"
183        and similar things into consideration.
184    */
185    pub fn to_days(&self) -> u64 {
186        let year_elapsed = self.year - 1;
187        let leap_years = year_elapsed / 4;
188        let regular_years = year_elapsed - leap_years;
189        
190        let base_days = leap_years * LEAP_YEAR_DAYS + regular_years * REGULAR_YEAR_DAYS;
191        let is_leap = self.leap_year();
192        let month_days = if is_leap { &LEAP_YEAR_MONTH_DAYS } else { &REGULAR_YEAR_MONTH_DAYS };
193        
194        let mut days = 0;
195        for i in 0..(self.month_index()) {
196            days += month_days[i];
197        }
198        
199        base_days + days + self.day
200    }
201
202    pub fn from_days(days:u64) -> Self {
203        let days = days - 1;
204        let quarters = days / FOUR_YEARS_DAYS;
205        let days  = days % FOUR_YEARS_DAYS;
206        let (years, mut days) = if days / THREE_REGULAR_YEAR_DAYS == 0 {
207            (days / REGULAR_YEAR_DAYS, days % REGULAR_YEAR_DAYS)
208        } else {(3, days % THREE_REGULAR_YEAR_DAYS)};
209        let year = (quarters << 2) + years + 1;
210        
211        let is_leap = date_util::leap_year(year);
212        let month_days = if is_leap { &LEAP_YEAR_MONTH_DAYS } else { &REGULAR_YEAR_MONTH_DAYS };
213        
214        let mut month = 1;
215        for &days_in_month in month_days {
216            if days < days_in_month {
217                break;
218            }
219            days -= days_in_month;
220            month += 1;
221        }
222        Date::new(year, month, days + 1)
223    }
224
225    /*
226        The function does not take "Adoption of the Gregorian calendar https://en.wikipedia.org/wiki/Adoption_of_the_Gregorian_calendar"
227        and similar things into consideration.
228    */
229    #[inline]
230    fn weekday(&self) -> u8 {
231        (self.to_days() % 7) as u8
232    }
233
234    pub fn is_monday(&self) -> bool{
235        self.weekday() == 2
236    }
237
238    pub fn is_tuesday(&self) -> bool{
239        self.weekday() == 3
240    }
241
242    pub fn is_wednesday(&self) -> bool{
243        self.weekday() == 4
244    }
245
246    pub fn is_thursday(&self) -> bool{
247        self.weekday() == 5
248    }
249
250    pub fn is_friday(&self) -> bool{
251        self.weekday() == 6
252    }
253
254    pub fn is_saturday(&self) -> bool{
255        self.weekday() == 0
256    }
257
258    pub fn is_sunday(&self) -> bool{
259        self.weekday() == 1
260    }
261
262    pub fn is_weekend(&self) -> bool{
263        self.weekday() < 2
264    }
265
266    pub fn is_week_day(&self) -> bool{
267        self.weekday() > 1
268    }
269
270    pub fn normalize(&self) -> Date {
271        let month_index = month_index(self.month) as u64;
272        let year = month_index / 12 + self.year;
273        let month = month_index as u64 % 12 + 1;
274        let mut result = Self::new(year, month, self.day);
275        let max_days = month_days(month, leap_year(year));
276        if max_days < result.day {
277            let add = result.day - max_days;
278            result.day = max_days;
279            result = result.add_days(add);
280        }
281        result
282    }
283}
284
285impl std::ops::Sub for Date {
286    type Output = u64;
287
288    fn sub(self, rhs: Self) -> Self::Output {
289        self.to_days() - rhs.to_days()
290    }
291}
292
293impl fmt::Display for Date {
294    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295        write!(f, "{}-{:02}-{:02}", self.year, self.month, self.day)
296    }
297}
298
299impl fmt::Debug for Date {
300    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301        fmt::Display::fmt(self, f)
302    }
303}
304
305impl FromStr for Date {
306    type Err = DateError;
307
308    fn from_str(date_str: &str) -> Result<Self, Self::Err> {
309        let bytes = date_str.as_bytes();
310        if bytes.len() != 10 || bytes[4] != b'-' || bytes[7] != b'-' {
311            return Err(DateErrorKind::WrongDateStringFormat.into());
312        }
313        
314        let year = parse_digits(&bytes[0..4])?;
315        let month = parse_digits(&bytes[5..7])?;
316        let day = parse_digits(&bytes[8..10])?;
317        
318        Ok(Date::new(year, month, day))
319    }
320}
321
322#[inline]
323fn parse_digits(bytes: &[u8]) -> Result<u64, DateError> {
324    let mut result = 0u64;
325    for &byte in bytes {
326        if byte < b'0' || byte > b'9' {
327            return Err(DateErrorKind::WrongDateStringFormat.into());
328        }
329        result = result * 10 + (byte - b'0') as u64;
330    }
331    Ok(result)
332}
333
334impl Ord for Date {
335    fn cmp(&self, other: &Self) -> Ordering {
336        if self.year != other.year {
337            return self.year.cmp(&other.year);
338        }
339        if self.month != other.month {
340            return self.month.cmp(&other.month);
341        }
342        self.day.cmp(&other.day)
343    }
344}
345
346impl PartialOrd for Date {
347    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
348        Some(self.cmp(other))
349    }
350}
351
352#[cfg(test)]
353mod tests {
354    use super::*;
355
356    #[test]
357    fn test_date_year_day(){
358        let date = Date::new(2020, 1,1);
359        assert_eq!(date.year_day(), 1);
360        let date = Date::new(2018, 2,28);
361        assert_eq!(date.year_day(), 59);
362        let date = Date::new(2016, 12,31);
363        assert_eq!(date.year_day(), 366);
364    }
365
366    #[test]
367    fn test_date_sub(){
368        let date_1 = Date::new(2020, 1,1);
369        let date_2 = Date::new(2019, 12,31);
370        assert_eq!(date_1 - date_2, 1);
371        let date_1 = Date::new(2020, 1,1);
372        let date_2 = Date::new(2016, 1,1);
373        assert_eq!(date_1 - date_2, 1461);
374        let date_1 = Date::new(2020, 1,1);
375        let date_2 = Date::new(2016, 3,1);
376        assert_eq!(date_1 - date_2, 1401);
377        let date_1 = Date::new(2020, 3,4);
378        let date_2 = Date::new(2016, 3,1);
379        assert_eq!(date_1 - date_2, 1464);
380        let date_1 = Date::new(2021, 3,2);
381        let date_2 = Date::new(2019, 12,31);
382        assert_eq!(date_1 - date_2, 427);
383        let date_1 = Date::new(2021, 3,2);
384        let date_2 = Date::new(2020, 12,31);
385        assert_eq!(date_1 - date_2, 61);
386        let date_1 = Date::new(2021, 3,2);
387        let date_2 = Date::new(2020, 1,15);
388        assert_eq!(date_1 - date_2, 412);
389        let date_1 = Date::new(2020, 12,31);
390        let date_2 = Date::new(2020, 1,1);
391        assert_eq!(date_1 - date_2, 365);
392    }
393
394    #[test]
395    fn test_date_from_str() -> Result<(), DateError>{
396        assert_eq!(Date::from_str("2020-02-29")?, Date::new(2020, 2, 29));
397        Ok(())
398    }
399
400    #[test]
401    fn test_add_days(){
402        assert_eq!(Date::new(2019, 12,31).add_days(1), Date::new(2020, 1,1));
403        assert_eq!(Date::new(2019, 12,31).add_days(3753), Date::new(2030, 4,10));
404        assert_eq!(Date::new(2019, 2,28).add_days(365), Date::new(2020, 2,28));
405        assert_eq!(Date::new(2019, 2,28).add_days(366), Date::new(2020, 2,29));
406        assert_eq!(Date::new(2019, 3,1).add_days(366), Date::new(2020, 3,1));
407        assert_eq!(Date::new(2018, 1,1).add_days(1198), Date::new(2021, 4,13));
408    }
409
410    #[test]
411    fn test_ms_dos_date(){
412        assert_eq!(Date::from_ms_dos_date(0x354b), Date::new(2006, 10, 11));
413    }
414
415    #[test]
416    fn test_date_cmp() {
417        assert!(Date::new(2019, 12,31) < Date::new(2020, 1,1));
418        assert!(Date::new(2020, 2,1) > Date::new(2020, 1,31));
419        assert!(Date::new(2020, 3,31) > Date::new(2020, 3,30));
420        assert_eq!(Date::new(2020, 1,1), Date::new(2020, 1,1));
421    }
422
423    #[test]
424    fn test_add_months() {
425        assert_eq!(Date::new(2019, 12,31).add_months(2), Date::new(2020, 2,29));
426        assert_eq!(Date::new(2019, 12,31).add_months(26), Date::new(2022, 2,28));
427        assert_eq!(Date::new(2019, 12,31).add_months(1), Date::new(2020, 1,31));
428        assert_eq!(Date::new(2020, 2,29).add_months(-2), Date::new(2019, 12,29));
429    }
430
431    #[test]
432    fn test_is_month_last_day() {
433        assert!(Date::new(2019, 12,31).is_month_last_day());
434        assert!(!Date::new(2019, 12,30).is_month_last_day());
435        assert!(Date::new(2019, 2,28).is_month_last_day());
436        assert!(!Date::new(2020, 2,28).is_month_last_day());
437        assert!(Date::new(2020, 2,29).is_month_last_day());
438    }
439
440    #[test]
441    fn test_month_last_day() {
442        assert_eq!(Date::new(2019, 2,2).month_last_day(), Date::new(2019, 2,28));
443        assert_eq!(Date::new(2020, 2,2).month_last_day(), Date::new(2020, 2,29));
444    }
445
446    #[test]
447    fn test_to_seconds_from_unix_epoch() {
448        assert_eq!(Date::new(1970, 1,1).to_seconds_from_unix_epoch(false), 0);
449        assert_eq!(Date::new(1970, 1,1).to_seconds_from_unix_epoch(true), SECONDS_IN_DAY);
450    }
451
452    #[test]
453    fn test_to_days() {
454        assert_eq!(Date::new(1, 1,1).to_days(), 1);
455        assert_eq!(Date::new(1, 12,31).to_days(), 365);
456        assert_eq!(Date::new(4, 2,29).to_days(), 1155);
457        assert_eq!(Date::new(5, 1,1).to_days(), 1462);
458    }
459
460    #[test]
461    fn test_from_days() {
462        assert_eq!(Date::from_days(1), Date::new(1, 1,1));
463        assert_eq!(Date::from_days(365), Date::new(1, 12,31));
464        assert_eq!(Date::from_days(1155), Date::new(4, 2,29));
465        assert_eq!(Date::from_days(1462), Date::new(5, 1,1));
466    }
467
468    #[test]
469    fn test_is_monday() {
470        assert_eq!(Date::new(2021, 8,2).is_monday(), true);
471        assert_eq!(Date::new(2021, 8,3).is_monday(), false);
472    }
473
474    #[test]
475    fn test_is_tuesday() {
476        assert_eq!(Date::new(2021, 8,3).is_tuesday(), true);
477        assert_eq!(Date::new(2021, 8,4).is_tuesday(), false);
478    }
479
480    #[test]
481    fn test_is_wednesday() {
482        assert_eq!(Date::new(2021, 8,4).is_wednesday(), true);
483        assert_eq!(Date::new(2021, 8,5).is_wednesday(), false);
484    }
485
486    #[test]
487    fn test_is_thursday() {
488        assert_eq!(Date::new(2021, 8,5).is_thursday(), true);
489        assert_eq!(Date::new(2021, 8,6).is_thursday(), false);
490    }
491
492    #[test]
493    fn test_is_friday() {
494        assert_eq!(Date::new(2021, 8,6).is_friday(), true);
495        assert_eq!(Date::new(2021, 8,7).is_friday(), false);
496    }
497
498    #[test]
499    fn test_is_saturday() {
500        assert_eq!(Date::new(2021, 8,7).is_saturday(), true);
501        assert_eq!(Date::new(2021, 8,8).is_saturday(), false);
502    }
503
504    #[test]
505    fn test_is_sunday() {
506        assert_eq!(Date::new(2021, 8,8).is_sunday(), true);
507        assert_eq!(Date::new(2021, 8,9).is_sunday(), false);
508    }
509
510    #[test]
511    fn test_add_sub_days(){
512        let a = Date::new(2020,12,31);
513        assert_eq!(a.add_days(1).sub_days(1), a);
514    }
515
516    #[test]
517    fn test_sub_months() {
518        assert_eq!(Date::new(2020, 2,29).sub_months(2), Date::new(2019, 12,29));
519        assert_eq!(Date::new(2020, 4,30).sub_months(2), Date::new(2020, 2,29));
520        assert_eq!(Date::new(2022, 2,28).sub_months(26), Date::new(2019, 12,28));
521        assert_eq!(Date::new(2020, 1,31).sub_months(1), Date::new(2019, 12,31));
522    }
523
524    #[test]
525    fn test_normalize() {
526        assert_eq!(Date::new(2020, 49,32).normalize(), Date::new(2024, 2, 1));
527        assert_eq!(Date::new(2020, 49,60).normalize(), Date::new(2024, 2, 29));
528        assert_eq!(Date::new(2020, 49,61).normalize(), Date::new(2024, 3, 1));
529    }
530
531    #[test]
532    fn test_date_validation() {
533        assert!(Date::new(2020, 2, 29).valid()); // Leap year
534        assert!(Date::new(2021, 2, 28).valid()); // Non-leap year
535        assert!(Date::new(2020, 12, 31).valid());
536        assert!(Date::new(2020, 1, 1).valid());
537        
538        assert!(!Date::new(2021, 2, 29).valid()); // Feb 29 in non-leap year
539        assert!(!Date::new(2020, 2, 30).valid()); // Feb 30
540        assert!(!Date::new(2020, 4, 31).valid()); // Apr 31
541        assert!(!Date::new(2020, 6, 31).valid()); // Jun 31
542        assert!(!Date::new(2020, 9, 31).valid()); // Sep 31
543        assert!(!Date::new(2020, 11, 31).valid()); // Nov 31
544        assert!(!Date::new(2020, 0, 1).valid()); // Month 0
545        assert!(!Date::new(2020, 13, 1).valid()); // Month 13
546        assert!(!Date::new(2020, 1, 0).valid()); // Day 0
547        assert!(!Date::new(2020, 1, 32).valid()); // Day 32
548    }
549
550    #[test]
551    fn test_date_from_str_invalid() {
552        assert!("invalid".parse::<Date>().is_err());
553        assert!("2020".parse::<Date>().is_err());
554        assert!("2020-13".parse::<Date>().is_err());
555        assert!("not-a-date".parse::<Date>().is_err());
556        assert!("2020/01/01".parse::<Date>().is_err());
557    }
558
559    #[test]
560    fn test_edge_cases() {
561        let date = Date::new(1, 1, 1);
562        assert!(date.valid());
563        assert_eq!(date.to_days(), 1);
564        
565        let date = Date::new(9999, 12, 31);
566        assert!(date.valid());
567        
568        assert!(Date::new(2000, 1, 1).leap_year());
569        assert!(!Date::new(1900, 1, 1).leap_year());
570        assert!(Date::new(2004, 1, 1).leap_year());
571        assert!(!Date::new(2001, 1, 1).leap_year());
572    }
573
574    #[test]
575    fn test_quarter_calculation() {
576        assert_eq!(Date::new(2020, 1, 1).quarter(), 1);
577        assert_eq!(Date::new(2020, 3, 31).quarter(), 1);
578        assert_eq!(Date::new(2020, 4, 1).quarter(), 2);
579        assert_eq!(Date::new(2020, 6, 30).quarter(), 2);
580        assert_eq!(Date::new(2020, 7, 1).quarter(), 3);
581        assert_eq!(Date::new(2020, 9, 30).quarter(), 3);
582        assert_eq!(Date::new(2020, 10, 1).quarter(), 4);
583        assert_eq!(Date::new(2020, 12, 31).quarter(), 4);
584    }
585
586    #[test]
587    fn test_weekend_weekday() {
588        assert!(Date::new(2021, 8, 7).is_weekend());
589        assert!(Date::new(2021, 8, 8).is_weekend());
590        assert!(!Date::new(2021, 8, 9).is_weekend());
591        assert!(Date::new(2021, 8, 9).is_week_day());
592        assert!(Date::new(2021, 8, 10).is_week_day());
593        assert!(!Date::new(2021, 8, 7).is_week_day());
594        assert!(!Date::new(2021, 8, 8).is_week_day());
595    }
596
597    #[test]
598    fn test_days_to_next_year() {
599        assert_eq!(Date::new(2020, 1, 1).days_to_next_year(), 365);
600        assert_eq!(Date::new(2021, 1, 1).days_to_next_year(), 364);
601        assert_eq!(Date::new(2020, 12, 31).days_to_next_year(), 0);
602        assert_eq!(Date::new(2020, 6, 15).days_to_next_year(), 199);
603    }
604
605    #[test]
606    fn test_year_day() {
607        assert_eq!(Date::new(2020, 1, 1).year_day(), 1);
608        assert_eq!(Date::new(2020, 1, 31).year_day(), 31);
609        assert_eq!(Date::new(2020, 2, 1).year_day(), 32);
610        assert_eq!(Date::new(2020, 2, 29).year_day(), 60);
611        assert_eq!(Date::new(2021, 2, 28).year_day(), 59);
612        assert_eq!(Date::new(2020, 12, 31).year_day(), 366);
613        assert_eq!(Date::new(2021, 12, 31).year_day(), 365);
614    }
615}