|
2 | 2 |
|
3 | 3 | #[cfg(feature = "formatting")] |
4 | 4 | use alloc::string::String; |
| 5 | +use core::fmt; |
| 6 | +use core::mem::MaybeUninit; |
5 | 7 | use core::num::NonZero; |
6 | 8 | use core::ops::{Add, AddAssign, Sub, SubAssign}; |
7 | 9 | use core::time::Duration as StdDuration; |
8 | | -use core::{cmp, fmt}; |
9 | 10 | #[cfg(feature = "formatting")] |
10 | 11 | use std::io; |
11 | 12 |
|
12 | 13 | use deranged::RangedI32; |
13 | 14 | use num_conv::prelude::*; |
14 | | -use powerfmt::ext::FormatterExt; |
15 | | -use powerfmt::smart_display::{self, FormatterOptions, Metadata, SmartDisplay}; |
| 15 | +use powerfmt::smart_display::{FormatterOptions, Metadata, SmartDisplay}; |
16 | 16 |
|
17 | | -use crate::ext::DigitCount; |
18 | 17 | #[cfg(feature = "formatting")] |
19 | 18 | use crate::formatting::Formattable; |
20 | 19 | use crate::internal_macros::{const_try, const_try_opt, div_floor, ensure_ranged}; |
| 20 | +use crate::num_fmt::{four_to_six_digits, str_from_raw_parts, two_digits_zero_padded}; |
21 | 21 | #[cfg(feature = "parsing")] |
22 | 22 | use crate::parsing::Parsable; |
23 | 23 | use crate::unit::*; |
@@ -1417,90 +1417,111 @@ impl Date { |
1417 | 1417 | mod private { |
1418 | 1418 | /// Metadata for `Date`. |
1419 | 1419 | #[non_exhaustive] |
1420 | | - #[derive(Debug, Clone, Copy)] |
1421 | | - pub struct DateMetadata { |
1422 | | - /// The width of the year component, including the sign. |
1423 | | - pub(super) year_width: u8, |
1424 | | - /// Whether the sign should be displayed. |
1425 | | - pub(super) display_sign: bool, |
1426 | | - pub(super) year: i32, |
1427 | | - pub(super) month: u8, |
1428 | | - pub(super) day: u8, |
1429 | | - } |
| 1420 | + #[derive(Debug)] |
| 1421 | + pub struct DateMetadata; |
1430 | 1422 | } |
1431 | 1423 | use private::DateMetadata; |
1432 | 1424 |
|
| 1425 | +// This no longer needs special handling, as the format is fixed and doesn't require anything |
| 1426 | +// advanced. Trait impls can't be deprecated and the info is still useful for other types |
| 1427 | +// implementing `SmartDisplay`, so leave it as-is for now. |
1433 | 1428 | impl SmartDisplay for Date { |
1434 | 1429 | type Metadata = DateMetadata; |
1435 | 1430 |
|
1436 | 1431 | #[inline] |
1437 | 1432 | fn metadata(&self, _: FormatterOptions) -> Metadata<'_, Self> { |
1438 | | - let (year, month, day) = self.to_calendar_date(); |
1439 | | - |
1440 | | - // There is a minimum of four digits for any year. |
1441 | | - let mut year_width = cmp::max(year.unsigned_abs().num_digits(), 4); |
1442 | | - let display_sign = if !(0..10_000).contains(&year) { |
1443 | | - // An extra character is required for the sign. |
1444 | | - year_width += 1; |
1445 | | - true |
1446 | | - } else { |
1447 | | - false |
1448 | | - }; |
| 1433 | + use crate::ext::DigitCount as _; |
1449 | 1434 |
|
1450 | | - let formatted_width = year_width.extend::<usize>() |
1451 | | - + smart_display::padded_width_of!( |
1452 | | - "-", |
1453 | | - u8::from(month) => width(2), |
1454 | | - "-", |
1455 | | - day => width(2), |
1456 | | - ); |
| 1435 | + let year_sign_width = |
| 1436 | + if self.year() < 0 || (cfg!(feature = "large-dates") && self.year() >= 10_000) { |
| 1437 | + 1 |
| 1438 | + } else { |
| 1439 | + 0 |
| 1440 | + }; |
| 1441 | + let year_width = self.year().unsigned_abs().num_digits().clamp(4, 6); |
| 1442 | + let formatted_width = year_sign_width + year_width + 6; // include two dashes and two digits each for month and day |
1457 | 1443 |
|
1458 | | - Metadata::new( |
1459 | | - formatted_width, |
1460 | | - self, |
1461 | | - DateMetadata { |
1462 | | - year_width, |
1463 | | - display_sign, |
1464 | | - year, |
1465 | | - month: u8::from(month), |
1466 | | - day, |
1467 | | - }, |
1468 | | - ) |
| 1444 | + Metadata::new(formatted_width as usize, self, DateMetadata) |
1469 | 1445 | } |
1470 | 1446 |
|
1471 | 1447 | #[inline] |
1472 | | - fn fmt_with_metadata( |
1473 | | - &self, |
1474 | | - f: &mut fmt::Formatter<'_>, |
1475 | | - metadata: Metadata<Self>, |
1476 | | - ) -> fmt::Result { |
1477 | | - let DateMetadata { |
1478 | | - year_width, |
1479 | | - display_sign, |
1480 | | - year, |
1481 | | - month, |
1482 | | - day, |
1483 | | - } = *metadata; |
1484 | | - let year_width = year_width.extend(); |
1485 | | - |
1486 | | - if display_sign { |
1487 | | - f.pad_with_width( |
1488 | | - metadata.unpadded_width(), |
1489 | | - format_args!("{year:+0year_width$}-{month:02}-{day:02}"), |
1490 | | - ) |
1491 | | - } else { |
1492 | | - f.pad_with_width( |
1493 | | - metadata.unpadded_width(), |
1494 | | - format_args!("{year:0year_width$}-{month:02}-{day:02}"), |
1495 | | - ) |
1496 | | - } |
| 1448 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 1449 | + fmt::Display::fmt(self, f) |
1497 | 1450 | } |
1498 | 1451 | } |
1499 | 1452 |
|
1500 | 1453 | impl fmt::Display for Date { |
1501 | 1454 | #[inline] |
1502 | 1455 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
1503 | | - SmartDisplay::fmt(self, f) |
| 1456 | + let mut buf = [MaybeUninit::uninit(); 13]; |
| 1457 | + let mut idx = 0; |
| 1458 | + let (year, month, day) = self.to_calendar_date(); |
| 1459 | + |
| 1460 | + // Compute the sign of the integer, if any. Doing this in a branchless manner gives a |
| 1461 | + // significant performance improvement. |
| 1462 | + let neg = year.is_negative() as u8; |
| 1463 | + let pos = (cfg!(feature = "large-dates") && year - 10_000 >= 0) as u8; |
| 1464 | + let sign = b'+' + 2 * neg; // b'-' if `neg` is true, b'+' otherwise |
| 1465 | + // Always write the computed byte, even if it's later overwritten by the first digit of the |
| 1466 | + // year. |
| 1467 | + buf[idx] = MaybeUninit::new(sign); |
| 1468 | + idx += (neg | pos) as usize; |
| 1469 | + |
| 1470 | + // Safety: `year.unsigned_abs()` is less than 1,000,000. |
| 1471 | + let [first_two, second_two, third_two] = unsafe { four_to_six_digits(year.unsigned_abs()) }; |
| 1472 | + // Safety: |
| 1473 | + // - both `first_two` and `buf` are valid for reads and writes of up to 2 bytes. |
| 1474 | + // - `u8` is 1-aligned, so that is not a concern. |
| 1475 | + // - `first_two` points to static memory, while `buf` is a local variable, so they do not |
| 1476 | + // overlap. |
| 1477 | + unsafe { |
| 1478 | + first_two |
| 1479 | + .as_ptr() |
| 1480 | + .copy_to_nonoverlapping(buf.as_mut_ptr().add(idx).cast(), first_two.len()); |
| 1481 | + } |
| 1482 | + idx += first_two.len(); |
| 1483 | + // Safety: See above. |
| 1484 | + unsafe { |
| 1485 | + second_two |
| 1486 | + .as_ptr() |
| 1487 | + .copy_to_nonoverlapping(buf.as_mut_ptr().add(idx).cast(), 2); |
| 1488 | + } |
| 1489 | + idx += 2; |
| 1490 | + // Safety: See above. |
| 1491 | + unsafe { |
| 1492 | + third_two |
| 1493 | + .as_ptr() |
| 1494 | + .copy_to_nonoverlapping(buf.as_mut_ptr().add(idx).cast(), 2); |
| 1495 | + } |
| 1496 | + idx += 2; |
| 1497 | + |
| 1498 | + buf[idx] = MaybeUninit::new(b'-'); |
| 1499 | + idx += 1; |
| 1500 | + |
| 1501 | + // Safety: See above for `copy_to_nonoverlapping`. `two_digits` is valid because `month` is |
| 1502 | + // in the range 1..=12. |
| 1503 | + unsafe { |
| 1504 | + two_digits_zero_padded(u8::from(month)) |
| 1505 | + .as_ptr() |
| 1506 | + .copy_to_nonoverlapping(buf.as_mut_ptr().add(idx).cast(), 2); |
| 1507 | + } |
| 1508 | + idx += 2; |
| 1509 | + |
| 1510 | + buf[idx] = MaybeUninit::new(b'-'); |
| 1511 | + idx += 1; |
| 1512 | + |
| 1513 | + // Safety: See above for `copy_to_nonoverlapping`. `two_digits` is valid because `day` is in |
| 1514 | + // the range 1..=31. |
| 1515 | + unsafe { |
| 1516 | + two_digits_zero_padded(day) |
| 1517 | + .as_ptr() |
| 1518 | + .copy_to_nonoverlapping(buf.as_mut_ptr().add(idx).cast(), 2); |
| 1519 | + } |
| 1520 | + idx += 2; |
| 1521 | + |
| 1522 | + // Safety: All bytes up to `idx` have been initialized with ASCII characters. |
| 1523 | + let s = unsafe { str_from_raw_parts((&raw const buf).cast(), idx) }; |
| 1524 | + f.pad(s) |
1504 | 1525 | } |
1505 | 1526 | } |
1506 | 1527 |
|
|
0 commit comments