rust_timeago/
time_ago.rs

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
23// Default implementation of Config
24// is_weeks: false -> "23 day(s) ago" is displayed instead of "1 week(s) ago".
25// is_months: false -> "Nov 20 at 11:30"  is displayed instead of "1 month(s) ago".
26// is_years: false -> "Nov 10 at 21:23" is displayed instead of "10 years ago"
27
28impl 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 to 1 second is just now
93            (0..=1) => "just now".to_string(),
94            // 2 to 59 seconds ago
95            (2..=59) => format!("{} seconds ago", seconds),
96            // 60 seconds to 119 seconds
97            (60..=119) => "1 minute ago".to_string(),
98            // 2 minutes to 59 minutes 59 seconds
99            (120..=3599) => format!("{} minutes ago", seconds / 60),
100            //1 hour to 2 hours
101            (3600..=7199) => "1 hour ago".to_string(),
102            //2 hours to 23 hours 59 minutes 59 seconds
103            (7200..=86_399) => format!("{} hours ago", seconds / (60 * 60)),
104            //1 day to 1 day 23 hours 59 minutes 59 seconds,
105            (86_400..=172_799) => "yesterday".to_string(),
106            //2 days to 6 days 23 hours 59 minutes 59 seconds
107            (172_800..=604_799) => format!("{} days ago", seconds / (60 * 60 * 24)),
108            //1 week to 1 week 6 days 23 hours 59 minutes 59 seconds
109            (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            //2 weeks to 29 days 23 hours 59 minutes 59 seconds
119            (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            //1 month to 1 month 29 days 23 hours 59 minutes 59 seconds
129            (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            //2 months to 365.25 days
139            (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            // 1 year(365.25 days) to 1 year 11 months 29 days 23 hours 59 minutes 59 seconds - (2*365.25 days) - 1 second
149            (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            // 2 years to 99 years ago.
159            (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            //100 years and ago
169            (3155760000..=std::u64::MAX) => "invalid string".to_string(),
170        }
171    }
172}