Skip to main content

time/formatting/
mod.rs

1//! Formatting for various types.
2
3mod component_provider;
4pub(crate) mod formattable;
5mod iso8601;
6mod metadata;
7
8use core::mem::MaybeUninit;
9use core::num::NonZero;
10use std::io;
11
12use deranged::{Option_ri32, Option_ru8, ri8, ri16, ri32, ru8, ru16, ru32};
13use num_conv::prelude::*;
14
15use self::component_provider::ComponentProvider;
16pub use self::formattable::Formattable;
17use crate::format_description::{Period, modifier};
18use crate::internal_macros::try_likely_ok;
19use crate::time::{Hours, Minutes, Nanoseconds, Seconds};
20use crate::utc_offset::{Hours as OffsetHours, Minutes as OffsetMinutes, Seconds as OffsetSeconds};
21use crate::{Month, Weekday, error, num_fmt};
22
23type Day = ru8<1, 31>;
24type OptionDay = Option_ru8<1, 31>;
25type Ordinal = ru16<1, 366>;
26type IsoWeekNumber = ru8<1, 53>;
27type OptionIsoWeekNumber = Option_ru8<1, 53>;
28type MondayBasedWeek = ru8<0, 53>;
29type SundayBasedWeek = ru8<0, 53>;
30type Year = ri32<-999_999, 999_999>;
31type StandardYear = ri16<-9_999, 9_999>;
32type OptionYear = Option_ri32<-999_999, 999_999>;
33type ExtendedCentury = ri16<-9_999, 9_999>;
34type StandardCentury = ri8<-99, 99>;
35type LastTwo = ru8<0, 99>;
36
37const MONTH_NAMES: [&str; 12] = [
38    "January",
39    "February",
40    "March",
41    "April",
42    "May",
43    "June",
44    "July",
45    "August",
46    "September",
47    "October",
48    "November",
49    "December",
50];
51
52const WEEKDAY_NAMES: [&str; 7] = [
53    "Monday",
54    "Tuesday",
55    "Wednesday",
56    "Thursday",
57    "Friday",
58    "Saturday",
59    "Sunday",
60];
61
62/// Helper function to obtain 10^x, guaranteeing determinism for x ≤ 9. For these cases, the
63/// function optimizes to a lookup table. For x ≥ 10, it falls back to `10_f64.powi(x)`. The only
64/// situation where this would occur is if the user explicitly requests such precision when
65/// configuring the ISO 8601 well known format. All other possibilities max out at nine digits.
66#[inline]
67fn f64_10_pow_x(x: NonZero<u8>) -> f64 {
68    match x.get() {
69        1 => 10.,
70        2 => 100.,
71        3 => 1_000.,
72        4 => 10_000.,
73        5 => 100_000.,
74        6 => 1_000_000.,
75        7 => 10_000_000.,
76        8 => 100_000_000.,
77        9 => 1_000_000_000.,
78        x => 10_f64.powi(x.cast_signed().widen()),
79    }
80}
81
82/// An `io::Write`r that keeps track of the number of bytes written to it.
83#[derive(Debug)]
84pub(crate) struct Output<W>
85where
86    W: ?Sized,
87{
88    /// The number of bytes written to the output.
89    pub(crate) bytes_written: usize,
90    /// The output that bytes are written to.
91    pub(crate) output: W,
92}
93
94impl<W> Output<W>
95where
96    W: io::Write + ?Sized,
97{
98    /// Write all bytes to the output.
99    #[inline]
100    pub(crate) fn write(&mut self, s: &str) -> io::Result<()> {
101        try_likely_ok!(self.output.write_all(s.as_bytes()));
102        self.bytes_written += s.len();
103        Ok(())
104    }
105
106    /// Write the string to the output.
107    #[inline]
108    pub(crate) fn write_bytes(&mut self, bytes: &[u8]) -> io::Result<()> {
109        try_likely_ok!(self.output.write_all(bytes));
110        self.bytes_written += bytes.len();
111        Ok(())
112    }
113
114    /// Write all strings to the output (in order).
115    #[inline]
116    pub(crate) fn write_many<const N: usize>(&mut self, arr: [&str; N]) -> io::Result<()> {
117        for s in arr {
118            try_likely_ok!(self.write(s));
119        }
120        Ok(())
121    }
122
123    /// Write the string to the output if and only if `pred` is true.
124    pub(crate) fn write_if(&mut self, pred: bool, s: &str) -> io::Result<()> {
125        if pred { self.write(s) } else { Ok(()) }
126    }
127
128    /// If and only if `pred` is true, write `true_str` to the output. Otherwise, write `false_str`.
129    #[inline]
130    pub(crate) fn write_if_else(
131        &mut self,
132        pred: bool,
133        true_str: &str,
134        false_str: &str,
135    ) -> io::Result<()> {
136        self.write(if pred { true_str } else { false_str })
137    }
138
139    /// Write an integer with zeros as trailing padding if necessary to reach the requested width.
140    ///
141    /// This function is intended to be used for formatting the fractional part of a value, as the
142    /// trailing zeros would change the semantic meaning for non-fractional values.
143    #[inline]
144    fn format_int_padded(&mut self, value: u64, width: u8) -> io::Result<()> {
145        let s = num_fmt::u64_pad_none(value);
146        let digit_count = s.len() as u8;
147        for _ in digit_count..width {
148            try_likely_ok!(self.write("0"));
149        }
150        try_likely_ok!(self.write(&s));
151        Ok(())
152    }
153
154    /// Write the floating point number to the output.
155    ///
156    /// This method accepts the number of digits before and after the decimal. The value will be
157    /// padded with zeroes to the left if necessary.
158    #[inline]
159    pub(crate) fn format_float(
160        &mut self,
161        mut value: f64,
162        digits_before_decimal: u8,
163        digits_after_decimal: Option<NonZero<u8>>,
164    ) -> io::Result<()> {
165        match digits_after_decimal {
166            Some(digits_after_decimal) => {
167                // If the precision is less than nine digits after the decimal point, truncate the
168                // value. This avoids rounding up and causing the value to exceed the maximum
169                // permitted value (as in #678). If the precision is at least nine, then we don't
170                // truncate so as to avoid having an off-by-one error (as in #724). The latter is
171                // necessary because floating point values are inherently imprecise with decimal
172                // values, so a minuscule error can be amplified easily.
173                //
174                // Note that this is largely an issue for second values, as for minute and hour
175                // decimals the value is divided by 60 or 3,600, neither of which divide evenly into
176                // 10^x.
177                //
178                // While not a perfect approach, this addresses the bugs that have been reported so
179                // far without being overly complex.
180                if digits_after_decimal.get() < 9 {
181                    let trunc_num = f64_10_pow_x(digits_after_decimal);
182                    value = f64::trunc(value * trunc_num) / trunc_num;
183
184                    let int_part = value.trunc() as u64;
185                    let frac_part =
186                        f64::round(value.fract() * f64_10_pow_x(digits_after_decimal)) as u64;
187
188                    try_likely_ok!(self.format_int_padded(int_part, digits_before_decimal.widen()));
189                    try_likely_ok!(self.write("."));
190                    try_likely_ok!(
191                        self.format_int_padded(frac_part, digits_after_decimal.get().widen())
192                    );
193                } else {
194                    // For precision >= 9, use write! to avoid off-by-one errors from floating point
195                    // rounding (see #724). Integer extraction of the fractional part could overflow
196                    // the digit count when rounding causes a carry.
197                    let digits_after = digits_after_decimal.get().widen::<usize>();
198                    let width = digits_before_decimal.widen::<usize>() + 1 + digits_after;
199                    try_likely_ok!(write!(self.output, "{value:0>width$.digits_after$}"));
200                    self.bytes_written += width;
201                }
202                Ok(())
203            }
204            None => self.format_int_padded(value.trunc() as u64, digits_before_decimal),
205        }
206    }
207
208    /// Format a single digit.
209    #[inline]
210    pub(crate) fn format_single_digit(&mut self, value: ru8<0, 9>) -> io::Result<()> {
211        self.write(num_fmt::single_digit(value))
212    }
213
214    /// Format a two digit number with the specified padding.
215    #[inline]
216    pub(crate) fn format_two_digits(
217        &mut self,
218        value: ru8<0, 99>,
219        padding: modifier::Padding,
220    ) -> io::Result<()> {
221        let s = match padding {
222            modifier::Padding::Space => num_fmt::two_digits_space_padded(value),
223            modifier::Padding::Zero => num_fmt::two_digits_zero_padded(value),
224            modifier::Padding::None => num_fmt::one_to_two_digits_no_padding(value),
225        };
226        self.write(s)
227    }
228
229    /// Format a three digit number with the specified padding.
230    #[inline]
231    pub(crate) fn format_three_digits(
232        &mut self,
233        value: ru16<0, 999>,
234        padding: modifier::Padding,
235    ) -> io::Result<()> {
236        let [first, second_and_third] = match padding {
237            modifier::Padding::Space => num_fmt::three_digits_space_padded(value),
238            modifier::Padding::Zero => num_fmt::three_digits_zero_padded(value),
239            modifier::Padding::None => num_fmt::one_to_three_digits_no_padding(value),
240        };
241        self.write_many([first, second_and_third])
242    }
243
244    /// Format a four digit number with the specified padding.
245    #[inline]
246    pub(crate) fn format_four_digits(
247        &mut self,
248        value: ru16<0, 9_999>,
249        padding: modifier::Padding,
250    ) -> io::Result<()> {
251        let [first_and_second, third_and_fourth] = match padding {
252            modifier::Padding::Space => num_fmt::four_digits_space_padded(value),
253            modifier::Padding::Zero => num_fmt::four_digits_zero_padded(value),
254            modifier::Padding::None => num_fmt::one_to_four_digits_no_padding(value),
255        };
256        self.write_many([first_and_second, third_and_fourth])
257    }
258
259    /// Format a four digit number that is padded with zeroes.
260    #[inline]
261    pub(crate) fn format_four_digits_pad_zero(&mut self, value: ru16<0, 9_999>) -> io::Result<()> {
262        self.write_many(num_fmt::four_digits_zero_padded(value))
263    }
264
265    /// Format a five digit number that is padded with zeroes.
266    #[inline]
267    pub(crate) fn format_five_digits_pad_zero(&mut self, value: ru32<0, 99_999>) -> io::Result<()> {
268        self.write_many(num_fmt::five_digits_zero_padded(value))
269    }
270
271    /// Format a six digit number that is padded with zeroes.
272    #[inline]
273    pub(crate) fn format_six_digits_pad_zero(&mut self, value: ru32<0, 999_999>) -> io::Result<()> {
274        self.write_many(num_fmt::six_digits_zero_padded(value))
275    }
276
277    /// Format a number with no padding.
278    ///
279    /// If the sign is mandatory, the sign must be written by the caller.
280    #[inline]
281    pub(crate) fn format_u64_pad_none(&mut self, value: u64) -> io::Result<()> {
282        self.write(&num_fmt::u64_pad_none(value))
283    }
284
285    /// Format a number with no padding.
286    ///
287    /// If the sign is mandatory, the sign must be written by the caller.
288    #[inline]
289    pub(crate) fn format_u128_pad_none(&mut self, value: u128) -> io::Result<()> {
290        self.write(&num_fmt::u128_pad_none(value))
291    }
292
293    /// Format the day into the designated output.
294    #[inline]
295    fn fmt_day(
296        &mut self,
297        day: Day,
298        modifier::Day { padding }: modifier::Day,
299    ) -> Result<(), io::Error> {
300        self.format_two_digits(day.expand(), padding)
301    }
302
303    /// Format the month into the designated output using the abbreviated name.
304    #[inline]
305    fn fmt_month_short(
306        &mut self,
307        month: Month,
308        modifier::MonthShort {
309            case_sensitive: _, // no effect on formatting
310        }: modifier::MonthShort,
311    ) -> io::Result<()> {
312        // Safety: All month names are at least three bytes long.
313        self.write(unsafe { MONTH_NAMES[u8::from(month).widen::<usize>() - 1].get_unchecked(..3) })
314    }
315
316    /// Format the month into the designated output using the full name.
317    #[inline]
318    fn fmt_month_long(
319        &mut self,
320        month: Month,
321        modifier::MonthLong {
322            case_sensitive: _, // no effect on formatting
323        }: modifier::MonthLong,
324    ) -> io::Result<()> {
325        self.write(MONTH_NAMES[u8::from(month).widen::<usize>() - 1])
326    }
327
328    /// Format the month into the designated output as a number from 1-12.
329    #[inline]
330    fn fmt_month_numerical(
331        &mut self,
332        month: Month,
333        modifier::MonthNumerical { padding }: modifier::MonthNumerical,
334    ) -> io::Result<()> {
335        // Safety: The month is guaranteed to be in the range `1..=12`.
336        self.format_two_digits(unsafe { ru8::new_unchecked(u8::from(month)) }, padding)
337    }
338
339    /// Format the ordinal into the designated output.
340    #[inline]
341    fn fmt_ordinal(
342        &mut self,
343        ordinal: Ordinal,
344        modifier::Ordinal { padding }: modifier::Ordinal,
345    ) -> Result<(), io::Error> {
346        self.format_three_digits(ordinal.expand(), padding)
347    }
348
349    /// Format the weekday into the designated output using the abbreviated name.
350    #[inline]
351    fn fmt_weekday_short(
352        &mut self,
353        weekday: Weekday,
354        modifier::WeekdayShort {
355            case_sensitive: _, // no effect on formatting
356        }: modifier::WeekdayShort,
357    ) -> io::Result<()> {
358        // Safety: All weekday names are at least three bytes long.
359        self.write(unsafe {
360            WEEKDAY_NAMES[weekday.number_days_from_monday().widen::<usize>()].get_unchecked(..3)
361        })
362    }
363
364    /// Format the weekday into the designated output using the full name.
365    #[inline]
366    fn fmt_weekday_long(
367        &mut self,
368        weekday: Weekday,
369        modifier::WeekdayLong {
370            case_sensitive: _, // no effect on formatting
371        }: modifier::WeekdayLong,
372    ) -> io::Result<()> {
373        self.write(WEEKDAY_NAMES[weekday.number_days_from_monday().widen::<usize>()])
374    }
375
376    /// Format the weekday into the designated output as a number from either 0-6 or 1-7 (depending
377    /// on the modifier), where Sunday is either 0 or 1.
378    #[inline]
379    fn fmt_weekday_sunday(
380        &mut self,
381        weekday: Weekday,
382        modifier::WeekdaySunday { one_indexed }: modifier::WeekdaySunday,
383    ) -> io::Result<()> {
384        // Safety: The value is guaranteed to be in the range `0..=7`.
385        self.format_single_digit(unsafe {
386            ru8::new_unchecked(weekday.number_days_from_sunday() + u8::from(one_indexed))
387        })
388    }
389
390    /// Format the weekday into the designated output as a number from either 0-6 or 1-7 (depending
391    /// on the modifier), where Monday is either 0 or 1.
392    #[inline]
393    fn fmt_weekday_monday(
394        &mut self,
395        weekday: Weekday,
396        modifier::WeekdayMonday { one_indexed }: modifier::WeekdayMonday,
397    ) -> io::Result<()> {
398        // Safety: The value is guaranteed to be in the range `0..=7`.
399        self.format_single_digit(unsafe {
400            ru8::new_unchecked(weekday.number_days_from_monday() + u8::from(one_indexed))
401        })
402    }
403
404    #[inline]
405    fn fmt_week_number_iso(
406        &mut self,
407        week_number: IsoWeekNumber,
408        modifier::WeekNumberIso { padding }: modifier::WeekNumberIso,
409    ) -> io::Result<()> {
410        self.format_two_digits(week_number.expand(), padding)
411    }
412
413    #[inline]
414    fn fmt_week_number_sunday(
415        &mut self,
416        week_number: SundayBasedWeek,
417        modifier::WeekNumberSunday { padding }: modifier::WeekNumberSunday,
418    ) -> io::Result<()> {
419        self.format_two_digits(week_number.expand(), padding)
420    }
421
422    #[inline]
423    fn fmt_week_number_monday(
424        &mut self,
425        week_number: MondayBasedWeek,
426        modifier::WeekNumberMonday { padding }: modifier::WeekNumberMonday,
427    ) -> io::Result<()> {
428        self.format_two_digits(week_number.expand(), padding)
429    }
430
431    #[inline]
432    fn fmt_calendar_year_full_extended_range(
433        &mut self,
434        full_year: Year,
435        modifier::CalendarYearFullExtendedRange {
436            padding,
437            sign_is_mandatory,
438        }: modifier::CalendarYearFullExtendedRange,
439    ) -> io::Result<()> {
440        try_likely_ok!(self.fmt_sign(
441            full_year.is_negative(),
442            sign_is_mandatory || full_year.get() >= 10_000
443        ));
444        // Safety: We just called `.abs()`, so zero is the minimum. The maximum is
445        // unchanged.
446        let value: ru32<0, 999_999> =
447            unsafe { full_year.abs().narrow_unchecked::<0, 999_999>().into() };
448
449        if let Some(value) = value.narrow::<0, 9_999>() {
450            try_likely_ok!(self.format_four_digits(value.into(), padding))
451        } else if let Some(value) = value.narrow::<0, 99_999>() {
452            try_likely_ok!(self.format_five_digits_pad_zero(value))
453        } else {
454            try_likely_ok!(self.format_six_digits_pad_zero(value))
455        };
456        Ok(())
457    }
458
459    #[inline]
460    fn fmt_calendar_year_full_standard_range(
461        &mut self,
462        full_year: StandardYear,
463        modifier::CalendarYearFullStandardRange {
464            padding,
465            sign_is_mandatory,
466        }: modifier::CalendarYearFullStandardRange,
467    ) -> io::Result<()> {
468        try_likely_ok!(self.fmt_sign(full_year.is_negative(), sign_is_mandatory));
469        try_likely_ok!(self.format_four_digits(
470            // Safety: The minimum is zero due to the `.abs()` call; the maximum is unchanged.
471            unsafe { full_year.abs().narrow_unchecked::<0, 9_999>().into() },
472            padding
473        ));
474        Ok(())
475    }
476
477    #[inline]
478    fn fmt_iso_year_full_extended_range(
479        &mut self,
480        full_year: Year,
481        modifier::IsoYearFullExtendedRange {
482            padding,
483            sign_is_mandatory,
484        }: modifier::IsoYearFullExtendedRange,
485    ) -> io::Result<()> {
486        try_likely_ok!(self.fmt_sign(
487            full_year.is_negative(),
488            sign_is_mandatory || full_year.get() >= 10_000
489        ));
490        // Safety: The minimum is zero due to the `.abs()` call, with the maximum is unchanged.
491        let value: ru32<0, 999_999> =
492            unsafe { full_year.abs().narrow_unchecked::<0, 999_999>().into() };
493
494        if let Some(value) = value.narrow::<0, 9_999>() {
495            try_likely_ok!(self.format_four_digits(value.into(), padding))
496        } else if let Some(value) = value.narrow::<0, 99_999>() {
497            try_likely_ok!(self.format_five_digits_pad_zero(value))
498        } else {
499            try_likely_ok!(self.format_six_digits_pad_zero(value))
500        };
501        Ok(())
502    }
503
504    #[inline]
505    fn fmt_iso_year_full_standard_range(
506        &mut self,
507        year: StandardYear,
508        modifier::IsoYearFullStandardRange {
509            padding,
510            sign_is_mandatory,
511        }: modifier::IsoYearFullStandardRange,
512    ) -> io::Result<()> {
513        try_likely_ok!(self.fmt_sign(year.is_negative(), sign_is_mandatory));
514        try_likely_ok!(self.format_four_digits(
515            // Safety: The minimum is zero due to the `.abs()` call; the maximum is unchanged.
516            unsafe { year.abs().narrow_unchecked::<0, 9_999>().into() },
517            padding
518        ));
519        Ok(())
520    }
521
522    #[inline]
523    fn fmt_calendar_year_century_extended_range(
524        &mut self,
525        century: ExtendedCentury,
526        is_negative: bool,
527        modifier::CalendarYearCenturyExtendedRange {
528            padding,
529            sign_is_mandatory,
530        }: modifier::CalendarYearCenturyExtendedRange,
531    ) -> io::Result<()> {
532        try_likely_ok!(self.fmt_sign(is_negative, sign_is_mandatory || century.get() >= 100));
533        // Safety: The minimum is zero due to the `.abs()` call;  the maximum is unchanged.
534        let century: ru16<0, 9_999> =
535            unsafe { century.abs().narrow_unchecked::<0, 9_999>().into() };
536
537        if let Some(century) = century.narrow::<0, 99>() {
538            try_likely_ok!(self.format_two_digits(century.into(), padding))
539        } else if let Some(century) = century.narrow::<0, 999>() {
540            try_likely_ok!(self.format_three_digits(century, padding))
541        } else {
542            try_likely_ok!(self.format_four_digits(century, padding))
543        };
544        Ok(())
545    }
546
547    #[inline]
548    fn fmt_calendar_year_century_standard_range(
549        &mut self,
550        century: StandardCentury,
551        is_negative: bool,
552        modifier::CalendarYearCenturyStandardRange {
553            padding,
554            sign_is_mandatory,
555        }: modifier::CalendarYearCenturyStandardRange,
556    ) -> io::Result<()> {
557        try_likely_ok!(self.fmt_sign(is_negative, sign_is_mandatory));
558        // Safety: The minimum is zero due to the `.unsigned_abs()` call.
559        let century = unsafe { century.abs().narrow_unchecked::<0, 99>() };
560        try_likely_ok!(self.format_two_digits(century.into(), padding));
561        Ok(())
562    }
563
564    #[inline]
565    fn fmt_iso_year_century_extended_range(
566        &mut self,
567        century: ExtendedCentury,
568        is_negative: bool,
569        modifier::IsoYearCenturyExtendedRange {
570            padding,
571            sign_is_mandatory,
572        }: modifier::IsoYearCenturyExtendedRange,
573    ) -> io::Result<()> {
574        try_likely_ok!(self.fmt_sign(is_negative, sign_is_mandatory || century.get() >= 100));
575        // Safety: The minimum is zero due to the `.unsigned_abs()` call, with the maximum is
576        // unchanged.
577        let century: ru16<0, 9_999> =
578            unsafe { century.abs().narrow_unchecked::<0, 9_999>().into() };
579
580        if let Some(century) = century.narrow::<0, 99>() {
581            try_likely_ok!(self.format_two_digits(century.into(), padding))
582        } else if let Some(century) = century.narrow::<0, 999>() {
583            try_likely_ok!(self.format_three_digits(century, padding))
584        } else {
585            try_likely_ok!(self.format_four_digits(century, padding))
586        };
587        Ok(())
588    }
589
590    #[inline]
591    fn fmt_iso_year_century_standard_range(
592        &mut self,
593        century: StandardCentury,
594        is_negative: bool,
595        modifier::IsoYearCenturyStandardRange {
596            padding,
597            sign_is_mandatory,
598        }: modifier::IsoYearCenturyStandardRange,
599    ) -> io::Result<()> {
600        try_likely_ok!(self.fmt_sign(is_negative, sign_is_mandatory));
601        // Safety: The minimum is zero due to the `.unsigned_abs()` call.
602        let century = unsafe { century.abs().narrow_unchecked::<0, 99>() };
603        try_likely_ok!(self.format_two_digits(century.into(), padding));
604        Ok(())
605    }
606
607    #[inline]
608    fn fmt_calendar_year_last_two(
609        &mut self,
610        last_two: LastTwo,
611        modifier::CalendarYearLastTwo { padding }: modifier::CalendarYearLastTwo,
612    ) -> io::Result<()> {
613        self.format_two_digits(last_two, padding)
614    }
615
616    #[inline]
617    fn fmt_iso_year_last_two(
618        &mut self,
619        last_two: LastTwo,
620        modifier::IsoYearLastTwo { padding }: modifier::IsoYearLastTwo,
621    ) -> io::Result<()> {
622        self.format_two_digits(last_two, padding)
623    }
624
625    /// Format the hour into the designated output using the 12-hour clock.
626    #[inline]
627    fn fmt_hour_12(
628        &mut self,
629        hour: Hours,
630        modifier::Hour12 { padding }: modifier::Hour12,
631    ) -> io::Result<()> {
632        // Safety: The value is guaranteed to be in the range `1..=12`.
633        self.format_two_digits(
634            unsafe { ru8::new_unchecked((hour.get() + 11) % 12 + 1) },
635            padding,
636        )
637    }
638
639    /// Format the hour into the designated output using the 24-hour clock.
640    #[inline]
641    fn fmt_hour_24(
642        &mut self,
643        hour: Hours,
644        modifier::Hour24 { padding }: modifier::Hour24,
645    ) -> io::Result<()> {
646        self.format_two_digits(hour.expand(), padding)
647    }
648
649    /// Format the minute into the designated output.
650    #[inline]
651    fn fmt_minute(
652        &mut self,
653        minute: Minutes,
654        modifier::Minute { padding }: modifier::Minute,
655    ) -> Result<(), io::Error> {
656        self.format_two_digits(minute.expand(), padding)
657    }
658
659    /// Format the period into the designated output.
660    #[inline]
661    fn fmt_period(
662        &mut self,
663        period: Period,
664        modifier::Period {
665            is_uppercase,
666            case_sensitive: _, // no effect on formatting
667        }: modifier::Period,
668    ) -> Result<(), io::Error> {
669        self.write(match (period, is_uppercase) {
670            (Period::Am, false) => "am",
671            (Period::Am, true) => "AM",
672            (Period::Pm, false) => "pm",
673            (Period::Pm, true) => "PM",
674        })
675    }
676
677    /// Format the second into the designated output.
678    #[inline]
679    fn fmt_second(
680        &mut self,
681        second: Seconds,
682        modifier::Second { padding }: modifier::Second,
683    ) -> Result<(), io::Error> {
684        self.format_two_digits(second.expand(), padding)
685    }
686
687    /// Format the subsecond into the designated output.
688    #[inline]
689    fn fmt_subsecond(
690        &mut self,
691        nanos: Nanoseconds,
692        modifier::Subsecond { digits }: modifier::Subsecond,
693    ) -> Result<(), io::Error> {
694        use modifier::SubsecondDigits::*;
695
696        #[repr(C, align(8))]
697        #[derive(Clone, Copy)]
698        struct Digits {
699            _padding: MaybeUninit<[u8; 7]>,
700            digit_1: u8,
701            digits_2_thru_9: [u8; 8],
702        }
703
704        let [
705            digit_1,
706            digits_2_and_3,
707            digits_4_and_5,
708            digits_6_and_7,
709            digits_8_and_9,
710        ] = num_fmt::subsecond_from_nanos(nanos);
711
712        // Ensure that digits 2 thru 9 are stored as a single array that is 8-aligned. This allows
713        // the conversion to a `u64` to be zero cost, resulting in a nontrivial performance
714        // improvement.
715        let buf = Digits {
716            _padding: MaybeUninit::uninit(),
717            digit_1: digit_1.as_bytes()[0],
718            digits_2_thru_9: [
719                digits_2_and_3.as_bytes()[0],
720                digits_2_and_3.as_bytes()[1],
721                digits_4_and_5.as_bytes()[0],
722                digits_4_and_5.as_bytes()[1],
723                digits_6_and_7.as_bytes()[0],
724                digits_6_and_7.as_bytes()[1],
725                digits_8_and_9.as_bytes()[0],
726                digits_8_and_9.as_bytes()[1],
727            ],
728        };
729
730        let len = match digits {
731            One => 1,
732            Two => 2,
733            Three => 3,
734            Four => 4,
735            Five => 5,
736            Six => 6,
737            Seven => 7,
738            Eight => 8,
739            Nine => 9,
740            OneOrMore => {
741                // By converting the bytes into a single integer, we can effectively perform an
742                // equality check against b'0' for all bytes at once. This is
743                // actually faster than using portable SIMD (even with
744                // `-Ctarget-cpu=native`).
745                let bitmask =
746                    u64::from_le_bytes(buf.digits_2_thru_9) ^ u64::from_le_bytes([b'0'; 8]);
747                let digits_to_truncate = bitmask.leading_zeros() / 8;
748                9 - digits_to_truncate as usize
749            }
750        };
751
752        // Safety: All bytes are initialized and valid UTF-8, and `len` represents the number of
753        // bytes we wish to display (that is between 1 and 9 inclusive). `Digits` is
754        // `#[repr(C)]`, so the layout is guaranteed.
755        let s = unsafe {
756            num_fmt::StackStr::new(
757                *(&raw const buf)
758                    .byte_add(core::mem::offset_of!(Digits, digit_1))
759                    .cast::<[MaybeUninit<u8>; 9]>(),
760                len,
761            )
762        };
763        self.write(&s)
764    }
765
766    #[inline]
767    fn fmt_sign(&mut self, is_negative: bool, sign_is_mandatory: bool) -> io::Result<()> {
768        if is_negative {
769            self.write("-")
770        } else if sign_is_mandatory {
771            self.write("+")
772        } else {
773            Ok(())
774        }
775    }
776
777    /// Format the offset hour into the designated output.
778    #[inline]
779    fn fmt_offset_hour(
780        &mut self,
781        is_negative: bool,
782        hour: OffsetHours,
783        modifier::OffsetHour {
784            padding,
785            sign_is_mandatory,
786        }: modifier::OffsetHour,
787    ) -> Result<(), io::Error> {
788        try_likely_ok!(self.fmt_sign(is_negative, sign_is_mandatory));
789        try_likely_ok!(self.format_two_digits(
790            // Safety: The value is guaranteed to be under 100 because of `OffsetHours`.
791            unsafe { ru8::new_unchecked(hour.get().unsigned_abs()) },
792            padding,
793        ));
794        Ok(())
795    }
796
797    /// Format the offset minute into the designated output.
798    #[inline]
799    fn fmt_offset_minute(
800        &mut self,
801        offset_minute: OffsetMinutes,
802        modifier::OffsetMinute { padding }: modifier::OffsetMinute,
803    ) -> Result<(), io::Error> {
804        self.format_two_digits(
805            // Safety: `OffsetMinutes` is guaranteed to be in the range `-59..=59`, so the absolute
806            // value is guaranteed to be in the range `0..=59`.
807            unsafe { ru8::new_unchecked(offset_minute.get().unsigned_abs()) },
808            padding,
809        )
810    }
811
812    /// Format the offset second into the designated output.
813    #[inline]
814    fn fmt_offset_second(
815        &mut self,
816        offset_second: OffsetSeconds,
817        modifier::OffsetSecond { padding }: modifier::OffsetSecond,
818    ) -> Result<(), io::Error> {
819        self.format_two_digits(
820            // Safety: `OffsetSeconds` is guaranteed to be in the range `-59..=59`, so the absolute
821            // value is guaranteed to be in the range `0..=59`.
822            unsafe { ru8::new_unchecked(offset_second.get().unsigned_abs()) },
823            padding,
824        )
825    }
826
827    /// Format the Unix timestamp (in seconds) into the designated output.
828    #[inline]
829    fn fmt_unix_timestamp_second(
830        &mut self,
831        timestamp: i64,
832        modifier::UnixTimestampSecond { sign_is_mandatory }: modifier::UnixTimestampSecond,
833    ) -> Result<(), io::Error> {
834        try_likely_ok!(self.fmt_sign(timestamp < 0, sign_is_mandatory));
835        try_likely_ok!(self.format_u64_pad_none(timestamp.unsigned_abs()));
836        Ok(())
837    }
838
839    /// Format the Unix timestamp (in milliseconds) into the designated output.
840    #[inline]
841    fn fmt_unix_timestamp_millisecond(
842        &mut self,
843        timestamp_millis: i64,
844        modifier::UnixTimestampMillisecond { sign_is_mandatory }:
845            modifier::UnixTimestampMillisecond,
846    ) -> Result<(), io::Error> {
847        try_likely_ok!(self.fmt_sign(timestamp_millis < 0, sign_is_mandatory));
848        try_likely_ok!(self.format_u64_pad_none(timestamp_millis.unsigned_abs()));
849        Ok(())
850    }
851
852    /// Format the Unix timestamp (in microseconds) into the designated output.
853    #[inline]
854    fn fmt_unix_timestamp_microsecond(
855        &mut self,
856        timestamp_micros: i128,
857        modifier::UnixTimestampMicrosecond { sign_is_mandatory }:
858            modifier::UnixTimestampMicrosecond,
859    ) -> Result<(), io::Error> {
860        try_likely_ok!(self.fmt_sign(timestamp_micros < 0, sign_is_mandatory));
861        try_likely_ok!(self.format_u128_pad_none(timestamp_micros.unsigned_abs()));
862        Ok(())
863    }
864
865    /// Format the Unix timestamp (in nanoseconds) into the designated output.
866    #[inline]
867    fn fmt_unix_timestamp_nanosecond(
868        &mut self,
869        timestamp_nanos: i128,
870        modifier::UnixTimestampNanosecond { sign_is_mandatory }: modifier::UnixTimestampNanosecond,
871    ) -> Result<(), io::Error> {
872        try_likely_ok!(self.fmt_sign(timestamp_nanos < 0, sign_is_mandatory));
873        try_likely_ok!(self.format_u128_pad_none(timestamp_nanos.unsigned_abs()));
874        Ok(())
875    }
876}