Skip to main content

chrono_node/common/
merge_date_range.rs

1//! upstream `common/refiners/AbstractMergeDateRangeRefiner.ts`.
2//!
3//! Implement [`MergeDateRangePattern`] and wrap it: `Box::new(Merging::new(MergeDateRange(MyPattern)))`.
4
5use crate::calculation::duration::{add_duration, Duration, DurationUnit};
6use crate::common::abstract_refiners::MergingRefiner;
7use crate::core::components::Component;
8use crate::core::context::ParsingContext;
9use crate::core::js_date::JsDate;
10use crate::core::regex::JsRegex;
11use crate::core::result::ParsingResult;
12
13/// upstream `patternBetween()`.
14pub trait MergeDateRangePattern: Send + Sync {
15    fn pattern_between(&self) -> &JsRegex;
16}
17
18pub struct MergeDateRange<P>(pub P);
19
20/// JS `a > b` on dates (false when either is NaN).
21fn gt(a: JsDate, b: JsDate) -> bool {
22    matches!((a.get_time(), b.get_time()), (Some(a), Some(b)) if a > b)
23}
24
25fn plus(date: JsDate, unit: DurationUnit, amount: f64) -> JsDate {
26    add_duration(date, &mut Duration::from_pairs(&[(unit, amount)]))
27}
28
29impl<P: MergeDateRangePattern> MergingRefiner for MergeDateRange<P> {
30    fn should_merge_results(
31        &self,
32        text_between: &str,
33        current: &ParsingResult,
34        next: &ParsingResult,
35        _context: &ParsingContext,
36    ) -> bool {
37        current.end.is_none() && next.end.is_none() && self.0.pattern_between().is_match(text_between)
38    }
39
40    fn merge_results(
41        &self,
42        text_between: &str,
43        current: ParsingResult,
44        next: ParsingResult,
45        _context: &ParsingContext,
46    ) -> ParsingResult {
47        let (mut from, mut to) = (current, next);
48        if !from.start.is_only_weekday_component() && !to.start.is_only_weekday_component() {
49            for key in to.start.get_certain_components() {
50                if !from.start.is_certain(key) {
51                    from.start.imply(key, to.start.get_or_zero(key));
52                }
53            }
54            for key in from.start.get_certain_components() {
55                if !to.start.is_certain(key) {
56                    to.start.imply(key, from.start.get_or_zero(key));
57                }
58            }
59        }
60
61        if gt(from.start.date(), to.start.date()) {
62            let mut from_date = from.start.date();
63            let mut to_date = to.start.date();
64            if to.start.is_only_weekday_component() && gt(plus(to_date, DurationUnit::Day, 7.0), from_date) {
65                to_date = plus(to_date, DurationUnit::Day, 7.0);
66                to.start.imply(Component::Day, to_date.get_date());
67                to.start.imply(Component::Month, to_date.get_month() + 1);
68                to.start.imply(Component::Year, to_date.get_full_year());
69            } else if from.start.is_only_weekday_component() && gt(to_date, plus(from_date, DurationUnit::Day, -7.0)) {
70                from_date = plus(from_date, DurationUnit::Day, -7.0);
71                from.start.imply(Component::Day, from_date.get_date());
72                from.start.imply(Component::Month, from_date.get_month() + 1);
73                from.start.imply(Component::Year, from_date.get_full_year());
74            } else if to.start.is_date_with_unknown_year() && gt(plus(to_date, DurationUnit::Year, 1.0), from_date) {
75                to_date = plus(to_date, DurationUnit::Year, 1.0);
76                to.start.imply(Component::Year, to_date.get_full_year());
77            } else if from.start.is_date_with_unknown_year() && gt(to_date, plus(from_date, DurationUnit::Year, -1.0)) {
78                from_date = plus(from_date, DurationUnit::Year, -1.0);
79                from.start.imply(Component::Year, from_date.get_full_year());
80            } else {
81                std::mem::swap(&mut from, &mut to);
82            }
83        }
84
85        let mut result = from.js_clone();
86        result.start = from.start.clone();
87        result.end = Some(to.start.clone());
88        result.index = from.index.min(to.index);
89        result.text = if from.index < to.index {
90            format!("{}{}{}", from.text, text_between, to.text)
91        } else {
92            format!("{}{}{}", to.text, text_between, from.text)
93        };
94        result
95    }
96}