ixdtf/parsers/
timezone.rs1use 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 let tz_name_start = cursor.pos();
134 assert_syntax!(cursor.next_if(is_tz_leading_char)?.is_some(), TzLeadingChar,);
135 while let Some(potential_value_char) = cursor.next_if(|ch| !is_annotation_close(ch))? {
136 if is_tz_name_separator(potential_value_char) {
137 assert_syntax!(cursor.next_if(is_tz_char)?.is_some(), IanaCharPostSeparator,);
138 continue;
139 }
140
141 assert_syntax!(is_tz_char(potential_value_char), IanaChar);
142 }
143
144 cursor
145 .slice(tz_name_start, cursor.pos())
146 .ok_or(ParseError::ImplAssert)
147}
148
149pub(crate) fn parse_date_time_utc_offset<T: EncodingType>(
153 cursor: &mut Cursor<T>,
154) -> ParserResult<UtcOffsetRecordOrZ> {
155 if cursor.check_or(false, is_utc_designator)? {
156 cursor.advance();
157 return Ok(UtcOffsetRecordOrZ::Z);
158 }
159
160 let utc_offset = parse_utc_offset(cursor)?;
161 Ok(UtcOffsetRecordOrZ::Offset(utc_offset))
162}
163
164pub(crate) fn parse_utc_offset<T: EncodingType>(
166 cursor: &mut Cursor<T>,
167) -> ParserResult<UtcOffsetRecord> {
168 let (minute_precision_offset, separated) = parse_utc_offset_minute_precision(cursor)?;
169
170 if !cursor.check_or(false, |ch| ch.is_ascii_digit() || is_time_separator(ch))? {
172 return Ok(UtcOffsetRecord::MinutePrecision(minute_precision_offset));
173 }
174
175 if Some(separated) != cursor.check(is_time_separator)? {
176 return Err(ParseError::UtcTimeSeparator);
177 }
178 cursor.advance_if(cursor.check_or(false, is_time_separator)?);
179
180 let second = parse_minute_second(cursor, false)?;
181
182 let fraction = parse_fraction(cursor)?;
183
184 Ok(UtcOffsetRecord::FullPrecisionOffset(FullPrecisionOffset {
185 minute_precision_offset,
186 second,
187 fraction,
188 }))
189}
190
191pub(crate) fn parse_utc_offset_minute_precision_strict<T: EncodingType>(
192 cursor: &mut Cursor<T>,
193) -> ParserResult<MinutePrecisionOffset> {
194 let (offset, _) = parse_utc_offset_minute_precision(cursor)?;
195 if cursor.check_or(false, |ch| is_time_separator(ch) || ch.is_ascii_digit())? {
196 return Err(ParseError::InvalidMinutePrecisionOffset);
197 }
198 Ok(offset)
199}
200
201pub(crate) fn parse_utc_offset_minute_precision<T: EncodingType>(
205 cursor: &mut Cursor<T>,
206) -> ParserResult<(MinutePrecisionOffset, bool)> {
207 let sign = cursor.next_or(ParseError::AbruptEnd {
209 location: "time-numoffset",
210 })?;
211 if !is_ascii_sign(sign) {
212 return Err(ParseError::OffsetNeedsSign);
213 }
214 let sign = Sign::from(sign == b'+');
215
216 let hour = parse_hour(cursor)?;
217
218 if !cursor.check_or(false, |ch| ch.is_ascii_digit() || is_time_separator(ch))? {
220 let offset = MinutePrecisionOffset {
221 sign,
222 hour,
223 minute: 0,
224 };
225 return Ok((offset, false));
226 }
227 let separated = cursor.check_or(false, is_time_separator)?;
229 cursor.advance_if(separated);
230
231 let minute = parse_minute_second(cursor, false)?;
232
233 Ok((MinutePrecisionOffset { sign, hour, minute }, separated))
234}