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