Skip to main content

deep_time/alloc_parse/
parse_date.rs

1use crate::{
2    ClassifiedDate, DateClassification, Dt, DtErr, DtErrKind, MAX_DATE_STRING_LEN, Mode, Order,
3    OrderFirst, ParseCfg, Scale, an_err, classify_date, default_date_parse_options,
4    generate_ambiguous_day_first_candidates, generate_ambiguous_month_first_candidates,
5    generate_ambiguous_year_first_candidates, generate_unambiguous_candidates,
6    is_week_date_missing_weekday, parse_pure_numeric_unix_timestamp, parse_syslog_no_year,
7    parse_week_date_no_weekday, parse_yyyy_mm, smart_detect_date_order, try_pure_numeric,
8};
9use alloc::borrow::Cow;
10use alloc::string::String;
11
12impl Dt {
13    pub fn from_str_parse(s: &str, opts: &Option<ParseCfg>) -> Result<Dt, DtErr> {
14        let opts: &ParseCfg = opts
15            .as_ref()
16            .unwrap_or_else(|| default_date_parse_options());
17
18        if s.is_empty() {
19            return Err(an_err!(DtErrKind::Incomplete, "empty"));
20        } else if s.len() > MAX_DATE_STRING_LEN {
21            return Err(an_err!(DtErrKind::InvalidInput, "too long: {}", s));
22        }
23
24        let lang = opts.lang;
25        let ref_time = &opts.ref_time;
26
27        let lowered: Cow<str> = if opts.to_lower {
28            Cow::Owned(s.to_lowercase())
29        } else {
30            Cow::Borrowed(s)
31        };
32
33        let classification = match classify_date(&lowered, lang, ref_time) {
34            Ok(ClassifiedDate::Parsed(time_point)) => return Ok(time_point),
35            Ok(ClassifiedDate::Cls(c)) => c,
36            Err(e) => {
37                // std::eprintln!("{}", e);
38                return Err(an_err!(
39                    DtErrKind::InvalidInput,
40                    "{}",
41                    s => e
42                ));
43            }
44        };
45        // let xx = &classification.date;
46        // if xx != trimmed {
47        //     eprintln!("NOT EQUAL: {:?}, {:?}", trimmed, xx);
48        // }
49        // eprintln!("BEFORE & AFTER: {:?}, {:?}", lowered, &classification.date);
50
51        let normalized = &classification.date;
52
53        let (mode, date_order) = if let Some(formats) = &opts.parse {
54            if !formats.is_empty() {
55                for fmt in formats {
56                    if let Ok(value) = Self::from_str(normalized, fmt, true, true, false) {
57                        return Ok(value);
58                    }
59                }
60                // None of the provided formats worked and mode is Explicit
61                if opts.mode == Mode::Explicit {
62                    return Err(an_err!(DtErrKind::InvalidInput, "{}", s));
63                }
64            }
65            (opts.mode, opts.order)
66        } else {
67            (opts.mode, opts.order)
68        };
69
70        // if s == "2460000" {
71        //     std::eprintln!("INPUT CLS: {:?}", classification);
72        // }
73
74        if classification.is_pure_numeric {
75            match mode {
76                Mode::UnixTimestamp => {
77                    if let Some(dt) = parse_pure_numeric_unix_timestamp(
78                        normalized,
79                        classification.num_non_decimal_digits as usize,
80                    ) {
81                        return Ok(dt);
82                    }
83                }
84                _ => {
85                    if let Some(dt) = try_pure_numeric(
86                        normalized,
87                        classification.num_digits,
88                        classification.num_non_decimal_digits,
89                        classification.is_decimal,
90                        mode,
91                    ) {
92                        // std::eprintln!("NUMERIC INPUT SUCCESS: {:?}", s);
93                        return Ok(dt);
94                    }
95                }
96            }
97        }
98        if !classification.has_year
99            && let Some(dt) = parse_syslog_no_year(normalized, lang, ref_time)
100        {
101            return Ok(dt);
102        }
103
104        if is_week_date_missing_weekday(&classification) {
105            // std::eprintln!("IS WEEK DATE MISSING WEEKDAY: {:?}", s);
106            if let Some(dt) = parse_week_date_no_weekday(&classification.date, lang, ref_time) {
107                return Ok(dt);
108            }
109        }
110        if let Some(dt) = try_unambiguous(normalized, &classification) {
111            return Ok(dt);
112        }
113        // std::eprintln!("done trying unambiguous");
114        if let Some(dt) = match date_order {
115            Order::Smart => {
116                let order = smart_detect_date_order(normalized, &classification);
117                let mut result: Option<Dt>;
118
119                match order {
120                    OrderFirst::Day => {
121                        result = try_compatible_formats(
122                            normalized,
123                            generate_ambiguous_day_first_candidates(&classification),
124                        );
125                        // std::eprintln!("done trying day first: {:?}", result);
126
127                        if result.is_none() {
128                            result = try_compatible_formats(
129                                normalized,
130                                generate_ambiguous_month_first_candidates(&classification),
131                            );
132                            // std::eprintln!("done trying month first: {:?}", result);
133                        }
134
135                        if result.is_none() {
136                            result = try_compatible_formats(
137                                normalized,
138                                generate_ambiguous_year_first_candidates(&classification),
139                            );
140                            // std::eprintln!("done trying year first: {:?}", result);
141                        }
142                    }
143                    OrderFirst::Month => {
144                        result = try_compatible_formats(
145                            normalized,
146                            generate_ambiguous_month_first_candidates(&classification),
147                        );
148                        // std::eprintln!("done trying month first: {:?}", result);
149
150                        if result.is_none() {
151                            result = try_compatible_formats(
152                                normalized,
153                                generate_ambiguous_day_first_candidates(&classification),
154                            );
155                            // std::eprintln!("done trying day first: {:?}", result);
156                        }
157
158                        if result.is_none() {
159                            result = try_compatible_formats(
160                                normalized,
161                                generate_ambiguous_year_first_candidates(&classification),
162                            );
163                            // std::eprintln!("done trying year first: {:?}", result);
164                        }
165                    }
166                    OrderFirst::Year => {
167                        result = try_compatible_formats(
168                            normalized,
169                            generate_ambiguous_year_first_candidates(&classification),
170                        );
171                        // std::eprintln!("done trying year first: {:?}", result);
172
173                        if result.is_none() {
174                            result = try_compatible_formats(
175                                normalized,
176                                generate_ambiguous_day_first_candidates(&classification),
177                            );
178                            // std::eprintln!("done trying day first: {:?}", result);
179                        }
180
181                        if result.is_none() {
182                            result = try_compatible_formats(
183                                normalized,
184                                generate_ambiguous_month_first_candidates(&classification),
185                            );
186                            // std::eprintln!("done trying month first: {:?}", result);
187                        }
188                    }
189                }
190
191                result
192            }
193            Order::Year => try_compatible_formats(
194                normalized,
195                generate_ambiguous_year_first_candidates(&classification),
196            ),
197            Order::Day => try_compatible_formats(
198                normalized,
199                generate_ambiguous_day_first_candidates(&classification),
200            ),
201            Order::Month => try_compatible_formats(
202                normalized,
203                generate_ambiguous_month_first_candidates(&classification),
204            ),
205        } {
206            return Ok(dt);
207        }
208        // std::eprintln!("NOW trying numeric timestamp");
209        if classification.is_pure_numeric
210            && mode != Mode::UnixTimestamp
211            && let Some(dt) = parse_pure_numeric_unix_timestamp(
212                normalized,
213                classification.num_non_decimal_digits as usize,
214            )
215        {
216            return Ok(dt);
217        }
218        Err(an_err!(DtErrKind::InvalidInput, "{}", s))
219    }
220
221    /// Same parsing logic as `Dt::from_str`, but returns attoseconds since
222    /// the library epoch: 2000-01-01 12:00:00 UTC (on the UTC scale).
223    ///
224    /// Returns `Some(attos)` on success (negative for pre-2000 dates) or `None`
225    /// on any parse error.
226    #[inline]
227    pub fn str_to_attos(s: &str, opts: &Option<ParseCfg>) -> Option<i128> {
228        Dt::from_str_parse(s, opts).ok().map(|tp| tp.to_attos())
229    }
230
231    /// Same parsing logic as `Dt::from_str`, but returns milliseconds since
232    /// the library epoch: 2000-01-01 12:00:00 UTC (on the UTC scale).
233    ///
234    /// Returns `Some(millis)` on success (negative for pre-2000 dates) or `None`
235    /// on any parse error.
236    #[inline]
237    pub fn str_to_ms(s: &str, opts: &Option<ParseCfg>) -> Option<i128> {
238        Dt::from_str_parse(s, opts).ok().map(|tp| tp.to_ms())
239    }
240
241    /// Same parsing logic as `Dt::from_str`, but returns milliseconds since
242    /// the UNIX epoch: (1970-01-01 00:00:00 UTC).
243    ///
244    /// Returns `Some(millis)` on success (negative for pre-2000 dates) or `None`
245    /// on any parse error.
246    #[inline]
247    pub fn str_to_unix_ms(s: &str, opts: &Option<ParseCfg>) -> Option<i128> {
248        Dt::from_str_parse(s, opts).ok().map(|tp| {
249            tp.to_scale_and_then_diff(Scale::UTC, Dt::UNIX_EPOCH)
250                .to_ms()
251        })
252    }
253}
254
255/// Core zero-allocation helper (updated to match the new `&str` signature).
256///
257/// The `fmt` we get from the iterator is still `'static`, but it coerces automatically
258/// to `&str`, so everything continues to work.
259#[inline]
260pub(crate) fn try_compatible_formats<I>(s: &str, formats: I) -> Option<Dt>
261where
262    I: IntoIterator<Item = String>,
263{
264    formats
265        .into_iter()
266        .find_map(|fmt| Dt::from_str(s, &fmt, true, true, false).ok())
267}
268
269#[inline]
270pub(crate) fn try_unambiguous(s: &str, classification: &DateClassification) -> Option<Dt> {
271    if matches!(classification.bytes_len, 6..=8)
272        && let Some(dt) = parse_yyyy_mm(s.as_bytes())
273    {
274        return Some(dt);
275    }
276    try_compatible_formats(s, generate_unambiguous_candidates(classification))
277}