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 { ®ULAR_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 { ®ULAR_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 { ®ULAR_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 { ®ULAR_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 { ®ULAR_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 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 { ®ULAR_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 { ®ULAR_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 #[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()); assert!(Date::new(2021, 2, 28).valid()); assert!(Date::new(2020, 12, 31).valid());
536 assert!(Date::new(2020, 1, 1).valid());
537
538 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()); }
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}