twilight_model/util/datetime/
display.rs

1//! Display implementation for formatting a [`Timestamp`].
2
3use super::Timestamp;
4use serde::{Serialize, Serializer};
5use std::fmt::{Display, Formatter, Result as FmtResult};
6
7/// Display implementation to format a [`Timestamp`] in an ISO 8601 format.
8///
9/// # Examples
10///
11/// Format a timestamp as an ISO 8601 datetime both with microseconds:
12///
13/// ```
14/// # use std::error::Error;
15/// # fn foo() -> Result<(), Box<dyn Error>> {
16/// use twilight_model::util::Timestamp;
17///
18/// let timestamp = Timestamp::from_micros(1_628_594_197_020_000)?;
19/// assert_eq!(
20///     "2021-08-10T11:16:37.020000+00:00",
21///     timestamp.iso_8601().to_string(),
22/// );
23/// # Ok(()) }
24/// ```
25#[derive(Debug)]
26pub struct TimestampIso8601Display {
27    /// Timestamp.
28    timestamp: Timestamp,
29    /// Whether to format the timestamp with microseconds included.
30    with_microseconds: bool,
31}
32
33impl TimestampIso8601Display {
34    /// Create a new ISO 8601 display formatter for a timestamp.
35    pub(super) const fn new(timestamp: Timestamp) -> Self {
36        Self {
37            timestamp,
38            with_microseconds: true,
39        }
40    }
41
42    /// Get the inner timestamp.
43    pub const fn get(self) -> Timestamp {
44        self.timestamp
45    }
46
47    /// Whether to format the timestamp with microseconds.
48    ///
49    /// The ISO 8601 display formatter formats with microseconds by default.
50    ///
51    /// # Examples
52    ///
53    /// Format a timestamp with microseconds:
54    ///
55    /// ```
56    /// # use std::error::Error;
57    /// # fn foo() -> Result<(), Box<dyn Error>> {
58    /// use twilight_model::util::Timestamp;
59    ///
60    /// let timestamp = Timestamp::from_micros(1_628_594_197_020_000)?;
61    /// let formatter = timestamp.iso_8601().with_microseconds(true);
62    ///
63    /// assert_eq!("2021-08-10T11:16:37.020000+00:00", formatter.to_string());
64    /// # Ok(()) }
65    /// ```
66    ///
67    /// Format a timestamp without microseconds:
68    ///
69    /// ```
70    /// # use std::error::Error;
71    /// # fn foo() -> Result<(), Box<dyn Error>> {
72    /// use twilight_model::util::Timestamp;
73    ///
74    /// let timestamp = Timestamp::from_micros(1_628_594_197_020_000)?;
75    /// let formatter = timestamp.iso_8601().with_microseconds(false);
76    ///
77    /// assert_eq!("2021-08-10T11:16:37+00:00", formatter.to_string());
78    /// # Ok(()) }
79    /// ```
80    #[must_use]
81    pub const fn with_microseconds(mut self, with_microseconds: bool) -> Self {
82        self.with_microseconds = with_microseconds;
83
84        self
85    }
86}
87
88impl Display for TimestampIso8601Display {
89    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
90        // Years.
91        //
92        // Input: 2021-01-01T01:01:01.010000+00:00
93        //       |----|
94        let year = self.timestamp.0.year();
95
96        Display::fmt(&(year / 1000), f)?;
97        Display::fmt(&(year / 100 % 10), f)?;
98        Display::fmt(&(year / 10 % 10), f)?;
99        Display::fmt(&(year % 10), f)?;
100
101        f.write_str("-")?;
102
103        // Months.
104        //
105        // Input: 2021-01-01T01:01:01.010000+00:00
106        //            |--|
107        let month = self.timestamp.0.month() as u8;
108
109        Display::fmt(&(month / 10), f)?;
110        Display::fmt(&(month % 10), f)?;
111        f.write_str("-")?;
112
113        // Days.
114        //
115        // Input: 2021-01-01T01:01:01.010000+00:00
116        //               |--|
117        let day = self.timestamp.0.day();
118
119        Display::fmt(&(day / 10), f)?;
120        Display::fmt(&(day % 10), f)?;
121
122        // Time designator.
123        //
124        // Input: 2021-01-01T01:01:01.010000+00:00
125        //                 |-|
126        f.write_str("T")?;
127
128        // Hours.
129        //
130        // Input: 2021-01-01T01:01:01.010000+00:00
131        //                  |--|
132        let hour = self.timestamp.0.hour();
133
134        Display::fmt(&(hour / 10), f)?;
135        Display::fmt(&(hour % 10), f)?;
136
137        f.write_str(":")?;
138
139        // Minutes.
140        //
141        // Input: 2021-01-01T01:01:01.010000+00:00
142        //                     |--|
143        let minute = self.timestamp.0.minute();
144
145        Display::fmt(&(minute / 10 % 6), f)?;
146        Display::fmt(&(minute % 10), f)?;
147
148        f.write_str(":")?;
149
150        // Seconds.
151        //
152        // Input: 2021-01-01T01:01:01.010000+00:00
153        //                        |--|
154        let second = self.timestamp.0.second();
155
156        Display::fmt(&(second / 10 % 6), f)?;
157        Display::fmt(&(second % 10), f)?;
158
159        if self.with_microseconds {
160            // Subsecond designator.
161            //
162            // Input: 2021-01-01T01:01:01.010000+00:00
163            //                          |-|
164            f.write_str(".")?;
165
166            // Microseconds.
167            //
168            // Input: 2021-01-01T01:01:01.010000+00:00
169            //                           |------|
170            let microsecond = self.timestamp.0.microsecond();
171
172            Display::fmt(&(microsecond / 100_000), f)?;
173            Display::fmt(&(microsecond / 10_000 % 10), f)?;
174            Display::fmt(&(microsecond / 1_000 % 10), f)?;
175            Display::fmt(&(microsecond / 100 % 10), f)?;
176            Display::fmt(&(microsecond / 10 % 10), f)?;
177            Display::fmt(&(microsecond % 10), f)?;
178        }
179
180        // Finish it all off.
181        //
182        // Input: 2021-01-01T01:01:01.010000+00:00
183        //                                 |-----|
184        //
185        // The API doesn't operate in offsets other than +00:00, so we can just
186        // fill that in.
187        f.write_str("+00:00")
188    }
189}
190
191impl Serialize for TimestampIso8601Display {
192    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
193        serializer.collect_str(self)
194    }
195}
196
197#[cfg(test)]
198mod tests {
199    use super::{super::Timestamp, TimestampIso8601Display};
200    use serde::Serialize;
201    use static_assertions::assert_impl_all;
202    use std::fmt::Debug;
203
204    assert_impl_all!(TimestampIso8601Display: Debug, Send, Serialize, Sync);
205
206    #[test]
207    fn display() {
208        const LONG: &str = "2020-02-02T02:02:02.020000+00:00";
209        const SHORT: &str = "2020-02-02T02:02:02+00:00";
210        const TIME: i64 = 1_580_608_922_020_000;
211
212        let mut formatter = Timestamp::from_micros(TIME).expect("non zero").iso_8601();
213
214        // Default formatter should be with microseconds.
215        assert_eq!(LONG, formatter.to_string());
216
217        // Now with explicitly setting it to format with microseconds.
218        formatter = formatter.with_microseconds(true);
219        assert_eq!(LONG, formatter.to_string());
220
221        // And now without microseconds.
222        formatter = formatter.with_microseconds(false);
223        assert_eq!(SHORT, formatter.to_string());
224    }
225}