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