better_tracing/fmt/time/
chrono_crate.rs

1use crate::fmt::format::Writer;
2use crate::fmt::time::FormatTime;
3
4use std::sync::Arc;
5
6/// Formats [local time]s and [UTC time]s with `FormatTime` implementations
7/// that use the [`chrono` crate].
8///
9/// [local time]: [`chrono::offset::Local`]
10/// [UTC time]: [`chrono::offset::Utc`]
11/// [`chrono` crate]: [`chrono`]
12
13/// Formats the current [local time] using a [formatter] from the [`chrono`] crate.
14///
15/// [local time]: chrono::Local::now()
16/// [formatter]: chrono::format
17#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
18#[derive(Debug, Clone, Eq, PartialEq, Default)]
19pub struct ChronoLocal {
20    format: Arc<ChronoFmtType>,
21}
22
23impl ChronoLocal {
24    /// Format the time using the [`RFC 3339`] format
25    /// (a subset of [`ISO 8601`]).
26    ///
27    /// [`RFC 3339`]: https://tools.ietf.org/html/rfc3339
28    /// [`ISO 8601`]: https://en.wikipedia.org/wiki/ISO_8601
29    pub fn rfc_3339() -> Self {
30        Self {
31            format: Arc::new(ChronoFmtType::Rfc3339),
32        }
33    }
34
35    /// RFC3339 with no fractional seconds and 'Z'.
36    pub fn rfc3339_seconds() -> Self {
37        Self {
38            format: Arc::new(ChronoFmtType::Rfc3339Opts(
39                chrono::SecondsFormat::Secs,
40                true,
41            )),
42        }
43    }
44
45    /// RFC3339 with 3 fractional digits (milliseconds) and 'Z'.
46    pub fn rfc3339_millis() -> Self {
47        Self {
48            format: Arc::new(ChronoFmtType::Rfc3339Opts(
49                chrono::SecondsFormat::Millis,
50                true,
51            )),
52        }
53    }
54
55    /// RFC3339 with 9 fractional digits (nanoseconds) and 'Z'.
56    pub fn rfc3339_nanos() -> Self {
57        Self {
58            format: Arc::new(ChronoFmtType::Rfc3339Opts(
59                chrono::SecondsFormat::Nanos,
60                true,
61            )),
62        }
63    }
64
65    /// Time-of-day with whole seconds, no suffix: HH:MM:SS
66    pub fn time_only_secs() -> Self {
67        Self::new("%H:%M:%S".to_owned())
68    }
69
70    /// Time-of-day with milliseconds, no suffix: HH:MM:SS.mmm
71    pub fn time_only_millis() -> Self {
72        Self::new("%H:%M:%S%.3f".to_owned())
73    }
74
75    /// Time-of-day with microseconds, no suffix: HH:MM:SS.uuuuuu
76    pub fn time_only_micros() -> Self {
77        Self::new("%H:%M:%S%.6f".to_owned())
78    }
79
80    /// Format the time using the given format string.
81    ///
82    /// See [`chrono::format::strftime`] for details on the supported syntax.
83    pub fn new(format_string: String) -> Self {
84        Self {
85            format: Arc::new(ChronoFmtType::Custom(format_string)),
86        }
87    }
88}
89
90impl FormatTime for ChronoLocal {
91    fn format_time(&self, w: &mut Writer<'_>) -> alloc::fmt::Result {
92        let t = chrono::Local::now();
93        match self.format.as_ref() {
94            ChronoFmtType::Rfc3339 => {
95                use chrono::format::{Fixed, Item};
96                write!(
97                    w,
98                    "{}",
99                    t.format_with_items(core::iter::once(Item::Fixed(Fixed::RFC3339)))
100                )
101            }
102            ChronoFmtType::Rfc3339Opts(secs_fmt, z) => {
103                write!(w, "{}", t.to_rfc3339_opts(*secs_fmt, *z))
104            }
105            ChronoFmtType::Custom(fmt) => {
106                write!(w, "{}", t.format(fmt))
107            }
108        }
109    }
110}
111
112/// Formats the current [UTC time] using a [formatter] from the [`chrono`] crate.
113///
114/// [UTC time]: chrono::Utc::now()
115/// [formatter]: chrono::format
116#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
117#[derive(Debug, Clone, Eq, PartialEq, Default)]
118pub struct ChronoUtc {
119    format: Arc<ChronoFmtType>,
120}
121
122impl ChronoUtc {
123    /// Format the time using the [`RFC 3339`] format
124    /// (a subset of [`ISO 8601`]).
125    ///
126    /// [`RFC 3339`]: https://tools.ietf.org/html/rfc3339
127    /// [`ISO 8601`]: https://en.wikipedia.org/wiki/ISO_8601
128    pub fn rfc_3339() -> Self {
129        Self {
130            format: Arc::new(ChronoFmtType::Rfc3339),
131        }
132    }
133
134    /// RFC3339 with no fractional seconds and 'Z'.
135    pub fn rfc3339_seconds() -> Self {
136        Self {
137            format: Arc::new(ChronoFmtType::Rfc3339Opts(
138                chrono::SecondsFormat::Secs,
139                true,
140            )),
141        }
142    }
143
144    /// RFC3339 with 3 fractional digits (milliseconds) and 'Z'.
145    pub fn rfc3339_millis() -> Self {
146        Self {
147            format: Arc::new(ChronoFmtType::Rfc3339Opts(
148                chrono::SecondsFormat::Millis,
149                true,
150            )),
151        }
152    }
153
154    /// RFC3339 with 9 fractional digits (nanoseconds) and 'Z'.
155    pub fn rfc3339_nanos() -> Self {
156        Self {
157            format: Arc::new(ChronoFmtType::Rfc3339Opts(
158                chrono::SecondsFormat::Nanos,
159                true,
160            )),
161        }
162    }
163
164    /// Time-of-day with whole seconds, no suffix: HH:MM:SS
165    pub fn time_only_secs() -> Self {
166        Self::new("%H:%M:%S".to_owned())
167    }
168
169    /// Time-of-day with milliseconds, no suffix: HH:MM:SS.mmm
170    pub fn time_only_millis() -> Self {
171        Self::new("%H:%M:%S%.3f".to_owned())
172    }
173
174    /// Time-of-day with microseconds, no suffix: HH:MM:SS.uuuuuu
175    pub fn time_only_micros() -> Self {
176        Self::new("%H:%M:%S%.6f".to_owned())
177    }
178
179    /// Format the time using the given format string.
180    ///
181    /// See [`chrono::format::strftime`] for details on the supported syntax.
182    pub fn new(format_string: String) -> Self {
183        Self {
184            format: Arc::new(ChronoFmtType::Custom(format_string)),
185        }
186    }
187}
188
189impl FormatTime for ChronoUtc {
190    fn format_time(&self, w: &mut Writer<'_>) -> alloc::fmt::Result {
191        let t = chrono::Utc::now();
192        match self.format.as_ref() {
193            ChronoFmtType::Rfc3339 => w.write_str(&t.to_rfc3339()),
194            ChronoFmtType::Custom(fmt) => w.write_str(&format!("{}", t.format(fmt))),
195            ChronoFmtType::Rfc3339Opts(secs_fmt, z) => {
196                w.write_str(&format!("{}", t.to_rfc3339_opts(*secs_fmt, *z)))
197            }
198        }
199    }
200}
201
202/// The RFC 3339 format is used by default but a custom format string
203/// can be used. See [`chrono::format::strftime`]for details on
204/// the supported syntax.
205///
206/// [`chrono::format::strftime`]: https://docs.rs/chrono/0.4.9/chrono/format/strftime/index.html
207/// The RFC 3339 format is used by default but a custom format string
208/// can be used. See [`chrono::format::strftime`]for details on
209/// the supported syntax.
210///
211/// `better-tracing`: Internal to keep the public API crate-agnostic.
212///
213/// [`chrono::format::strftime`]: https://docs.rs/chrono/latest/chrono/format/strftime/index.html
214#[derive(Debug, Clone, Eq, PartialEq, Default)]
215enum ChronoFmtType {
216    /// Format according to the RFC 3339 convention.
217    #[default]
218    Rfc3339,
219    /// Format with [`SecondsFormat`] seconds and with/without `Z`.
220    /// Passes these options to [`chrono::DateTime::to_rfc3339_opts`].
221    Rfc3339Opts(chrono::SecondsFormat, bool),
222    /// Format according to a custom format string.
223    Custom(String),
224}
225
226// Internal helper enum; constructors above build instances as needed.
227
228#[cfg(test)]
229mod tests {
230    use crate::fmt::format::Writer;
231    use crate::fmt::time::FormatTime;
232
233    use std::sync::Arc;
234
235    use super::ChronoFmtType;
236    use super::ChronoLocal;
237    use super::ChronoUtc;
238
239    #[test]
240    fn test_chrono_format_time_utc_default() {
241        let mut buf = String::new();
242        let mut dst: Writer<'_> = Writer::new(&mut buf);
243        assert!(FormatTime::format_time(&ChronoUtc::default(), &mut dst).is_ok());
244        // e.g. `buf` contains "2023-08-18T19:05:08.662499+00:00"
245        assert!(chrono::DateTime::parse_from_str(&buf, "%FT%H:%M:%S%.6f%z").is_ok());
246    }
247
248    #[test]
249    fn test_chrono_format_time_utc_custom() {
250        let fmt = ChronoUtc {
251            format: Arc::new(ChronoFmtType::Custom("%a %b %e %T %Y".to_owned())),
252        };
253        let mut buf = String::new();
254        let mut dst: Writer<'_> = Writer::new(&mut buf);
255        assert!(FormatTime::format_time(&fmt, &mut dst).is_ok());
256        // e.g. `buf` contains "Wed Aug 23 15:53:23 2023"
257        assert!(chrono::NaiveDateTime::parse_from_str(&buf, "%a %b %e %T %Y").is_ok());
258    }
259
260    #[test]
261    fn test_chrono_format_time_local_default() {
262        let mut buf = String::new();
263        let mut dst: Writer<'_> = Writer::new(&mut buf);
264        assert!(FormatTime::format_time(&ChronoLocal::default(), &mut dst).is_ok());
265        // e.g. `buf` contains "2023-08-18T14:59:08.662499-04:00".
266        assert!(chrono::DateTime::parse_from_str(&buf, "%FT%H:%M:%S%.6f%z").is_ok());
267    }
268
269    #[test]
270    fn test_chrono_format_time_local_custom() {
271        let fmt = ChronoLocal {
272            format: Arc::new(ChronoFmtType::Custom("%a %b %e %T %Y".to_owned())),
273        };
274        let mut buf = String::new();
275        let mut dst: Writer<'_> = Writer::new(&mut buf);
276        assert!(FormatTime::format_time(&fmt, &mut dst).is_ok());
277        // e.g. `buf` contains "Wed Aug 23 15:55:46 2023".
278        assert!(chrono::NaiveDateTime::parse_from_str(&buf, "%a %b %e %T %Y").is_ok());
279    }
280}