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