use crate::{
assert_syntax,
parsers::{
annotations,
grammar::{
is_annotation_open, is_date_time_separator, is_hyphen, is_sign, is_utc_designator,
},
records::{DateRecord, TimeRecord},
time::parse_time_record,
timezone, Cursor, IxdtfParseRecord,
},
ParseError, ParserResult,
};
use super::records::{Annotation, UtcOffsetRecordOrZ};
#[derive(Debug, Default, Clone)]
pub(crate) struct DateTimeRecord {
pub(crate) date: Option<DateRecord>,
pub(crate) time: Option<TimeRecord>,
pub(crate) time_zone: Option<UtcOffsetRecordOrZ>,
}
pub(crate) fn parse_annotated_date_time<'a>(
cursor: &mut Cursor<'a>,
handler: impl FnMut(Annotation<'a>) -> Option<Annotation<'a>>,
) -> ParserResult<IxdtfParseRecord<'a>> {
let date_time = parse_date_time(cursor)?;
if !cursor.check_or(false, is_annotation_open) {
cursor.close()?;
return Ok(IxdtfParseRecord {
date: date_time.date,
time: date_time.time,
offset: date_time.time_zone,
tz: None,
calendar: None,
});
}
let annotation_set = annotations::parse_annotation_set(cursor, handler)?;
cursor.close()?;
Ok(IxdtfParseRecord {
date: date_time.date,
time: date_time.time,
offset: date_time.time_zone,
tz: annotation_set.tz,
calendar: annotation_set.calendar,
})
}
pub(crate) fn parse_annotated_month_day<'a>(
cursor: &mut Cursor<'a>,
handler: impl FnMut(Annotation<'a>) -> Option<Annotation<'a>>,
) -> ParserResult<IxdtfParseRecord<'a>> {
let date = parse_month_day(cursor)?;
if !cursor.check_or(false, is_annotation_open) {
cursor.close()?;
return Ok(IxdtfParseRecord {
date: Some(date),
time: None,
offset: None,
tz: None,
calendar: None,
});
}
let annotation_set = annotations::parse_annotation_set(cursor, handler)?;
Ok(IxdtfParseRecord {
date: Some(date),
time: None,
offset: None,
tz: annotation_set.tz,
calendar: annotation_set.calendar,
})
}
pub(crate) fn parse_annotated_year_month<'a>(
cursor: &mut Cursor<'a>,
handler: impl FnMut(Annotation<'a>) -> Option<Annotation<'a>>,
) -> ParserResult<IxdtfParseRecord<'a>> {
let year = parse_date_year(cursor)?;
cursor.advance_if(cursor.check_or(false, is_hyphen));
let month = parse_date_month(cursor)?;
let date = DateRecord {
year,
month,
day: 1,
};
if !cursor.check_or(false, is_annotation_open) {
cursor.close()?;
return Ok(IxdtfParseRecord {
date: Some(date),
time: None,
offset: None,
tz: None,
calendar: None,
});
}
let annotation_set = annotations::parse_annotation_set(cursor, handler)?;
Ok(IxdtfParseRecord {
date: Some(date),
time: None,
offset: None,
tz: annotation_set.tz,
calendar: annotation_set.calendar,
})
}
fn parse_date_time(cursor: &mut Cursor) -> ParserResult<DateTimeRecord> {
let date = parse_date(cursor)?;
if !cursor.check_or(false, is_date_time_separator) {
return Ok(DateTimeRecord {
date: Some(date),
time: None,
time_zone: None,
});
}
cursor.advance();
let time = parse_time_record(cursor)?;
let time_zone = if cursor.check_or(false, |ch| is_sign(ch) || is_utc_designator(ch)) {
Some(timezone::parse_date_time_utc(cursor)?)
} else {
None
};
Ok(DateTimeRecord {
date: Some(date),
time: Some(time),
time_zone,
})
}
fn parse_date(cursor: &mut Cursor) -> ParserResult<DateRecord> {
let year = parse_date_year(cursor)?;
let hyphenated = cursor
.check(is_hyphen)
.ok_or(ParseError::abrupt_end("Date"))?;
cursor.advance_if(hyphenated);
let month = parse_date_month(cursor)?;
let second_hyphen = cursor.check_or(false, is_hyphen);
assert_syntax!(hyphenated == second_hyphen, DateSeparator);
cursor.advance_if(second_hyphen);
let day = parse_date_day(cursor)?;
check_date_validity(year, month, day)?;
Ok(DateRecord { year, month, day })
}
pub(crate) fn parse_month_day(cursor: &mut Cursor) -> ParserResult<DateRecord> {
let hyphenated = cursor
.check(is_hyphen)
.ok_or(ParseError::abrupt_end("MonthDay"))?;
cursor.advance_if(hyphenated);
let balanced_hyphens = hyphenated
&& cursor
.check(is_hyphen)
.ok_or(ParseError::abrupt_end("MonthDay"))?;
cursor.advance_if(balanced_hyphens);
if hyphenated && !balanced_hyphens {
return Err(ParseError::MonthDayHyphen);
}
let month = parse_date_month(cursor)?;
cursor.advance_if(cursor.check_or(false, is_hyphen));
let day = parse_date_day(cursor)?;
assert_syntax!(cursor.check_or(true, is_annotation_open), InvalidEnd);
Ok(DateRecord {
year: 0,
month,
day,
})
}
#[inline]
fn parse_date_year(cursor: &mut Cursor) -> ParserResult<i32> {
if cursor.check_or(false, is_sign) {
let sign = if cursor.next_or(ParseError::ImplAssert)? == '+' {
1
} else {
-1
};
let first = cursor.next_digit()?.ok_or(ParseError::DateExtendedYear)? as i32 * 100_000;
let second = cursor.next_digit()?.ok_or(ParseError::DateExtendedYear)? as i32 * 10_000;
let third = cursor.next_digit()?.ok_or(ParseError::DateExtendedYear)? as i32 * 1000;
let fourth = cursor.next_digit()?.ok_or(ParseError::DateExtendedYear)? as i32 * 100;
let fifth = cursor.next_digit()?.ok_or(ParseError::DateExtendedYear)? as i32 * 10;
let year_value = first
+ second
+ third
+ fourth
+ fifth
+ cursor.next_digit()?.ok_or(ParseError::DateExtendedYear)? as i32;
if sign == -1 && year_value == 0 {
return Err(ParseError::DateExtendedYear);
}
let year = sign * year_value;
return Ok(year);
}
let first = cursor.next_digit()?.ok_or(ParseError::DateYear)? as i32 * 1000;
let second = cursor.next_digit()?.ok_or(ParseError::DateYear)? as i32 * 100;
let third = cursor.next_digit()?.ok_or(ParseError::DateYear)? as i32 * 10;
let year_value =
first + second + third + cursor.next_digit()?.ok_or(ParseError::DateYear)? as i32;
Ok(year_value)
}
#[inline]
fn parse_date_month(cursor: &mut Cursor) -> ParserResult<u8> {
let first = cursor.next_digit()?.ok_or(ParseError::DateMonth)?;
let month_value = first * 10 + cursor.next_digit()?.ok_or(ParseError::DateMonth)?;
if !(1..=12).contains(&month_value) {
return Err(ParseError::InvalidMonthRange);
}
Ok(month_value)
}
#[inline]
fn parse_date_day(cursor: &mut Cursor) -> ParserResult<u8> {
let first = cursor.next_digit()?.ok_or(ParseError::DateDay)?;
let day_value = first * 10 + cursor.next_digit()?.ok_or(ParseError::DateDay)?;
Ok(day_value)
}
#[inline]
fn check_date_validity(year: i32, month: u8, day: u8) -> ParserResult<()> {
let Some(days_in_month) = days_in_month(year, month) else {
return Err(ParseError::InvalidMonthRange);
};
if !(1..=days_in_month).contains(&day) {
return Err(ParseError::InvalidDayRange);
}
Ok(())
}
#[inline]
fn days_in_month(year: i32, month: u8) -> Option<u8> {
match month {
1 | 3 | 5 | 7 | 8 | 10 | 12 => Some(31),
4 | 6 | 9 | 11 => Some(30),
2 => Some(28 + u8::from(in_leap_year(year))),
_ => None,
}
}
#[inline]
fn in_leap_year(year: i32) -> bool {
if year % 4 != 0 {
false
} else if year % 4 == 0 && year % 100 != 0 {
true
} else if year % 100 == 0 && year % 400 != 0 {
false
} else {
assert_eq!(year % 400, 0);
true
}
}