format_distance.rs

  1use chrono::NaiveDateTime;
  2
  3fn distance_in_seconds(date: NaiveDateTime, base_date: NaiveDateTime) -> i64 {
  4    let duration = date.signed_duration_since(base_date);
  5    -duration.num_seconds()
  6}
  7
  8fn distance_string(distance: i64, include_seconds: bool, add_suffix: bool) -> String {
  9    let suffix = if distance < 0 { " from now" } else { " ago" };
 10
 11    let d = distance.abs();
 12
 13    let minutes = d / 60;
 14    let hours = d / 3600;
 15    let days = d / 86400;
 16    let months = d / 2592000;
 17    let years = d / 31536000;
 18
 19    let string = if d < 5 && include_seconds {
 20        "less than 5 seconds".to_string()
 21    } else if d < 10 && include_seconds {
 22        "less than 10 seconds".to_string()
 23    } else if d < 20 && include_seconds {
 24        "less than 20 seconds".to_string()
 25    } else if d < 40 && include_seconds {
 26        "half a minute".to_string()
 27    } else if d < 60 && include_seconds {
 28        "less than a minute".to_string()
 29    } else if d < 90 && include_seconds {
 30        "1 minute".to_string()
 31    } else if d < 30 {
 32        "less than a minute".to_string()
 33    } else if d < 90 {
 34        "1 minute".to_string()
 35    } else if d < 2700 {
 36        format!("{} minutes", minutes)
 37    } else if d < 5400 {
 38        "about 1 hour".to_string()
 39    } else if d < 86400 {
 40        format!("about {} hours", hours)
 41    } else if d < 172800 {
 42        "1 day".to_string()
 43    } else if d < 2592000 {
 44        format!("{} days", days)
 45    } else if d < 5184000 {
 46        "about 1 month".to_string()
 47    } else if d < 7776000 {
 48        "about 2 months".to_string()
 49    } else if d < 31540000 {
 50        format!("{} months", months)
 51    } else if d < 39425000 {
 52        "about 1 year".to_string()
 53    } else if d < 55195000 {
 54        "over 1 year".to_string()
 55    } else if d < 63080000 {
 56        "almost 2 years".to_string()
 57    } else {
 58        let years = d / 31536000;
 59        let remaining_months = (d % 31536000) / 2592000;
 60
 61        if remaining_months < 3 {
 62            format!("about {} years", years)
 63        } else if remaining_months < 9 {
 64            format!("over {} years", years)
 65        } else {
 66            format!("almost {} years", years + 1)
 67        }
 68    };
 69
 70    if add_suffix {
 71        return format!("{}{}", string, suffix);
 72    } else {
 73        string
 74    }
 75}
 76
 77pub fn naive_format_distance(
 78    date: NaiveDateTime,
 79    base_date: NaiveDateTime,
 80    include_seconds: bool,
 81    add_suffix: bool,
 82) -> String {
 83    let distance = distance_in_seconds(date, base_date);
 84
 85    distance_string(distance, include_seconds, add_suffix)
 86}
 87
 88pub fn naive_format_distance_from_now(
 89    datetime: NaiveDateTime,
 90    include_seconds: bool,
 91    add_suffix: bool,
 92) -> String {
 93    let now = chrono::offset::Local::now().naive_local();
 94
 95    naive_format_distance(datetime, now, include_seconds, add_suffix)
 96}
 97
 98#[cfg(test)]
 99mod tests {
100    use super::*;
101    use chrono::NaiveDateTime;
102
103    #[test]
104    fn test_naive_format_distance() {
105        let date =
106            NaiveDateTime::from_timestamp_opt(9600, 0).expect("Invalid NaiveDateTime for date");
107        let base_date =
108            NaiveDateTime::from_timestamp_opt(0, 0).expect("Invalid NaiveDateTime for base_date");
109
110        assert_eq!(
111            "about 2 hours",
112            naive_format_distance(date, base_date, false, false)
113        );
114    }
115
116    #[test]
117    fn test_naive_format_distance_with_suffix() {
118        let date =
119            NaiveDateTime::from_timestamp_opt(9600, 0).expect("Invalid NaiveDateTime for date");
120        let base_date =
121            NaiveDateTime::from_timestamp_opt(0, 0).expect("Invalid NaiveDateTime for base_date");
122
123        assert_eq!(
124            "about 2 hours from now",
125            naive_format_distance(date, base_date, false, true)
126        );
127    }
128
129    #[test]
130    fn test_naive_format_distance_from_now() {
131        let date = NaiveDateTime::parse_from_str("1969-07-20T00:00:00Z", "%Y-%m-%dT%H:%M:%SZ")
132            .expect("Invalid NaiveDateTime for date");
133
134        assert_eq!(
135            "over 54 years ago",
136            naive_format_distance_from_now(date, false, true)
137        );
138    }
139
140    #[test]
141    fn test_naive_format_distance_string() {
142        assert_eq!(distance_string(3, false, false), "less than a minute");
143        assert_eq!(distance_string(7, false, false), "less than a minute");
144        assert_eq!(distance_string(13, false, false), "less than a minute");
145        assert_eq!(distance_string(21, false, false), "less than a minute");
146        assert_eq!(distance_string(45, false, false), "1 minute");
147        assert_eq!(distance_string(61, false, false), "1 minute");
148        assert_eq!(distance_string(1920, false, false), "32 minutes");
149        assert_eq!(distance_string(3902, false, false), "about 1 hour");
150        assert_eq!(distance_string(18002, false, false), "about 5 hours");
151        assert_eq!(distance_string(86470, false, false), "1 day");
152        assert_eq!(distance_string(345880, false, false), "4 days");
153        assert_eq!(distance_string(2764800, false, false), "about 1 month");
154        assert_eq!(distance_string(5184000, false, false), "about 2 months");
155        assert_eq!(distance_string(10368000, false, false), "4 months");
156        assert_eq!(distance_string(34694000, false, false), "about 1 year");
157        assert_eq!(distance_string(47310000, false, false), "over 1 year");
158        assert_eq!(distance_string(61503000, false, false), "almost 2 years");
159        assert_eq!(distance_string(160854000, false, false), "about 5 years");
160        assert_eq!(distance_string(236550000, false, false), "over 7 years");
161        assert_eq!(distance_string(249166000, false, false), "almost 8 years");
162    }
163
164    #[test]
165    fn test_naive_format_distance_string_include_seconds() {
166        assert_eq!(distance_string(3, true, false), "less than 5 seconds");
167        assert_eq!(distance_string(7, true, false), "less than 10 seconds");
168        assert_eq!(distance_string(13, true, false), "less than 20 seconds");
169        assert_eq!(distance_string(21, true, false), "half a minute");
170        assert_eq!(distance_string(45, true, false), "less than a minute");
171        assert_eq!(distance_string(61, true, false), "1 minute");
172    }
173}