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