1use core::num::NonZeroU8;
8
9use crate::{
10 assert_syntax,
11 encoding::EncodingType,
12 parsers::{
13 datetime::{parse_month_day, parse_year_month},
14 grammar::{
15 is_annotation_open, is_decimal_separator, is_time_designator, is_time_separator,
16 is_utc_designator,
17 },
18 Cursor,
19 },
20 records::{Annotation, Fraction, IxdtfParseRecord, TimeRecord},
21 ParseError, ParserResult,
22};
23
24use super::{annotations, grammar::is_ascii_sign, timezone};
25
26pub(crate) fn parse_annotated_time_record<'a, T: EncodingType>(
29 cursor: &mut Cursor<'a, T>,
30 handler: impl FnMut(Annotation<'a, T>) -> Option<Annotation<'a, T>>,
31) -> ParserResult<IxdtfParseRecord<'a, T>> {
32 let start = cursor.pos();
33 let designator = cursor.check_or(false, is_time_designator)?;
34 cursor.advance_if(designator);
35
36 let time = parse_time_record(cursor)?;
37
38 let offset = if cursor.check_or(false, |ch| is_ascii_sign(ch) || is_utc_designator(ch))? {
42 Some(timezone::parse_date_time_utc_offset(cursor)?)
43 } else {
44 None
45 };
46
47 if !cursor.check_or(false, is_annotation_open)? {
49 cursor.close()?;
50 check_time_ambiguity(cursor, start)?;
51
52 return Ok(IxdtfParseRecord {
53 date: None,
54 time: Some(time),
55 offset,
56 tz: None,
57 calendar: None,
58 });
59 }
60
61 check_time_ambiguity(cursor, start)?;
62 let annotations = annotations::parse_annotation_set(cursor, handler)?;
63
64 cursor.close()?;
65
66 Ok(IxdtfParseRecord {
67 date: None,
68 time: Some(time),
69 offset,
70 tz: annotations.tz,
71 calendar: annotations.calendar,
72 })
73}
74
75#[inline]
76fn check_time_ambiguity<T: EncodingType>(cursor: &mut Cursor<T>, start: usize) -> ParserResult<()> {
77 let current_loc = cursor.pos();
78 cursor.set_position(start);
80 if parse_month_day(cursor).is_ok() {
81 return Err(ParseError::AmbiguousTimeMonthDay);
82 }
83 cursor.set_position(start);
85 if parse_year_month(cursor).is_ok() {
86 return Err(ParseError::AmbiguousTimeYearMonth);
87 }
88 cursor.set_position(current_loc);
89 Ok(())
90}
91
92pub(crate) fn parse_time_record<T: EncodingType>(
94 cursor: &mut Cursor<T>,
95) -> ParserResult<TimeRecord> {
96 let hour = parse_hour(cursor)?;
97
98 if !cursor.check_or(false, |ch| is_time_separator(ch) || ch.is_ascii_digit())? {
99 return Ok(TimeRecord {
100 hour,
101 minute: 0,
102 second: 0,
103 fraction: None,
104 });
105 }
106
107 let separator_present = cursor.check_or(false, is_time_separator)?;
108 cursor.advance_if(separator_present);
109
110 let minute = parse_minute_second(cursor, false)?;
111
112 if !cursor.check_or(false, |ch| is_time_separator(ch) || ch.is_ascii_digit())? {
113 return Ok(TimeRecord {
114 hour,
115 minute,
116 second: 0,
117 fraction: None,
118 });
119 }
120
121 let second_separator = cursor.check_or(false, is_time_separator)?;
122 assert_syntax!(separator_present == second_separator, TimeSeparator);
123 cursor.advance_if(second_separator);
124
125 let second = parse_minute_second(cursor, true)?;
126
127 let fraction = parse_fraction(cursor)?;
128
129 Ok(TimeRecord {
130 hour,
131 minute,
132 second,
133 fraction,
134 })
135}
136
137#[inline]
139pub(crate) fn parse_hour<T: EncodingType>(cursor: &mut Cursor<T>) -> ParserResult<u8> {
140 let first = cursor.next_digit()?.ok_or(ParseError::TimeHour)?;
141 let hour_value = first * 10 + cursor.next_digit()?.ok_or(ParseError::TimeHour)?;
142 if !(0..=23).contains(&hour_value) {
143 return Err(ParseError::TimeHour);
144 }
145 Ok(hour_value)
146}
147
148#[inline]
150pub(crate) fn parse_minute_second<T: EncodingType>(
151 cursor: &mut Cursor<T>,
152 is_leap_second_valid: bool,
153) -> ParserResult<u8> {
154 let (valid_range, err) = if is_leap_second_valid {
155 (0..=60, ParseError::TimeSecond)
156 } else {
157 (0..=59, ParseError::TimeMinuteSecond)
158 };
159 let first = cursor.next_digit()?.ok_or(err)?;
160 let min_sec_value = first * 10 + cursor.next_digit()?.ok_or(err)?;
161 if !valid_range.contains(&min_sec_value) {
162 return Err(err);
163 }
164 Ok(min_sec_value)
165}
166
167#[inline]
172pub(crate) fn parse_fraction<T: EncodingType>(
173 cursor: &mut Cursor<T>,
174) -> ParserResult<Option<Fraction>> {
175 if !cursor.check_or(false, is_decimal_separator)? {
177 return Ok(None);
178 }
179 cursor.next_or(ParseError::FractionPart)?;
180
181 let mut value = 0;
182 let mut digits: u8 = 0;
183 while cursor.check_or(false, |ch| ch.is_ascii_digit())? {
184 let next_value = u64::from(cursor.next_digit()?.ok_or(ParseError::ImplAssert)?);
185 if digits < 18 {
186 value = value * 10 + next_value;
187 }
188 digits = digits.saturating_add(1);
189 }
190
191 let digits = NonZeroU8::new(digits).ok_or(ParseError::FractionPart)?;
192
193 Ok(Some(Fraction { digits, value }))
194}