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