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