Skip to content

Commit

Permalink
Feature/custom datetime format (#54)
Browse files Browse the repository at this point in the history
* Bump up version to `0.1.7`

* Replace CI badges from TravisCI to GitHub Actions.

* Remove redundant constants.

* Enable custom datetime format via environment variable `UT_DATETIME_FORMAT`.

* Refactor command options.

* Add description about `UT_DATETIME_FORMAT` on README.md.

* Update README.md about pre-built binary, and version.
  • Loading branch information
yoshihitoh authored Apr 28, 2020
1 parent c3b756a commit 05ddfea
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 56 deletions.
42 changes: 28 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ ut
ut is a command line tool to handle a unix timestamp.

[![Latest Version](https://img.shields.io/crates/v/ut-cli.svg)](https://crates.io/crates/ut-cli)
[![Build Status](https://travis-ci.com/yoshihitoh/ut-cli.svg?branch=master)](https://travis-ci.com/yoshihitoh/ut-cli)
![ci](https://github.com/yoshihitoh/ut-cli/workflows/ci/badge.svg)
![Dependabot](https://api.dependabot.com/badges/status?host=github&repo=yoshihitoh/ut-cli)

### Motivation
Expand Down Expand Up @@ -44,15 +44,15 @@ $ git clone https://github.com/yoshihitoh/ut-cli
$ cd ut-cli
$ cargo build --release
$ ./target/release/ut --version
ut 0.1.6
ut 0.1.7
```

Also there are pre-built binary for Linux and macOS.
Also there are pre-built binary for Linux, macOS and Windows.
See [releases](https://github.com/yoshihitoh/ut-cli/releases).

### Usage
``` bash
ut-cli 0.1.6
ut-cli 0.1.7
yoshihitoh <yoshihito.arih@gmail.com>
A command line tool to handle unix timestamp.

Expand All @@ -78,18 +78,32 @@ SUBCOMMANDS:

You can set options via envrionment variables.

| name | equiv option | example
|:------------:|:--------------:|:-----------
| UT_OFFSET | -o/--offset | 09:00
| UT_PRECISION | -p/--precision | millisecond
| name | equiv option | example
|:------------ :|:--------------:|:-----------
| UT_OFFSET | -o/--offset | 09:00
| UT_PRECISION | -p/--precision | millisecond
| UT_DATETIME_FORMAT | - | %Y-%m-%d %H:%M

```bash
# set variables
$ export UT_OFFSET='09:00'
$ export UT_PRECISION=millisecond
UT_DATETIME_FORMAT follows chrono's datetime specifiers.
See [the document](https://docs.rs/chrono/0.4.11/chrono/format/strftime/index.html) for details.

# run command without `-o` and `-p` option
$ ut p $(ut g)
```bash
# Set variables.
$ export UT_OFFSET='09:00' # Use JST(+9).
$ export UT_PRECISION=millisecond # Use timestamps in milliseconds.

# Generate a timestamp.
$ ut g
1588059756238

# Parse a timestamp.
$ echo 1588059756238 | ut p
2020-04-28 16:42:36.238 (+09:00)

# Change custom format and timezone.
$ export UT_DATETIME_FORMAT="%m/%d/%Y"
$ echo 1588059756238 | ut --offset=-7 p
04/28/2020
```

is equivalent to
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ mod app;
mod run;

pub use app::command;
pub use run::run;
pub use run::{run, GenerateRequest};
49 changes: 32 additions & 17 deletions src/cmd/generate/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,32 +121,47 @@ impl TryFrom<&ArgMatches<'_>> for GenerateOptions {
}
}

struct GenerateRequest<Tz: TimeZone> {
pub struct GenerateRequest<Tz: TimeZone> {
base: DateTime<Tz>,
deltas: Vec<DeltaItem>,
precision: Precision,
}

pub fn run<Tz, P>(m: &ArgMatches, provider: P, precision: Precision) -> Result<(), UtError>
impl<Tz> GenerateRequest<Tz>
where
Tz: TimeZone + Debug,
P: DateTimeProvider<Tz>,
{
let maybe_precision = Precision::find_by_name_opt(m.value_of("PRECISION"))
.context(UtErrorKind::PrecisionError)?;
if maybe_precision.is_some() {
eprintln!("-p PRECISION option is deprecated.");
pub fn new<P>(
m: &ArgMatches,
provider: P,
precision: Precision,
) -> Result<GenerateRequest<Tz>, UtError>
where
P: DateTimeProvider<Tz>,
{
let maybe_precision = Precision::find_by_name_opt(m.value_of("PRECISION"))
.context(UtErrorKind::PrecisionError)?;
if maybe_precision.is_some() {
eprintln!("-p PRECISION option is deprecated.");
}
let precision = maybe_precision.unwrap_or(precision);

let generate_options = GenerateOptions::try_from(m)?;
let base = generate_options.base_datetime(provider, precision)?;
let deltas = generate_options.deltas;
Ok(GenerateRequest {
base,
deltas,
precision,
})
}
let precision = maybe_precision.unwrap_or(precision);

let generate_options = GenerateOptions::try_from(m)?;
let base = generate_options.base_datetime(provider, precision)?;
let deltas = generate_options.deltas;
generate(GenerateRequest {
base,
deltas,
precision,
})
}

pub fn run<Tz>(request: GenerateRequest<Tz>) -> Result<(), UtError>
where
Tz: TimeZone + Debug,
{
generate(request)
}

fn generate<Tz: TimeZone>(request: GenerateRequest<Tz>) -> Result<(), UtError> {
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ mod app;
mod run;

pub use app::command;
pub use run::run;
pub use run::{run, ParseRequest};
51 changes: 40 additions & 11 deletions src/cmd/parse/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,51 @@ use crate::precision::Precision;
use crate::provider::DateTimeProvider;
use crate::read::{read_next, ReadError};

pub fn run<O, Tz, P>(m: &ArgMatches, provider: P, precision: Precision) -> Result<(), UtError>
#[derive(Debug)]
pub struct ParseRequest<P> {
provider: P,
precision: Precision,
datetime_format: String,
timestamp: i64,
}

impl<P> ParseRequest<P> {
pub fn new(
m: &ArgMatches,
provider: P,
precision: Precision,
datetime_format: Option<&str>,
) -> Result<ParseRequest<P>, UtError> {
let timestamp = get_timestamp(m.value_of("TIMESTAMP"))?;
let maybe_precision = Precision::find_by_name_opt(m.value_of("PRECISION"))
.context(UtErrorKind::PrecisionError)?;
if maybe_precision.is_some() {
eprintln!("-p PRECISION option is deprecated.");
}
let precision = maybe_precision.unwrap_or(precision);
let datetime_format = datetime_format
.unwrap_or_else(|| precision.preferred_format())
.to_string();

Ok(ParseRequest {
provider,
precision,
datetime_format,
timestamp,
})
}
}

pub fn run<O, Tz, P>(request: ParseRequest<P>) -> Result<(), UtError>
where
O: Offset + Display + Sized,
Tz: TimeZone<Offset = O> + Debug,
P: DateTimeProvider<Tz>,
{
let timestamp = get_timestamp(m.value_of("TIMESTAMP"))?;
let maybe_precision = Precision::find_by_name_opt(m.value_of("PRECISION"))
.context(UtErrorKind::PrecisionError)?;
if maybe_precision.is_some() {
eprintln!("-p PRECISION option is deprecated.");
}
let precision = maybe_precision.unwrap_or(precision);

let dt = precision.parse_timestamp(provider.timezone(), timestamp);
println!("{}", dt.format(precision.preferred_format()).to_string());
let dt = request
.precision
.parse_timestamp(request.provider.timezone(), request.timestamp);
println!("{}", dt.format(&request.datetime_format).to_string());
Ok(())
}

Expand Down
14 changes: 9 additions & 5 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
use std::env;

static OFFSET_KEY: &str = "UT_OFFSET";
static PRECISION_KEY: &str = "UT_PRECISION";

#[derive(Debug)]
pub struct Config {
offset: Option<String>,
precision: Option<String>,
datetime_format: Option<String>,
}

impl Config {
pub fn from_env() -> Config {
Config {
offset: env::var(OFFSET_KEY).ok(),
precision: env::var(PRECISION_KEY).ok(),
offset: env::var("UT_OFFSET").ok(),
precision: env::var("UT_PRECISION").ok(),
datetime_format: env::var("UT_DATETIME_FORMAT").ok(),
}
}

Expand All @@ -24,13 +23,18 @@ impl Config {
pub fn precision(&self) -> Option<&str> {
self.precision.as_deref()
}

pub fn datetime_format(&self) -> Option<&str> {
self.datetime_format.as_deref()
}
}

impl Default for Config {
fn default() -> Self {
Config {
offset: None,
precision: None,
datetime_format: None,
}
}
}
23 changes: 16 additions & 7 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use clap::{
};
use failure::ResultExt;

use crate::cmd::generate::GenerateRequest;
use crate::config::Config;
use crate::error::{UtError, UtErrorKind};
use crate::find::FindByName;
Expand Down Expand Up @@ -89,34 +90,42 @@ fn run() -> Result<(), UtError> {

if main_matches.is_present("UTC") {
let provider: UtcProvider = UtcProvider::from_timezone(Utc);
run_with(&main_matches, provider, precision)
run_with(&main_matches, provider, precision, &config)
} else if let Some(offset_text) = main_matches.value_of("OFFSET").or_else(|| config.offset()) {
let offset = Offset::from_str(offset_text)
.context(UtErrorKind::WrongTimeOffset)?
.into();
let provider: FixedOffsetProvider = FixedOffsetProvider::from_timezone(offset);
run_with(&main_matches, provider, precision)
run_with(&main_matches, provider, precision, &config)
} else {
let provider: LocalProvider = LocalProvider::from_timezone(Local);
run_with(&main_matches, provider, precision)
run_with(&main_matches, provider, precision, &config)
}
}

fn run_with<O, Tz, P>(
main_matches: &ArgMatches,
provider: P,
precision: Precision,
config: &Config,
) -> Result<(), UtError>
where
O: chrono::Offset + Display + Sized,
Tz: TimeZone<Offset = O> + Debug,
P: DateTimeProvider<Tz>,
{
match main_matches.subcommand() {
("generate", generate_matches) => {
cmd::generate::run(generate_matches.unwrap(), provider, precision)
}
("parse", parse_matches) => cmd::parse::run(parse_matches.unwrap(), provider, precision),
("generate", generate_matches) => cmd::generate::run(GenerateRequest::new(
generate_matches.unwrap(),
provider,
precision,
)?),
("parse", parse_matches) => cmd::parse::run(cmd::parse::ParseRequest::new(
parse_matches.unwrap(),
provider,
precision,
config.datetime_format(),
)?),
_ => panic!("never happen"),
}
}
Expand Down

0 comments on commit 05ddfea

Please sign in to comment.