use chrono::{TimeZone, Utc};
use std::cmp::min;
use std::time::{Duration, Instant, SystemTime};
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TimeAgo {
config: Config,
time_type: TimeType,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum TimeType {
Instant(Instant),
Duration(Duration),
SystemTime(SystemTime),
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Config {
pub is_weeks: bool,
pub is_months: bool,
pub is_years: bool,
}
impl Default for Config {
fn default() -> Config {
Config {
is_weeks: false,
is_months: false,
is_years: false,
}
}
}
impl TimeAgo {
pub fn with_config(config: Config, time_type: TimeType) -> TimeAgo {
TimeAgo { config, time_type }
}
pub fn from_duration(config: Config, duration: Duration) -> TimeAgo {
TimeAgo {
config,
time_type: TimeType::Duration(duration),
}
}
pub fn from_system_time(config: Config, system_time: SystemTime) -> TimeAgo {
TimeAgo {
config,
time_type: TimeType::SystemTime(system_time),
}
}
pub fn now(config: Config) -> TimeAgo {
Self::from_instant(config, Instant::now())
}
pub fn from_instant(config: Config, instant: Instant) -> TimeAgo {
TimeAgo {
config,
time_type: TimeType::Instant(instant),
}
}
pub fn convert(&self) -> String {
let current_sec_function = || {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs()
};
let (seconds, epoch_seconds) = match &self.time_type {
TimeType::SystemTime(value) => (
SystemTime::now()
.duration_since(*value)
.unwrap_or(Duration::from_secs(0))
.as_secs(),
value
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs(),
),
TimeType::Instant(value) => {
let seconds = Instant::now().duration_since(*value).as_secs();
(seconds, current_sec_function().wrapping_sub(seconds))
}
TimeType::Duration(value) => {
let seconds = value.as_secs();
(seconds, current_sec_function().wrapping_sub(seconds))
}
};
match seconds {
(0..=1) => "just now".to_string(),
(2..=59) => format!("{} seconds ago", seconds),
(60..=119) => "1 minute ago".to_string(),
(120..=3599) => format!("{} minutes ago", seconds / 60),
(3600..=7199) => "1 hour ago".to_string(),
(7200..=86_399) => format!("{} hours ago", seconds / (60 * 60)),
(86_400..=172_799) => "yesterday".to_string(),
(172_800..=604_799) => format!("{} days ago", seconds / (60 * 60 * 24)),
(604_800..=1_209_599) => {
if self.config.is_weeks {
"1 week ago".to_string()
} else {
Utc.timestamp(epoch_seconds as i64, 0)
.format("%h %Y at %X")
.to_string()
}
}
(1_209_600..=2_591_999) => {
if self.config.is_weeks {
format!("{} weeks ago", seconds / (60 * 60 * 24 * 7))
} else {
Utc.timestamp(epoch_seconds as i64, 0)
.format("%h %Y at %X")
.to_string()
}
}
(2_592_000..=5_183_999) => {
if self.config.is_months {
"1 month ago".to_string()
} else {
Utc.timestamp(epoch_seconds as i64, 0)
.format("%h %Y at %X")
.to_string()
}
}
(5_184_000..=31_557_599) => {
if self.config.is_months {
format!("{} months ago", min(seconds / (60 * 60 * 24 * 30), 11))
} else {
Utc.timestamp(epoch_seconds as i64, 0)
.format("%h %Y at %X")
.to_string()
}
}
(31_557_600..=63_115_199) => {
if self.config.is_years {
"1 year ago".to_string()
} else {
Utc.timestamp(epoch_seconds as i64, 0)
.format("%h %Y at %X")
.to_string()
}
}
(63_115_200..=3155759999) => {
if self.config.is_years {
format!("{} years ago", seconds / (60 * 60 * 24 * 365))
} else {
Utc.timestamp(epoch_seconds as i64, 0)
.format("%h %Y at %X")
.to_string()
}
}
(3155760000..=std::u64::MAX) => "invalid string".to_string(),
}
}
}