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}