1use core::num::{NonZeroU16, NonZeroU8};
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
199pub(crate) fn parse_ordinal(
201 input: &[u8],
202 modifiers: modifier::Ordinal,
203) -> Option<ParsedItem<'_, NonZeroU16>> {
204 exactly_n_digits_padded::<3, _>(modifiers.padding)(input)
205}
206
207pub(crate) fn parse_day(
209 input: &[u8],
210 modifiers: modifier::Day,
211) -> Option<ParsedItem<'_, NonZeroU8>> {
212 exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
213}
214
215#[derive(Debug, Clone, Copy, PartialEq, Eq)]
217pub(crate) enum Period {
218 #[allow(clippy::missing_docs_in_private_items)]
219 Am,
220 #[allow(clippy::missing_docs_in_private_items)]
221 Pm,
222}
223
224pub(crate) fn parse_hour(input: &[u8], modifiers: modifier::Hour) -> Option<ParsedItem<'_, u8>> {
226 exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
227}
228
229pub(crate) fn parse_minute(
231 input: &[u8],
232 modifiers: modifier::Minute,
233) -> Option<ParsedItem<'_, u8>> {
234 exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
235}
236
237pub(crate) fn parse_second(
239 input: &[u8],
240 modifiers: modifier::Second,
241) -> Option<ParsedItem<'_, u8>> {
242 exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
243}
244
245pub(crate) fn parse_period(
247 input: &[u8],
248 modifiers: modifier::Period,
249) -> Option<ParsedItem<'_, Period>> {
250 first_match(
251 if modifiers.is_uppercase {
252 [
253 (b"AM".as_slice(), Period::Am),
254 (b"PM".as_slice(), Period::Pm),
255 ]
256 } else {
257 [
258 (b"am".as_slice(), Period::Am),
259 (b"pm".as_slice(), Period::Pm),
260 ]
261 },
262 modifiers.case_sensitive,
263 )(input)
264}
265
266pub(crate) fn parse_subsecond(
268 input: &[u8],
269 modifiers: modifier::Subsecond,
270) -> Option<ParsedItem<'_, u32>> {
271 use modifier::SubsecondDigits::*;
272 Some(match modifiers.digits {
273 One => exactly_n_digits::<1, u32>(input)?.map(|v| v * 100_000_000),
274 Two => exactly_n_digits::<2, u32>(input)?.map(|v| v * 10_000_000),
275 Three => exactly_n_digits::<3, u32>(input)?.map(|v| v * 1_000_000),
276 Four => exactly_n_digits::<4, u32>(input)?.map(|v| v * 100_000),
277 Five => exactly_n_digits::<5, u32>(input)?.map(|v| v * 10_000),
278 Six => exactly_n_digits::<6, u32>(input)?.map(|v| v * 1_000),
279 Seven => exactly_n_digits::<7, u32>(input)?.map(|v| v * 100),
280 Eight => exactly_n_digits::<8, u32>(input)?.map(|v| v * 10),
281 Nine => exactly_n_digits::<9, _>(input)?,
282 OneOrMore => {
283 let ParsedItem(mut input, mut value) =
284 any_digit(input)?.map(|v| (v - b'0').extend::<u32>() * 100_000_000);
285
286 let mut multiplier = 10_000_000;
287 while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
288 value += (digit - b'0').extend::<u32>() * multiplier;
289 input = new_input;
290 multiplier /= 10;
291 }
292
293 ParsedItem(input, value)
294 }
295 })
296}
297
298pub(crate) fn parse_offset_hour(
302 input: &[u8],
303 modifiers: modifier::OffsetHour,
304) -> Option<ParsedItem<'_, (i8, bool)>> {
305 let ParsedItem(input, sign) = opt(sign)(input);
306 let ParsedItem(input, hour) = exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?;
307 match sign {
308 Some(b'-') => Some(ParsedItem(input, (-hour.cast_signed(), true))),
309 None if modifiers.sign_is_mandatory => None,
310 _ => Some(ParsedItem(input, (hour.cast_signed(), false))),
311 }
312}
313
314pub(crate) fn parse_offset_minute(
316 input: &[u8],
317 modifiers: modifier::OffsetMinute,
318) -> Option<ParsedItem<'_, i8>> {
319 Some(
320 exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
321 .map(|offset_minute| offset_minute.cast_signed()),
322 )
323}
324
325pub(crate) fn parse_offset_second(
327 input: &[u8],
328 modifiers: modifier::OffsetSecond,
329) -> Option<ParsedItem<'_, i8>> {
330 Some(
331 exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
332 .map(|offset_second| offset_second.cast_signed()),
333 )
334}
335
336pub(crate) fn parse_ignore(
338 input: &[u8],
339 modifiers: modifier::Ignore,
340) -> Option<ParsedItem<'_, ()>> {
341 let modifier::Ignore { count } = modifiers;
342 let input = input.get((count.get().extend())..)?;
343 Some(ParsedItem(input, ()))
344}
345
346pub(crate) fn parse_unix_timestamp(
348 input: &[u8],
349 modifiers: modifier::UnixTimestamp,
350) -> Option<ParsedItem<'_, i128>> {
351 let ParsedItem(input, sign) = opt(sign)(input);
352 let ParsedItem(input, nano_timestamp) = match modifiers.precision {
353 modifier::UnixTimestampPrecision::Second => n_to_m_digits::<1, 14, u128>(input)?
354 .map(|val| val * Nanosecond::per(Second).extend::<u128>()),
355 modifier::UnixTimestampPrecision::Millisecond => n_to_m_digits::<1, 17, u128>(input)?
356 .map(|val| val * Nanosecond::per(Millisecond).extend::<u128>()),
357 modifier::UnixTimestampPrecision::Microsecond => n_to_m_digits::<1, 20, u128>(input)?
358 .map(|val| val * Nanosecond::per(Microsecond).extend::<u128>()),
359 modifier::UnixTimestampPrecision::Nanosecond => n_to_m_digits::<1, 23, _>(input)?,
360 };
361
362 match sign {
363 Some(b'-') => Some(ParsedItem(input, -nano_timestamp.cast_signed())),
364 None if modifiers.sign_is_mandatory => None,
365 _ => Some(ParsedItem(input, nano_timestamp.cast_signed())),
366 }
367}
368
369pub(crate) const fn parse_end(input: &[u8], end: modifier::End) -> Option<ParsedItem<'_, ()>> {
372 let modifier::End {} = end;
373
374 if input.is_empty() {
375 Some(ParsedItem(input, ()))
376 } else {
377 None
378 }
379}