use crate::bounds::{date_bounds, is_leap, Bound};
use crate::types::*;
use alloc::vec;
use alloc::vec::Vec;
pub(crate) fn parse(input: &str) -> Result<Edtf, ParseError> {
if input.is_empty() {
return Err(err(0, "empty input"));
}
if let Some(pos) = input.bytes().position(|b| !b.is_ascii()) {
return Err(err(pos, "EDTF permits ASCII characters only"));
}
if let Some(pos) = input
.bytes()
.position(|b| b == b' ' || b.is_ascii_control())
{
return Err(err(pos, "whitespace is not allowed"));
}
match input.as_bytes()[0] {
b'{' | b'[' => parse_set(input),
_ if input.contains('/') => parse_interval(input),
_ if input.contains('T') => parse_datetime(input),
_ => parse_date_at(input, 0).map(Edtf::Date),
}
}
fn err(offset: usize, message: &'static str) -> ParseError {
ParseError { message, offset }
}
fn offset_in(whole: &str, part: &str) -> usize {
(part.as_ptr() as usize).saturating_sub(whole.as_ptr() as usize)
}
struct Cur<'a> {
b: &'a [u8],
i: usize,
base: usize,
}
impl<'a> Cur<'a> {
fn new(s: &'a str, base: usize) -> Self {
Self {
b: s.as_bytes(),
i: 0,
base,
}
}
fn pos(&self) -> usize {
self.base + self.i
}
fn fail(&self, message: &'static str) -> ParseError {
err(self.pos(), message)
}
fn peek(&self) -> Option<u8> {
self.b.get(self.i).copied()
}
fn bump(&mut self) -> Option<u8> {
let c = self.peek();
if c.is_some() {
self.i += 1;
}
c
}
fn eat(&mut self, c: u8) -> bool {
if self.peek() == Some(c) {
self.i += 1;
true
} else {
false
}
}
fn eof(&self) -> bool {
self.i >= self.b.len()
}
fn take_qualifier(&mut self) -> Option<Qualifier> {
let q = match self.peek()? {
b'?' => Qualifier {
uncertain: true,
approximate: false,
},
b'~' => Qualifier {
uncertain: false,
approximate: true,
},
b'%' => Qualifier {
uncertain: true,
approximate: true,
},
_ => return None,
};
self.i += 1;
Some(q)
}
fn take_number(&mut self) -> Result<(i64, usize), ParseError> {
let start = self.i;
let mut v: i64 = 0;
while let Some(d @ b'0'..=b'9') = self.peek() {
self.i += 1;
v = v
.checked_mul(10)
.and_then(|x| x.checked_add(i64::from(d - b'0')))
.ok_or_else(|| err(self.base + start, "number out of supported range"))?;
}
Ok((v, self.i - start))
}
fn take_two(&mut self, what: &'static str) -> Result<[Option<u8>; 2], ParseError> {
let mut out = [None; 2];
for slot in &mut out {
match self.bump() {
Some(d @ b'0'..=b'9') => *slot = Some(d - b'0'),
Some(b'X') => *slot = None,
_ => {
return Err(ParseError {
message: match what {
"month" => "month must be two digits (or X)",
_ => "day must be two digits (or X)",
},
offset: self.base + self.i.saturating_sub(1),
})
}
}
}
Ok(out)
}
}
pub(crate) fn parse_date_at(s: &str, base: usize) -> Result<Date, ParseError> {
if s.is_empty() {
return Err(err(base, "empty date"));
}
if s.as_bytes()[0] == b'Y' {
return parse_prefixed_year(s, base);
}
let mut c = Cur::new(s, base);
let mut year_qual = Qualifier::default();
if let Some(q) = c.take_qualifier() {
year_qual.merge(q);
}
let negative = c.eat(b'-');
let year_off = c.pos();
let mut ydigits = [None; 4];
for slot in &mut ydigits {
match c.bump() {
Some(d @ b'0'..=b'9') => *slot = Some(d - b'0'),
Some(b'X') => *slot = None,
_ => {
return Err(err(
c.pos().saturating_sub(1),
"year must have exactly four digits (or X)",
))
}
}
}
let year_has_x = ydigits.iter().any(|d| d.is_none());
if negative && year_has_x {
return Err(err(
year_off,
"negative years cannot contain unspecified digits",
));
}
if negative && ydigits == [Some(0); 4] {
return Err(err(year_off - 1, "-0000 is not a valid year"));
}
let mut significant = None;
if c.eat(b'S') {
if year_has_x {
return Err(err(
c.pos() - 1,
"significant digits cannot combine with unspecified digits",
));
}
let sig_off = c.pos();
let (n, len) = c.take_number()?;
if len == 0 || n == 0 || n > 4 {
return Err(err(
sig_off,
"significant digits must be 1-4 for a four-digit year",
));
}
significant = Some(n as u32);
}
if let Some(q) = c.take_qualifier() {
year_qual.merge(q);
}
if c.eof() {
return finish_date(
negative,
ydigits,
significant,
year_qual,
None,
None,
year_off,
year_off,
);
}
if significant.is_some() {
return Err(c.fail("significant-digit years are year-precision only"));
}
if !c.eat(b'-') {
return Err(c.fail("expected '-' before month"));
}
let mut month_qual = Qualifier::default();
if let Some(q) = c.take_qualifier() {
month_qual.merge(q);
}
let month_off = c.pos();
let month_digits = c.take_two("month")?;
if let Some(q) = c.take_qualifier() {
month_qual.merge(q);
year_qual.merge(q);
}
let mut month = DateField {
digits: month_digits,
qualifier: month_qual,
};
if c.eof() {
return finish_date(
negative,
ydigits,
None,
year_qual,
Some(month),
None,
month_off,
month_off,
);
}
if !c.eat(b'-') {
return Err(c.fail("expected '-' before day"));
}
let mut day_qual = Qualifier::default();
if let Some(q) = c.take_qualifier() {
day_qual.merge(q);
}
let day_off = c.pos();
let day_digits = c.take_two("day")?;
if let Some(q) = c.take_qualifier() {
day_qual.merge(q);
month.qualifier.merge(q);
year_qual.merge(q);
}
if !c.eof() {
return Err(c.fail("unexpected characters after day"));
}
let day = DateField {
digits: day_digits,
qualifier: day_qual,
};
finish_date(
negative,
ydigits,
None,
year_qual,
Some(month),
Some(day),
month_off,
day_off,
)
}
#[allow(clippy::too_many_arguments)]
fn finish_date(
negative: bool,
ydigits: [Option<u8>; 4],
significant: Option<u32>,
year_qual: Qualifier,
month: Option<DateField>,
day: Option<DateField>,
month_off: usize,
day_off: usize,
) -> Result<Date, ParseError> {
let year = Year {
kind: YearKind::Standard {
negative,
digits: ydigits,
},
significant_digits: significant,
qualifier: year_qual,
};
validate_month_day(&year.kind, month.as_ref(), day.as_ref(), month_off, day_off)?;
Ok(Date { year, month, day })
}
fn validate_month_day(
year: &YearKind,
month: Option<&DateField>,
day: Option<&DateField>,
month_off: usize,
day_off: usize,
) -> Result<(), ParseError> {
let Some(m) = month else {
return Ok(());
};
match m.value() {
Some(v) => {
if !((1..=12).contains(&v) || (21..=41).contains(&v)) {
return Err(err(
month_off,
"month must be 01-12 or a sub-year code 21-41",
));
}
if (21..=41).contains(&v) && day.is_some() {
return Err(err(day_off, "sub-year groupings cannot carry a day"));
}
}
None => {
if month_candidates(m).is_empty() {
return Err(err(
month_off,
"no calendar month matches the unspecified digits",
));
}
}
}
if let Some(d) = day {
if !day_has_valid_completion(year, m, d) {
return Err(err(day_off, "day is out of range for the month"));
}
}
Ok(())
}
fn month_candidates(m: &DateField) -> Vec<u8> {
match m.value() {
Some(v) if (1..=12).contains(&v) => vec![v],
Some(_) => Vec::new(),
None => (1..=12).filter(|v| field_matches(m, *v)).collect(),
}
}
fn day_candidates(d: &DateField) -> Vec<u8> {
match d.value() {
Some(v) if (1..=31).contains(&v) => vec![v],
Some(_) => Vec::new(),
None => (1..=31).filter(|v| field_matches(d, *v)).collect(),
}
}
fn field_matches(f: &DateField, v: u8) -> bool {
f.digits[0].is_none_or(|p| p == v / 10) && f.digits[1].is_none_or(|p| p == v % 10)
}
fn day_has_valid_completion(year: &YearKind, m: &DateField, d: &DateField) -> bool {
let months = month_candidates(m);
let days = day_candidates(d);
let mut leap_possible: Option<bool> = None;
for &mm in &months {
for &dd in &days {
let max = match mm {
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
4 | 6 | 9 | 11 => 30,
2 => {
if dd <= 28 {
28
} else {
let leap = *leap_possible.get_or_insert_with(|| year_leap_possible(year));
if leap {
29
} else {
28
}
}
}
_ => unreachable!("month candidates are 1-12"),
};
if dd <= max {
return true;
}
}
}
false
}
fn year_leap_possible(year: &YearKind) -> bool {
match year {
YearKind::Standard { negative, digits } => {
if digits.iter().all(|d| d.is_some()) {
let mut v: i64 = 0;
for d in digits.iter().flatten() {
v = v * 10 + i64::from(*d);
}
is_leap(if *negative { -v } else { v })
} else {
(0..=9999i64).any(|y| year_matches(digits, y) && is_leap(y))
}
}
YearKind::Big { value } => is_leap(*value),
YearKind::Exponential { .. } => true,
}
}
fn year_matches(digits: &[Option<u8>; 4], y: i64) -> bool {
let actual = [(y / 1000) % 10, (y / 100) % 10, (y / 10) % 10, y % 10];
digits
.iter()
.zip(actual)
.all(|(pat, a)| pat.is_none_or(|p| i64::from(p) == a))
}
fn parse_prefixed_year(s: &str, base: usize) -> Result<Date, ParseError> {
let mut c = Cur::new(s, base);
c.eat(b'Y');
let negative = c.eat(b'-');
let value_off = c.pos();
let (mantissa, mantissa_len) = c.take_number()?;
if mantissa_len == 0 {
return Err(c.fail("expected digits after 'Y'"));
}
if mantissa > 0 && mantissa_len as u32 != mantissa.ilog10() + 1 {
return Err(err(
value_off,
"leading zeros are not allowed in Y-prefixed years",
));
}
let kind;
let digit_count: u64;
if c.eat(b'E') {
let exp_off = c.pos();
let (exp, exp_len) = c.take_number()?;
if exp_len == 0 {
return Err(err(exp_off, "expected digits after exponent 'E'"));
}
if mantissa == 0 {
return Err(err(
value_off,
"exponential year significand cannot be zero",
));
}
if exp > 100_000 {
return Err(err(exp_off, "exponent out of supported range"));
}
let significand = if negative { -mantissa } else { mantissa };
let exponent = exp as u32;
if let Some(v) = 10i64
.checked_pow(exponent)
.and_then(|p| significand.checked_mul(p))
{
if v.abs() <= 9999 {
return Err(err(value_off, "Y-prefixed years require |year| > 9999"));
}
}
kind = YearKind::Exponential {
significand,
exponent,
};
digit_count = mantissa_len as u64 + u64::from(exponent);
} else {
if mantissa <= 9999 {
return Err(err(value_off, "Y-prefixed years require |year| > 9999"));
}
kind = YearKind::Big {
value: if negative { -mantissa } else { mantissa },
};
digit_count = mantissa_len as u64;
}
let mut significant = None;
if c.eat(b'S') {
let sig_off = c.pos();
let (n, len) = c.take_number()?;
if len == 0 || n == 0 || (n as u64) > digit_count {
return Err(err(
sig_off,
"significant digits exceed the year's digit count",
));
}
significant = Some(n as u32);
}
let mut qualifier = Qualifier::default();
if let Some(q) = c.take_qualifier() {
qualifier.merge(q);
}
if !c.eof() {
return Err(c.fail("Y-prefixed years are year-precision only"));
}
Ok(Date {
year: Year {
kind,
significant_digits: significant,
qualifier,
},
month: None,
day: None,
})
}
fn parse_datetime(s: &str) -> Result<Edtf, ParseError> {
let (date_part, time_part) = s.split_once('T').expect("caller checked for 'T'");
let date = parse_plain_complete_date(date_part)?;
let time = parse_time(time_part, date_part.len() + 1)?;
Ok(Edtf::DateTime(DateTime { date, time }))
}
fn parse_plain_complete_date(s: &str) -> Result<Date, ParseError> {
let b = s.as_bytes();
if b.len() != 10 || b[4] != b'-' || b[7] != b'-' {
return Err(err(0, "date-times require a complete YYYY-MM-DD date"));
}
let digit = |i: usize| -> Result<u8, ParseError> {
match b[i] {
d @ b'0'..=b'9' => Ok(d - b'0'),
_ => Err(err(i, "date-times require a plain all-digit date")),
}
};
let ydigits = [
Some(digit(0)?),
Some(digit(1)?),
Some(digit(2)?),
Some(digit(3)?),
];
let month = DateField {
digits: [Some(digit(5)?), Some(digit(6)?)],
qualifier: Qualifier::default(),
};
let day = DateField {
digits: [Some(digit(8)?), Some(digit(9)?)],
qualifier: Qualifier::default(),
};
let m = month.value().expect("fully specified");
if !(1..=12).contains(&m) {
return Err(err(5, "month must be 01-12"));
}
let kind = YearKind::Standard {
negative: false,
digits: ydigits,
};
if !day_has_valid_completion(&kind, &month, &day) {
return Err(err(8, "day is out of range for the month"));
}
Ok(Date {
year: Year {
kind,
significant_digits: None,
qualifier: Qualifier::default(),
},
month: Some(month),
day: Some(day),
})
}
fn parse_time(s: &str, base: usize) -> Result<Time, ParseError> {
let b = s.as_bytes();
if b.len() < 8 || b[2] != b':' || b[5] != b':' {
return Err(err(base, "time must be hh:mm:ss"));
}
let two = |i: usize| -> Result<u8, ParseError> {
match (b[i], b[i + 1]) {
(t @ b'0'..=b'9', o @ b'0'..=b'9') => Ok((t - b'0') * 10 + (o - b'0')),
_ => Err(err(base + i, "time components must be two digits")),
}
};
let hour = two(0)?;
let minute = two(3)?;
let second = two(6)?;
if hour > 23 {
return Err(err(base, "hour must be 00-23 (24 is not allowed)"));
}
if minute > 59 {
return Err(err(base + 3, "minute must be 00-59"));
}
if second > 60 {
return Err(err(base + 6, "second must be 00-60"));
}
let rest = &s[8..];
let shift = if rest.is_empty() {
None
} else if rest == "Z" {
Some(TimeShift::Utc)
} else {
Some(parse_shift(rest, base + 8)?)
};
Ok(Time {
hour,
minute,
second,
shift,
})
}
fn parse_shift(s: &str, base: usize) -> Result<TimeShift, ParseError> {
let b = s.as_bytes();
let negative = match b[0] {
b'+' => false,
b'-' => true,
_ => return Err(err(base, "time shift must be Z, +hh, +hh:mm or negative")),
};
let (hours, minutes, hours_only) = match b.len() {
3 => {
let h = shift_two(b, 1, base)?;
(h, 0, true)
}
6 if b[3] == b':' => {
let h = shift_two(b, 1, base)?;
let m = shift_two(b, 4, base)?;
(h, m, false)
}
_ => return Err(err(base, "time shift must be ±hh or ±hh:mm")),
};
if minutes > 59 {
return Err(err(base + 4, "shift minutes must be 00-59"));
}
let total = i16::from(hours) * 60 + i16::from(minutes);
if total > 14 * 60 {
return Err(err(base, "time shift exceeds ±14:00"));
}
if negative && total == 0 {
return Err(err(base, "negative zero time shift is not allowed"));
}
Ok(TimeShift::Offset {
minutes: if negative { -total } else { total },
hours_only,
})
}
fn shift_two(b: &[u8], i: usize, base: usize) -> Result<u8, ParseError> {
match (b[i], b[i + 1]) {
(t @ b'0'..=b'9', o @ b'0'..=b'9') => Ok((t - b'0') * 10 + (o - b'0')),
_ => Err(err(base + i, "time shift components must be two digits")),
}
}
fn parse_interval(s: &str) -> Result<Edtf, ParseError> {
let mut parts = s.splitn(3, '/');
let start_str = parts.next().expect("split yields at least one part");
let end_str = parts
.next()
.ok_or_else(|| err(s.len(), "interval requires '/'"))?;
if parts.next().is_some() {
return Err(err(
offset_in(s, end_str) + end_str.len(),
"interval must contain exactly one '/'",
));
}
let start = parse_endpoint(start_str, offset_in(s, start_str), true)?;
let end = parse_endpoint(end_str, offset_in(s, end_str), false)?;
if !start.is_dated() && !end.is_dated() {
return Err(err(0, "interval needs at least one dated endpoint"));
}
if let (IntervalEndpoint::Date(a), IntervalEndpoint::Date(b)) = (&start, &end) {
if dates_out_of_order(a, b) {
return Err(err(
offset_in(s, end_str),
"interval end precedes interval start",
));
}
}
Ok(Edtf::Interval(Interval { start, end }))
}
fn parse_endpoint(s: &str, base: usize, is_start: bool) -> Result<IntervalEndpoint, ParseError> {
if s.is_empty() {
return Ok(IntervalEndpoint::Unknown);
}
if s == ".." {
return Ok(IntervalEndpoint::Open);
}
if let Some(rest) = s.strip_prefix("..") {
if !is_start {
return Err(err(
base,
"'..'-prefixed date is only allowed as interval start",
));
}
return Ok(IntervalEndpoint::OnOrBefore(parse_date_at(rest, base + 2)?));
}
if let Some(rest) = s.strip_suffix("..") {
if is_start {
return Err(err(
base + rest.len(),
"'..'-suffixed date is only allowed as interval end",
));
}
return Ok(IntervalEndpoint::OnOrAfter(parse_date_at(rest, base)?));
}
Ok(IntervalEndpoint::Date(parse_date_at(s, base)?))
}
fn dates_out_of_order(a: &Date, b: &Date) -> bool {
match (date_bounds(a).earliest, date_bounds(b).latest) {
(Bound::Date(lo), Bound::Date(hi)) => lo > hi,
_ => false,
}
}
fn parse_set(s: &str) -> Result<Edtf, ParseError> {
let b = s.as_bytes();
let (kind, close) = match b[0] {
b'{' => (SetKind::AllMembers, b'}'),
_ => (SetKind::OneMember, b']'),
};
if b.len() < 2 || b[b.len() - 1] != close {
return Err(err(s.len().saturating_sub(1), "unterminated set"));
}
let inner = &s[1..s.len() - 1];
if inner.is_empty() {
return Err(err(1, "set must contain at least one element"));
}
if let Some(pos) = inner
.bytes()
.position(|c| matches!(c, b'{' | b'}' | b'[' | b']'))
{
return Err(err(1 + pos, "sets cannot nest"));
}
let mut elements = Vec::new();
for part in inner.split(',') {
let part_off = offset_in(s, part);
if part.is_empty() {
return Err(err(part_off, "empty set element"));
}
elements.push(parse_set_element(part, part_off)?);
}
Ok(Edtf::Set(Set { kind, elements }))
}
fn parse_set_element(p: &str, base: usize) -> Result<SetElement, ParseError> {
if p == ".." {
return Err(err(base, "'..' alone is not a set element"));
}
if let Some(rest) = p.strip_prefix("..") {
return Ok(SetElement::OnOrBefore(parse_date_at(rest, base + 2)?));
}
if let Some(rest) = p.strip_suffix("..") {
return Ok(SetElement::OnOrAfter(parse_date_at(rest, base)?));
}
if let Some(idx) = p.find("..") {
let (a, b) = (&p[..idx], &p[idx + 2..]);
if b.contains("..") {
return Err(err(
base + idx + 2,
"set element cannot contain multiple '..'",
));
}
let from = parse_date_at(a, base)?;
let to = parse_date_at(b, base + idx + 2)?;
if dates_out_of_order(&from, &to) {
return Err(err(base + idx + 2, "set range end precedes range start"));
}
return Ok(SetElement::Range(from, to));
}
Ok(SetElement::Date(parse_date_at(p, base)?))
}