Skip to main content

deep_time/alloc_parse/
parse_date.rs

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