Compare commits
27 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a86e930f9 | ||
|
|
e2c0b08c03 | ||
|
|
51a200d1a3 | ||
|
|
2b55df907b | ||
|
|
9087e69c79 | ||
|
|
b6d1c0ea0c | ||
|
|
7a6aa2489b | ||
|
|
27b9345377 | ||
|
|
a11d5c0f5d | ||
|
|
b6df8af541 | ||
|
|
ada5e10483 | ||
|
|
259948ef1c | ||
|
|
63861a8d32 | ||
|
|
0884006809 | ||
|
|
283876ebda | ||
|
|
6137e6c046 | ||
|
|
fb725bbaee | ||
|
|
df02ab447b | ||
|
|
fc07ffb480 | ||
|
|
7d90764cbd | ||
|
|
1f532ead7f | ||
|
|
98d5fbe232 | ||
|
|
6b300b3d0a | ||
|
|
1b49361f59 | ||
|
|
708d2bb049 | ||
|
|
5a79895f92 | ||
|
|
c18cec94a1 |
8 changed files with 448 additions and 29 deletions
9
.envrc
Normal file
9
.envrc
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#!/usr/bin/env bash
|
||||
# the shebang is ignored, but nice for editors
|
||||
|
||||
if type -P lorri &>/dev/null; then
|
||||
eval "$(lorri direnv)"
|
||||
else
|
||||
echo 'while direnv evaluated .envrc, could not find the command "lorri" [https://github.com/nix-community/lorri]'
|
||||
use nix
|
||||
fi
|
||||
241
Cargo.lock
generated
241
Cargo.lock
generated
|
|
@ -2,6 +2,15 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
|
|
@ -17,12 +26,73 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is-terminal",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.13.0"
|
||||
|
|
@ -56,12 +126,92 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1640e5cc7fb47dbb8338fd471b105e7ed6c3cb2aeb00c2e067127ffd3764a05d"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98c59138d527eeaf9b53f35a77fcc1fad9d883116070c63d5de1c7dc7b00c72b"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
|
||||
dependencies = [
|
||||
"errno-dragonfly",
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno-dragonfly"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.57"
|
||||
|
|
@ -85,6 +235,17 @@ dependencies = [
|
|||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.64"
|
||||
|
|
@ -100,12 +261,24 @@ version = "0.2.147"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.15"
|
||||
|
|
@ -139,6 +312,54 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac5ffa1efe7548069688cd7028f32591853cd7b5b756d41bcffd2353e4fc75b4"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.23"
|
||||
|
|
@ -163,9 +384,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "time-track"
|
||||
version = "0.1.0"
|
||||
version = "2.1.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"clap",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -174,6 +398,12 @@ version = "1.0.10"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.0+wasi-snapshot-preview1"
|
||||
|
|
@ -265,6 +495,15 @@ dependencies = [
|
|||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.1"
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
[package]
|
||||
name = "time-track"
|
||||
version = "0.1.0"
|
||||
version = "2.1.4"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.72"
|
||||
chrono = "0.4.26"
|
||||
clap = { version = "4.3.11", features = ["derive"] }
|
||||
regex = "1.11.1"
|
||||
|
|
|
|||
22
README.md
Normal file
22
README.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# time-track
|
||||
|
||||
A simple CLI tool for tracking how much time you have left to work. I find myself stressing about whether I'm hitting 8 real hours, so this little tool helps me avoid wasting
|
||||
time calculating when my work day will end.
|
||||
|
||||
Simply enter times, one per line and send an EOF character when you're done. The first lines opens a span of work and the next line closes it so that you can build up working
|
||||
time be clocking in and out. Finally, you can send additional arguments to the program to configure how long you intend to work (the default is 8 hours).
|
||||
|
||||
```
|
||||
❯ time-track
|
||||
Working for 8 hours
|
||||
Input times one per line. Send an EOF character to finish inputting...
|
||||
8:30
|
||||
9:30
|
||||
11:15
|
||||
12:30
|
||||
13:16
|
||||
18:20
|
||||
19:30
|
||||
20:11 # Send an EOF
|
||||
Exactly done
|
||||
```
|
||||
8
shell.nix
Normal file
8
shell.nix
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{ pkgs ? import <nixpkgs> {} }:
|
||||
|
||||
pkgs.mkShell {
|
||||
buildInputs = [
|
||||
pkgs.rustc
|
||||
pkgs.cargo
|
||||
] ++ [ pkgs.libiconv pkgs.darwin.apple_sdk.frameworks.CoreServices ];
|
||||
}
|
||||
19
src/args.rs
Normal file
19
src/args.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
use clap::Parser;
|
||||
|
||||
/// A simple program to track time spans and calculate remaining hours to work
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
pub struct Args {
|
||||
/// How many hours you intend to work (sums with `minutes`)
|
||||
/// Usually defaults to 8 hours, but if discount is set then it defaults to 0
|
||||
#[arg(long)]
|
||||
pub hours: Option<i64>,
|
||||
|
||||
/// How many minutes you intend to work (sums with `hours`)
|
||||
#[arg(long, default_value_t = 0)]
|
||||
pub minutes: i64,
|
||||
|
||||
/// If true, the the hours and minutes fields are treated as subtracting from 8 hours
|
||||
#[arg(long, default_value_t = false)]
|
||||
pub discount: bool,
|
||||
}
|
||||
84
src/main.rs
84
src/main.rs
|
|
@ -1,35 +1,65 @@
|
|||
use std::io::{stdin, Read};
|
||||
use chrono::{NaiveTime, Duration};
|
||||
use std::io::{self, BufRead};
|
||||
use chrono::{DateTime, Local};
|
||||
|
||||
fn read_times() -> Vec<String> {
|
||||
println!("Write all times separated by newlines and finish with CTRL+D\n");
|
||||
let mut times = String::new();
|
||||
stdin().read_to_string(&mut times).expect("An error occurred reading a line");
|
||||
times.split("\n")
|
||||
.map(|s| { s.trim().to_string() })
|
||||
.collect()
|
||||
use clap::Parser;
|
||||
use anyhow::{anyhow, Result};
|
||||
mod args;
|
||||
mod time;
|
||||
|
||||
use args::Args;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = Args::parse();
|
||||
let stdin = io::stdin();
|
||||
let hours = args.hours.unwrap_or(
|
||||
if args.discount {
|
||||
0
|
||||
} else {
|
||||
8
|
||||
}
|
||||
);
|
||||
|
||||
let target_minutes = if args.discount {
|
||||
(8 * 60) - ((hours * 60) + args.minutes)
|
||||
} else {
|
||||
hours * 60 + args.minutes
|
||||
};
|
||||
let (hrs, mins) = time::to_hrs_minutes(target_minutes);
|
||||
println!("Working for {}", time::show_time(hrs, mins));
|
||||
println!("Input times one per line. Send an EOF character to finish inputting...");
|
||||
let mut lines: Vec<String> = vec![];
|
||||
for line in stdin.lock().lines() {
|
||||
lines.push(line.expect("Issues when reading from stdin"));
|
||||
}
|
||||
|
||||
fn parse_time(time_str: &str) -> NaiveTime {
|
||||
NaiveTime::parse_from_str(time_str, "%H:%M")
|
||||
.expect(&format!("Failed to parse time from {time_str}"))
|
||||
// This is immeidately going to be turned back into a DateTime, which the doc says is fine
|
||||
#[allow(deprecated)]
|
||||
let midnight = Local::now().date().and_hms_opt(0, 0, 0).ok_or(anyhow!("Expected midnight to exist"))?;
|
||||
let times = time::from_stream(&midnight, lines.iter())?;
|
||||
|
||||
let mut total_minutes: i64 = 0;
|
||||
let mut first: Option<DateTime<Local>> = None;
|
||||
let mut last: Option<DateTime<Local>> = None;
|
||||
for time in times {
|
||||
match first {
|
||||
None => {
|
||||
first = Some(time);
|
||||
},
|
||||
Some(prev) => {
|
||||
total_minutes += (time - prev).num_minutes();
|
||||
first = None;
|
||||
last = Some(time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let times = read_times();
|
||||
let mut durations = vec![];
|
||||
for i in (0..times.len() - 1).step_by(2) {
|
||||
let first = parse_time(times.get(i).unwrap());
|
||||
let mut second = parse_time(times.get(i + 1).unwrap());
|
||||
if second < first {
|
||||
second = second + Duration::hours(12);
|
||||
}
|
||||
durations.push(second - first);
|
||||
if let Some(remaining) = first {
|
||||
let now = Local::now();
|
||||
let now_str = now.format("%-I:%M %p");
|
||||
println!("Ended with unclosed span... assuming ending now: {}", now_str);
|
||||
total_minutes += (now - remaining).num_minutes();
|
||||
}
|
||||
|
||||
let total_minutes: i64 = durations.iter().map(|d| { d.num_minutes() }).sum();
|
||||
let minutes = total_minutes % 60;
|
||||
let hours = total_minutes / 60;
|
||||
|
||||
println!("You have been working for {hours} hour(s) and {minutes} minute(s)");
|
||||
println!("{}", time::get_charaterized_time_remaining(total_minutes, target_minutes, last.unwrap_or_else(|| Local::now())));
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
89
src/time.rs
Normal file
89
src/time.rs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
use chrono::{Duration, Local, DateTime};
|
||||
use anyhow::{Result, anyhow};
|
||||
use std::iter::Iterator;
|
||||
use regex::Regex;
|
||||
|
||||
fn parse_datetime(reference_date: &DateTime<Local>, s: &str) -> Result<DateTime<Local>> {
|
||||
let re = Regex::new(r"^\s*([12]?\d):([012345]\d)\s*$")?;
|
||||
let (_, [hrs, mins]) = re.captures(s).ok_or(anyhow!(format!("Failed to parse \"{s}\" as a time")))?.extract();
|
||||
let mins: u32 = mins.parse()?;
|
||||
let mut hrs: u32 = hrs.parse()?;
|
||||
let mut date = *reference_date;
|
||||
|
||||
if hrs > 23 {
|
||||
let num_days = hrs / 24;
|
||||
date = *reference_date + Duration::days(num_days.into());
|
||||
hrs = hrs % 24;
|
||||
}
|
||||
#[allow(deprecated)]
|
||||
Ok(date.date().and_hms_opt(hrs, mins, 0).ok_or(anyhow!(format!("{}:{:0>2} not a real time", hrs, mins)))?)
|
||||
}
|
||||
|
||||
pub fn from_stream<'a>(reference_date: &DateTime<Local>, stream: impl Iterator<Item = &'a String>) -> Result<Vec<DateTime<Local>>> {
|
||||
let mut durations: Vec<DateTime<Local>> = vec![];
|
||||
for val in stream {
|
||||
durations.push(parse_datetime(reference_date, val)?);
|
||||
}
|
||||
return Ok(durations);
|
||||
}
|
||||
|
||||
pub fn show_time(hours: i64, minutes: i64) -> String {
|
||||
let pluralized_hours = match hours {
|
||||
1 => "1 hour".to_string(),
|
||||
_ => format!("{hours} hours"),
|
||||
};
|
||||
let pluralized_minutes = match minutes {
|
||||
1 => "1 minute".to_string(),
|
||||
_ => format!("{minutes} minutes"),
|
||||
};
|
||||
|
||||
if hours == 0 {
|
||||
return pluralized_minutes.to_string();
|
||||
}
|
||||
|
||||
if minutes == 0 {
|
||||
return pluralized_hours.to_string();
|
||||
}
|
||||
|
||||
return format!("{pluralized_hours} and {pluralized_minutes}");
|
||||
}
|
||||
|
||||
pub fn to_hrs_minutes(total_minutes: i64) -> (i64, i64) {
|
||||
let minutes = total_minutes % 60;
|
||||
let hours = total_minutes / 60;
|
||||
(hours, minutes)
|
||||
}
|
||||
|
||||
pub fn get_charaterized_time_remaining(
|
||||
total_minutes: i64,
|
||||
target_minutes: i64,
|
||||
ended_at: DateTime<Local>,
|
||||
) -> String {
|
||||
if total_minutes == target_minutes {
|
||||
return "Exactly done".to_string();
|
||||
}
|
||||
|
||||
if total_minutes > target_minutes {
|
||||
let diff = total_minutes - target_minutes;
|
||||
let (hours, minutes) = to_hrs_minutes(diff);
|
||||
return format!("You have overworked {}", show_time(hours, minutes))
|
||||
} else {
|
||||
let diff = target_minutes - total_minutes;
|
||||
let (hours, minutes) = to_hrs_minutes(diff);
|
||||
let now = Local::now();
|
||||
return if ended_at > now {
|
||||
let end_at = (ended_at + Duration::minutes(diff)).time();
|
||||
let end_str = end_at.format("%-I:%M %p");
|
||||
format!(
|
||||
"You have {} remaining (end at {} starting from {})",
|
||||
show_time(hours, minutes),
|
||||
end_str,
|
||||
ended_at.format("%-I:%M %p"),
|
||||
)
|
||||
} else {
|
||||
let end_at = (now + Duration::minutes(diff)).time();
|
||||
let end_str = end_at.format("%-I:%M %p");
|
||||
format!("You have {} remaining (end at {} starting now)", show_time(hours, minutes), end_str)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue