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(
20 input: &[u8],
21 modifiers: modifier::Year,
22) -> Option<ParsedItem<'_, (i32, bool)>> {
23 match modifiers.repr {
24 modifier::YearRepr::Full => {
25 let ParsedItem(input, sign) = opt(sign)(input);
26
27 if let Some(sign) = sign {
28 #[cfg(not(feature = "large-dates"))]
29 let ParsedItem(input, year) =
30 exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
31 #[cfg(feature = "large-dates")]
32 let ParsedItem(input, year) =
33 n_to_m_digits_padded::<4, 6, u32>(modifiers.padding)(input)?;
34
35 Some(if sign == b'-' {
36 ParsedItem(input, (-year.cast_signed(), true))
37 } else {
38 ParsedItem(input, (year.cast_signed(), false))
39 })
40 } else if modifiers.sign_is_mandatory {
41 None
42 } else {
43 let ParsedItem(input, year) =
44 exactly_n_digits_padded::<4, u32>(modifiers.padding)(input)?;
45 Some(ParsedItem(input, (year.cast_signed(), false)))
46 }
47 }
48 modifier::YearRepr::Century => {
49 let ParsedItem(input, sign) = opt(sign)(input);
50
51 if let Some(sign) = sign {
52 #[cfg(not(feature = "large-dates"))]
53 let ParsedItem(input, year) =
54 exactly_n_digits_padded::<2, u32>(modifiers.padding)(input)?;
55 #[cfg(feature = "large-dates")]
56 let ParsedItem(input, year) =
57 n_to_m_digits_padded::<2, 4, u32>(modifiers.padding)(input)?;
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 exactly_n_digits_padded::<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#[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
226pub(crate) fn parse_hour(input: &[u8], modifiers: modifier::Hour) -> Option<ParsedItem<'_, u8>> {
228 exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
229}
230
231pub(crate) fn parse_minute(
233 input: &[u8],
234 modifiers: modifier::Minute,
235) -> Option<ParsedItem<'_, u8>> {
236 exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
237}
238
239pub(crate) fn parse_second(
241 input: &[u8],
242 modifiers: modifier::Second,
243) -> Option<ParsedItem<'_, u8>> {
244 exactly_n_digits_padded::<2, _>(modifiers.padding)(input)
245}
246
247pub(crate) fn parse_period(
249 input: &[u8],
250 modifiers: modifier::Period,
251) -> Option<ParsedItem<'_, Period>> {
252 first_match(
253 if modifiers.is_uppercase {
254 [
255 (b"AM".as_slice(), Period::Am),
256 (b"PM".as_slice(), Period::Pm),
257 ]
258 } else {
259 [
260 (b"am".as_slice(), Period::Am),
261 (b"pm".as_slice(), Period::Pm),
262 ]
263 },
264 modifiers.case_sensitive,
265 )(input)
266}
267
268pub(crate) fn parse_subsecond(
270 input: &[u8],
271 modifiers: modifier::Subsecond,
272) -> Option<ParsedItem<'_, u32>> {
273 use modifier::SubsecondDigits::*;
274 Some(match modifiers.digits {
275 One => exactly_n_digits::<1, u32>(input)?.map(|v| v * 100_000_000),
276 Two => exactly_n_digits::<2, u32>(input)?.map(|v| v * 10_000_000),
277 Three => exactly_n_digits::<3, u32>(input)?.map(|v| v * 1_000_000),
278 Four => exactly_n_digits::<4, u32>(input)?.map(|v| v * 100_000),
279 Five => exactly_n_digits::<5, u32>(input)?.map(|v| v * 10_000),
280 Six => exactly_n_digits::<6, u32>(input)?.map(|v| v * 1_000),
281 Seven => exactly_n_digits::<7, u32>(input)?.map(|v| v * 100),
282 Eight => exactly_n_digits::<8, u32>(input)?.map(|v| v * 10),
283 Nine => exactly_n_digits::<9, _>(input)?,
284 OneOrMore => {
285 let ParsedItem(mut input, mut value) =
286 any_digit(input)?.map(|v| (v - b'0').extend::<u32>() * 100_000_000);
287
288 let mut multiplier = 10_000_000;
289 while let Some(ParsedItem(new_input, digit)) = any_digit(input) {
290 value += (digit - b'0').extend::<u32>() * multiplier;
291 input = new_input;
292 multiplier /= 10;
293 }
294
295 ParsedItem(input, value)
296 }
297 })
298}
299pub(crate) fn parse_offset_hour(
306 input: &[u8],
307 modifiers: modifier::OffsetHour,
308) -> Option<ParsedItem<'_, (i8, bool)>> {
309 let ParsedItem(input, sign) = opt(sign)(input);
310 let ParsedItem(input, hour) = exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?;
311 match sign {
312 Some(b'-') => Some(ParsedItem(input, (-hour.cast_signed(), true))),
313 None if modifiers.sign_is_mandatory => None,
314 _ => Some(ParsedItem(input, (hour.cast_signed(), false))),
315 }
316}
317
318pub(crate) fn parse_offset_minute(
320 input: &[u8],
321 modifiers: modifier::OffsetMinute,
322) -> Option<ParsedItem<'_, i8>> {
323 Some(
324 exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
325 .map(|offset_minute| offset_minute.cast_signed()),
326 )
327}
328
329pub(crate) fn parse_offset_second(
331 input: &[u8],
332 modifiers: modifier::OffsetSecond,
333) -> Option<ParsedItem<'_, i8>> {
334 Some(
335 exactly_n_digits_padded::<2, u8>(modifiers.padding)(input)?
336 .map(|offset_second| offset_second.cast_signed()),
337 )
338}
339pub(crate) fn parse_ignore(
343 input: &[u8],
344 modifiers: modifier::Ignore,
345) -> Option<ParsedItem<'_, ()>> {
346 let modifier::Ignore { count } = modifiers;
347 let input = input.get((count.get().extend())..)?;
348 Some(ParsedItem(input, ()))
349}
350
351pub(crate) fn parse_unix_timestamp(
353 input: &[u8],
354 modifiers: modifier::UnixTimestamp,
355) -> Option<ParsedItem<'_, i128>> {
356 let ParsedItem(input, sign) = opt(sign)(input);
357 let ParsedItem(input, nano_timestamp) = match modifiers.precision {
358 modifier::UnixTimestampPrecision::Second => n_to_m_digits::<1, 14, u128>(input)?
359 .map(|val| val * Nanosecond::per(Second).extend::<u128>()),
360 modifier::UnixTimestampPrecision::Millisecond => n_to_m_digits::<1, 17, u128>(input)?
361 .map(|val| val * Nanosecond::per(Millisecond).extend::<u128>()),
362 modifier::UnixTimestampPrecision::Microsecond => n_to_m_digits::<1, 20, u128>(input)?
363 .map(|val| val * Nanosecond::per(Microsecond).extend::<u128>()),
364 modifier::UnixTimestampPrecision::Nanosecond => n_to_m_digits::<1, 23, _>(input)?,
365 };
366
367 match sign {
368 Some(b'-') => Some(ParsedItem(input, -nano_timestamp.cast_signed())),
369 None if modifiers.sign_is_mandatory => None,
370 _ => Some(ParsedItem(input, nano_timestamp.cast_signed())),
371 }
372}
373
374pub(crate) const fn parse_end(input: &[u8], end: modifier::End) -> Option<ParsedItem<'_, ()>> {
377 let modifier::End {} = end;
378
379 if input.is_empty() {
380 Some(ParsedItem(input, ()))
381 } else {
382 None
383 }
384}