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}