You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

111 lines
3.2KB

  1. use chrono::prelude::*;
  2. use chrono_tz::Tz;
  3. lazy_static::lazy_static! {
  4. static ref HOUR_AMPM: regex::Regex = regex::Regex::new(r#"(?P<hr>\d{1,2})\s?(?P<ampm>(am|pm|AM|PM))"#).unwrap();
  5. static ref HOUR_MINUTE_AMPM: regex::Regex = regex::Regex::new(r#"(?P<hr>\d{1,2}):(?P<minute>\d{2})\s?(?P<ampm>(am|pm|AM|PM))"#).unwrap();
  6. }
  7. fn parse_time(time_input: &str) -> NaiveTime {
  8. let time: String = time_input.split_whitespace().collect::<Vec<_>>().join("");
  9. match NaiveTime::parse_from_str(&time, "%H:%M") {
  10. Ok(t) => return t,
  11. Err(_e) => {}
  12. }
  13. if HOUR_MINUTE_AMPM.is_match(&time) {
  14. return NaiveTime::parse_from_str(&time, "%I:%M%p").unwrap();
  15. }
  16. let t2 = HOUR_AMPM.replace(&time, "$hr:00$ampm");
  17. match NaiveTime::parse_from_str(&t2, "%I:%M%p") {
  18. Ok(t) => return t,
  19. Err(_e) => {}
  20. }
  21. panic!("failed to parse time -- try HH:MM format (input = '{}')", time)
  22. }
  23. fn convert_common_tz_abbrs(tz: &str) -> &str {
  24. let tz = tz.trim();
  25. if tz.eq_ignore_ascii_case("EST") { return "America/New_York" }
  26. if tz.eq_ignore_ascii_case("EPT") { return "America/New_York" }
  27. if tz.eq_ignore_ascii_case("EDT") { return "America/New_York" }
  28. if tz.eq_ignore_ascii_case("US/Eastern") { return "America/New_York" }
  29. if tz.eq_ignore_ascii_case("CET") { return "Europe/Brussels" }
  30. if tz.eq_ignore_ascii_case("CEST") { return "Europe/Brussels" }
  31. if tz.eq_ignore_ascii_case("Brussels") { return "Europe/Brussels" }
  32. tz
  33. }
  34. pub fn convert(from: &str, to: &str, time: &str, date: Option<NaiveDate>) {
  35. let from = convert_common_tz_abbrs(from);
  36. let to = convert_common_tz_abbrs(to);
  37. let fromtz: Tz = match from.parse() {
  38. Ok(tz) => tz,
  39. Err(e) => panic!("failed to parse from tz: {} (input = '{}')", e, from),
  40. };
  41. let totz: Tz = match to.parse() {
  42. Ok(tz) => tz,
  43. Err(e) => panic!("failed to parse to tz: {} (input = '{}')", e, to),
  44. };
  45. let tm = parse_time(time);
  46. let dt: Date<Tz> = match date {
  47. Some(dt) => fromtz.from_local_date(&dt).unwrap(),
  48. None => Utc::today().with_timezone(&fromtz),
  49. };
  50. let src = dt.and_time(tm).unwrap();
  51. let dst = src.with_timezone(&totz);
  52. let fromtz_str = fromtz.to_string();
  53. let totz_str = totz.to_string();
  54. let n_spaces = std::cmp::max(fromtz_str.len(), totz_str.len());
  55. let indent = " ";
  56. println!();
  57. println!("{} {} ... {} ... {}",
  58. indent,
  59. pad_spaces(fromtz_str, n_spaces),
  60. src.format("%a, %b %e"),
  61. src.format("%l:%M%P"),
  62. );
  63. println!();
  64. println!("{} {} ... {} ... {}",
  65. indent,
  66. pad_spaces(totz_str, n_spaces),
  67. dst.format("%a, %b %e"),
  68. dst.format("%l:%M%P"),
  69. );
  70. println!();
  71. }
  72. fn pad_spaces<S: ToString>(s: S, n: usize) -> String {
  73. let s = s.to_string();
  74. let mut out = String::with_capacity(n);
  75. out.push_str(&s);
  76. while out.len() < n {
  77. out.push_str(" ");
  78. }
  79. out
  80. }
  81. #[test]
  82. fn parse_time_like_2pm() {
  83. dbg!(parse_time("2pm"));
  84. assert_eq!(parse_time("2pm"), NaiveTime::from_hms(14, 0, 0));
  85. assert_eq!(parse_time("7am"), NaiveTime::from_hms(7, 0, 0));
  86. dbg!(HOUR_AMPM.replace("8:30am", "$hr:00$ampm"));
  87. assert_eq!(parse_time("8:30am"), NaiveTime::from_hms(8, 30, 0));
  88. }