Skip to main content

edtf_core/
enumerate.rs

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