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 < 31536000 {
 50        format!("{} months", months)
 51    } else if d < 39408000 {
 52        "about 1 year".to_string()
 53    } else if d < 47318400 {
 54        "over 1 year".to_string()
 55    } else if d < 63072000 {
 56        "almost 2 years".to_string()
 57    } else {
 58        format!("{} years", years)
 59    };
 60
 61    if add_suffix {
 62        return format!("{}{}", string, suffix);
 63    } else {
 64        string
 65    }
 66}
 67
 68pub fn naive_format_distance(
 69    date: NaiveDateTime,
 70    base_date: NaiveDateTime,
 71    include_seconds: bool,
 72    add_suffix: bool,
 73) -> String {
 74    let distance = distance_in_seconds(date, base_date);
 75
 76    distance_string(distance, include_seconds, add_suffix)
 77}
 78
 79pub fn naive_format_distance_from_now(
 80    datetime: NaiveDateTime,
 81    include_seconds: bool,
 82    add_suffix: bool,
 83) -> String {
 84    let now = chrono::offset::Local::now().naive_local();
 85
 86    naive_format_distance(datetime, now, include_seconds, add_suffix)
 87}
 88
 89#[cfg(test)]
 90mod tests {
 91    use super::*;
 92    use chrono::NaiveDateTime;
 93
 94    // #[test]
 95    // fn test_naive_format_distance() {
 96    //     let date = NaiveDateTime::from_timestamp_opt(0, 0);
 97    //     let base_date = NaiveDateTime::from_timestamp_opt(9400, 0);
 98
 99    //     assert_eq!(
100    //         "about 2 hours ago",
101    //         naive_format_distance(
102    //             date.expect("invalid datetime"),
103    //             base_date.expect("invalid datetime"),
104    //             false,
105    //             true
106    //         )
107    //     );
108    // }
109
110    // #[test]
111    // fn test_naive_format_distance_with_seconds() {
112    //     let date = NaiveDateTime::from_timestamp_opt(0, 0);
113    //     let base_date = NaiveDateTime::from_timestamp_opt(10, 0);
114
115    //     assert_eq!(
116    //         "less than 30 seconds ago",
117    //         naive_format_distance(
118    //             date.expect("invalid datetime"),
119    //             base_date.expect("invalid datetime"),
120    //             true,
121    //             true
122    //         )
123    //     );
124    // }
125
126    // #[test]
127    // fn test_naive_format_distance_with_future_date() {
128    //     let date = NaiveDateTime::from_timestamp_opt(3400, 0);
129    //     let base_date = NaiveDateTime::from_timestamp_opt(00, 0);
130
131    //     assert_eq!(
132    //         "about 56 from now",
133    //         naive_format_distance(
134    //             date.expect("invalid datetime"),
135    //             base_date.expect("invalid datetime"),
136    //             false,
137    //             true
138    //         )
139    //     );
140    // }
141
142    #[test]
143    fn test_naive_format_distance_string() {
144        assert_eq!(distance_string(3, false, false), "less than a minute");
145        assert_eq!(distance_string(7, false, false), "less than a minute");
146        assert_eq!(distance_string(13, false, false), "less than a minute");
147        assert_eq!(distance_string(21, false, false), "less than a minute");
148        assert_eq!(distance_string(45, false, false), "1 minute");
149        assert_eq!(distance_string(61, false, false), "1 minute");
150        assert_eq!(distance_string(1920, false, false), "32 minutes");
151        assert_eq!(distance_string(3902, false, false), "about 1 hour");
152        assert_eq!(distance_string(18002, false, false), "about 5 hours");
153        assert_eq!(distance_string(86470, false, false), "1 day");
154        assert_eq!(distance_string(345880, false, false), "4 days");
155    }
156
157    #[test]
158    fn test_naive_format_distance_string_include_seconds() {
159        assert_eq!(distance_string(3, true, false), "less than 5 seconds");
160        assert_eq!(distance_string(7, true, false), "less than 10 seconds");
161        assert_eq!(distance_string(13, true, false), "less than 20 seconds");
162        assert_eq!(distance_string(21, true, false), "half a minute");
163        assert_eq!(distance_string(45, true, false), "less than a minute");
164        assert_eq!(distance_string(61, true, false), "1 minute");
165    }
166}