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