use super::{
grammar::{
is_a_key_leading_char, is_annotation_close, is_annotation_key_value_separator,
is_annotation_open, is_ascii_sign, is_critical_flag, is_time_separator, is_tz_char,
is_tz_leading_char, is_tz_name_separator, is_utc_designator,
},
time::{parse_fraction, parse_hour, parse_minute_second},
Cursor,
};
use crate::{
assert_syntax,
encoding::EncodingType,
records::{
FullPrecisionOffset, MinutePrecisionOffset, Sign, TimeZoneAnnotation, TimeZoneRecord,
UtcOffsetRecord, UtcOffsetRecordOrZ,
},
ParseError, ParserResult,
};
pub(crate) fn parse_ambiguous_tz_annotation<'a, T: EncodingType>(
cursor: &mut Cursor<'a, T>,
) -> ParserResult<Option<TimeZoneAnnotation<'a, T>>> {
let mut current_peek = 1;
let critical =
cursor
.peek_n(current_peek)?
.map(is_critical_flag)
.ok_or(ParseError::AbruptEnd {
location: "AmbiguousAnnotation",
})?;
if critical {
current_peek += 1;
}
let leading_char = cursor.peek_n(current_peek)?.ok_or(ParseError::AbruptEnd {
location: "AmbiguousAnnotation",
})?;
if is_a_key_leading_char(leading_char) {
let mut peek_pos = current_peek + 1;
while let Some(ch) = cursor.peek_n(peek_pos)? {
if is_annotation_key_value_separator(ch) {
return Ok(None);
} else if is_annotation_close(ch) {
let tz = parse_tz_annotation(cursor)?;
return Ok(Some(tz));
}
peek_pos += 1;
}
Err(ParseError::AbruptEnd {
location: "AmbiguousAnnotation",
})
} else {
let tz = parse_tz_annotation(cursor)?;
Ok(Some(tz))
}
}
fn parse_tz_annotation<'a, T: EncodingType>(
cursor: &mut Cursor<'a, T>,
) -> ParserResult<TimeZoneAnnotation<'a, T>> {
assert_syntax!(
is_annotation_open(cursor.next_or(ParseError::AnnotationOpen)?),
AnnotationOpen
);
let critical = cursor.check_or(false, is_critical_flag)?;
cursor.advance_if(critical);
let tz = parse_time_zone(cursor)?;
assert_syntax!(
is_annotation_close(cursor.next_or(ParseError::AnnotationClose)?),
AnnotationClose
);
Ok(TimeZoneAnnotation { critical, tz })
}
pub(crate) fn parse_time_zone<'a, T: EncodingType>(
cursor: &mut Cursor<'a, T>,
) -> ParserResult<TimeZoneRecord<'a, T>> {
let is_iana = cursor
.check(is_tz_leading_char)?
.ok_or(ParseError::AbruptEnd {
location: "TimeZoneAnnotation",
})?;
let is_offset = cursor.check_or(false, is_ascii_sign)?;
if is_iana {
return Ok(TimeZoneRecord::Name(parse_tz_iana_name(cursor)?));
} else if is_offset {
let offset = parse_utc_offset_minute_precision_strict(cursor)?;
return Ok(TimeZoneRecord::Offset(offset));
}
Err(ParseError::TzLeadingChar)
}
pub(crate) fn parse_tz_iana_name<'a, T: EncodingType>(
cursor: &mut Cursor<'a, T>,
) -> ParserResult<&'a [T::CodeUnit]> {
assert_syntax!(cursor.check_or(false, is_tz_leading_char)?, TzLeadingChar);
let tz_name_start = cursor.pos();
while let Some(potential_value_char) = cursor.next()? {
if cursor.check_or(true, is_annotation_close)? {
break;
}
if is_tz_name_separator(potential_value_char) {
assert_syntax!(cursor.check_or(false, is_tz_char)?, IanaCharPostSeparator,);
continue;
}
assert_syntax!(is_tz_char(potential_value_char), IanaChar,);
}
cursor
.slice(tz_name_start, cursor.pos())
.ok_or(ParseError::ImplAssert)
}
pub(crate) fn parse_date_time_utc_offset<T: EncodingType>(
cursor: &mut Cursor<T>,
) -> ParserResult<UtcOffsetRecordOrZ> {
if cursor.check_or(false, is_utc_designator)? {
cursor.advance();
return Ok(UtcOffsetRecordOrZ::Z);
}
let utc_offset = parse_utc_offset(cursor)?;
Ok(UtcOffsetRecordOrZ::Offset(utc_offset))
}
pub(crate) fn parse_utc_offset<T: EncodingType>(
cursor: &mut Cursor<T>,
) -> ParserResult<UtcOffsetRecord> {
let (minute_precision_offset, separated) = parse_utc_offset_minute_precision(cursor)?;
if !cursor.check_or(false, |ch| ch.is_ascii_digit() || is_time_separator(ch))? {
return Ok(UtcOffsetRecord::MinutePrecision(minute_precision_offset));
}
if Some(separated) != cursor.check(is_time_separator)? {
return Err(ParseError::UtcTimeSeparator);
}
cursor.advance_if(cursor.check_or(false, is_time_separator)?);
let second = parse_minute_second(cursor, false)?;
let fraction = parse_fraction(cursor)?;
Ok(UtcOffsetRecord::FullPrecisionOffset(FullPrecisionOffset {
minute_precision_offset,
second,
fraction,
}))
}
pub(crate) fn parse_utc_offset_minute_precision_strict<T: EncodingType>(
cursor: &mut Cursor<T>,
) -> ParserResult<MinutePrecisionOffset> {
let (offset, _) = parse_utc_offset_minute_precision(cursor)?;
if cursor.check_or(false, |ch| is_time_separator(ch) || ch.is_ascii_digit())? {
return Err(ParseError::InvalidMinutePrecisionOffset);
}
Ok(offset)
}
pub(crate) fn parse_utc_offset_minute_precision<T: EncodingType>(
cursor: &mut Cursor<T>,
) -> ParserResult<(MinutePrecisionOffset, bool)> {
let sign = cursor.next_or(ParseError::AbruptEnd {
location: "time-numoffset",
})?;
if !is_ascii_sign(sign) {
return Err(ParseError::OffsetNeedsSign);
}
let sign = Sign::from(sign == b'+');
let hour = parse_hour(cursor)?;
if !cursor.check_or(false, |ch| ch.is_ascii_digit() || is_time_separator(ch))? {
let offset = MinutePrecisionOffset {
sign,
hour,
minute: 0,
};
return Ok((offset, false));
}
let separated = cursor.check_or(false, is_time_separator)?;
cursor.advance_if(separated);
let minute = parse_minute_second(cursor, false)?;
Ok((MinutePrecisionOffset { sign, hour, minute }, separated))
}