1use super::{
8 grammar::{
9 is_a_key_leading_char, is_annotation_close, is_annotation_key_value_separator,
10 is_annotation_open, is_ascii_sign, is_critical_flag, is_time_separator, is_tz_char,
11 is_tz_leading_char, is_tz_name_separator, is_utc_designator,
12 },
13 time::{parse_fraction, parse_hour, parse_minute_second},
14 Cursor,
15};
16use crate::{
17 assert_syntax,
18 encoding::EncodingType,
19 records::{
20 FullPrecisionOffset, MinutePrecisionOffset, Sign, TimeZoneAnnotation, TimeZoneRecord,
21 UtcOffsetRecord, UtcOffsetRecordOrZ,
22 },
23 ParseError, ParserResult,
24};
25
26pub(crate) fn parse_ambiguous_tz_annotation<'a, T: EncodingType>(
38 cursor: &mut Cursor<'a, T>,
39) -> ParserResult<Option<TimeZoneAnnotation<'a, T>>> {
40 let mut current_peek = 1;
42 let critical =
43 cursor
44 .peek_n(current_peek)?
45 .map(is_critical_flag)
46 .ok_or(ParseError::AbruptEnd {
47 location: "AmbiguousAnnotation",
48 })?;
49
50 if critical {
52 current_peek += 1;
53 }
54
55 let leading_char = cursor.peek_n(current_peek)?.ok_or(ParseError::AbruptEnd {
56 location: "AmbiguousAnnotation",
57 })?;
58
59 if is_a_key_leading_char(leading_char) {
61 let mut peek_pos = current_peek + 1;
62 while let Some(ch) = cursor.peek_n(peek_pos)? {
64 if is_annotation_key_value_separator(ch) {
65 return Ok(None);
67 } else if is_annotation_close(ch) {
68 let tz = parse_tz_annotation(cursor)?;
70 return Ok(Some(tz));
71 }
72
73 peek_pos += 1;
74 }
75 Err(ParseError::AbruptEnd {
76 location: "AmbiguousAnnotation",
77 })
78 } else {
79 let tz = parse_tz_annotation(cursor)?;
81 Ok(Some(tz))
82 }
83}
84
85fn parse_tz_annotation<'a, T: EncodingType>(
86 cursor: &mut Cursor<'a, T>,
87) -> ParserResult<TimeZoneAnnotation<'a, T>> {
88 assert_syntax!(
89 is_annotation_open(cursor.next_or(ParseError::AnnotationOpen)?),
90 AnnotationOpen
91 );
92
93 let critical = cursor.check_or(false, is_critical_flag)?;
94 cursor.advance_if(critical);
95
96 let tz = parse_time_zone(cursor)?;
97
98 assert_syntax!(
99 is_annotation_close(cursor.next_or(ParseError::AnnotationClose)?),
100 AnnotationClose
101 );
102
103 Ok(TimeZoneAnnotation { critical, tz })
104}
105
106pub(crate) fn parse_time_zone<'a, T: EncodingType>(
110 cursor: &mut Cursor<'a, T>,
111) -> ParserResult<TimeZoneRecord<'a, T>> {
112 let is_iana = cursor
113 .check(is_tz_leading_char)?
114 .ok_or(ParseError::AbruptEnd {
115 location: "TimeZoneAnnotation",
116 })?;
117 let is_offset = cursor.check_or(false, is_ascii_sign)?;
118
119 if is_iana {
120 return Ok(TimeZoneRecord::Name(parse_tz_iana_name(cursor)?));
121 } else if is_offset {
122 let offset = parse_utc_offset_minute_precision_strict(cursor)?;
123 return Ok(TimeZoneRecord::Offset(offset));
124 }
125
126 Err(ParseError::TzLeadingChar)
127}
128
129pub(crate) fn parse_tz_iana_name<'a, T: EncodingType>(
131 cursor: &mut Cursor<'a, T>,
132) -> ParserResult<&'a [T::CodeUnit]> {
133 assert_syntax!(cursor.check_or(false, is_tz_leading_char)?, TzLeadingChar);
134 let tz_name_start = cursor.pos();
135 while let Some(potential_value_char) = cursor.next()? {
136 if cursor.check_or(true, is_annotation_close)? {
137 break;
139 }
140
141 if is_tz_name_separator(potential_value_char) {
142 assert_syntax!(cursor.check_or(false, is_tz_char)?, IanaCharPostSeparator,);
143 continue;
144 }
145
146 assert_syntax!(is_tz_char(potential_value_char), IanaChar,);
147 }
148
149 cursor
150 .slice(tz_name_start, cursor.pos())
151 .ok_or(ParseError::ImplAssert)
152}
153
154pub(crate) fn parse_date_time_utc_offset<T: EncodingType>(
158 cursor: &mut Cursor<T>,
159) -> ParserResult<UtcOffsetRecordOrZ> {
160 if cursor.check_or(false, is_utc_designator)? {
161 cursor.advance();
162 return Ok(UtcOffsetRecordOrZ::Z);
163 }
164
165 let utc_offset = parse_utc_offset(cursor)?;
166 Ok(UtcOffsetRecordOrZ::Offset(utc_offset))
167}
168
169pub(crate) fn parse_utc_offset<T: EncodingType>(
171 cursor: &mut Cursor<T>,
172) -> ParserResult<UtcOffsetRecord> {
173 let (minute_precision_offset, separated) = parse_utc_offset_minute_precision(cursor)?;
174
175 if !cursor.check_or(false, |ch| ch.is_ascii_digit() || is_time_separator(ch))? {
177 return Ok(UtcOffsetRecord::MinutePrecision(minute_precision_offset));
178 }
179
180 if Some(separated) != cursor.check(is_time_separator)? {
181 return Err(ParseError::UtcTimeSeparator);
182 }
183 cursor.advance_if(cursor.check_or(false, is_time_separator)?);
184
185 let second = parse_minute_second(cursor, false)?;
186
187 let fraction = parse_fraction(cursor)?;
188
189 Ok(UtcOffsetRecord::FullPrecisionOffset(FullPrecisionOffset {
190 minute_precision_offset,
191 second,
192 fraction,
193 }))
194}
195
196pub(crate) fn parse_utc_offset_minute_precision_strict<T: EncodingType>(
197 cursor: &mut Cursor<T>,
198) -> ParserResult<MinutePrecisionOffset> {
199 let (offset, _) = parse_utc_offset_minute_precision(cursor)?;
200 if cursor.check_or(false, |ch| is_time_separator(ch) || ch.is_ascii_digit())? {
201 return Err(ParseError::InvalidMinutePrecisionOffset);
202 }
203 Ok(offset)
204}
205
206pub(crate) fn parse_utc_offset_minute_precision<T: EncodingType>(
210 cursor: &mut Cursor<T>,
211) -> ParserResult<(MinutePrecisionOffset, bool)> {
212 let sign = cursor.next_or(ParseError::AbruptEnd {
214 location: "time-numoffset",
215 })?;
216 if !is_ascii_sign(sign) {
217 return Err(ParseError::OffsetNeedsSign);
218 }
219 let sign = Sign::from(sign == b'+');
220
221 let hour = parse_hour(cursor)?;
222
223 if !cursor.check_or(false, |ch| ch.is_ascii_digit() || is_time_separator(ch))? {
225 let offset = MinutePrecisionOffset {
226 sign,
227 hour,
228 minute: 0,
229 };
230 return Ok((offset, false));
231 }
232 let separated = cursor.check_or(false, is_time_separator)?;
234 cursor.advance_if(separated);
235
236 let minute = parse_minute_second(cursor, false)?;
237
238 Ok((MinutePrecisionOffset { sign, hour, minute }, separated))
239}