1use crate::ast::{DatePattern, DateToken};
6use crate::loader::Context;
7use crate::types::{BaseUnit, BigInt, BigRat, Dimensionality, GenericDateTime, Number, Numeric};
8use chrono::format::Parsed;
9use chrono::{DateTime, Duration, FixedOffset, Local, TimeZone, Weekday};
10use chrono_tz::Tz;
11use std::iter::Peekable;
12use std::str::FromStr;
13
14fn parse_fixed(value: &str, digits: usize) -> Option<i32> {
15 if digits != 0 && value.len() != digits {
16 return None;
17 }
18 i32::from_str_radix(value, 10).ok()
19}
20
21fn parse_range(value: &str, digits: usize, range: std::ops::RangeInclusive<i32>) -> Option<i32> {
22 parse_fixed(value, digits).filter(|v| range.contains(v))
23}
24
25fn numeric_match(
26 tok: Option<&DateToken>,
27 name: &str,
28 digits: usize,
29 range: std::ops::RangeInclusive<i32>,
30) -> Result<i32, String> {
31 let tok = tok.ok_or_else(|| format!("Expected {}-digit {}, got eof", digits, name))?;
32
33 if let DateToken::Number(ref s, None) = tok {
34 parse_range(s, digits, range.clone())
35 .ok_or(format!("Expected {} in range {:?}, got {}", name, range, s))
36 } else {
37 Err(format!("Expected {}-digit {}, got {}", digits, name, tok))
38 }
39}
40
41pub fn parse_date<I>(
42 out: &mut Parsed,
43 out_tz: &mut Option<Tz>,
44 date: &mut Peekable<I>,
45 pat: &[DatePattern],
46) -> Result<(), String>
47where
48 I: Iterator<Item = DateToken> + Clone,
49{
50 use std::borrow::Borrow;
51
52 let tok = date.peek().cloned();
53
54 fn ts<T>(x: Option<T>) -> String
55 where
56 T: Borrow<DateToken>,
57 {
58 match x {
59 Some(ref x) => format!("`{}`", x.borrow()),
60 None => "eof".to_owned(),
61 }
62 }
63
64 let mut advance = true;
65
66 #[allow(unused_assignments)]
67 macro_rules! take {
68 ($($pat: pat)|+) => {
69 match date.peek().cloned() {
70 $(Some($pat))|+ => {date.next().unwrap()},
71 x => return Err(format!("Expected {}, got {}", stringify!($($pat)|+), ts(x)))
72 }
73 };
74 ($pat:pat, $var:ident) => {
75 match date.peek().cloned() {
76 Some($pat) => {date.next(); $var},
77 x => return Err(format!("Expected {}, got {}", stringify!($pat), ts(x)))
78 }
79 }
80 }
81
82 let res = match pat.first() {
83 None => return Ok(()),
84 Some(&DatePattern::Literal(ref l)) => match tok {
85 Some(DateToken::Literal(ref s)) if s == l => Ok(()),
86 x => Err(format!("Expected `{}`, got {}", l, ts(x))),
87 },
88 Some(&DatePattern::Match(ref what)) => match &**what {
89 "fullyear" => numeric_match(tok.as_ref(), "fullyear", 4, 0..=9999).and_then(|v| {
90 out.year = Some(v);
91 Ok(())
92 }),
93 "shortyear" => numeric_match(tok.as_ref(), "shortyear", 2, 0..=99).and_then(|v| {
94 out.year_mod_100 = Some(v);
95 Ok(())
96 }),
97 "century" => numeric_match(tok.as_ref(), "century", 2, 0..=99).and_then(|v| {
98 out.year_div_100 = Some(v);
99 Ok(())
100 }),
101 "monthnum" => numeric_match(tok.as_ref(), "monthnum", 2, 1..=12).and_then(|v| {
102 out.month = Some(v as u32);
103 Ok(())
104 }),
105 "day" => numeric_match(tok.as_ref(), "day", 0, 1..=31).and_then(|v| {
106 out.day = Some(v as u32);
107 Ok(())
108 }),
109 "fullday" => numeric_match(tok.as_ref(), "fullday", 2, 1..=31).and_then(|v| {
110 out.day = Some(v as u32);
111 Ok(())
112 }),
113 "min" => numeric_match(tok.as_ref(), "min", 2, 0..=60).and_then(|v| {
114 out.minute = Some(v as u32);
115 Ok(())
116 }),
117 "ordinal" => numeric_match(tok.as_ref(), "ordinal", 3, 1..=366).and_then(|v| {
118 out.ordinal = Some(v as u32);
119 Ok(())
120 }),
121 "isoyear" => numeric_match(tok.as_ref(), "isoyear", 4, 0..=9999).and_then(|v| {
122 out.isoyear = Some(v);
123 Ok(())
124 }),
125 "isoweek" => numeric_match(tok.as_ref(), "isoweek", 2, 1..=53).and_then(|v| {
126 out.isoweek = Some(v as u32);
127 Ok(())
128 }),
129 "unix" => numeric_match(tok.as_ref(), "unix", 0, 0..=i32::MAX).and_then(|v| {
130 out.timestamp = Some(v as i64);
131 Ok(())
132 }),
133 "year" => {
134 advance = false;
135 let x = take!(DateToken::Dash | DateToken::Plus | DateToken::Number(_, None));
136 let (sign, num) = match x {
137 DateToken::Dash => (-1, None),
138 DateToken::Plus => (1, None),
139 DateToken::Number(i, None) => (1, Some(i)),
140 _ => unreachable!(),
141 };
142 let num = match num {
143 Some(x) => x,
144 None => take!(DateToken::Number(x, None), x),
145 };
146 let value = i32::from_str_radix(&*num, 10);
147 if let Ok(value) = value {
148 out.year = Some(value * sign);
149 Ok(())
150 } else {
151 Err(format!("Expected year, got out of range value"))
152 }
153 }
154 "adbc" => match tok {
155 Some(DateToken::Literal(ref s))
156 if { s.to_lowercase() == "ad" || s.to_lowercase() == "ce" } =>
157 {
158 Ok(())
159 }
160 Some(DateToken::Literal(ref s))
161 if { s.to_lowercase() == "bc" || s.to_lowercase() == "bce" } =>
162 {
163 out.year = out.year.map(|x| -x + 1);
164 Ok(())
165 }
166 x => Err(format!("Expected AD/BC or CE/BCE, got {}", ts(x))),
167 },
168 "hour12" => match tok {
169 Some(DateToken::Number(ref s, None)) => {
170 if let Some(value) = parse_range(s, 2, 1..=12) {
171 out.hour_mod_12 = Some(value as u32 % 12);
172 Ok(())
173 } else {
174 Err(format!(
175 "Expected 2-digit hour12, got out of range value {}",
176 s
177 ))
178 }
179 }
180 x => Err(format!("Expected 2-digit hour12, got {}", ts(x))),
181 },
182 "hour24" => match tok {
183 Some(DateToken::Number(ref s, None)) => {
184 if let Some(value) = parse_range(s, 2, 0..=23) {
185 out.hour_div_12 = Some(value as u32 / 12);
186 out.hour_mod_12 = Some(value as u32 % 12);
187 Ok(())
188 } else {
189 Err(format!(
190 "Expected 2-digit hour24, got out of range value {}",
191 s
192 ))
193 }
194 }
195 x => Err(format!("Expected 2-digit hour24, got {}", ts(x))),
196 },
197 "meridiem" => match tok {
198 Some(DateToken::Literal(ref s)) if s.to_lowercase() == "am" => {
199 out.hour_div_12 = Some(0);
200 Ok(())
201 }
202 Some(DateToken::Literal(ref s)) if s.to_lowercase() == "pm" => {
203 out.hour_div_12 = Some(1);
204 Ok(())
205 }
206 x => Err(format!("Expected AM/PM, got {}", ts(x))),
207 },
208 "sec" => match tok {
209 Some(DateToken::Number(ref s, None)) => {
210 if let Some(value) = parse_range(s, 2, 0..=60) {
211 out.second = Some(value as u32);
212 Ok(())
213 } else {
214 Err(format!("Expected 2-digit sec in range 0..=60, got {}", s))
215 }
216 }
217 Some(DateToken::Number(ref s, Some(ref f))) if s.len() == 2 => {
218 let secs = u32::from_str_radix(&**s, 10);
219 let nsecs = u32::from_str_radix(&**f, 10);
220 if let (Ok(secs), Ok(nsecs)) = (secs, nsecs) {
221 let nsecs = nsecs * 10u32.pow(9 - f.len() as u32);
222 out.second = Some(secs);
223 out.nanosecond = Some(nsecs);
224 Ok(())
225 } else {
226 Err(format!("Expected 2-digit sec, got {}.{}", s, f))
227 }
228 }
229 x => Err(format!("Expected 2-digit sec, got {}", ts(x))),
230 },
231 "offset" => {
232 advance = false;
233 if let Some(DateToken::Literal(ref s)) = date.peek().cloned() {
234 date.next();
235 if let Ok(tz) = Tz::from_str(s) {
236 *out_tz = Some(tz);
237 Ok(())
238 } else {
239 Err(format!("Invalid timezone {}", s))
240 }
241 } else {
242 let s = match take!(DateToken::Plus | DateToken::Dash) {
243 DateToken::Plus => 1,
244 DateToken::Dash => -1,
245 _ => unreachable!(),
246 };
247 let h = take!(DateToken::Number(s, None), s);
248 if let Some(hm) = parse_fixed(&h, 4) {
249 let m = hm % 100;
250 let h = hm / 100;
251 out.offset = Some(s * (h * 3600 + m * 60));
252 Ok(())
253 } else if let Ok(h) = i32::from_str_radix(&h, 10) {
254 take!(DateToken::Colon);
255 let m = take!(DateToken::Number(s, None), s);
256 if let Some(m) = parse_range(&m, 2, 0..=59) {
257 out.offset = Some(s * (h * 3600 + m * 60));
258 Ok(())
259 } else {
260 Err(format!("Expected 2 digits after : in offset, got {}", m))
261 }
262 } else {
263 Err(format!("Expected offset, got {}", h))
264 }
265 }
266 }
267 "monthname" => match tok {
268 Some(DateToken::Literal(ref s)) => {
269 let res = match &*s.to_lowercase() {
270 "jan" | "january" => 1,
271 "feb" | "february" => 2,
272 "mar" | "march" => 3,
273 "apr" | "april" => 4,
274 "may" => 5,
275 "jun" | "june" => 6,
276 "jul" | "july" => 7,
277 "aug" | "august" => 8,
278 "sep" | "september" => 9,
279 "oct" | "october" => 10,
280 "nov" | "november" => 11,
281 "dec" | "december" => 12,
282 x => return Err(format!("Unknown month name: {}", x)),
283 };
284 out.month = Some(res);
285 Ok(())
286 }
287 x => Err(format!("Expected month name, got {}", ts(x))),
288 },
289 "weekday" => match tok {
290 Some(DateToken::Literal(ref s)) => {
291 let res = match &*s.to_lowercase() {
292 "mon" | "monday" => Weekday::Mon,
293 "tue" | "tuesday" => Weekday::Tue,
294 "wed" | "wednesday" => Weekday::Wed,
295 "thu" | "thursday" => Weekday::Thu,
296 "fri" | "friday" => Weekday::Fri,
297 "sat" | "saturday" => Weekday::Sat,
298 "sun" | "sunday" => Weekday::Sun,
299 x => return Err(format!("Unknown weekday: {}", x)),
300 };
301 out.weekday = Some(res);
302 Ok(())
303 }
304 x => Err(format!("Expected weekday, got {}", ts(x))),
305 },
306 x => Err(format!("Unknown match pattern `{}`", x)),
307 },
308 Some(&DatePattern::Optional(ref pats)) => {
309 advance = false;
310 let mut iter = date.clone();
311 if let Ok(()) = parse_date(out, out_tz, &mut iter, &pats[..]) {
312 *date = iter
313 }
314 Ok(())
315 }
316 Some(&DatePattern::Dash) => match tok {
317 Some(DateToken::Dash) => Ok(()),
318 x => Err(format!("Expected `-`, got {}", ts(x))),
319 },
320 Some(&DatePattern::Colon) => match tok {
321 Some(DateToken::Colon) => Ok(()),
322 x => Err(format!("Expected `:`, got {}", ts(x))),
323 },
324 Some(&DatePattern::Space) => match tok {
325 Some(DateToken::Space) => Ok(()),
326 x => Err(format!("Expected ` `, got {}", ts(x))),
327 },
328 };
329 if advance {
330 date.next();
331 }
332 res.and_then(|_| parse_date(out, out_tz, date, &pat[1..]))
333}
334
335fn attempt(
336 now: DateTime<Local>,
337 date: &[DateToken],
338 pat: &[DatePattern],
339) -> Result<GenericDateTime, (String, usize)> {
340 let mut parsed = Parsed::new();
341 let mut tz = None;
342 let mut iter = date.iter().cloned().peekable();
343 let res = parse_date(&mut parsed, &mut tz, &mut iter, pat);
344 let count = iter.count();
345 let res = if count > 0 && res.is_ok() {
346 Err(format!(
347 "Expected eof, got {}",
348 date[date.len() - count..]
349 .iter()
350 .map(ToString::to_string)
351 .collect::<Vec<_>>()
352 .join("")
353 ))
354 } else {
355 res
356 };
357 res.map_err(|e| (e, count))?;
358 let time = parsed.to_naive_time();
359 let date = parsed.to_naive_date();
360 if let Some(tz) = tz {
361 match (time, date) {
362 (Ok(time), Ok(date)) => tz
363 .from_local_datetime(&date.and_time(time))
364 .earliest()
365 .ok_or_else(|| {
366 (
367 "Datetime does not represent a valid moment in time".to_string(),
368 count,
369 )
370 })
371 .map(GenericDateTime::Timezone),
372 (Ok(time), Err(_)) => {
373 Ok(now.with_timezone(&tz).with_time(time).unwrap()).map(GenericDateTime::Timezone)
374 }
375 (Err(_), Ok(date)) => tz
376 .from_local_datetime(&date.and_hms_opt(0, 0, 0).unwrap())
377 .earliest()
378 .ok_or_else(|| {
379 (
380 "Datetime does not represent a valid moment in time".to_string(),
381 count,
382 )
383 })
384 .map(GenericDateTime::Timezone),
385 _ => Err(("Failed to construct a useful datetime".to_string(), count)),
386 }
387 } else {
388 let offset = parsed
389 .to_fixed_offset()
390 .unwrap_or_else(|_| FixedOffset::east_opt(0).unwrap());
391 match (time, date) {
392 (Ok(time), Ok(date)) => offset
393 .from_local_datetime(&date.and_time(time))
394 .earliest()
395 .ok_or_else(|| {
396 (
397 "Datetime does not represent a valid moment in time".to_string(),
398 count,
399 )
400 })
401 .map(GenericDateTime::Fixed),
402 (Ok(time), Err(_)) => Ok(GenericDateTime::Fixed(
403 now.with_timezone(&offset).with_time(time).unwrap(),
404 )),
405 (Err(_), Ok(date)) => offset
406 .from_local_datetime(&date.and_hms_opt(0, 0, 0).unwrap())
407 .earliest()
408 .ok_or_else(|| {
409 (
410 "Datetime does not represent a valid moment in time".to_string(),
411 count,
412 )
413 })
414 .map(GenericDateTime::Fixed),
415 _ => Err(("Failed to construct a useful datetime".to_string(), count)),
416 }
417 }
418}
419
420pub fn try_decode(date: &[DateToken], context: &Context) -> Result<GenericDateTime, String> {
421 let mut best = None;
422 for pat in &context.registry.datepatterns {
423 match attempt(context.now, date, pat) {
424 Ok(datetime) => return Ok(datetime),
425 Err((e, c)) => {
426 let better = if let Some((count, _, _)) = best {
428 c < count
429 } else {
430 true
431 };
432 if better {
433 best = Some((c, pat, e.clone()));
434 }
435 }
436 }
437 }
438 if let Some((_, pat, err)) = best {
439 Err(format!(
440 "Most likely pattern `{}` failed: {}",
441 DatePattern::show(pat),
442 err
443 ))
444 } else {
445 Err("Invalid date literal".to_string())
446 }
447}
448
449pub fn to_duration(num: &Number) -> Result<Duration, String> {
450 if num.unit != Dimensionality::base_unit(BaseUnit::new("s")) {
451 return Err("Expected seconds".to_string());
452 }
453 let max = Numeric::from(i64::max_value() / 1000);
454 if num.value.abs() > max {
455 return Err(format!(
456 "Implementation error: Number is out of range ({:?})",
457 max
458 ));
459 }
460 let ms = &num.value * &Numeric::from(1000);
461 let (ms, rem) = ms.div_rem(&Numeric::from(1));
462 let ns = &rem * &Numeric::from(1_000_000_000);
463 Ok(Duration::milliseconds(ms.to_int().unwrap()) + Duration::nanoseconds(ns.to_int().unwrap()))
464}
465
466pub fn from_duration(duration: &Duration) -> Result<Number, String> {
467 let ms = duration.num_milliseconds();
468 let ns = (*duration - Duration::milliseconds(ms))
469 .num_nanoseconds()
470 .unwrap();
471 let ms_div = BigInt::from(1_000u64);
472 let ns_div = BigInt::from(1_000_000_000u64);
473 let ms = BigRat::ratio(&BigInt::from(ms), &ms_div);
474 let ns = BigRat::ratio(&BigInt::from(ns), &ns_div);
475 Ok(Number::new_unit(
476 Numeric::Rational(&ms + &ns),
477 BaseUnit::new("s"),
478 ))
479}
480
481pub fn parse_datepattern<I>(iter: &mut Peekable<I>) -> Result<Vec<DatePattern>, String>
482where
483 I: Iterator<Item = char>,
484{
485 let mut out = vec![];
486 while iter.peek().is_some() {
487 let res = match iter.peek().cloned().unwrap() {
488 '-' => DatePattern::Dash,
489 ':' => DatePattern::Colon,
490 '[' => {
491 iter.next();
492 let res = DatePattern::Optional(parse_datepattern(iter)?);
493 if iter.peek().cloned() != Some(']') {
494 return Err("Expected ]".to_string());
495 } else {
496 res
497 }
498 }
499 ']' => break,
500 '\'' => {
501 iter.next();
502 let mut buf = String::new();
503 while let Some(c) = iter.peek().cloned() {
504 if c == '\'' {
505 break;
506 } else {
507 iter.next();
508 buf.push(c);
509 }
510 }
511 DatePattern::Literal(buf)
512 }
513 x if x.is_whitespace() => {
514 while iter.peek().map(|c| c.is_whitespace()).unwrap_or(false) {
515 iter.next();
516 }
517 out.push(DatePattern::Space);
518 continue;
519 }
520 x if x.is_alphabetic() => {
521 let mut buf = String::new();
522 while let Some(c) = iter.peek().cloned() {
523 if c.is_alphanumeric() {
524 iter.next();
525 buf.push(c);
526 } else {
527 break;
528 }
529 }
530 out.push(DatePattern::Match(buf));
531 continue;
532 }
533 x => return Err(format!("Unrecognized character {}", x)),
534 };
535 out.push(res);
536 iter.next();
537 }
538 Ok(out)
539}
540
541pub fn parse_datefile(file: &str) -> Vec<Vec<DatePattern>> {
542 let mut defs = vec![];
543 for (num, line) in file.lines().enumerate() {
544 let line = line.split('#').next().unwrap();
545 let line = line.trim();
546 if line.is_empty() {
547 continue;
548 }
549 let res = parse_datepattern(&mut line.chars().peekable());
550 match res {
551 Ok(res) => defs.push(res),
552 Err(e) => println!("Line {}: {}: {}", num, e, line),
553 }
554 }
555 defs
556}
557
558pub fn humanize<Tz: TimeZone>(now: DateTime<Local>, date: DateTime<Tz>) -> Option<String> {
559 if cfg!(feature = "chrono-humanize") {
560 use chrono_humanize::HumanTime;
561 let now = now.with_timezone(&date.timezone());
562 let duration = date - now;
563 Some(HumanTime::from(duration).to_string())
564 } else {
565 None
566 }
567}
568
569#[cfg(test)]
570mod tests {
571 use super::*;
572
573 fn pattern(s: &str) -> Vec<DatePattern> {
574 parse_datepattern(&mut s.chars().peekable()).unwrap()
575 }
576
577 fn parse_with_tz(date: Vec<DateToken>, pat: &str) -> (Result<(), String>, Parsed, Option<Tz>) {
578 let mut parsed = Parsed::new();
579 let mut tz = None;
580 let pat = pattern(pat);
581 let res = parse_date(&mut parsed, &mut tz, &mut date.into_iter().peekable(), &pat);
582
583 (res, parsed, tz)
584 }
585
586 fn parse(date: Vec<DateToken>, pat: &str) -> (Result<(), String>, Parsed) {
587 let (res, parsed, _) = parse_with_tz(date, pat);
588 (res, parsed)
589 }
590
591 #[test]
592 fn test_literal() {
593 let date = vec![DateToken::Literal("abc".into())];
594 let (res, parsed) = parse(date.clone(), "'abc'");
595 assert_eq!(parsed, Parsed::new());
596 assert!(res.is_ok());
597
598 let (res, parsed) = parse(date, "'def'");
599 assert_eq!(parsed, Parsed::new());
600 assert_eq!(res, Err("Expected `def`, got `abc`".into()));
601 }
602
603 #[test]
604 fn test_year_plus() {
605 let mut expected = Parsed::new();
606 expected.set_year(123).unwrap();
607
608 let date = vec![
609 DateToken::Plus,
610 DateToken::Number(expected.year.unwrap().to_string(), None),
611 ];
612 let (res, parsed) = parse(date.clone(), "year");
613 assert!(res.is_ok());
614 assert_eq!(parsed, expected);
615
616 let date = vec![DateToken::Number(expected.year.unwrap().to_string(), None)];
617 let (res, parsed2) = parse(date.clone(), "year");
618 assert!(res.is_ok());
619 assert_eq!(parsed2, parsed);
620 }
621
622 #[test]
623 fn test_complicated_date_input() {
624 let mut expected = Parsed::new();
625 expected.set_year(123).unwrap();
626 expected.set_month(5).unwrap();
627 expected.set_day(2).unwrap();
628 expected.set_ampm(true).unwrap();
629 expected.set_hour(13).unwrap();
630 expected.set_minute(57).unwrap();
631
632 let date = vec![
633 DateToken::Number(expected.day.unwrap().to_string(), None),
634 DateToken::Space,
635 DateToken::Literal("Pm".into()),
636 DateToken::Dash,
637 DateToken::Number(format!("{:02}", expected.month.unwrap()), None),
638 DateToken::Colon,
639 DateToken::Number(expected.year.unwrap().to_string(), None),
640 DateToken::Space,
641 DateToken::Number(format!("{:02}", expected.hour_mod_12.unwrap()), None),
642 DateToken::Dash,
643 DateToken::Number(format!("{:02}", expected.minute.unwrap()), None),
644 DateToken::Space,
645 DateToken::Literal("May".into()),
646 ];
647
648 let (res, parsed) = parse(date, "day meridiem-monthnum:year hour12-min monthname");
649 assert!(res.is_ok());
650 assert_eq!(parsed, expected);
651 }
652
653 #[test]
654 fn ad_bc() {
655 let year = -100;
656 let mut expected = Parsed::new();
657 expected.set_year(year + 1).unwrap();
658 expected.set_hour(7).unwrap();
659
660 let date = vec![
661 DateToken::Number(year.abs().to_string(), None),
662 DateToken::Space,
663 DateToken::Literal("bce".into()),
664 DateToken::Space,
665 DateToken::Number(format!("{:02}", expected.hour_mod_12.unwrap()), None),
666 DateToken::Space,
667 DateToken::Literal("am".into()),
668 ];
669
670 let (res, parsed) = parse(date, "year adbc hour12 meridiem");
671 assert!(res.is_ok(), "{}", res.unwrap_err());
672 assert_eq!(parsed, expected);
673 }
674
675 #[test]
676 fn ad_bc_wrong() {
677 for date in vec![
678 vec![DateToken::Literal("foo".into())],
679 vec![DateToken::Plus],
680 ] {
681 let (res, _) = parse(date, "adbc");
682 assert!(res.is_err());
683 }
684 }
685
686 #[test]
687 fn wrong_length_24h() {
688 let date = vec![DateToken::Number("7".into(), None)];
689 let (res, _) = parse(date, "hour24");
690 assert_eq!(
691 res,
692 Err(format!("Expected 2-digit hour24, got out of range value 7"))
693 );
694 }
695
696 #[test]
697 fn test_24h() {
698 let (res, parsed) = parse(vec![DateToken::Number("23".into(), None)], "hour24");
699 assert!(res.is_ok());
700 assert_eq!(parsed.hour_div_12, Some(1));
701 assert_eq!(parsed.hour_mod_12, Some(11));
702 }
703
704 #[test]
705 fn seconds() {
706 let mut expected = Parsed::new();
707 expected.set_second(27).unwrap();
708 expected.set_nanosecond(12345).unwrap();
709
710 let date = vec![DateToken::Number("27".into(), Some("000012345".into()))];
711 let (res, parsed) = parse(date, "sec");
712 assert!(res.is_ok());
713 assert_eq!(parsed, expected);
714
715 let date = vec![DateToken::Number("27".into(), None)];
716 let (res, parsed) = parse(date, "sec");
717 assert!(res.is_ok());
718 expected.nanosecond = None;
719 assert_eq!(parsed, expected);
720 }
721
722 #[test]
723 fn test_offset() {
724 let date = vec![DateToken::Plus, DateToken::Number("0200".into(), None)];
725 let (res, parsed) = parse(date, "offset");
726 assert!(res.is_ok());
727 assert_eq!(parsed.offset, Some(2 * 3600));
728
729 let date = vec![
730 DateToken::Dash,
731 DateToken::Number("01".into(), None),
732 DateToken::Colon,
733 DateToken::Number("23".into(), None),
734 ];
735 let (res, parsed) = parse(date, "offset");
736 assert!(res.is_ok());
737 assert_eq!(parsed.offset, Some(-(1 * 60 + 23) * 60));
738
739 let date = vec![DateToken::Literal("Europe/London".into())];
740 let (res, parsed, tz) = parse_with_tz(date, "offset");
741 assert!(res.is_ok(), "{}", res.unwrap_err());
742 assert_eq!(tz.unwrap(), Tz::Europe__London);
743 assert_eq!(parsed.offset, None);
744 }
745
746 #[test]
747 fn test_weekday() {
748 let date = vec![DateToken::Literal("saturday".into())];
749 let (res, parsed) = parse(date, "weekday");
750 assert!(res.is_ok());
751 assert_eq!(parsed.weekday, Some(Weekday::Sat));
752
753 let date = vec![DateToken::Literal("sun".into())];
754 assert!(parse(date, "weekday").0.is_ok());
755
756 let date = vec![DateToken::Literal("snu".into())];
757 assert_eq!(parse(date, "weekday").0, Err("Unknown weekday: snu".into()));
758 }
759
760 #[test]
761 fn test_monthname() {
762 for (i, &s) in [
763 "jan", "feb", "mar", "apr", "may", "june", "jul", "AUGUST", "SEp", "Oct", "novemBer",
764 "dec",
765 ]
766 .iter()
767 .enumerate()
768 {
769 let date = vec![DateToken::Literal(s.into())];
770 let (res, parsed) = parse(date, "monthname");
771 assert!(res.is_ok());
772 assert_eq!(parsed.month, Some(i as u32 + 1));
773 }
774
775 let date = vec![DateToken::Literal("foobar".into())];
776 let (res, parsed) = parse(date, "monthname");
777 assert_eq!(res, Err("Unknown month name: foobar".into()));
778 assert_eq!(parsed.month, None);
779 }
780
781 #[test]
782 fn test_parse_datepattern() {
783 use self::DatePattern::*;
784
785 fn parse(s: &str) -> Result<Vec<DatePattern>, String> {
786 parse_datepattern(&mut s.chars().peekable())
787 }
788
789 assert_eq!(
790 parse("-:['abc']"),
791 Ok(vec![Dash, Colon, Optional(vec![Literal("abc".into())])])
792 );
793 assert!(parse("-:['abc'").is_err());
794 assert!(parse("*").is_err());
795 }
796
797 #[test]
798 fn test_attempt() {
799 use self::DateToken::*;
800 fn n(x: &str) -> DateToken {
801 Number(x.into(), None)
802 }
803
804 let now = Local::now();
805
806 macro_rules! check_attempt {
807 ($date:expr, $pat:expr) => {{
808 let pat = parse_datepattern(&mut $pat.chars().peekable()).unwrap();
809 attempt(now, $date, pat.as_ref())
810 }};
811 }
812
813 let tz = Literal("Europe/London".into());
814
815 let date = &[n("23"), Space, n("05"), Space, tz.clone()];
816 let res = check_attempt!(date, "hour24 min offset");
817 assert!(res.is_ok(), "{:?}", res);
818
819 let date = &[n("23"), Space, tz.clone()];
820 let res = check_attempt!(date, "hour24 offset");
821 assert_eq!(
822 res,
823 Err(("Failed to construct a useful datetime".into(), 0))
824 );
825
826 let date = &[n("2018"), Space, n("01"), Space, n("01"), Space, tz.clone()];
827 let res = check_attempt!(date, "year monthnum day offset");
828 assert!(res.is_ok(), "{:?}", res);
829 }
830}