Skip to main content

edtf_core/
enumerate.rs

1//! Enumeration of the concrete calendar values an expression denotes.
2//!
3//! ISO 8601-2 grounds exactly three value-set constructions, and
4//! [`Edtf::values`] enumerates those and nothing else:
5//!
6//! - set range elements expand inclusively at their own precision (§6.3 c, §6.4
7//!   Example 1: `{1667,1668,1670..1672}` *is* `{1667,1668,1670,1671, 1672}`);
8//! - unspecified digits denote their valid completions (§9.2.2 Example 6:
9//!   `1560-X2` is "either February or December");
10//! - significant-digit years denote a swept year range (§4.4.3: `1950S2` is
11//!   some year 1900–1999).
12//!
13//! Everything else is a singleton (a concrete date, datetime, season or
14//! `Y`-year yields itself, verbatim) or [`Unenumerable`]: intervals denote
15//! one continuous extent — Clause 10 has no enumeration language — and the
16//! `..`-prefixed/suffixed set elements of §6.3 a–b are "indication", never
17//! "expansion" (§6.4 Example 2 pointedly leaves `1760-12..` unexpanded).
18//!
19//! Values are yielded lazily in written element order, ascending within
20//! each element, with qualifiers copied through unchanged (qualification
21//! never moves the value set, §8.4.2 NOTE). Decisions D24–D29 in
22//! docs/spec-notes.md.
23
24use alloc::vec::Vec;
25
26use crate::{
27    bounds::{
28        big_width, day_candidates_of, is_leap, last_day, month_candidates_of, significant_range,
29    },
30    types::{Date, DateField, Edtf, Precision, Qualifier, SetElement, Year, YearKind},
31};
32
33/// Why [`Edtf::values`] cannot enumerate an expression (decisions D24–D25).
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
36pub enum Unenumerable {
37    /// Intervals denote a single continuous extent, not a collection of
38    /// values (ISO 8601-2 Clause 10 defines no enumeration for them).
39    Interval,
40    /// The set contains a `..date` or `date..` element, whose value set is
41    /// unbounded (§6.3 a–b give "indication", not "expansion").
42    UnboundedSetElement,
43    /// A significant-digit sweep or range endpoint lies beyond the years
44    /// this library computes with — the same cases where `bounds()` reports
45    /// [`Bound::Unknown`](crate::Bound::Unknown).
46    YearRangeOverflow,
47}
48
49impl core::fmt::Display for Unenumerable {
50    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
51        f.write_str(match self {
52            Self::Interval => "intervals denote an extent, not enumerable values",
53            Self::UnboundedSetElement => "'..'-open set elements denote unbounded value sets",
54            Self::YearRangeOverflow => "year range exceeds the computable range",
55        })
56    }
57}
58
59impl core::error::Error for Unenumerable {}
60
61impl Edtf {
62    /// Enumerate the concrete calendar values this expression denotes, each
63    /// at its own precision (decisions D24–D29 in docs/spec-notes.md).
64    ///
65    /// The iterator is lazy — state is proportional to the number of set
66    /// elements, never to the number of values — so pathological
67    /// cardinalities (`XXXX-XX-XX` denotes ~3.65 million days) stream
68    /// without allocation. Errors are structural and complete at
69    /// construction: iteration itself cannot fail.
70    ///
71    /// ```
72    /// use edtf_core::Edtf;
73    ///
74    /// // ISO 8601-2 §6.4 Example 1, expanded exactly as the spec does:
75    /// let set = Edtf::parse("{1667,1668,1670..1672}").unwrap();
76    /// let years: Vec<String> = set.values().unwrap().map(|v| v.to_string()).collect();
77    /// assert_eq!(years, ["1667", "1668", "1670", "1671", "1672"]);
78    ///
79    /// // Unspecified digits enumerate their valid completions (§9.2.2):
80    /// let masked = Edtf::parse("1985-0X-31").unwrap();
81    /// let months: Vec<String> = masked.values().unwrap().map(|v| v.to_string()).collect();
82    /// assert_eq!(
83    ///     months,
84    ///     [
85    ///         "1985-01-31",
86    ///         "1985-03-31",
87    ///         "1985-05-31",
88    ///         "1985-07-31",
89    ///         "1985-08-31"
90    ///     ]
91    /// );
92    ///
93    /// // Intervals denote one extent, not a collection:
94    /// assert!(Edtf::parse("2004/2005").unwrap().values().is_err());
95    /// ```
96    ///
97    /// # Errors
98    ///
99    /// [`Unenumerable`] when the value denotes no finite collection: an
100    /// interval, a set with an unbounded `..date`/`date..` element, or a
101    /// year sweep beyond the numeric range this library computes with.
102    pub fn values(&self) -> Result<Values, Unenumerable> {
103        let state = match self {
104            Self::Interval(_) => return Err(Unenumerable::Interval),
105            Self::DateTime(dt) => State::Singleton(Some(Self::DateTime(*dt))),
106            Self::Date(d) => State::Date(DateValues::new(d)?),
107            Self::Set(s) => {
108                let mut queue = Vec::with_capacity(s.elements.len());
109                for e in &s.elements {
110                    queue.push(match e {
111                        SetElement::Date(d) => ElementValues::Date(DateValues::new(d)?),
112                        SetElement::Range(a, b) => ElementValues::Range(RangeWalk::new(a, b)?),
113                        SetElement::OnOrBefore(_) | SetElement::OnOrAfter(_) => {
114                            return Err(Unenumerable::UnboundedSetElement);
115                        },
116                    });
117                }
118                State::Set { queue, idx: 0 }
119            },
120        };
121        Ok(Values { state })
122    }
123}
124
125/// Lazy iterator over the values an expression denotes; see [`Edtf::values`].
126#[derive(Debug, Clone)]
127pub struct Values {
128    state: State,
129}
130
131#[derive(Debug, Clone)]
132enum State {
133    Singleton(Option<Edtf>),
134    Date(DateValues),
135    Set {
136        queue: Vec<ElementValues>,
137        idx: usize,
138    },
139}
140
141#[derive(Debug, Clone)]
142enum ElementValues {
143    Date(DateValues),
144    Range(RangeWalk),
145}
146
147impl ElementValues {
148    fn next(&mut self) -> Option<Date> {
149        match self {
150            Self::Date(dv) => dv.next(),
151            Self::Range(rw) => rw.next(),
152        }
153    }
154}
155
156impl Iterator for Values {
157    type Item = Edtf;
158
159    fn next(&mut self) -> Option<Edtf> {
160        match &mut self.state {
161            State::Singleton(v) => v.take(),
162            State::Date(dv) => dv.next().map(Edtf::Date),
163            State::Set { queue, idx } => loop {
164                let cur = queue.get_mut(*idx)?;
165                if let Some(d) = cur.next() {
166                    return Some(Edtf::Date(d));
167                }
168                *idx += 1;
169            },
170        }
171    }
172}
173
174/// Rebuild a concrete year value in its canonical syntactic form: four
175/// digits within ±9999 (D2: `-0000` cannot arise — the value would be 0),
176/// `Y`-prefixed beyond (D1).
177fn concrete_year(v: i64, qualifier: Qualifier) -> Year {
178    let kind = if (-9999..=9999).contains(&v) {
179        let mag = v.unsigned_abs();
180        YearKind::Standard {
181            negative: v < 0,
182            digits: [
183                Some((mag / 1000 % 10) as u8),
184                Some((mag / 100 % 10) as u8),
185                Some((mag / 10 % 10) as u8),
186                Some((mag % 10) as u8),
187            ],
188        }
189    } else {
190        YearKind::Big { value: v }
191    };
192    Year {
193        kind,
194        significant_digits: None,
195        qualifier,
196    }
197}
198
199const fn concrete_field(v: u8, qualifier: Qualifier) -> DateField {
200    DateField {
201        digits: [Some(v / 10), Some(v % 10)],
202        qualifier,
203    }
204}
205
206/// Values of a single (possibly masked or significant-digit) date.
207#[derive(Debug, Clone)]
208enum DateValues {
209    /// Fully specified: yields itself, verbatim, once.
210    Singleton(Option<Date>),
211    /// `S`-suffix year sweep (§4.4.3), ascending, year precision.
212    Sweep {
213        cur: i64,
214        hi: i64,
215        qualifier: Qualifier,
216        done: bool,
217    },
218    /// Unspecified digits: valid completions in ascending calendar order.
219    Masked(Masked),
220}
221
222impl DateValues {
223    fn new(d: &Date) -> Result<Self, Unenumerable> {
224        if d.year.significant_digits.is_some() {
225            // S-years are year-precision only (the parser enforces this).
226            let value = d.year.value().ok_or(Unenumerable::YearRangeOverflow)?;
227            let (lo, hi) =
228                significant_range(value, d.year.significant_digits, big_width(&d.year.kind))
229                    .ok_or(Unenumerable::YearRangeOverflow)?;
230            return Ok(Self::Sweep {
231                cur: lo,
232                hi,
233                qualifier: d.year.qualifier,
234                done: false,
235            });
236        }
237        if !d.has_unspecified() {
238            return Ok(Self::Singleton(Some(*d)));
239        }
240        Ok(Self::Masked(Masked::new(d)))
241    }
242
243    fn next(&mut self) -> Option<Date> {
244        match self {
245            Self::Singleton(d) => d.take(),
246            Self::Sweep {
247                cur,
248                hi,
249                qualifier,
250                done,
251            } => {
252                if *done {
253                    return None;
254                }
255                let v = *cur;
256                if v == *hi {
257                    *done = true;
258                } else {
259                    *cur += 1;
260                }
261                Some(Date {
262                    year: concrete_year(v, *qualifier),
263                    month: None,
264                    day: None,
265                })
266            },
267            Self::Masked(m) => m.next(),
268        }
269    }
270}
271
272/// Odometer over the valid completions of a masked date: years ascend
273/// (least-significant masked digit fastest, so consecutive counters yield
274/// consecutive matching years), months and days ascend within each year,
275/// and calendar-invalid day combinations are skipped. Validation (D11)
276/// guarantees at least one completion overall; masked years are
277/// non-negative (D21) and masked months draw from 01–12 only (D14).
278#[derive(Debug, Clone)]
279enum MaskedYear {
280    /// Concrete (possibly negative) year value.
281    Fixed(i64),
282    /// Masked digit pattern to sweep (non-negative, D21).
283    Pattern([Option<u8>; 4]),
284}
285
286#[derive(Debug, Clone)]
287struct Masked {
288    year: MaskedYear,
289    year_count: u32,
290    months: Option<Vec<u8>>,
291    days: Option<Vec<u8>>,
292    yq: Qualifier,
293    mq: Qualifier,
294    dq: Qualifier,
295    yi: u32,
296    mi: usize,
297    di: usize,
298}
299
300impl Masked {
301    fn new(d: &Date) -> Self {
302        // Masks only occur on standard years; Y-years are digit-valued.
303        let (year, year_count) = match (d.year.value(), d.year.kind) {
304            (Some(v), _) => (MaskedYear::Fixed(v), 1),
305            (None, YearKind::Standard { digits, .. }) => {
306                // At most four maskable digit positions.
307                let n = u32::try_from(digits.iter().filter(|x| x.is_none()).count()).unwrap_or(4);
308                (MaskedYear::Pattern(digits), 10u32.pow(n))
309            },
310            (None, _) => unreachable!("only standard years carry X digits"),
311        };
312        Self {
313            year,
314            year_count,
315            months: d.month.map(month_candidates_of),
316            days: d.day.map(day_candidates_of),
317            yq: d.year.qualifier,
318            mq: d.month.map(|m| m.qualifier).unwrap_or_default(),
319            dq: d.day.map(|f| f.qualifier).unwrap_or_default(),
320            yi: 0,
321            mi: 0,
322            di: 0,
323        }
324    }
325
326    fn year_at(&self, counter: u32) -> i64 {
327        match self.year {
328            MaskedYear::Fixed(v) => v,
329            MaskedYear::Pattern(digits) => {
330                let mut rem = counter;
331                let mut filled = digits.map(|d| d.unwrap_or(0));
332                for pos in (0..4).rev() {
333                    if digits[pos].is_none() {
334                        filled[pos] = (rem % 10) as u8;
335                        rem /= 10;
336                    }
337                }
338                filled.iter().fold(0, |acc, d| acc * 10 + i64::from(*d))
339            },
340        }
341    }
342
343    fn next(&mut self) -> Option<Date> {
344        loop {
345            if self.yi >= self.year_count {
346                return None;
347            }
348            let y = self.year_at(self.yi);
349            let Some(months) = &self.months else {
350                self.yi += 1;
351                return Some(Date {
352                    year: concrete_year(y, self.yq),
353                    month: None,
354                    day: None,
355                });
356            };
357            while self.mi < months.len() {
358                let m = months[self.mi];
359                let Some(days) = &self.days else {
360                    self.mi += 1;
361                    // `m` may be a season code 21–41 (only when written
362                    // concretely — masked slots draw from 01–12, D14).
363                    return Some(Date {
364                        year: concrete_year(y, self.yq),
365                        month: Some(concrete_field(m, self.mq)),
366                        day: None,
367                    });
368                };
369                while self.di < days.len() {
370                    let dd = days[self.di];
371                    self.di += 1;
372                    if dd <= last_day(m, is_leap(y)) {
373                        return Some(Date {
374                            year: concrete_year(y, self.yq),
375                            month: Some(concrete_field(m, self.mq)),
376                            day: Some(concrete_field(dd, self.dq)),
377                        });
378                    }
379                }
380                self.di = 0;
381                self.mi += 1;
382            }
383            self.mi = 0;
384            self.yi += 1;
385        }
386    }
387}
388
389/// Inclusive expansion of a set range element `a..b` (§6.3 c, §6.4) by
390/// calendar successor at the endpoints' shared precision. The parser (D27)
391/// guarantees the endpoints are concrete, unqualified, non-season,
392/// same-precision dates in order (D18), so the walk terminates by reaching
393/// `b` exactly.
394#[derive(Debug, Clone)]
395struct RangeWalk {
396    /// Current position; slots beyond the precision stay 0 so plain
397    /// tuple equality detects the end.
398    cur: (i64, u8, u8),
399    end: (i64, u8, u8),
400    precision: Precision,
401    done: bool,
402}
403
404impl RangeWalk {
405    fn new(a: &Date, b: &Date) -> Result<Self, Unenumerable> {
406        let ay = a.year.value().ok_or(Unenumerable::YearRangeOverflow)?;
407        let by = b.year.value().ok_or(Unenumerable::YearRangeOverflow)?;
408        let field = |f: Option<DateField>| f.and_then(DateField::value).unwrap_or(0);
409        Ok(Self {
410            cur: (ay, field(a.month), field(a.day)),
411            end: (by, field(b.month), field(b.day)),
412            precision: a.precision(),
413            done: false,
414        })
415    }
416
417    fn next(&mut self) -> Option<Date> {
418        if self.done {
419            return None;
420        }
421        let (y, m, d) = self.cur;
422        if self.cur == self.end {
423            self.done = true;
424        } else {
425            self.cur = match self.precision {
426                Precision::Year => (y + 1, 0, 0),
427                Precision::Month => {
428                    if m == 12 {
429                        (y + 1, 1, 0)
430                    } else {
431                        (y, m + 1, 0)
432                    }
433                },
434                Precision::Day => {
435                    if d < last_day(m, is_leap(y)) {
436                        (y, m, d + 1)
437                    } else if m == 12 {
438                        (y + 1, 1, 1)
439                    } else {
440                        (y, m + 1, 1)
441                    }
442                },
443                Precision::Season => unreachable!("D27 rejects season range endpoints"),
444            };
445        }
446        let q = Qualifier::default();
447        Some(Date {
448            year: concrete_year(y, q),
449            month: (self.precision != Precision::Year).then(|| concrete_field(m, q)),
450            day: (self.precision == Precision::Day).then(|| concrete_field(d, q)),
451        })
452    }
453}