1use 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 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 '&' => {
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 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}