1//! A trait that can be used to format an item from its components.
2
3use core::ops::Deref;
4use std::io;
5
6use crate::format_description::well_known::iso8601::EncodedConfig;
7use crate::format_description::well_known::{Iso8601, Rfc2822, Rfc3339};
8use crate::format_description::{FormatItem, OwnedFormatItem};
9use crate::formatting::{
10 format_component, format_number_pad_zero, iso8601, write, MONTH_NAMES, WEEKDAY_NAMES,
11};
12use 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))]
21pub trait Formattable: sealed::Sealed {}
22impl Formattable for FormatItem<'_> {}
23impl Formattable for [FormatItem<'_>] {}
24impl Formattable for OwnedFormatItem {}
25impl Formattable for [OwnedFormatItem] {}
26impl Formattable for Rfc3339 {}
27impl Formattable for Rfc2822 {}
28impl<const CONFIG: EncodedConfig> Formattable for Iso8601<CONFIG> {}
29impl<T: Deref> Formattable for T where T::Target: Formattable {}
30
31/// Seal the trait to prevent downstream users from implementing it.
32mod 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
62impl<'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
83impl<'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
99impl 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
120impl 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
136impl<T: Deref> sealed::Sealed for T
137where
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
153impl 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
201impl 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
275impl<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