Browse Source

initial commit

tags/v1.0.0
Jonathan Strong 1 year ago
commit
cab79d44ec
7 changed files with 229 additions and 0 deletions
  1. +3
    -0
      .gitignore
  2. +29
    -0
      Cargo.toml
  3. +16
    -0
      justfile
  4. +19
    -0
      src/cet2est.rs
  5. +19
    -0
      src/est2cet.rs
  6. +110
    -0
      src/lib.rs
  7. +33
    -0
      src/main.rs

+ 3
- 0
.gitignore View File

@@ -0,0 +1,3 @@
/target
Cargo.lock
*.swp

+ 29
- 0
Cargo.toml View File

@@ -0,0 +1,29 @@
[package]
name = "tzconvert"
version = "1.0.0"
edition = "2018"
authors = ["Jonathan Strong <jstrong@mmcxi.com>"]

[lib]
name = "tzconvert"
path = "src/lib.rs"

[[bin]]
name = "tzconvert"
path = "src/main.rs"

[[bin]]
name = "cet2est"
path = "src/cet2est.rs"

[[bin]]
name = "est2cet"
path = "src/est2cet.rs"

[dependencies]
chrono = { version = "0.4", features = ["serde"] }
chrono-tz = { version = "0.5", features = ["serde"] }
structopt = "0.3"
regex = "1.5"
lazy_static = "1.4"


+ 16
- 0
justfile View File

@@ -0,0 +1,16 @@
release-build:
cargo build --bin tzconvert --release
cargo build --bin cet2est --release
cargo build --bin est2cet --release

install: release-build
cp target/release/tzconvert ~/.cargo/bin/
cp target/release/cet2est ~/.cargo/bin/
cp target/release/est2cet ~/.cargo/bin/

debug-build:
cargo build --bin tzconvert
cargo build --bin cet2est
cargo build --bin est2cet



+ 19
- 0
src/cet2est.rs View File

@@ -0,0 +1,19 @@
use structopt::StructOpt;
use chrono::prelude::*;

#[derive(StructOpt, Debug)]
#[structopt(author = env!("CARGO_PKG_AUTHORS"))]
struct Opt {
/// time to convert
#[structopt(value_name = "TIME")]
time: String,

/// specify the date on which the time lies. defaults to today.
#[structopt(long, short)]
date: Option<NaiveDate>,
}

fn main() {
let Opt { time, date } = Opt::from_args();
tzconvert::convert("Europe/Brussels", "EST", &time, date);
}

+ 19
- 0
src/est2cet.rs View File

@@ -0,0 +1,19 @@
use structopt::StructOpt;
use chrono::prelude::*;

#[derive(StructOpt, Debug)]
#[structopt(author = env!("CARGO_PKG_AUTHORS"))]
struct Opt {
/// time to convert
#[structopt(value_name = "HH:MM")]
time: String,

/// specify the date on which the time lies. defaults to today.
#[structopt(long, short)]
date: Option<NaiveDate>,
}

fn main() {
let Opt { time, date } = Opt::from_args();
tzconvert::convert("EST", "Europe/Brussels", &time, date);
}

+ 110
- 0
src/lib.rs View File

@@ -0,0 +1,110 @@
use chrono::prelude::*;
use chrono_tz::Tz;

lazy_static::lazy_static! {
static ref HOUR_AMPM: regex::Regex = regex::Regex::new(r#"(?P<hr>\d{1,2})\s?(?P<ampm>(am|pm|AM|PM))"#).unwrap();
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();
}

fn parse_time(time_input: &str) -> NaiveTime {
let time: String = time_input.split_whitespace().collect::<Vec<_>>().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, date: Option<NaiveDate>) {
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<Tz> = match date {
Some(dt) => fromtz.from_local_date(&dt).unwrap(),
None => Utc::today().with_timezone(&fromtz),
};

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!();

println!("{} {} ... {} ... {}",
indent,
pad_spaces(totz_str, n_spaces),
dst.format("%a, %b %e"),
dst.format("%l:%M%P"),
);

println!();
}

fn pad_spaces<S: ToString>(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));
}

+ 33
- 0
src/main.rs View File

@@ -0,0 +1,33 @@
use structopt::StructOpt;
use chrono::prelude::*;

/// timezone converter
///
/// Examples:
///
/// - tzconvert CET EST 2pm
///
/// - tzconvert Africa/Timbuktu America/Jamaica 18:30 --date 2021-11-03
///
#[derive(StructOpt, Debug)]
#[structopt(author = env!("CARGO_PKG_AUTHORS"))]
struct Opt {
/// originating time zone
#[structopt(value_name = "FROM TIMEZONE")]
from: String,
/// destination time zone
#[structopt(value_name = "TO TIMEZONE")]
to: String,
/// time to convert (HH:MM or HH[:MM]am/HH[:MM]pm)
#[structopt(value_name = "TIME")]
time: String,

/// specify the date on which the time lies; defaults to today
#[structopt(long, short)]
date: Option<NaiveDate>,
}

fn main() {
let Opt { from, to, time, date } = Opt::from_args();
tzconvert::convert(&from, &to, &time, date);
}

Loading…
Cancel
Save