1use chrono::{TimeZone, Utc};
2use std::cmp::min;
3use std::time::{Duration, Instant, SystemTime};
4
5#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
6pub struct TimeAgo {
7 config: Config,
8 time_type: TimeType,
9}
10#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
11pub enum TimeType {
12 Instant(Instant),
13 Duration(Duration),
14 SystemTime(SystemTime),
15}
16#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
17pub struct Config {
18 pub is_weeks: bool,
19 pub is_months: bool,
20 pub is_years: bool,
21}
22
23impl Default for Config {
29 fn default() -> Config {
30 Config {
31 is_weeks: false,
32 is_months: false,
33 is_years: false,
34 }
35 }
36}
37
38impl TimeAgo {
39 pub fn with_config(config: Config, time_type: TimeType) -> TimeAgo {
40 TimeAgo { config, time_type }
41 }
42 pub fn from_duration(config: Config, duration: Duration) -> TimeAgo {
43 TimeAgo {
44 config,
45 time_type: TimeType::Duration(duration),
46 }
47 }
48 pub fn from_system_time(config: Config, system_time: SystemTime) -> TimeAgo {
49 TimeAgo {
50 config,
51 time_type: TimeType::SystemTime(system_time),
52 }
53 }
54 pub fn now(config: Config) -> TimeAgo {
55 Self::from_instant(config, Instant::now())
56 }
57 pub fn from_instant(config: Config, instant: Instant) -> TimeAgo {
58 TimeAgo {
59 config,
60 time_type: TimeType::Instant(instant),
61 }
62 }
63
64 pub fn convert(&self) -> String {
65 let current_sec_function = || {
66 SystemTime::now()
67 .duration_since(SystemTime::UNIX_EPOCH)
68 .unwrap()
69 .as_secs()
70 };
71 let (seconds, epoch_seconds) = match &self.time_type {
72 TimeType::SystemTime(value) => (
73 SystemTime::now()
74 .duration_since(*value)
75 .unwrap_or(Duration::from_secs(0))
76 .as_secs(),
77 value
78 .duration_since(SystemTime::UNIX_EPOCH)
79 .unwrap()
80 .as_secs(),
81 ),
82 TimeType::Instant(value) => {
83 let seconds = Instant::now().duration_since(*value).as_secs();
84 (seconds, current_sec_function().wrapping_sub(seconds))
85 }
86 TimeType::Duration(value) => {
87 let seconds = value.as_secs();
88 (seconds, current_sec_function().wrapping_sub(seconds))
89 }
90 };
91 match seconds {
92 (0..=1) => "just now".to_string(),
94 (2..=59) => format!("{} seconds ago", seconds),
96 (60..=119) => "1 minute ago".to_string(),
98 (120..=3599) => format!("{} minutes ago", seconds / 60),
100 (3600..=7199) => "1 hour ago".to_string(),
102 (7200..=86_399) => format!("{} hours ago", seconds / (60 * 60)),
104 (86_400..=172_799) => "yesterday".to_string(),
106 (172_800..=604_799) => format!("{} days ago", seconds / (60 * 60 * 24)),
108 (604_800..=1_209_599) => {
110 if self.config.is_weeks {
111 "1 week ago".to_string()
112 } else {
113 Utc.timestamp(epoch_seconds as i64, 0)
114 .format("%h %Y at %X")
115 .to_string()
116 }
117 }
118 (1_209_600..=2_591_999) => {
120 if self.config.is_weeks {
121 format!("{} weeks ago", seconds / (60 * 60 * 24 * 7))
122 } else {
123 Utc.timestamp(epoch_seconds as i64, 0)
124 .format("%h %Y at %X")
125 .to_string()
126 }
127 }
128 (2_592_000..=5_183_999) => {
130 if self.config.is_months {
131 "1 month ago".to_string()
132 } else {
133 Utc.timestamp(epoch_seconds as i64, 0)
134 .format("%h %Y at %X")
135 .to_string()
136 }
137 }
138 (5_184_000..=31_557_599) => {
140 if self.config.is_months {
141 format!("{} months ago", min(seconds / (60 * 60 * 24 * 30), 11))
142 } else {
143 Utc.timestamp(epoch_seconds as i64, 0)
144 .format("%h %Y at %X")
145 .to_string()
146 }
147 }
148 (31_557_600..=63_115_199) => {
150 if self.config.is_years {
151 "1 year ago".to_string()
152 } else {
153 Utc.timestamp(epoch_seconds as i64, 0)
154 .format("%h %Y at %X")
155 .to_string()
156 }
157 }
158 (63_115_200..=3155759999) => {
160 if self.config.is_years {
161 format!("{} years ago", seconds / (60 * 60 * 24 * 365))
162 } else {
163 Utc.timestamp(epoch_seconds as i64, 0)
164 .format("%h %Y at %X")
165 .to_string()
166 }
167 }
168 (3155760000..=std::u64::MAX) => "invalid string".to_string(),
170 }
171 }
172}