1use core::num::NonZero;
4
5use num_conv::prelude::*;
6
7use crate::convert::*;
8use crate::format_description::modifier;
9use crate::parsing::combinator::{
10 any_digit, exactly_n_digits, exactly_n_digits_padded, first_match, n_to_m_digits,
11 n_to_m_digits_padded, opt, sign,
12};
13use crate::parsing::ParsedItem;
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(if sign == b'-' {
35 ParsedItem(input, (-year.cast_signed(), true))
36 } else {
37 ParsedItem(input, (year.cast_signed(), false))
38 })
39 } else if modifiers.sign_is_mandatory {
40 None
41 } else {
42 let ParsedItem(input, year) =
43 exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
44 Some(ParsedItem(input, (year.cast_signed(), false)))
45 }
46 }
47 modifier::YearRepr::Century => {
48 let ParsedItem(input, sign) = opt(sign)(input);
49
50 if let Some(sign) = sign {
51 let ParsedItem(input, year) = if cfg!(feature = "large-dates")
52 && modifiers.range == modifier::YearRange::Extended
53 {
54 n_to_m_digits_padded::<2, 4, u32>(modifiers.padding)(input)?
55 } else {
56 exactly_n_digits_padded::<2, u32>(modifiers.padding)(input)?
57 };
58
59 Some(if sign == b'-' {
60 ParsedItem(input, (-year.cast_signed(), true))
61 } else {
62 ParsedItem(input, (year.cast_signed(), false))
63 })
64 } else if modifiers.sign_is_mandatory {
65 None
66 } else {
67 let ParsedItem(input, year) =
68 n_to_m_digits_padded::<1, 2, u32>(modifiers.padding)(input)?;
69 Some(ParsedItem(input, (year.cast_signed(), false)))
70 }
71 }
72 modifier::YearRepr::LastTwo => Some(
73 exactly_n_digits_padded::<2, u32>(modifiers.padding)(input)?
74 .map(|v| (v.cast_signed(), false)),
75 ),
76 }
77}
78
79pub(crate) fn parse_month(
81 input: &[u8],
82 modifiers: modifier::Month,
83) -> Option<ParsedItem<'_, Month>> {
84 use Month::*;
85 let ParsedItem(remaining, value) = first_match(
86 match modifiers.repr {
87 modifier::MonthRepr::Numerical => {
88 return exactly_n_digits_padded::<2, _>(modifiers.padding)(input)?
89 .flat_map(|n| Month::from_number(n).ok());
90 }
91 modifier::MonthRepr::Long => [
92 (b"January".as_slice(), January),
93 (b"February".as_slice(), February),
94 (b"March".as_slice(), March),
95 (b"April".as_slice(), April),
96 (b"May".as_slice(), May),
97 (b"June".as_slice(), June),
98 (b"July".as_slice(), July),
99 (b"August".as_slice(), August),
100 (b"September".as_slice(), September),
101 (b"October".as_slice(), October),
102 (b"November".as_slice(), November),
103 (b"December".as_slice(), December),
104 ],
105 modifier::MonthRepr::Short => [
106 (b"Jan".as_slice(), January),
107 (b"Feb".as_slice(), February),
108 (b"Mar".as_slice(), March),
109 (b"Apr".as_slice(), April),
110 (b"May".as_slice(), May),
111 (b"Jun".as_slice(), June),
112 (b"Jul".as_slice(), July),
113 (b"Aug".as_slice(), August),
114 (b"Sep".as_slice(), September),
115 (b"Oct".as_slice(), October),
116 (b"Nov".as_slice(), November),
117 (b"Dec".as_slice(), December),
118 ],
119 },
120 modifiers.case_sensitive,
121 )(input)?;
122 Some(ParsedItem(remaining, value))
123}
124
125pub(crate) fn parse_week_number(
127 input: &[u8],
128 modifiers: modifier::WeekNumber,
129) -> Option<ParsedItem<'_, u8>> {
130 exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
131}
132
133pub(crate) fn parse_weekday(
135 input: &[u8],
136 modifiers: modifier::Weekday,
137) -> Option<ParsedItem<'_, Weekday>> {
138 first_match(
139 match (modifiers.repr, modifiers.one_indexed) {
140 (modifier::WeekdayRepr::Short, _) => [
141 (b"Mon".as_slice(), Weekday::Monday),
142 (b"Tue".as_slice(), Weekday::Tuesday),
143 (b"Wed".as_slice(), Weekday::Wednesday),
144 (b"Thu".as_slice(), Weekday::Thursday),
145 (b"Fri".as_slice(), Weekday::Friday),
146 (b"Sat".as_slice(), Weekday::Saturday),
147 (b"Sun".as_slice(), Weekday::Sunday),
148 ],
149 (modifier::WeekdayRepr::Long, _) => [
150 (b"Monday".as_slice(), Weekday::Monday),
151 (b"Tuesday".as_slice(), Weekday::Tuesday),
152 (b"Wednesday".as_slice(), Weekday::Wednesday),
153 (b"Thursday".as_slice(), Weekday::Thursday),
154 (b"Friday".as_slice(), Weekday::Friday),
155 (b"Saturday".as_slice(), Weekday::Saturday),
156 (b"Sunday".as_slice(), Weekday::Sunday),
157 ],
158 (modifier::WeekdayRepr::Sunday, false) => [
159 (b"1".as_slice(), Weekday::Monday),
160 (b"2".as_slice(), Weekday::Tuesday),
161 (b"3".as_slice(), Weekday::Wednesday),
162 (b"4".as_slice(), Weekday::Thursday),
163 (b"5".as_slice(), Weekday::Friday),
164 (b"6".as_slice(), Weekday::Saturday),
165 (b"0".as_slice(), Weekday::Sunday),
166 ],
167 (modifier::WeekdayRepr::Sunday, true) => [
168 (b"2".as_slice(), Weekday::Monday),
169 (b"3".as_slice(), Weekday::Tuesday),
170 (b"4".as_slice(), Weekday::Wednesday),
171 (b"5".as_slice(), Weekday::Thursday),
172 (b"6".as_slice(), Weekday::Friday),
173 (b"7".as_slice(), Weekday::Saturday),
174 (b"1".as_slice(), Weekday::Sunday),
175 ],
176 (modifier::WeekdayRepr::Monday, false) => [
177 (b"0".as_slice(), Weekday::Monday),
178 (b"1".as_slice(), Weekday::Tuesday),
179 (b"2".as_slice(), Weekday::Wednesday),
180 (b"3".as_slice(), Weekday::Thursday),
181 (b"4".as_slice(), Weekday::Friday),
182 (b"5".as_slice(), Weekday::Saturday),
183 (b"6".as_slice(), Weekday::Sunday),
184 ],
185 (modifier::WeekdayRepr::Monday, true) => [
186 (b"1".as_slice(), Weekday::Monday),
187 (b"2".as_slice(), Weekday::Tuesday),
188 (b"3".as_slice(), Weekday::Wednesday),
189 (b"4".as_slice(), Weekday::Thursday),
190 (b"5".as_slice(), Weekday::Friday),
191 (b"6".as_slice(), Weekday::Saturday),
192 (b"7".as_slice(), Weekday::Sunday),
193 ],
194 },
195 modifiers.case_sensitive,
196 )(input)
197}
198
199#[inline]
201pub(crate) fn parse_ordinal(
202 input: &[u8],
203 modifiers: modifier::Ordinal,
204) -> Option<ParsedItem<'_, NonZero<u16>>> {
205 exactly_n_digits_padded::<3, _>(modifiers.padding)(input)
206}
207
208#[inline]
210pub(crate) fn parse_day(
211 input: &[u8],
212 modifiers: modifier::Day,
213) -> Option<ParsedItem<'_, NonZero<u8>>> {
214 exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
215}
216
217#[derive(Debug, Clone, Copy, PartialEq, Eq)]
219pub(crate) enum Period {
220 #[allow(clippy::missing_docs_in_private_items)]
221 Am,
222 #[allow(clippy::missing_docs_in_private_items)]
223 Pm,
224}
225
226#[inline]
228pub(crate) fn parse_hour(input: &[u8], modifiers: modifier::Hour) -> Option<ParsedItem<'_, u8>> {
229 exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
230}
231
232#[inline]
234pub(crate) fn parse_minute(
235 input: &[u8],
236 modifiers: modifier::Minute,
237) -> Option<ParsedItem<'_, u8>> {
238 exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
239}
240
241#[inline]
243pub(crate) fn parse_second(
244 input: &[u8],
245 modifiers: modifier::Second,
246) -> Option<ParsedItem<'_, u8>> {
247 exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
248}
249
250#[inline]
252pub(crate) fn parse_period(
253 input: &[u8],
254 modifiers: modifier::Period,
255) -> Option<ParsedItem<'_, Period>> {
256 first_match(
257 if modifiers.is_uppercase {
258 [
259 (b"AM".as_slice(), Period::Am),
260 (b"PM".as_slice(), Period::Pm),
261 ]
262 } else {
263 [
264 (b"am".as_slice(), Period::Am),
265 (b"pm".as_slice(), Period::Pm),
266 ]
267 },
268 modifiers.case_sensitive,
269 )(input)
270}
271
272pub(crate) fn parse_subsecond(
274 input: &[u8],
275 modifiers: modifier::Subsecond,
276) -> Option<ParsedItem<'_, u32>> {
277 use modifier::SubsecondDigits::*;
278 Some(match modifiers.digits {
279 One => exactly_n_digits::<1, u32>(input)?.map(|v| v * 100_000_000),
280 Two => exactly_n_digits::<2, u32>(input)?.map(|v| v * 10_000_000),
281 Three => exactly_n_digits::<3, u32>(input)?.map(|v| v * 1_000_000),
282 Four => exactly_n_digits::<4, u32>(input)?.map(|v| v * 100_000),
283 Five => exactly_n_digits::<5, u32>(input)?.map(|v| v * 10_000),
284 Six => exactly_n_digits::<6, u32>(input)?.map(|v| v * 1_000),
285 Seven => exactly_n_digits::<7, u32>(input)?.map(|v| v * 100),
286 Eight => exactly_n_digits::<8, u32>(input)?.map(|v| v * 10),
287 Nine => exactly_n_digits::<9, _>(input)?,
288 OneOrMore => {
289 let ParsedItem(mut input, mut value) =
290 any_digit(input)?.map(|v| (v - b'0').extend::<u32>() * 100_000_000);
291
292 let mut multiplier = 10_000_000;
293 while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
294 value += (digit - b'0').extend::<u32>() * multiplier;
295 input = new_input;
296 multiplier /= 10;
297 }
298
299 ParsedItem(input, value)
300 }
301 })
302}
303
304pub(crate) fn parse_offset_hour(
308 input: &[u8],
309 modifiers: modifier::OffsetHour,
310) -> Option<ParsedItem<'_, (i8, bool)>> {
311 let ParsedItem(input, sign) = opt(sign)(input);
312 let ParsedItem(input, hour) = exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?;
313 match sign {
314 Some(b'-') => Some(ParsedItem(input, (-hour.cast_signed(), true))),
315 None if modifiers.sign_is_mandatory => None,
316 _ => Some(ParsedItem(input, (hour.cast_signed(), false))),
317 }
318}
319
320#[inline]
322pub(crate) fn parse_offset_minute(
323 input: &[u8],
324 modifiers: modifier::OffsetMinute,
325) -> Option<ParsedItem<'_, i8>> {
326 Some(
327 exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
328 .map(|offset_minute| offset_minute.cast_signed()),
329 )
330}
331
332#[inline]
334pub(crate) fn parse_offset_second(
335 input: &[u8],
336 modifiers: modifier::OffsetSecond,
337) -> Option<ParsedItem<'_, i8>> {
338 Some(
339 exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
340 .map(|offset_second| offset_second.cast_signed()),
341 )
342}
343
344#[inline]
346pub(crate) fn parse_ignore(
347 input: &[u8],
348 modifiers: modifier::Ignore,
349) -> Option<ParsedItem<'_, ()>> {
350 let modifier::Ignore { count } = modifiers;
351 let input = input.get((count.get().extend())..)?;
352 Some(ParsedItem(input, ()))
353}
354
355pub(crate) fn parse_unix_timestamp(
357 input: &[u8],
358 modifiers: modifier::UnixTimestamp,
359) -> Option<ParsedItem<'_, i128>> {
360 let ParsedItem(input, sign) = opt(sign)(input);
361 let ParsedItem(input, nano_timestamp) = match modifiers.precision {
362 modifier::UnixTimestampPrecision::Second => {
363 n_to_m_digits::<1, 14, u128>(input)?.map(|val| val * Nanosecond::per_t::<u128>(Second))
364 }
365 modifier::UnixTimestampPrecision::Millisecond => n_to_m_digits::<1, 17, u128>(input)?
366 .map(|val| val * Nanosecond::per_t::<u128>(Millisecond)),
367 modifier::UnixTimestampPrecision::Microsecond => n_to_m_digits::<1, 20, u128>(input)?
368 .map(|val| val * Nanosecond::per_t::<u128>(Microsecond)),
369 modifier::UnixTimestampPrecision::Nanosecond => n_to_m_digits::<1, 23, _>(input)?,
370 };
371
372 match sign {
373 Some(b'-') => Some(ParsedItem(input, -nano_timestamp.cast_signed())),
374 None if modifiers.sign_is_mandatory => None,
375 _ => Some(ParsedItem(input, nano_timestamp.cast_signed())),
376 }
377}
378
379#[inline]
382pub(crate) const fn parse_end(input: &[u8], end: modifier::End) -> Option<ParsedItem<'_, ()>> {
383 let modifier::End {} = end;
384
385 if input.is_empty() {
386 Some(ParsedItem(input, ()))
387 } else {
388 None
389 }
390}