1#![recursion_limit = "1024"]
223#[macro_use]
224extern crate pidgin;
225#[macro_use]
226extern crate lazy_static;
227extern crate chrono;
228extern crate serde_json;
229use chrono::naive::{NaiveDate, NaiveDateTime};
230use chrono::{Datelike, Duration, Local, Timelike, Weekday};
231use pidgin::{Grammar, Match, Matcher};
232use regex::Regex;
233
234lazy_static! {
235 #[doc(hidden)]
238 pub static ref GRAMMAR: Grammar = grammar!{
239 (?ibBw)
240
241 TOP -> r(r"\A") <time_expression> r(r"\z")
242
243 time_expression => <universal> | <particular>
247
248 particular => <one_time> | <two_times>
249
250 one_time => <moment_or_period>
251
252 two_times -> ("from")? <moment_or_period> <to> <moment_or_period> | <since_time>
253
254 since_time -> <since> <clusivity>? <moment_or_period>
255
256 clusivity -> ("the") <terminus> ("of")
257
258 terminus => <beginning> | <end>
259
260 to => <up_to> | <through>
261
262 moment_or_period => <moment> | <period>
263
264 period => <named_period> | <specific_period>
265
266 specific_period => <modified_period> | <month_and_year> | <year> | <relative_period>
267
268 modified_period -> <modifier>? <modifiable_period>
269
270 modifiable_period => [["week", "month", "year", "pay period", "payperiod", "pp", "weekend"]] | <a_month> | <a_day>
271
272 month_and_year -> <a_month> <year>
273
274 year => <short_year> | ("-")? <n_year>
275 year -> <suffix_year> <year_suffix>
276
277 year_suffix => <ce> | <bce>
278
279 relative_period -> <count> <displacement> <from_now_or_ago>
280
281 count => r(r"[1-9][0-9]*") | <a_count>
282
283 named_period => <a_day> | <a_month>
284
285 moment -> <adjustment>? <point_in_time>
286
287 adjustment -> <amount> <direction> amount -> <count> <unit>
290
291 point_in_time -> <at_time_on>? <some_day> <at_time>? | <specific_time> | <time>
292
293 at_time_on -> ("at")? <time> ("on")?
294
295 some_day => <specific_day> | <relative_day>
296
297 specific_day => <adverb> | <date_with_year>
298
299 date_with_year => <n_date> | <a_date>
300
301 n_date -> <year> r("[./-]") <n_month> r("[./-]") <n_day>
302 n_date -> <year> r("[./-]") <n_day> r("[./-]") <n_month>
303 n_date -> <n_month> r("[./-]") <n_day> r("[./-]") <year>
304 n_date -> <n_day> r("[./-]") <n_month> r("[./-]") <year>
305
306 a_date -> <day_prefix>? <a_month> <o_n_day> (",") <year>
307 a_date -> <day_prefix>? <n_day> <a_month> <year>
308 a_date -> <day_prefix>? ("the") <o_day> ("of") <a_month> <year>
309
310 day_prefix => <a_day> (",")?
311
312 relative_day => <a_day> | <a_day_in_month>
313
314 at_time -> ("at") <time>
315
316 specific_time => <first_time> | <last_time> | <precise_time>
317
318 precise_time -> <n_date> <hour_24>
319
320 time -> <hour_12> <am_pm>? | <hour_24> | <named_time>
321
322 hour_12 => <h12>
323 hour_12 => <h12> (":") <minute>
324 hour_12 => <h12> (":") <minute> (":") <second>
325
326 hour_24 => <h24>
327 hour_24 => <h24> (":") <minute>
328 hour_24 => <h24> (":") <minute> (":") <second>
329
330 a_day_in_month => <ordinal_day> | <day_and_month>
331
332 ordinal_day -> <day_prefix>? ("the") <o_day> o_day => <n_ordinal> | <a_ordinal> | <roman>
335
336 day_and_month -> <n_month> r("[./-]") <n_day> day_and_month -> <a_month> ("the")? <o_n_day> day_and_month -> ("the") <o_day> ("of") <a_month> o_n_day => <n_day> | <o_day>
341
342 a_count => [["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"]]
347 adverb => [["now", "today", "tomorrow", "yesterday"]]
348 am_pm => (?-ib) [["am", "AM", "pm", "PM", "a.m.", "A.M.", "p.m.", "P.M."]]
349 bce => (?-ib) [["bce", "b.c.e.", "bc", "b.c.", "BCE", "B.C.E.", "BC", "B.C."]]
350 beginning => [["beginning", "start"]]
351 ce => (?-ib) [["ce", "c.e.", "ad", "a.d.", "CE", "C.E.", "AD", "A.D."]]
352 direction -> [["before", "after", "around", "before and after"]]
353 displacement => [["week", "day", "hour", "minute", "second"]] ("s")? end => ("end")
355 from_now_or_ago => [["from now", "ago"]]
356 h12 => (?-B) [(1..=12).into_iter().collect::<Vec<_>>()]
357 h24 => [(1..=24).into_iter().flat_map(|i| vec![format!("{}", i), format!("{:02}", i)]).collect::<Vec<_>>()]
358 minute => (?-B) [ (0..60).into_iter().map(|i| format!("{:02}", i)).collect::<Vec<_>>() ]
359 modifier => [["the", "this", "last", "next"]]
360 named_time => [["noon", "midnight"]]
361 n_year => r(r"\b(?:[1-9][0-9]{0,4}|0)\b")
362 roman => [["nones", "ides", "kalends"]]
363 since => [["since", "after"]]
364 unit => [["week", "day", "hour", "minute", "second"]] ("s")?
365 universal => [["always", "ever", "all time", "forever", "from beginning to end", "from the beginning to the end"]]
366 up_to => [["to", "until", "up to", "till"]]
367 second => (?-B) [ (0..60).into_iter().map(|i| format!("{:02}", i)).collect::<Vec<_>>() ]
368 suffix_year => r(r"\b[1-9][0-9]{0,4}")
369 through => [["up through", "through", "thru"]] | r("-+")
370
371 a_day => (?-i) [["M", "T", "W", "R", "F", "S", "U"]]
372 a_day => [
373 "Sunday Monday Tuesday Wednesday Thursday Friday Saturday Tues Weds Thurs Tues. Weds. Thurs."
374 .split(" ")
375 .into_iter()
376 .flat_map(|w| vec![
377 w.to_string(),
378 w[0..2].to_string(),
379 w[0..3].to_string(),
380 format!("{}.", w[0..2].to_string()),
381 format!("{}.", w[0..3].to_string()),
382 ])
383 .collect::<Vec<_>>()
384 ]
385 a_month => [
386 "January February March April May June July August September October November December"
387 .split(" ")
388 .into_iter()
389 .flat_map(|w| vec![w.to_string(), w[0..3].to_string()])
390 .collect::<Vec<_>>()
391 ]
392 a_ordinal => [[
393 "first",
394 "second",
395 "third",
396 "fourth",
397 "fifth",
398 "sixth",
399 "seventh",
400 "eighth",
401 "ninth",
402 "tenth",
403 "eleventh",
404 "twelfth",
405 "thirteenth",
406 "fourteenth",
407 "fifteenth",
408 "sixteenth",
409 "seventeenth",
410 "eighteenth",
411 "nineteenth",
412 "twentieth",
413 "twenty-first",
414 "twenty-second",
415 "twenty-third",
416 "twenty-fourth",
417 "twenty-fifth",
418 "twenty-sixth",
419 "twenty-seventh",
420 "twenty-eighth",
421 "twenty-ninth",
422 "thirtieth",
423 "thirty-first"
424 ]]
425 first_time => [[
426 "the beginning",
427 "the beginning of time",
428 "the first moment",
429 "the start",
430 "the very start",
431 "the first instant",
432 "the dawn of time",
433 "the big bang",
434 "the birth of the universe",
435 ]]
436 last_time => [[
437 "the end",
438 "the end of time",
439 "the very end",
440 "the last moment",
441 "eternity",
442 "infinity",
443 "doomsday",
444 "the crack of doom",
445 "armageddon",
446 "ragnarok",
447 "the big crunch",
448 "the heat death of the universe",
449 "doom",
450 "death",
451 "perdition",
452 "the last hurrah",
453 "ever after",
454 "the last syllable of recorded time",
455 ]]
456 n_day => [
457 (1..=31)
458 .into_iter()
459 .flat_map(|i| vec![i.to_string(), format!("{:02}", i)])
460 .collect::<Vec<_>>()
461 ]
462 n_month => [
463 (1..=12)
464 .into_iter()
465 .flat_map(|i| vec![format!("{:02}", i), format!("{}", i)])
466 .collect::<Vec<_>>()
467 ]
468 n_ordinal => [[
469 "1st",
470 "2nd",
471 "3rd",
472 "4th",
473 "5th",
474 "6th",
475 "7th",
476 "8th",
477 "9th",
478 "10th",
479 "11th",
480 "12th",
481 "13th",
482 "14th",
483 "15th",
484 "16th",
485 "17th",
486 "18th",
487 "19th",
488 "20th",
489 "21st",
490 "22nd",
491 "23rd",
492 "24th",
493 "25th",
494 "26th",
495 "27th",
496 "28th",
497 "29th",
498 "30th",
499 "31st",
500 ]]
501 short_year => [
502 (0..=99)
503 .into_iter()
504 .flat_map(|i| vec![format!("'{:02}", i), format!("{:02}", i)])
505 .collect::<Vec<_>>()
506 ]
507 };
508}
509lazy_static! {
512 #[doc(hidden)]
513 pub static ref MATCHER: Matcher = GRAMMAR.matcher().unwrap();
514}
515lazy_static! {
516 #[doc(hidden)]
519 pub static ref SMALL_GRAMMAR: Grammar = grammar!{
521 (?ibBw)
522
523 TOP -> r(r"\A") <time_expression> r(r"\z")
524
525 time_expression => <particular>
529
530 particular => <one_time>
531
532 one_time => <moment_or_period>
533
534 moment_or_period => <moment> | <period>
535
536 period => <named_period> | <specific_period>
537
538 specific_period => <modified_period>
539
540 modified_period -> <modifier>? <modifiable_period>
541
542 modifiable_period => [["week", "month", "year", "pay period", "pp"]] | <a_month> | <a_day>
543
544 named_period => <a_day> | <a_month>
545
546 moment -> <point_in_time>
547
548 point_in_time -> <some_day>
549
550 some_day => <specific_day> | <relative_day>
551
552 specific_day => <adverb>
553
554 relative_day => <a_day>
555
556 adverb => [["now", "today", "yesterday"]]
561 modifier => [["the", "this", "last"]]
562
563 a_day => [
564 "Sunday Monday Tuesday Wednesday Thursday Friday Saturday Tues Weds Thurs Tues. Weds. Thurs."
565 .split(" ")
566 .into_iter()
567 .flat_map(|w| vec![
568 w.to_string(),
569 w[0..2].to_string(),
570 w[0..3].to_string(),
571 format!("{}.", w[0..2].to_string()),
572 format!("{}.", w[0..3].to_string()),
573 ])
574 .collect::<Vec<_>>()
575 ]
576 a_month => [
577 "January February March April May June July August September October November December"
578 .split(" ")
579 .into_iter()
580 .flat_map(|w| vec![w.to_string(), w[0..3].to_string()])
581 .collect::<Vec<_>>()
582 ]
583 };
584}
585lazy_static! {
588 #[doc(hidden)]
589 pub static ref SMALL_MATCHER : Matcher = SMALL_GRAMMAR.matcher().unwrap();
590}
591
592pub fn parsable(phrase: &str) -> bool {
603 if cfg!(feature = "small_grammar") {
604 SMALL_MATCHER.rx.is_match(phrase) || MATCHER.rx.is_match(phrase)
605 } else {
606 MATCHER.rx.is_match(phrase)
607 }
608}
609
610pub fn parse(
624 phrase: &str,
625 config: Option<Config>,
626) -> Result<(NaiveDateTime, NaiveDateTime, bool), TimeError> {
627 let parse = if cfg!(feature = "small_grammar") {
628 SMALL_MATCHER
629 .parse(phrase)
630 .or_else(|| MATCHER.parse(phrase))
631 } else {
632 MATCHER.parse(phrase)
633 };
634 if parse.is_none() {
635 return Err(TimeError::Parse(format!(
636 "could not parse \"{}\" as a time expression",
637 phrase
638 )));
639 }
640 let parse = parse.unwrap();
641 if parse.has("universal") {
642 return Ok((first_moment(), last_moment(), false));
643 }
644 let parse = parse.name("particular").unwrap();
645 let config = config.unwrap_or(Config::new());
646 if let Some(moment) = parse.name("one_time") {
647 return match handle_one_time(moment, &config) {
648 Err(e) => Err(e),
649 Ok((d1, d2, b)) => {
650 let (d3, d4) = adjust(d1, d2, moment);
651 if d1 == d3 {
652 Ok((d1, d2, b))
653 } else {
654 Ok((d3, d4, b))
655 }
656 }
657 };
658 }
659 if let Some(two_times) = parse.name("two_times") {
660 let mut inclusive = two_times.has("beginning");
661 let exclusive = !inclusive && two_times.has("end"); if !(inclusive || exclusive) && (two_times.has("time") || two_times.has("precise_time")) {
663 inclusive = true;
665 }
666 if let Some(previous_time) = two_times.name("since_time") {
667 if specific(previous_time) {
668 return match specific_moment(previous_time, &config) {
669 Ok((d1, d2)) => {
670 let t = if inclusive { d1 } else { d2 };
671 let t = if !(inclusive || exclusive) && t > config.now {
673 d1
674 } else {
675 t
676 };
677 if t > config.now {
678 Err(TimeError::Misordered(format!(
679 "the inferred times, {} and {}, are misordered",
680 t, config.now
681 )))
682 } else {
683 Ok((t, config.now.clone(), false))
684 }
685 }
686 Err(s) => Err(s),
687 };
688 }
689 return match relative_moment(previous_time, &config, &config.now, true) {
690 Ok((d1, d2)) => {
691 let t = if inclusive { d1 } else { d2 };
692 let t = if !(inclusive || exclusive) && t > config.now {
693 d1
694 } else {
695 t
696 };
697 if t > config.now {
698 Err(TimeError::Misordered(format!(
699 "the inferred times, {} and {}, are misordered",
700 t, config.now
701 )))
702 } else {
703 Ok((t, config.now.clone(), false))
704 }
705 }
706 Err(s) => Err(s),
707 };
708 }
709 let first = &two_times.children().unwrap()[0];
710 let last = &two_times.children().unwrap()[2];
711 let is_through = two_times.has("through");
712 if specific(first) {
713 if specific(last) {
714 return match specific_moment(first, &config) {
715 Ok((d1, d2)) => {
716 let (d1, _) = adjust(d1, d2, first);
717 match specific_moment(last, &config) {
718 Ok((d2, d3)) => {
719 let (d2, d3) = adjust(d2, d3, last);
720 let d2 = pick_terminus(d2, d3, is_through);
721 if d1 <= d2 {
722 Ok((d1, d2, true))
723 } else {
724 Err(TimeError::Misordered(format!(
725 "{} is after {}",
726 first.as_str(),
727 last.as_str()
728 )))
729 }
730 }
731 Err(s) => Err(s),
732 }
733 }
734 Err(s) => Err(s),
735 };
736 } else {
737 return match specific_moment(first, &config) {
738 Ok((d1, d2)) => {
739 let (d1, _) = adjust(d1, d2, first);
740 match relative_moment(last, &config, &d1, false) {
741 Ok((d2, d3)) => {
742 let (d2, d3) = adjust(d2, d3, last);
743 let d2 = pick_terminus(d2, d3, is_through);
744 Ok((d1, d2, true))
745 }
746 Err(s) => Err(s),
747 }
748 }
749 Err(s) => Err(s),
750 };
751 }
752 } else if specific(last) {
753 return match specific_moment(last, &config) {
754 Ok((d2, d3)) => {
755 let (d2, d3) = adjust(d2, d3, last);
756 let d2 = pick_terminus(d2, d3, is_through);
757 match relative_moment(first, &config, &d2, true) {
758 Ok((d1, d3)) => {
759 let (d1, _) = adjust(d1, d3, first);
760 Ok((d1, d2, true))
761 }
762 Err(s) => Err(s),
763 }
764 }
765 Err(s) => Err(s),
766 };
767 } else {
768 return match relative_moment(first, &config, &config.now, config.default_to_past) {
770 Ok((d1, d2)) => {
771 let (d1, _) = adjust(d1, d2, first);
772 match relative_moment(last, &config, &d1, false) {
774 Ok((d2, d3)) => {
775 let (d2, d3) = adjust(d2, d3, last);
776 let d2 = pick_terminus(d2, d3, is_through);
777 Ok((d1, d2, true))
778 }
779 Err(s) => Err(s),
780 }
781 }
782 Err(s) => Err(s),
783 };
784 }
785 }
786 unreachable!();
787}
788
789#[derive(Debug, Clone)]
792pub struct Config {
793 now: NaiveDateTime,
794 monday_starts_week: bool,
795 period: Period,
796 pay_period_length: u32,
797 pay_period_start: Option<NaiveDate>,
798 default_to_past: bool,
799}
800
801impl Config {
802 pub fn new() -> Config {
804 Config {
805 now: Local::now().naive_local(),
806 monday_starts_week: true,
807 period: Period::Minute,
808 pay_period_length: 7,
809 pay_period_start: None,
810 default_to_past: true,
811 }
812 }
813 pub fn now(&self, n: NaiveDateTime) -> Config {
816 let mut c = self.clone();
817 c.now = n;
818 c
819 }
820 fn period(&self, period: Period) -> Config {
821 let mut c = self.clone();
822 c.period = period;
823 c
824 }
825 pub fn monday_starts_week(&self, monday_starts_week: bool) -> Config {
830 let mut c = self.clone();
831 c.monday_starts_week = monday_starts_week;
832 c
833 }
834 pub fn pay_period_length(&self, pay_period_length: u32) -> Config {
838 let mut c = self.clone();
839 c.pay_period_length = pay_period_length;
840 c
841 }
842 pub fn pay_period_start(&self, pay_period_start: Option<NaiveDate>) -> Config {
847 let mut c = self.clone();
848 c.pay_period_start = pay_period_start;
849 c
850 }
851 pub fn default_to_past(&self, default_to_past: bool) -> Config {
857 let mut c = self.clone();
858 c.default_to_past = default_to_past;
859 c
860 }
861}
862
863#[derive(Debug, Clone)]
867pub enum TimeError {
868 Parse(String),
870 Misordered(String),
873 ImpossibleDate(String),
875 Weekday(String),
878 NoPayPeriod(String),
881}
882
883impl TimeError {
884 pub fn msg(&self) -> &str {
886 match self {
887 TimeError::Parse(s) => s.as_ref(),
888 TimeError::Misordered(s) => s.as_ref(),
889 TimeError::ImpossibleDate(s) => s.as_ref(),
890 TimeError::Weekday(s) => s.as_ref(),
891 TimeError::NoPayPeriod(s) => s.as_ref(),
892 }
893 }
894}
895
896impl std::error::Error for TimeError {}
897
898impl std::fmt::Display for TimeError {
899 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
900 match self {
901 TimeError::Parse(s) => write!(f, "Parse error: {}", s),
902 TimeError::Misordered(s) => write!(f, "Misordered error: {}", s),
903 TimeError::ImpossibleDate(s) => write!(f, "Impossible date: {}", s),
904 TimeError::Weekday(s) => write!(f, "Weekday error: {}", s),
905 TimeError::NoPayPeriod(s) => write!(f, "No Pay Period error: {}", s),
906 }
907 }
908}
909
910fn pick_terminus(d1: NaiveDateTime, d2: NaiveDateTime, through: bool) -> NaiveDateTime {
913 if through {
914 d2
915 } else {
916 d1
917 }
918}
919
920pub fn first_moment() -> NaiveDateTime {
930 NaiveDate::MIN.and_hms_milli_opt(0, 0, 0, 0).unwrap()
931}
932
933pub fn last_moment() -> NaiveDateTime {
943 NaiveDate::MAX.and_hms_milli_opt(23, 59, 59, 999).unwrap()
944}
945
946fn specific(m: &Match) -> bool {
947 m.has("specific_day") || m.has("specific_period") || m.has("specific_time")
948}
949
950fn n_date(date: &Match, config: &Config) -> Result<NaiveDate, TimeError> {
951 let year = year(date, config);
952 let month = n_month(date);
953 let day = n_day(date);
954 match NaiveDate::from_ymd_opt(year, month, day) {
955 None => Err(TimeError::ImpossibleDate(format!(
956 "cannot construct date with year {}, month {}, and day {}",
957 year, month, day
958 ))),
959 Some(d) => Ok(d),
960 }
961}
962
963fn handle_specific_day(
964 m: &Match,
965 config: &Config,
966) -> Result<(NaiveDateTime, NaiveDateTime), TimeError> {
967 let now = config.now.clone();
968 let mut times = m.all_names("time");
969 if times.len() > 1 {
970 return Err(TimeError::Parse(format!(
971 "more than one daytime specified in {}",
972 m.as_str()
973 )));
974 }
975 let time = times.pop();
976 if let Some(adverb) = m.name("adverb") {
977 return match adverb.as_str().chars().nth(0).expect("empty string") {
978 'n' | 'N' => Ok(moment_and_time(config, time)),
980 't' | 'T' => match adverb.as_str().chars().nth(2).expect("impossible string") {
981 'd' | 'D' => Ok(moment_and_time(&config.period(Period::Day), time)),
983 'm' | 'M' => Ok(moment_and_time(
985 &Config::new()
986 .now(now + Duration::days(1))
987 .period(Period::Day),
988 time,
989 )),
990 _ => unreachable!(),
991 },
992 'y' | 'Y' => Ok(moment_and_time(
994 &Config::new()
995 .now(now - Duration::days(1))
996 .period(Period::Day),
997 time,
998 )),
999 _ => unreachable!(),
1000 };
1001 }
1002 if let Some(date) = m.name("date_with_year") {
1003 if let Some(date) = date.name("n_date") {
1004 return match n_date(date, config) {
1005 Err(s) => Err(s),
1006 Ok(d1) => {
1007 let d1 = d1.and_hms_opt(0, 0, 0).unwrap();
1008 Ok(moment_and_time(
1009 &Config::new().now(d1).period(Period::Day),
1010 time,
1011 ))
1012 }
1013 };
1014 }
1015 if let Some(date) = date.name("a_date") {
1016 let year = year(date, config);
1017 let month = a_month(date);
1018 let day = if date.has("n_day") {
1019 n_day(date)
1020 } else {
1021 o_day(date, month)
1022 };
1023 let d_opt = NaiveDate::from_ymd_opt(year, month, day);
1024 return match d_opt {
1025 None => Err(TimeError::ImpossibleDate(format!(
1026 "cannot construct date with year {}, month {}, and day {}",
1027 year, month, day
1028 ))),
1029 Some(d1) => {
1030 if let Some(wd) = date.name("a_day") {
1031 let wd = weekday(wd.as_str());
1032 if wd == d1.weekday() {
1033 let d1 = d1.and_hms_opt(0, 0, 0).unwrap();
1034 Ok(moment_and_time(
1035 &Config::new().now(d1).period(Period::Day),
1036 time,
1037 ))
1038 } else {
1039 Err(TimeError::Weekday(format!(
1040 "the weekday of year {}, month {}, day {} is not {}",
1041 year,
1042 month,
1043 day,
1044 date.name("a_day").unwrap().as_str()
1045 )))
1046 }
1047 } else {
1048 let d1 = d1.and_hms_opt(0, 0, 0).unwrap();
1049 Ok(moment_and_time(
1050 &Config::new().now(d1).period(Period::Day),
1051 time,
1052 ))
1053 }
1054 }
1055 };
1056 }
1057 unreachable!();
1058 }
1059 unimplemented!();
1060}
1061
1062fn handle_specific_period(
1063 moment: &Match,
1064 config: &Config,
1065) -> Result<(NaiveDateTime, NaiveDateTime), TimeError> {
1066 if let Some(moment) = moment.name("relative_period") {
1067 let count = count(moment.name("count").unwrap()) as i64;
1068 let (displacement, period) = match moment
1069 .name("displacement")
1070 .unwrap()
1071 .as_str()
1072 .chars()
1073 .nth(0)
1074 .unwrap()
1075 {
1076 'w' | 'W' => (Duration::weeks(count), Period::Week),
1077 'd' | 'D' => (Duration::days(count), Period::Day),
1078 'h' | 'H' => (Duration::hours(count), Period::Hour),
1079 'm' | 'M' => (Duration::minutes(count), Period::Minute),
1080 's' | 'S' => (Duration::seconds(count), Period::Second),
1081 _ => unreachable!(),
1082 };
1083 let d = match moment
1084 .name("from_now_or_ago")
1085 .unwrap()
1086 .as_str()
1087 .chars()
1088 .nth(0)
1089 .unwrap()
1090 {
1091 'a' | 'A' => config.now - displacement,
1092 'f' | 'F' => config.now + displacement,
1093 _ => unreachable!(),
1094 };
1095 let span = match period {
1096 Period::Week => (d, d + Duration::weeks(1)),
1097 _ => moment_to_period(d, &period, config),
1098 };
1099 return Ok(span);
1100 }
1101 if let Some(moment) = moment.name("month_and_year") {
1102 let y = year(moment, &config);
1103 let m = a_month(moment);
1104 return match NaiveDate::from_ymd_opt(y, m, 1) {
1105 None => unreachable!(),
1106 Some(d1) => {
1107 let d1 = d1.and_hms_opt(0, 0, 0).unwrap();
1108 Ok(moment_and_time(
1109 &Config::new().now(d1).period(Period::Month),
1110 None,
1111 ))
1112 }
1113 };
1114 }
1115 if let Some(moment) = moment.name("modified_period") {
1116 let modifier = PeriodModifier::from_match(moment.name("modifier"));
1117 if let Some(month) = moment.name("a_month") {
1118 let d = config.now.with_month(a_month(month)).unwrap();
1119 let (d, _) = moment_to_period(d, &Period::Month, config);
1120 let d = match modifier {
1121 PeriodModifier::Next => d.with_year(d.year() + 1).unwrap(),
1122 PeriodModifier::Last => d.with_year(d.year() - 1).unwrap(),
1123 PeriodModifier::This => d,
1124 };
1125 return Ok(moment_to_period(d, &Period::Month, config));
1126 }
1127 if let Some(wd) = moment.name("a_day") {
1128 let wd = weekday(wd.as_str());
1129 let offset = config.now.weekday().num_days_from_monday() as i64
1130 - wd.num_days_from_monday() as i64;
1131 let d = config.now.date() - Duration::days(offset);
1132 let d = match modifier {
1133 PeriodModifier::Next => d + Duration::days(7),
1134 PeriodModifier::Last => d - Duration::days(7),
1135 PeriodModifier::This => d,
1136 };
1137 return Ok(moment_to_period(
1138 d.and_hms_opt(0, 0, 0).unwrap(),
1139 &Period::Day,
1140 config,
1141 ));
1142 }
1143 return match ModifiablePeriod::from_match(moment.name("modifiable_period").unwrap()) {
1144 ModifiablePeriod::Week => {
1145 let (d, _) = moment_to_period(config.now, &Period::Week, config);
1146 let d = match modifier {
1147 PeriodModifier::Next => d + Duration::days(7),
1148 PeriodModifier::Last => d - Duration::days(7),
1149 PeriodModifier::This => d,
1150 };
1151 Ok(moment_to_period(d, &Period::Week, config))
1152 }
1153 ModifiablePeriod::Weekend => {
1154 let (_, d2) =
1155 moment_to_period(config.now, &Period::Week, &config.monday_starts_week(true));
1156 let d2 = match modifier {
1157 PeriodModifier::Next => d2 + Duration::days(7),
1158 PeriodModifier::Last => d2 - Duration::days(7),
1159 PeriodModifier::This => d2,
1160 };
1161 let d1 = d2 - Duration::days(2);
1162 Ok((d1, d2))
1163 }
1164 ModifiablePeriod::Month => {
1165 let (d, _) = moment_to_period(config.now, &Period::Month, config);
1166 let d = match modifier {
1167 PeriodModifier::Next => {
1168 if d.month() == 12 {
1169 d.with_year(d.year() + 1).unwrap().with_month(1).unwrap()
1170 } else {
1171 d.with_month(d.month() + 1).unwrap()
1172 }
1173 }
1174 PeriodModifier::Last => {
1175 if d.month() == 1 {
1176 d.with_year(d.year() - 1).unwrap().with_month(12).unwrap()
1177 } else {
1178 d.with_month(d.month() - 1).unwrap()
1179 }
1180 }
1181 PeriodModifier::This => d,
1182 };
1183 Ok(moment_to_period(d, &Period::Month, config))
1184 }
1185 ModifiablePeriod::Year => {
1186 let (d, _) = moment_to_period(config.now, &Period::Year, config);
1187 let d = match modifier {
1188 PeriodModifier::Next => d.with_year(d.year() + 1).unwrap(),
1189 PeriodModifier::Last => d.with_year(d.year() - 1).unwrap(),
1190 PeriodModifier::This => d,
1191 };
1192 Ok(moment_to_period(d, &Period::Year, config))
1193 }
1194 ModifiablePeriod::PayPeriod => {
1195 if config.pay_period_start.is_some() {
1196 let (d, _) = moment_to_period(config.now, &Period::PayPeriod, config);
1197 let d = match modifier {
1198 PeriodModifier::Next => d + Duration::days(config.pay_period_length as i64),
1199 PeriodModifier::Last => d - Duration::days(config.pay_period_length as i64),
1200 PeriodModifier::This => d,
1201 };
1202 Ok(moment_to_period(d, &Period::PayPeriod, config))
1203 } else {
1204 Err(TimeError::NoPayPeriod(String::from(
1205 "no pay period start date provided",
1206 )))
1207 }
1208 }
1209 };
1210 }
1211 if let Some(moment) = moment.name("year") {
1212 let year = year(moment, config);
1213 return Ok(moment_to_period(
1214 first_moment_of_day(year, 1, 1),
1215 &Period::Year,
1216 config,
1217 ));
1218 }
1219 unreachable!()
1220}
1221
1222enum ModifiablePeriod {
1223 Week,
1224 Month,
1225 Year,
1226 PayPeriod,
1227 Weekend,
1228}
1229
1230impl ModifiablePeriod {
1231 fn from_match(m: &Match) -> ModifiablePeriod {
1232 match m.as_str().chars().nth(0).expect("unreachable") {
1233 'w' | 'W' => {
1234 if m.as_str().len() == 4 {
1235 ModifiablePeriod::Week
1236 } else {
1237 ModifiablePeriod::Weekend
1238 }
1239 }
1240 'm' | 'M' => ModifiablePeriod::Month,
1241 'y' | 'Y' => ModifiablePeriod::Year,
1242 'p' | 'P' => ModifiablePeriod::PayPeriod,
1243 _ => unreachable!(),
1244 }
1245 }
1246}
1247
1248enum PeriodModifier {
1249 This,
1250 Next,
1251 Last,
1252}
1253
1254impl PeriodModifier {
1255 fn from_match(m: Option<&Match>) -> PeriodModifier {
1256 if let Some(m) = m {
1257 match m.as_str().chars().nth(0).expect("unreachable") {
1258 't' | 'T' => PeriodModifier::This,
1259 'l' | 'L' => PeriodModifier::Last,
1260 'n' | 'N' => PeriodModifier::Next,
1261 _ => unreachable!(),
1262 }
1263 } else {
1264 PeriodModifier::This
1265 }
1266 }
1267}
1268
1269fn handle_specific_time(
1270 moment: &Match,
1271 config: &Config,
1272) -> Result<(NaiveDateTime, NaiveDateTime), TimeError> {
1273 if let Some(moment) = moment.name("precise_time") {
1274 return match n_date(moment, config) {
1275 Err(s) => Err(s),
1276 Ok(d) => {
1277 let (hour, minute, second, _) = time(moment);
1278 let m = d.and_hms_opt(hour, minute, second).unwrap();
1279 Ok(moment_to_period(m, &Period::Second, config))
1280 }
1281 };
1282 }
1283 return if moment.has("first_time") {
1284 Ok(moment_to_period(first_moment(), &config.period, config))
1285 } else {
1286 Ok((last_moment(), last_moment()))
1287 };
1288}
1289
1290fn handle_one_time(
1291 moment: &Match,
1292 config: &Config,
1293) -> Result<(NaiveDateTime, NaiveDateTime, bool), TimeError> {
1294 let r = if moment.has("specific_day") {
1295 handle_specific_day(moment, config)
1296 } else if let Some(moment) = moment.name("specific_period") {
1297 handle_specific_period(moment, config)
1298 } else if let Some(moment) = moment.name("specific_time") {
1299 handle_specific_time(moment, config)
1300 } else {
1301 relative_moment(moment, config, &config.now, config.default_to_past)
1302 };
1303 match r {
1304 Ok((d1, d2)) => Ok((d1, d2, false)),
1305 Err(e) => Err(e),
1306 }
1307}
1308
1309fn moment_and_time(config: &Config, daytime: Option<&Match>) -> (NaiveDateTime, NaiveDateTime) {
1311 if let Some(daytime) = daytime {
1312 let (hour, minute, second, is_midnight) = time(daytime);
1313 let mut m = config
1314 .now
1315 .with_hour(hour)
1316 .unwrap()
1317 .with_minute(minute)
1318 .unwrap()
1319 .with_second(second)
1320 .unwrap();
1321 if is_midnight {
1322 m = m + Duration::days(1); }
1324 moment_to_period(m, &Period::Second, config)
1325 } else {
1326 moment_to_period(config.now, &config.period, config)
1327 }
1328}
1329
1330fn relative_moment(
1331 m: &Match,
1332 config: &Config,
1333 other_time: &NaiveDateTime,
1334 before: bool, ) -> Result<(NaiveDateTime, NaiveDateTime), TimeError> {
1336 if let Some(a_month_and_a_day) = m.name("a_day_in_month") {
1337 return match month_and_a_day(a_month_and_a_day, config, other_time, before) {
1338 Ok(d) => Ok(moment_and_time(
1339 &config
1340 .now(d.and_hms_opt(0, 0, 0).unwrap())
1341 .period(Period::Day),
1342 m.name("time"),
1343 )),
1344 Err(e) => Err(e),
1345 };
1346 }
1347 if let Some(day) = m.name("a_day") {
1348 let wd = weekday(day.as_str());
1349 let mut delta =
1350 other_time.weekday().num_days_from_sunday() as i64 - wd.num_days_from_sunday() as i64;
1351 if delta <= 0 {
1352 delta += 7;
1353 }
1354 let mut d = other_time.date() - Duration::days(delta);
1355 if !before {
1356 d = d + Duration::days(7);
1357 }
1358 return Ok(moment_and_time(
1359 &config
1360 .now(d.and_hms_opt(0, 0, 0).unwrap())
1361 .period(Period::Day),
1362 m.name("time"),
1363 ));
1364 }
1365 if let Some(t) = m.name("time") {
1366 let (hour, minute, second, is_midnight) = time(t);
1367 let mut t = other_time
1368 .with_hour(hour)
1369 .unwrap()
1370 .with_minute(minute)
1371 .unwrap()
1372 .with_second(second)
1373 .unwrap();
1374 if is_midnight {
1375 t = t + Duration::days(1); }
1377 if before && t > *other_time {
1378 t = t - Duration::days(1);
1379 } else if !before && t < *other_time {
1380 t = t + Duration::days(1);
1381 }
1382 return Ok(moment_to_period(t, &Period::Second, config));
1383 }
1384 if let Some(month) = m.name("a_month") {
1385 let month = a_month(month);
1386 let year = if before {
1387 if month > other_time.month() {
1388 other_time.year() - 1
1389 } else {
1390 other_time.year()
1391 }
1392 } else {
1393 if month < other_time.month() {
1394 other_time.year() + 1
1395 } else {
1396 other_time.year()
1397 }
1398 };
1399 let d = first_moment_of_day(year, month, 1);
1400 let (d1, d2) = moment_to_period(d, &Period::Month, config);
1401 if before && d1 >= *other_time {
1402 return Ok(moment_to_period(
1403 d1.with_year(d1.year() - 1).unwrap(),
1404 &Period::Month,
1405 config,
1406 ));
1407 } else if !before && d2 <= *other_time {
1408 return Ok(moment_to_period(
1409 d1.with_year(d1.year() + 1).unwrap(),
1410 &Period::Month,
1411 config,
1412 ));
1413 }
1414 return Ok((d1, d2));
1415 }
1416 unreachable!()
1417}
1418
1419fn month_and_a_day(
1421 m: &Match,
1422 config: &Config,
1423 other_time: &NaiveDateTime,
1424 before: bool,
1425) -> Result<NaiveDate, TimeError> {
1426 if m.has("ordinal_day") {
1427 let mut year = config.now.year();
1428 let mut month = other_time.month();
1429 let day = o_day(m, month);
1430 let wd = if let Some(a_day) = m.name("a_day") {
1431 Some(weekday(a_day.as_str()))
1432 } else {
1433 None
1434 };
1435 for _ in 0..4 * 7 * 12 {
1437 if let Some(d) = NaiveDate::from_ymd_opt(year, month, day) {
1438 if wd.is_none() || d.weekday() == wd.unwrap() {
1439 return Ok(d);
1440 }
1441 }
1442 if month == 1 {
1443 month = 12;
1444 year -= 1;
1445 } else {
1446 month -= 1;
1447 }
1448 }
1449 return Err(TimeError::ImpossibleDate(format!(
1450 "there is no day {} in the year {}",
1451 m.as_str(),
1452 config.now.year()
1453 )));
1454 }
1455 let (month, day) = if let Some(month) = m.name("n_month") {
1456 let month = n_month(month);
1457 let day = m.name("n_day").unwrap();
1458 (month, n_day(day))
1459 } else {
1460 let month = a_month(m);
1461 let day = if let Some(day) = m.name("n_day") {
1462 n_day(day)
1463 } else {
1464 o_day(m, month)
1465 };
1466 (month, day)
1467 };
1468 let year = if before {
1469 config.now.year()
1470 } else {
1471 if month < other_time.month() {
1472 other_time.year() + 1
1473 } else {
1474 other_time.year()
1475 }
1476 };
1477 match NaiveDate::from_ymd_opt(year, month, day) {
1478 Some(d) => Ok(d),
1479 None => Err(TimeError::ImpossibleDate(format!(
1480 "could not construct date from {} with year {}, month {}, and day {}",
1481 m.as_str(),
1482 year,
1483 month,
1484 day
1485 ))),
1486 }
1487}
1488
1489fn specific_moment(
1490 m: &Match,
1491 config: &Config,
1492) -> Result<(NaiveDateTime, NaiveDateTime), TimeError> {
1493 if m.has("specific_day") {
1494 return handle_specific_day(m, config);
1495 }
1496 if let Some(m) = m.name("specific_period") {
1497 return handle_specific_period(m, config);
1498 }
1499 if let Some(m) = m.name("specific_time") {
1500 return handle_specific_time(m, config);
1501 }
1502 unreachable!()
1503}
1504
1505fn a_month(m: &Match) -> u32 {
1506 match m.name("a_month").unwrap().as_str()[0..3]
1507 .to_lowercase()
1508 .as_ref()
1509 {
1510 "jan" => 1,
1511 "feb" => 2,
1512 "mar" => 3,
1513 "apr" => 4,
1514 "may" => 5,
1515 "jun" => 6,
1516 "jul" => 7,
1517 "aug" => 8,
1518 "sep" => 9,
1519 "oct" => 10,
1520 "nov" => 11,
1521 "dec" => 12,
1522 _ => unreachable!(),
1523 }
1524}
1525
1526fn time(m: &Match) -> (u32, u32, u32, bool) {
1529 if let Some(m) = m.name("named_time") {
1530 return match m.as_str().chars().nth(0).unwrap() {
1531 'n' | 'N' => (12, 0, 0, false),
1532 _ => (0, 0, 0, true),
1533 };
1534 }
1535 let hour = if let Some(hour_24) = m.name("hour_24") {
1536 let hour = s_to_n(hour_24.name("h24").unwrap().as_str());
1537 if hour == 24 {
1538 0
1539 } else {
1540 hour
1541 }
1542 } else if let Some(hour_12) = m.name("hour_12") {
1543 let mut hour = s_to_n(hour_12.name("h12").unwrap().as_str());
1544 hour = if let Some(am_pm) = m.name("am_pm") {
1545 match am_pm.as_str().chars().nth(0).expect("empty string") {
1546 'a' | 'A' => hour,
1547 _ => hour + 12,
1548 }
1549 } else {
1550 hour
1551 };
1552 if hour == 24 {
1553 0
1554 } else {
1555 hour
1556 }
1557 } else {
1558 unreachable!()
1559 };
1560 if let Some(minute) = m.name("minute") {
1561 let minute = s_to_n(minute.as_str());
1562 if let Some(second) = m.name("second") {
1563 let second = s_to_n(second.as_str());
1564 (hour, minute, second, false)
1565 } else {
1566 (hour, minute, 0, false)
1567 }
1568 } else {
1569 (hour, 0, 0, false)
1570 }
1571}
1572
1573fn n_month(m: &Match) -> u32 {
1574 lazy_static! {
1575 static ref MONTH: Regex = Regex::new(r"\A0?(\d{1,2})\z").unwrap();
1576 }
1577 let cap = MONTH.captures(m.name("n_month").unwrap().as_str()).unwrap();
1578 cap[1].parse::<u32>().unwrap()
1579}
1580
1581fn year(m: &Match, config: &Config) -> i32 {
1582 let year = m.name("year").unwrap();
1583 if let Some(sy) = year.name("short_year") {
1584 let y = s_to_n(sy.as_str()) as i32;
1585 let this_year = config.now.year() % 100;
1586 if config.default_to_past {
1587 if this_year < y {
1588 config.now.year() - this_year - 100 + y
1590 } else {
1591 config.now.year() - this_year + y
1593 }
1594 } else {
1595 if this_year > y {
1596 config.now.year() - this_year + 100 + y
1598 } else {
1599 config.now.year() - this_year + y
1601 }
1602 }
1603 } else if let Some(suffix) = year.name("year_suffix") {
1604 let y = s_to_n(year.name("suffix_year").unwrap().as_str()) as i32;
1605 if suffix.has("bce") {
1606 1 - y } else {
1608 y
1609 }
1610 } else {
1611 let y = s_to_n(year.name("n_year").unwrap().as_str()) as i32;
1612 if year.as_str().chars().nth(0).expect("unreachable") == '-' {
1613 -y
1614 } else {
1615 y
1616 }
1617 }
1618}
1619
1620fn s_to_n(s: &str) -> u32 {
1621 lazy_static! {
1622 static ref S_TO_N: Regex = Regex::new(r"\A[\D0]*(\d+)\z").unwrap();
1623 }
1624 S_TO_N.captures(s).unwrap()[1].parse::<u32>().unwrap()
1625}
1626
1627fn n_day(m: &Match) -> u32 {
1628 m.name("n_day").unwrap().as_str().parse::<u32>().unwrap()
1629}
1630
1631fn o_day(m: &Match, month: u32) -> u32 {
1632 let m = m.name("o_day").unwrap();
1633 let s = m.as_str();
1634 if m.has("a_ordinal") {
1635 ordinal(s)
1636 } else if m.has("n_ordinal") {
1637 s[0..s.len() - 2].parse::<u32>().unwrap()
1638 } else {
1639 match s.chars().nth(0).expect("empty string") {
1641 'n' | 'N' => {
1642 match month {
1644 3 | 5 | 7 | 10 => 7, _ => 5,
1646 }
1647 }
1648 'i' | 'I' => {
1649 match month {
1651 3 | 5 | 7 | 10 => 15, _ => 13,
1653 }
1654 }
1655 _ => 1, }
1657 }
1658}
1659
1660fn ordinal(s: &str) -> u32 {
1662 match s.chars().nth(0).expect("empty string") {
1663 'f' | 'F' => {
1664 match s.chars().nth(1).expect("too short") {
1665 'i' | 'I' => {
1666 match s.chars().nth(2).expect("too short") {
1667 'r' | 'R' => 1, _ => {
1669 if s.len() == 5 {
1670 5 } else {
1672 15 }
1674 }
1675 }
1676 }
1677 _ => {
1678 if s.len() == 6 {
1679 4 } else {
1681 14 }
1683 }
1684 }
1685 }
1686 's' | 'S' => {
1687 match s.chars().nth(1).expect("too short") {
1688 'e' | 'E' => {
1689 match s.len() {
1690 6 => 2, 7 => 7, _ => 17, }
1694 }
1695 _ => {
1696 if s.len() == 5 {
1697 6 } else {
1699 16 }
1701 }
1702 }
1703 }
1704 't' | 'T' => {
1705 match s.chars().nth(1).expect("too short") {
1706 'h' | 'H' => {
1707 match s.chars().nth(4).expect("too short") {
1708 'd' | 'D' => 3, _ => {
1710 match s.chars().nth(5).expect("too short") {
1711 'e' | 'E' => 13, 'i' | 'I' => 30, _ => 31, }
1715 }
1716 }
1717 }
1718 'e' | 'E' => 10, _ => {
1720 match s.chars().nth(3).expect("too short") {
1721 'l' | 'L' => 12, _ => {
1723 if s.len() == 9 {
1724 20 } else {
1726 20 + ordinal(&s[7..s.len()]) }
1728 }
1729 }
1730 }
1731 }
1732 }
1733 'e' | 'E' => {
1734 match s.chars().nth(1).expect("too short") {
1735 'i' | 'I' => {
1736 if s.len() == 6 {
1737 8 } else {
1739 18 }
1741 }
1742 _ => 11, }
1744 }
1745 _ => {
1746 if s.len() == 5 {
1747 9 } else {
1749 19 }
1751 }
1752 }
1753}
1754
1755fn moment_to_period(
1757 now: NaiveDateTime,
1758 period: &Period,
1759 config: &Config,
1760) -> (NaiveDateTime, NaiveDateTime) {
1761 match period {
1762 Period::Year => {
1763 let d1 = first_moment_of_day(now.year(), 1, 1);
1764 let d2 = first_moment_of_day(now.year() + 1, 1, 1);
1765 (d1, d2)
1766 }
1767 Period::Month => {
1768 let d1 = first_moment_of_day(now.year(), now.month(), 1);
1769 let d2 = if now.month() == 12 {
1770 first_moment_of_day(now.year() + 1, 1, 1)
1771 } else {
1772 first_moment_of_day(now.year(), now.month() + 1, 1)
1773 };
1774 (d1, d2)
1775 }
1776 Period::Week => {
1777 let offset = if config.monday_starts_week {
1778 now.weekday().num_days_from_monday()
1779 } else {
1780 now.weekday().num_days_from_sunday()
1781 };
1782 let d1 = first_moment_of_day(now.year(), now.month(), now.day())
1783 - Duration::days(offset as i64);
1784 (d1, d1 + Duration::days(7))
1785 }
1786 Period::Day => {
1787 let d1 = first_moment_of_day(now.year(), now.month(), now.day());
1788 (d1, d1 + Duration::days(1))
1789 }
1790 Period::Hour => {
1791 let d1 = precise_moment(now.year(), now.month(), now.day(), now.hour(), 0, 0);
1792 (d1, d1 + Duration::hours(1))
1793 }
1794 Period::Minute => {
1795 let d1 = precise_moment(
1796 now.year(),
1797 now.month(),
1798 now.day(),
1799 now.hour(),
1800 now.minute(),
1801 0,
1802 );
1803 (d1, d1 + Duration::minutes(1))
1804 }
1805 Period::Second => {
1806 let d1 = precise_moment(
1807 now.year(),
1808 now.month(),
1809 now.day(),
1810 now.hour(),
1811 now.minute(),
1812 now.second(),
1813 );
1814 (d1, d1 + Duration::seconds(1))
1815 }
1816 Period::PayPeriod => {
1817 if let Some(pps) = config.pay_period_start {
1818 let offset = now.num_days_from_ce() - pps.num_days_from_ce();
1820 let ppl = config.pay_period_length as i32;
1821 let mut offset = (offset % ppl) as i64;
1822 if offset < 0 {
1823 offset += config.pay_period_length as i64;
1824 }
1825 let d1 = (now.date() - Duration::days(offset))
1826 .and_hms_opt(0, 0, 0)
1827 .unwrap();
1828 (d1, d1 + Duration::days(config.pay_period_length as i64))
1829 } else {
1830 unreachable!()
1831 }
1832 }
1833 }
1834}
1835
1836#[derive(Debug, Clone)]
1837enum Period {
1838 Year,
1839 Month,
1840 Week,
1841 Day,
1842 Hour,
1843 Minute,
1844 Second,
1845 PayPeriod,
1846}
1847
1848fn weekday(s: &str) -> Weekday {
1849 match s.chars().nth(0).expect("empty string") {
1850 'm' | 'M' => Weekday::Mon,
1851 't' | 'T' => {
1852 if s.len() == 1 {
1853 Weekday::Tue
1854 } else {
1855 match s.chars().nth(1).unwrap() {
1856 'u' | 'U' => Weekday::Tue,
1857 'h' | 'H' => Weekday::Thu,
1858 _ => unreachable!(),
1859 }
1860 }
1861 }
1862 'w' | 'W' => Weekday::Wed,
1863 'H' => Weekday::Thu,
1864 'F' | 'f' => Weekday::Fri,
1865 'S' | 's' => {
1866 if s.len() == 1 {
1867 Weekday::Sat
1868 } else {
1869 match s.chars().nth(1).unwrap() {
1870 'a' | 'A' => Weekday::Sat,
1871 'u' | 'U' => Weekday::Sun,
1872 _ => unreachable!(),
1873 }
1874 }
1875 }
1876 'U' => Weekday::Sun,
1877 _ => unreachable!(),
1878 }
1879}
1880
1881fn adjust(d1: NaiveDateTime, d2: NaiveDateTime, m: &Match) -> (NaiveDateTime, NaiveDateTime) {
1883 if let Some(adjustment) = m.name("adjustment") {
1884 let count = count(adjustment.name("count").unwrap()) as i64;
1885 let unit = match adjustment
1886 .name("unit")
1887 .unwrap()
1888 .as_str()
1889 .chars()
1890 .nth(0)
1891 .unwrap()
1892 {
1893 'w' | 'W' => Duration::weeks(count),
1894 'd' | 'D' => Duration::days(count),
1895 'h' | 'H' => Duration::hours(count),
1896 'm' | 'M' => Duration::minutes(count),
1897 _ => Duration::seconds(count),
1898 };
1899 let direction = adjustment.name("direction").unwrap().as_str();
1900 match direction.chars().nth(0).unwrap() {
1901 'b' | 'B' => {
1902 if direction.len() == 6 {
1903 let d = d1 - unit;
1905 (d, d)
1906 } else {
1907 (d1 - unit, d1 + unit)
1909 }
1910 }
1911 _ => match direction.chars().nth(1).unwrap() {
1912 'f' | 'F' => {
1913 let d = d2 + unit;
1914 (d, d)
1915 }
1916 _ => {
1917 let d1 = d1 - Duration::milliseconds(unit.num_milliseconds() / 2);
1918 let d2 = d1 + unit;
1919 (d1, d2)
1920 }
1921 },
1922 }
1923 } else {
1924 (d1, d2)
1925 }
1926}
1927
1928fn count(m: &Match) -> u32 {
1930 let s = m.as_str();
1931 if m.has("a_count") {
1932 match s.chars().nth(0).expect("impossibly short") {
1934 'o' | 'O' => 1,
1935 't' | 'T' => match s.chars().nth(1).expect("impossibly short") {
1936 'w' | 'W' => 2,
1937 'h' | 'H' => 3,
1938 _ => 10,
1939 },
1940 'f' | 'F' => match s.chars().nth(1).expect("impossibly short") {
1941 'o' | 'O' => 4,
1942 _ => 5,
1943 },
1944 's' | 'S' => match s.chars().nth(1).expect("impossibly short") {
1945 'i' | 'I' => 6,
1946 _ => 7,
1947 },
1948 'e' | 'E' => 8,
1949 _ => 9,
1950 }
1951 } else {
1952 s.parse::<u32>().unwrap()
1953 }
1954}
1955
1956fn first_moment_of_day(year: i32, month: u32, day: u32) -> NaiveDateTime {
1957 NaiveDate::from_ymd_opt(year, month, day)
1958 .unwrap()
1959 .and_hms_opt(0, 0, 0)
1960 .unwrap()
1961}
1962
1963fn precise_moment(
1964 year: i32,
1965 month: u32,
1966 day: u32,
1967 hour: u32,
1968 minute: u32,
1969 second: u32,
1970) -> NaiveDateTime {
1971 NaiveDate::from_ymd_opt(year, month, day)
1972 .unwrap()
1973 .and_hms_opt(hour, minute, second)
1974 .unwrap()
1975}