1use crate::{
8 assert_syntax,
9 encoding::EncodingType,
10 parsers::{
11 annotations,
12 grammar::{is_annotation_open, is_date_time_separator, is_hyphen, is_utc_designator},
13 time::parse_time_record,
14 timezone, Cursor, IxdtfParseRecord,
15 },
16 records::{Annotation, DateRecord, TimeRecord, UtcOffsetRecordOrZ},
17 ParseError, ParserResult,
18};
19
20use super::grammar::is_ascii_sign;
21
22#[derive(Debug, Default, Clone)]
23pub(crate) struct DateTimeRecord {
25 pub(crate) date: Option<DateRecord>,
27 pub(crate) time: Option<TimeRecord>,
29 pub(crate) time_zone: Option<UtcOffsetRecordOrZ>,
31}
32
33pub(crate) fn parse_annotated_date_time<'a, T: EncodingType>(
42 cursor: &mut Cursor<'a, T>,
43 handler: impl FnMut(Annotation<'a, T>) -> Option<Annotation<'a, T>>,
44) -> ParserResult<IxdtfParseRecord<'a, T>> {
45 let date_time = parse_date_time(cursor)?;
46
47 if !cursor.check_or(false, is_annotation_open)? {
50 cursor.close()?;
51
52 return Ok(IxdtfParseRecord {
53 date: date_time.date,
54 time: date_time.time,
55 offset: date_time.time_zone,
56 tz: None,
57 calendar: None,
58 });
59 }
60
61 let annotation_set = annotations::parse_annotation_set(cursor, handler)?;
62
63 cursor.close()?;
64
65 Ok(IxdtfParseRecord {
66 date: date_time.date,
67 time: date_time.time,
68 offset: date_time.time_zone,
69 tz: annotation_set.tz,
70 calendar: annotation_set.calendar,
71 })
72}
73
74fn parse_date_time<T: EncodingType>(cursor: &mut Cursor<T>) -> ParserResult<DateTimeRecord> {
76 let date = parse_date(cursor)?;
77
78 if !cursor.check_or(false, is_date_time_separator)? {
80 return Ok(DateTimeRecord {
81 date: Some(date),
82 time: None,
83 time_zone: None,
84 });
85 }
86
87 cursor.advance();
88
89 let time = parse_time_record(cursor)?;
90
91 let time_zone = if cursor.check_or(false, |ch| is_ascii_sign(ch) || is_utc_designator(ch))? {
92 Some(timezone::parse_date_time_utc_offset(cursor)?)
93 } else {
94 None
95 };
96
97 Ok(DateTimeRecord {
98 date: Some(date),
99 time: Some(time),
100 time_zone,
101 })
102}
103
104fn parse_date<T: EncodingType>(cursor: &mut Cursor<T>) -> ParserResult<DateRecord> {
106 let year = parse_date_year(cursor)?;
107 let hyphenated = cursor
108 .check(is_hyphen)?
109 .ok_or(ParseError::AbruptEnd { location: "Date" })?;
110
111 cursor.advance_if(hyphenated);
112
113 let month = parse_date_month(cursor)?;
114
115 let second_hyphen = cursor.check_or(false, is_hyphen)?;
116 assert_syntax!(hyphenated == second_hyphen, DateSeparator);
117 cursor.advance_if(second_hyphen);
118
119 let day = parse_date_day(cursor)?;
120
121 check_date_validity(year, month, day)?;
122
123 Ok(DateRecord { year, month, day })
124}
125
126pub(crate) fn parse_annotated_year_month<'a, T: EncodingType>(
130 cursor: &mut Cursor<'a, T>,
131 handler: impl FnMut(Annotation<'a, T>) -> Option<Annotation<'a, T>>,
132) -> ParserResult<IxdtfParseRecord<'a, T>> {
133 let year_month = parse_year_month(cursor)?;
134 if !cursor.check_or(false, is_annotation_open)? {
135 cursor.close()?;
136
137 return Ok(IxdtfParseRecord {
138 date: Some(year_month),
139 time: None,
140 offset: None,
141 tz: None,
142 calendar: None,
143 });
144 }
145
146 let annotation_set = annotations::parse_annotation_set(cursor, handler)?;
147 cursor.close()?;
148
149 Ok(IxdtfParseRecord {
150 date: Some(year_month),
151 time: None,
152 offset: None,
153 tz: annotation_set.tz,
154 calendar: annotation_set.calendar,
155 })
156}
157
158pub(crate) fn parse_year_month<T: EncodingType>(
159 cursor: &mut Cursor<T>,
160) -> ParserResult<DateRecord> {
161 let year = parse_date_year(cursor)?;
162 cursor.advance_if(cursor.check_or(false, is_hyphen)?);
163 let month = parse_date_month(cursor)?;
164
165 Ok(DateRecord {
166 year,
167 month,
168 day: 1,
169 })
170}
171
172pub(crate) fn parse_annotated_month_day<'a, T: EncodingType>(
176 cursor: &mut Cursor<'a, T>,
177 handler: impl FnMut(Annotation<'a, T>) -> Option<Annotation<'a, T>>,
178) -> ParserResult<IxdtfParseRecord<'a, T>> {
179 let date = parse_month_day(cursor)?;
180
181 if !cursor.check_or(false, is_annotation_open)? {
182 cursor.close()?;
183
184 return Ok(IxdtfParseRecord {
185 date: Some(date),
186 time: None,
187 offset: None,
188 tz: None,
189 calendar: None,
190 });
191 }
192
193 let annotation_set = annotations::parse_annotation_set(cursor, handler)?;
194 cursor.close()?;
195
196 Ok(IxdtfParseRecord {
197 date: Some(date),
198 time: None,
199 offset: None,
200 tz: annotation_set.tz,
201 calendar: annotation_set.calendar,
202 })
203}
204
205pub(crate) fn parse_month_day<T: EncodingType>(cursor: &mut Cursor<T>) -> ParserResult<DateRecord> {
207 let hyphenated = cursor.check(is_hyphen)?.ok_or(ParseError::AbruptEnd {
208 location: "MonthDay",
209 })?;
210 cursor.advance_if(hyphenated);
211 let balanced_hyphens = hyphenated
212 && cursor.check(is_hyphen)?.ok_or(ParseError::AbruptEnd {
213 location: "MonthDay",
214 })?;
215 cursor.advance_if(balanced_hyphens);
216
217 if hyphenated && !balanced_hyphens {
218 return Err(ParseError::MonthDayHyphen);
219 }
220
221 let month = parse_date_month(cursor)?;
222
223 cursor.advance_if(cursor.check_or(false, is_hyphen)?);
224
225 let day = parse_date_day(cursor)?;
226
227 if !is_valid_month_day(month, day) {
228 return Err(ParseError::InvalidMonthDay);
229 }
230
231 Ok(DateRecord {
232 year: 0,
233 month,
234 day,
235 })
236}
237
238fn is_valid_month_day(month: u8, day: u8) -> bool {
239 match month {
240 2 | 4 | 6 | 9 | 11 if day >= 31 => false,
241 2 if day == 30 => false,
242 _ => day <= 31,
243 }
244}
245
246#[inline]
249fn parse_date_year<T: EncodingType>(cursor: &mut Cursor<T>) -> ParserResult<i32> {
250 if cursor.check_or(false, is_ascii_sign)? {
251 let sign = if cursor.next_or(ParseError::ImplAssert)? == b'+' {
252 1
253 } else {
254 -1
255 };
256
257 let first = cursor.next_digit()?.ok_or(ParseError::DateExtendedYear)? as i32 * 100_000;
258 let second = cursor.next_digit()?.ok_or(ParseError::DateExtendedYear)? as i32 * 10_000;
259 let third = cursor.next_digit()?.ok_or(ParseError::DateExtendedYear)? as i32 * 1000;
260 let fourth = cursor.next_digit()?.ok_or(ParseError::DateExtendedYear)? as i32 * 100;
261 let fifth = cursor.next_digit()?.ok_or(ParseError::DateExtendedYear)? as i32 * 10;
262
263 let year_value = first
264 + second
265 + third
266 + fourth
267 + fifth
268 + cursor.next_digit()?.ok_or(ParseError::DateExtendedYear)? as i32;
269
270 if sign == -1 && year_value == 0 {
274 return Err(ParseError::DateExtendedYear);
275 }
276
277 let year = sign * year_value;
278
279 return Ok(year);
280 }
281
282 let first = cursor.next_digit()?.ok_or(ParseError::DateYear)? as i32 * 1000;
283 let second = cursor.next_digit()?.ok_or(ParseError::DateYear)? as i32 * 100;
284 let third = cursor.next_digit()?.ok_or(ParseError::DateYear)? as i32 * 10;
285 let year_value =
286 first + second + third + cursor.next_digit()?.ok_or(ParseError::DateYear)? as i32;
287
288 Ok(year_value)
289}
290
291#[inline]
292fn parse_date_month<T: EncodingType>(cursor: &mut Cursor<T>) -> ParserResult<u8> {
293 let first = cursor.next_digit()?.ok_or(ParseError::DateMonth)?;
294 let month_value = first * 10 + cursor.next_digit()?.ok_or(ParseError::DateMonth)?;
295 if !(1..=12).contains(&month_value) {
296 return Err(ParseError::InvalidMonthRange);
297 }
298 Ok(month_value)
299}
300
301#[inline]
302fn parse_date_day<T: EncodingType>(cursor: &mut Cursor<T>) -> ParserResult<u8> {
303 let first = cursor.next_digit()?.ok_or(ParseError::DateDay)?;
304 let day_value = first * 10 + cursor.next_digit()?.ok_or(ParseError::DateDay)?;
305 Ok(day_value)
306}
307
308#[inline]
309fn check_date_validity(year: i32, month: u8, day: u8) -> ParserResult<()> {
310 let Some(days_in_month) = days_in_month(year, month) else {
311 return Err(ParseError::InvalidMonthRange);
313 };
314 if !(1..=days_in_month).contains(&day) {
315 return Err(ParseError::InvalidDayRange);
316 }
317 Ok(())
318}
319
320#[inline]
322fn days_in_month(year: i32, month: u8) -> Option<u8> {
323 match month {
324 1 | 3 | 5 | 7 | 8 | 10 | 12 => Some(31),
325 4 | 6 | 9 | 11 => Some(30),
326 2 => Some(28 + u8::from(in_leap_year(year))),
327 _ => None,
328 }
329}
330
331#[inline]
333fn in_leap_year(year: i32) -> bool {
334 if year % 4 != 0 {
335 false
336 } else if year % 4 == 0 && year % 100 != 0 {
337 true
338 } else if year % 100 == 0 && year % 400 != 0 {
339 false
340 } else {
341 assert_eq!(year % 400, 0);
342 true
343 }
344}