1 | //! A trait that can be used to format an item from its components. |
2 | |
3 | use core::ops::Deref; |
4 | use std::io; |
5 | |
6 | use crate::format_description::well_known::iso8601::EncodedConfig; |
7 | use crate::format_description::well_known::{Iso8601, Rfc2822, Rfc3339}; |
8 | use crate::format_description::{FormatItem, OwnedFormatItem}; |
9 | use crate::formatting::{ |
10 | format_component, format_number_pad_zero, iso8601, write, MONTH_NAMES, WEEKDAY_NAMES, |
11 | }; |
12 | use crate::{error, Date, Time, UtcOffset}; |
13 | |
14 | /// A type that describes a format. |
15 | /// |
16 | /// Implementors of [`Formattable`] are [format descriptions](crate::format_description). |
17 | /// |
18 | /// [`Date::format`] and [`Time::format`] each use a format description to generate |
19 | /// a String from their data. See the respective methods for usage examples. |
20 | #[cfg_attr (__time_03_docs, doc(notable_trait))] |
21 | pub trait Formattable: sealed::Sealed {} |
22 | impl Formattable for FormatItem<'_> {} |
23 | impl Formattable for [FormatItem<'_>] {} |
24 | impl Formattable for OwnedFormatItem {} |
25 | impl Formattable for [OwnedFormatItem] {} |
26 | impl Formattable for Rfc3339 {} |
27 | impl Formattable for Rfc2822 {} |
28 | impl<const CONFIG: EncodedConfig> Formattable for Iso8601<CONFIG> {} |
29 | impl<T: Deref> Formattable for T where T::Target: Formattable {} |
30 | |
31 | /// Seal the trait to prevent downstream users from implementing it. |
32 | mod sealed { |
33 | #[allow (clippy::wildcard_imports)] |
34 | use super::*; |
35 | |
36 | /// Format the item using a format description, the intended output, and the various components. |
37 | pub trait Sealed { |
38 | /// Format the item into the provided output, returning the number of bytes written. |
39 | fn format_into( |
40 | &self, |
41 | output: &mut impl io::Write, |
42 | date: Option<Date>, |
43 | time: Option<Time>, |
44 | offset: Option<UtcOffset>, |
45 | ) -> Result<usize, error::Format>; |
46 | |
47 | /// Format the item directly to a `String`. |
48 | fn format( |
49 | &self, |
50 | date: Option<Date>, |
51 | time: Option<Time>, |
52 | offset: Option<UtcOffset>, |
53 | ) -> Result<String, error::Format> { |
54 | let mut buf = Vec::new(); |
55 | self.format_into(&mut buf, date, time, offset)?; |
56 | Ok(String::from_utf8_lossy(&buf).into_owned()) |
57 | } |
58 | } |
59 | } |
60 | |
61 | // region: custom formats |
62 | impl<'a> sealed::Sealed for FormatItem<'a> { |
63 | fn format_into( |
64 | &self, |
65 | output: &mut impl io::Write, |
66 | date: Option<Date>, |
67 | time: Option<Time>, |
68 | offset: Option<UtcOffset>, |
69 | ) -> Result<usize, error::Format> { |
70 | Ok(match *self { |
71 | Self::Literal(literal: &[u8]) => write(output, bytes:literal)?, |
72 | Self::Component(component: Component) => format_component(output, component, date, time, offset)?, |
73 | Self::Compound(items: &[BorrowedFormatItem<'_>]) => items.format_into(output, date, time, offset)?, |
74 | Self::Optional(item: &BorrowedFormatItem<'_>) => item.format_into(output, date, time, offset)?, |
75 | Self::First(items: &[BorrowedFormatItem<'_>]) => match items { |
76 | [] => 0, |
77 | [item: &BorrowedFormatItem<'_>, ..] => item.format_into(output, date, time, offset)?, |
78 | }, |
79 | }) |
80 | } |
81 | } |
82 | |
83 | impl<'a> sealed::Sealed for [FormatItem<'a>] { |
84 | fn format_into( |
85 | &self, |
86 | output: &mut impl io::Write, |
87 | date: Option<Date>, |
88 | time: Option<Time>, |
89 | offset: Option<UtcOffset>, |
90 | ) -> Result<usize, error::Format> { |
91 | let mut bytes: usize = 0; |
92 | for item: &BorrowedFormatItem<'_> in self.iter() { |
93 | bytes += item.format_into(output, date, time, offset)?; |
94 | } |
95 | Ok(bytes) |
96 | } |
97 | } |
98 | |
99 | impl sealed::Sealed for OwnedFormatItem { |
100 | fn format_into( |
101 | &self, |
102 | output: &mut impl io::Write, |
103 | date: Option<Date>, |
104 | time: Option<Time>, |
105 | offset: Option<UtcOffset>, |
106 | ) -> Result<usize, error::Format> { |
107 | match self { |
108 | Self::Literal(literal: &Box<[u8]>) => Ok(write(output, bytes:literal)?), |
109 | Self::Component(component: &Component) => format_component(output, *component, date, time, offset), |
110 | Self::Compound(items: &Box<[OwnedFormatItem]>) => items.format_into(output, date, time, offset), |
111 | Self::Optional(item: &Box) => item.format_into(output, date, time, offset), |
112 | Self::First(items: &Box<[OwnedFormatItem]>) => match &**items { |
113 | [] => Ok(0), |
114 | [item: &OwnedFormatItem, ..] => item.format_into(output, date, time, offset), |
115 | }, |
116 | } |
117 | } |
118 | } |
119 | |
120 | impl sealed::Sealed for [OwnedFormatItem] { |
121 | fn format_into( |
122 | &self, |
123 | output: &mut impl io::Write, |
124 | date: Option<Date>, |
125 | time: Option<Time>, |
126 | offset: Option<UtcOffset>, |
127 | ) -> Result<usize, error::Format> { |
128 | let mut bytes: usize = 0; |
129 | for item: &OwnedFormatItem in self.iter() { |
130 | bytes += item.format_into(output, date, time, offset)?; |
131 | } |
132 | Ok(bytes) |
133 | } |
134 | } |
135 | |
136 | impl<T: Deref> sealed::Sealed for T |
137 | where |
138 | T::Target: sealed::Sealed, |
139 | { |
140 | fn format_into( |
141 | &self, |
142 | output: &mut impl io::Write, |
143 | date: Option<Date>, |
144 | time: Option<Time>, |
145 | offset: Option<UtcOffset>, |
146 | ) -> Result<usize, error::Format> { |
147 | self.deref().format_into(output, date, time, offset) |
148 | } |
149 | } |
150 | // endregion custom formats |
151 | |
152 | // region: well-known formats |
153 | impl sealed::Sealed for Rfc2822 { |
154 | fn format_into( |
155 | &self, |
156 | output: &mut impl io::Write, |
157 | date: Option<Date>, |
158 | time: Option<Time>, |
159 | offset: Option<UtcOffset>, |
160 | ) -> Result<usize, error::Format> { |
161 | let date = date.ok_or(error::Format::InsufficientTypeInformation)?; |
162 | let time = time.ok_or(error::Format::InsufficientTypeInformation)?; |
163 | let offset = offset.ok_or(error::Format::InsufficientTypeInformation)?; |
164 | |
165 | let mut bytes = 0; |
166 | |
167 | let (year, month, day) = date.to_calendar_date(); |
168 | |
169 | if year < 1900 { |
170 | return Err(error::Format::InvalidComponent("year" )); |
171 | } |
172 | if offset.seconds_past_minute() != 0 { |
173 | return Err(error::Format::InvalidComponent("offset_second" )); |
174 | } |
175 | |
176 | bytes += write( |
177 | output, |
178 | &WEEKDAY_NAMES[date.weekday().number_days_from_monday() as usize][..3], |
179 | )?; |
180 | bytes += write(output, b", " )?; |
181 | bytes += format_number_pad_zero::<2>(output, day)?; |
182 | bytes += write(output, b" " )?; |
183 | bytes += write(output, &MONTH_NAMES[month as usize - 1][..3])?; |
184 | bytes += write(output, b" " )?; |
185 | bytes += format_number_pad_zero::<4>(output, year as u32)?; |
186 | bytes += write(output, b" " )?; |
187 | bytes += format_number_pad_zero::<2>(output, time.hour())?; |
188 | bytes += write(output, b":" )?; |
189 | bytes += format_number_pad_zero::<2>(output, time.minute())?; |
190 | bytes += write(output, b":" )?; |
191 | bytes += format_number_pad_zero::<2>(output, time.second())?; |
192 | bytes += write(output, b" " )?; |
193 | bytes += write(output, if offset.is_negative() { b"-" } else { b"+" })?; |
194 | bytes += format_number_pad_zero::<2>(output, offset.whole_hours().unsigned_abs())?; |
195 | bytes += format_number_pad_zero::<2>(output, offset.minutes_past_hour().unsigned_abs())?; |
196 | |
197 | Ok(bytes) |
198 | } |
199 | } |
200 | |
201 | impl sealed::Sealed for Rfc3339 { |
202 | fn format_into( |
203 | &self, |
204 | output: &mut impl io::Write, |
205 | date: Option<Date>, |
206 | time: Option<Time>, |
207 | offset: Option<UtcOffset>, |
208 | ) -> Result<usize, error::Format> { |
209 | let date = date.ok_or(error::Format::InsufficientTypeInformation)?; |
210 | let time = time.ok_or(error::Format::InsufficientTypeInformation)?; |
211 | let offset = offset.ok_or(error::Format::InsufficientTypeInformation)?; |
212 | |
213 | let mut bytes = 0; |
214 | |
215 | let year = date.year(); |
216 | |
217 | if !(0..10_000).contains(&year) { |
218 | return Err(error::Format::InvalidComponent("year" )); |
219 | } |
220 | if offset.seconds_past_minute() != 0 { |
221 | return Err(error::Format::InvalidComponent("offset_second" )); |
222 | } |
223 | |
224 | bytes += format_number_pad_zero::<4>(output, year as u32)?; |
225 | bytes += write(output, b"-" )?; |
226 | bytes += format_number_pad_zero::<2>(output, date.month() as u8)?; |
227 | bytes += write(output, b"-" )?; |
228 | bytes += format_number_pad_zero::<2>(output, date.day())?; |
229 | bytes += write(output, b"T" )?; |
230 | bytes += format_number_pad_zero::<2>(output, time.hour())?; |
231 | bytes += write(output, b":" )?; |
232 | bytes += format_number_pad_zero::<2>(output, time.minute())?; |
233 | bytes += write(output, b":" )?; |
234 | bytes += format_number_pad_zero::<2>(output, time.second())?; |
235 | |
236 | #[allow (clippy::if_not_else)] |
237 | if time.nanosecond() != 0 { |
238 | let nanos = time.nanosecond(); |
239 | bytes += write(output, b"." )?; |
240 | bytes += if nanos % 10 != 0 { |
241 | format_number_pad_zero::<9>(output, nanos) |
242 | } else if (nanos / 10) % 10 != 0 { |
243 | format_number_pad_zero::<8>(output, nanos / 10) |
244 | } else if (nanos / 100) % 10 != 0 { |
245 | format_number_pad_zero::<7>(output, nanos / 100) |
246 | } else if (nanos / 1_000) % 10 != 0 { |
247 | format_number_pad_zero::<6>(output, nanos / 1_000) |
248 | } else if (nanos / 10_000) % 10 != 0 { |
249 | format_number_pad_zero::<5>(output, nanos / 10_000) |
250 | } else if (nanos / 100_000) % 10 != 0 { |
251 | format_number_pad_zero::<4>(output, nanos / 100_000) |
252 | } else if (nanos / 1_000_000) % 10 != 0 { |
253 | format_number_pad_zero::<3>(output, nanos / 1_000_000) |
254 | } else if (nanos / 10_000_000) % 10 != 0 { |
255 | format_number_pad_zero::<2>(output, nanos / 10_000_000) |
256 | } else { |
257 | format_number_pad_zero::<1>(output, nanos / 100_000_000) |
258 | }?; |
259 | } |
260 | |
261 | if offset == UtcOffset::UTC { |
262 | bytes += write(output, b"Z" )?; |
263 | return Ok(bytes); |
264 | } |
265 | |
266 | bytes += write(output, if offset.is_negative() { b"-" } else { b"+" })?; |
267 | bytes += format_number_pad_zero::<2>(output, offset.whole_hours().unsigned_abs())?; |
268 | bytes += write(output, b":" )?; |
269 | bytes += format_number_pad_zero::<2>(output, offset.minutes_past_hour().unsigned_abs())?; |
270 | |
271 | Ok(bytes) |
272 | } |
273 | } |
274 | |
275 | impl<const CONFIG: EncodedConfig> sealed::Sealed for Iso8601<CONFIG> { |
276 | fn format_into( |
277 | &self, |
278 | output: &mut impl io::Write, |
279 | date: Option<Date>, |
280 | time: Option<Time>, |
281 | offset: Option<UtcOffset>, |
282 | ) -> Result<usize, error::Format> { |
283 | let mut bytes = 0; |
284 | |
285 | if Self::FORMAT_DATE { |
286 | let date = date.ok_or(error::Format::InsufficientTypeInformation)?; |
287 | bytes += iso8601::format_date::<CONFIG>(output, date)?; |
288 | } |
289 | if Self::FORMAT_TIME { |
290 | let time = time.ok_or(error::Format::InsufficientTypeInformation)?; |
291 | bytes += iso8601::format_time::<CONFIG>(output, time)?; |
292 | } |
293 | if Self::FORMAT_OFFSET { |
294 | let offset = offset.ok_or(error::Format::InsufficientTypeInformation)?; |
295 | bytes += iso8601::format_offset::<CONFIG>(output, offset)?; |
296 | } |
297 | |
298 | if bytes == 0 { |
299 | // The only reason there would be no bytes written is if the format was only for |
300 | // parsing. |
301 | panic!("attempted to format a parsing-only format description" ); |
302 | } |
303 | |
304 | Ok(bytes) |
305 | } |
306 | } |
307 | // endregion well-known formats |
308 | |