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, format_description_v3, 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 provided component into the designated output. An `Err` will be returned if the
282/// component requires information that it does not provide or if the value cannot be output to the
283/// stream.
284#[inline]
285fn fmt_component_v3<V>(
286    output: &mut (impl io::Write + ?Sized),
287    value: &V,
288    state: &mut <V as ComponentProvider>::State,
289    component: &format_description_v3::Component,
290) -> Result<usize, error::Format>
291where
292    V: ComponentProvider,
293{
294    use format_description_v3::Component::*;
295
296    use crate::formatting::*;
297
298    match component {
299        Day(modifier) if V::SUPPLIES_DATE => fmt_day(output, value.day(state), *modifier),
300        MonthShort(modifier) if V::SUPPLIES_DATE => {
301            fmt_month_short(output, value.month(state), *modifier)
302        }
303        MonthLong(modifier) if V::SUPPLIES_DATE => {
304            fmt_month_long(output, value.month(state), *modifier)
305        }
306        MonthNumerical(modifier) if V::SUPPLIES_DATE => {
307            fmt_month_numerical(output, value.month(state), *modifier)
308        }
309        Ordinal(modifier) if V::SUPPLIES_DATE => {
310            fmt_ordinal(output, value.ordinal(state), *modifier)
311        }
312        WeekdayShort(modifier) if V::SUPPLIES_DATE => {
313            fmt_weekday_short(output, value.weekday(state), *modifier)
314        }
315        WeekdayLong(modifier) if V::SUPPLIES_DATE => {
316            fmt_weekday_long(output, value.weekday(state), *modifier)
317        }
318        WeekdaySunday(modifier) if V::SUPPLIES_DATE => {
319            fmt_weekday_sunday(output, value.weekday(state), *modifier)
320        }
321        WeekdayMonday(modifier) if V::SUPPLIES_DATE => {
322            fmt_weekday_monday(output, value.weekday(state), *modifier)
323        }
324        WeekNumberIso(modifier) if V::SUPPLIES_DATE => {
325            fmt_week_number_iso(output, value.iso_week_number(state), *modifier)
326        }
327        WeekNumberSunday(modifier) if V::SUPPLIES_DATE => {
328            fmt_week_number_sunday(output, value.sunday_based_week(state), *modifier)
329        }
330        WeekNumberMonday(modifier) if V::SUPPLIES_DATE => {
331            fmt_week_number_monday(output, value.monday_based_week(state), *modifier)
332        }
333        CalendarYearFullExtendedRange(modifier) if V::SUPPLIES_DATE => {
334            fmt_calendar_year_full_extended_range(output, value.calendar_year(state), *modifier)
335        }
336        CalendarYearFullStandardRange(modifier) if V::SUPPLIES_DATE => {
337            fmt_calendar_year_full_standard_range(
338                output,
339                try_likely_ok!(
340                    value
341                        .calendar_year(state)
342                        .narrow::<-9_999, 9_999>()
343                        .ok_or_else(|| error::ComponentRange::conditional("year"))
344                )
345                .into(),
346                *modifier,
347            )
348        }
349        IsoYearFullExtendedRange(modifier) if V::SUPPLIES_DATE => {
350            fmt_iso_year_full_extended_range(output, value.iso_year(state), *modifier)
351        }
352        IsoYearFullStandardRange(modifier) if V::SUPPLIES_DATE => fmt_iso_year_full_standard_range(
353            output,
354            try_likely_ok!(
355                value
356                    .iso_year(state)
357                    .narrow::<-9_999, 9_999>()
358                    .ok_or_else(|| error::ComponentRange::conditional("year"))
359            )
360            .into(),
361            *modifier,
362        ),
363        CalendarYearCenturyExtendedRange(modifier) if V::SUPPLIES_DATE => {
364            let year = value.calendar_year(state);
365            // Safety: Given the range of `year`, the range of the century is
366            // `-9_999..=9_999`.
367            let century = unsafe { ri16::new_unchecked((year.get() / 100).truncate()) };
368            fmt_calendar_year_century_extended_range(output, century, year.is_negative(), *modifier)
369        }
370        CalendarYearCenturyStandardRange(modifier) if V::SUPPLIES_DATE => {
371            let year = value.calendar_year(state);
372            let is_negative = year.is_negative();
373            // Safety: Given the range of `year`, the range of the century is
374            // `-9_999..=9_999`.
375            let year = unsafe { ri16::<0, 9_999>::new_unchecked((year.get() / 100).truncate()) };
376            fmt_calendar_year_century_standard_range(
377                output,
378                year.narrow::<0, 99>()
379                    .ok_or_else(|| error::ComponentRange::conditional("year"))?
380                    .into(),
381                is_negative,
382                *modifier,
383            )
384        }
385        IsoYearCenturyExtendedRange(modifier) if V::SUPPLIES_DATE => {
386            let year = value.iso_year(state);
387            // Safety: Given the range of `year`, the range of the century is
388            // `-9_999..=9_999`.
389            let century = unsafe { ri16::new_unchecked((year.get() / 100).truncate()) };
390            fmt_iso_year_century_extended_range(output, century, year.is_negative(), *modifier)
391        }
392        IsoYearCenturyStandardRange(modifier) if V::SUPPLIES_DATE => {
393            let year = value.iso_year(state);
394            // Safety: Given the range of `year`, the range of the century is
395            // `-9_999..=9_999`.
396            let year = unsafe { ri16::<0, 9_999>::new_unchecked((year.get() / 100).truncate()) };
397            fmt_iso_year_century_standard_range(
398                output,
399                year.narrow::<0, 99>()
400                    .ok_or_else(|| error::ComponentRange::conditional("year"))?
401                    .into(),
402                year.is_negative(),
403                *modifier,
404            )
405        }
406        CalendarYearLastTwo(modifier) if V::SUPPLIES_DATE => {
407            // Safety: Modulus of 100 followed by `.unsigned_abs()` guarantees that the
408            // value is in the range `0..=99`.
409            let last_two = unsafe {
410                ru8::new_unchecked(
411                    (value.calendar_year(state).get().unsigned_abs() % 100).truncate(),
412                )
413            };
414            fmt_calendar_year_last_two(output, last_two, *modifier)
415        }
416        IsoYearLastTwo(modifier) if V::SUPPLIES_DATE => {
417            // Safety: Modulus of 100 followed by `.unsigned_abs()` guarantees that the
418            // value is in the range `0..=99`.
419            let last_two = unsafe {
420                ru8::new_unchecked((value.iso_year(state).get().unsigned_abs() % 100).truncate())
421            };
422            fmt_iso_year_last_two(output, last_two, *modifier)
423        }
424        Hour12(modifier) if V::SUPPLIES_TIME => fmt_hour_12(output, value.hour(state), *modifier),
425        Hour24(modifier) if V::SUPPLIES_TIME => fmt_hour_24(output, value.hour(state), *modifier),
426        Minute(modifier) if V::SUPPLIES_TIME => fmt_minute(output, value.minute(state), *modifier),
427        Period(modifier) if V::SUPPLIES_TIME => fmt_period(output, value.period(state), *modifier),
428        Second(modifier) if V::SUPPLIES_TIME => fmt_second(output, value.second(state), *modifier),
429        Subsecond(modifier) if V::SUPPLIES_TIME => {
430            fmt_subsecond(output, value.nanosecond(state), *modifier)
431        }
432        OffsetHour(modifier) if V::SUPPLIES_OFFSET => fmt_offset_hour(
433            output,
434            value.offset_is_negative(state),
435            value.offset_hour(state),
436            *modifier,
437        ),
438        OffsetMinute(modifier) if V::SUPPLIES_OFFSET => {
439            fmt_offset_minute(output, value.offset_minute(state), *modifier)
440        }
441        OffsetSecond(modifier) if V::SUPPLIES_OFFSET => {
442            fmt_offset_second(output, value.offset_second(state), *modifier)
443        }
444        Ignore(_) => return Ok(0),
445        UnixTimestampSecond(modifier) if V::SUPPLIES_TIMESTAMP => {
446            fmt_unix_timestamp_second(output, value.unix_timestamp_seconds(state), *modifier)
447        }
448        UnixTimestampMillisecond(modifier) if V::SUPPLIES_TIMESTAMP => {
449            fmt_unix_timestamp_millisecond(
450                output,
451                value.unix_timestamp_milliseconds(state),
452                *modifier,
453            )
454        }
455        UnixTimestampMicrosecond(modifier) if V::SUPPLIES_TIMESTAMP => {
456            fmt_unix_timestamp_microsecond(
457                output,
458                value.unix_timestamp_microseconds(state),
459                *modifier,
460            )
461        }
462        UnixTimestampNanosecond(modifier) if V::SUPPLIES_TIMESTAMP => {
463            fmt_unix_timestamp_nanosecond(
464                output,
465                value.unix_timestamp_nanoseconds(state),
466                *modifier,
467            )
468        }
469        End(modifier::End { trailing_input: _ }) => return Ok(0),
470
471        // This is functionally the same as a wildcard arm, but it will cause an error
472        // if a new component is added. This is to avoid a bug where
473        // a new component, the code compiles, and formatting fails.
474        // Allow unreachable patterns because some branches may be fully matched above.
475        #[allow(unreachable_patterns)]
476        Day(_)
477        | MonthShort(_)
478        | MonthLong(_)
479        | MonthNumerical(_)
480        | Ordinal(_)
481        | WeekdayShort(_)
482        | WeekdayLong(_)
483        | WeekdaySunday(_)
484        | WeekdayMonday(_)
485        | WeekNumberIso(_)
486        | WeekNumberSunday(_)
487        | WeekNumberMonday(_)
488        | CalendarYearFullExtendedRange(_)
489        | CalendarYearFullStandardRange(_)
490        | IsoYearFullExtendedRange(_)
491        | IsoYearFullStandardRange(_)
492        | CalendarYearCenturyExtendedRange(_)
493        | CalendarYearCenturyStandardRange(_)
494        | IsoYearCenturyExtendedRange(_)
495        | IsoYearCenturyStandardRange(_)
496        | CalendarYearLastTwo(_)
497        | IsoYearLastTwo(_)
498        | Hour12(_)
499        | Hour24(_)
500        | Minute(_)
501        | Period(_)
502        | Second(_)
503        | Subsecond(_)
504        | OffsetHour(_)
505        | OffsetMinute(_)
506        | OffsetSecond(_)
507        | Ignore(_)
508        | UnixTimestampSecond(_)
509        | UnixTimestampMillisecond(_)
510        | UnixTimestampMicrosecond(_)
511        | UnixTimestampNanosecond(_)
512        | End(_) => return Err(error::Format::InsufficientTypeInformation),
513    }
514    .map_err(Into::into)
515}
516
517/// Format the day into the designated output.
518#[inline]
519fn fmt_day(
520    output: &mut (impl io::Write + ?Sized),
521    day: Day,
522    modifier::Day { padding }: modifier::Day,
523) -> Result<usize, io::Error> {
524    format_two_digits(output, day.expand(), padding)
525}
526
527/// Format the month into the designated output using the abbreviated name.
528#[inline]
529fn fmt_month_short(
530    output: &mut (impl io::Write + ?Sized),
531    month: Month,
532    modifier::MonthShort {
533        case_sensitive: _, // no effect on formatting
534    }: modifier::MonthShort,
535) -> io::Result<usize> {
536    // Safety: All month names are at least three bytes long.
537    write(output, unsafe {
538        MONTH_NAMES[u8::from(month).extend::<usize>() - 1].get_unchecked(..3)
539    })
540}
541
542/// Format the month into the designated output using the full name.
543#[inline]
544fn fmt_month_long(
545    output: &mut (impl io::Write + ?Sized),
546    month: Month,
547    modifier::MonthLong {
548        case_sensitive: _, // no effect on formatting
549    }: modifier::MonthLong,
550) -> io::Result<usize> {
551    write(output, MONTH_NAMES[u8::from(month).extend::<usize>() - 1])
552}
553
554/// Format the month into the designated output as a number from 1-12.
555#[inline]
556fn fmt_month_numerical(
557    output: &mut (impl io::Write + ?Sized),
558    month: Month,
559    modifier::MonthNumerical { padding }: modifier::MonthNumerical,
560) -> io::Result<usize> {
561    format_two_digits(
562        output,
563        // Safety: The month is guaranteed to be in the range `1..=12`.
564        unsafe { ru8::new_unchecked(u8::from(month)) },
565        padding,
566    )
567}
568
569/// Format the ordinal into the designated output.
570#[inline]
571fn fmt_ordinal(
572    output: &mut (impl io::Write + ?Sized),
573    ordinal: Ordinal,
574    modifier::Ordinal { padding }: modifier::Ordinal,
575) -> Result<usize, io::Error> {
576    format_three_digits(output, ordinal.expand(), padding)
577}
578
579/// Format the weekday into the designated output using the abbreviated name.
580#[inline]
581fn fmt_weekday_short(
582    output: &mut (impl io::Write + ?Sized),
583    weekday: Weekday,
584    modifier::WeekdayShort {
585        case_sensitive: _, // no effect on formatting
586    }: modifier::WeekdayShort,
587) -> io::Result<usize> {
588    // Safety: All weekday names are at least three bytes long.
589    write(output, unsafe {
590        WEEKDAY_NAMES[weekday.number_days_from_monday().extend::<usize>()].get_unchecked(..3)
591    })
592}
593
594/// Format the weekday into the designated output using the full name.
595#[inline]
596fn fmt_weekday_long(
597    output: &mut (impl io::Write + ?Sized),
598    weekday: Weekday,
599    modifier::WeekdayLong {
600        case_sensitive: _, // no effect on formatting
601    }: modifier::WeekdayLong,
602) -> io::Result<usize> {
603    write(
604        output,
605        WEEKDAY_NAMES[weekday.number_days_from_monday().extend::<usize>()],
606    )
607}
608
609/// Format the weekday into the designated output as a number from either 0-6 or 1-7 (depending on
610/// the modifier), where Sunday is either 0 or 1.
611#[inline]
612fn fmt_weekday_sunday(
613    output: &mut (impl io::Write + ?Sized),
614    weekday: Weekday,
615    modifier::WeekdaySunday { one_indexed }: modifier::WeekdaySunday,
616) -> io::Result<usize> {
617    // Safety: The value is guaranteed to be in the range `0..=7`.
618    format_single_digit(output, unsafe {
619        ru8::new_unchecked(weekday.number_days_from_sunday() + u8::from(one_indexed))
620    })
621}
622
623/// Format the weekday into the designated output as a number from either 0-6 or 1-7 (depending on
624/// the modifier), where Monday is either 0 or 1.
625#[inline]
626fn fmt_weekday_monday(
627    output: &mut (impl io::Write + ?Sized),
628    weekday: Weekday,
629    modifier::WeekdayMonday { one_indexed }: modifier::WeekdayMonday,
630) -> io::Result<usize> {
631    // Safety: The value is guaranteed to be in the range `0..=7`.
632    format_single_digit(output, unsafe {
633        ru8::new_unchecked(weekday.number_days_from_monday() + u8::from(one_indexed))
634    })
635}
636
637#[inline]
638fn fmt_week_number_iso(
639    output: &mut (impl io::Write + ?Sized),
640    week_number: IsoWeekNumber,
641    modifier::WeekNumberIso { padding }: modifier::WeekNumberIso,
642) -> io::Result<usize> {
643    format_two_digits(output, week_number.expand(), padding)
644}
645
646#[inline]
647fn fmt_week_number_sunday(
648    output: &mut (impl io::Write + ?Sized),
649    week_number: SundayBasedWeek,
650    modifier::WeekNumberSunday { padding }: modifier::WeekNumberSunday,
651) -> io::Result<usize> {
652    format_two_digits(output, week_number.expand(), padding)
653}
654
655#[inline]
656fn fmt_week_number_monday(
657    output: &mut (impl io::Write + ?Sized),
658    week_number: MondayBasedWeek,
659    modifier::WeekNumberMonday { padding }: modifier::WeekNumberMonday,
660) -> io::Result<usize> {
661    format_two_digits(output, week_number.expand(), padding)
662}
663
664#[inline]
665fn fmt_calendar_year_full_extended_range(
666    output: &mut (impl io::Write + ?Sized),
667    full_year: Year,
668    modifier::CalendarYearFullExtendedRange {
669        padding,
670        sign_is_mandatory,
671    }: modifier::CalendarYearFullExtendedRange,
672) -> io::Result<usize> {
673    let mut bytes = 0;
674    bytes += try_likely_ok!(fmt_sign(
675        output,
676        full_year.is_negative(),
677        sign_is_mandatory || full_year.get() >= 10_000,
678    ));
679    // Safety: We just called `.abs()`, so zero is the minimum. The maximum is
680    // unchanged.
681    let value: ru32<0, 999_999> =
682        unsafe { full_year.abs().narrow_unchecked::<0, 999_999>().into() };
683
684    bytes += if let Some(value) = value.narrow::<0, 9_999>() {
685        try_likely_ok!(format_four_digits(output, value.into(), padding))
686    } else if let Some(value) = value.narrow::<0, 99_999>() {
687        try_likely_ok!(format_five_digits_pad_zero(output, value))
688    } else {
689        try_likely_ok!(format_six_digits_pad_zero(output, value))
690    };
691    Ok(bytes)
692}
693
694#[inline]
695fn fmt_calendar_year_full_standard_range(
696    output: &mut (impl io::Write + ?Sized),
697    full_year: StandardYear,
698    modifier::CalendarYearFullStandardRange {
699        padding,
700        sign_is_mandatory,
701    }: modifier::CalendarYearFullStandardRange,
702) -> io::Result<usize> {
703    let mut bytes = 0;
704    bytes += try_likely_ok!(fmt_sign(output, full_year.is_negative(), sign_is_mandatory));
705    // Safety: The minimum is zero due to the `.abs()` call; the maximum is unchanged.
706    bytes += try_likely_ok!(format_four_digits(
707        output,
708        unsafe { full_year.abs().narrow_unchecked::<0, 9_999>().into() },
709        padding
710    ));
711    Ok(bytes)
712}
713
714#[inline]
715fn fmt_iso_year_full_extended_range(
716    output: &mut (impl io::Write + ?Sized),
717    full_year: Year,
718    modifier::IsoYearFullExtendedRange {
719        padding,
720        sign_is_mandatory,
721    }: modifier::IsoYearFullExtendedRange,
722) -> io::Result<usize> {
723    let mut bytes = 0;
724    bytes += try_likely_ok!(fmt_sign(
725        output,
726        full_year.is_negative(),
727        sign_is_mandatory || full_year.get() >= 10_000,
728    ));
729    // Safety: The minimum is zero due to the `.abs()` call, with the maximum is unchanged.
730    let value: ru32<0, 999_999> =
731        unsafe { full_year.abs().narrow_unchecked::<0, 999_999>().into() };
732
733    bytes += if let Some(value) = value.narrow::<0, 9_999>() {
734        try_likely_ok!(format_four_digits(output, value.into(), padding))
735    } else if let Some(value) = value.narrow::<0, 99_999>() {
736        try_likely_ok!(format_five_digits_pad_zero(output, value))
737    } else {
738        try_likely_ok!(format_six_digits_pad_zero(output, value))
739    };
740    Ok(bytes)
741}
742
743#[inline]
744fn fmt_iso_year_full_standard_range(
745    output: &mut (impl io::Write + ?Sized),
746    year: StandardYear,
747    modifier::IsoYearFullStandardRange {
748        padding,
749        sign_is_mandatory,
750    }: modifier::IsoYearFullStandardRange,
751) -> io::Result<usize> {
752    let mut bytes = 0;
753    bytes += try_likely_ok!(fmt_sign(output, year.is_negative(), sign_is_mandatory));
754    // Safety: The minimum is zero due to the `.abs()` call; the maximum is unchanged.
755    bytes += try_likely_ok!(format_four_digits(
756        output,
757        unsafe { year.abs().narrow_unchecked::<0, 9_999>().into() },
758        padding
759    ));
760    Ok(bytes)
761}
762
763#[inline]
764fn fmt_calendar_year_century_extended_range(
765    output: &mut (impl io::Write + ?Sized),
766    century: ExtendedCentury,
767    is_negative: bool,
768    modifier::CalendarYearCenturyExtendedRange {
769        padding,
770        sign_is_mandatory,
771    }: modifier::CalendarYearCenturyExtendedRange,
772) -> io::Result<usize> {
773    let mut bytes = 0;
774    bytes += try_likely_ok!(fmt_sign(
775        output,
776        is_negative,
777        sign_is_mandatory || century.get() >= 100,
778    ));
779    // Safety: The minimum is zero due to the `.abs()` call;  the maximum is unchanged.
780    let century: ru16<0, 9_999> = unsafe { century.abs().narrow_unchecked::<0, 9_999>().into() };
781
782    bytes += if let Some(century) = century.narrow::<0, 99>() {
783        try_likely_ok!(format_two_digits(output, century.into(), padding))
784    } else if let Some(century) = century.narrow::<0, 999>() {
785        try_likely_ok!(format_three_digits(output, century, padding))
786    } else {
787        try_likely_ok!(format_four_digits(output, century, padding))
788    };
789    Ok(bytes)
790}
791
792#[inline]
793fn fmt_calendar_year_century_standard_range(
794    output: &mut (impl io::Write + ?Sized),
795    century: StandardCentury,
796    is_negative: bool,
797    modifier::CalendarYearCenturyStandardRange {
798        padding,
799        sign_is_mandatory,
800    }: modifier::CalendarYearCenturyStandardRange,
801) -> io::Result<usize> {
802    let mut bytes = 0;
803    bytes += try_likely_ok!(fmt_sign(output, is_negative, sign_is_mandatory));
804    // Safety: The minimum is zero due to the `.unsigned_abs()` call.
805    let century = unsafe { century.abs().narrow_unchecked::<0, 99>() };
806    bytes += try_likely_ok!(format_two_digits(output, century.into(), padding));
807    Ok(bytes)
808}
809
810#[inline]
811fn fmt_iso_year_century_extended_range(
812    output: &mut (impl io::Write + ?Sized),
813    century: ExtendedCentury,
814    is_negative: bool,
815    modifier::IsoYearCenturyExtendedRange {
816        padding,
817        sign_is_mandatory,
818    }: modifier::IsoYearCenturyExtendedRange,
819) -> io::Result<usize> {
820    let mut bytes = 0;
821    bytes += try_likely_ok!(fmt_sign(
822        output,
823        is_negative,
824        sign_is_mandatory || century.get() >= 100,
825    ));
826    // Safety: The minimum is zero due to the `.unsigned_abs()` call, with the maximum is unchanged.
827    let century: ru16<0, 9_999> = unsafe { century.abs().narrow_unchecked::<0, 9_999>().into() };
828
829    bytes += if let Some(century) = century.narrow::<0, 99>() {
830        try_likely_ok!(format_two_digits(output, century.into(), padding))
831    } else if let Some(century) = century.narrow::<0, 999>() {
832        try_likely_ok!(format_three_digits(output, century, padding))
833    } else {
834        try_likely_ok!(format_four_digits(output, century, padding))
835    };
836    Ok(bytes)
837}
838
839#[inline]
840fn fmt_iso_year_century_standard_range(
841    output: &mut (impl io::Write + ?Sized),
842    century: StandardCentury,
843    is_negative: bool,
844    modifier::IsoYearCenturyStandardRange {
845        padding,
846        sign_is_mandatory,
847    }: modifier::IsoYearCenturyStandardRange,
848) -> io::Result<usize> {
849    let mut bytes = 0;
850    bytes += try_likely_ok!(fmt_sign(output, is_negative, sign_is_mandatory));
851    // Safety: The minimum is zero due to the `.unsigned_abs()` call.
852    let century = unsafe { century.abs().narrow_unchecked::<0, 99>() };
853    bytes += try_likely_ok!(format_two_digits(output, century.into(), padding));
854    Ok(bytes)
855}
856
857#[inline]
858fn fmt_calendar_year_last_two(
859    output: &mut (impl io::Write + ?Sized),
860    last_two: LastTwo,
861    modifier::CalendarYearLastTwo { padding }: modifier::CalendarYearLastTwo,
862) -> io::Result<usize> {
863    format_two_digits(output, last_two, padding)
864}
865
866#[inline]
867fn fmt_iso_year_last_two(
868    output: &mut (impl io::Write + ?Sized),
869    last_two: LastTwo,
870    modifier::IsoYearLastTwo { padding }: modifier::IsoYearLastTwo,
871) -> io::Result<usize> {
872    format_two_digits(output, last_two, padding)
873}
874
875/// Format the hour into the designated output using the 12-hour clock.
876#[inline]
877fn fmt_hour_12(
878    output: &mut (impl io::Write + ?Sized),
879    hour: Hours,
880    modifier::Hour12 { padding }: modifier::Hour12,
881) -> io::Result<usize> {
882    // Safety: The value is guaranteed to be in the range `1..=12`.
883    format_two_digits(
884        output,
885        unsafe { ru8::new_unchecked((hour.get() + 11) % 12 + 1) },
886        padding,
887    )
888}
889
890/// Format the hour into the designated output using the 24-hour clock.
891#[inline]
892fn fmt_hour_24(
893    output: &mut (impl io::Write + ?Sized),
894    hour: Hours,
895    modifier::Hour24 { padding }: modifier::Hour24,
896) -> io::Result<usize> {
897    format_two_digits(output, hour.expand(), padding)
898}
899
900/// Format the minute into the designated output.
901#[inline]
902fn fmt_minute(
903    output: &mut (impl io::Write + ?Sized),
904    minute: Minutes,
905    modifier::Minute { padding }: modifier::Minute,
906) -> Result<usize, io::Error> {
907    format_two_digits(output, minute.expand(), padding)
908}
909
910/// Format the period into the designated output.
911#[inline]
912fn fmt_period(
913    output: &mut (impl io::Write + ?Sized),
914    period: Period,
915    modifier::Period {
916        is_uppercase,
917        case_sensitive: _, // no effect on formatting
918    }: modifier::Period,
919) -> Result<usize, io::Error> {
920    write(
921        output,
922        match (period, is_uppercase) {
923            (Period::Am, false) => "am",
924            (Period::Am, true) => "AM",
925            (Period::Pm, false) => "pm",
926            (Period::Pm, true) => "PM",
927        },
928    )
929}
930
931/// Format the second into the designated output.
932#[inline]
933fn fmt_second(
934    output: &mut (impl io::Write + ?Sized),
935    second: Seconds,
936    modifier::Second { padding }: modifier::Second,
937) -> Result<usize, io::Error> {
938    format_two_digits(output, second.expand(), padding)
939}
940
941/// Format the subsecond into the designated output.
942#[inline]
943fn fmt_subsecond(
944    output: &mut (impl io::Write + ?Sized),
945    nanos: Nanoseconds,
946    modifier::Subsecond { digits }: modifier::Subsecond,
947) -> Result<usize, io::Error> {
948    use modifier::SubsecondDigits::*;
949
950    #[repr(C, align(8))]
951    #[derive(Clone, Copy)]
952    struct Digits {
953        _padding: MaybeUninit<[u8; 7]>,
954        digit_1: u8,
955        digits_2_thru_9: [u8; 8],
956    }
957
958    let [
959        digit_1,
960        digits_2_and_3,
961        digits_4_and_5,
962        digits_6_and_7,
963        digits_8_and_9,
964    ] = num_fmt::subsecond_from_nanos(nanos);
965
966    // Ensure that digits 2 thru 9 are stored as a single array that is 8-aligned. This allows the
967    // conversion to a `u64` to be zero cost, resulting in a nontrivial performance improvement.
968    let buf = Digits {
969        _padding: MaybeUninit::uninit(),
970        digit_1: digit_1.as_bytes()[0],
971        digits_2_thru_9: [
972            digits_2_and_3.as_bytes()[0],
973            digits_2_and_3.as_bytes()[1],
974            digits_4_and_5.as_bytes()[0],
975            digits_4_and_5.as_bytes()[1],
976            digits_6_and_7.as_bytes()[0],
977            digits_6_and_7.as_bytes()[1],
978            digits_8_and_9.as_bytes()[0],
979            digits_8_and_9.as_bytes()[1],
980        ],
981    };
982
983    let len = match digits {
984        One => 1,
985        Two => 2,
986        Three => 3,
987        Four => 4,
988        Five => 5,
989        Six => 6,
990        Seven => 7,
991        Eight => 8,
992        Nine => 9,
993        OneOrMore => {
994            // By converting the bytes into a single integer, we can effectively perform an equality
995            // check against b'0' for all bytes at once. This is actually faster than
996            // using portable SIMD (even with `-Ctarget-cpu=native`).
997            let bitmask = u64::from_le_bytes(buf.digits_2_thru_9) ^ u64::from_le_bytes([b'0'; 8]);
998            let digits_to_truncate = bitmask.leading_zeros() / 8;
999            9 - digits_to_truncate as usize
1000        }
1001    };
1002
1003    // Safety: All bytes are initialized and valid UTF-8, and `len` represents the number of bytes
1004    // we wish to display (that is between 1 and 9 inclusive). `Digits` is `#[repr(C)]`, so the
1005    // layout is guaranteed.
1006    let s = unsafe {
1007        num_fmt::StackStr::new(
1008            *(&raw const buf)
1009                .byte_add(core::mem::offset_of!(Digits, digit_1))
1010                .cast::<[MaybeUninit<u8>; 9]>(),
1011            len,
1012        )
1013    };
1014    write(output, &s)
1015}
1016
1017#[inline]
1018fn fmt_sign(
1019    output: &mut (impl io::Write + ?Sized),
1020    is_negative: bool,
1021    sign_is_mandatory: bool,
1022) -> Result<usize, io::Error> {
1023    if is_negative {
1024        write(output, "-")
1025    } else if sign_is_mandatory {
1026        write(output, "+")
1027    } else {
1028        Ok(0)
1029    }
1030}
1031
1032/// Format the offset hour into the designated output.
1033#[inline]
1034fn fmt_offset_hour(
1035    output: &mut (impl io::Write + ?Sized),
1036    is_negative: bool,
1037    hour: OffsetHours,
1038    modifier::OffsetHour {
1039        padding,
1040        sign_is_mandatory,
1041    }: modifier::OffsetHour,
1042) -> Result<usize, io::Error> {
1043    let mut bytes = 0;
1044    bytes += try_likely_ok!(fmt_sign(output, is_negative, sign_is_mandatory));
1045    // Safety: The value is guaranteed to be under 100 because of `OffsetHours`.
1046    bytes += try_likely_ok!(format_two_digits(
1047        output,
1048        unsafe { ru8::new_unchecked(hour.get().unsigned_abs()) },
1049        padding,
1050    ));
1051    Ok(bytes)
1052}
1053
1054/// Format the offset minute into the designated output.
1055#[inline]
1056fn fmt_offset_minute(
1057    output: &mut (impl io::Write + ?Sized),
1058    offset_minute: OffsetMinutes,
1059    modifier::OffsetMinute { padding }: modifier::OffsetMinute,
1060) -> Result<usize, io::Error> {
1061    format_two_digits(
1062        output,
1063        // Safety: `OffsetMinutes` is guaranteed to be in the range `-59..=59`, so the absolute
1064        // value is guaranteed to be in the range `0..=59`.
1065        unsafe { ru8::new_unchecked(offset_minute.get().unsigned_abs()) },
1066        padding,
1067    )
1068}
1069
1070/// Format the offset second into the designated output.
1071#[inline]
1072fn fmt_offset_second(
1073    output: &mut (impl io::Write + ?Sized),
1074    offset_second: OffsetSeconds,
1075    modifier::OffsetSecond { padding }: modifier::OffsetSecond,
1076) -> Result<usize, io::Error> {
1077    format_two_digits(
1078        output,
1079        // Safety: `OffsetSeconds` is guaranteed to be in the range `-59..=59`, so the absolute
1080        // value is guaranteed to be in the range `0..=59`.
1081        unsafe { ru8::new_unchecked(offset_second.get().unsigned_abs()) },
1082        padding,
1083    )
1084}
1085
1086/// Format the Unix timestamp (in seconds) into the designated output.
1087#[inline]
1088fn fmt_unix_timestamp_second(
1089    output: &mut (impl io::Write + ?Sized),
1090    timestamp: i64,
1091    modifier::UnixTimestampSecond { sign_is_mandatory }: modifier::UnixTimestampSecond,
1092) -> Result<usize, io::Error> {
1093    let mut bytes = 0;
1094    bytes += try_likely_ok!(fmt_sign(output, timestamp < 0, sign_is_mandatory));
1095    bytes += try_likely_ok!(format_u64_pad_none(output, timestamp.unsigned_abs()));
1096    Ok(bytes)
1097}
1098
1099/// Format the Unix timestamp (in milliseconds) into the designated output.
1100#[inline]
1101fn fmt_unix_timestamp_millisecond(
1102    output: &mut (impl io::Write + ?Sized),
1103    timestamp_millis: i64,
1104    modifier::UnixTimestampMillisecond { sign_is_mandatory }: modifier::UnixTimestampMillisecond,
1105) -> Result<usize, io::Error> {
1106    let mut bytes = 0;
1107    bytes += try_likely_ok!(fmt_sign(output, timestamp_millis < 0, sign_is_mandatory));
1108    bytes += try_likely_ok!(format_u64_pad_none(output, timestamp_millis.unsigned_abs()));
1109    Ok(bytes)
1110}
1111
1112/// Format the Unix timestamp (in microseconds) into the designated output.
1113#[inline]
1114fn fmt_unix_timestamp_microsecond(
1115    output: &mut (impl io::Write + ?Sized),
1116    timestamp_micros: i128,
1117    modifier::UnixTimestampMicrosecond { sign_is_mandatory }: modifier::UnixTimestampMicrosecond,
1118) -> Result<usize, io::Error> {
1119    let mut bytes = 0;
1120    bytes += try_likely_ok!(fmt_sign(output, timestamp_micros < 0, sign_is_mandatory));
1121    bytes += try_likely_ok!(format_u128_pad_none(
1122        output,
1123        timestamp_micros.unsigned_abs()
1124    ));
1125    Ok(bytes)
1126}
1127
1128/// Format the Unix timestamp (in nanoseconds) into the designated output.
1129#[inline]
1130fn fmt_unix_timestamp_nanosecond(
1131    output: &mut (impl io::Write + ?Sized),
1132    timestamp_nanos: i128,
1133    modifier::UnixTimestampNanosecond { sign_is_mandatory }: modifier::UnixTimestampNanosecond,
1134) -> Result<usize, io::Error> {
1135    let mut bytes = 0;
1136    bytes += try_likely_ok!(fmt_sign(output, timestamp_nanos < 0, sign_is_mandatory));
1137    bytes += try_likely_ok!(format_u128_pad_none(output, timestamp_nanos.unsigned_abs()));
1138    Ok(bytes)
1139}