Skip to main content

deep_time/alloc_parse/
types.rs

1use crate::{Dt, Lang};
2use alloc::string::String;
3use alloc::vec::Vec;
4
5/// Used by [`ParseCfg`] in
6/// [`Dt::from_str_parse`](../struct.Dt.html#method.from_str_parse). Controls
7/// how ambiguous dates (e.g. `01/02/03`) are parsed.
8///
9/// The default `Smart` variant applies a practical heuristic that prefers
10/// year-first for compact formats and uses numeric plausibility checks
11/// for other cases. The other variants force a specific ordering.
12#[derive(Clone, Copy, Debug, PartialEq, Default)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14#[cfg_attr(feature = "tsify", derive(tsify::Tsify))]
15pub enum Order {
16    /// Heuristic for **mixed data**. Uses the following rules, in this order:
17    ///
18    /// 1. **Pure-numeric compact formats** (≥ 6 digits with no separators,
19    ///    e.g. `240314153045`, `20240315`, `YYMMDDHHMMSS`):
20    ///    treated as **Year-first** (`%Y%m%d` / `%y%m%d`).
21    ///    These are overwhelmingly used in logs, filenames, databases, APIs,
22    ///    configs, and JSON for sortability.
23    ///
24    /// 2. **Delimited formats that start with a plausible 4-digit year**
25    ///    (1900–2100): treated as **Year-first**.
26    ///
27    /// 3. **Numeric plausibility check** (strongest universal signal):
28    ///    - First number is 13–31 → **Day-first** (international/European style).
29    ///    - First number is 1–12 **and** second number is 13–31 → **Month-first**
30    ///      (US style).
31    ///
32    /// 4. **Strong ISO 8601 / timestamp markers** (`T` connector, `Z`, numeric
33    ///    offsets, or IANA timezone names) → **Year-first**.
34    ///
35    /// 5. **Fallback**:
36    ///    - With the `locale` feature enabled: respects the system locale
37    ///      preference (Day-first in most of the world).
38    ///    - Without the `locale` feature: **Day-first** (global majority).
39    ///
40    /// The `/` separator is deliberately ignored in the plausibility step
41    /// because it is culturally ambiguous.
42    ///
43    /// Once the preferred ordering is determined, the parser tries that order
44    /// first, then the other two (e.g. Day → Month → Year), before any
45    /// year-first unambiguous fallback.
46    #[default]
47    Smart,
48    /// Prefer **Year-first** (YYYY/MM/DD or YY/MM/DD), then Day, then Month.
49    Year,
50    /// Prefer **Day-first** (DD/MM/YYYY), then Month, then Year.
51    Day,
52    /// Prefer **Month-first** (MM/DD/YYYY), then Day, then Year.
53    Month,
54}
55
56#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
57#[cfg_attr(feature = "tsify", derive(tsify::Tsify))]
58#[derive(Clone, Copy, Debug, Default, PartialEq)]
59/// Used by [`ParseCfg`] in
60/// [`Dt::from_str_parse`](../struct.Dt.html#method.from_str_parse). Controls how purely
61/// numeric dates are parsed.
62pub enum Mode {
63    /// **Default mode** — Smart heuristic:
64    /// - 5/7-digit pure-numeric inside `LEGACY_ORDINAL_YEAR_RANGE` → treated as business
65    ///   ordinal (YYYYDDD / YYDDD)
66    /// - Outside that range or invalid ordinal → treated as MJD or JD
67    #[default]
68    Auto,
69    /// When combined with a provided Vec of formats in parse no other formats are tried.
70    Explicit,
71    /// It's some sort of unix timestamp
72    UnixTimestamp,
73    /// Business/legacy-only mode:
74    /// Only accepts ordinal dates (YYYYDDD / YYDDD). No astronomy (JD/MJD) support.
75    /// Strict and predictable for ERP/mainframe data.
76    Legacy,
77    /// Scientific / astronomy-first mode:
78    /// Prioritizes MJD (5-digit) and JD (7-digit). Ordinals are only fallback.
79    /// Use this when parsing data from astronomy tools or large numeric epochs.
80    Scientific,
81}
82
83/// Configuration options for
84/// [`Dt::from_str_parse`](../struct.Dt.html#method.from_str_parse).
85///
86/// Controls language, ambiguous date order, numeric parsing mode,
87/// explicit `strptime` formats, relative-date support, and reference time.
88///
89/// These settings will not persist between parse calls and have to be used
90/// as an arg every time you want them.
91#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
92#[cfg_attr(feature = "tsify", derive(tsify::Tsify))]
93#[derive(Clone, Debug, PartialEq)]
94pub struct ParseCfg {
95    /// Explicit list of formats to try **in the exact order given**.
96    ///
97    /// If this is provided and the vec is non-empty and the mode is Explicit
98    /// then only these formats are tried and `mode` and `order` are ignored.
99    ///
100    /// If the mode is not Explicit then after trying the formats in parse the
101    /// rest of the parser will continue as normal, using `mode` and `order`.
102    ///
103    /// Partial date formats are allowed: missing month/day default to `1`
104    /// (so `"%Y"` on `"2024"` yields `2024-01-01`, and `"%Y-%m"` on
105    /// `"2024-03"` yields `2024-03-01`).
106    ///
107    /// Example:
108    /// ```js
109    /// parse: ["%Y-%m-%d", "%d/%m/%Y", "%m/%d/%Y", "%d.%m.%Y", "%Y"]
110    /// ```
111    #[cfg_attr(feature = "serde", serde(default))]
112    pub parse: Option<Vec<String>>,
113
114    /// Controls which preset format sets are used (astronomy/scientific formats,
115    /// legacy business rules, etc.).
116    #[cfg_attr(feature = "serde", serde(default))]
117    pub mode: Mode,
118
119    /// Controls ambiguous numeric dates.
120    #[cfg_attr(feature = "serde", serde(default))]
121    pub order: Order,
122
123    /// Sets language to use for a particular parse call.
124    #[cfg_attr(feature = "serde", serde(default))]
125    pub lang: Lang,
126
127    /// Whether to lowercase the input:
128    /// ONLY set to `false` if the &str is already lowercase.
129    #[cfg_attr(feature = "serde", serde(default = "default_true"))]
130    pub to_lower: bool,
131
132    /// Whether to parse relative dates as well as normal dates.
133    #[cfg_attr(feature = "serde", serde(default = "default_true"))]
134    pub relative: bool,
135
136    /// **Reference ("current") time** used for relative expressions:
137    /// - "tomorrow", "next Friday", "in 3 days", "next week"
138    /// - If `Some`, this `Dt` is used as "now" (overrides everything).
139    /// - If `None` + `std` feature enabled: automatically uses real system time.
140    /// - If `None` + no `std`: parsing relative dates will fail with a clear error.
141    #[cfg_attr(feature = "serde", serde(default))]
142    pub ref_time: Option<Dt>,
143}
144
145#[cfg(feature = "serde")]
146fn default_true() -> bool {
147    true
148}
149
150impl ParseCfg {
151    /// Default configuration (all smart defaults).
152    ///
153    /// Pass a reference to this when you want the standard parsing behavior:
154    ///
155    /// ```rust
156    /// use deep_time::{Dt, ParseCfg};
157    ///
158    /// let dt = Dt::from_str_parse("2024-03-15 12:00", &ParseCfg::DEFAULT).unwrap();
159    /// ```
160    pub const DEFAULT: Self = Self {
161        parse: None,
162        mode: Mode::Auto,
163        order: Order::Smart,
164        lang: Lang::En,
165        to_lower: true,
166        relative: true,
167        ref_time: None,
168    };
169}
170
171impl Default for ParseCfg {
172    fn default() -> ParseCfg {
173        Self::DEFAULT
174    }
175}
176
177#[derive(Debug, Clone, Copy, PartialEq, Eq)]
178pub(crate) enum OrderFirst {
179    /// Year-Month-Day ordering (ISO 8601 style, `YYYY-MM-DD`, `20240315`, etc.)
180    Year,
181    /// Month-Day-Year ordering (US / some English locales, `MM/DD/YYYY`)
182    Month,
183    /// Day-Month-Year ordering (most of the world, `DD/MM/YYYY`, `DD.MM.YYYY`)
184    Day,
185}
186
187#[derive(Clone)]
188pub(crate) struct AmBuilder {
189    pub pieces: Vec<&'static str>,
190    pub seen_year: bool,
191    pub seen_month: bool,
192    pub seen_day: bool,
193}
194
195#[inline]
196pub(crate) fn append_to_all(builders: &mut Vec<AmBuilder>, s: &'static str) {
197    for b in builders {
198        b.pieces.push(s);
199    }
200}