1use std::ops::Add;
27use std::ops::Range;
28use std::ops::Sub;
29use std::sync::atomic::AtomicI32;
30use std::sync::atomic::Ordering;
31use std::time::SystemTime;
32
33use chrono::prelude::*;
34use chrono::Duration;
35use chrono::Local;
36use chrono::NaiveDateTime;
37use chrono::NaiveTime;
38use chrono::TimeZone;
39use regex::Regex;
40
41#[derive(Clone, Copy, Debug, PartialEq)]
45pub struct HgTime {
46 pub unixtime: u64,
47 pub offset: i32,
48}
49
50const DEFAULT_FORMATS: [&str; 44] = [
51 "%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M", "%Y-%m-%dT%H%M%S", "%Y-%m-%dT%H%M", "%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M", "%Y-%m-%d %H%M%S", "%Y-%m-%d %H%M", "%Y-%m-%d %I:%M:%S%p",
61 "%Y-%m-%d %H:%M",
62 "%Y-%m-%d %I:%M%p",
63 "%a %b %d %H:%M:%S %Y",
64 "%a %b %d %I:%M:%S%p %Y",
65 "%a, %d %b %Y %H:%M:%S", "%b %d %H:%M:%S %Y",
67 "%b %d %I:%M:%S%p %Y",
68 "%b %d %H:%M:%S",
69 "%b %d %I:%M:%S%p",
70 "%b %d %H:%M",
71 "%b %d %I:%M%p",
72 "%m-%d",
73 "%m/%d",
74 "%Y-%m-%d",
75 "%m/%d/%y",
76 "%m/%d/%Y",
77 "%b",
78 "%b %d",
79 "%b %Y",
80 "%b %d %Y",
81 "%I:%M%p",
82 "%H:%M",
83 "%H:%M:%S",
84 "%I:%M:%S%p",
85 "%Y",
86 "%Y-%m",
87 "%m/%d/%Y %I:%M:%S %P",
88 "%m/%d/%Y %I:%M:%S%p",
89 "%m/%d/%Y %H:%M:%S",
90 "%m/%d/%Y %I:%M%p",
91 "%m/%d/%Y %H:%M",
92 "%m/%d %I:%M:%S%p",
93 "%m/%d %H:%M:%S",
94 "%m/%d %I:%M%p",
95 "%m/%d %H:%M",
96];
97
98const TIME_OF_DAY_FORMATS: [&str; 6] = [
99 "%I:%M%p",
100 "%I:%M%P",
101 "%I:%M:%S%p",
102 "%I:%M:%S%P",
103 "%H:%M",
104 "%H:%M:%S",
105];
106
107const INVALID_OFFSET: i32 = i32::max_value();
108static DEFAUL_OFFSET: AtomicI32 = AtomicI32::new(INVALID_OFFSET);
109
110fn today() -> NaiveDate {
111 Local::now().date_naive()
112}
113
114impl HgTime {
115 pub fn now() -> Self {
116 let now: HgTime = Local::now().into();
117 now.use_default_offset()
118 }
119
120 pub fn parse(date: &str) -> Option<Self> {
128 match date {
129 "now" => Some(Self::now()),
130 "today" => Some(Self::from(today().and_hms_opt(0, 0, 0).unwrap()).use_default_offset()),
131 "yesterday" => Some(
132 Self::from(today().and_hms_opt(0, 0, 0).unwrap() - Duration::days(1))
133 .use_default_offset(),
134 ),
135 "tomorrow" => Some(
136 Self::from(today().and_hms_opt(0, 0, 0).unwrap() + Duration::days(1))
137 .use_default_offset(),
138 ),
139 "day after tomorrow" | "the day after tomorrow" | "overmorrow" => Some(
140 Self::from(today().and_hms_opt(0, 0, 0).unwrap() + Duration::days(2))
141 .use_default_offset(),
142 ),
143 date if match (
147 Regex::new(r"(?i)\+?.*([dhms]|ago|from now)$"),
148 Regex::new(r"(?i)(pm|am)$"),
149 ) {
150 (Ok(relative_re), Ok(ampm_re)) => {
151 relative_re.is_match(date) && !ampm_re.is_match(date)
152 }
153 _ => false,
154 } =>
155 {
156 let date = date.to_ascii_lowercase();
157 let plus = date.starts_with('+') && !date.ends_with("ago");
158 let from_now = date.ends_with("from now");
159 let mut duration_str = if plus { &date[1..] } else { &date };
161 duration_str = if date.ends_with("ago") {
162 &duration_str[..duration_str.len() - 4]
163 } else {
164 duration_str
165 };
166 duration_str = if from_now {
167 &duration_str[..duration_str.len() - 9]
168 } else {
169 duration_str
170 };
171
172 if plus || from_now {
173 duration_str
174 .parse::<humantime::Duration>()
175 .ok()
176 .map(|duration| Self::now() + duration.as_secs())
177 } else {
178 duration_str
179 .parse::<humantime::Duration>()
180 .ok()
181 .map(|duration| Self::now() - duration.as_secs())
182 }
183 }
184 date if match Regex::new(r"^\d{10}$") {
185 Ok(systime_re) => systime_re.is_match(date),
186 _ => false,
187 } =>
188 {
189 match date.parse::<u64>() {
190 Ok(d) => Some(
191 Self {
192 unixtime: d,
193 offset: 0,
194 }
195 .use_default_offset(),
196 ),
197 _ => None,
198 }
199 }
200 _ => Self::parse_absolute(date, default_date_lower),
201 }
202 }
203
204 #[allow(dead_code)]
212 pub fn parse_range(date: &str) -> Option<Range<Self>> {
213 match date {
214 "now" => {
215 let now = Self::now();
216 Some(now..now + 1)
217 }
218 "today" => {
219 let date = today();
220 let start = Self::from(date.and_hms_opt(0, 0, 0).unwrap()).use_default_offset();
221 let end =
222 Self::from(date.and_hms_opt(23, 59, 59).unwrap()).use_default_offset() + 1;
223 Some(start..end)
224 }
225 "yesterday" => {
226 let date = today() - Duration::days(1);
227 let start = Self::from(date.and_hms_opt(0, 0, 0).unwrap()).use_default_offset();
228 let end =
229 Self::from(date.and_hms_opt(23, 59, 59).unwrap()).use_default_offset() + 1;
230 Some(start..end)
231 }
232 "tomorrow" => {
233 let date = today() + Duration::days(1);
234 let start = Self::from(date.and_hms_opt(0, 0, 0).unwrap()).use_default_offset();
235 let end =
236 Self::from(date.and_hms_opt(23, 59, 59).unwrap()).use_default_offset() + 1;
237 Some(start..end)
238 }
239 "day after tomorrow" | "the day after tomorrow" | "overmorrow" => {
240 let date = today() + Duration::days(2);
241 let start = Self::from(date.and_hms_opt(0, 0, 0).unwrap()).use_default_offset();
242 let end =
243 Self::from(date.and_hms_opt(23, 59, 59).unwrap()).use_default_offset() + 1;
244 Some(start..end)
245 }
246 date if date.starts_with('>') => {
247 Self::parse(&date[1..]).map(|start| start..Self::max_value())
248 }
249 date if date.starts_with("since ") => {
250 Self::parse(&date[6..]).map(|start| start..Self::max_value())
251 }
252 date if date.starts_with('<') => {
253 Self::parse(&date[1..]).map(|end| Self::min_value()..end)
254 }
255 date if date.starts_with('-') => {
256 Self::parse_range(&format!("since {} days ago", &date[1..]))
259 }
260 date if date.starts_with("before ") => {
261 Self::parse(&date[7..]).map(|end| Self::min_value()..end)
262 }
263 date if date.contains(" to ") => {
264 let phrases: Vec<_> = date.split(" to ").collect();
265 if phrases.len() == 2 {
266 if let (Some(start), Some(end)) =
267 (Self::parse(phrases[0]), Self::parse(phrases[1]))
268 {
269 Some(start..end)
270 } else {
271 None
272 }
273 } else {
274 None
275 }
276 }
277 _ => {
278 let start = Self::parse_absolute(date, default_date_lower);
279 let end = Self::parse_absolute(date, default_date_upper::<N31>)
280 .or_else(|| Self::parse_absolute(date, default_date_upper::<N30>))
281 .or_else(|| Self::parse_absolute(date, default_date_upper::<N29>))
282 .or_else(|| Self::parse_absolute(date, default_date_upper::<N28>));
283 if let (Some(start), Some(end)) = (start, end) {
284 Some(start..end + 1)
285 } else {
286 None
287 }
288 }
289 }
290 }
291
292 fn parse_absolute(date: &str, default_date: fn(char) -> &'static str) -> Option<Self> {
299 let date = date.trim();
300
301 let parts: Vec<_> = date.split(' ').collect();
303 if parts.len() == 2 {
304 if let Ok(unixtime) = parts[0].parse() {
305 if let Ok(offset) = parts[1].parse() {
306 if is_valid_offset(offset) {
307 return Some(Self { unixtime, offset });
308 }
309 }
310 }
311 }
312
313 let date = if date.ends_with("GMT") || date.ends_with("UTC") {
316 format!("{} +0000", &date[..date.len() - 3])
317 } else {
318 date.to_string()
319 };
320 let mut now = None; for naive_format in DEFAULT_FORMATS.iter() {
324 let mut default_format = String::new();
328 let mut date_with_defaults = date.clone();
329 let mut use_now = false;
330 for part in ["S", "M", "HI", "d", "mb", "Yy"] {
331 if part
332 .chars()
333 .any(|ch| naive_format.contains(&format!("%{}", ch)))
334 {
335 use_now = true;
339 } else {
340 let format_char = part.chars().nth(0).unwrap();
341 default_format += &format!(" @%{}", format_char);
342 if use_now {
343 let now = now.get_or_insert_with(Local::now);
347 date_with_defaults +=
348 &format!(" @{}", now.format(&format!("%{}", format_char)));
349 } else {
350 date_with_defaults += " @";
354 date_with_defaults += default_date(format_char);
355 }
356 }
357 }
358
359 let format = format!("{}%#z{}", naive_format, default_format);
362 if let Ok(parsed) = DateTime::parse_from_str(&date_with_defaults, &format) {
363 return Some(parsed.into());
364 }
365
366 let format = format!("{}{}", naive_format, default_format);
368 if let Ok(parsed) = NaiveDateTime::parse_from_str(&date_with_defaults, &format) {
369 return Some(parsed.into());
370 }
371 }
372
373 None
374 }
375
376 pub fn parse_time_of_day(date: &str) -> Option<NaiveTime> {
377 for naive_format in TIME_OF_DAY_FORMATS.iter() {
378 let format = naive_format.to_string();
379 if let Ok(parsed) = NaiveTime::parse_from_str(date, &format) {
380 return Some(parsed);
381 }
382 }
383
384 None
385 }
386
387 fn extract_local_date_from_system_time(system_time: SystemTime) -> Option<NaiveDate> {
388 if let Ok(system_time_as_duration) = system_time.duration_since(SystemTime::UNIX_EPOCH) {
389 return Some(
390 Local
391 .timestamp_opt(system_time_as_duration.as_secs() as i64, 0)
392 .unwrap()
393 .date_naive(),
394 );
395 }
396
397 None
398 }
399
400 pub fn time_of_day_relative_to_system_time(
401 system_time: SystemTime,
402 time_of_day: NaiveTime,
403 ) -> Option<SystemTime> {
404 if let Some(date_from_system_time) = Self::extract_local_date_from_system_time(system_time)
405 {
406 let naive_date_with_time_of_day =
407 NaiveDateTime::new(date_from_system_time, time_of_day);
408 if let Some(local_time) = Local
409 .from_local_datetime(&naive_date_with_time_of_day)
410 .single()
411 {
412 let local_system_time = SystemTime::UNIX_EPOCH
413 + std::time::Duration::from_secs(local_time.timestamp() as u64);
414 return Some(local_system_time);
415 }
416 }
417
418 None
419 }
420
421 fn use_default_offset(mut self) -> Self {
424 let offset = DEFAUL_OFFSET.load(Ordering::SeqCst);
425 if is_valid_offset(offset) {
426 self.offset = offset
427 }
428 self
429 }
430
431 pub fn min_value() -> Self {
432 Self {
433 unixtime: 0,
434 offset: 0,
435 }
436 }
437
438 pub fn max_value() -> Self {
439 Self {
440 unixtime: u64::max_value() >> 2,
441 offset: 0,
442 }
443 }
444}
445
446impl Add<u64> for HgTime {
447 type Output = Self;
448
449 fn add(self, seconds: u64) -> Self {
450 Self {
451 unixtime: self.unixtime + seconds,
452 offset: self.offset,
453 }
454 }
455}
456
457impl Sub<u64> for HgTime {
458 type Output = Self;
459
460 fn sub(self, seconds: u64) -> Self {
461 Self {
462 unixtime: self.unixtime.max(seconds) - seconds,
464 offset: self.offset,
465 }
466 }
467}
468
469impl PartialOrd for HgTime {
470 fn partial_cmp(&self, other: &HgTime) -> Option<std::cmp::Ordering> {
471 self.unixtime.partial_cmp(&other.unixtime)
472 }
473}
474
475impl<Tz: TimeZone> From<DateTime<Tz>> for HgTime {
476 fn from(time: DateTime<Tz>) -> Self {
477 assert!(time.timestamp() >= 0);
478 Self {
479 unixtime: time.timestamp() as u64,
480 offset: time.offset().fix().utc_minus_local(),
481 }
482 }
483}
484
485impl From<NaiveDateTime> for HgTime {
486 fn from(time: NaiveDateTime) -> Self {
487 let timestamp = time.timestamp();
488 let offset = Self::now().offset;
490 let unixtime = (timestamp + offset as i64).max(0) as u64;
492 Self { unixtime, offset }
493 }
494}
495
496#[allow(dead_code)]
498pub fn set_default_offset(offset: i32) {
499 DEFAUL_OFFSET.store(offset, Ordering::SeqCst);
500}
501
502fn is_valid_offset(offset: i32) -> bool {
503 (-50400..=43200).contains(&offset)
505}
506
507fn default_date_lower(format_char: char) -> &'static str {
509 match format_char {
510 'H' | 'M' | 'S' => "00",
511 'm' | 'd' => "1",
512 _ => unreachable!(),
513 }
514}
515
516trait ToStaticStr {
517 fn to_static_str() -> &'static str;
518}
519
520struct N31;
521struct N30;
522struct N29;
523struct N28;
524
525impl ToStaticStr for N31 {
526 fn to_static_str() -> &'static str {
527 "31"
528 }
529}
530
531impl ToStaticStr for N30 {
532 fn to_static_str() -> &'static str {
533 "30"
534 }
535}
536
537impl ToStaticStr for N29 {
538 fn to_static_str() -> &'static str {
539 "29"
540 }
541}
542
543impl ToStaticStr for N28 {
544 fn to_static_str() -> &'static str {
545 "28"
546 }
547}
548
549fn default_date_upper<N: ToStaticStr>(format_char: char) -> &'static str {
551 match format_char {
552 'H' => "23",
553 'M' | 'S' => "59",
554 'm' => "12",
555 'd' => N::to_static_str(),
556 _ => unreachable!(),
557 }
558}
559
560#[cfg(test)]
561mod tests {
562 use super::*;
563
564 #[test]
565 fn test_parse_date() {
566 set_default_offset(7200);
569
570 assert_eq!(t("2006-02-01 13:00:30"), "1138806030 7200");
575 assert_eq!(t("2006-02-01 13:00:30-0500"), "1138816830 18000");
576 assert_eq!(t("2006-02-01 13:00:30 +05:00"), "1138780830 -18000");
577 assert_eq!(t("2006-02-01 13:00:30Z"), "1138798830 0");
578 assert_eq!(t("2006-02-01 13:00:30 GMT"), "1138798830 0");
579 assert_eq!(t("2006-4-5 13:30"), "1144251000 7200");
580 assert_eq!(t("1150000000 14400"), "1150000000 14400");
581 assert_eq!(t("100000 1400000"), "fail");
582 assert_eq!(t("1000000000 -16200"), "1000000000 -16200");
583 assert_eq!(t("2006-02-01 1:00:30PM +0000"), "1138798830 0");
584
585 assert_eq!(d("1:00:30PM +0000", Duration::days(1)), "0");
586 assert_eq!(d("02/01", Duration::weeks(52)), "0");
587 assert_eq!(d("today", Duration::days(1)), "0");
588 assert_eq!(d("yesterday", Duration::days(2)), "0");
589 assert_eq!(d("tomorrow", Duration::days(1)), "0");
590 assert_eq!(d("day after tomorrow", Duration::days(2)), "0");
591 assert_eq!(d("overmorrow", Duration::days(2)), "0");
592
593 assert_eq!(t("2016-07-27T12:10:21"), "1469628621 7200");
595 assert_eq!(t("2016-07-27T12:10:21Z"), "1469621421 0");
596 assert_eq!(t("2016-07-27T12:10:21+00:00"), "1469621421 0");
597 assert_eq!(t("2016-07-27T121021Z"), "1469621421 0");
598 assert_eq!(t("2016-07-27 12:10:21"), "1469628621 7200");
599 assert_eq!(t("2016-07-27 12:10:21Z"), "1469621421 0");
600 assert_eq!(t("2016-07-27 12:10:21+00:00"), "1469621421 0");
601 assert_eq!(t("2016-07-27 121021Z"), "1469621421 0");
602
603 assert_eq!(t("Jan 2018"), "1514772000 7200");
605 assert_eq!(t("Feb 2018"), "1517450400 7200");
606 assert_eq!(t("Mar 2018"), "1519869600 7200");
607 assert_eq!(t("Apr 2018"), "1522548000 7200");
608 assert_eq!(t("May 2018"), "1525140000 7200");
609 assert_eq!(t("Jun 2018"), "1527818400 7200");
610 assert_eq!(t("Jul 2018"), "1530410400 7200");
611 assert_eq!(t("Sep 2018"), "1535767200 7200");
612 assert_eq!(t("Oct 2018"), "1538359200 7200");
613 assert_eq!(t("Nov 2018"), "1541037600 7200");
614 assert_eq!(t("Dec 2018"), "1543629600 7200");
615 assert_eq!(t("Foo 2018"), "fail");
616
617 assert_eq!(d("Jan", Duration::weeks(52)), "0");
619 assert_eq!(d("Jan 1", Duration::weeks(52)), "0"); assert_eq!(d("4-26", Duration::weeks(52)), "0");
621 assert_eq!(d("4/26", Duration::weeks(52)), "0");
622 assert_eq!(t("4/26/2000"), "956714400 7200");
623 assert_eq!(t("Apr 26 2000"), "956714400 7200");
624 assert_eq!(t("2020"), "1577844000 7200"); assert_eq!(t("2020 GMT"), "1577836800 0");
626 assert_eq!(t("2020-12"), "1606788000 7200");
627 assert_eq!(t("2020-13"), "fail");
628
629 assert_eq!(t("Fri, 20 Sep 2019 12:15:13 -0700"), "1569006913 25200"); assert_eq!(t("Fri, 20 Sep 2019 12:15:13"), "1568988913 7200");
631
632 assert_eq!(t("09/20/2019 12:15:13"), "1568988913 7200");
633 assert_eq!(t("09/20/2019 12:15"), "1568988900 7200");
634 assert_eq!(t("09/20/2019 12:15:13PM"), "1568988913 7200");
635 assert_eq!(t("09/20/2019 12:15PM"), "1568988900 7200");
636 assert_eq!(t("09/20 12:15:13"), t("Sep 20 12:15:13"));
637 assert_eq!(t("09/20 12:15"), t("Sep 20 12:15:00"));
638 assert_eq!(t("09/20 12:15:13PM"), t("Sep 20 12:15:13"));
639 assert_eq!(t("09/20 12:15PM"), t("Sep 20 12:15:00"));
640 }
641
642 #[test]
643 fn test_parse_ago() {
644 set_default_offset(7200);
645 assert_eq!(d("10m ago", Duration::hours(1)), "0");
649 assert_eq!(d("10 min ago", Duration::hours(1)), "0");
650 assert_eq!(d("10 minutes ago", Duration::hours(1)), "0");
651 assert_eq!(d("10 hours ago", Duration::days(1)), "0");
652 assert_eq!(d("10 h ago", Duration::days(1)), "0");
653 assert_eq!(t("9999999 years ago"), "0 7200");
654 }
655
656 #[test]
657 fn test_parse_from_now() {
658 set_default_offset(7200);
659 assert_eq!(d("10m from now", Duration::hours(1)), "0");
663 assert_eq!(d("10 min from now", Duration::hours(1)), "0");
664 assert_eq!(d("10 minutes from now", Duration::hours(1)), "0");
665 assert_eq!(d("10 hours from now", Duration::days(1)), "0");
666 assert_eq!(d("10 h from now", Duration::days(1)), "0");
667 }
668
669 #[test]
670 fn test_parse_ago_short() {
671 set_default_offset(7200);
672 assert!(diff_from_now("10m", Duration::minutes(10)) < 2);
673 assert!(diff_from_now("2d", Duration::days(2)) < 2);
674 assert!(diff_from_now("10s", Duration::seconds(10)) < 2);
675 assert!(diff_from_now("10h", Duration::hours(10)) < 2);
676 assert!(diff_from_now("10h10m5s", Duration::seconds(36_605)) < 2);
677 assert!(diff_from_now("10h5s10m", Duration::seconds(36_605)) < 2);
678 assert!(diff_from_now("10H5s10M", Duration::seconds(36_605)) < 2);
679 assert_eq!(diff_from_now("10AM", Duration::minutes(10)), -1);
681 assert_eq!(diff_from_now("10hm", Duration::minutes(10)), -1);
682 }
683
684 #[test]
685 fn test_parse_from_now_short() {
686 set_default_offset(7200);
687 assert!(future_diff_from_now("+10m", Duration::minutes(10)) < 2);
688 assert!(future_diff_from_now("+2d", Duration::days(2)) < 2);
689 assert!(future_diff_from_now("+10s", Duration::seconds(10)) < 2);
690 assert!(future_diff_from_now("+10h", Duration::hours(10)) < 2);
691 assert!(future_diff_from_now("+10h10m5s", Duration::seconds(36_605)) < 2);
692 assert!(future_diff_from_now("+10h5s10m", Duration::seconds(36_605)) < 2);
693 assert!(future_diff_from_now("+10H5s10M", Duration::seconds(36_605)) < 2);
694 assert_eq!(future_diff_from_now("+10AM", Duration::minutes(10)), -1);
696 assert_eq!(future_diff_from_now("+10hm", Duration::minutes(10)), -1);
697 }
698
699 #[test]
700 fn test_parse_range() {
701 set_default_offset(7200);
702
703 assert_eq!(c("today", "tomorrow"), "does not contain");
704 assert_eq!(c("tomorrow", "overmorrow"), "does not contain");
705 assert_eq!(c("the day after tomorrow", "overmorrow"), "contains");
706 assert_eq!(c("overmorrow", "1 days from now"), "does not contain");
707 assert_eq!(c("overmorrow", "2 days from now"), "contains");
708 assert_eq!(c("overmorrow", "3 days from now"), "does not contain");
709 assert_eq!(c("since 1 month ago", "now"), "contains");
710 assert_eq!(c("since 1 month ago", "2 months ago"), "does not contain");
711 assert_eq!(c("> 1 month ago", "2 months ago"), "does not contain");
712 assert_eq!(c("< 1 month ago", "2 months ago"), "contains");
713 assert_eq!(c("< 1 month ago", "now"), "does not contain");
714
715 assert_eq!(c("-3", "now"), "contains");
716 assert_eq!(c("-3", "2 days ago"), "contains");
717 assert_eq!(c("-3", "4 days ago"), "does not contain");
718
719 assert_eq!(c("2018", "2017-12-31 23:59:59"), "does not contain");
720 assert_eq!(c("2018", "2018-1-1"), "contains");
721 assert_eq!(c("2018", "2018-12-31 23:59:59"), "contains");
722 assert_eq!(c("2018", "2019-1-1"), "does not contain");
723
724 assert_eq!(c("2018-5-1 to 2018-6-2", "2018-4-30"), "does not contain");
725 assert_eq!(c("2018-5-1 to 2018-6-2", "2018-5-30"), "contains");
726 assert_eq!(c("2018-5-1 to 2018-6-2", "2018-6-30"), "does not contain");
727 }
728
729 fn t(date: &str) -> String {
731 match HgTime::parse(date) {
732 Some(time) => format!("{} {}", time.unixtime, time.offset),
733 None => "fail".to_string(),
734 }
735 }
736
737 fn d(date: &str, duration: Duration) -> String {
739 match HgTime::parse(date) {
740 Some(time) => {
741 let value = (time.unixtime as i64 - HgTime::now().unixtime as i64).abs()
742 / duration.num_seconds();
743 format!("{}", value)
744 }
745 None => "fail".to_string(),
746 }
747 }
748
749 fn c(range: &str, date: &str) -> &'static str {
752 if let (Some(range), Some(date)) = (HgTime::parse_range(range), HgTime::parse(date)) {
753 if range.contains(&date) {
754 "contains"
755 } else {
756 "does not contain"
757 }
758 } else {
759 "fail"
760 }
761 }
762
763 fn diff_from_now(date: &str, expected: Duration) -> i64 {
767 match HgTime::parse(date) {
768 Some(time) => {
769 let now = HgTime::now().unixtime as i64;
770 let before = time.unixtime as i64;
771 let expected_diff = expected.num_seconds();
772 (now - before - expected_diff).abs()
773 }
774 None => -1,
775 }
776 }
777
778 fn future_diff_from_now(date: &str, expected: Duration) -> i64 {
782 match HgTime::parse(date) {
783 Some(time) => {
784 let now = HgTime::now().unixtime as i64;
785 let future = time.unixtime as i64;
786 let expected_diff = expected.num_seconds();
787 (future - now - expected_diff).abs()
788 }
789 None => -1,
790 }
791 }
792}