use std::str::FromStr; use chrono::prelude::*; use chrono_tz::Tz; use colored::*; lazy_static::lazy_static! { static ref HOUR_AMPM: regex::Regex = regex::Regex::new(r#"(?P
\d{1,2})\s?(?P(am|pm|AM|PM))"#).unwrap(); static ref HOUR_MINUTE_AMPM: regex::Regex = regex::Regex::new(r#"(?P
\d{1,2}):(?P\d{2})\s?(?P(am|pm|AM|PM))"#).unwrap(); } fn parse_time(time_input: &str) -> NaiveTime { let time: String = time_input.split_whitespace().collect::>().join(""); match NaiveTime::parse_from_str(&time, "%H:%M") { Ok(t) => return t, Err(_e) => {} } if HOUR_MINUTE_AMPM.is_match(&time) { return NaiveTime::parse_from_str(&time, "%I:%M%p").unwrap(); } let t2 = HOUR_AMPM.replace(&time, "$hr:00$ampm"); match NaiveTime::parse_from_str(&t2, "%I:%M%p") { Ok(t) => return t, Err(_e) => {} } panic!("failed to parse time -- try HH:MM format (input = '{}')", time) } fn convert_common_tz_abbrs(tz: &str) -> &str { let tz = tz.trim(); if tz.eq_ignore_ascii_case("EST") { return "America/New_York" } if tz.eq_ignore_ascii_case("EPT") { return "America/New_York" } if tz.eq_ignore_ascii_case("EDT") { return "America/New_York" } if tz.eq_ignore_ascii_case("US/Eastern") { return "America/New_York" } if tz.eq_ignore_ascii_case("CET") { return "Europe/Brussels" } if tz.eq_ignore_ascii_case("CEST") { return "Europe/Brussels" } if tz.eq_ignore_ascii_case("Brussels") { return "Europe/Brussels" } tz } pub fn convert(from: &str, to: &str, time: &str, day: Option, date: Option) { let from = convert_common_tz_abbrs(from); let to = convert_common_tz_abbrs(to); let fromtz: Tz = match from.parse() { Ok(tz) => tz, Err(e) => panic!("failed to parse from tz: {} (input = '{}')", e, from), }; let totz: Tz = match to.parse() { Ok(tz) => tz, Err(e) => panic!("failed to parse to tz: {} (input = '{}')", e, to), }; let tm = parse_time(time); let dt: Date = match date { Some(dt) => fromtz.from_local_date(&dt).unwrap(), None => Utc::today().with_timezone(&fromtz), }; let dt = match day { Some(day_str) => { let target_day_of_week: Weekday = Weekday::from_str(&day_str) .map_err(|e| { eprintln!("failed to parse day (input = '{}')", day_str); e }).unwrap(); let mut cur = dt; while cur.weekday() != target_day_of_week { cur = cur.succ(); } cur } None => dt, }; let src = dt.and_time(tm).unwrap(); let dst = src.with_timezone(&totz); let fromtz_str = fromtz.to_string(); let totz_str = totz.to_string(); let n_spaces = std::cmp::max(fromtz_str.len(), totz_str.len()); let indent = " "; println!(); println!("{}{} ... {} ... {}", indent, pad_spaces(fromtz_str, n_spaces), src.format("%a, %b %e"), src.format("%l:%M%P"), ); println!(); let dst_line = format!("{}{} ... {} ... {}", indent, pad_spaces(totz_str, n_spaces), dst.format("%a, %b %e"), dst.format("%l:%M%P").to_string(), ); println!("{}", dst_line.bold()); println!(); } fn pad_spaces(s: S, n: usize) -> String { let s = s.to_string(); let mut out = String::with_capacity(n); out.push_str(&s); while out.len() < n { out.push_str(" "); } out } #[test] fn parse_time_like_2pm() { dbg!(parse_time("2pm")); assert_eq!(parse_time("2pm"), NaiveTime::from_hms(14, 0, 0)); assert_eq!(parse_time("7am"), NaiveTime::from_hms(7, 0, 0)); dbg!(HOUR_AMPM.replace("8:30am", "$hr:00$ampm")); assert_eq!(parse_time("8:30am"), NaiveTime::from_hms(8, 30, 0)); }