1use core::num::NonZero;
4
5use num_conv::prelude::*;
6
7use crate::convert::*;
8use crate::format_description::{Period, modifier};
9use crate::parsing::ParsedItem;
10use crate::parsing::combinator::{
11 ExactlyNDigits, Sign, any_digit, exactly_n_digits_padded, n_to_m_digits, n_to_m_digits_padded,
12 opt, sign,
13};
14use crate::{Month, Weekday};
15
16pub(crate) fn parse_year(
18 input: &[u8],
19 modifiers: modifier::Year,
20) -> Option<ParsedItem<'_, (i32, bool)>> {
21 match modifiers.repr {
22 modifier::YearRepr::Full => {
23 let ParsedItem(input, sign) = opt(sign)(input);
24
25 if let Some(sign) = sign {
26 let ParsedItem(input, year) = if cfg!(feature = "large-dates")
27 && modifiers.range == modifier::YearRange::Extended
28 {
29 n_to_m_digits_padded::<4, 6, u32>(modifiers.padding)(input)?
30 } else {
31 exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?
32 };
33
34 Some(ParsedItem(
35 input,
36 match sign {
37 Sign::Negative => (-year.cast_signed(), true),
38 Sign::Positive => (year.cast_signed(), false),
39 },
40 ))
41 } else if modifiers.sign_is_mandatory {
42 None
43 } else {
44 let ParsedItem(input, year) =
45 exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
46 Some(ParsedItem(input, (year.cast_signed(), false)))
47 }
48 }
49 modifier::YearRepr::Century => {
50 let ParsedItem(input, sign) = opt(sign)(input);
51
52 if let Some(sign) = sign {
53 let ParsedItem(input, year) = if cfg!(feature = "large-dates")
54 && modifiers.range == modifier::YearRange::Extended
55 {
56 n_to_m_digits_padded::<2, 4, u32>(modifiers.padding)(input)?
57 } else {
58 exactly_n_digits_padded::<2, u32>(modifiers.padding)(input)?
59 };
60
61 Some(ParsedItem(
62 input,
63 match sign {
64 Sign::Negative => (-year.cast_signed(), true),
65 Sign::Positive => (year.cast_signed(), false),
66 },
67 ))
68 } else if modifiers.sign_is_mandatory {
69 None
70 } else {
71 let ParsedItem(input, year) =
72 n_to_m_digits_padded::<1, 2, u32>(modifiers.padding)(input)?;
73 Some(ParsedItem(input, (year.cast_signed(), false)))
74 }
75 }
76 modifier::YearRepr::LastTwo => Some(
77 exactly_n_digits_padded::<2, u32>(modifiers.padding)(input)?
78 .map(|v| (v.cast_signed(), false)),
79 ),
80 }
81}
82
83#[inline]
85pub(crate) fn parse_month(
86 input: &[u8],
87 modifiers: modifier::Month,
88) -> Option<ParsedItem<'_, Month>> {
89 use Month::*;
90 match modifiers.repr {
91 modifier::MonthRepr::Numerical => {
92 exactly_n_digits_padded::<2, _>(modifiers.padding)(input)?
93 .flat_map(|n| Month::from_number(NonZero::new(n)?).ok())
94 }
95 modifier::MonthRepr::Long | modifier::MonthRepr::Short => {
96 let [first, second, third, rest @ ..] = input else {
97 return None;
98 };
99 let byte = if modifiers.case_sensitive {
100 u32::from_ne_bytes([0, *first, *second, *third])
101 } else {
102 u32::from_ne_bytes([
103 0,
104 first.to_ascii_uppercase(),
105 second.to_ascii_lowercase(),
106 third.to_ascii_lowercase(),
107 ])
108 };
109 const WEEKDAYS: [u32; 12] = [
110 u32::from_ne_bytes([0, b'J', b'a', b'n']),
111 u32::from_ne_bytes([0, b'F', b'e', b'b']),
112 u32::from_ne_bytes([0, b'M', b'a', b'r']),
113 u32::from_ne_bytes([0, b'A', b'p', b'r']),
114 u32::from_ne_bytes([0, b'M', b'a', b'y']),
115 u32::from_ne_bytes([0, b'J', b'u', b'n']),
116 u32::from_ne_bytes([0, b'J', b'u', b'l']),
117 u32::from_ne_bytes([0, b'A', b'u', b'g']),
118 u32::from_ne_bytes([0, b'S', b'e', b'p']),
119 u32::from_ne_bytes([0, b'O', b'c', b't']),
120 u32::from_ne_bytes([0, b'N', b'o', b'v']),
121 u32::from_ne_bytes([0, b'D', b'e', b'c']),
122 ];
123
124 let bitmask = ((WEEKDAYS[0] == byte) as u32) << 1
125 | ((WEEKDAYS[1] == byte) as u32) << 2
126 | ((WEEKDAYS[2] == byte) as u32) << 3
127 | ((WEEKDAYS[3] == byte) as u32) << 4
128 | ((WEEKDAYS[4] == byte) as u32) << 5
129 | ((WEEKDAYS[5] == byte) as u32) << 6
130 | ((WEEKDAYS[6] == byte) as u32) << 7
131 | ((WEEKDAYS[7] == byte) as u32) << 8
132 | ((WEEKDAYS[8] == byte) as u32) << 9
133 | ((WEEKDAYS[9] == byte) as u32) << 10
134 | ((WEEKDAYS[10] == byte) as u32) << 11
135 | ((WEEKDAYS[11] == byte) as u32) << 12;
136 if bitmask == 0 {
137 return None;
138 }
139 let index = if cfg!(target_endian = "little") {
140 bitmask.trailing_zeros() as u8
141 } else {
142 31 - bitmask.leading_zeros() as u8
143 };
144
145 let month = unsafe { Month::from_number(NonZero::new(index)?).unwrap_unchecked() };
149
150 if modifiers.repr == modifier::MonthRepr::Short {
153 return Some(ParsedItem(rest, month));
154 }
155
156 let expected_remaining = match month {
157 January => b"uary".as_slice(),
158 February => b"ruary".as_slice(),
159 March => b"ch".as_slice(),
160 April => b"il".as_slice(),
161 May => b"".as_slice(),
162 June => b"e".as_slice(),
163 July => b"y".as_slice(),
164 August => b"ust".as_slice(),
165 September => b"tember".as_slice(),
166 October => b"ober".as_slice(),
167 November | December => b"ember".as_slice(),
168 };
169
170 if modifiers.case_sensitive {
171 rest.strip_prefix(expected_remaining)
172 .map(|remaining| ParsedItem(remaining, month))
173 } else {
174 let (head, tail) = rest.split_at_checked(expected_remaining.len())?;
175 core::iter::zip(head, expected_remaining)
176 .all(|(a, b)| a.eq_ignore_ascii_case(b))
177 .then_some(ParsedItem(tail, month))
178 }
179 }
180 }
181}
182
183pub(crate) fn parse_week_number(
185 input: &[u8],
186 modifiers: modifier::WeekNumber,
187) -> Option<ParsedItem<'_, u8>> {
188 exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
189}
190
191#[inline]
193pub(crate) fn parse_weekday(
194 input: &[u8],
195 modifiers: modifier::Weekday,
196) -> Option<ParsedItem<'_, Weekday>> {
197 match modifiers.repr {
198 modifier::WeekdayRepr::Long | modifier::WeekdayRepr::Short => {
199 let [first, second, third, rest @ ..] = input else {
200 return None;
201 };
202 let byte = if modifiers.case_sensitive {
203 u32::from_ne_bytes([0, *first, *second, *third])
204 } else {
205 u32::from_ne_bytes([
206 0,
207 first.to_ascii_uppercase(),
208 second.to_ascii_lowercase(),
209 third.to_ascii_lowercase(),
210 ])
211 };
212 const WEEKDAYS: [u32; 7] = [
213 u32::from_ne_bytes([0, b'M', b'o', b'n']),
214 u32::from_ne_bytes([0, b'T', b'u', b'e']),
215 u32::from_ne_bytes([0, b'W', b'e', b'd']),
216 u32::from_ne_bytes([0, b'T', b'h', b'u']),
217 u32::from_ne_bytes([0, b'F', b'r', b'i']),
218 u32::from_ne_bytes([0, b'S', b'a', b't']),
219 u32::from_ne_bytes([0, b'S', b'u', b'n']),
220 ];
221
222 let bitmask = ((WEEKDAYS[0] == byte) as u32)
223 | ((WEEKDAYS[1] == byte) as u32) << 1
224 | ((WEEKDAYS[2] == byte) as u32) << 2
225 | ((WEEKDAYS[3] == byte) as u32) << 3
226 | ((WEEKDAYS[4] == byte) as u32) << 4
227 | ((WEEKDAYS[5] == byte) as u32) << 5
228 | ((WEEKDAYS[6] == byte) as u32) << 6;
229 if bitmask == 0 {
230 return None;
231 }
232 let index = if cfg!(target_endian = "little") {
233 bitmask.trailing_zeros()
234 } else {
235 31 - bitmask.leading_zeros()
236 };
237
238 if index > 6 {
239 return None;
240 }
241 let weekday = unsafe { core::mem::transmute::<u8, Weekday>(index.truncate()) };
245
246 if modifiers.repr == modifier::WeekdayRepr::Short {
249 return Some(ParsedItem(rest, weekday));
250 }
251
252 let expected_remaining = match weekday {
253 Weekday::Monday | Weekday::Friday | Weekday::Sunday => b"day".as_slice(),
254 Weekday::Tuesday => b"sday".as_slice(),
255 Weekday::Wednesday => b"nesday".as_slice(),
256 Weekday::Thursday => b"rsday".as_slice(),
257 Weekday::Saturday => b"urday".as_slice(),
258 };
259
260 if modifiers.case_sensitive {
261 rest.strip_prefix(expected_remaining)
262 .map(|remaining| ParsedItem(remaining, weekday))
263 } else {
264 let (head, tail) = rest.split_at_checked(expected_remaining.len())?;
265 core::iter::zip(head, expected_remaining)
266 .all(|(a, b)| a.eq_ignore_ascii_case(b))
267 .then_some(ParsedItem(tail, weekday))
268 }
269 }
270 modifier::WeekdayRepr::Sunday | modifier::WeekdayRepr::Monday => {
271 let [digit, rest @ ..] = input else {
272 return None;
273 };
274 let mut digit = digit
275 .wrapping_sub(b'0')
276 .wrapping_sub(u8::from(modifiers.one_indexed));
277 if digit > 6 {
278 return None;
279 }
280
281 if modifiers.repr == modifier::WeekdayRepr::Sunday {
282 digit = (digit + 6) % 7;
284 }
285 let weekday = unsafe { core::mem::transmute::<u8, Weekday>(digit) };
287 Some(ParsedItem(rest, weekday))
288 }
289 }
290}
291
292#[inline]
294pub(crate) fn parse_ordinal(
295 input: &[u8],
296 modifiers: modifier::Ordinal,
297) -> Option<ParsedItem<'_, NonZero<u16>>> {
298 exactly_n_digits_padded::<3, _>(modifiers.padding)(input)
299 .and_then(|parsed| parsed.flat_map(NonZero::new))
300}
301
302#[inline]
304pub(crate) fn parse_day(
305 input: &[u8],
306 modifiers: modifier::Day,
307) -> Option<ParsedItem<'_, NonZero<u8>>> {
308 exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
309 .and_then(|parsed| parsed.flat_map(NonZero::new))
310}
311
312#[inline]
314pub(crate) fn parse_hour(input: &[u8], modifiers: modifier::Hour) -> Option<ParsedItem<'_, u8>> {
315 exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
316}
317
318#[inline]
320pub(crate) fn parse_minute(
321 input: &[u8],
322 modifiers: modifier::Minute,
323) -> Option<ParsedItem<'_, u8>> {
324 exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
325}
326
327#[inline]
329pub(crate) fn parse_second(
330 input: &[u8],
331 modifiers: modifier::Second,
332) -> Option<ParsedItem<'_, u8>> {
333 exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
334}
335
336#[inline]
338pub(crate) fn parse_period(
339 input: &[u8],
340 modifiers: modifier::Period,
341) -> Option<ParsedItem<'_, Period>> {
342 let [first, second, rest @ ..] = input else {
343 return None;
344 };
345 let mut first = *first;
346 let mut second = *second;
347
348 if modifiers.is_uppercase && modifiers.case_sensitive {
349 match [first, second].as_slice() {
350 b"AM" => Some(ParsedItem(rest, Period::Am)),
351 b"PM" => Some(ParsedItem(rest, Period::Pm)),
352 _ => None,
353 }
354 } else {
355 first = first.to_ascii_lowercase();
356 second = second.to_ascii_lowercase();
357
358 match &[first, second] {
359 b"am" => Some(ParsedItem(rest, Period::Am)),
360 b"pm" => Some(ParsedItem(rest, Period::Pm)),
361 _ => None,
362 }
363 }
364}
365
366pub(crate) fn parse_subsecond(
368 input: &[u8],
369 modifiers: modifier::Subsecond,
370) -> Option<ParsedItem<'_, u32>> {
371 use modifier::SubsecondDigits::*;
372 Some(match modifiers.digits {
373 One => ExactlyNDigits::<1>::parse(input)?.map(|v| v.extend::<u32>() * 100_000_000),
374 Two => ExactlyNDigits::<2>::parse(input)?.map(|v| v.extend::<u32>() * 10_000_000),
375 Three => ExactlyNDigits::<3>::parse(input)?.map(|v| v.extend::<u32>() * 1_000_000),
376 Four => ExactlyNDigits::<4>::parse(input)?.map(|v| v.extend::<u32>() * 100_000),
377 Five => ExactlyNDigits::<5>::parse(input)?.map(|v| v * 10_000),
378 Six => ExactlyNDigits::<6>::parse(input)?.map(|v| v * 1_000),
379 Seven => ExactlyNDigits::<7>::parse(input)?.map(|v| v * 100),
380 Eight => ExactlyNDigits::<8>::parse(input)?.map(|v| v * 10),
381 Nine => ExactlyNDigits::<9>::parse(input)?,
382 OneOrMore => {
383 let ParsedItem(mut input, mut value) =
384 any_digit(input)?.map(|v| (v - b'0').extend::<u32>() * 100_000_000);
385
386 let mut multiplier = 10_000_000;
387 while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
388 value += (digit - b'0').extend::<u32>() * multiplier;
389 input = new_input;
390 multiplier /= 10;
391 }
392
393 ParsedItem(input, value)
394 }
395 })
396}
397
398#[inline]
402pub(crate) fn parse_offset_hour(
403 input: &[u8],
404 modifiers: modifier::OffsetHour,
405) -> Option<ParsedItem<'_, (i8, bool)>> {
406 let ParsedItem(input, sign) = opt(sign)(input);
407 let ParsedItem(input, hour) = exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?;
408 match sign {
409 Some(Sign::Negative) => Some(ParsedItem(input, (-hour.cast_signed(), true))),
410 None if modifiers.sign_is_mandatory => None,
411 _ => Some(ParsedItem(input, (hour.cast_signed(), false))),
412 }
413}
414
415#[inline]
417pub(crate) fn parse_offset_minute(
418 input: &[u8],
419 modifiers: modifier::OffsetMinute,
420) -> Option<ParsedItem<'_, i8>> {
421 Some(
422 exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
423 .map(|offset_minute| offset_minute.cast_signed()),
424 )
425}
426
427#[inline]
429pub(crate) fn parse_offset_second(
430 input: &[u8],
431 modifiers: modifier::OffsetSecond,
432) -> Option<ParsedItem<'_, i8>> {
433 Some(
434 exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
435 .map(|offset_second| offset_second.cast_signed()),
436 )
437}
438
439#[inline]
441pub(crate) fn parse_ignore(
442 input: &[u8],
443 modifiers: modifier::Ignore,
444) -> Option<ParsedItem<'_, ()>> {
445 let modifier::Ignore { count } = modifiers;
446 let input = input.get((count.get().extend())..)?;
447 Some(ParsedItem(input, ()))
448}
449
450pub(crate) fn parse_unix_timestamp(
452 input: &[u8],
453 modifiers: modifier::UnixTimestamp,
454) -> Option<ParsedItem<'_, i128>> {
455 let ParsedItem(input, sign) = opt(sign)(input);
456 let ParsedItem(input, nano_timestamp) = match modifiers.precision {
457 modifier::UnixTimestampPrecision::Second => {
458 n_to_m_digits::<1, 14, u128>(input)?.map(|val| val * Nanosecond::per_t::<u128>(Second))
459 }
460 modifier::UnixTimestampPrecision::Millisecond => n_to_m_digits::<1, 17, u128>(input)?
461 .map(|val| val * Nanosecond::per_t::<u128>(Millisecond)),
462 modifier::UnixTimestampPrecision::Microsecond => n_to_m_digits::<1, 20, u128>(input)?
463 .map(|val| val * Nanosecond::per_t::<u128>(Microsecond)),
464 modifier::UnixTimestampPrecision::Nanosecond => n_to_m_digits::<1, 23, _>(input)?,
465 };
466
467 match sign {
468 Some(Sign::Negative) => Some(ParsedItem(input, -nano_timestamp.cast_signed())),
469 None if modifiers.sign_is_mandatory => None,
470 _ => Some(ParsedItem(input, nano_timestamp.cast_signed())),
471 }
472}
473
474#[inline]
478pub(crate) fn parse_end(input: &[u8], end: modifier::End) -> Option<ParsedItem<'_, ()>> {
479 let modifier::End { trailing_input } = end;
480
481 if trailing_input == modifier::TrailingInput::Discard || input.is_empty() {
482 Some(ParsedItem(b"", ()))
483 } else {
484 None
485 }
486}