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 use super::*;
38
39 pub trait Sealed {
41 fn format_into(
43 &self,
44 output: &mut (impl io::Write + ?Sized),
45 date: Option<Date>,
46 time: Option<Time>,
47 offset: Option<UtcOffset>,
48 ) -> Result<usize, error::Format>;
49
50 #[inline]
52 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<'_> {
66 #[inline]
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 #[inline]
89 fn format_into(
90 &self,
91 output: &mut (impl io::Write + ?Sized),
92 date: Option<Date>,
93 time: Option<Time>,
94 offset: Option<UtcOffset>,
95 ) -> Result<usize, error::Format> {
96 let mut bytes = 0;
97 for item in self.iter() {
98 bytes += item.format_into(output, date, time, offset)?;
99 }
100 Ok(bytes)
101 }
102}
103
104impl sealed::Sealed for OwnedFormatItem {
105 #[inline]
106 fn format_into(
107 &self,
108 output: &mut (impl io::Write + ?Sized),
109 date: Option<Date>,
110 time: Option<Time>,
111 offset: Option<UtcOffset>,
112 ) -> Result<usize, error::Format> {
113 match self {
114 Self::Literal(literal) => Ok(write(output, literal)?),
115 Self::Component(component) => format_component(output, *component, date, time, offset),
116 Self::Compound(items) => items.format_into(output, date, time, offset),
117 Self::Optional(item) => item.format_into(output, date, time, offset),
118 Self::First(items) => match &**items {
119 [] => Ok(0),
120 [item, ..] => item.format_into(output, date, time, offset),
121 },
122 }
123 }
124}
125
126impl sealed::Sealed for [OwnedFormatItem] {
127 #[inline]
128 fn format_into(
129 &self,
130 output: &mut (impl io::Write + ?Sized),
131 date: Option<Date>,
132 time: Option<Time>,
133 offset: Option<UtcOffset>,
134 ) -> Result<usize, error::Format> {
135 let mut bytes = 0;
136 for item in self.iter() {
137 bytes += item.format_into(output, date, time, offset)?;
138 }
139 Ok(bytes)
140 }
141}
142
143impl<T> sealed::Sealed for T
144where
145 T: Deref<Target: sealed::Sealed>,
146{
147 #[inline]
148 fn format_into(
149 &self,
150 output: &mut (impl io::Write + ?Sized),
151 date: Option<Date>,
152 time: Option<Time>,
153 offset: Option<UtcOffset>,
154 ) -> Result<usize, error::Format> {
155 self.deref().format_into(output, date, time, offset)
156 }
157}
158
159impl sealed::Sealed for Rfc2822 {
160 fn format_into(
161 &self,
162 output: &mut (impl io::Write + ?Sized),
163 date: Option<Date>,
164 time: Option<Time>,
165 offset: Option<UtcOffset>,
166 ) -> Result<usize, error::Format> {
167 let date = date.ok_or(error::Format::InsufficientTypeInformation)?;
168 let time = time.ok_or(error::Format::InsufficientTypeInformation)?;
169 let offset = offset.ok_or(error::Format::InsufficientTypeInformation)?;
170
171 let mut bytes = 0;
172
173 let (year, month, day) = date.to_calendar_date();
174
175 if year < 1900 {
176 return Err(error::Format::InvalidComponent("year"));
177 }
178 if offset.seconds_past_minute() != 0 {
179 return Err(error::Format::InvalidComponent("offset_second"));
180 }
181
182 bytes += write(
183 output,
184 &WEEKDAY_NAMES[date.weekday().number_days_from_monday().extend::<usize>()][..3],
185 )?;
186 bytes += write(output, b", ")?;
187 bytes += format_number_pad_zero::<2>(output, day)?;
188 bytes += write(output, b" ")?;
189 bytes += write(
190 output,
191 &MONTH_NAMES[u8::from(month).extend::<usize>() - 1][..3],
192 )?;
193 bytes += write(output, b" ")?;
194 bytes += format_number_pad_zero::<4>(output, year.cast_unsigned())?;
195 bytes += write(output, b" ")?;
196 bytes += format_number_pad_zero::<2>(output, time.hour())?;
197 bytes += write(output, b":")?;
198 bytes += format_number_pad_zero::<2>(output, time.minute())?;
199 bytes += write(output, b":")?;
200 bytes += format_number_pad_zero::<2>(output, time.second())?;
201 bytes += write(output, b" ")?;
202 bytes += write(output, if offset.is_negative() { b"-" } else { b"+" })?;
203 bytes += format_number_pad_zero::<2>(output, offset.whole_hours().unsigned_abs())?;
204 bytes += format_number_pad_zero::<2>(output, offset.minutes_past_hour().unsigned_abs())?;
205
206 Ok(bytes)
207 }
208}
209
210impl sealed::Sealed for Rfc3339 {
211 fn format_into(
212 &self,
213 output: &mut (impl io::Write + ?Sized),
214 date: Option<Date>,
215 time: Option<Time>,
216 offset: Option<UtcOffset>,
217 ) -> Result<usize, error::Format> {
218 let date = date.ok_or(error::Format::InsufficientTypeInformation)?;
219 let time = time.ok_or(error::Format::InsufficientTypeInformation)?;
220 let offset = offset.ok_or(error::Format::InsufficientTypeInformation)?;
221
222 let mut bytes = 0;
223
224 let year = date.year();
225
226 if !(0..10_000).contains(&year) {
227 return Err(error::Format::InvalidComponent("year"));
228 }
229 if offset.whole_hours().unsigned_abs() > 23 {
230 return Err(error::Format::InvalidComponent("offset_hour"));
231 }
232 if offset.seconds_past_minute() != 0 {
233 return Err(error::Format::InvalidComponent("offset_second"));
234 }
235
236 bytes += format_number_pad_zero::<4>(output, year.cast_unsigned())?;
237 bytes += write(output, b"-")?;
238 bytes += format_number_pad_zero::<2>(output, u8::from(date.month()))?;
239 bytes += write(output, b"-")?;
240 bytes += format_number_pad_zero::<2>(output, date.day())?;
241 bytes += write(output, b"T")?;
242 bytes += format_number_pad_zero::<2>(output, time.hour())?;
243 bytes += write(output, b":")?;
244 bytes += format_number_pad_zero::<2>(output, time.minute())?;
245 bytes += write(output, b":")?;
246 bytes += format_number_pad_zero::<2>(output, time.second())?;
247
248 if time.nanosecond() != 0 {
249 let nanos = time.nanosecond();
250 bytes += write(output, b".")?;
251 bytes += if nanos % 10 != 0 {
252 format_number_pad_zero::<9>(output, nanos)
253 } else if (nanos / 10) % 10 != 0 {
254 format_number_pad_zero::<8>(output, nanos / 10)
255 } else if (nanos / 100) % 10 != 0 {
256 format_number_pad_zero::<7>(output, nanos / 100)
257 } else if (nanos / 1_000) % 10 != 0 {
258 format_number_pad_zero::<6>(output, nanos / 1_000)
259 } else if (nanos / 10_000) % 10 != 0 {
260 format_number_pad_zero::<5>(output, nanos / 10_000)
261 } else if (nanos / 100_000) % 10 != 0 {
262 format_number_pad_zero::<4>(output, nanos / 100_000)
263 } else if (nanos / 1_000_000) % 10 != 0 {
264 format_number_pad_zero::<3>(output, nanos / 1_000_000)
265 } else if (nanos / 10_000_000) % 10 != 0 {
266 format_number_pad_zero::<2>(output, nanos / 10_000_000)
267 } else {
268 format_number_pad_zero::<1>(output, nanos / 100_000_000)
269 }?;
270 }
271
272 if offset == UtcOffset::UTC {
273 bytes += write(output, b"Z")?;
274 return Ok(bytes);
275 }
276
277 bytes += write(output, if offset.is_negative() { b"-" } else { b"+" })?;
278 bytes += format_number_pad_zero::<2>(output, offset.whole_hours().unsigned_abs())?;
279 bytes += write(output, b":")?;
280 bytes += format_number_pad_zero::<2>(output, offset.minutes_past_hour().unsigned_abs())?;
281
282 Ok(bytes)
283 }
284}
285
286impl<const CONFIG: EncodedConfig> sealed::Sealed for Iso8601<CONFIG> {
287 #[inline]
288 fn format_into(
289 &self,
290 output: &mut (impl io::Write + ?Sized),
291 date: Option<Date>,
292 time: Option<Time>,
293 offset: Option<UtcOffset>,
294 ) -> Result<usize, error::Format> {
295 let mut bytes = 0;
296
297 if Self::FORMAT_DATE {
298 let date = date.ok_or(error::Format::InsufficientTypeInformation)?;
299 bytes += iso8601::format_date::<CONFIG>(output, date)?;
300 }
301 if Self::FORMAT_TIME {
302 let time = time.ok_or(error::Format::InsufficientTypeInformation)?;
303 bytes += iso8601::format_time::<CONFIG>(output, time)?;
304 }
305 if Self::FORMAT_OFFSET {
306 let offset = offset.ok_or(error::Format::InsufficientTypeInformation)?;
307 bytes += iso8601::format_offset::<CONFIG>(output, offset)?;
308 }
309
310 if bytes == 0 {
311 panic!("attempted to format a parsing-only format description");
314 }
315
316 Ok(bytes)
317 }
318}