Skip to content

Commit

Permalink
Implement SmartDisplay for main types
Browse files Browse the repository at this point in the history
  • Loading branch information
jhpratt committed Oct 13, 2023
1 parent dec2a2d commit 9564e59
Show file tree
Hide file tree
Showing 14 changed files with 383 additions and 81 deletions.
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ time-core = { path = "time-core", version = "=0.1.2" }
time-macros = { path = "time-macros", version = "=0.2.15" }

criterion = { version = "0.5.1", default-features = false }
deranged = { version = "0.3.7", default-features = false }
deranged = { version = "0.3.9", default-features = false, features = [
"powerfmt",
] }
itoa = "1.0.1"
js-sys = "0.3.58"
libc = "0.2.98"
num_threads = "0.1.2"
powerfmt = { version = "0.2.0", default-features = false }
quickcheck = { version = "1.0.3", default-features = false }
quickcheck_macros = "1.0.0"
rand = { version = "0.8.4", default-features = false }
Expand Down
6 changes: 6 additions & 0 deletions tests/formatting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,9 @@ fn display_time() {
assert_eq!(time!(0:00:00.000_000_1).to_string(), "0:00:00.0000001");
assert_eq!(time!(0:00:00.000_000_01).to_string(), "0:00:00.00000001");
assert_eq!(time!(0:00:00.000_000_001).to_string(), "0:00:00.000000001");

assert_eq!(format!("{:>12}", time!(0:00)), " 0:00:00.0");
assert_eq!(format!("{:x^14}", time!(0:00)), "xx0:00:00.0xxx");
}

#[test]
Expand Down Expand Up @@ -464,6 +467,9 @@ fn display_offset() {
assert_eq!(offset!(-23:59).to_string(), "-23:59:00");
assert_eq!(offset!(+23:59:59).to_string(), "+23:59:59");
assert_eq!(offset!(-23:59:59).to_string(), "-23:59:59");

assert_eq!(format!("{:>10}", offset!(UTC)), " +00:00:00");
assert_eq!(format!("{:x^14}", offset!(UTC)), "xx+00:00:00xxx");
}

#[test]
Expand Down
1 change: 1 addition & 0 deletions time/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ wasm-bindgen = ["dep:js-sys"]
[dependencies]
deranged = { workspace = true }
itoa = { workspace = true, optional = true }
powerfmt = { workspace = true }
quickcheck = { workspace = true, optional = true }
rand = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
Expand Down
100 changes: 83 additions & 17 deletions time/src/date.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
//! The [`Date`] struct and its associated `impl`s.
use core::fmt;
use core::num::NonZeroI32;
use core::ops::{Add, Sub};
use core::time::Duration as StdDuration;
use core::{cmp, fmt};
#[cfg(feature = "formatting")]
use std::io;

use deranged::RangedI32;
use powerfmt::ext::FormatterExt;
use powerfmt::smart_display::{self, FormatterOptions, Metadata, SmartDisplay};

use crate::convert::*;
use crate::ext::DigitCount;
#[cfg(feature = "formatting")]
use crate::formatting::Formattable;
use crate::internal_macros::{
Expand Down Expand Up @@ -1303,29 +1306,92 @@ impl Date {
}
}

impl fmt::Display for Date {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if cfg!(feature = "large-dates") && self.year().abs() >= 10_000 {
write!(
f,
"{:+}-{:02}-{:02}",
self.year(),
self.month() as u8,
self.day()
mod private {
#[non_exhaustive]
#[derive(Debug, Clone, Copy)]
pub struct DateMetadata {
/// The width of the year component, including the sign.
pub(super) year_width: u8,
/// Whether the sign should be displayed.
pub(super) display_sign: bool,
pub(super) year: i32,
pub(super) month: u8,
pub(super) day: u8,
}
}
use private::DateMetadata;

impl SmartDisplay for Date {
type Metadata = DateMetadata;

fn metadata(&self, _: FormatterOptions) -> Metadata<Self> {
let (year, month, day) = self.to_calendar_date();

// There is a minimum of four digits for any year.
let mut year_width = cmp::max(year.unsigned_abs().num_digits(), 4);
let display_sign = if !(0..10_000).contains(&year) {
// An extra character is required for the sign.
year_width += 1;
true
} else {
false
};

let formatted_width = year_width as usize
+ smart_display::padded_width_of!(
"-",
month as u8 => width(2),
"-",
day => width(2),
);

Metadata::new(
formatted_width,
self,
DateMetadata {
year_width,
display_sign,
year,
month: month as u8,
day,
},
)
}

fn fmt_with_metadata(
&self,
f: &mut fmt::Formatter<'_>,
metadata: Metadata<Self>,
) -> fmt::Result {
let DateMetadata {
year_width,
display_sign,
year,
month,
day,
} = *metadata;
let year_width = year_width as usize;

if display_sign {
f.pad_with_width(
metadata.unpadded_width(),
format_args!("{year:+0year_width$}-{month:02}-{day:02}"),
)
} else {
write!(
f,
"{:0width$}-{:02}-{:02}",
self.year(),
self.month() as u8,
self.day(),
width = 4 + (self.year() < 0) as usize
f.pad_with_width(
metadata.unpadded_width(),
format_args!("{year:0year_width$}-{month:02}-{day:02}"),
)
}
}
}

impl fmt::Display for Date {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
SmartDisplay::fmt(self, f)
}
}

impl fmt::Debug for Date {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
fmt::Display::fmt(self, f)
Expand Down
57 changes: 48 additions & 9 deletions time/src/date_time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ use std::io;
use std::time::SystemTime;

use deranged::RangedI64;
use powerfmt::ext::FormatterExt;
use powerfmt::smart_display::{self, FormatterOptions, Metadata, SmartDisplay};

use crate::convert::*;
use crate::date::{MAX_YEAR, MIN_YEAR};
Expand Down Expand Up @@ -911,23 +913,60 @@ impl<O: MaybeOffset> DateTime<O> {
// endregion deprecated time getters
}

impl<O: MaybeOffset> fmt::Debug for DateTime<O> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
<Self as fmt::Display>::fmt(self, f)
// region: trait impls
mod private {
use super::*;

#[non_exhaustive]
#[derive(Debug, Clone, Copy)]
pub struct DateTimeMetadata {
pub(super) maybe_offset: Option<UtcOffset>,
}
}
pub(crate) use private::DateTimeMetadata;

impl<O: MaybeOffset> SmartDisplay for DateTime<O> {
type Metadata = DateTimeMetadata;

fn metadata(&self, _: FormatterOptions) -> Metadata<Self> {
let maybe_offset = maybe_offset_as_offset_opt::<O>(self.offset);
let width = match maybe_offset {
Some(offset) => smart_display::padded_width_of!(self.date, " ", self.time, " ", offset),
None => smart_display::padded_width_of!(self.date, " ", self.time),
};
Metadata::new(width, self, DateTimeMetadata { maybe_offset })
}

fn fmt_with_metadata(
&self,
f: &mut fmt::Formatter<'_>,
metadata: Metadata<Self>,
) -> fmt::Result {
match metadata.maybe_offset {
Some(offset) => f.pad_with_width(
metadata.unpadded_width(),
format_args!("{} {} {offset}", self.date, self.time),
),
None => f.pad_with_width(
metadata.unpadded_width(),
format_args!("{} {}", self.date, self.time),
),
}
}
}

impl<O: MaybeOffset> fmt::Display for DateTime<O> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {}", self.date, self.time)?;
if let Some(offset) = maybe_offset_as_offset_opt::<O>(self.offset) {
write!(f, " {offset}")?;
}
Ok(())
SmartDisplay::fmt(self, f)
}
}

impl<O: MaybeOffset> fmt::Debug for DateTime<O> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}

// region: trait impls
impl<O: MaybeOffset> PartialEq for DateTime<O> {
fn eq(&self, rhs: &Self) -> bool {
if O::HAS_LOGICAL_OFFSET {
Expand Down
27 changes: 27 additions & 0 deletions time/src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,3 +290,30 @@ impl NumericalStdDuration for f64 {
}
}
// endregion NumericalStdDuration

// region: DigitCount
/// A trait that indicates the formatted width of the value can be determined.
///
/// Note that this should not be implemented for any signed integers. This forces the caller to
/// write the sign if desired.
pub(crate) trait DigitCount {
/// The number of digits in the stringified value.
fn num_digits(self) -> u8;
}

/// A macro to generate implementations of `DigitCount` for unsigned integers.
macro_rules! impl_digit_count {
($($t:ty),* $(,)?) => {
$(impl DigitCount for $t {
fn num_digits(self) -> u8 {
match self.checked_ilog10() {
Some(n) => (n as u8) + 1,
None => 1,
}
}
})*
};
}

impl_digit_count!(u8, u16, u32);
// endregion DigitCount
29 changes: 1 addition & 28 deletions time/src/formatting/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
pub(crate) mod formattable;
mod iso8601;

use core::num::NonZeroU8;
use std::io;

pub use self::formattable::Formattable;
use crate::convert::*;
use crate::ext::DigitCount;
use crate::format_description::{modifier, Component};
use crate::{error, Date, OffsetDateTime, Time, UtcOffset};

Expand Down Expand Up @@ -38,33 +38,6 @@ const WEEKDAY_NAMES: [&[u8]; 7] = [
b"Sunday",
];

// region: extension trait
/// A trait that indicates the formatted width of the value can be determined.
///
/// Note that this should not be implemented for any signed integers. This forces the caller to
/// write the sign if desired.
pub(crate) trait DigitCount {
/// The number of digits in the stringified value.
fn num_digits(self) -> u8;
}

/// A macro to generate implementations of `DigitCount` for unsigned integers.
macro_rules! impl_digit_count {
($($t:ty),* $(,)?) => {
$(impl DigitCount for $t {
fn num_digits(self) -> u8 {
match self.checked_ilog10() {
Some(n) => (n as u8) + 1,
None => 1,
}
}
})*
};
}

impl_digit_count!(u8, u16, u32);
// endregion extension trait

/// Write all bytes to the output, returning the number of bytes written.
pub(crate) fn write(output: &mut impl io::Write, bytes: &[u8]) -> io::Result<usize> {
output.write_all(bytes)?;
Expand Down
38 changes: 36 additions & 2 deletions time/src/month.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use core::fmt;
use core::num::NonZeroU8;
use core::str::FromStr;

use powerfmt::smart_display::{FormatterOptions, Metadata, SmartDisplay};

use self::Month::*;
use crate::error;

Expand Down Expand Up @@ -164,9 +166,35 @@ impl Month {
}
}

impl fmt::Display for Month {
mod private {
#[non_exhaustive]
#[derive(Debug, Clone, Copy)]
pub struct MonthMetadata;
}
use private::MonthMetadata;

impl SmartDisplay for Month {
type Metadata = MonthMetadata;

fn metadata(&self, _: FormatterOptions) -> Metadata<Self> {
match self {
January => Metadata::new(7, self, MonthMetadata),
February => Metadata::new(8, self, MonthMetadata),
March => Metadata::new(5, self, MonthMetadata),
April => Metadata::new(5, self, MonthMetadata),
May => Metadata::new(3, self, MonthMetadata),
June => Metadata::new(4, self, MonthMetadata),
July => Metadata::new(4, self, MonthMetadata),
August => Metadata::new(6, self, MonthMetadata),
September => Metadata::new(9, self, MonthMetadata),
October => Metadata::new(7, self, MonthMetadata),
November => Metadata::new(8, self, MonthMetadata),
December => Metadata::new(8, self, MonthMetadata),
}
}

fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
f.pad(match self {
January => "January",
February => "February",
March => "March",
Expand All @@ -183,6 +211,12 @@ impl fmt::Display for Month {
}
}

impl fmt::Display for Month {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
SmartDisplay::fmt(self, f)
}
}

impl FromStr for Month {
type Err = error::InvalidVariant;

Expand Down
Loading

0 comments on commit 9564e59

Please sign in to comment.