1 //! Formatting for various types.
3 pub(crate) mod formattable
;
6 use core
::num
::NonZeroU8
;
9 pub use self::formattable
::Formattable
;
10 use crate::format_description
::{modifier, Component}
;
11 use crate::{error, Date, Time, UtcOffset}
;
13 #[allow(clippy::missing_docs_in_private_items)]
14 const MONTH_NAMES
: [&[u8]; 12] = [
29 #[allow(clippy::missing_docs_in_private_items)]
30 const WEEKDAY_NAMES
: [&[u8]; 7] = [
40 // region: extension trait
41 /// A trait that indicates the formatted width of the value can be determined.
43 /// Note that this should not be implemented for any signed integers. This forces the caller to
44 /// write the sign if desired.
45 pub(crate) trait DigitCount
{
46 /// The number of digits in the stringified value.
47 fn num_digits(self) -> u8;
49 impl DigitCount
for u8 {
50 fn num_digits(self) -> u8 {
51 // Using a lookup table as with u32 is *not* faster in standalone benchmarks.
54 } else if self < 100 {
61 impl DigitCount
for u16 {
62 fn num_digits(self) -> u8 {
63 // Using a lookup table as with u32 is *not* faster in standalone benchmarks.
66 } else if self < 100 {
68 } else if self < 1_000 {
70 } else if self < 10_000 {
78 impl DigitCount
for u32 {
79 fn num_digits(self) -> u8 {
81 const TABLE
: &[u64] = &[
115 ((self as u64 + TABLE
[31_u32.saturating_sub(self.leading_zeros()) as usize]) >> 32) as _
118 // endregion extension trait
120 /// Write all bytes to the output, returning the number of bytes written.
121 pub(crate) fn write(output
: &mut impl io
::Write
, bytes
: &[u8]) -> io
::Result
<usize> {
122 output
.write_all(bytes
)?
;
126 /// If `pred` is true, write all bytes to the output, returning the number of bytes written.
127 pub(crate) fn write_if(output
: &mut impl io
::Write
, pred
: bool
, bytes
: &[u8]) -> io
::Result
<usize> {
128 if pred { write(output, bytes) }
else { Ok(0) }
131 /// If `pred` is true, write `true_bytes` to the output. Otherwise, write `false_bytes`.
132 pub(crate) fn write_if_else(
133 output
: &mut impl io
::Write
,
137 ) -> io
::Result
<usize> {
138 write(output
, if pred { true_bytes }
else { false_bytes }
)
141 /// Write the floating point number to the output, returning the number of bytes written.
143 /// This method accepts the number of digits before and after the decimal. The value will be padded
144 /// with zeroes to the left if necessary.
145 pub(crate) fn format_float(
146 output
: &mut impl io
::Write
,
148 digits_before_decimal
: u8,
149 digits_after_decimal
: Option
<NonZeroU8
>,
150 ) -> io
::Result
<usize> {
151 match digits_after_decimal
{
152 Some(digits_after_decimal
) => {
153 let digits_after_decimal
= digits_after_decimal
.get() as usize;
154 let width
= digits_before_decimal
as usize + 1 + digits_after_decimal
;
157 "{value:0>width$.digits_after_decimal$}",
160 digits_after_decimal
= digits_after_decimal
,
165 let value
= value
.trunc() as u64;
166 let width
= digits_before_decimal
as usize;
167 write
!(output
, "{value:0>width$?}", value
= value
, width
= width
)?
;
173 /// Format a number with the provided padding and width.
175 /// The sign must be written by the caller.
176 pub(crate) fn format_number
<const WIDTH
: u8, W
: io
::Write
, V
: itoa
::Integer
+ DigitCount
+ Copy
>(
179 padding
: modifier
::Padding
,
180 ) -> Result
<usize, io
::Error
> {
182 modifier
::Padding
::Space
=> format_number_pad_space
::<WIDTH
, _
, _
>(output
, value
),
183 modifier
::Padding
::Zero
=> format_number_pad_zero
::<WIDTH
, _
, _
>(output
, value
),
184 modifier
::Padding
::None
=> write(output
, itoa
::Buffer
::new().format(value
).as_bytes()),
188 /// Format a number with the provided width and spaces as padding.
190 /// The sign must be written by the caller.
191 pub(crate) fn format_number_pad_space
<
194 V
: itoa
::Integer
+ DigitCount
+ Copy
,
198 ) -> Result
<usize, io
::Error
> {
200 for _
in 0..(WIDTH
.saturating_sub(value
.num_digits())) {
201 bytes
+= write(output
, b
" ")?
;
203 bytes
+= write(output
, itoa
::Buffer
::new().format(value
).as_bytes())?
;
207 /// Format a number with the provided width and zeros as padding.
209 /// The sign must be written by the caller.
210 pub(crate) fn format_number_pad_zero
<
213 V
: itoa
::Integer
+ DigitCount
+ Copy
,
217 ) -> Result
<usize, io
::Error
> {
219 for _
in 0..(WIDTH
.saturating_sub(value
.num_digits())) {
220 bytes
+= write(output
, b
"0")?
;
222 bytes
+= write(output
, itoa
::Buffer
::new().format(value
).as_bytes())?
;
226 /// Format the provided component into the designated output. An `Err` will be returned if the
227 /// component requires information that it does not provide or if the value cannot be output to the
229 pub(crate) fn format_component(
230 output
: &mut impl io
::Write
,
231 component
: Component
,
234 offset
: Option
<UtcOffset
>,
235 ) -> Result
<usize, error
::Format
> {
237 Ok(match (component
, date
, time
, offset
) {
238 (Day(modifier
), Some(date
), ..) => fmt_day(output
, date
, modifier
)?
,
239 (Month(modifier
), Some(date
), ..) => fmt_month(output
, date
, modifier
)?
,
240 (Ordinal(modifier
), Some(date
), ..) => fmt_ordinal(output
, date
, modifier
)?
,
241 (Weekday(modifier
), Some(date
), ..) => fmt_weekday(output
, date
, modifier
)?
,
242 (WeekNumber(modifier
), Some(date
), ..) => fmt_week_number(output
, date
, modifier
)?
,
243 (Year(modifier
), Some(date
), ..) => fmt_year(output
, date
, modifier
)?
,
244 (Hour(modifier
), _
, Some(time
), _
) => fmt_hour(output
, time
, modifier
)?
,
245 (Minute(modifier
), _
, Some(time
), _
) => fmt_minute(output
, time
, modifier
)?
,
246 (Period(modifier
), _
, Some(time
), _
) => fmt_period(output
, time
, modifier
)?
,
247 (Second(modifier
), _
, Some(time
), _
) => fmt_second(output
, time
, modifier
)?
,
248 (Subsecond(modifier
), _
, Some(time
), _
) => fmt_subsecond(output
, time
, modifier
)?
,
249 (OffsetHour(modifier
), .., Some(offset
)) => fmt_offset_hour(output
, offset
, modifier
)?
,
250 (OffsetMinute(modifier
), .., Some(offset
)) => fmt_offset_minute(output
, offset
, modifier
)?
,
251 (OffsetSecond(modifier
), .., Some(offset
)) => fmt_offset_second(output
, offset
, modifier
)?
,
252 _
=> return Err(error
::Format
::InsufficientTypeInformation
),
256 // region: date formatters
257 /// Format the day into the designated output.
259 output
: &mut impl io
::Write
,
261 modifier
::Day { padding }
: modifier
::Day
,
262 ) -> Result
<usize, io
::Error
> {
263 format_number
::<2, _
, _
>(output
, date
.day(), padding
)
266 /// Format the month into the designated output.
268 output
: &mut impl io
::Write
,
273 case_sensitive
: _
, // no effect on formatting
275 ) -> Result
<usize, io
::Error
> {
277 modifier
::MonthRepr
::Numerical
=> {
278 format_number
::<2, _
, _
>(output
, date
.month() as u8, padding
)
280 modifier
::MonthRepr
::Long
=> write(output
, MONTH_NAMES
[date
.month() as usize - 1]),
281 modifier
::MonthRepr
::Short
=> write(output
, &MONTH_NAMES
[date
.month() as usize - 1][..3]),
285 /// Format the ordinal into the designated output.
287 output
: &mut impl io
::Write
,
289 modifier
::Ordinal { padding }
: modifier
::Ordinal
,
290 ) -> Result
<usize, io
::Error
> {
291 format_number
::<3, _
, _
>(output
, date
.ordinal(), padding
)
294 /// Format the weekday into the designated output.
296 output
: &mut impl io
::Write
,
301 case_sensitive
: _
, // no effect on formatting
302 }: modifier
::Weekday
,
303 ) -> Result
<usize, io
::Error
> {
305 modifier
::WeekdayRepr
::Short
=> write(
307 &WEEKDAY_NAMES
[date
.weekday().number_days_from_monday() as usize][..3],
309 modifier
::WeekdayRepr
::Long
=> write(
311 WEEKDAY_NAMES
[date
.weekday().number_days_from_monday() as usize],
313 modifier
::WeekdayRepr
::Sunday
=> format_number
::<1, _
, _
>(
315 date
.weekday().number_days_from_sunday() + one_indexed
as u8,
316 modifier
::Padding
::None
,
318 modifier
::WeekdayRepr
::Monday
=> format_number
::<1, _
, _
>(
320 date
.weekday().number_days_from_monday() + one_indexed
as u8,
321 modifier
::Padding
::None
,
326 /// Format the week number into the designated output.
328 output
: &mut impl io
::Write
,
330 modifier
::WeekNumber { padding, repr }
: modifier
::WeekNumber
,
331 ) -> Result
<usize, io
::Error
> {
332 format_number
::<2, _
, _
>(
335 modifier
::WeekNumberRepr
::Iso
=> date
.iso_week(),
336 modifier
::WeekNumberRepr
::Sunday
=> date
.sunday_based_week(),
337 modifier
::WeekNumberRepr
::Monday
=> date
.monday_based_week(),
343 /// Format the year into the designated output.
345 output
: &mut impl io
::Write
,
353 ) -> Result
<usize, io
::Error
> {
354 let full_year
= if iso_week_based
{
355 date
.iso_year_week().0
359 let value
= match repr
{
360 modifier
::YearRepr
::Full
=> full_year
,
361 modifier
::YearRepr
::LastTwo
=> (full_year
% 100).abs(),
363 let format_number
= match repr
{
364 #[cfg(feature = "large-dates")]
365 modifier
::YearRepr
::Full
if value
.abs() >= 100_000 => format_number
::<6, _
, _
>,
366 #[cfg(feature = "large-dates")]
367 modifier
::YearRepr
::Full
if value
.abs() >= 10_000 => format_number
::<5, _
, _
>,
368 modifier
::YearRepr
::Full
=> format_number
::<4, _
, _
>,
369 modifier
::YearRepr
::LastTwo
=> format_number
::<2, _
, _
>,
372 if repr
!= modifier
::YearRepr
::LastTwo
{
374 bytes
+= write(output
, b
"-")?
;
375 } else if sign_is_mandatory
|| cfg
!(feature
= "large-dates") && full_year
>= 10_000 {
376 bytes
+= write(output
, b
"+")?
;
379 bytes
+= format_number(output
, value
.unsigned_abs(), padding
)?
;
382 // endregion date formatters
384 // region: time formatters
385 /// Format the hour into the designated output.
387 output
: &mut impl io
::Write
,
393 ) -> Result
<usize, io
::Error
> {
394 let value
= match (time
.hour(), is_12_hour_clock
) {
395 (hour
, false) => hour
,
396 (0 | 12, true) => 12,
397 (hour
, true) if hour
< 12 => hour
,
398 (hour
, true) => hour
- 12,
400 format_number
::<2, _
, _
>(output
, value
, padding
)
403 /// Format the minute into the designated output.
405 output
: &mut impl io
::Write
,
407 modifier
::Minute { padding }
: modifier
::Minute
,
408 ) -> Result
<usize, io
::Error
> {
409 format_number
::<2, _
, _
>(output
, time
.minute(), padding
)
412 /// Format the period into the designated output.
414 output
: &mut impl io
::Write
,
418 case_sensitive
: _
, // no effect on formatting
420 ) -> Result
<usize, io
::Error
> {
421 match (time
.hour() >= 12, is_uppercase
) {
422 (false, false) => write(output
, b
"am"),
423 (false, true) => write(output
, b
"AM"),
424 (true, false) => write(output
, b
"pm"),
425 (true, true) => write(output
, b
"PM"),
429 /// Format the second into the designated output.
431 output
: &mut impl io
::Write
,
433 modifier
::Second { padding }
: modifier
::Second
,
434 ) -> Result
<usize, io
::Error
> {
435 format_number
::<2, _
, _
>(output
, time
.second(), padding
)
438 /// Format the subsecond into the designated output.
439 fn fmt_subsecond
<W
: io
::Write
>(
442 modifier
::Subsecond { digits }
: modifier
::Subsecond
,
443 ) -> Result
<usize, io
::Error
> {
444 use modifier
::SubsecondDigits
::*;
445 let nanos
= time
.nanosecond();
447 if digits
== Nine
|| (digits
== OneOrMore
&& nanos
% 10 != 0) {
448 format_number_pad_zero
::<9, _
, _
>(output
, nanos
)
449 } else if digits
== Eight
|| (digits
== OneOrMore
&& (nanos
/ 10) % 10 != 0) {
450 format_number_pad_zero
::<8, _
, _
>(output
, nanos
/ 10)
451 } else if digits
== Seven
|| (digits
== OneOrMore
&& (nanos
/ 100) % 10 != 0) {
452 format_number_pad_zero
::<7, _
, _
>(output
, nanos
/ 100)
453 } else if digits
== Six
|| (digits
== OneOrMore
&& (nanos
/ 1_000) % 10 != 0) {
454 format_number_pad_zero
::<6, _
, _
>(output
, nanos
/ 1_000)
455 } else if digits
== Five
|| (digits
== OneOrMore
&& (nanos
/ 10_000) % 10 != 0) {
456 format_number_pad_zero
::<5, _
, _
>(output
, nanos
/ 10_000)
457 } else if digits
== Four
|| (digits
== OneOrMore
&& (nanos
/ 100_000) % 10 != 0) {
458 format_number_pad_zero
::<4, _
, _
>(output
, nanos
/ 100_000)
459 } else if digits
== Three
|| (digits
== OneOrMore
&& (nanos
/ 1_000_000) % 10 != 0) {
460 format_number_pad_zero
::<3, _
, _
>(output
, nanos
/ 1_000_000)
461 } else if digits
== Two
|| (digits
== OneOrMore
&& (nanos
/ 10_000_000) % 10 != 0) {
462 format_number_pad_zero
::<2, _
, _
>(output
, nanos
/ 10_000_000)
464 format_number_pad_zero
::<1, _
, _
>(output
, nanos
/ 100_000_000)
467 // endregion time formatters
469 // region: offset formatters
470 /// Format the offset hour into the designated output.
472 output
: &mut impl io
::Write
,
474 modifier
::OffsetHour
{
477 }: modifier
::OffsetHour
,
478 ) -> Result
<usize, io
::Error
> {
480 if offset
.is_negative() {
481 bytes
+= write(output
, b
"-")?
;
482 } else if sign_is_mandatory
{
483 bytes
+= write(output
, b
"+")?
;
485 bytes
+= format_number
::<2, _
, _
>(output
, offset
.whole_hours().unsigned_abs(), padding
)?
;
489 /// Format the offset minute into the designated output.
490 fn fmt_offset_minute(
491 output
: &mut impl io
::Write
,
493 modifier
::OffsetMinute { padding }
: modifier
::OffsetMinute
,
494 ) -> Result
<usize, io
::Error
> {
495 format_number
::<2, _
, _
>(output
, offset
.minutes_past_hour().unsigned_abs(), padding
)
498 /// Format the offset second into the designated output.
499 fn fmt_offset_second(
500 output
: &mut impl io
::Write
,
502 modifier
::OffsetSecond { padding }
: modifier
::OffsetSecond
,
503 ) -> Result
<usize, io
::Error
> {
504 format_number
::<2, _
, _
>(output
, offset
.seconds_past_minute().unsigned_abs(), padding
)
506 // endregion offset formatters