1use alloc::string::String;
4use alloc::vec::Vec;
5use core::ops::Deref;
6use std::io;
7
8use deranged::{ru8, ru16};
9use num_conv::prelude::*;
10
11use crate::format_description::modifier::Padding;
12use crate::format_description::well_known::iso8601::EncodedConfig;
13use crate::format_description::well_known::{Iso8601, Rfc2822, Rfc3339};
14use crate::format_description::{BorrowedFormatItem, OwnedFormatItem};
15use crate::formatting::{
16 ComponentProvider, MONTH_NAMES, WEEKDAY_NAMES, format_component, format_four_digits_pad_zero,
17 format_two_digits, iso8601, write, write_bytes, write_if_else,
18};
19use crate::internal_macros::try_likely_ok;
20use crate::{error, num_fmt};
21
22#[cfg_attr(docsrs, doc(notable_trait))]
28pub trait Formattable: sealed::Sealed {}
29impl Formattable for BorrowedFormatItem<'_> {}
30impl Formattable for [BorrowedFormatItem<'_>] {}
31impl Formattable for OwnedFormatItem {}
32impl Formattable for [OwnedFormatItem] {}
33impl Formattable for Rfc3339 {}
34impl Formattable for Rfc2822 {}
35impl<const CONFIG: EncodedConfig> Formattable for Iso8601<CONFIG> {}
36impl<T> Formattable for T where T: Deref<Target: Formattable> {}
37
38mod sealed {
40 use super::*;
41 use crate::formatting::ComponentProvider;
42 use crate::formatting::metadata::ComputeMetadata;
43
44 #[expect(
46 private_bounds,
47 private_interfaces,
48 reason = "irrelevant due to being a sealed trait"
49 )]
50 pub trait Sealed: ComputeMetadata {
51 fn format_into<V>(
53 &self,
54 output: &mut (impl io::Write + ?Sized),
55 value: &V,
56 state: &mut V::State,
57 ) -> Result<usize, error::Format>
58 where
59 V: ComponentProvider;
60
61 #[inline]
63 fn format<V>(&self, value: &V, state: &mut V::State) -> Result<String, error::Format>
64 where
65 V: ComponentProvider,
66 {
67 let crate::formatting::metadata::Metadata {
68 max_bytes_needed,
69 guaranteed_utf8,
70 } = self.compute_metadata();
71
72 let mut buf = Vec::with_capacity(max_bytes_needed);
73 try_likely_ok!(self.format_into(&mut buf, value, state));
74 Ok(if guaranteed_utf8 {
75 unsafe { String::from_utf8_unchecked(buf) }
77 } else {
78 String::from_utf8_lossy(&buf).into_owned()
79 })
80 }
81 }
82}
83
84impl sealed::Sealed for BorrowedFormatItem<'_> {
85 #[expect(
86 private_bounds,
87 private_interfaces,
88 reason = "irrelevant due to being a sealed trait"
89 )]
90 #[inline]
91 fn format_into<V>(
92 &self,
93 output: &mut (impl io::Write + ?Sized),
94 value: &V,
95 state: &mut V::State,
96 ) -> Result<usize, error::Format>
97 where
98 V: ComponentProvider,
99 {
100 Ok(match *self {
101 #[expect(deprecated)]
102 Self::Literal(literal) => try_likely_ok!(write_bytes(output, literal)),
103 Self::StringLiteral(literal) => try_likely_ok!(write(output, literal)),
104 Self::Component(component) => {
105 try_likely_ok!(format_component(output, component, value, state))
106 }
107 Self::Compound(items) => try_likely_ok!((*items).format_into(output, value, state)),
108 Self::Optional(item) => try_likely_ok!((*item).format_into(output, value, state)),
109 Self::First(items) => match items {
110 [] => 0,
111 [item, ..] => try_likely_ok!((*item).format_into(output, value, state)),
112 },
113 })
114 }
115}
116
117impl sealed::Sealed for [BorrowedFormatItem<'_>] {
118 #[expect(
119 private_bounds,
120 private_interfaces,
121 reason = "irrelevant due to being a sealed trait"
122 )]
123 #[inline]
124 fn format_into<V>(
125 &self,
126 output: &mut (impl io::Write + ?Sized),
127 value: &V,
128 state: &mut V::State,
129 ) -> Result<usize, error::Format>
130 where
131 V: ComponentProvider,
132 {
133 let mut bytes = 0;
134 for item in self.iter() {
135 bytes += try_likely_ok!(item.format_into(output, value, state));
136 }
137 Ok(bytes)
138 }
139}
140
141impl sealed::Sealed for OwnedFormatItem {
142 #[expect(
143 private_bounds,
144 private_interfaces,
145 reason = "irrelevant due to being a sealed trait"
146 )]
147 #[inline]
148 fn format_into<V>(
149 &self,
150 output: &mut (impl io::Write + ?Sized),
151 value: &V,
152 state: &mut V::State,
153 ) -> Result<usize, error::Format>
154 where
155 V: ComponentProvider,
156 {
157 match self {
158 #[expect(deprecated)]
159 Self::Literal(literal) => Ok(try_likely_ok!(write_bytes(output, literal))),
160 Self::StringLiteral(literal) => Ok(try_likely_ok!(write(output, literal))),
161 Self::Component(component) => format_component(output, *component, value, state),
162 Self::Compound(items) => (**items).format_into(output, value, state),
163 Self::Optional(item) => (**item).format_into(output, value, state),
164 Self::First(items) => match &**items {
165 [] => Ok(0),
166 [item, ..] => (*item).format_into(output, value, state),
167 },
168 }
169 }
170}
171
172impl sealed::Sealed for [OwnedFormatItem] {
173 #[expect(
174 private_bounds,
175 private_interfaces,
176 reason = "irrelevant due to being a sealed trait"
177 )]
178 #[inline]
179 fn format_into<V>(
180 &self,
181 output: &mut (impl io::Write + ?Sized),
182 value: &V,
183 state: &mut V::State,
184 ) -> Result<usize, error::Format>
185 where
186 V: ComponentProvider,
187 {
188 let mut bytes = 0;
189 for item in self.iter() {
190 bytes += try_likely_ok!(item.format_into(output, value, state));
191 }
192 Ok(bytes)
193 }
194}
195
196impl<T> sealed::Sealed for T
197where
198 T: Deref<Target: sealed::Sealed>,
199{
200 #[expect(
201 private_bounds,
202 private_interfaces,
203 reason = "irrelevant due to being a sealed trait"
204 )]
205 #[inline]
206 fn format_into<V>(
207 &self,
208 output: &mut (impl io::Write + ?Sized),
209 value: &V,
210 state: &mut V::State,
211 ) -> Result<usize, error::Format>
212 where
213 V: ComponentProvider,
214 {
215 self.deref().format_into(output, value, state)
216 }
217}
218
219#[expect(
220 private_bounds,
221 private_interfaces,
222 reason = "irrelevant due to being a sealed trait"
223)]
224impl sealed::Sealed for Rfc2822 {
225 fn format_into<V>(
226 &self,
227 output: &mut (impl io::Write + ?Sized),
228 value: &V,
229 state: &mut V::State,
230 ) -> Result<usize, error::Format>
231 where
232 V: ComponentProvider,
233 {
234 const {
235 assert!(
236 V::SUPPLIES_DATE && V::SUPPLIES_TIME && V::SUPPLIES_OFFSET,
237 "Rfc2822 requires date, time, and offset components, but not all can be provided \
238 by this type"
239 );
240 }
241
242 let mut bytes = 0;
243
244 if value.calendar_year(state).get() < 1900
245 || (cfg!(feature = "large-dates") && value.calendar_year(state).get() >= 10_000)
247 {
248 crate::hint::cold_path();
249 return Err(error::Format::InvalidComponent("year"));
250 }
251 if value.offset_second(state).get() != 0 {
252 crate::hint::cold_path();
253 return Err(error::Format::InvalidComponent("offset_second"));
254 }
255
256 bytes += try_likely_ok!(write(output, unsafe {
258 WEEKDAY_NAMES[value
259 .weekday(state)
260 .number_days_from_monday()
261 .extend::<usize>()]
262 .get_unchecked(..3)
263 }));
264 bytes += try_likely_ok!(write(output, ", "));
265 bytes += try_likely_ok!(format_two_digits(
266 output,
267 value.day(state).expand(),
268 Padding::Zero
269 ));
270 bytes += try_likely_ok!(write(output, " "));
271 bytes += try_likely_ok!(write(output, unsafe {
273 MONTH_NAMES[u8::from(value.month(state)).extend::<usize>() - 1].get_unchecked(..3)
274 }));
275 bytes += try_likely_ok!(write(output, " "));
276 bytes += try_likely_ok!(format_four_digits_pad_zero(output, unsafe {
278 ru16::new_unchecked(value.calendar_year(state).get().cast_unsigned().truncate())
279 }));
280 bytes += try_likely_ok!(write(output, " "));
281 bytes += try_likely_ok!(format_two_digits(
282 output,
283 value.hour(state).expand(),
284 Padding::Zero
285 ));
286 bytes += try_likely_ok!(write(output, ":"));
287 bytes += try_likely_ok!(format_two_digits(
288 output,
289 value.minute(state).expand(),
290 Padding::Zero
291 ));
292 bytes += try_likely_ok!(write(output, ":"));
293 bytes += try_likely_ok!(format_two_digits(
294 output,
295 value.second(state).expand(),
296 Padding::Zero
297 ));
298 bytes += try_likely_ok!(write(output, " "));
299 bytes += try_likely_ok!(write_if_else(
300 output,
301 value.offset_is_negative(state),
302 "-",
303 "+"
304 ));
305 bytes += try_likely_ok!(format_two_digits(
306 output,
307 unsafe { ru8::new_unchecked(value.offset_hour(state).get().unsigned_abs()) },
310 Padding::Zero,
311 ));
312 bytes += try_likely_ok!(format_two_digits(
313 output,
314 unsafe { ru8::new_unchecked(value.offset_minute(state).get().unsigned_abs()) },
317 Padding::Zero,
318 ));
319
320 Ok(bytes)
321 }
322}
323
324#[expect(
325 private_bounds,
326 private_interfaces,
327 reason = "irrelevant due to being a sealed trait"
328)]
329impl sealed::Sealed for Rfc3339 {
330 fn format_into<V>(
331 &self,
332 output: &mut (impl io::Write + ?Sized),
333 value: &V,
334 state: &mut V::State,
335 ) -> Result<usize, error::Format>
336 where
337 V: ComponentProvider,
338 {
339 const {
340 assert!(
341 V::SUPPLIES_DATE && V::SUPPLIES_TIME && V::SUPPLIES_OFFSET,
342 "Rfc3339 requires date, time, and offset components, but not all can be provided \
343 by this type"
344 );
345 }
346
347 let offset_hour = value.offset_hour(state);
348 let mut bytes = 0;
349
350 if !(0..10_000).contains(&value.calendar_year(state).get()) {
351 crate::hint::cold_path();
352 return Err(error::Format::InvalidComponent("year"));
353 }
354 if offset_hour.get().unsigned_abs() > 23 {
355 crate::hint::cold_path();
356 return Err(error::Format::InvalidComponent("offset_hour"));
357 }
358 if value.offset_second(state).get() != 0 {
359 crate::hint::cold_path();
360 return Err(error::Format::InvalidComponent("offset_second"));
361 }
362
363 bytes += try_likely_ok!(format_four_digits_pad_zero(output, unsafe {
365 ru16::new_unchecked(value.calendar_year(state).get().cast_unsigned().truncate())
366 }));
367 bytes += try_likely_ok!(write(output, "-"));
368 bytes += try_likely_ok!(format_two_digits(
369 output,
370 unsafe { ru8::new_unchecked(u8::from(value.month(state))) },
372 Padding::Zero,
373 ));
374 bytes += try_likely_ok!(write(output, "-"));
375 bytes += try_likely_ok!(format_two_digits(
376 output,
377 value.day(state).expand(),
378 Padding::Zero
379 ));
380 bytes += try_likely_ok!(write(output, "T"));
381 bytes += try_likely_ok!(format_two_digits(
382 output,
383 value.hour(state).expand(),
384 Padding::Zero
385 ));
386 bytes += try_likely_ok!(write(output, ":"));
387 bytes += try_likely_ok!(format_two_digits(
388 output,
389 value.minute(state).expand(),
390 Padding::Zero
391 ));
392 bytes += try_likely_ok!(write(output, ":"));
393 bytes += try_likely_ok!(format_two_digits(
394 output,
395 value.second(state).expand(),
396 Padding::Zero
397 ));
398
399 let nanos = value.nanosecond(state);
400 if nanos.get() != 0 {
401 bytes += try_likely_ok!(write(output, "."));
402 try_likely_ok!(write(
403 output,
404 &num_fmt::truncated_subsecond_from_nanos(nanos)
405 ));
406 }
407
408 if value.offset_is_utc(state) {
409 bytes += try_likely_ok!(write(output, "Z"));
410 return Ok(bytes);
411 }
412
413 bytes += try_likely_ok!(write_if_else(
414 output,
415 value.offset_is_negative(state),
416 "-",
417 "+"
418 ));
419 bytes += try_likely_ok!(format_two_digits(
420 output,
421 unsafe { ru8::new_unchecked(offset_hour.get().unsigned_abs()) },
424 Padding::Zero,
425 ));
426 bytes += try_likely_ok!(write(output, ":"));
427 bytes += try_likely_ok!(format_two_digits(
428 output,
429 unsafe { ru8::new_unchecked(value.offset_minute(state).get().unsigned_abs()) },
432 Padding::Zero,
433 ));
434
435 Ok(bytes)
436 }
437}
438
439impl<const CONFIG: EncodedConfig> sealed::Sealed for Iso8601<CONFIG> {
440 #[expect(
441 private_bounds,
442 private_interfaces,
443 reason = "irrelevant due to being a sealed trait"
444 )]
445 #[inline]
446 fn format_into<V>(
447 &self,
448 output: &mut (impl io::Write + ?Sized),
449 value: &V,
450 state: &mut V::State,
451 ) -> Result<usize, error::Format>
452 where
453 V: ComponentProvider,
454 {
455 let mut bytes = 0;
456
457 const {
458 assert!(
459 !Self::FORMAT_DATE || V::SUPPLIES_DATE,
460 "this Iso8601 configuration formats date components, but this type cannot provide \
461 them"
462 );
463 assert!(
464 !Self::FORMAT_TIME || V::SUPPLIES_TIME,
465 "this Iso8601 configuration formats time components, but this type cannot provide \
466 them"
467 );
468 assert!(
469 !Self::FORMAT_OFFSET || V::SUPPLIES_OFFSET,
470 "this Iso8601 configuration formats offset components, but this type cannot \
471 provide them"
472 );
473 assert!(
474 Self::FORMAT_DATE || Self::FORMAT_TIME || Self::FORMAT_OFFSET,
475 "this Iso8601 configuration does not format any components"
476 );
477 }
478
479 if Self::FORMAT_DATE {
480 bytes += try_likely_ok!(iso8601::format_date::<_, CONFIG>(output, value, state));
481 }
482 if Self::FORMAT_TIME {
483 bytes += try_likely_ok!(iso8601::format_time::<_, CONFIG>(output, value, state));
484 }
485 if Self::FORMAT_OFFSET {
486 bytes += try_likely_ok!(iso8601::format_offset::<_, CONFIG>(output, value, state));
487 }
488
489 Ok(bytes)
490 }
491}