use std::{
borrow::Borrow,
collections::HashMap,
convert::TryFrom,
fmt::{write, Display},
};
use chrono::{Date, DateTime, Duration, Local, Utc};
use math::round::floor;
#[derive(Debug, Clone)]
pub struct Elapsed {
datetime_context: DateTime<Local>,
datetime: DateTime<Local>,
date: Date<Local>,
duration: Duration,
passed: bool,
cache: HashMap<TimeFrame, (String, i64)>, }
pub type DueDateTime = Elapsed;
pub type TimeBetween = Elapsed;
impl Elapsed {
pub fn new(datetime: DateTime<Local>) -> Self {
let datetime_context = Local::now();
Self {
datetime_context,
datetime,
date: datetime.date(),
duration: datetime.signed_duration_since(datetime_context),
passed: datetime.le(&datetime_context),
cache: HashMap::new(),
}
}
pub fn new_from_date(date: Date<Local>) -> Self {
let datetime = date.and_hms(0, 0, 0);
let datetime_context = Local::now();
Self {
datetime_context,
datetime,
date,
duration: datetime.signed_duration_since(datetime_context),
passed: datetime.le(&datetime_context),
cache: HashMap::new(),
}
}
pub fn new_then_localize(datetime: DateTime<Utc>) -> Self {
let datetime_context = Local::now();
let datetime = datetime.with_timezone(&Local);
Self {
datetime_context,
datetime,
date: datetime.date(),
duration: datetime.signed_duration_since(datetime_context),
passed: datetime.le(&datetime_context),
cache: HashMap::new(),
}
}
pub fn new_from_date_then_localize(date: Date<Utc>) -> Self {
let datetime = date.and_hms(0, 0, 0).with_timezone(&Local);
let datetime_context = Local::now();
Self {
datetime_context,
datetime,
date: datetime.date(),
duration: datetime.signed_duration_since(datetime_context),
passed: datetime.le(&datetime_context),
cache: HashMap::new(),
}
}
pub fn new_with_context(datetime: DateTime<Local>, context: DateTime<Local>) -> Self {
Self {
datetime_context: context,
datetime,
date: datetime.date(),
duration: datetime.signed_duration_since(context),
passed: datetime.le(&context),
cache: HashMap::new(),
}
}
pub fn new_from_date_with_context(date: Date<Local>, context: Date<Local>) -> Self {
let datetime = date.and_hms(0, 0, 0);
let datetime_context = context.and_hms(0, 0, 0);
Self {
datetime_context,
datetime,
date,
duration: datetime.signed_duration_since(datetime_context),
passed: datetime.le(&datetime_context),
cache: HashMap::new(),
}
}
pub fn set_datetime_context(&mut self, datetime_context: DateTime<Local>) -> &mut Self {
self.datetime_context = datetime_context;
self.duration = self.datetime.signed_duration_since(datetime_context);
self.passed = self.datetime.le(&self.datetime_context);
self
}
pub fn set_datetime(&mut self, datetime: DateTime<Local>) -> &mut Self {
self.datetime = datetime;
self.date = datetime.date();
self.duration = datetime.signed_duration_since(self.datetime_context);
self.passed = datetime.le(&self.datetime_context);
self
}
pub fn set_date(&mut self, date: Date<Local>) {
self.date = date;
self.datetime = date.and_hms(0, 0, 0);
self.duration = self.datetime.signed_duration_since(self.datetime_context);
self.passed = self.datetime.le(&self.datetime_context);
}
pub fn process_all(&mut self) {
let diff = self.duration;
let weeks = diff.num_weeks().abs();
let days = diff.num_days().abs();
let hours = diff.num_hours().abs();
let minutes = diff.num_minutes().abs();
let seconds = diff.num_seconds().abs();
if weeks > 0 {
if weeks > 0 && weeks < 4 {
self.cache_insert(TimeFrame::Week, weeks);
} else
{
let months = floor((weeks / 4) as f64, 0) as i64;
let weeks_remaining = weeks - months * 4;
if months < 12
{
self.cache_insert(TimeFrame::Month, months);
self.cache_insert(TimeFrame::Week, weeks_remaining);
} else
{
let years = floor((months / 12) as f64, 0) as i64;
let months_remaining = months - years * 12;
self.cache_insert(TimeFrame::Year, years);
self.cache_insert(TimeFrame::Month, months_remaining);
}
}
} else if days > 0
{
self.cache_insert(TimeFrame::Day, days);
} else if hours >= 4
{
self.cache_insert(TimeFrame::Hour, hours);
} else if minutes >= 60
{
self.cache_insert(TimeFrame::Minute, minutes - hours * 60);
} else if minutes >= 5
{
self.cache_insert(TimeFrame::Minute, minutes);
} else if seconds > 0
{
self.cache_insert(TimeFrame::Minute, minutes);
self.cache_insert(TimeFrame::Second, seconds - minutes * 60);
}
}
fn cache_insert(&mut self, k: TimeFrame, v: i64) {
let tup = (format!("{}{}", v, k.abbrev()), v);
let val = self.cache.entry(k).or_insert(tup);
val.1 = v;
}
pub fn years(&mut self) -> Option<&(String, i64)> {
let years = floor((self.duration.num_weeks() / 52) as f64, 0) as i64;
if years.ne(&0) {
let tf = TimeFrame::Year;
self.cache_insert(tf, years);
self.cache.get(&tf)
} else {
None
}
}
pub fn months(&mut self) -> Option<&(String, i64)> {
let months = floor((self.duration.num_weeks() / 4) as f64, 0) as i64;
let tf = TimeFrame::Month;
self.cache_insert(tf, months);
self.cache.get(&tf)
}
pub fn weeks(&mut self) {}
pub fn days(&mut self) {}
pub fn hours(&mut self) {}
pub fn minutes(&mut self) {}
pub fn seconds(&mut self) {}
}
impl Display for Elapsed {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut vec: Vec<&str> = Vec::new();
if let Some(years) = self.cache.get(&TimeFrame::Year) {
vec.push(&years.0);
}
if let Some(months) = self.cache.get(&TimeFrame::Month) {
vec.push(&months.0);
}
if let Some(weeks) = self.cache.get(&TimeFrame::Week) {
vec.push(&weeks.0);
}
if let Some(days) = self.cache.get(&TimeFrame::Day) {
vec.push(&days.0);
}
if let Some(hours) = self.cache.get(&TimeFrame::Hour) {
vec.push(&hours.0);
}
if let Some(minutes) = self.cache.get(&TimeFrame::Minute) {
vec.push(&minutes.0);
}
if let Some(seconds) = self.cache.get(&TimeFrame::Second) {
vec.push(&seconds.0);
}
write!(f, "{}", vec.join(" "))
}
}
impl From<DateTime<Local>> for Elapsed {
fn from(datetime: DateTime<Local>) -> Self {
Self::new(datetime)
}
}
impl From<Date<Local>> for Elapsed {
fn from(date: Date<Local>) -> Self {
Self::new_from_date(date)
}
}
impl From<DateTime<Utc>> for Elapsed {
fn from(datetime: DateTime<Utc>) -> Self {
Self::new_then_localize(datetime)
}
}
impl From<Date<Utc>> for Elapsed {
fn from(date: Date<Utc>) -> Self {
Self::new_from_date_then_localize(date)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum TimeFrame {
MilliSecond,
Second,
Minute,
Hour,
Day,
Week,
Month,
Year,
}
impl From<TimeFrame> for String {
fn from(tf: TimeFrame) -> Self {
match tf {
TimeFrame::MilliSecond => String::from("millisecond(s)"),
TimeFrame::Second => String::from("second(s)"),
TimeFrame::Minute => String::from("minute(s)"),
TimeFrame::Hour => String::from("hour(s)"),
TimeFrame::Day => String::from("day(s)"),
TimeFrame::Week => String::from("week(s)"),
TimeFrame::Month => String::from("month(s)"),
TimeFrame::Year => String::from("year(s)"),
}
}
}
impl TryFrom<&str> for TimeFrame {
type Error = &'static str;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value.to_lowercase().trim() {
"millisecond" | "ms" => Ok(Self::MilliSecond),
"second" | "sec" | "s" => Ok(Self::Second),
"minute" | "min" => Ok(Self::Minute),
"hour" | "hr" | "h" => Ok(Self::Hour),
"day" | "d" => Ok(Self::Day),
"week" | "wk" | "w" => Ok(Self::Week),
"month" | "mon" => Ok(Self::Month),
"year" | "yr" | "y" => Ok(Self::Year),
_ => Err("Invalid or ambiguous string for `elapsed::TimeFrame`"),
}
}
}
impl From<TimeFrame> for char {
fn from(tf: TimeFrame) -> Self {
match tf {
TimeFrame::MilliSecond => 'm',
TimeFrame::Second => 's',
TimeFrame::Minute => 'm',
TimeFrame::Hour => 'h',
TimeFrame::Day => 'd',
TimeFrame::Week => 'w',
TimeFrame::Month => 'm',
TimeFrame::Year => 'y',
}
}
}
impl TryFrom<char> for TimeFrame {
type Error = &'static str;
fn try_from(value: char) -> Result<Self, Self::Error> {
match value.to_ascii_lowercase() {
's' => Ok(Self::Second),
'h' => Ok(Self::Hour),
'd' => Ok(Self::Day),
'w' => Ok(Self::Week),
'y' => Ok(Self::Year),
_ => Err("Invalid or ambiguous char for `elapsed::TimeFrame`"),
}
}
}
pub trait Abbreviate {
fn abbrev(&self) -> &'static str;
fn abbrev_short(&self) -> &'static str;
}
impl Abbreviate for TimeFrame {
fn abbrev(&self) -> &'static str {
match self {
TimeFrame::MilliSecond => "ms",
TimeFrame::Second => "sec",
TimeFrame::Minute => "min",
TimeFrame::Hour => "hr",
TimeFrame::Day => "d",
TimeFrame::Week => "w",
TimeFrame::Month => "m",
TimeFrame::Year => "y",
}
}
fn abbrev_short(&self) -> &'static str {
match self {
TimeFrame::MilliSecond => "ms",
TimeFrame::Second => "s",
TimeFrame::Minute => "min",
TimeFrame::Hour => "h",
TimeFrame::Day => "d",
TimeFrame::Week => "w",
TimeFrame::Month => "m",
TimeFrame::Year => "y",
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}