Skip to main content

rink_core/parsing/
datetime.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5use jiff::civil::{Date, Era, ISOWeekDate, Time, Weekday};
6use jiff::fmt::strtime::Meridiem;
7use jiff::tz::Offset;
8use jiff::{SignedDuration, Span, Unit, Zoned};
9
10use crate::ast::{DateMatch, DatePattern, DateToken};
11use crate::loader::Context;
12use crate::types::{BaseUnit, BigInt, BigRat, DateTime, Dimensionality, Number, Numeric, TimeZone};
13use std::iter::Peekable;
14
15fn parse_fixed(value: &str, digits: usize) -> Option<i32> {
16    if digits != 0 && value.len() != digits {
17        return None;
18    }
19    i32::from_str_radix(value, 10).ok()
20}
21
22fn parse_range(value: &str, digits: usize, range: std::ops::RangeInclusive<i32>) -> Option<i32> {
23    parse_fixed(value, digits).filter(|v| range.contains(v))
24}
25
26fn numeric_match(
27    tok: Option<&DateToken>,
28    name: &str,
29    digits: usize,
30    range: std::ops::RangeInclusive<i32>,
31) -> Result<i32, String> {
32    let tok = tok.ok_or_else(|| format!("Expected {}-digit {}, got eof", digits, name))?;
33
34    if let DateToken::Number(ref s, None) = tok {
35        parse_range(s, digits, range.clone())
36            .ok_or(format!("Expected {} in range {:?}, got {}", name, range, s))
37    } else {
38        Err(format!("Expected {}-digit {}, got {}", digits, name, tok))
39    }
40}
41
42#[derive(Debug, PartialEq, Eq)]
43pub(crate) struct Relative {
44    unit: Unit,
45    value: i32,
46}
47
48pub(crate) struct Fields {
49    era: Option<Era>,
50    year: Option<i32>,
51    iso_year: Option<i32>,
52    month: Option<i32>,
53    week: Option<i32>,
54    weekday: Option<Weekday>,
55    day: Option<i32>,
56    ordinal: Option<i32>,
57
58    hour: Option<i32>,
59    hour12: Option<i32>,
60    meridiem: Option<Meridiem>,
61    minute: Option<i32>,
62    second: Option<i32>,
63    nanosecond: Option<i32>,
64
65    is_today: bool,
66    is_now: bool,
67    relative: Option<Relative>,
68
69    time_zone: Option<TimeZone>,
70}
71
72impl Fields {
73    fn get_year(&self) -> Option<i32> {
74        if let Some(iso_year) = self.iso_year {
75            return Some(iso_year);
76        }
77        let era = self.era.unwrap_or(Era::CE);
78        if let Some(year) = self.year {
79            match era {
80                Era::CE => return Some(year),
81                Era::BCE => return Some(-year - 1),
82            }
83        }
84        None
85    }
86
87    fn to_ymd_date(&self, year: i32) -> Option<Date> {
88        let month = self.month?;
89        let day = self.day?;
90        Date::new(year as i16, month as i8, day as i8).ok()
91    }
92
93    fn to_week_date(&self, year: i32) -> Option<Date> {
94        let week = self.week?;
95        let weekday = self.weekday?;
96        ISOWeekDate::new(year as i16, week as i8, weekday)
97            .ok()
98            .map(Into::into)
99    }
100
101    fn to_ordinal_date(&self, year: i32) -> Option<Date> {
102        let ordinal = self.ordinal?;
103        let date = Date::new(year as i16, 1, 1).ok()?;
104        date.with().day_of_year(ordinal as i16).build().ok()
105    }
106
107    fn to_date(&self, current_year: i32) -> Option<Date> {
108        let year = self.get_year().unwrap_or(current_year);
109        self.to_ymd_date(year)
110            .or_else(|| self.to_week_date(year))
111            .or_else(|| self.to_ordinal_date(year))
112    }
113
114    fn get_hour12(&self) -> Option<i32> {
115        let hour12 = self.hour12?;
116        let meridiem = self.meridiem?;
117        Some(match (hour12, meridiem) {
118            (12, Meridiem::AM) => 0,
119            (12, Meridiem::PM) => 12,
120            (x, Meridiem::AM) => x,
121            (x, Meridiem::PM) => 12 + x,
122        })
123    }
124
125    fn get_hour(&self) -> Option<i32> {
126        self.hour.or_else(|| self.get_hour12())
127    }
128
129    fn to_time(&self) -> Option<Time> {
130        let hour = self.get_hour()?;
131        let minute = self.minute.unwrap_or(0);
132        let second = self.second.unwrap_or(0);
133        let nanos = self.nanosecond.unwrap_or(0);
134        Time::new(hour as i8, minute as i8, second as i8, nanos).ok()
135    }
136}
137
138impl Default for Fields {
139    fn default() -> Fields {
140        Fields {
141            era: None,
142            year: None,
143            iso_year: None,
144            month: None,
145            week: None,
146            weekday: None,
147            day: None,
148            ordinal: None,
149            hour: None,
150            hour12: None,
151            meridiem: None,
152            minute: None,
153            second: None,
154            nanosecond: None,
155            is_today: false,
156            is_now: false,
157            relative: None,
158            time_zone: None,
159        }
160    }
161}
162
163pub(crate) fn parse_date<I>(
164    out: &mut Fields,
165    date: &mut Peekable<I>,
166    pat: &[DatePattern],
167) -> Result<(), String>
168where
169    I: Iterator<Item = DateToken> + Clone,
170{
171    use std::borrow::Borrow;
172
173    let tok = date.peek().cloned();
174
175    fn ts<T>(x: Option<T>) -> String
176    where
177        T: Borrow<DateToken>,
178    {
179        match x {
180            Some(ref x) => format!("`{}`", x.borrow()),
181            None => "eof".to_owned(),
182        }
183    }
184
185    let mut advance = true;
186
187    #[allow(unused_assignments)]
188    macro_rules! take {
189        ($($pat: pat)|+) => {
190            match date.peek().cloned() {
191                $(Some($pat))|+ => {date.next().unwrap()},
192                x => return Err(format!("Expected {}, got {}", stringify!($($pat)|+), ts(x)))
193            }
194        };
195        ($pat:pat, $var:ident) => {
196            match date.peek().cloned() {
197                Some($pat) => {date.next(); $var},
198                x => return Err(format!("Expected {}, got {}", stringify!($pat), ts(x)))
199            }
200        }
201    }
202
203    let res = match pat.first() {
204        None => return Ok(()),
205        Some(&DatePattern::Literal(ref l)) => match tok {
206            Some(DateToken::Literal(ref s)) if s == l => Ok(()),
207            x => Err(format!("Expected `{}`, got {}", l, ts(x))),
208        },
209        Some(&DatePattern::Match(what)) => match what {
210            DateMatch::FullYear => {
211                numeric_match(tok.as_ref(), "fullyear", 4, 0..=9999).and_then(|v| {
212                    out.year = Some(v);
213                    Ok(())
214                })
215            }
216            DateMatch::MonthNum => {
217                numeric_match(tok.as_ref(), "monthnum", 2, 1..=12).and_then(|v| {
218                    out.month = Some(v);
219                    Ok(())
220                })
221            }
222            DateMatch::Day => numeric_match(tok.as_ref(), "day", 0, 1..=31).and_then(|v| {
223                out.day = Some(v);
224                Ok(())
225            }),
226            DateMatch::FullDay => numeric_match(tok.as_ref(), "fullday", 2, 1..=31).and_then(|v| {
227                out.day = Some(v);
228                Ok(())
229            }),
230            DateMatch::Min => numeric_match(tok.as_ref(), "min", 2, 0..=60).and_then(|v| {
231                out.minute = Some(v);
232                Ok(())
233            }),
234            DateMatch::Ordinal => {
235                numeric_match(tok.as_ref(), "ordinal", 3, 1..=366).and_then(|v| {
236                    out.ordinal = Some(v);
237                    Ok(())
238                })
239            }
240            DateMatch::Year => numeric_match(tok.as_ref(), "year", 0, 0..=9999).and_then(|v| {
241                out.year = Some(v);
242                Ok(())
243            }),
244            DateMatch::IsoWeek => numeric_match(tok.as_ref(), "isoweek", 2, 1..=53).and_then(|v| {
245                out.week = Some(v);
246                Ok(())
247            }),
248            DateMatch::IsoYear => {
249                advance = false;
250                let x = take!(DateToken::Dash | DateToken::Plus | DateToken::Number(_, None));
251                let (sign, num) = match x {
252                    DateToken::Dash => (-1, None),
253                    DateToken::Plus => (1, None),
254                    DateToken::Number(i, None) => (1, Some(i)),
255                    _ => unreachable!(),
256                };
257                let num = match num {
258                    Some(x) => x,
259                    None => take!(DateToken::Number(x, None), x),
260                };
261                let value = i32::from_str_radix(&*num, 10);
262                if let Ok(value) = value {
263                    out.iso_year = Some(value * sign);
264                    Ok(())
265                } else {
266                    Err(format!("Expected isoyear, got out of range value"))
267                }
268            }
269            DateMatch::Era => match tok {
270                Some(DateToken::Literal(ref s))
271                    if { s.to_lowercase() == "ad" || s.to_lowercase() == "ce" } =>
272                {
273                    out.era = Some(Era::CE);
274                    Ok(())
275                }
276                Some(DateToken::Literal(ref s))
277                    if { s.to_lowercase() == "bc" || s.to_lowercase() == "bce" } =>
278                {
279                    out.era = Some(Era::BCE);
280                    Ok(())
281                }
282                x => Err(format!("Expected AD/BC or CE/BCE, got {}", ts(x))),
283            },
284            DateMatch::Hour12 => match tok {
285                Some(DateToken::Number(ref s, None)) => {
286                    if let Some(value) = parse_range(s, 0, 1..=12) {
287                        out.hour12 = Some(value);
288                        Ok(())
289                    } else {
290                        Err(format!("Expected hour12, got out of range value {}", s))
291                    }
292                }
293                x => Err(format!("Expected hour12, got {}", ts(x))),
294            },
295            DateMatch::Hour24 => match tok {
296                Some(DateToken::Number(ref s, None)) => {
297                    if let Some(value) = parse_range(s, 0, 0..=23) {
298                        out.hour = Some(value);
299                        Ok(())
300                    } else {
301                        Err(format!("Expected hour24, got out of range value {}", s))
302                    }
303                }
304                x => Err(format!("Expected hour24, got {}", ts(x))),
305            },
306            DateMatch::FullHour12 => match tok {
307                Some(DateToken::Number(ref s, None)) => {
308                    if let Some(value) = parse_range(s, 2, 1..=12) {
309                        out.hour12 = Some(value);
310                        Ok(())
311                    } else {
312                        Err(format!(
313                            "Expected 2-digit hour12, got out of range value {}",
314                            s
315                        ))
316                    }
317                }
318                x => Err(format!("Expected 2-digit hour12, got {}", ts(x))),
319            },
320            DateMatch::FullHour24 => match tok {
321                Some(DateToken::Number(ref s, None)) => {
322                    if let Some(value) = parse_range(s, 2, 0..=23) {
323                        out.hour = Some(value);
324                        Ok(())
325                    } else {
326                        Err(format!(
327                            "Expected 2-digit hour24, got out of range value {}",
328                            s
329                        ))
330                    }
331                }
332                x => Err(format!("Expected 2-digit hour24, got {}", ts(x))),
333            },
334            DateMatch::Meridiem => match tok {
335                Some(DateToken::Literal(ref s)) if s.to_lowercase() == "am" => {
336                    out.meridiem = Some(Meridiem::AM);
337                    Ok(())
338                }
339                Some(DateToken::Literal(ref s)) if s.to_lowercase() == "pm" => {
340                    out.meridiem = Some(Meridiem::PM);
341                    Ok(())
342                }
343                x => Err(format!("Expected AM/PM, got {}", ts(x))),
344            },
345            DateMatch::Sec => match tok {
346                Some(DateToken::Number(ref s, None)) => {
347                    if let Some(value) = parse_range(s, 2, 0..=60) {
348                        out.second = Some(value);
349                        Ok(())
350                    } else {
351                        Err(format!("Expected 2-digit sec in range 0..=60, got {}", s))
352                    }
353                }
354                Some(DateToken::Number(ref s, Some(ref f))) if s.len() == 2 => {
355                    let secs = u32::from_str_radix(&**s, 10);
356                    let nsecs = u32::from_str_radix(&**f, 10);
357                    if let (Ok(secs), Ok(nsecs)) = (secs, nsecs) {
358                        let nsecs = nsecs * 10u32.pow(9 - f.len() as u32);
359                        out.second = Some(secs as i32);
360                        out.nanosecond = Some(nsecs as i32);
361                        Ok(())
362                    } else {
363                        Err(format!("Expected 2-digit sec, got {}.{}", s, f))
364                    }
365                }
366                x => Err(format!("Expected 2-digit sec, got {}", ts(x))),
367            },
368            DateMatch::Offset => {
369                advance = false;
370                if let Some(DateToken::Literal(ref s)) = date.peek().cloned() {
371                    date.next();
372                    let s = s
373                        .strip_prefix('[')
374                        .and_then(|s| s.strip_suffix(']'))
375                        .unwrap_or(s);
376                    if let Ok(tz) = TimeZone::get(s) {
377                        out.time_zone = Some(tz);
378                        Ok(())
379                    } else {
380                        Err(format!("Invalid timezone {}", s))
381                    }
382                } else {
383                    let s = match take!(DateToken::Plus | DateToken::Dash) {
384                        DateToken::Plus => 1,
385                        DateToken::Dash => -1,
386                        _ => unreachable!(),
387                    };
388                    let h = take!(DateToken::Number(s, None), s);
389                    if let Some(hm) = parse_fixed(&h, 4) {
390                        let m = hm % 100;
391                        let h = hm / 100;
392                        let offset = Offset::from_seconds(s * (h * 3600 + m * 60)).unwrap();
393                        out.time_zone = Some(TimeZone::fixed(offset));
394                        Ok(())
395                    } else if let Ok(h) = i32::from_str_radix(&h, 10) {
396                        take!(DateToken::Colon);
397                        let m = take!(DateToken::Number(s, None), s);
398                        if let Some(m) = parse_range(&m, 2, 0..=59) {
399                            let offset = Offset::from_seconds(s * (h * 3600 + m * 60)).unwrap();
400                            out.time_zone = Some(TimeZone::fixed(offset));
401                            Ok(())
402                        } else {
403                            Err(format!("Expected 2 digits after : in offset, got {}", m))
404                        }
405                    } else {
406                        Err(format!("Expected offset, got {}", h))
407                    }
408                }
409            }
410            DateMatch::MonthName => match tok {
411                Some(DateToken::Literal(ref s)) => {
412                    let res = match &*s.to_lowercase() {
413                        "jan" | "january" => 1,
414                        "feb" | "february" => 2,
415                        "mar" | "march" => 3,
416                        "apr" | "april" => 4,
417                        "may" => 5,
418                        "jun" | "june" => 6,
419                        "jul" | "july" => 7,
420                        "aug" | "august" => 8,
421                        "sep" | "september" => 9,
422                        "oct" | "october" => 10,
423                        "nov" | "november" => 11,
424                        "dec" | "december" => 12,
425                        x => return Err(format!("Unknown month name: {}", x)),
426                    };
427                    out.month = Some(res);
428                    Ok(())
429                }
430                x => Err(format!("Expected month name, got {}", ts(x))),
431            },
432            DateMatch::WeekDay => match tok {
433                Some(DateToken::Literal(ref s)) => {
434                    let res = match &*s.to_lowercase() {
435                        "mon" | "monday" => Weekday::Monday,
436                        "tue" | "tuesday" => Weekday::Tuesday,
437                        "wed" | "wednesday" => Weekday::Wednesday,
438                        "thu" | "thursday" => Weekday::Thursday,
439                        "fri" | "friday" => Weekday::Friday,
440                        "sat" | "saturday" => Weekday::Saturday,
441                        "sun" | "sunday" => Weekday::Sunday,
442                        x => return Err(format!("Unknown weekday: {}", x)),
443                    };
444                    out.weekday = Some(res);
445                    Ok(())
446                }
447                x => Err(format!("Expected weekday, got {}", ts(x))),
448            },
449            DateMatch::Today => match tok {
450                Some(DateToken::Literal(ref lit)) if lit == "today" => {
451                    out.is_today = true;
452                    Ok(())
453                }
454                x => Err(format!("Expected `today`, got {}", ts(x))),
455            },
456            DateMatch::Now => match tok {
457                Some(DateToken::Literal(ref lit)) if lit == "now" => {
458                    out.is_now = true;
459                    Ok(())
460                }
461                x => Err(format!("Expected `now`, got {}", ts(x))),
462            },
463            DateMatch::Relative => match tok {
464                Some(sign @ DateToken::Plus | sign @ DateToken::Dash) => {
465                    date.next();
466                    let number = match date.peek() {
467                        Some(DateToken::Number(n, None)) => n,
468                        x => return Err(format!("Expected integer, got {}", ts(x))),
469                    };
470                    let value = i32::from_str_radix(number, 10)
471                        .map_err(|e| format!("Invalid integer: {e}"))?;
472                    let value = if sign == DateToken::Dash {
473                        -value
474                    } else {
475                        value
476                    };
477                    date.next();
478                    let unit = match date.peek() {
479                        Some(DateToken::Literal(lit)) => match &lit.to_lowercase()[..] {
480                            "ns" | "nano" | "nanos" | "nanosecond" | "nanoseconds" => {
481                                Unit::Nanosecond
482                            }
483                            "us" | "µs" | "micro" | "micros" | "microsecond" | "microseconds" => {
484                                Unit::Microsecond
485                            }
486                            "ms" | "milli" | "millis" | "millisecond" | "milliseconds" => {
487                                Unit::Millisecond
488                            }
489                            "s" | "sec" | "secs" | "second" | "seconds" => Unit::Second,
490                            "mi" | "min" | "mins" | "minute" | "minutes" => Unit::Minute,
491                            "h" | "hr" | "hrs" | "hour" | "hours" => Unit::Hour,
492                            "d" | "dy" | "day" | "days" => Unit::Day,
493                            "w" | "wk" | "week" | "weeks" => Unit::Week,
494                            "mo" | "mon" | "month" | "months" => Unit::Month,
495                            "y" | "yr" | "yrs" | "year" | "years" => Unit::Year,
496                            "m" => {
497                                return Err(format!(
498                                    "`m` is ambiguous, did you mean `minutes` or `months`?"
499                                ))
500                            }
501                            _ => return Err(format!("Unknown unit `{}`", lit)),
502                        },
503                        x => return Err(format!("Expected unit, got {}", ts(x))),
504                    };
505                    out.relative = Some(Relative { unit, value });
506                    Ok(())
507                }
508                x => Err(format!("Expected + or -, got {}", ts(x))),
509            },
510        },
511        Some(&DatePattern::Optional(ref pats)) => {
512            advance = false;
513            let mut iter = date.clone();
514            if let Ok(()) = parse_date(out, &mut iter, &pats[..]) {
515                *date = iter
516            }
517            Ok(())
518        }
519        Some(&DatePattern::Dash) => match tok {
520            Some(DateToken::Dash) => Ok(()),
521            x => Err(format!("Expected `-`, got {}", ts(x))),
522        },
523        Some(&DatePattern::Colon) => match tok {
524            Some(DateToken::Colon) => Ok(()),
525            x => Err(format!("Expected `:`, got {}", ts(x))),
526        },
527        Some(&DatePattern::Space) => match tok {
528            Some(DateToken::Space) => Ok(()),
529            x => Err(format!("Expected ` `, got {}", ts(x))),
530        },
531    };
532    if advance {
533        date.next();
534    }
535    res.and_then(|_| parse_date(out, date, &pat[1..]))
536}
537
538fn attempt(
539    now: &DateTime,
540    date: &[DateToken],
541    pat: &[DatePattern],
542) -> Result<DateTime, (String, usize)> {
543    let mut parsed = Fields::default();
544    let mut iter = date.iter().cloned().peekable();
545    let res = parse_date(&mut parsed, &mut iter, pat);
546    let count = iter.count();
547    let res = if count > 0 && res.is_ok() {
548        Err(format!(
549            "Expected eof, got {}",
550            date[date.len() - count..]
551                .iter()
552                .map(ToString::to_string)
553                .collect::<Vec<_>>()
554                .join("")
555        ))
556    } else {
557        res
558    };
559    res.map_err(|e| (e, count))?;
560
561    let mut date = parsed.to_date(now.year());
562    let mut time = parsed.to_time();
563
564    if (parsed.is_now || parsed.is_today) && date.is_none() {
565        date = Some(now.dt.date());
566    }
567    if parsed.is_now && time.is_none() {
568        time = Some(now.dt.time());
569    }
570
571    let tz = parsed.time_zone.unwrap_or(now.dt.time_zone().clone());
572
573    let dt = match (date, time) {
574        (Some(date), Some(time)) => jiff::civil::DateTime::from_parts(date, time),
575        (Some(date), None) => date.into(),
576        (None, Some(time)) => jiff::civil::DateTime::from_parts(now.dt.date(), time),
577        (None, None) => return Err(("Can't make a valid datetime".to_owned(), count)),
578    };
579
580    let mut zoned: Zoned = match dt.to_zoned(tz) {
581        Ok(zoned) => zoned,
582        Err(err) => return Err((err.to_string(), count)),
583    };
584
585    if let Some(relative) = parsed.relative {
586        let span = Span::new();
587        let span = match relative.unit {
588            Unit::Year => span.years(relative.value),
589            Unit::Month => span.months(relative.value),
590            Unit::Week => span.weeks(relative.value),
591            Unit::Day => span.days(relative.value),
592            Unit::Hour => span.hours(relative.value),
593            Unit::Minute => span.minutes(relative.value),
594            Unit::Second => span.seconds(relative.value),
595            Unit::Millisecond => span.milliseconds(relative.value),
596            Unit::Microsecond => span.microseconds(relative.value),
597            Unit::Nanosecond => span.nanoseconds(relative.value),
598        };
599        zoned = match zoned.checked_add(span) {
600            Ok(zoned) => zoned,
601            Err(err) => return Err((err.to_string(), count)),
602        };
603    }
604
605    Ok(zoned.into())
606}
607
608pub fn try_decode(date: &[DateToken], context: &Context) -> Result<DateTime, String> {
609    let mut best = None;
610    for pat in &context.registry.datepatterns {
611        match attempt(&context.now, date, pat) {
612            Ok(datetime) => return Ok(datetime),
613            Err((e, c)) => {
614                //println!("{}", e);
615                let better = if let Some((count, _, _)) = best {
616                    c < count
617                } else {
618                    true
619                };
620                if better {
621                    best = Some((c, pat, e.clone()));
622                }
623            }
624        }
625    }
626    if let Some((_, pat, err)) = best {
627        Err(format!(
628            "Most likely pattern `{}` failed: {}",
629            DatePattern::show(pat),
630            err
631        ))
632    } else {
633        Err("Invalid date literal".to_string())
634    }
635}
636
637pub fn to_duration(num: &Number) -> Result<SignedDuration, String> {
638    if num.unit != Dimensionality::base_unit(BaseUnit::new("s")) {
639        return Err("Expected seconds".to_string());
640    }
641    let max = Numeric::from(i64::max_value());
642    if num.value.abs() > max {
643        return Err(format!(
644            "Implementation error: Duration is too large, max is 2^63 seconds",
645        ));
646    }
647    let (seconds, rem) = num.value.div_rem(&Numeric::from(1));
648    let nanos = &rem * &Numeric::from(1_000_000_000);
649    let seconds = seconds.to_int().unwrap();
650    let nanos = nanos.to_int().unwrap();
651    Ok(SignedDuration::new(seconds, nanos as i32))
652}
653
654pub fn from_duration(duration: &SignedDuration) -> Result<Number, String> {
655    let seconds = duration.as_secs();
656    let nanos = duration.subsec_nanos();
657    let seconds_div = BigInt::one();
658    let nanos_div = BigInt::from(1_000_000_000u64);
659    let seconds = BigRat::ratio(&BigInt::from(seconds), &seconds_div);
660    let nanos = BigRat::ratio(&BigInt::from(nanos), &nanos_div);
661    Ok(Number::new_unit(
662        Numeric::Rational(&seconds + &nanos),
663        BaseUnit::new("s"),
664    ))
665}
666
667pub fn parse_datepattern<I>(iter: &mut Peekable<I>) -> Result<Vec<DatePattern>, String>
668where
669    I: Iterator<Item = char>,
670{
671    let mut out = vec![];
672    while iter.peek().is_some() {
673        let res = match iter.peek().cloned().unwrap() {
674            '-' => DatePattern::Dash,
675            ':' => DatePattern::Colon,
676            '[' => {
677                iter.next();
678                let res = DatePattern::Optional(parse_datepattern(iter)?);
679                if iter.peek().cloned() != Some(']') {
680                    return Err("Expected ]".to_string());
681                } else {
682                    res
683                }
684            }
685            ']' => break,
686            '\'' => {
687                iter.next();
688                let mut buf = String::new();
689                while let Some(c) = iter.peek().cloned() {
690                    if c == '\'' {
691                        break;
692                    } else {
693                        iter.next();
694                        buf.push(c);
695                    }
696                }
697                DatePattern::Literal(buf)
698            }
699            // Separator that allows putting patterns next to each other without a space.
700            '&' => {
701                iter.next();
702                continue;
703            }
704            x if x.is_whitespace() => {
705                while iter.peek().map(|c| c.is_whitespace()).unwrap_or(false) {
706                    iter.next();
707                }
708                out.push(DatePattern::Space);
709                continue;
710            }
711            x if x.is_alphabetic() => {
712                let mut buf = String::new();
713                while let Some(c) = iter.peek().cloned() {
714                    if c.is_alphanumeric() {
715                        iter.next();
716                        buf.push(c);
717                    } else {
718                        break;
719                    }
720                }
721                match DateMatch::from_str(&buf) {
722                    Some(dm) => out.push(DatePattern::Match(dm)),
723                    None => return Err(format!("Unknown date pattern `{}`", buf)),
724                }
725                continue;
726            }
727            x => return Err(format!("Unrecognized character {}", x)),
728        };
729        out.push(res);
730        iter.next();
731    }
732    Ok(out)
733}
734
735pub fn parse_datefile(file: &str) -> Vec<Vec<DatePattern>> {
736    let mut defs = vec![];
737    for (num, line) in file.lines().enumerate() {
738        let line = line.split('#').next().unwrap();
739        let line = line.trim();
740        if line.is_empty() {
741            continue;
742        }
743        let res = parse_datepattern(&mut line.chars().peekable());
744        match res {
745            Ok(res) => defs.push(res),
746            Err(e) => println!("Line {}: {}: {}", num, e, line),
747        }
748    }
749    defs
750}
751
752#[cfg(test)]
753mod tests {
754    use super::*;
755
756    fn pattern(s: &str) -> Vec<DatePattern> {
757        parse_datepattern(&mut s.chars().peekable()).unwrap()
758    }
759
760    fn parse_with_tz(date: Vec<DateToken>, pat: &str) -> (Result<(), String>, Fields) {
761        let mut parsed = Fields::default();
762        let pat = pattern(pat);
763        let res = parse_date(&mut parsed, &mut date.into_iter().peekable(), &pat);
764
765        (res, parsed)
766    }
767
768    fn parse(date: Vec<DateToken>, pat: &str) -> (Result<(), String>, Fields) {
769        let (res, parsed) = parse_with_tz(date, pat);
770        (res, parsed)
771    }
772
773    #[test]
774    fn test_literal() {
775        let date = vec![DateToken::Literal("abc".into())];
776        let (res, _parsed) = parse(date.clone(), "'abc'");
777        assert!(res.is_ok());
778
779        let (res, _parsed) = parse(date, "'def'");
780        assert_eq!(res, Err("Expected `def`, got `abc`".into()));
781    }
782
783    #[test]
784    fn test_year_plus() {
785        let date = vec![DateToken::Plus, DateToken::Number("123".into(), None)];
786        let (res, parsed) = parse(date.clone(), "isoyear");
787        assert!(res.is_ok());
788        assert_eq!(parsed.iso_year, Some(123));
789
790        let date = vec![DateToken::Number("123".into(), None)];
791        let (res, parsed2) = parse(date.clone(), "year");
792        assert!(res.is_ok());
793        assert_eq!(parsed2.year, Some(123));
794    }
795
796    #[test]
797    fn test_complicated_date_input() {
798        let date = vec![
799            DateToken::Number("2".to_owned(), None),
800            DateToken::Space,
801            DateToken::Literal("Pm".into()),
802            DateToken::Dash,
803            DateToken::Number("05".to_owned(), None),
804            DateToken::Colon,
805            DateToken::Number("123".to_owned(), None),
806            DateToken::Space,
807            DateToken::Number("01".to_owned(), None),
808            DateToken::Dash,
809            DateToken::Number("57".to_owned(), None),
810            DateToken::Space,
811            DateToken::Literal("May".into()),
812        ];
813
814        let (res, parsed) = parse(date, "day meridiem-monthnum:year hour12-min monthname");
815        assert_eq!(res, Ok(()));
816        assert_eq!(parsed.year, Some(123));
817        assert_eq!(parsed.month, Some(5));
818        assert_eq!(parsed.day, Some(2));
819        assert_eq!(parsed.hour12, Some(1));
820        assert_eq!(parsed.meridiem, Some(Meridiem::PM));
821        assert_eq!(parsed.get_hour(), Some(13));
822        assert_eq!(parsed.minute, Some(57));
823    }
824
825    #[test]
826    fn ad_bc() {
827        let date = vec![
828            DateToken::Number("100".to_owned(), None),
829            DateToken::Space,
830            DateToken::Literal("bce".into()),
831            DateToken::Space,
832            DateToken::Number("07".to_owned(), None),
833            DateToken::Space,
834            DateToken::Literal("am".into()),
835        ];
836
837        let (res, parsed) = parse(date, "year adbc hour12 meridiem");
838        assert!(res.is_ok(), "{}", res.unwrap_err());
839        assert_eq!(parsed.year, Some(100));
840        assert_eq!(parsed.era, Some(Era::BCE));
841        assert_eq!(parsed.get_year(), Some(-101));
842        assert_eq!(parsed.hour12, Some(7));
843        assert_eq!(parsed.meridiem, Some(Meridiem::AM));
844    }
845
846    #[test]
847    fn ad_bc_wrong() {
848        for date in vec![
849            vec![DateToken::Literal("foo".into())],
850            vec![DateToken::Plus],
851        ] {
852            let (res, _) = parse(date, "adbc");
853            assert!(res.is_err());
854        }
855    }
856
857    #[test]
858    fn short_hour() {
859        let date = vec![DateToken::Number("7".into(), None)];
860        let (res, _) = parse(date, "hour24");
861        assert_eq!(res, Ok(()));
862    }
863
864    #[test]
865    fn wrong_length_24h() {
866        let date = vec![DateToken::Number("7".into(), None)];
867        let (res, _) = parse(date, "fullhour24");
868        assert_eq!(
869            res,
870            Err(format!("Expected 2-digit hour24, got out of range value 7"))
871        );
872    }
873
874    #[test]
875    fn test_24h() {
876        let (res, parsed) = parse(vec![DateToken::Number("23".into(), None)], "hour24");
877        assert!(res.is_ok());
878        assert_eq!(parsed.hour, Some(23));
879    }
880
881    #[test]
882    fn seconds() {
883        let date = vec![DateToken::Number("27".into(), Some("000012345".into()))];
884        let (res, parsed) = parse(date, "sec");
885        assert!(res.is_ok());
886        assert_eq!(parsed.second, Some(27));
887        assert_eq!(parsed.nanosecond, Some(12345));
888
889        let date = vec![DateToken::Number("27".into(), None)];
890        let (res, parsed) = parse(date, "sec");
891        assert!(res.is_ok());
892        assert_eq!(parsed.second, Some(27));
893        assert_eq!(parsed.nanosecond, None);
894    }
895
896    #[test]
897    fn test_offset() {
898        let date = vec![DateToken::Plus, DateToken::Number("0200".into(), None)];
899        let (res, parsed) = parse(date, "offset");
900        assert!(res.is_ok());
901        assert_eq!(
902            parsed.time_zone,
903            Some(TimeZone::fixed(Offset::from_seconds(2 * 3600).unwrap()))
904        );
905
906        let date = vec![
907            DateToken::Dash,
908            DateToken::Number("01".into(), None),
909            DateToken::Colon,
910            DateToken::Number("23".into(), None),
911        ];
912        let (res, parsed) = parse(date, "offset");
913        assert!(res.is_ok());
914        assert_eq!(
915            parsed.time_zone,
916            Some(TimeZone::fixed(
917                Offset::from_seconds(-(1 * 60 + 23) * 60).unwrap()
918            ))
919        );
920
921        let date = vec![DateToken::Literal("Europe/London".into())];
922        let (res, parsed) = parse_with_tz(date, "offset");
923        assert!(res.is_ok(), "{}", res.unwrap_err());
924        assert_eq!(
925            parsed.time_zone,
926            Some(TimeZone::get("Europe/London").unwrap())
927        );
928    }
929
930    #[test]
931    fn test_weekday() {
932        let date = vec![DateToken::Literal("saturday".into())];
933        let (res, parsed) = parse(date, "weekday");
934        assert!(res.is_ok());
935        assert_eq!(parsed.weekday, Some(Weekday::Saturday));
936
937        let date = vec![DateToken::Literal("sun".into())];
938        assert!(parse(date, "weekday").0.is_ok());
939
940        let date = vec![DateToken::Literal("snu".into())];
941        assert_eq!(parse(date, "weekday").0, Err("Unknown weekday: snu".into()));
942    }
943
944    #[test]
945    fn test_monthname() {
946        for (i, &s) in [
947            "jan", "feb", "mar", "apr", "may", "june", "jul", "AUGUST", "SEp", "Oct", "novemBer",
948            "dec",
949        ]
950        .iter()
951        .enumerate()
952        {
953            let date = vec![DateToken::Literal(s.into())];
954            let (res, parsed) = parse(date, "monthname");
955            assert!(res.is_ok());
956            assert_eq!(parsed.month, Some(i as i32 + 1));
957        }
958
959        let date = vec![DateToken::Literal("foobar".into())];
960        let (res, parsed) = parse(date, "monthname");
961        assert_eq!(res, Err("Unknown month name: foobar".into()));
962        assert_eq!(parsed.month, None);
963    }
964
965    #[test]
966    fn test_parse_datepattern() {
967        use self::DatePattern::*;
968
969        fn parse(s: &str) -> Result<Vec<DatePattern>, String> {
970            parse_datepattern(&mut s.chars().peekable())
971        }
972
973        assert_eq!(
974            parse("-:['abc']"),
975            Ok(vec![Dash, Colon, Optional(vec![Literal("abc".into())])])
976        );
977        assert!(parse("-:['abc'").is_err());
978        assert!(parse("*").is_err());
979    }
980
981    #[test]
982    fn test_attempt() {
983        use self::DateToken::*;
984        fn n(x: &str) -> DateToken {
985            Number(x.into(), None)
986        }
987
988        let now = DateTime::default();
989
990        macro_rules! check_attempt {
991            ($date:expr, $pat:expr) => {{
992                let pat = parse_datepattern(&mut $pat.chars().peekable()).unwrap();
993                attempt(&now, $date, pat.as_ref())
994            }};
995        }
996
997        let tz = Literal("Europe/London".into());
998
999        let date = &[n("23"), Space, n("05"), Space, tz.clone()];
1000        let res = check_attempt!(date, "hour24 min offset");
1001        assert!(res.is_ok(), "{:?}", res);
1002
1003        let date = &[n("23"), Space, tz.clone()];
1004        let res = check_attempt!(date, "hour24 offset");
1005        let res = res.expect("should have parsed");
1006        assert_eq!(res.hour(), 23);
1007        assert_eq!(res.dt.time_zone(), &TimeZone::get("Europe/London").unwrap());
1008
1009        let date = &[n("2018"), Space, n("01"), Space, n("01"), Space, tz.clone()];
1010        let res = check_attempt!(date, "year monthnum day offset");
1011        assert!(res.is_ok(), "{:?}", res);
1012    }
1013
1014    #[test]
1015    fn test_ordinal() {
1016        let date = vec![DateToken::Number("227".into(), None)];
1017        let (res, parsed) = parse(date, "ordinal");
1018        assert!(res.is_ok());
1019        assert_eq!(
1020            parsed.to_ordinal_date(2025),
1021            Some(Date::new(2025, 8, 15).unwrap())
1022        );
1023    }
1024
1025    #[test]
1026    fn test_week_date() {
1027        let date = vec![
1028            DateToken::Literal("fri".into()),
1029            DateToken::Space,
1030            DateToken::Number("33".into(), None),
1031        ];
1032        let (res, parsed) = parse(date, "weekday isoweek");
1033        assert!(res.is_ok());
1034        assert_eq!(
1035            parsed.to_week_date(2025),
1036            Some(Date::new(2025, 8, 15).unwrap())
1037        );
1038    }
1039
1040    #[test]
1041    fn test_meridiem() {
1042        let date = vec![
1043            DateToken::Number("8".into(), None),
1044            DateToken::Colon,
1045            DateToken::Number("00".into(), None),
1046            DateToken::Space,
1047            DateToken::Literal("am".into()),
1048        ];
1049        let (res, parsed) = parse(date, "hour12:min meridiem");
1050        assert!(res.is_ok());
1051        assert_eq!(parsed.to_time(), Some(Time::new(8, 0, 0, 0).unwrap()));
1052
1053        let date = vec![
1054            DateToken::Number("8".into(), None),
1055            DateToken::Colon,
1056            DateToken::Number("00".into(), None),
1057            DateToken::Space,
1058            DateToken::Literal("pm".into()),
1059        ];
1060        let (res, parsed) = parse(date, "hour12:min meridiem");
1061        assert!(res.is_ok());
1062        assert_eq!(parsed.to_time(), Some(Time::new(20, 0, 0, 0).unwrap()));
1063
1064        let date = vec![
1065            DateToken::Number("12".into(), None),
1066            DateToken::Colon,
1067            DateToken::Number("00".into(), None),
1068            DateToken::Space,
1069            DateToken::Literal("am".into()),
1070        ];
1071        let (res, parsed) = parse(date, "hour12:min meridiem");
1072        assert!(res.is_ok());
1073        assert_eq!(parsed.to_time(), Some(Time::new(0, 0, 0, 0).unwrap()));
1074
1075        let date = vec![
1076            DateToken::Number("12".into(), None),
1077            DateToken::Colon,
1078            DateToken::Number("00".into(), None),
1079            DateToken::Space,
1080            DateToken::Literal("pm".into()),
1081        ];
1082        let (res, parsed) = parse(date, "hour12:min meridiem");
1083        assert!(res.is_ok());
1084        assert_eq!(parsed.to_time(), Some(Time::new(12, 0, 0, 0).unwrap()));
1085
1086        let date = vec![
1087            DateToken::Number("12".into(), None),
1088            DateToken::Colon,
1089            DateToken::Number("00".into(), None),
1090            DateToken::Space,
1091            DateToken::Literal("asdf".into()),
1092        ];
1093        let (res, _parsed) = parse(date, "hour12:min meridiem");
1094        assert!(!res.is_ok());
1095
1096        let date = vec![
1097            DateToken::Number("13".into(), None),
1098            DateToken::Colon,
1099            DateToken::Number("00".into(), None),
1100            DateToken::Space,
1101            DateToken::Literal("am".into()),
1102        ];
1103        let (res, _parsed) = parse(date, "hour12:min meridiem");
1104        assert!(!res.is_ok());
1105    }
1106
1107    #[test]
1108    fn test_full_hour12() {
1109        let date = vec![
1110            DateToken::Number("08".into(), None),
1111            DateToken::Colon,
1112            DateToken::Number("00".into(), None),
1113            DateToken::Space,
1114            DateToken::Literal("am".into()),
1115        ];
1116        let (res, parsed) = parse(date, "fullhour12:min meridiem");
1117        assert!(res.is_ok());
1118        assert_eq!(parsed.to_time(), Some(Time::new(8, 0, 0, 0).unwrap()));
1119
1120        let date = vec![
1121            DateToken::Number("8".into(), None),
1122            DateToken::Colon,
1123            DateToken::Number("00".into(), None),
1124            DateToken::Space,
1125            DateToken::Literal("am".into()),
1126        ];
1127        let (res, _parsed) = parse(date, "fullhour12:min meridiem");
1128        assert!(!res.is_ok());
1129
1130        let date = vec![
1131            DateToken::Literal("b".into()),
1132            DateToken::Colon,
1133            DateToken::Number("00".into(), None),
1134            DateToken::Space,
1135            DateToken::Literal("am".into()),
1136        ];
1137        let (res, _parsed) = parse(date, "fullhour12:min meridiem");
1138        assert!(!res.is_ok());
1139
1140        let date = vec![
1141            DateToken::Number("08".into(), None),
1142            DateToken::Colon,
1143            DateToken::Number("00".into(), None),
1144            DateToken::Space,
1145            DateToken::Literal("pm".into()),
1146        ];
1147        let (res, parsed) = parse(date, "fullhour12:min meridiem");
1148        assert!(res.is_ok());
1149        assert_eq!(parsed.to_time(), Some(Time::new(20, 0, 0, 0).unwrap()));
1150
1151        let date = vec![
1152            DateToken::Number("12".into(), None),
1153            DateToken::Colon,
1154            DateToken::Number("00".into(), None),
1155            DateToken::Space,
1156            DateToken::Literal("am".into()),
1157        ];
1158        let (res, parsed) = parse(date, "fullhour12:min meridiem");
1159        assert!(res.is_ok());
1160        assert_eq!(parsed.to_time(), Some(Time::new(0, 0, 0, 0).unwrap()));
1161
1162        let date = vec![
1163            DateToken::Number("12".into(), None),
1164            DateToken::Colon,
1165            DateToken::Number("00".into(), None),
1166            DateToken::Space,
1167            DateToken::Literal("pm".into()),
1168        ];
1169        let (res, parsed) = parse(date, "fullhour12:min meridiem");
1170        assert!(res.is_ok());
1171        assert_eq!(parsed.to_time(), Some(Time::new(12, 0, 0, 0).unwrap()));
1172    }
1173
1174    #[test]
1175    fn test_full_hour24() {
1176        let date = vec![
1177            DateToken::Number("23".into(), None),
1178            DateToken::Colon,
1179            DateToken::Number("00".into(), None),
1180        ];
1181        let (res, parsed) = parse(date, "fullhour24");
1182        assert!(res.is_ok());
1183        assert_eq!(parsed.to_time(), Some(Time::new(23, 0, 0, 0).unwrap()));
1184
1185        let date = vec![
1186            DateToken::Number("9".into(), None),
1187            DateToken::Colon,
1188            DateToken::Number("00".into(), None),
1189        ];
1190        let (res, _parsed) = parse(date, "fullhour24");
1191        assert!(!res.is_ok());
1192
1193        let date = vec![
1194            DateToken::Number("25".into(), None),
1195            DateToken::Colon,
1196            DateToken::Number("00".into(), None),
1197        ];
1198        let (res, _parsed) = parse(date, "fullhour24");
1199        assert!(!res.is_ok());
1200
1201        let date = vec![
1202            DateToken::Literal("b".into()),
1203            DateToken::Colon,
1204            DateToken::Number("00".into(), None),
1205        ];
1206        let (res, _parsed) = parse(date, "fullhour24");
1207        assert!(!res.is_ok());
1208    }
1209
1210    #[test]
1211    fn test_isoyear() {
1212        let date = vec![DateToken::Dash, DateToken::Number("7".into(), None)];
1213        let (res, parsed) = parse(date, "isoyear");
1214        assert!(res.is_ok());
1215        assert_eq!(parsed.iso_year, Some(-7));
1216
1217        let date = vec![DateToken::Number("0".into(), None)];
1218        let (res, parsed) = parse(date, "isoyear");
1219        assert!(res.is_ok());
1220        assert_eq!(parsed.iso_year, Some(0));
1221
1222        let date = vec![DateToken::Plus, DateToken::Number("2025".into(), None)];
1223        let (res, parsed) = parse(date, "isoyear");
1224        assert!(res.is_ok());
1225        assert_eq!(parsed.iso_year, Some(2025));
1226    }
1227
1228    #[test]
1229    fn test_today() {
1230        let date = vec![DateToken::Literal("today".into())];
1231        let (res, parsed) = parse(date, "today");
1232        assert!(res.is_ok());
1233        assert_eq!(parsed.is_today, true);
1234
1235        let date = vec![DateToken::Literal("am".into())];
1236        let (res, parsed) = parse(date, "today");
1237        assert!(!res.is_ok());
1238        assert_eq!(parsed.is_today, false);
1239    }
1240
1241    #[test]
1242    fn test_now() {
1243        let date = vec![DateToken::Literal("now".into())];
1244        let (res, parsed) = parse(date, "now");
1245        assert!(res.is_ok());
1246        assert_eq!(parsed.is_now, true);
1247
1248        let date = vec![DateToken::Literal("am".into())];
1249        let (res, parsed) = parse(date, "now");
1250        assert!(!res.is_ok());
1251        assert_eq!(parsed.is_now, false);
1252    }
1253
1254    #[test]
1255    fn test_relative() {
1256        let date = vec![
1257            DateToken::Plus,
1258            DateToken::Number("3".into(), None),
1259            DateToken::Literal("d".into()),
1260        ];
1261        let (res, parsed) = parse(date, "relative");
1262        assert!(res.is_ok());
1263        assert_eq!(
1264            parsed.relative,
1265            Some(Relative {
1266                unit: Unit::Day,
1267                value: 3,
1268            })
1269        );
1270
1271        let date = vec![
1272            DateToken::Dash,
1273            DateToken::Number("7".into(), None),
1274            DateToken::Literal("w".into()),
1275        ];
1276        let (res, parsed) = parse(date, "relative");
1277        assert!(res.is_ok());
1278        assert_eq!(
1279            parsed.relative,
1280            Some(Relative {
1281                unit: Unit::Week,
1282                value: -7,
1283            })
1284        );
1285
1286        let cases = [
1287            (Unit::Nanosecond, "ns"),
1288            (Unit::Nanosecond, "nano"),
1289            (Unit::Nanosecond, "nanos"),
1290            (Unit::Nanosecond, "nanosecond"),
1291            (Unit::Nanosecond, "nanoseconds"),
1292            (Unit::Microsecond, "us"),
1293            (Unit::Microsecond, "µs"),
1294            (Unit::Microsecond, "micro"),
1295            (Unit::Microsecond, "micros"),
1296            (Unit::Microsecond, "microsecond"),
1297            (Unit::Microsecond, "microseconds"),
1298            (Unit::Millisecond, "ms"),
1299            (Unit::Millisecond, "milli"),
1300            (Unit::Millisecond, "millis"),
1301            (Unit::Millisecond, "millisecond"),
1302            (Unit::Millisecond, "milliseconds"),
1303            (Unit::Second, "s"),
1304            (Unit::Second, "sec"),
1305            (Unit::Second, "secs"),
1306            (Unit::Second, "second"),
1307            (Unit::Second, "seconds"),
1308            (Unit::Minute, "mi"),
1309            (Unit::Minute, "min"),
1310            (Unit::Minute, "mins"),
1311            (Unit::Minute, "minute"),
1312            (Unit::Minute, "minutes"),
1313            (Unit::Hour, "h"),
1314            (Unit::Hour, "hr"),
1315            (Unit::Hour, "hrs"),
1316            (Unit::Hour, "hour"),
1317            (Unit::Hour, "hours"),
1318            (Unit::Day, "d"),
1319            (Unit::Day, "dy"),
1320            (Unit::Day, "day"),
1321            (Unit::Day, "days"),
1322            (Unit::Week, "w"),
1323            (Unit::Week, "wk"),
1324            (Unit::Week, "week"),
1325            (Unit::Week, "weeks"),
1326            (Unit::Month, "mo"),
1327            (Unit::Month, "mon"),
1328            (Unit::Month, "month"),
1329            (Unit::Month, "months"),
1330            (Unit::Year, "y"),
1331            (Unit::Year, "yr"),
1332            (Unit::Year, "yrs"),
1333            (Unit::Year, "year"),
1334            (Unit::Year, "years"),
1335        ];
1336
1337        for (unit, name) in cases {
1338            let date = vec![
1339                DateToken::Dash,
1340                DateToken::Number("2".into(), None),
1341                DateToken::Literal(name.into()),
1342            ];
1343            let (res, parsed) = parse(date, "relative");
1344            assert!(res.is_ok());
1345            assert_eq!(parsed.relative, Some(Relative { unit, value: -2 }));
1346        }
1347
1348        // Error cases
1349        let date = vec![
1350            DateToken::Dash,
1351            DateToken::Number("5".into(), None),
1352            DateToken::Literal("m".into()),
1353        ];
1354        let (res, _parsed) = parse(date, "relative");
1355        assert!(!res.is_ok());
1356
1357        let date = vec![
1358            DateToken::Dash,
1359            DateToken::Number("5".into(), None),
1360            DateToken::Literal("asdf".into()),
1361        ];
1362        let (res, _parsed) = parse(date, "relative");
1363        assert!(!res.is_ok());
1364
1365        let date = vec![DateToken::Dash, DateToken::Number("5".into(), None)];
1366        let (res, _parsed) = parse(date, "relative");
1367        assert!(!res.is_ok());
1368
1369        let date = vec![DateToken::Dash];
1370        let (res, _parsed) = parse(date, "relative");
1371        assert!(!res.is_ok());
1372
1373        let date = vec![DateToken::Dash, DateToken::Literal("hr".into())];
1374        let (res, _parsed) = parse(date, "relative");
1375        assert!(!res.is_ok());
1376
1377        let date = vec![
1378            DateToken::Number("1".into(), None),
1379            DateToken::Literal("hr".into()),
1380        ];
1381        let (res, _parsed) = parse(date, "relative");
1382        assert!(!res.is_ok());
1383
1384        let date = vec![
1385            DateToken::Plus,
1386            DateToken::Number("1".into(), Some("5".into())),
1387            DateToken::Literal("hr".into()),
1388        ];
1389        let (res, _parsed) = parse(date, "relative");
1390        assert!(!res.is_ok());
1391    }
1392
1393    fn dt(input: &str) -> DateTime {
1394        input.parse::<Zoned>().unwrap().into()
1395    }
1396
1397    fn now_plus_xy(value: i32, unit: &str) -> [DateToken; 4] {
1398        [
1399            DateToken::Literal("now".into()),
1400            if value > 0 {
1401                DateToken::Plus
1402            } else {
1403                DateToken::Dash
1404            },
1405            DateToken::Number(format!("{}", value.abs()), None),
1406            DateToken::Literal(unit.into()),
1407        ]
1408    }
1409
1410    #[test]
1411    fn test_attempt_relative() {
1412        let now = dt("2016-08-02 15:33:19[America/New_York]");
1413        let pattern = &[
1414            DatePattern::Match(DateMatch::Now),
1415            DatePattern::Match(DateMatch::Relative),
1416        ];
1417
1418        assert_eq!(
1419            attempt(&now, &now_plus_xy(1, "ns"), pattern),
1420            Ok(dt("2016-08-02 15:33:19.000000001[America/New_York]"))
1421        );
1422
1423        assert_eq!(
1424            attempt(&now, &now_plus_xy(1, "us"), pattern),
1425            Ok(dt("2016-08-02 15:33:19.000001[America/New_York]"))
1426        );
1427
1428        assert_eq!(
1429            attempt(&now, &now_plus_xy(1, "ms"), pattern),
1430            Ok(dt("2016-08-02 15:33:19.001[America/New_York]"))
1431        );
1432
1433        assert_eq!(
1434            attempt(&now, &now_plus_xy(1, "s"), pattern),
1435            Ok(dt("2016-08-02 15:33:20[America/New_York]"))
1436        );
1437
1438        assert_eq!(
1439            attempt(&now, &now_plus_xy(1, "min"), pattern),
1440            Ok(dt("2016-08-02 15:34:19[America/New_York]"))
1441        );
1442
1443        assert_eq!(
1444            attempt(&now, &now_plus_xy(-1, "min"), pattern),
1445            Ok(dt("2016-08-02 15:32:19[America/New_York]"))
1446        );
1447
1448        assert_eq!(
1449            attempt(&now, &now_plus_xy(1, "hr"), pattern),
1450            Ok(dt("2016-08-02 16:33:19[America/New_York]"))
1451        );
1452
1453        assert_eq!(
1454            attempt(&now, &now_plus_xy(1, "d"), pattern),
1455            Ok(dt("2016-08-03 15:33:19[America/New_York]"))
1456        );
1457
1458        assert_eq!(
1459            attempt(&now, &now_plus_xy(1, "w"), pattern),
1460            Ok(dt("2016-08-09 15:33:19[America/New_York]"))
1461        );
1462
1463        assert_eq!(
1464            attempt(&now, &now_plus_xy(1, "mon"), pattern),
1465            Ok(dt("2016-09-02 15:33:19[America/New_York]"))
1466        );
1467
1468        assert_eq!(
1469            attempt(&now, &now_plus_xy(1, "y"), pattern),
1470            Ok(dt("2017-08-02 15:33:19[America/New_York]"))
1471        );
1472    }
1473}