1use std::fmt::{self, Display};
34use std::time::Duration;
35
36use chrono::{Datelike, Local, NaiveDate, NaiveDateTime, NaiveTime, Timelike};
37
38pub mod error;
39pub mod parse;
40
41pub use parse::DateParser;
43pub use parse::RangeParser;
45pub type Weekday = chrono::Weekday;
47pub type Time = chrono::NaiveTime;
49
50pub use error::DateError;
52pub type Result<T> = std::result::Result<T, DateError>;
54
55#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
57pub struct Date(chrono::NaiveDate);
58
59impl Date {
61 pub fn new(year: i32, month: u32, day: u32) -> Result<Self> {
68 Ok(Self(
69 NaiveDate::from_ymd_opt(year, month, day).ok_or(DateError::InvalidDate)?
70 ))
71 }
72
73 #[must_use]
75 pub fn today() -> Self { Self(Local::now().date_naive()) }
76
77 pub fn parse(datespec: &str) -> Result<Self> { DateParser::default().parse(datespec) }
88}
89
90impl Date {
92 pub fn year(&self) -> i32 { self.0.year() }
94
95 pub fn month(&self) -> u32 { self.0.month() }
97
98 pub fn day(&self) -> u32 { self.0.day() }
100
101 pub fn weekday(&self) -> Weekday { self.0.weekday() }
103
104 pub fn weekday_str(&self) -> &'static str {
106 match self.0.weekday() {
107 Weekday::Sun => "Sunday",
108 Weekday::Mon => "Monday",
109 Weekday::Tue => "Tuesday",
110 Weekday::Wed => "Wednesday",
111 Weekday::Thu => "Thursday",
112 Weekday::Fri => "Friday",
113 Weekday::Sat => "Saturday"
114 }
115 }
116}
117
118impl Date {
120 #[must_use]
122 pub fn day_start(&self) -> DateTime {
123 DateTime(self.0.and_hms_opt(0, 0, 0).expect("Midnight exists"))
124 }
125
126 #[must_use]
128 pub fn day_end(&self) -> DateTime {
129 DateTime(self.0.and_hms_opt(23, 59, 59).expect("Midnight exists"))
130 }
131
132 #[must_use]
135 fn find_previous(&self, weekday: Weekday) -> Self {
136 let mut day = self.0.pred_opt().expect("Not at beginning of time");
137 while day.weekday() != weekday {
138 day = day.pred_opt().expect("Not at beginning of time");
139 }
140 Self(day)
141 }
142
143 #[must_use]
146 fn find_next(&self, weekday: Weekday) -> Self {
147 let mut day = self.0.succ_opt().expect("Not at end of time");
148 while day.weekday() != weekday {
149 day = day.succ_opt().expect("Not at end of time");
150 }
151 Self(day)
152 }
153
154 #[must_use]
156 pub fn month_start(&self) -> Date { Date(self.0.with_day(1).expect("Reasonable date range")) }
157
158 fn is_leap_year(year: i32) -> bool {
160 (year % 400 == 0) || ((year % 4 == 0) && (year % 100 != 0))
161 }
162
163 #[must_use]
165 #[rustfmt::skip]
166 pub fn month_end(&self) -> Date {
167 let last_day = match self.0.month() {
168 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
169 4 | 6 | 9 | 11 => 30,
170 2 => if Self::is_leap_year(self.0.year()) { 29 } else { 28 },
171 _ => unreachable!()
172 };
173 Date(self.0.with_day(last_day).expect("End of month should work"))
174 }
175
176 #[must_use]
178 pub fn week_start(&self) -> Date {
179 match self.0.weekday() {
180 Weekday::Sun => *self,
181 _ => self.find_previous(Weekday::Sun)
182 }
183 }
184
185 #[must_use]
187 pub fn week_end(&self) -> Date {
188 match self.0.weekday() {
189 Weekday::Sat => *self,
190 _ => self.find_next(Weekday::Sat)
191 }
192 }
193
194 #[must_use]
196 pub fn year_start(&self) -> Date {
197 Self(self.0
198 .with_month(1).expect("Within reasonable dates")
199 .with_day(1).expect("Within reasonable dates"))
200 }
201
202 #[must_use]
204 pub fn year_end(&self) -> Date {
205 Self(self.0
206 .with_month(12).expect("Within reasonable dates")
207 .with_day(31).expect("Within reasonable dates"))
208 }
209
210 #[must_use]
212 pub fn succ(&self) -> Date { Self(self.0.succ_opt().expect("Not at end of time")) }
213
214 #[must_use]
216 pub fn pred(&self) -> Date { Self(self.0.pred_opt().expect("Not at beginnning of time")) }
217}
218
219impl Default for Date {
220 fn default() -> Date { Self::today() }
222}
223
224impl std::str::FromStr for Date {
225 type Err = DateError;
226
227 #[rustfmt::skip]
235 fn from_str(date: &str) -> std::result::Result<Self, Self::Err> {
236 let Ok(parsed) = NaiveDate::parse_from_str(date, "%Y-%m-%d") else {
237 return Err(DateError::InvalidDate);
238 };
239 Ok(Self(parsed))
240 }
241}
242
243impl Display for Date {
244 #[rustfmt::skip]
246 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247 write!(f, "{}-{:02}-{:02}", self.0.year(), self.0.month(), self.0.day())
248 }
249}
250
251impl From<Date> for String {
252 fn from(date: Date) -> Self { date.to_string() }
254}
255
256#[derive(Debug, PartialEq, Eq)]
258pub struct DateRange {
259 start: Date,
260 end: Date
261}
262
263impl DateRange {
264 pub fn new(start: Date, end: Date) -> Self {
267 Self::new_opt(start, end).unwrap_or(Self { start, end: start })
268 }
269
270 pub fn new_opt(start: Date, end: Date) -> Option<Self> {
272 (start < end).then_some(Self { start, end })
273 }
274
275 pub fn parse<'a, I>(datespec: &mut I) -> Result<Self>
282 where
283 I: Iterator<Item = &'a str>
284 {
285 RangeParser::default().parse(datespec).map(|(dr, _)| dr)
286 }
287}
288
289impl DateRange {
290 pub fn start(&self) -> Date { self.start }
292
293 pub fn end(&self) -> Date { self.end }
295
296 pub fn is_empty(&self) -> bool { self.start >= self.end }
298}
299
300impl From<Date> for DateRange {
301 fn from(date: Date) -> DateRange { Self { start: date, end: date.succ() } }
303}
304
305impl Default for DateRange {
306 fn default() -> Self {
308 let today = Date::today();
309 Self { start: today, end: today.succ() }
310 }
311}
312
313#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
315pub struct DateTime(chrono::NaiveDateTime);
316
317impl DateTime {
318 pub fn new(date: (i32, u32, u32), time: (u32, u32, u32)) -> Result<Self> {
326 let Some(d) = NaiveDate::from_ymd_opt(date.0, date.1, date.2) else {
327 return Err(DateError::InvalidDate);
328 };
329 let Some(t) = NaiveTime::from_hms_opt(time.0, time.1, time.2) else {
330 return Err(DateError::InvalidDate);
331 };
332 Ok(Self(NaiveDateTime::new(d, t)))
333 }
334
335 pub fn now() -> Self { Self(Local::now().naive_local()) }
337
338 pub(crate) fn new_from_date_time(date: Date, time: NaiveTime) -> Self {
340 Self(NaiveDateTime::new(date.0, time))
341 }
342}
343
344impl DateTime {
345 pub fn timestamp(&self) -> i64 { self.0.and_utc().timestamp() }
347
348 pub fn date(&self) -> Date { Date(self.0.date()) }
350
351 pub fn hour(&self) -> u32 { self.0.hour() }
353
354 pub fn minute(&self) -> u32 { self.0.minute() }
356
357 pub fn second_offset(&self) -> u32 { self.0.minute() * 60 + self.0.second() }
359
360 pub fn hhmm(&self) -> String { format!("{:02}:{:02}", self.0.hour(), self.0.minute()) }
362}
363
364impl DateTime {
365 pub fn seconds(seconds: u64) -> Duration { Duration::from_secs(seconds) }
367
368 pub fn minutes(minutes: u64) -> Duration { Duration::from_secs(minutes * 60) }
370
371 pub fn hours(hours: u64) -> Duration { Duration::from_secs(hours * 3600) }
373
374 pub fn days(days: u64) -> Duration { Duration::from_secs(days * 86400) }
376
377 pub fn weeks(weeks: u64) -> Duration { Self::days(weeks * 7) }
379}
380
381impl std::ops::Add<Duration> for DateTime {
382 type Output = Result<DateTime>;
383
384 fn add(self, rhs: Duration) -> Result<Self> {
391 Ok(Self(
392 self.0 + chrono::Duration::from_std(rhs).map_err(|_| DateError::InvalidDate)?
393 ))
394 }
395}
396
397impl std::ops::Sub<Self> for DateTime {
398 type Output = Result<Duration>;
399
400 fn sub(self, rhs: Self) -> Result<Duration> {
407 (self.0 - rhs.0).to_std().map_err(|_| DateError::EntryOrder)
408 }
409}
410
411impl std::ops::Sub<Duration> for DateTime {
412 type Output = Result<DateTime>;
413
414 fn sub(self, rhs: Duration) -> Result<Self> {
421 Ok(Self(
422 self.0 - chrono::Duration::from_std(rhs).map_err(|_| DateError::InvalidDate)?
423 ))
424 }
425}
426
427impl std::str::FromStr for DateTime {
428 type Err = DateError;
429
430 #[rustfmt::skip]
438 fn from_str(datetime: &str) -> Result<Self> {
439 let Ok(parsed) = NaiveDateTime::parse_from_str(datetime, "%Y-%m-%d %H:%M:%S") else {
440 return Err(DateError::InvalidDate);
441 };
442 Ok(Self(parsed))
443 }
444}
445
446impl Display for DateTime {
447 #[rustfmt::skip]
449 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
450 write!(f, "{}-{:02}-{:02} {:02}:{:02}:{:02}",
451 self.0.year(), self.0.month(), self.0.day(),
452 self.0.hour(), self.0.minute(), self.0.second())
453 }
454}
455
456impl From<DateTime> for String {
457 fn from(datetime: DateTime) -> Self { datetime.to_string() }
459}
460
461#[cfg(test)]
462mod tests {
463 use assert2::{assert, let_assert};
464 use rstest::rstest;
465
466 use super::*;
467
468 #[test]
469 fn test_date_new() {
470 let_assert!(Ok(date) = Date::new(2021, 11, 20));
471 assert!(date.to_string() == String::from("2021-11-20"));
472 }
473
474 #[rstest]
475 #[case(2021, 0, 20, "bad month zero")]
476 #[case(2021, 13, 20, "bad month too high")]
477 #[case(2021, 11, 0, "bad day zero")]
478 #[case(2021, 11, 32, "bad day too high")]
479 fn test_date_new_unsuccess(
480 #[case]year: i32,
481 #[case]month: u32,
482 #[case]day: u32,
483 #[case]msg: &str
484 ) {
485 assert!(Err(DateError::InvalidDate) == Date::new(year, month, day), "{msg}");
486 }
487
488 #[test]
489 fn test_date_day_end() {
490 let_assert!(Ok(date) = Date::new(2021, 11, 20));
491 let_assert!(Ok(expected) = DateTime::new((2021, 11, 20), (23, 59, 59)));
492 assert!(date.day_end() == expected);
493 }
494
495 #[test]
496 fn test_date_day_start() {
497 let_assert!(Ok(date) = Date::new(2021, 11, 20));
498 let_assert!(Ok(expected) = DateTime::new((2021, 11, 20), (0, 0, 0)));
499 assert!(date.day_start() == expected);
500 }
501
502 #[test]
503 fn test_month_start() {
504 let_assert!(Ok(date) = Date::new(2022, 11, 20));
505 let_assert!(Ok(expected) = Date::new(2022, 11, 1));
506 assert!(date.month_start() == expected);
507 }
508
509 #[rstest]
510 #[case("jan", 1, 31)]
511 #[case("feb", 2, 28)]
512 #[case("mar", 3, 31)]
513 #[case("apr", 4, 30)]
514 #[case("may", 5, 31)]
515 #[case("jun", 6, 30)]
516 #[case("jul", 7, 31)]
517 #[case("aug", 8, 31)]
518 #[case("sep", 9, 30)]
519 #[case("oct", 10, 31)]
520 #[case("nov", 11, 30)]
521 #[case("dec", 12, 31)]
522 fn test_month_end(#[case]name: &str, #[case]mon: u32, #[case]day: u32) {
523 let_assert!(Ok(date) = Date::new(2022, mon, 20));
524 let_assert!(Ok(expected) = Date::new(2022, mon, day));
525 assert!(date.month_end() == expected, "{name}");
526 }
527
528 #[test]
529 fn test_month_end_leap_year() {
530 let_assert!(Ok(date) = Date::new(2020, 2, 20));
531 let_assert!(Ok(expected) = Date::new(2020, 2, 29));
532 assert!(date.month_end() == expected);
533 }
534
535 #[test]
536 fn test_date_week_start() {
537 let_assert!(Ok(date) = Date::new(2022, 12, 20));
538 let_assert!(Ok(expected) = Date::new(2022, 12, 18));
539 assert!(date.week_start() == expected);
540 }
541
542 #[test]
543 fn test_date_week_start_no_change() {
544 let_assert!(Ok(date) = Date::new(2022, 12, 18));
545 let_assert!(Ok(expected) = Date::new(2022, 12, 18));
546 assert!(date.week_start() == expected);
547 }
548
549 #[test]
550 fn test_date_week_end() {
551 let_assert!(Ok(date) = Date::new(2022, 12, 15));
552 let_assert!(Ok(expected) = Date::new(2022, 12, 17));
553 assert!(date.week_end() == expected);
554 }
555
556 #[test]
557 fn test_date_week_end_no_change() {
558 let_assert!(Ok(date) = Date::new(2022, 12, 17));
559 let_assert!(Ok(expected) = Date::new(2022, 12, 17));
560 assert!(date.week_end() == expected);
561 }
562
563 #[test]
564 fn test_date_year_start() {
565 let_assert!(Ok(date) = Date::new(2022, 12, 20));
566 let_assert!(Ok(expected) = Date::new(2022, 1, 1));
567 assert!(date.year_start() == expected);
568 }
569
570 #[test]
571 fn test_date_year_end() {
572 let_assert!(Ok(date) = Date::new(2022, 12, 20));
573 let_assert!(Ok(expected) = Date::new(2022, 12, 31));
574 assert!(date.year_end() == expected);
575 }
576
577 #[test]
578 fn test_date_succ() {
579 let_assert!(Ok(date) = Date::new(2021, 11, 20));
580 let_assert!(Ok(expected) = Date::new(2021, 11, 21));
581 assert!(date.succ() == expected);
582 }
583
584 #[test]
585 fn test_date_pred() {
586 let_assert!(Ok(date) = Date::new(2021, 11, 20));
587 let_assert!(Ok(expected) = Date::new(2021, 11, 19));
588 assert!(date.pred() == expected);
589 }
590
591 #[test]
592 fn test_date_parse() {
593 let_assert!(Ok(date) = "2021-11-20".parse::<Date>());
594 let_assert!(Ok(expected) = Date::new(2021, 11, 20));
595 assert!(date == expected);
596 }
597
598 #[test]
599 fn test_date_parse_bad() {
600 let date = "fred".parse::<Date>();
601 assert!(&date.is_err());
602 }
603
604 #[test]
607 fn test_date_range_default() {
608 let range = DateRange::default();
609 assert!(range.start() == Date::today());
610 assert!(range.end() == Date::today().succ());
611 }
612
613 #[test]
614 fn test_date_range_new_opt() {
615 let_assert!(Some(range) = DateRange::new_opt(Date::today(), Date::today().succ()));
616 assert!(range.start() == Date::today());
617 assert!(range.end() == Date::today().succ());
618 }
619
620 #[test]
621 fn test_date_range_new_opt_backwards() {
622 let range = DateRange::new_opt(Date::today(), Date::today().pred());
623 assert!(range.is_none());
624 }
625
626 #[test]
627 fn test_date_range_new_opt_empty() {
628 let range = DateRange::new_opt(Date::today(), Date::today());
629 assert!(range.is_none());
630 }
631
632 #[test]
633 fn test_date_range_new() {
634 let range = DateRange::new(Date::today(), Date::today().succ());
635 assert!(range.start() == Date::today());
636 assert!(range.end() == Date::today().succ());
637 assert!(range.is_empty() == false);
638 }
639
640 #[test]
641 fn test_date_range_new_backwards() {
642 let range = DateRange::new(Date::today(), Date::today().pred());
643 assert!(range.start() == Date::today());
644 assert!(range.end() == Date::today());
645 assert!(range.is_empty() == true);
646 }
647
648 #[test]
649 fn test_date_range_new_empty() {
650 let range = DateRange::new(Date::today(), Date::today());
651 assert!(range.start() == Date::today());
652 assert!(range.end() == Date::today());
653 assert!(range.is_empty() == true);
654 }
655
656 #[test]
657 fn test_date_range_from_date() {
658 let_assert!(Ok(date) = Date::new(2022, 12, 1));
659 let range: DateRange = date.into();
660 let expect = DateRange::new(date, date.succ());
661
662 assert!(range == expect);
663 }
664
665 #[test]
668 fn test_datetime_new() {
669 let_assert!(Ok(date) = DateTime::new((2021, 11, 20), (11, 32, 18)));
670 assert!(date.to_string() == String::from("2021-11-20 11:32:18"));
671 }
672
673 #[test]
674 fn test_datetime_new_bad_date() {
675 let date = DateTime::new((2021, 13, 20), (11, 32, 18));
676 assert!(date.is_err());
677 }
678
679 #[test]
680 fn test_datetime_new_bad_time() {
681 let date = DateTime::new((2021, 11, 20), (11, 82, 18));
682 assert!(date.is_err());
683 }
684
685 #[test]
686 fn test_datetime_parse() {
687 let_assert!(Ok(date) = "2021-11-20 11:32:18".parse::<DateTime>());
688 let_assert!(Ok(expected) = DateTime::new((2021, 11, 20), (11, 32, 18)));
689 assert!(date == expected);
690 }
691
692 #[test]
693 fn test_datetime_diff() {
694 let_assert!(Ok(date) = DateTime::new((2021, 11, 20), (11, 32, 18)));
695 let_assert!(Ok(old) = DateTime::new((2021, 11, 18), (12, 00, 00)));
696 let_assert!(Ok(dur) = date - old);
697 assert!(dur == Duration::from_secs(2 * 86400 - 28 * 60 + 18));
698 }
699
700 #[test]
701 fn test_datetime_diff_bad() {
702 let_assert!(Ok(date) = DateTime::new((2021, 11, 18), (12, 00, 00)));
703 let_assert!(Ok(old) = DateTime::new((2021, 11, 20), (11, 32, 18)));
704 let_assert!(Err(_) = date - old);
705 }
706
707 #[test]
708 fn test_datetime_add_time() {
709 let_assert!(Ok(date) = DateTime::new((2021, 11, 18), (12, 00, 00)));
710 let_assert!(Ok(new) = date + DateTime::minutes(10));
711 let_assert!(Ok(expected) = DateTime::new((2021, 11, 18), (12, 10, 00)));
712 assert!(new == expected);
713 }
714
715 #[test]
716 fn test_datetime_add_days() {
717 let_assert!(Ok(date) = DateTime::new((2021, 11, 18), (12, 00, 00)));
718 let_assert!(Ok(new) = date + DateTime::days(3));
719 let_assert!(Ok(expected) = DateTime::new((2021, 11, 21), (12, 00, 00)));
720 assert!(new == expected);
721 }
722
723 #[test]
724 fn test_datetime_hhmm() {
725 let_assert!(Ok(date) = DateTime::new((2021, 11, 18), (8, 5, 13)));
726 assert!(date.hhmm() == String::from("08:05"));
727 }
728
729 #[test]
730 fn test_datetime_parse_bad() {
731 let date = "fred".parse::<DateTime>();
732 assert!(&date.is_err());
733 }
734}