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 the
44 /// corresponding ambiguous candidate formats (Year-first → Day-first →
45 /// Month-first, or the reverse, depending on the detected order) and falls
46 /// back gracefully.
47 #[default]
48 Smart,
49 /// Force **Year-first** only (YYYY/MM/DD or YY/MM/DD)
50 Year,
51 /// Force **Day-first** only (DD/MM/YYYY)
52 Day,
53 /// Force **Month-first** only (MM/DD/YYYY)
54 Month,
55}
56
57#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
58#[cfg_attr(feature = "tsify", derive(tsify::Tsify))]
59#[derive(Clone, Copy, Debug, Default, PartialEq)]
60/// Used by [`ParseCfg`] in
61/// [`Dt::from_str_parse`](../struct.Dt.html#method.from_str_parse). Controls how purely
62/// numeric dates are parsed.
63pub enum Mode {
64 /// **Default mode** — Smart heuristic:
65 /// - 5/7-digit pure-numeric inside `LEGACY_ORDINAL_YEAR_RANGE` → treated as business
66 /// ordinal (YYYYDDD / YYDDD)
67 /// - Outside that range or invalid ordinal → treated as MJD or JD
68 #[default]
69 Auto,
70 /// When combined with a provided Vec of formats in parse no other formats are tried.
71 Explicit,
72 /// It's some sort of unix timestamp
73 UnixTimestamp,
74 /// Business/legacy-only mode:
75 /// Only accepts ordinal dates (YYYYDDD / YYDDD). No astronomy (JD/MJD) support.
76 /// Strict and predictable for ERP/mainframe data.
77 Legacy,
78 /// Scientific / astronomy-first mode:
79 /// Prioritizes MJD (5-digit) and JD (7-digit). Ordinals are only fallback.
80 /// Use this when parsing data from astronomy tools or large numeric epochs.
81 Scientific,
82}
83
84/// Configuration options for
85/// [`Dt::from_str_parse`](../struct.Dt.html#method.from_str_parse).
86///
87/// Controls language, ambiguous date order, numeric parsing mode,
88/// explicit `strptime` formats, relative-date support, and reference time.
89///
90/// These settings will not persist between parse calls and have to be used
91/// as an arg every time you want them.
92#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
93#[cfg_attr(feature = "tsify", derive(tsify::Tsify))]
94#[derive(Clone, Debug, PartialEq)]
95pub struct ParseCfg {
96 /// Explicit list of formats to try **in the exact order given**.
97 ///
98 /// If this is provided and the vec is non-empty and the mode is Explicit
99 /// then only these formats are tried and `mode` and `order` are ignored.
100 ///
101 /// If the mode is not Explicit then after trying the formats in parse the
102 /// rest of the parser will continue as normal, using `mode` and `order`.
103 ///
104 /// Example:
105 /// ```js
106 /// parse: ["%Y-%m-%d", "%d/%m/%Y", "%m/%d/%Y", "%d.%m.%Y"]
107 /// ```
108 #[cfg_attr(feature = "serde", serde(default))]
109 pub parse: Option<Vec<String>>,
110
111 /// Controls which preset format sets are used (astronomy/scientific formats,
112 /// legacy business rules, etc.).
113 #[cfg_attr(feature = "serde", serde(default))]
114 pub mode: Mode,
115
116 /// Controls ambiguous numeric dates.
117 #[cfg_attr(feature = "serde", serde(default))]
118 pub order: Order,
119
120 /// Sets language to use for a particular parse call.
121 #[cfg_attr(feature = "serde", serde(default))]
122 pub lang: Lang,
123
124 /// Whether to lowercase the input:
125 /// ONLY set to `false` if the &str is already lowercase.
126 #[cfg_attr(feature = "serde", serde(default = "default_true"))]
127 pub to_lower: bool,
128
129 /// Whether to parse relative dates as well as normal dates.
130 #[cfg_attr(feature = "serde", serde(default = "default_true"))]
131 pub relative: bool,
132
133 /// **Reference ("current") time** used for relative expressions:
134 /// - "tomorrow", "next Friday", "in 3 days", "next week"
135 /// - If `Some`, this `Dt` is used as "now" (overrides everything).
136 /// - If `None` + `std` feature enabled: automatically uses real system time.
137 /// - If `None` + no `std`: parsing relative dates will fail with a clear error.
138 #[cfg_attr(feature = "serde", serde(default))]
139 pub ref_time: Option<Dt>,
140}
141
142#[cfg(feature = "serde")]
143fn default_true() -> bool {
144 true
145}
146
147impl ParseCfg {
148 /// Default configuration (all smart defaults).
149 ///
150 /// Pass a reference to this when you want the standard parsing behavior:
151 ///
152 /// ```rust
153 /// use deep_time::{Dt, ParseCfg};
154 ///
155 /// let dt = Dt::from_str_parse("2024-03-15 12:00", &ParseCfg::DEFAULT).unwrap();
156 /// ```
157 pub const DEFAULT: Self = Self {
158 parse: None,
159 mode: Mode::Auto,
160 order: Order::Smart,
161 lang: Lang::En,
162 to_lower: true,
163 relative: true,
164 ref_time: None,
165 };
166}
167
168impl Default for ParseCfg {
169 fn default() -> ParseCfg {
170 Self::DEFAULT
171 }
172}
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq)]
175pub(crate) enum OrderFirst {
176 /// Year-Month-Day ordering (ISO 8601 style, `YYYY-MM-DD`, `20240315`, etc.)
177 Year,
178 /// Month-Day-Year ordering (US / some English locales, `MM/DD/YYYY`)
179 Month,
180 /// Day-Month-Year ordering (most of the world, `DD/MM/YYYY`, `DD.MM.YYYY`)
181 Day,
182}
183
184#[derive(Clone)]
185pub(crate) struct AmBuilder {
186 pub pieces: Vec<&'static str>,
187 pub seen_year: bool,
188 pub seen_month: bool,
189 pub seen_day: bool,
190}
191
192#[inline]
193pub(crate) fn append_to_all(builders: &mut Vec<AmBuilder>, s: &'static str) {
194 for b in builders {
195 b.pieces.push(s);
196 }
197}