Skip to main content

bento_kit/time/
format.rs

1//! Unix timestamps and `chrono`-style formatting.
2
3use chrono::{DateTime, Local, TimeZone, Utc};
4
5/// Current Unix timestamp in milliseconds.
6pub fn now_millis() -> i64 {
7    Utc::now().timestamp_millis()
8}
9
10/// Current Unix timestamp in seconds.
11pub fn now_seconds() -> i64 {
12    Utc::now().timestamp()
13}
14
15/// Current Unix timestamp in microseconds.
16pub fn now_micros() -> i64 {
17    Utc::now().timestamp_micros()
18}
19
20/// Format the current UTC time using a `chrono` format string.
21///
22/// See [chrono's strftime reference](https://docs.rs/chrono/latest/chrono/format/strftime/index.html).
23///
24/// ```
25/// let s = bento_kit::time::format_now_utc("%Y-%m-%d");
26/// assert_eq!(s.len(), 10);
27/// ```
28pub fn format_now_utc(fmt: &str) -> String {
29    Utc::now().format(fmt).to_string()
30}
31
32/// Format the current local time using a `chrono` format string.
33pub fn format_now_local(fmt: &str) -> String {
34    Local::now().format(fmt).to_string()
35}
36
37/// Format a Unix timestamp (in seconds) as UTC.
38///
39/// Returns `None` if the timestamp is out of `chrono`'s representable range.
40pub fn format_timestamp_utc(ts_seconds: i64, fmt: &str) -> Option<String> {
41    Utc.timestamp_opt(ts_seconds, 0)
42        .single()
43        .map(|dt: DateTime<Utc>| dt.format(fmt).to_string())
44}
45
46/// Format a Unix timestamp (in seconds) as local time.
47pub fn format_timestamp_local(ts_seconds: i64, fmt: &str) -> Option<String> {
48    Local
49        .timestamp_opt(ts_seconds, 0)
50        .single()
51        .map(|dt| dt.format(fmt).to_string())
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    #[test]
59    fn now_millis_is_close_to_chrono() {
60        let mine = now_millis();
61        let chrono_now = Utc::now().timestamp_millis();
62        assert!((chrono_now - mine).abs() < 1000);
63    }
64
65    #[test]
66    fn now_seconds_is_close_to_chrono() {
67        let mine = now_seconds();
68        let chrono_now = Utc::now().timestamp();
69        assert!((chrono_now - mine).abs() <= 1);
70    }
71
72    #[test]
73    fn format_timestamp_utc_known_value() {
74        // 2024-01-01 00:00:00 UTC == 1704067200
75        let s = format_timestamp_utc(1_704_067_200, "%Y-%m-%d %H:%M:%S").unwrap();
76        assert_eq!(s, "2024-01-01 00:00:00");
77    }
78
79    #[test]
80    fn format_timestamp_utc_rejects_out_of_range() {
81        assert!(format_timestamp_utc(i64::MAX, "%Y").is_none());
82    }
83
84    #[test]
85    fn format_now_utc_produces_expected_length() {
86        let s = format_now_utc("%Y-%m-%dT%H:%M:%SZ");
87        assert_eq!(s.len(), 20);
88        assert!(s.ends_with('Z'));
89    }
90}