clia_local_time/
lib.rs

1//! A LocalTime implementation to set timezone manually.
2
3use std::fmt;
4use std::io;
5use time::{format_description::well_known, formatting::Formattable, OffsetDateTime, UtcOffset};
6use tracing_subscriber::fmt::{format::Writer, time::FormatTime};
7
8/// Formats the current [local time] using a [formatter] from the [`time` crate].
9///
10/// To format the current [UTC time] instead, use the [`UtcTime`] type.
11///
12/// [local time]: https://docs.rs/time/0.3/time/struct.OffsetDateTime.html#method.now_local
13/// [UTC time]: https://docs.rs/time/0.3/time/struct.OffsetDateTime.html#method.now_utc
14/// [formatter]: https://docs.rs/time/0.3/time/formatting/trait.Formattable.html
15/// [`time` crate]: https://docs.rs/time/0.3/time/
16#[derive(Clone, Debug)]
17// #[cfg_attr(docsrs, doc(cfg(all(feature = "time", feature = "local-time"))))]
18// #[cfg(feature = "local-time")]
19pub struct LocalTime<F> {
20    format: F,
21    tz_hours: i8,
22    tz_minutes: i8,
23    tz_seconds: i8,
24}
25
26// === impl LocalTime ===
27
28// #[cfg(feature = "local-time")]
29impl LocalTime<well_known::Rfc3339> {
30    /// Returns a formatter that formats the current [local time] in the
31    /// [RFC 3339] format (a subset of the [ISO 8601] timestamp format).
32    ///
33    /// # Examples
34    ///
35    /// ```
36    /// use tracing_subscriber::fmt::{self, time};
37    ///
38    /// let collector = tracing_subscriber::fmt()
39    ///     .with_timer(time::LocalTime::rfc_3339());
40    /// # drop(collector);
41    /// ```
42    ///
43    /// [local time]: https://docs.rs/time/0.3/time/struct.OffsetDateTime.html#method.now_local
44    /// [RFC 3339]: https://datatracker.ietf.org/doc/html/rfc3339
45    /// [ISO 8601]: https://en.wikipedia.org/wiki/ISO_8601
46    pub fn rfc_3339() -> Self {
47        Self::new(well_known::Rfc3339)
48    }
49}
50
51// #[cfg(feature = "local-time")]
52impl<F: Formattable> LocalTime<F> {
53    /// Returns a formatter that formats the current [local time] using the
54    /// [`time` crate] with the provided provided format. The format may be any
55    /// type that implements the [`Formattable`] trait.
56    ///
57    /// This default use UTC.
58    ///
59    /// Typically, the format will be a format description string, or one of the
60    /// `time` crate's [well-known formats].
61    ///
62    /// If the format description is statically known, then the
63    /// [`format_description!`] macro should be used. This is identical to the
64    /// [`time::format_description::parse`] method, but runs at compile-time,
65    /// throwing an error if the format description is invalid. If the desired format
66    /// is not known statically (e.g., a user is providing a format string), then the
67    /// [`time::format_description::parse`] method should be used. Note that this
68    /// method is fallible.
69    ///
70    /// See the [`time` book] for details on the format description syntax.
71    ///
72    /// # Examples
73    ///
74    /// Using the [`format_description!`] macro:
75    ///
76    /// ```
77    /// use tracing_subscriber::fmt::{self, time::LocalTime};
78    /// use time::macros::format_description;
79    ///
80    /// let timer = LocalTime::new(format_description!("[hour]:[minute]:[second]"));
81    /// let collector = tracing_subscriber::fmt()
82    ///     .with_timer(timer);
83    /// # drop(collector);
84    /// ```
85    ///
86    /// Using [`time::format_description::parse`]:
87    ///
88    /// ```
89    /// use tracing_subscriber::fmt::{self, time::LocalTime};
90    ///
91    /// let time_format = time::format_description::parse("[hour]:[minute]:[second]")
92    ///     .expect("format string should be valid!");
93    /// let timer = LocalTime::new(time_format);
94    /// let collector = tracing_subscriber::fmt()
95    ///     .with_timer(timer);
96    /// # drop(collector);
97    /// ```
98    ///
99    /// Using the [`format_description!`] macro requires enabling the `time`
100    /// crate's "macros" feature flag.
101    ///
102    /// Using a [well-known format][well-known formats] (this is equivalent to
103    /// [`LocalTime::rfc_3339`]):
104    ///
105    /// ```
106    /// use tracing_subscriber::fmt::{self, time::LocalTime};
107    ///
108    /// let timer = LocalTime::new(time::format_description::well_known::Rfc3339);
109    /// let collector = tracing_subscriber::fmt()
110    ///     .with_timer(timer);
111    /// # drop(collector);
112    /// ```
113    ///
114    /// [local time]: https://docs.rs/time/latest/time/struct.OffsetDateTime.html#method.now_local
115    /// [`time` crate]: https://docs.rs/time/0.3/time/
116    /// [`Formattable`]: https://docs.rs/time/0.3/time/formatting/trait.Formattable.html
117    /// [well-known formats]: https://docs.rs/time/0.3/time/format_description/well_known/index.html
118    /// [`format_description!`]: https://docs.rs/time/0.3/time/macros/macro.format_description.html
119    /// [`time::format_description::parse`]: https://docs.rs/time/0.3/time/format_description/fn.parse.html
120    /// [`time` book]: https://time-rs.github.io/book/api/format-description.html
121    pub fn new(format: F) -> Self {
122        Self {
123            format,
124            tz_hours: 0,
125            tz_minutes: 0,
126            tz_seconds: 0,
127        }
128    }
129
130    /// New with a format and timezone setting.
131    /// 
132    /// Timezone format: (tz_hours, tz_minutes, tz_seconds)
133    /// 
134    /// # Examples:
135    /// 
136    /// ```
137    ///     (8, 0, 0)
138    ///     (-2, 30, 0)
139    /// ```
140    /// 
141    pub fn with_timezone(format: F, tz_hms: (i8, i8, i8)) -> Self {
142        Self {
143            format,
144            tz_hours: tz_hms.0,
145            tz_minutes: tz_hms.1,
146            tz_seconds: tz_hms.2,
147        }
148    }
149}
150
151// #[cfg(feature = "local-time")]
152impl<F> FormatTime for LocalTime<F>
153where
154    F: Formattable,
155{
156    fn format_time(&self, w: &mut Writer<'_>) -> fmt::Result {
157        //
158        //
159        //
160        // Fix here:
161        //
162        //
163        //
164
165        // let now = OffsetDateTime::now_local().map_err(|_| fmt::Error)?;
166        let offset = UtcOffset::from_hms(self.tz_hours, self.tz_minutes, self.tz_seconds)
167            .unwrap_or(UtcOffset::UTC);
168        let now = OffsetDateTime::now_utc().to_offset(offset);
169        format_datetime(now, w, &self.format)
170    }
171}
172
173// #[cfg(feature = "local-time")]
174impl<F> Default for LocalTime<F>
175where
176    F: Formattable + Default,
177{
178    fn default() -> Self {
179        Self::new(F::default())
180    }
181}
182
183fn format_datetime(
184    now: OffsetDateTime,
185    into: &mut Writer<'_>,
186    fmt: &impl Formattable,
187) -> fmt::Result {
188    let mut into = WriteAdaptor::new(into);
189    now.format_into(&mut into, fmt)
190        .map_err(|_| fmt::Error)
191        .map(|_| ())
192}
193
194/// A bridge between `fmt::Write` and `io::Write`.
195///
196/// This is used by the timestamp formatting implementation for the `time`
197/// crate and by the JSON formatter. In both cases, this is needed because
198/// `tracing-subscriber`'s `FormatEvent`/`FormatTime` traits expect a
199/// `fmt::Write` implementation, while `serde_json::Serializer` and `time`'s
200/// `format_into` methods expect an `io::Write`.
201// #[cfg(any(feature = "json", feature = "time"))]
202pub(crate) struct WriteAdaptor<'a> {
203    fmt_write: &'a mut dyn fmt::Write,
204}
205
206// === impl WriteAdaptor ===
207
208// #[cfg(any(feature = "json", feature = "time"))]
209impl<'a> WriteAdaptor<'a> {
210    pub fn new(fmt_write: &'a mut dyn fmt::Write) -> Self {
211        Self { fmt_write }
212    }
213}
214// #[cfg(any(feature = "json", feature = "time"))]
215impl<'a> io::Write for WriteAdaptor<'a> {
216    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
217        let s =
218            std::str::from_utf8(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
219
220        self.fmt_write
221            .write_str(s)
222            .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
223
224        Ok(s.as_bytes().len())
225    }
226
227    fn flush(&mut self) -> io::Result<()> {
228        Ok(())
229    }
230}
231
232// #[cfg(any(feature = "json", feature = "time"))]
233impl<'a> fmt::Debug for WriteAdaptor<'a> {
234    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235        f.pad("WriteAdaptor { .. }")
236    }
237}
238// === blanket impls ===
239
240#[cfg(test)]
241mod tests {
242    use super::LocalTime;
243    use time::macros::format_description;
244
245    #[test]
246    fn test_init_tracing() {
247        let timer = LocalTime::with_timezone(
248            format_description!(
249                "[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:3]"
250            ),
251            (8, 0, 0),
252        );
253        tracing_subscriber::fmt().with_timer(timer).init();
254    }
255}