1use chrono::{Datelike, Local, NaiveDate};
2use nom::{
3 branch::alt,
4 bytes::complete::{tag, take},
5 character::complete::space1,
6 combinator::map_res,
7 sequence::{separated_pair, tuple},
8};
9
10use crate::{error::Error, types::IResult};
11
12pub fn numeric_date_parts_separator(input: &str) -> IResult<&str, ()> {
21 let (input, _) = alt((tag("/"), tag("-"), tag("."), space1))(input)?;
22
23 Ok((input, ()))
24}
25
26pub fn dd(input: &str) -> IResult<&str, u32> {
34 let (input, dd) = alt((
35 map_res(take(2_u8), |s: &str| s.parse()),
36 map_res(take(1_u8), |s: &str| s.parse()),
37 ))(input)?;
38
39 if dd == 0 || dd > 31 {
40 return Err(nom::Err::Error(Error::DayOutOfRange));
41 }
42 Ok((input, dd))
43}
44
45pub fn dd_only(input: &str) -> IResult<&str, NaiveDate> {
64 let (input, day) = dd(input)?;
65 let now = Local::now();
66 let (month, year) = (now.month(), now.year());
67
68 Ok((
69 input,
70 NaiveDate::from_ymd_opt(year, month, day).ok_or(nom::Err::Error(Error::NonExistentDate))?,
71 ))
72}
73
74pub fn mm(input: &str) -> IResult<&str, u32> {
79 let (input, mm) = alt((
80 map_res(take(2_u8), |s: &str| s.parse()),
81 map_res(take(1_u8), |s: &str| s.parse()),
82 ))(input)?;
83 if mm == 0 || mm > 12 {
84 return Err(nom::Err::Error(Error::MonthOutOfRange));
85 }
86
87 Ok((input, mm))
88}
89
90pub fn dd_mm(input: &str) -> IResult<&str, (u32, u32)> {
93 separated_pair(dd, numeric_date_parts_separator, mm)(input)
94}
95
96pub fn dd_mm_only(input: &str) -> IResult<&str, NaiveDate> {
117 let (input, (day, month)) = dd_mm(input)?;
118 let year = Local::now().year();
119
120 Ok((
121 input,
122 NaiveDate::from_ymd_opt(year, month, day).ok_or(nom::Err::Error(Error::NonExistentDate))?,
123 ))
124}
125
126pub fn mm_dd(input: &str) -> IResult<&str, (u32, u32)> {
129 separated_pair(mm, numeric_date_parts_separator, dd)(input)
130}
131
132pub fn mm_dd_only(input: &str) -> IResult<&str, NaiveDate> {
151 let (input, (month, day)) = mm_dd(input)?;
152
153 Ok((
154 input,
155 NaiveDate::from_ymd_opt(Local::now().year(), month, day)
156 .ok_or(nom::Err::Error(Error::NonExistentDate))?,
157 ))
158}
159
160pub fn y4(input: &str) -> IResult<&str, u32> {
164 map_res(take(4_u8), |s: &str| s.parse::<u32>())(input)
165}
166
167pub fn y4_mm_dd(input: &str) -> IResult<&str, NaiveDate> {
185 let (input, (y4, (), mm, (), dd)) = tuple((
186 y4,
187 numeric_date_parts_separator,
188 mm,
189 numeric_date_parts_separator,
190 dd,
191 ))(input)?;
192
193 Ok((
194 input,
195 NaiveDate::from_ymd_opt(y4 as i32, mm, dd)
196 .ok_or(nom::Err::Error(Error::NonExistentDate))?,
197 ))
198}
199
200pub fn dd_mm_y4(input: &str) -> IResult<&str, NaiveDate> {
218 let (input, (dd, (), mm, (), y4)) = tuple((
219 dd,
220 numeric_date_parts_separator,
221 mm,
222 numeric_date_parts_separator,
223 y4,
224 ))(input)?;
225
226 Ok((
227 input,
228 NaiveDate::from_ymd_opt(y4 as i32, mm, dd)
229 .ok_or(nom::Err::Error(Error::NonExistentDate))?,
230 ))
231}
232
233pub fn mm_dd_y4(input: &str) -> IResult<&str, NaiveDate> {
251 let (input, (mm, (), dd, (), y4)) = tuple((
252 mm,
253 numeric_date_parts_separator,
254 dd,
255 numeric_date_parts_separator,
256 y4,
257 ))(input)?;
258
259 Ok((
260 input,
261 NaiveDate::from_ymd_opt(y4 as i32, mm, dd)
262 .ok_or(nom::Err::Error(Error::NonExistentDate))?,
263 ))
264}
265
266#[cfg(test)]
267mod tests {
268 use chrono::Local;
269 use nom::error::ErrorKind;
270 use pretty_assertions::assert_eq;
271 use rstest::rstest;
272
273 use super::*;
274
275 #[rstest]
276 #[case("9", Ok(("", 9)))]
277 #[case("09", Ok(("", 9)))]
278 #[case("31", Ok(("", 31)))]
279 #[case("00", Err(nom::Err::Error(Error::DayOutOfRange)))]
280 #[case("42", Err(nom::Err::Error(Error::DayOutOfRange)))]
281 fn test_dd(#[case] input: &str, #[case] expected: IResult<&str, u32>) {
282 assert_eq!(dd(input), expected);
283 }
284
285 #[rstest]
286 #[case("9", Ok(("", Local::now().date_naive().with_day(9).unwrap())))]
287 #[case("09", Ok(("", Local::now().date_naive().with_day(9).unwrap())))]
288 #[case("31", Local::now().date_naive().with_day(31).ok_or(nom::Err::Error(Error::NonExistentDate)).map(|d| ("", d)))]
289 #[case("00", Err(nom::Err::Error(Error::DayOutOfRange)))]
290 #[case("42", Err(nom::Err::Error(Error::DayOutOfRange)))]
291 fn test_dd_only(#[case] input: &str, #[case] expected: IResult<&str, NaiveDate>) {
292 assert_eq!(dd_only(input), expected)
293 }
294
295 #[rstest]
296 #[case("9", Ok(("", 9)))]
297 #[case("09", Ok(("", 9)))]
298 #[case("12", Ok(("", 12)))]
299 #[case("00", Err(nom::Err::Error(Error::MonthOutOfRange)))]
300 #[case("13", Err(nom::Err::Error(Error::MonthOutOfRange)))]
301 fn test_mm(#[case] input: &str, #[case] expected: IResult<&str, u32>) {
302 assert_eq!(mm(input), expected);
303 }
304
305 #[rstest]
306 #[case("3/9", Ok(("", Local::now().date_naive().with_day(3).unwrap().with_month(9).unwrap())))]
307 #[case("03-09", Ok(("", Local::now().date_naive().with_day(3).unwrap().with_month(9).unwrap())))]
308 #[case("03/12", Ok(("", Local::now().date_naive().with_day(3).unwrap().with_month(12).unwrap())))]
309 #[case("00", Err(nom::Err::Error(Error::DayOutOfRange)))]
310 #[case("42", Err(nom::Err::Error(Error::DayOutOfRange)))]
311 #[case("13.00", Err(nom::Err::Error(Error::MonthOutOfRange)))]
312 #[case("13\t13", Err(nom::Err::Error(Error::MonthOutOfRange)))]
313 fn test_dd_mm_only(#[case] input: &str, #[case] expected: IResult<&str, NaiveDate>) {
314 assert_eq!(dd_mm_only(input), expected);
315 }
316
317 #[rstest]
318 #[case("0042", Ok(("", 42)))]
319 #[case("2024", Ok(("", 2024)))]
320 #[case("42", Err(nom::Err::Error(Error::Nom("42", ErrorKind::Eof))))]
321 #[case("10001", Ok(("1", 1000)))]
322 fn test_y4(#[case] input: &str, #[case] expected: IResult<&str, u32>) {
323 assert_eq!(y4(input), expected);
324 }
325
326 #[rstest]
327 #[case("2024-06-13", Ok(("", NaiveDate::from_ymd_opt(2024, 6, 13).unwrap())))]
328 #[case("2024/06-13", Ok(("", NaiveDate::from_ymd_opt(2024, 6, 13).unwrap())))]
329 #[case("2024.06.13", Ok(("", NaiveDate::from_ymd_opt(2024, 6, 13).unwrap())))]
330 #[case("2024 06\t13", Ok(("", NaiveDate::from_ymd_opt(2024, 6, 13).unwrap())))]
331 #[case("2024/00/06", Err(nom::Err::Error(Error::MonthOutOfRange)))]
332 #[case("2024/13/06", Err(nom::Err::Error(Error::MonthOutOfRange)))]
333 #[case("2024/10/00", Err(nom::Err::Error(Error::DayOutOfRange)))]
334 #[case("2024/10/42", Err(nom::Err::Error(Error::DayOutOfRange)))]
335 fn test_y4_mm_dd(#[case] input: &str, #[case] expected: IResult<&str, NaiveDate>) {
336 assert_eq!(y4_mm_dd(input), expected);
337 }
338
339 #[rstest]
340 #[case("13-06-2024", Ok(("", NaiveDate::from_ymd_opt(2024, 6, 13).unwrap())))]
341 #[case("13/06-2024", Ok(("", NaiveDate::from_ymd_opt(2024, 6, 13).unwrap())))]
342 #[case("13.06.2024", Ok(("", NaiveDate::from_ymd_opt(2024, 6, 13).unwrap())))]
343 #[case("13 06\t2024", Ok(("", NaiveDate::from_ymd_opt(2024, 6, 13).unwrap())))]
344 #[case("00/10/2024", Err(nom::Err::Error(Error::DayOutOfRange)))]
345 #[case("42/10/2024", Err(nom::Err::Error(Error::DayOutOfRange)))]
346 #[case("06/00/2024", Err(nom::Err::Error(Error::MonthOutOfRange)))]
347 #[case("06/13/2024", Err(nom::Err::Error(Error::MonthOutOfRange)))]
348 #[case("31/02/2024", Err(nom::Err::Error(Error::NonExistentDate)))]
349 fn test_dd_mm_y4(#[case] input: &str, #[case] expected: IResult<&str, NaiveDate>) {
350 assert_eq!(dd_mm_y4(input), expected);
351 }
352
353 #[rstest]
354 #[case("06-13-2024", Ok(("", NaiveDate::from_ymd_opt(2024, 6, 13).unwrap())))]
355 #[case("06/13-2024", Ok(("", NaiveDate::from_ymd_opt(2024, 6, 13).unwrap())))]
356 #[case("06.13.2024", Ok(("", NaiveDate::from_ymd_opt(2024, 6, 13).unwrap())))]
357 #[case("06 13\t2024", Ok(("", NaiveDate::from_ymd_opt(2024, 6, 13).unwrap())))]
358 #[case("00/06/2024", Err(nom::Err::Error(Error::MonthOutOfRange)))]
359 #[case("13/06/2024", Err(nom::Err::Error(Error::MonthOutOfRange)))]
360 #[case("10/00/2024", Err(nom::Err::Error(Error::DayOutOfRange)))]
361 #[case("10/32/2024", Err(nom::Err::Error(Error::DayOutOfRange)))]
362 #[case("02/31/2024", Err(nom::Err::Error(Error::NonExistentDate)))]
363 fn test_mm_dd_y4(#[case] input: &str, #[case] expected: IResult<&str, NaiveDate>) {
364 assert_eq!(mm_dd_y4(input), expected)
365 }
366}