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::{
15 BorrowedFormatItem, FormatDescriptionV3, OwnedFormatItem, format_description_v3,
16};
17use crate::formatting::{
18 ComponentProvider, MONTH_NAMES, WEEKDAY_NAMES, fmt_component_v3, format_four_digits_pad_zero,
19 format_two_digits, iso8601, write, write_bytes, write_if_else,
20};
21use crate::internal_macros::try_likely_ok;
22use crate::{error, num_fmt};
23
24#[cfg_attr(docsrs, doc(notable_trait))]
30pub trait Formattable: sealed::Sealed {}
31impl Formattable for FormatDescriptionV3<'_> {}
32impl Formattable for BorrowedFormatItem<'_> {}
33impl Formattable for [BorrowedFormatItem<'_>] {}
34impl Formattable for OwnedFormatItem {}
35impl Formattable for [OwnedFormatItem] {}
36impl Formattable for Rfc3339 {}
37impl Formattable for Rfc2822 {}
38impl<const CONFIG: EncodedConfig> Formattable for Iso8601<CONFIG> {}
39impl<T> Formattable for T where T: Deref<Target: Formattable> {}
40
41mod sealed {
43 use super::*;
44 use crate::formatting::ComponentProvider;
45 use crate::formatting::metadata::ComputeMetadata;
46
47 #[expect(
49 private_bounds,
50 private_interfaces,
51 reason = "irrelevant due to being a sealed trait"
52 )]
53 pub trait Sealed: ComputeMetadata {
54 fn format_into<V>(
56 &self,
57 output: &mut (impl io::Write + ?Sized),
58 value: &V,
59 state: &mut V::State,
60 ) -> Result<usize, error::Format>
61 where
62 V: ComponentProvider;
63
64 #[inline]
66 fn format<V>(&self, value: &V, state: &mut V::State) -> Result<String, error::Format>
67 where
68 V: ComponentProvider,
69 {
70 let crate::formatting::metadata::Metadata {
71 max_bytes_needed,
72 guaranteed_utf8,
73 } = self.compute_metadata();
74
75 let mut buf = Vec::with_capacity(max_bytes_needed);
76 try_likely_ok!(self.format_into(&mut buf, value, state));
77 Ok(if guaranteed_utf8 {
78 unsafe { String::from_utf8_unchecked(buf) }
80 } else {
81 String::from_utf8_lossy(&buf).into_owned()
82 })
83 }
84 }
85}
86
87impl sealed::Sealed for FormatDescriptionV3<'_> {
88 #[expect(
89 private_bounds,
90 private_interfaces,
91 reason = "irrelevant due to being a sealed trait"
92 )]
93 #[inline]
94 fn format_into<V>(
95 &self,
96 output: &mut (impl io::Write + ?Sized),
97 value: &V,
98 state: &mut V::State,
99 ) -> Result<usize, error::Format>
100 where
101 V: ComponentProvider,
102 {
103 self.inner.format_into(output, value, state)
104 }
105}
106
107impl sealed::Sealed for format_description_v3::FormatDescriptionV3Inner<'_> {
108 #[expect(
109 private_bounds,
110 private_interfaces,
111 reason = "irrelevant due to being a sealed trait"
112 )]
113 #[inline]
114 fn format_into<V>(
115 &self,
116 output: &mut (impl io::Write + ?Sized),
117 value: &V,
118 state: &mut V::State,
119 ) -> Result<usize, error::Format>
120 where
121 V: ComponentProvider,
122 {
123 match &self {
124 Self::Component(component) => fmt_component_v3(output, value, state, component),
125 Self::BorrowedLiteral(literal) => {
126 write_bytes(output, literal.as_bytes()).map_err(Into::into)
127 }
128 Self::BorrowedCompound(items) => {
129 let mut bytes = 0;
130 for item in *items {
131 bytes += try_likely_ok!(item.format_into(output, value, state));
132 }
133 Ok(bytes)
134 }
135 Self::BorrowedOptional {
136 format: should_format,
137 item,
138 } => {
139 if *should_format {
140 item.format_into(output, value, state)
141 } else {
142 Ok(0)
143 }
144 }
145 Self::BorrowedFirst(items) => match items {
146 [] => Ok(0),
147 [item, ..] => item.format_into(output, value, state),
148 },
149 Self::OwnedLiteral(literal) => {
150 write_bytes(output, literal.as_bytes()).map_err(Into::into)
151 }
152 Self::OwnedCompound(items) => {
153 let mut bytes = 0;
154 for item in &**items {
155 bytes += try_likely_ok!(item.format_into(output, value, state));
156 }
157 Ok(bytes)
158 }
159 Self::OwnedOptional {
160 format: should_format,
161 item,
162 } => {
163 if *should_format {
164 item.format_into(output, value, state)
165 } else {
166 Ok(0)
167 }
168 }
169 Self::OwnedFirst(items) => match &items[..] {
170 [] => Ok(0),
171 [item, ..] => item.format_into(output, value, state),
172 },
173 }
174 }
175}
176
177impl sealed::Sealed for BorrowedFormatItem<'_> {
178 #[expect(
179 private_bounds,
180 private_interfaces,
181 reason = "irrelevant due to being a sealed trait"
182 )]
183 #[inline]
184 fn format_into<V>(
185 &self,
186 output: &mut (impl io::Write + ?Sized),
187 value: &V,
188 state: &mut V::State,
189 ) -> Result<usize, error::Format>
190 where
191 V: ComponentProvider,
192 {
193 Ok(match *self {
194 #[expect(deprecated)]
195 Self::Literal(literal) => try_likely_ok!(write_bytes(output, literal)),
196 Self::StringLiteral(literal) => try_likely_ok!(write(output, literal)),
197 Self::Component(component) => {
198 try_likely_ok!(fmt_component_v3(output, value, state, &component.into()))
199 }
200 Self::Compound(items) => try_likely_ok!((*items).format_into(output, value, state)),
201 Self::Optional(item) => try_likely_ok!((*item).format_into(output, value, state)),
202 Self::First(items) => match items {
203 [] => 0,
204 [item, ..] => try_likely_ok!((*item).format_into(output, value, state)),
205 },
206 })
207 }
208}
209
210impl sealed::Sealed for [BorrowedFormatItem<'_>] {
211 #[expect(
212 private_bounds,
213 private_interfaces,
214 reason = "irrelevant due to being a sealed trait"
215 )]
216 #[inline]
217 fn format_into<V>(
218 &self,
219 output: &mut (impl io::Write + ?Sized),
220 value: &V,
221 state: &mut V::State,
222 ) -> Result<usize, error::Format>
223 where
224 V: ComponentProvider,
225 {
226 let mut bytes = 0;
227 for item in self.iter() {
228 bytes += try_likely_ok!(item.format_into(output, value, state));
229 }
230 Ok(bytes)
231 }
232}
233
234impl sealed::Sealed for OwnedFormatItem {
235 #[expect(
236 private_bounds,
237 private_interfaces,
238 reason = "irrelevant due to being a sealed trait"
239 )]
240 #[inline]
241 fn format_into<V>(
242 &self,
243 output: &mut (impl io::Write + ?Sized),
244 value: &V,
245 state: &mut V::State,
246 ) -> Result<usize, error::Format>
247 where
248 V: ComponentProvider,
249 {
250 match self {
251 #[expect(deprecated)]
252 Self::Literal(literal) => Ok(try_likely_ok!(write_bytes(output, literal))),
253 Self::StringLiteral(literal) => Ok(try_likely_ok!(write(output, literal))),
254 Self::Component(component) => {
255 fmt_component_v3(output, value, state, &((*component).into()))
256 }
257 Self::Compound(items) => (**items).format_into(output, value, state),
258 Self::Optional(item) => (**item).format_into(output, value, state),
259 Self::First(items) => match &**items {
260 [] => Ok(0),
261 [item, ..] => (*item).format_into(output, value, state),
262 },
263 }
264 }
265}
266
267impl sealed::Sealed for [OwnedFormatItem] {
268 #[expect(
269 private_bounds,
270 private_interfaces,
271 reason = "irrelevant due to being a sealed trait"
272 )]
273 #[inline]
274 fn format_into<V>(
275 &self,
276 output: &mut (impl io::Write + ?Sized),
277 value: &V,
278 state: &mut V::State,
279 ) -> Result<usize, error::Format>
280 where
281 V: ComponentProvider,
282 {
283 let mut bytes = 0;
284 for item in self.iter() {
285 bytes += try_likely_ok!(item.format_into(output, value, state));
286 }
287 Ok(bytes)
288 }
289}
290
291impl<T> sealed::Sealed for T
292where
293 T: Deref<Target: sealed::Sealed>,
294{
295 #[expect(
296 private_bounds,
297 private_interfaces,
298 reason = "irrelevant due to being a sealed trait"
299 )]
300 #[inline]
301 fn format_into<V>(
302 &self,
303 output: &mut (impl io::Write + ?Sized),
304 value: &V,
305 state: &mut V::State,
306 ) -> Result<usize, error::Format>
307 where
308 V: ComponentProvider,
309 {
310 self.deref().format_into(output, value, state)
311 }
312}
313
314#[expect(
315 private_bounds,
316 private_interfaces,
317 reason = "irrelevant due to being a sealed trait"
318)]
319impl sealed::Sealed for Rfc2822 {
320 fn format_into<V>(
321 &self,
322 output: &mut (impl io::Write + ?Sized),
323 value: &V,
324 state: &mut V::State,
325 ) -> Result<usize, error::Format>
326 where
327 V: ComponentProvider,
328 {
329 const {
330 assert!(
331 V::SUPPLIES_DATE && V::SUPPLIES_TIME && V::SUPPLIES_OFFSET,
332 "Rfc2822 requires date, time, and offset components, but not all can be provided \
333 by this type"
334 );
335 }
336
337 let mut bytes = 0;
338
339 if value.calendar_year(state).get() < 1900
340 || (cfg!(feature = "large-dates") && value.calendar_year(state).get() >= 10_000)
342 {
343 crate::hint::cold_path();
344 return Err(error::Format::InvalidComponent("year"));
345 }
346 if value.offset_second(state).get() != 0 {
347 crate::hint::cold_path();
348 return Err(error::Format::InvalidComponent("offset_second"));
349 }
350
351 bytes += try_likely_ok!(write(output, unsafe {
353 WEEKDAY_NAMES[value
354 .weekday(state)
355 .number_days_from_monday()
356 .extend::<usize>()]
357 .get_unchecked(..3)
358 }));
359 bytes += try_likely_ok!(write(output, ", "));
360 bytes += try_likely_ok!(format_two_digits(
361 output,
362 value.day(state).expand(),
363 Padding::Zero
364 ));
365 bytes += try_likely_ok!(write(output, " "));
366 bytes += try_likely_ok!(write(output, unsafe {
368 MONTH_NAMES[u8::from(value.month(state)).extend::<usize>() - 1].get_unchecked(..3)
369 }));
370 bytes += try_likely_ok!(write(output, " "));
371 bytes += try_likely_ok!(format_four_digits_pad_zero(output, unsafe {
373 ru16::new_unchecked(value.calendar_year(state).get().cast_unsigned().truncate())
374 }));
375 bytes += try_likely_ok!(write(output, " "));
376 bytes += try_likely_ok!(format_two_digits(
377 output,
378 value.hour(state).expand(),
379 Padding::Zero
380 ));
381 bytes += try_likely_ok!(write(output, ":"));
382 bytes += try_likely_ok!(format_two_digits(
383 output,
384 value.minute(state).expand(),
385 Padding::Zero
386 ));
387 bytes += try_likely_ok!(write(output, ":"));
388 bytes += try_likely_ok!(format_two_digits(
389 output,
390 value.second(state).expand(),
391 Padding::Zero
392 ));
393 bytes += try_likely_ok!(write(output, " "));
394 bytes += try_likely_ok!(write_if_else(
395 output,
396 value.offset_is_negative(state),
397 "-",
398 "+"
399 ));
400 bytes += try_likely_ok!(format_two_digits(
401 output,
402 unsafe { ru8::new_unchecked(value.offset_hour(state).get().unsigned_abs()) },
405 Padding::Zero,
406 ));
407 bytes += try_likely_ok!(format_two_digits(
408 output,
409 unsafe { ru8::new_unchecked(value.offset_minute(state).get().unsigned_abs()) },
412 Padding::Zero,
413 ));
414
415 Ok(bytes)
416 }
417}
418
419#[expect(
420 private_bounds,
421 private_interfaces,
422 reason = "irrelevant due to being a sealed trait"
423)]
424impl sealed::Sealed for Rfc3339 {
425 fn format_into<V>(
426 &self,
427 output: &mut (impl io::Write + ?Sized),
428 value: &V,
429 state: &mut V::State,
430 ) -> Result<usize, error::Format>
431 where
432 V: ComponentProvider,
433 {
434 const {
435 assert!(
436 V::SUPPLIES_DATE && V::SUPPLIES_TIME && V::SUPPLIES_OFFSET,
437 "Rfc3339 requires date, time, and offset components, but not all can be provided \
438 by this type"
439 );
440 }
441
442 let offset_hour = value.offset_hour(state);
443 let mut bytes = 0;
444
445 if !(0..10_000).contains(&value.calendar_year(state).get()) {
446 crate::hint::cold_path();
447 return Err(error::Format::InvalidComponent("year"));
448 }
449 if offset_hour.get().unsigned_abs() > 23 {
450 crate::hint::cold_path();
451 return Err(error::Format::InvalidComponent("offset_hour"));
452 }
453 if value.offset_second(state).get() != 0 {
454 crate::hint::cold_path();
455 return Err(error::Format::InvalidComponent("offset_second"));
456 }
457
458 bytes += try_likely_ok!(format_four_digits_pad_zero(output, unsafe {
460 ru16::new_unchecked(value.calendar_year(state).get().cast_unsigned().truncate())
461 }));
462 bytes += try_likely_ok!(write(output, "-"));
463 bytes += try_likely_ok!(format_two_digits(
464 output,
465 unsafe { ru8::new_unchecked(u8::from(value.month(state))) },
467 Padding::Zero,
468 ));
469 bytes += try_likely_ok!(write(output, "-"));
470 bytes += try_likely_ok!(format_two_digits(
471 output,
472 value.day(state).expand(),
473 Padding::Zero
474 ));
475 bytes += try_likely_ok!(write(output, "T"));
476 bytes += try_likely_ok!(format_two_digits(
477 output,
478 value.hour(state).expand(),
479 Padding::Zero
480 ));
481 bytes += try_likely_ok!(write(output, ":"));
482 bytes += try_likely_ok!(format_two_digits(
483 output,
484 value.minute(state).expand(),
485 Padding::Zero
486 ));
487 bytes += try_likely_ok!(write(output, ":"));
488 bytes += try_likely_ok!(format_two_digits(
489 output,
490 value.second(state).expand(),
491 Padding::Zero
492 ));
493
494 let nanos = value.nanosecond(state);
495 if nanos.get() != 0 {
496 bytes += try_likely_ok!(write(output, "."));
497 try_likely_ok!(write(
498 output,
499 &num_fmt::truncated_subsecond_from_nanos(nanos)
500 ));
501 }
502
503 if value.offset_is_utc(state) {
504 bytes += try_likely_ok!(write(output, "Z"));
505 return Ok(bytes);
506 }
507
508 bytes += try_likely_ok!(write_if_else(
509 output,
510 value.offset_is_negative(state),
511 "-",
512 "+"
513 ));
514 bytes += try_likely_ok!(format_two_digits(
515 output,
516 unsafe { ru8::new_unchecked(offset_hour.get().unsigned_abs()) },
519 Padding::Zero,
520 ));
521 bytes += try_likely_ok!(write(output, ":"));
522 bytes += try_likely_ok!(format_two_digits(
523 output,
524 unsafe { ru8::new_unchecked(value.offset_minute(state).get().unsigned_abs()) },
527 Padding::Zero,
528 ));
529
530 Ok(bytes)
531 }
532}
533
534impl<const CONFIG: EncodedConfig> sealed::Sealed for Iso8601<CONFIG> {
535 #[expect(
536 private_bounds,
537 private_interfaces,
538 reason = "irrelevant due to being a sealed trait"
539 )]
540 #[inline]
541 fn format_into<V>(
542 &self,
543 output: &mut (impl io::Write + ?Sized),
544 value: &V,
545 state: &mut V::State,
546 ) -> Result<usize, error::Format>
547 where
548 V: ComponentProvider,
549 {
550 let mut bytes = 0;
551
552 const {
553 assert!(
554 !Self::FORMAT_DATE || V::SUPPLIES_DATE,
555 "this Iso8601 configuration formats date components, but this type cannot provide \
556 them"
557 );
558 assert!(
559 !Self::FORMAT_TIME || V::SUPPLIES_TIME,
560 "this Iso8601 configuration formats time components, but this type cannot provide \
561 them"
562 );
563 assert!(
564 !Self::FORMAT_OFFSET || V::SUPPLIES_OFFSET,
565 "this Iso8601 configuration formats offset components, but this type cannot \
566 provide them"
567 );
568 assert!(
569 Self::FORMAT_DATE || Self::FORMAT_TIME || Self::FORMAT_OFFSET,
570 "this Iso8601 configuration does not format any components"
571 );
572 }
573
574 if Self::FORMAT_DATE {
575 bytes += try_likely_ok!(iso8601::format_date::<_, CONFIG>(output, value, state));
576 }
577 if Self::FORMAT_TIME {
578 bytes += try_likely_ok!(iso8601::format_time::<_, CONFIG>(output, value, state));
579 }
580 if Self::FORMAT_OFFSET {
581 bytes += try_likely_ok!(iso8601::format_offset::<_, CONFIG>(output, value, state));
582 }
583
584 Ok(bytes)
585 }
586}