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::{Component, 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>;
36type AnyWeekNumber = ru8<0, 53>;
37
38const MONTH_NAMES: [&str; 12] = [
39    "January",
40    "February",
41    "March",
42    "April",
43    "May",
44    "June",
45    "July",
46    "August",
47    "September",
48    "October",
49    "November",
50    "December",
51];
52
53const WEEKDAY_NAMES: [&str; 7] = [
54    "Monday",
55    "Tuesday",
56    "Wednesday",
57    "Thursday",
58    "Friday",
59    "Saturday",
60    "Sunday",
61];
62
63/// Write all bytes to the output, returning the number of bytes written.
64#[inline]
65pub(crate) fn write_bytes(
66    output: &mut (impl io::Write + ?Sized),
67    bytes: &[u8],
68) -> io::Result<usize> {
69    try_likely_ok!(output.write_all(bytes));
70    Ok(bytes.len())
71}
72
73/// Write the string to the output, returning the number of bytes written.
74#[inline]
75pub(crate) fn write(output: &mut (impl io::Write + ?Sized), s: &str) -> io::Result<usize> {
76    try_likely_ok!(output.write_all(s.as_bytes()));
77    Ok(s.len())
78}
79
80/// Write all strings to the output (in order), returning the total number of bytes written.
81#[inline]
82pub(crate) fn write_many<const N: usize>(
83    output: &mut (impl io::Write + ?Sized),
84    arr: [&str; N],
85) -> io::Result<usize> {
86    let mut bytes = 0;
87    for s in arr {
88        try_likely_ok!(output.write_all(s.as_bytes()));
89        bytes += s.len();
90    }
91    Ok(bytes)
92}
93
94/// If `pred` is true, write the string to the output, returning the number of bytes written.
95#[inline]
96pub(crate) fn write_if(
97    output: &mut (impl io::Write + ?Sized),
98    pred: bool,
99    s: &str,
100) -> io::Result<usize> {
101    if pred { write(output, s) } else { Ok(0) }
102}
103
104/// If `pred` is true, write `true_str` to the output. Otherwise, write `false_str`.
105#[inline]
106pub(crate) fn write_if_else(
107    output: &mut (impl io::Write + ?Sized),
108    pred: bool,
109    true_str: &str,
110    false_str: &str,
111) -> io::Result<usize> {
112    write(output, if pred { true_str } else { false_str })
113}
114
115/// Helper function to obtain 10^x, guaranteeing determinism for x ≤ 9. For these cases, the
116/// function optimizes to a lookup table. For x ≥ 10, it falls back to `10_f64.powi(x)`. The only
117/// situation where this would occur is if the user explicitly requests such precision when
118/// configuring the ISO 8601 well known format. All other possibilities max out at nine digits.
119#[inline]
120fn f64_10_pow_x(x: NonZero<u8>) -> f64 {
121    match x.get() {
122        1 => 10.,
123        2 => 100.,
124        3 => 1_000.,
125        4 => 10_000.,
126        5 => 100_000.,
127        6 => 1_000_000.,
128        7 => 10_000_000.,
129        8 => 100_000_000.,
130        9 => 1_000_000_000.,
131        x => 10_f64.powi(x.cast_signed().extend()),
132    }
133}
134
135/// Write the floating point number to the output, returning the number of bytes written.
136///
137/// This method accepts the number of digits before and after the decimal. The value will be padded
138/// with zeroes to the left if necessary.
139#[inline]
140pub(crate) fn format_float(
141    output: &mut (impl io::Write + ?Sized),
142    mut value: f64,
143    digits_before_decimal: u8,
144    digits_after_decimal: Option<NonZero<u8>>,
145) -> io::Result<usize> {
146    match digits_after_decimal {
147        Some(digits_after_decimal) => {
148            // If the precision is less than nine digits after the decimal point, truncate the
149            // value. This avoids rounding up and causing the value to exceed the maximum permitted
150            // value (as in #678). If the precision is at least nine, then we don't truncate so as
151            // to avoid having an off-by-one error (as in #724). The latter is necessary
152            // because floating point values are inherently imprecise with decimal
153            // values, so a minuscule error can be amplified easily.
154            //
155            // Note that this is largely an issue for second values, as for minute and hour decimals
156            // the value is divided by 60 or 3,600, neither of which divide evenly into 10^x.
157            //
158            // While not a perfect approach, this addresses the bugs that have been reported so far
159            // without being overly complex.
160            if digits_after_decimal.get() < 9 {
161                let trunc_num = f64_10_pow_x(digits_after_decimal);
162                value = f64::trunc(value * trunc_num) / trunc_num;
163            }
164
165            let digits_after_decimal = digits_after_decimal.get().extend();
166            let width = digits_before_decimal.extend::<usize>() + 1 + digits_after_decimal;
167            try_likely_ok!(write!(output, "{value:0>width$.digits_after_decimal$}"));
168            Ok(width)
169        }
170        None => {
171            let value = value.trunc() as u64;
172            let width = digits_before_decimal.extend();
173            try_likely_ok!(write!(output, "{value:0>width$}"));
174            Ok(width)
175        }
176    }
177}
178
179/// Format a single digit.
180#[inline]
181pub(crate) fn format_single_digit(
182    output: &mut (impl io::Write + ?Sized),
183    value: ru8<0, 9>,
184) -> io::Result<usize> {
185    write(output, num_fmt::single_digit(value))
186}
187
188/// Format a two digit number with the specified padding.
189#[inline]
190pub(crate) fn format_two_digits(
191    output: &mut (impl io::Write + ?Sized),
192    value: ru8<0, 99>,
193    padding: modifier::Padding,
194) -> io::Result<usize> {
195    let s = match padding {
196        modifier::Padding::Space => num_fmt::two_digits_space_padded(value),
197        modifier::Padding::Zero => num_fmt::two_digits_zero_padded(value),
198        modifier::Padding::None => num_fmt::one_to_two_digits_no_padding(value),
199    };
200    write(output, s)
201}
202
203/// Format a three digit number with the specified padding.
204#[inline]
205pub(crate) fn format_three_digits(
206    output: &mut (impl io::Write + ?Sized),
207    value: ru16<0, 999>,
208    padding: modifier::Padding,
209) -> io::Result<usize> {
210    let [first, second_and_third] = match padding {
211        modifier::Padding::Space => num_fmt::three_digits_space_padded(value),
212        modifier::Padding::Zero => num_fmt::three_digits_zero_padded(value),
213        modifier::Padding::None => num_fmt::one_to_three_digits_no_padding(value),
214    };
215    write_many(output, [first, second_and_third])
216}
217
218/// Format a four digit number with the specified padding.
219#[inline]
220pub(crate) fn format_four_digits(
221    output: &mut (impl io::Write + ?Sized),
222    value: ru16<0, 9_999>,
223    padding: modifier::Padding,
224) -> io::Result<usize> {
225    let [first_and_second, third_and_fourth] = match padding {
226        modifier::Padding::Space => num_fmt::four_digits_space_padded(value),
227        modifier::Padding::Zero => num_fmt::four_digits_zero_padded(value),
228        modifier::Padding::None => num_fmt::one_to_four_digits_no_padding(value),
229    };
230    write_many(output, [first_and_second, third_and_fourth])
231}
232
233/// Format a four digit number that is padded with zeroes.
234#[inline]
235pub(crate) fn format_four_digits_pad_zero(
236    output: &mut (impl io::Write + ?Sized),
237    value: ru16<0, 9_999>,
238) -> io::Result<usize> {
239    write_many(output, num_fmt::four_digits_zero_padded(value))
240}
241
242/// Format a five digit number that is padded with zeroes.
243#[inline]
244pub(crate) fn format_five_digits_pad_zero(
245    output: &mut (impl io::Write + ?Sized),
246    value: ru32<0, 99_999>,
247) -> io::Result<usize> {
248    write_many(output, num_fmt::five_digits_zero_padded(value))
249}
250
251/// Format a six digit number that is padded with zeroes.
252#[inline]
253pub(crate) fn format_six_digits_pad_zero(
254    output: &mut (impl io::Write + ?Sized),
255    value: ru32<0, 999_999>,
256) -> io::Result<usize> {
257    write_many(output, num_fmt::six_digits_zero_padded(value))
258}
259
260/// Format a number with no padding.
261///
262/// If the sign is mandatory, the sign must be written by the caller.
263#[inline]
264pub(crate) fn format_number_pad_none(
265    output: &mut (impl io::Write + ?Sized),
266    value: impl itoa::Integer + Copy,
267) -> Result<usize, io::Error> {
268    write(output, itoa::Buffer::new().format(value))
269}
270
271/// Format the provided component into the designated output. An `Err` will be returned if the
272/// component requires information that it does not provide or if the value cannot be output to the
273/// stream.
274#[inline]
275pub(crate) fn format_component<V>(
276    output: &mut (impl io::Write + ?Sized),
277    component: Component,
278    value: &V,
279    state: &mut V::State,
280) -> Result<usize, error::Format>
281where
282    V: ComponentProvider,
283{
284    use Component::*;
285    match component {
286        Day(modifier) if V::SUPPLIES_DATE => fmt_day(output, value.day(state), modifier),
287        MonthShort(modifier) if V::SUPPLIES_DATE => {
288            fmt_month_short(output, value.month(state), modifier)
289        }
290        MonthLong(modifier) if V::SUPPLIES_DATE => {
291            fmt_month_long(output, value.month(state), modifier)
292        }
293        MonthNumerical(modifier) if V::SUPPLIES_DATE => {
294            fmt_month_numerical(output, value.month(state), modifier)
295        }
296        Ordinal(modifier) if V::SUPPLIES_DATE => {
297            fmt_ordinal(output, value.ordinal(state), modifier)
298        }
299        WeekdayShort(modifier) if V::SUPPLIES_DATE => {
300            fmt_weekday_short(output, value.weekday(state), modifier)
301        }
302        WeekdayLong(modifier) if V::SUPPLIES_DATE => {
303            fmt_weekday_long(output, value.weekday(state), modifier)
304        }
305        WeekdaySunday(modifier) if V::SUPPLIES_DATE => {
306            fmt_weekday_sunday(output, value.weekday(state), modifier)
307        }
308        WeekdayMonday(modifier) if V::SUPPLIES_DATE => {
309            fmt_weekday_monday(output, value.weekday(state), modifier)
310        }
311        WeekNumberIso(modifier) if V::SUPPLIES_DATE => {
312            fmt_week_number_iso(output, value.iso_week_number(state), modifier)
313        }
314        WeekNumberSunday(modifier) if V::SUPPLIES_DATE => {
315            fmt_week_number_sunday(output, value.sunday_based_week(state), modifier)
316        }
317        WeekNumberMonday(modifier) if V::SUPPLIES_DATE => {
318            fmt_week_number_monday(output, value.monday_based_week(state), modifier)
319        }
320        CalendarYearFullExtendedRange(modifier) if V::SUPPLIES_DATE => {
321            fmt_calendar_year_full_extended_range(output, value.calendar_year(state), modifier)
322        }
323        CalendarYearFullStandardRange(modifier) if V::SUPPLIES_DATE => {
324            fmt_calendar_year_full_standard_range(
325                output,
326                try_likely_ok!(
327                    value
328                        .calendar_year(state)
329                        .narrow::<-9_999, 9_999>()
330                        .ok_or_else(|| error::ComponentRange::conditional("year"))
331                )
332                .into(),
333                modifier,
334            )
335        }
336        IsoYearFullExtendedRange(modifier) if V::SUPPLIES_DATE => {
337            fmt_iso_year_full_extended_range(output, value.iso_year(state), modifier)
338        }
339        IsoYearFullStandardRange(modifier) if V::SUPPLIES_DATE => fmt_iso_year_full_standard_range(
340            output,
341            try_likely_ok!(
342                value
343                    .iso_year(state)
344                    .narrow::<-9_999, 9_999>()
345                    .ok_or_else(|| error::ComponentRange::conditional("year"))
346            )
347            .into(),
348            modifier,
349        ),
350        CalendarYearCenturyExtendedRange(modifier) if V::SUPPLIES_DATE => {
351            let year = value.calendar_year(state);
352            // Safety: Given the range of `year`, the range of the century is `-9_999..=9_999`.
353            let century = unsafe { ri16::new_unchecked((year.get() / 100).truncate()) };
354            fmt_calendar_year_century_extended_range(output, century, year.is_negative(), modifier)
355        }
356        CalendarYearCenturyStandardRange(modifier) if V::SUPPLIES_DATE => {
357            let year = value.calendar_year(state);
358            let is_negative = year.is_negative();
359            // Safety: Given the range of `year`, the range of the century is `-9_999..=9_999`.
360            let year = unsafe { ri16::<0, 9_999>::new_unchecked((year.get() / 100).truncate()) };
361            fmt_calendar_year_century_standard_range(
362                output,
363                year.narrow::<0, 99>()
364                    .ok_or_else(|| error::ComponentRange::conditional("year"))?
365                    .into(),
366                is_negative,
367                modifier,
368            )
369        }
370        IsoYearCenturyExtendedRange(modifier) if V::SUPPLIES_DATE => {
371            let year = value.iso_year(state);
372            // Safety: Given the range of `year`, the range of the century is `-9_999..=9_999`.
373            let century = unsafe { ri16::new_unchecked((year.get() / 100).truncate()) };
374            fmt_iso_year_century_extended_range(output, century, year.is_negative(), modifier)
375        }
376        IsoYearCenturyStandardRange(modifier) if V::SUPPLIES_DATE => {
377            let year = value.iso_year(state);
378            // Safety: Given the range of `year`, the range of the century is `-9_999..=9_999`.
379            let year = unsafe { ri16::<0, 9_999>::new_unchecked((year.get() / 100).truncate()) };
380            fmt_iso_year_century_standard_range(
381                output,
382                year.narrow::<0, 99>()
383                    .ok_or_else(|| error::ComponentRange::conditional("year"))?
384                    .into(),
385                year.is_negative(),
386                modifier,
387            )
388        }
389        CalendarYearLastTwo(modifier) if V::SUPPLIES_DATE => {
390            // Safety: Modulus of 100 followed by `.unsigned_abs()` guarantees that the value is in
391            // the range `0..=99`.
392            let last_two = unsafe {
393                ru8::new_unchecked(
394                    (value.calendar_year(state).get().unsigned_abs() % 100).truncate(),
395                )
396            };
397            fmt_calendar_year_last_two(output, last_two, modifier)
398        }
399        IsoYearLastTwo(modifier) if V::SUPPLIES_DATE => {
400            // Safety: Modulus of 100 followed by `.unsigned_abs()` guarantees that the value is in
401            // the range `0..=99`.
402            let last_two = unsafe {
403                ru8::new_unchecked((value.iso_year(state).get().unsigned_abs() % 100).truncate())
404            };
405            fmt_iso_year_last_two(output, last_two, modifier)
406        }
407        Hour12(modifier) if V::SUPPLIES_TIME => fmt_hour_12(output, value.hour(state), modifier),
408        Hour24(modifier) if V::SUPPLIES_TIME => fmt_hour_24(output, value.hour(state), modifier),
409        Minute(modifier) if V::SUPPLIES_TIME => fmt_minute(output, value.minute(state), modifier),
410        Period(modifier) if V::SUPPLIES_TIME => fmt_period(output, value.period(state), modifier),
411        Second(modifier) if V::SUPPLIES_TIME => fmt_second(output, value.second(state), modifier),
412        Subsecond(modifier) if V::SUPPLIES_TIME => {
413            fmt_subsecond(output, value.nanosecond(state), modifier)
414        }
415        OffsetHour(modifier) if V::SUPPLIES_OFFSET => fmt_offset_hour(
416            output,
417            value.offset_is_negative(state),
418            value.offset_hour(state),
419            modifier,
420        ),
421        OffsetMinute(modifier) if V::SUPPLIES_OFFSET => {
422            fmt_offset_minute(output, value.offset_minute(state), modifier)
423        }
424        OffsetSecond(modifier) if V::SUPPLIES_OFFSET => {
425            fmt_offset_second(output, value.offset_second(state), modifier)
426        }
427        Ignore(_) => return Ok(0),
428        UnixTimestampSecond(modifier) if V::SUPPLIES_TIMESTAMP => {
429            fmt_unix_timestamp_second(output, value.unix_timestamp_seconds(state), modifier)
430        }
431        UnixTimestampMillisecond(modifier) if V::SUPPLIES_TIMESTAMP => {
432            fmt_unix_timestamp_millisecond(
433                output,
434                value.unix_timestamp_milliseconds(state),
435                modifier,
436            )
437        }
438        UnixTimestampMicrosecond(modifier) if V::SUPPLIES_TIMESTAMP => {
439            fmt_unix_timestamp_microsecond(
440                output,
441                value.unix_timestamp_microseconds(state),
442                modifier,
443            )
444        }
445        UnixTimestampNanosecond(modifier) if V::SUPPLIES_TIMESTAMP => {
446            fmt_unix_timestamp_nanosecond(output, value.unix_timestamp_nanoseconds(state), modifier)
447        }
448        End(modifier::End { trailing_input: _ }) => return Ok(0),
449
450        // Deprecated components that are no long emitted by macros or parsers.
451        #[expect(deprecated)]
452        Month(modifier::Month {
453            padding,
454            repr,
455            case_sensitive,
456        }) if V::SUPPLIES_DATE => {
457            let month = value.month(state);
458            match repr {
459                modifier::MonthRepr::Numerical => {
460                    fmt_month_numerical(output, month, modifier::MonthNumerical { padding })
461                }
462                modifier::MonthRepr::Long => {
463                    fmt_month_long(output, month, modifier::MonthLong { case_sensitive })
464                }
465                modifier::MonthRepr::Short => {
466                    fmt_month_short(output, month, modifier::MonthShort { case_sensitive })
467                }
468            }
469        }
470        #[expect(deprecated)]
471        Weekday(modifier::Weekday {
472            repr,
473            one_indexed,
474            case_sensitive,
475        }) if V::SUPPLIES_DATE => {
476            let weekday = value.weekday(state);
477            match repr {
478                modifier::WeekdayRepr::Short => {
479                    fmt_weekday_short(output, weekday, modifier::WeekdayShort { case_sensitive })
480                }
481                modifier::WeekdayRepr::Long => {
482                    fmt_weekday_long(output, weekday, modifier::WeekdayLong { case_sensitive })
483                }
484                modifier::WeekdayRepr::Sunday => {
485                    fmt_weekday_sunday(output, weekday, modifier::WeekdaySunday { one_indexed })
486                }
487                modifier::WeekdayRepr::Monday => {
488                    fmt_weekday_monday(output, weekday, modifier::WeekdayMonday { one_indexed })
489                }
490            }
491        }
492        #[expect(deprecated)]
493        Hour(modifier::Hour {
494            padding,
495            is_12_hour_clock,
496        }) if V::SUPPLIES_TIME => {
497            let hour = value.hour(state);
498            if is_12_hour_clock {
499                fmt_hour_12(output, hour, modifier::Hour12 { padding })
500            } else {
501                fmt_hour_24(output, hour, modifier::Hour24 { padding })
502            }
503        }
504        #[expect(deprecated)]
505        UnixTimestamp(modifier) if V::SUPPLIES_TIMESTAMP => match modifier.precision {
506            modifier::UnixTimestampPrecision::Second => fmt_unix_timestamp_second(
507                output,
508                value.unix_timestamp_seconds(state),
509                modifier::UnixTimestampSecond {
510                    sign_is_mandatory: modifier.sign_is_mandatory,
511                },
512            ),
513            modifier::UnixTimestampPrecision::Millisecond => fmt_unix_timestamp_millisecond(
514                output,
515                value.unix_timestamp_milliseconds(state),
516                modifier::UnixTimestampMillisecond {
517                    sign_is_mandatory: modifier.sign_is_mandatory,
518                },
519            ),
520            modifier::UnixTimestampPrecision::Microsecond => fmt_unix_timestamp_microsecond(
521                output,
522                value.unix_timestamp_microseconds(state),
523                modifier::UnixTimestampMicrosecond {
524                    sign_is_mandatory: modifier.sign_is_mandatory,
525                },
526            ),
527            modifier::UnixTimestampPrecision::Nanosecond => fmt_unix_timestamp_nanosecond(
528                output,
529                value.unix_timestamp_nanoseconds(state),
530                modifier::UnixTimestampNanosecond {
531                    sign_is_mandatory: modifier.sign_is_mandatory,
532                },
533            ),
534        },
535        #[expect(deprecated)]
536        WeekNumber(modifier) if V::SUPPLIES_DATE => fmt_week_number(
537            output,
538            match modifier.repr {
539                modifier::WeekNumberRepr::Iso => value.iso_week_number(state).expand(),
540                modifier::WeekNumberRepr::Sunday => value.sunday_based_week(state).expand(),
541                modifier::WeekNumberRepr::Monday => value.monday_based_week(state).expand(),
542            },
543            modifier,
544        ),
545        #[expect(deprecated)]
546        Year(
547            modifier @ modifier::Year {
548                repr: modifier::YearRepr::Full,
549                iso_week_based: false,
550                ..
551            },
552        ) if V::SUPPLIES_DATE => {
553            return fmt_full_year(output, value.calendar_year(state), modifier);
554        }
555        #[expect(deprecated)]
556        Year(
557            modifier @ modifier::Year {
558                repr: modifier::YearRepr::Century,
559                iso_week_based: false,
560                ..
561            },
562        ) if V::SUPPLIES_DATE => return fmt_century(output, value.calendar_year(state), modifier),
563        #[expect(deprecated)]
564        Year(
565            modifier @ modifier::Year {
566                repr: modifier::YearRepr::LastTwo,
567                iso_week_based: false,
568                ..
569            },
570        ) if V::SUPPLIES_DATE => fmt_year_last_two(output, value.calendar_year(state), modifier),
571        #[expect(deprecated)]
572        Year(
573            modifier @ modifier::Year {
574                repr: modifier::YearRepr::Full,
575                iso_week_based: true,
576                ..
577            },
578        ) if V::SUPPLIES_DATE => {
579            return fmt_full_year(output, value.iso_year(state), modifier);
580        }
581        #[expect(deprecated)]
582        Year(
583            modifier @ modifier::Year {
584                repr: modifier::YearRepr::Century,
585                iso_week_based: true,
586                ..
587            },
588        ) if V::SUPPLIES_DATE => return fmt_century(output, value.iso_year(state), modifier),
589        #[expect(deprecated)]
590        Year(
591            modifier @ modifier::Year {
592                repr: modifier::YearRepr::LastTwo,
593                iso_week_based: true,
594                ..
595            },
596        ) if V::SUPPLIES_DATE => fmt_year_last_two(output, value.iso_year(state), modifier),
597
598        // This is functionally the same as a wildcard arm, but it will cause an error if a new
599        // component is added. This is to avoid a bug where a new component, the code compiles, and
600        // formatting fails.
601        // Allow unreachable patterns because some branches may be fully matched above.
602        #[allow(unreachable_patterns)]
603        #[expect(deprecated)]
604        Day(_)
605        | MonthShort(_)
606        | MonthLong(_)
607        | MonthNumerical(_)
608        | Ordinal(_)
609        | WeekdayShort(_)
610        | WeekdayLong(_)
611        | WeekdaySunday(_)
612        | WeekdayMonday(_)
613        | WeekNumberIso(_)
614        | WeekNumberSunday(_)
615        | WeekNumberMonday(_)
616        | CalendarYearFullExtendedRange(_)
617        | CalendarYearFullStandardRange(_)
618        | IsoYearFullExtendedRange(_)
619        | IsoYearFullStandardRange(_)
620        | CalendarYearCenturyExtendedRange(_)
621        | CalendarYearCenturyStandardRange(_)
622        | IsoYearCenturyExtendedRange(_)
623        | IsoYearCenturyStandardRange(_)
624        | CalendarYearLastTwo(_)
625        | IsoYearLastTwo(_)
626        | Hour12(_)
627        | Hour24(_)
628        | Minute(_)
629        | Period(_)
630        | Second(_)
631        | Subsecond(_)
632        | OffsetHour(_)
633        | OffsetMinute(_)
634        | OffsetSecond(_)
635        | Ignore(_)
636        | UnixTimestampSecond(_)
637        | UnixTimestampMillisecond(_)
638        | UnixTimestampMicrosecond(_)
639        | UnixTimestampNanosecond(_)
640        | End(_)
641        | Month(_)
642        | Weekday(_)
643        | Hour(_)
644        | UnixTimestamp(_)
645        | WeekNumber(_)
646        | Year(_) => return Err(error::Format::InsufficientTypeInformation),
647    }
648    .map_err(Into::into)
649}
650
651/// Format the day into the designated output.
652#[inline]
653fn fmt_day(
654    output: &mut (impl io::Write + ?Sized),
655    day: Day,
656    modifier::Day { padding }: modifier::Day,
657) -> Result<usize, io::Error> {
658    format_two_digits(output, day.expand(), padding)
659}
660
661/// Format the month into the designated output using the abbreviated name.
662#[inline]
663fn fmt_month_short(
664    output: &mut (impl io::Write + ?Sized),
665    month: Month,
666    modifier::MonthShort {
667        case_sensitive: _, // no effect on formatting
668    }: modifier::MonthShort,
669) -> io::Result<usize> {
670    // Safety: All month names are at least three bytes long.
671    write(output, unsafe {
672        MONTH_NAMES[u8::from(month).extend::<usize>() - 1].get_unchecked(..3)
673    })
674}
675
676/// Format the month into the designated output using the full name.
677#[inline]
678fn fmt_month_long(
679    output: &mut (impl io::Write + ?Sized),
680    month: Month,
681    modifier::MonthLong {
682        case_sensitive: _, // no effect on formatting
683    }: modifier::MonthLong,
684) -> io::Result<usize> {
685    write(output, MONTH_NAMES[u8::from(month).extend::<usize>() - 1])
686}
687
688/// Format the month into the designated output as a number from 1-12.
689#[inline]
690fn fmt_month_numerical(
691    output: &mut (impl io::Write + ?Sized),
692    month: Month,
693    modifier::MonthNumerical { padding }: modifier::MonthNumerical,
694) -> io::Result<usize> {
695    format_two_digits(
696        output,
697        // Safety: The month is guaranteed to be in the range `1..=12`.
698        unsafe { ru8::new_unchecked(u8::from(month)) },
699        padding,
700    )
701}
702
703/// Format the ordinal into the designated output.
704#[inline]
705fn fmt_ordinal(
706    output: &mut (impl io::Write + ?Sized),
707    ordinal: Ordinal,
708    modifier::Ordinal { padding }: modifier::Ordinal,
709) -> Result<usize, io::Error> {
710    format_three_digits(output, ordinal.expand(), padding)
711}
712
713/// Format the weekday into the designated output using the abbreviated name.
714#[inline]
715fn fmt_weekday_short(
716    output: &mut (impl io::Write + ?Sized),
717    weekday: Weekday,
718    modifier::WeekdayShort {
719        case_sensitive: _, // no effect on formatting
720    }: modifier::WeekdayShort,
721) -> io::Result<usize> {
722    // Safety: All weekday names are at least three bytes long.
723    write(output, unsafe {
724        WEEKDAY_NAMES[weekday.number_days_from_monday().extend::<usize>()].get_unchecked(..3)
725    })
726}
727
728/// Format the weekday into the designated output using the full name.
729#[inline]
730fn fmt_weekday_long(
731    output: &mut (impl io::Write + ?Sized),
732    weekday: Weekday,
733    modifier::WeekdayLong {
734        case_sensitive: _, // no effect on formatting
735    }: modifier::WeekdayLong,
736) -> io::Result<usize> {
737    write(
738        output,
739        WEEKDAY_NAMES[weekday.number_days_from_monday().extend::<usize>()],
740    )
741}
742
743/// Format the weekday into the designated output as a number from either 0-6 or 1-7 (depending on
744/// the modifier), where Sunday is either 0 or 1.
745#[inline]
746fn fmt_weekday_sunday(
747    output: &mut (impl io::Write + ?Sized),
748    weekday: Weekday,
749    modifier::WeekdaySunday { one_indexed }: modifier::WeekdaySunday,
750) -> io::Result<usize> {
751    // Safety: The value is guaranteed to be in the range `0..=7`.
752    format_single_digit(output, unsafe {
753        ru8::new_unchecked(weekday.number_days_from_sunday() + u8::from(one_indexed))
754    })
755}
756
757/// Format the weekday into the designated output as a number from either 0-6 or 1-7 (depending on
758/// the modifier), where Monday is either 0 or 1.
759#[inline]
760fn fmt_weekday_monday(
761    output: &mut (impl io::Write + ?Sized),
762    weekday: Weekday,
763    modifier::WeekdayMonday { one_indexed }: modifier::WeekdayMonday,
764) -> io::Result<usize> {
765    // Safety: The value is guaranteed to be in the range `0..=7`.
766    format_single_digit(output, unsafe {
767        ru8::new_unchecked(weekday.number_days_from_monday() + u8::from(one_indexed))
768    })
769}
770
771#[inline]
772fn fmt_week_number_iso(
773    output: &mut (impl io::Write + ?Sized),
774    week_number: IsoWeekNumber,
775    modifier::WeekNumberIso { padding }: modifier::WeekNumberIso,
776) -> io::Result<usize> {
777    format_two_digits(output, week_number.expand(), padding)
778}
779
780#[inline]
781fn fmt_week_number_sunday(
782    output: &mut (impl io::Write + ?Sized),
783    week_number: SundayBasedWeek,
784    modifier::WeekNumberSunday { padding }: modifier::WeekNumberSunday,
785) -> io::Result<usize> {
786    format_two_digits(output, week_number.expand(), padding)
787}
788
789#[inline]
790fn fmt_week_number_monday(
791    output: &mut (impl io::Write + ?Sized),
792    week_number: MondayBasedWeek,
793    modifier::WeekNumberMonday { padding }: modifier::WeekNumberMonday,
794) -> io::Result<usize> {
795    format_two_digits(output, week_number.expand(), padding)
796}
797
798/// Format the week number into the designated output.
799#[inline]
800#[expect(deprecated)]
801#[deprecated(since = "0.3.48", note = "use `fmt_week_number_*` methods instead")]
802fn fmt_week_number(
803    output: &mut (impl io::Write + ?Sized),
804    week_number: AnyWeekNumber,
805    modifier::WeekNumber { padding, repr: _ }: modifier::WeekNumber,
806) -> Result<usize, io::Error> {
807    format_two_digits(output, week_number.expand(), padding)
808}
809
810#[inline]
811fn fmt_calendar_year_full_extended_range(
812    output: &mut (impl io::Write + ?Sized),
813    full_year: Year,
814    modifier::CalendarYearFullExtendedRange {
815        padding,
816        sign_is_mandatory,
817    }: modifier::CalendarYearFullExtendedRange,
818) -> io::Result<usize> {
819    let mut bytes = 0;
820    bytes += try_likely_ok!(fmt_sign(
821        output,
822        full_year.is_negative(),
823        sign_is_mandatory || full_year.get() >= 10_000,
824    ));
825    // Safety: We just called `.abs()`, so zero is the minimum. The maximum is
826    // unchanged.
827    let value: ru32<0, 999_999> =
828        unsafe { full_year.abs().narrow_unchecked::<0, 999_999>().into() };
829
830    bytes += if let Some(value) = value.narrow::<0, 9_999>() {
831        try_likely_ok!(format_four_digits(output, value.into(), padding))
832    } else if let Some(value) = value.narrow::<0, 99_999>() {
833        try_likely_ok!(format_five_digits_pad_zero(output, value))
834    } else {
835        try_likely_ok!(format_six_digits_pad_zero(output, value))
836    };
837    Ok(bytes)
838}
839
840#[inline]
841fn fmt_calendar_year_full_standard_range(
842    output: &mut (impl io::Write + ?Sized),
843    full_year: StandardYear,
844    modifier::CalendarYearFullStandardRange {
845        padding,
846        sign_is_mandatory,
847    }: modifier::CalendarYearFullStandardRange,
848) -> io::Result<usize> {
849    let mut bytes = 0;
850    bytes += try_likely_ok!(fmt_sign(output, full_year.is_negative(), sign_is_mandatory));
851    // Safety: The minimum is zero due to the `.abs()` call; the maximum is unchanged.
852    bytes += try_likely_ok!(format_four_digits(
853        output,
854        unsafe { full_year.abs().narrow_unchecked::<0, 9_999>().into() },
855        padding
856    ));
857    Ok(bytes)
858}
859
860#[inline]
861fn fmt_iso_year_full_extended_range(
862    output: &mut (impl io::Write + ?Sized),
863    full_year: Year,
864    modifier::IsoYearFullExtendedRange {
865        padding,
866        sign_is_mandatory,
867    }: modifier::IsoYearFullExtendedRange,
868) -> io::Result<usize> {
869    let mut bytes = 0;
870    bytes += try_likely_ok!(fmt_sign(
871        output,
872        full_year.is_negative(),
873        sign_is_mandatory || full_year.get() >= 10_000,
874    ));
875    // Safety: The minimum is zero due to the `.abs()` call, with the maximum is unchanged.
876    let value: ru32<0, 999_999> =
877        unsafe { full_year.abs().narrow_unchecked::<0, 999_999>().into() };
878
879    bytes += if let Some(value) = value.narrow::<0, 9_999>() {
880        try_likely_ok!(format_four_digits(output, value.into(), padding))
881    } else if let Some(value) = value.narrow::<0, 99_999>() {
882        try_likely_ok!(format_five_digits_pad_zero(output, value))
883    } else {
884        try_likely_ok!(format_six_digits_pad_zero(output, value))
885    };
886    Ok(bytes)
887}
888
889#[inline]
890fn fmt_iso_year_full_standard_range(
891    output: &mut (impl io::Write + ?Sized),
892    year: StandardYear,
893    modifier::IsoYearFullStandardRange {
894        padding,
895        sign_is_mandatory,
896    }: modifier::IsoYearFullStandardRange,
897) -> io::Result<usize> {
898    let mut bytes = 0;
899    bytes += try_likely_ok!(fmt_sign(output, year.is_negative(), sign_is_mandatory));
900    // Safety: The minimum is zero due to the `.abs()` call; the maximum is unchanged.
901    bytes += try_likely_ok!(format_four_digits(
902        output,
903        unsafe { year.abs().narrow_unchecked::<0, 9_999>().into() },
904        padding
905    ));
906    Ok(bytes)
907}
908
909#[inline]
910fn fmt_calendar_year_century_extended_range(
911    output: &mut (impl io::Write + ?Sized),
912    century: ExtendedCentury,
913    is_negative: bool,
914    modifier::CalendarYearCenturyExtendedRange {
915        padding,
916        sign_is_mandatory,
917    }: modifier::CalendarYearCenturyExtendedRange,
918) -> io::Result<usize> {
919    let mut bytes = 0;
920    bytes += try_likely_ok!(fmt_sign(
921        output,
922        is_negative,
923        sign_is_mandatory || century.get() >= 100,
924    ));
925    // Safety: The minimum is zero due to the `.abs()` call;  the maximum is unchanged.
926    let century: ru16<0, 9_999> = unsafe { century.abs().narrow_unchecked::<0, 9_999>().into() };
927
928    bytes += if let Some(century) = century.narrow::<0, 99>() {
929        try_likely_ok!(format_two_digits(output, century.into(), padding))
930    } else if let Some(century) = century.narrow::<0, 999>() {
931        try_likely_ok!(format_three_digits(output, century, padding))
932    } else {
933        try_likely_ok!(format_four_digits(output, century, padding))
934    };
935    Ok(bytes)
936}
937
938#[inline]
939fn fmt_calendar_year_century_standard_range(
940    output: &mut (impl io::Write + ?Sized),
941    century: StandardCentury,
942    is_negative: bool,
943    modifier::CalendarYearCenturyStandardRange {
944        padding,
945        sign_is_mandatory,
946    }: modifier::CalendarYearCenturyStandardRange,
947) -> io::Result<usize> {
948    let mut bytes = 0;
949    bytes += try_likely_ok!(fmt_sign(output, is_negative, sign_is_mandatory));
950    // Safety: The minimum is zero due to the `.unsigned_abs()` call.
951    let century = unsafe { century.abs().narrow_unchecked::<0, 99>() };
952    bytes += try_likely_ok!(format_two_digits(output, century.into(), padding));
953    Ok(bytes)
954}
955
956#[inline]
957fn fmt_iso_year_century_extended_range(
958    output: &mut (impl io::Write + ?Sized),
959    century: ExtendedCentury,
960    is_negative: bool,
961    modifier::IsoYearCenturyExtendedRange {
962        padding,
963        sign_is_mandatory,
964    }: modifier::IsoYearCenturyExtendedRange,
965) -> io::Result<usize> {
966    let mut bytes = 0;
967    bytes += try_likely_ok!(fmt_sign(
968        output,
969        is_negative,
970        sign_is_mandatory || century.get() >= 100,
971    ));
972    // Safety: The minimum is zero due to the `.unsigned_abs()` call, with the maximum is unchanged.
973    let century: ru16<0, 9_999> = unsafe { century.abs().narrow_unchecked::<0, 9_999>().into() };
974
975    bytes += if let Some(century) = century.narrow::<0, 99>() {
976        try_likely_ok!(format_two_digits(output, century.into(), padding))
977    } else if let Some(century) = century.narrow::<0, 999>() {
978        try_likely_ok!(format_three_digits(output, century, padding))
979    } else {
980        try_likely_ok!(format_four_digits(output, century, padding))
981    };
982    Ok(bytes)
983}
984
985#[inline]
986fn fmt_iso_year_century_standard_range(
987    output: &mut (impl io::Write + ?Sized),
988    century: StandardCentury,
989    is_negative: bool,
990    modifier::IsoYearCenturyStandardRange {
991        padding,
992        sign_is_mandatory,
993    }: modifier::IsoYearCenturyStandardRange,
994) -> io::Result<usize> {
995    let mut bytes = 0;
996    bytes += try_likely_ok!(fmt_sign(output, is_negative, sign_is_mandatory));
997    // Safety: The minimum is zero due to the `.unsigned_abs()` call.
998    let century = unsafe { century.abs().narrow_unchecked::<0, 99>() };
999    bytes += try_likely_ok!(format_two_digits(output, century.into(), padding));
1000    Ok(bytes)
1001}
1002
1003#[inline]
1004fn fmt_calendar_year_last_two(
1005    output: &mut (impl io::Write + ?Sized),
1006    last_two: LastTwo,
1007    modifier::CalendarYearLastTwo { padding }: modifier::CalendarYearLastTwo,
1008) -> io::Result<usize> {
1009    format_two_digits(output, last_two, padding)
1010}
1011
1012#[inline]
1013fn fmt_iso_year_last_two(
1014    output: &mut (impl io::Write + ?Sized),
1015    last_two: LastTwo,
1016    modifier::IsoYearLastTwo { padding }: modifier::IsoYearLastTwo,
1017) -> io::Result<usize> {
1018    format_two_digits(output, last_two, padding)
1019}
1020
1021/// Format the full year into the designated output.
1022#[inline]
1023#[expect(deprecated)]
1024#[deprecated]
1025fn fmt_full_year(
1026    output: &mut (impl io::Write + ?Sized),
1027    full_year: Year,
1028    modifier::Year {
1029        padding,
1030        repr: _,
1031        range,
1032        iso_week_based: _,
1033        sign_is_mandatory,
1034    }: modifier::Year,
1035) -> Result<usize, error::Format> {
1036    let mut bytes = 0;
1037    bytes += try_likely_ok!(fmt_sign(
1038        output,
1039        full_year.is_negative(),
1040        sign_is_mandatory || (cfg!(feature = "large-dates") && full_year.get() >= 10_000),
1041    ));
1042    bytes += if cfg!(feature = "large-dates") && range == modifier::YearRange::Extended {
1043        // Safety: We just called `.abs()`, so zero is the minimum. The maximum is
1044        // unchanged.
1045        let value: ru32<0, 999_999> =
1046            unsafe { full_year.abs().narrow_unchecked::<0, 999_999>().into() };
1047
1048        if let Some(value) = value.narrow::<0, 9_999>() {
1049            try_likely_ok!(format_four_digits(output, value.into(), padding))
1050        } else if let Some(value) = value.narrow::<0, 99_999>() {
1051            try_likely_ok!(format_five_digits_pad_zero(output, value))
1052        } else {
1053            try_likely_ok!(format_six_digits_pad_zero(output, value))
1054        }
1055    } else if let Some(value) = full_year.abs().narrow::<0, 9_999>() {
1056        try_likely_ok!(format_four_digits(output, value.into(), padding))
1057    } else {
1058        return Err(error::ComponentRange::conditional("year").into());
1059    };
1060    Ok(bytes)
1061}
1062
1063/// Format the century into the designated output. Requires the full year be provided as an
1064/// argument.
1065#[inline]
1066#[expect(deprecated)]
1067#[deprecated]
1068fn fmt_century(
1069    output: &mut (impl io::Write + ?Sized),
1070    full_year: Year,
1071    modifier::Year {
1072        padding,
1073        repr: _,
1074        range,
1075        iso_week_based: _,
1076        sign_is_mandatory,
1077    }: modifier::Year,
1078) -> Result<usize, error::Format> {
1079    let mut bytes = 0;
1080    bytes += try_likely_ok!(fmt_sign(
1081        output,
1082        full_year.is_negative(),
1083        sign_is_mandatory || (cfg!(feature = "large-dates") && full_year.get() >= 10_000),
1084    ));
1085    bytes += if cfg!(feature = "large-dates") && range == modifier::YearRange::Extended {
1086        // Safety: The maximum divided by 100 is 9,9999, and the minimum is zero due to the
1087        // `.unsigned_abs()` call.
1088        let value: ru16<0, 9_999> =
1089            unsafe { ru16::new_unchecked((full_year.get().unsigned_abs() / 100).truncate()) };
1090
1091        if let Some(value) = value.narrow::<0, 99>() {
1092            try_likely_ok!(format_two_digits(output, value.into(), padding))
1093        } else if let Some(value) = value.narrow::<0, 999>() {
1094            try_likely_ok!(format_three_digits(output, value, padding))
1095        } else {
1096            try_likely_ok!(format_four_digits(output, value, padding))
1097        }
1098    } else {
1099        // Safety: The maximum year in any configuration is 999,999, so dividing by 100
1100        // results in 9,999. The minimum is zero due to the `.unsigned_abs()` call.
1101        let value =
1102            unsafe { ru32::<0, 9_999>::new_unchecked(full_year.get().unsigned_abs() / 100) };
1103
1104        if let Some(value) = value.narrow::<0, 99>() {
1105            try_likely_ok!(format_two_digits(output, value.into(), padding))
1106        } else {
1107            return Err(error::ComponentRange::conditional("year").into());
1108        }
1109    };
1110    Ok(bytes)
1111}
1112
1113#[inline]
1114#[expect(deprecated)]
1115#[deprecated]
1116fn fmt_year_last_two(
1117    output: &mut (impl io::Write + ?Sized),
1118    full_year: Year,
1119    modifier::Year {
1120        padding,
1121        repr: _,
1122        range: _,
1123        iso_week_based: _,
1124        sign_is_mandatory: _,
1125    }: modifier::Year,
1126) -> Result<usize, io::Error> {
1127    let mut bytes = 0;
1128    // Safety: Modulus of 100 followed by `.unsigned_abs()` guarantees that the value
1129    // is in the range `0..=99`.
1130    let value = unsafe { ru8::new_unchecked((full_year.get() % 100).unsigned_abs().truncate()) };
1131    bytes += try_likely_ok!(format_two_digits(output, value, padding));
1132    Ok(bytes)
1133}
1134
1135/// Format the hour into the designated output using the 12-hour clock.
1136#[inline]
1137fn fmt_hour_12(
1138    output: &mut (impl io::Write + ?Sized),
1139    hour: Hours,
1140    modifier::Hour12 { padding }: modifier::Hour12,
1141) -> io::Result<usize> {
1142    // Safety: The value is guaranteed to be in the range `1..=12`.
1143    format_two_digits(
1144        output,
1145        unsafe { ru8::new_unchecked((hour.get() + 11) % 12 + 1) },
1146        padding,
1147    )
1148}
1149
1150/// Format the hour into the designated output using the 24-hour clock.
1151#[inline]
1152fn fmt_hour_24(
1153    output: &mut (impl io::Write + ?Sized),
1154    hour: Hours,
1155    modifier::Hour24 { padding }: modifier::Hour24,
1156) -> io::Result<usize> {
1157    format_two_digits(output, hour.expand(), padding)
1158}
1159
1160/// Format the minute into the designated output.
1161#[inline]
1162fn fmt_minute(
1163    output: &mut (impl io::Write + ?Sized),
1164    minute: Minutes,
1165    modifier::Minute { padding }: modifier::Minute,
1166) -> Result<usize, io::Error> {
1167    format_two_digits(output, minute.expand(), padding)
1168}
1169
1170/// Format the period into the designated output.
1171#[inline]
1172fn fmt_period(
1173    output: &mut (impl io::Write + ?Sized),
1174    period: Period,
1175    modifier::Period {
1176        is_uppercase,
1177        case_sensitive: _, // no effect on formatting
1178    }: modifier::Period,
1179) -> Result<usize, io::Error> {
1180    write(
1181        output,
1182        match (period, is_uppercase) {
1183            (Period::Am, false) => "am",
1184            (Period::Am, true) => "AM",
1185            (Period::Pm, false) => "pm",
1186            (Period::Pm, true) => "PM",
1187        },
1188    )
1189}
1190
1191/// Format the second into the designated output.
1192#[inline]
1193fn fmt_second(
1194    output: &mut (impl io::Write + ?Sized),
1195    second: Seconds,
1196    modifier::Second { padding }: modifier::Second,
1197) -> Result<usize, io::Error> {
1198    format_two_digits(output, second.expand(), padding)
1199}
1200
1201/// Format the subsecond into the designated output.
1202#[inline]
1203fn fmt_subsecond(
1204    output: &mut (impl io::Write + ?Sized),
1205    nanos: Nanoseconds,
1206    modifier::Subsecond { digits }: modifier::Subsecond,
1207) -> Result<usize, io::Error> {
1208    use modifier::SubsecondDigits::*;
1209
1210    #[repr(C, align(8))]
1211    #[derive(Clone, Copy)]
1212    struct Digits {
1213        _padding: MaybeUninit<[u8; 7]>,
1214        digit_1: u8,
1215        digits_2_thru_9: [u8; 8],
1216    }
1217
1218    let [
1219        digit_1,
1220        digits_2_and_3,
1221        digits_4_and_5,
1222        digits_6_and_7,
1223        digits_8_and_9,
1224    ] = num_fmt::subsecond_from_nanos(nanos);
1225
1226    // Ensure that digits 2 thru 9 are stored as a single array that is 8-aligned. This allows the
1227    // conversion to a `u64` to be zero cost, resulting in a nontrivial performance improvement.
1228    let buf = Digits {
1229        _padding: MaybeUninit::uninit(),
1230        digit_1: digit_1.as_bytes()[0],
1231        digits_2_thru_9: [
1232            digits_2_and_3.as_bytes()[0],
1233            digits_2_and_3.as_bytes()[1],
1234            digits_4_and_5.as_bytes()[0],
1235            digits_4_and_5.as_bytes()[1],
1236            digits_6_and_7.as_bytes()[0],
1237            digits_6_and_7.as_bytes()[1],
1238            digits_8_and_9.as_bytes()[0],
1239            digits_8_and_9.as_bytes()[1],
1240        ],
1241    };
1242
1243    let len = match digits {
1244        One => 1,
1245        Two => 2,
1246        Three => 3,
1247        Four => 4,
1248        Five => 5,
1249        Six => 6,
1250        Seven => 7,
1251        Eight => 8,
1252        Nine => 9,
1253        OneOrMore => {
1254            // By converting the bytes into a single integer, we can effectively perform an equality
1255            // check against b'0' for all bytes at once. This is actually faster than
1256            // using portable SIMD (even with `-Ctarget-cpu=native`).
1257            let bitmask = u64::from_le_bytes(buf.digits_2_thru_9) ^ u64::from_le_bytes([b'0'; 8]);
1258            let digits_to_truncate = bitmask.leading_zeros() / 8;
1259            9 - digits_to_truncate as usize
1260        }
1261    };
1262
1263    // Safety: All bytes are initialized and valid UTF-8, and `len` represents the number of bytes
1264    // we wish to display (that is between 1 and 9 inclusive). `Digits` is `#[repr(C)]`, so the
1265    // layout is guaranteed.
1266    let s = unsafe {
1267        num_fmt::StackStr::new(
1268            *(&raw const buf)
1269                .byte_add(core::mem::offset_of!(Digits, digit_1))
1270                .cast::<[MaybeUninit<u8>; 9]>(),
1271            len,
1272        )
1273    };
1274    write(output, &s)
1275}
1276
1277#[inline]
1278fn fmt_sign(
1279    output: &mut (impl io::Write + ?Sized),
1280    is_negative: bool,
1281    sign_is_mandatory: bool,
1282) -> Result<usize, io::Error> {
1283    if is_negative {
1284        write(output, "-")
1285    } else if sign_is_mandatory {
1286        write(output, "+")
1287    } else {
1288        Ok(0)
1289    }
1290}
1291
1292/// Format the offset hour into the designated output.
1293#[inline]
1294fn fmt_offset_hour(
1295    output: &mut (impl io::Write + ?Sized),
1296    is_negative: bool,
1297    hour: OffsetHours,
1298    modifier::OffsetHour {
1299        padding,
1300        sign_is_mandatory,
1301    }: modifier::OffsetHour,
1302) -> Result<usize, io::Error> {
1303    let mut bytes = 0;
1304    bytes += try_likely_ok!(fmt_sign(output, is_negative, sign_is_mandatory));
1305    // Safety: The value is guaranteed to be under 100 because of `OffsetHours`.
1306    bytes += try_likely_ok!(format_two_digits(
1307        output,
1308        unsafe { ru8::new_unchecked(hour.get().unsigned_abs()) },
1309        padding,
1310    ));
1311    Ok(bytes)
1312}
1313
1314/// Format the offset minute into the designated output.
1315#[inline]
1316fn fmt_offset_minute(
1317    output: &mut (impl io::Write + ?Sized),
1318    offset_minute: OffsetMinutes,
1319    modifier::OffsetMinute { padding }: modifier::OffsetMinute,
1320) -> Result<usize, io::Error> {
1321    format_two_digits(
1322        output,
1323        // Safety: `OffsetMinutes` is guaranteed to be in the range `-59..=59`, so the absolute
1324        // value is guaranteed to be in the range `0..=59`.
1325        unsafe { ru8::new_unchecked(offset_minute.get().unsigned_abs()) },
1326        padding,
1327    )
1328}
1329
1330/// Format the offset second into the designated output.
1331#[inline]
1332fn fmt_offset_second(
1333    output: &mut (impl io::Write + ?Sized),
1334    offset_second: OffsetSeconds,
1335    modifier::OffsetSecond { padding }: modifier::OffsetSecond,
1336) -> Result<usize, io::Error> {
1337    format_two_digits(
1338        output,
1339        // Safety: `OffsetSeconds` is guaranteed to be in the range `-59..=59`, so the absolute
1340        // value is guaranteed to be in the range `0..=59`.
1341        unsafe { ru8::new_unchecked(offset_second.get().unsigned_abs()) },
1342        padding,
1343    )
1344}
1345
1346/// Format the Unix timestamp (in seconds) into the designated output.
1347#[inline]
1348fn fmt_unix_timestamp_second(
1349    output: &mut (impl io::Write + ?Sized),
1350    timestamp: i64,
1351    modifier::UnixTimestampSecond { sign_is_mandatory }: modifier::UnixTimestampSecond,
1352) -> Result<usize, io::Error> {
1353    let mut bytes = 0;
1354    bytes += try_likely_ok!(fmt_sign(output, timestamp < 0, sign_is_mandatory));
1355    bytes += try_likely_ok!(format_number_pad_none(output, timestamp.unsigned_abs()));
1356    Ok(bytes)
1357}
1358
1359/// Format the Unix timestamp (in milliseconds) into the designated output.
1360#[inline]
1361fn fmt_unix_timestamp_millisecond(
1362    output: &mut (impl io::Write + ?Sized),
1363    timestamp_millis: i64,
1364    modifier::UnixTimestampMillisecond { sign_is_mandatory }: modifier::UnixTimestampMillisecond,
1365) -> Result<usize, io::Error> {
1366    let mut bytes = 0;
1367    bytes += try_likely_ok!(fmt_sign(output, timestamp_millis < 0, sign_is_mandatory));
1368    bytes += try_likely_ok!(format_number_pad_none(
1369        output,
1370        timestamp_millis.unsigned_abs()
1371    ));
1372    Ok(bytes)
1373}
1374
1375/// Format the Unix timestamp (in microseconds) into the designated output.
1376#[inline]
1377fn fmt_unix_timestamp_microsecond(
1378    output: &mut (impl io::Write + ?Sized),
1379    timestamp_micros: i128,
1380    modifier::UnixTimestampMicrosecond { sign_is_mandatory }: modifier::UnixTimestampMicrosecond,
1381) -> Result<usize, io::Error> {
1382    let mut bytes = 0;
1383    bytes += try_likely_ok!(fmt_sign(output, timestamp_micros < 0, sign_is_mandatory));
1384    bytes += try_likely_ok!(format_number_pad_none(
1385        output,
1386        timestamp_micros.unsigned_abs()
1387    ));
1388    Ok(bytes)
1389}
1390
1391/// Format the Unix timestamp (in nanoseconds) into the designated output.
1392#[inline]
1393fn fmt_unix_timestamp_nanosecond(
1394    output: &mut (impl io::Write + ?Sized),
1395    timestamp_nanos: i128,
1396    modifier::UnixTimestampNanosecond { sign_is_mandatory }: modifier::UnixTimestampNanosecond,
1397) -> Result<usize, io::Error> {
1398    let mut bytes = 0;
1399    bytes += try_likely_ok!(fmt_sign(output, timestamp_nanos < 0, sign_is_mandatory));
1400    bytes += try_likely_ok!(format_number_pad_none(
1401        output,
1402        timestamp_nanos.unsigned_abs()
1403    ));
1404    Ok(bytes)
1405}