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