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}