Skip to content

Commit

Permalink
Add niche value optimization to Date
Browse files Browse the repository at this point in the history
  • Loading branch information
jhpratt committed Sep 24, 2023
1 parent bcbca6e commit 313f980
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 46 deletions.
2 changes: 1 addition & 1 deletion tests/meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ fn size() {
};
}

assert_size!(Date, 4, 8);
assert_size!(Date, 4, 4);
assert_size!(Duration, 16, 16);
assert_size!(OffsetDateTime, 16, 16);
assert_size!(PrimitiveDateTime, 12, 12);
Expand Down
10 changes: 6 additions & 4 deletions time-macros/src/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,12 @@ pub(crate) fn parse(chars: &mut Peekable<token_stream::IntoIter>) -> Result<Date
impl ToTokenTree for Date {
fn into_token_tree(self) -> TokenTree {
quote_group! {{
const DATE: ::time::Date = ::time::Date::__from_ordinal_date_unchecked(
#(self.year),
#(self.ordinal),
);
const DATE: ::time::Date = unsafe {
::time::Date::__from_ordinal_date_unchecked(
#(self.year),
#(self.ordinal),
)
};
DATE
}}
}
Expand Down
110 changes: 73 additions & 37 deletions time/src/date.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! 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;
#[cfg(feature = "formatting")]
Expand Down Expand Up @@ -47,32 +48,43 @@ pub struct Date {
// | 2 bits | 21 bits | 9 bits |
// | unassigned | year | ordinal |
// The year is 15 bits when `large-dates` is not enabled.
value: i32,
value: NonZeroI32,
}

impl Date {
/// The minimum valid `Date`.
///
/// The value of this may vary depending on the feature flags enabled.
pub const MIN: Self = Self::__from_ordinal_date_unchecked(MIN_YEAR, 1);
// Safety: `ordinal` is not zero.
#[allow(clippy::undocumented_unsafe_blocks)]
pub const MIN: Self = unsafe { Self::__from_ordinal_date_unchecked(MIN_YEAR, 1) };

/// The maximum valid `Date`.
///
/// The value of this may vary depending on the feature flags enabled.
pub const MAX: Self = Self::__from_ordinal_date_unchecked(MAX_YEAR, days_in_year(MAX_YEAR));
// Safety: `ordinal` is not zero.
#[allow(clippy::undocumented_unsafe_blocks)]
pub const MAX: Self =
unsafe { Self::__from_ordinal_date_unchecked(MAX_YEAR, days_in_year(MAX_YEAR)) };

// region: constructors
/// Construct a `Date` from the year and ordinal values, the validity of which must be
/// guaranteed by the caller.
///
/// # Safety
///
/// `ordinal` must not be zero. `year` should be in the range `MIN_YEAR..=MAX_YEAR`, but this
/// is not a safety invariant.
#[doc(hidden)]
pub const fn __from_ordinal_date_unchecked(year: i32, ordinal: u16) -> Self {
pub const unsafe fn __from_ordinal_date_unchecked(year: i32, ordinal: u16) -> Self {
debug_assert!(year >= MIN_YEAR);
debug_assert!(year <= MAX_YEAR);
debug_assert!(ordinal != 0);
debug_assert!(ordinal <= days_in_year(year));

Self {
value: (year << 9) | ordinal as i32,
// Safety: The caller must guarantee that `ordinal` is not zero.
value: unsafe { NonZeroI32::new_unchecked((year << 9) | ordinal as i32) },
}
}

Expand Down Expand Up @@ -114,11 +126,14 @@ impl Date {
}
}

Ok(Self::__from_ordinal_date_unchecked(
year,
DAYS_CUMULATIVE_COMMON_LEAP[is_leap_year(year) as usize][month as usize - 1]
+ day as u16,
))
// Safety: `ordinal` is not zero.
Ok(unsafe {
Self::__from_ordinal_date_unchecked(
year,
DAYS_CUMULATIVE_COMMON_LEAP[is_leap_year(year) as usize][month as usize - 1]
+ day as u16,
)
})
}

/// Attempt to create a `Date` from the year and ordinal day number.
Expand Down Expand Up @@ -149,7 +164,8 @@ impl Date {
}
}

Ok(Self::__from_ordinal_date_unchecked(year, ordinal))
// Safety: `ordinal` is not zero.
Ok(unsafe { Self::__from_ordinal_date_unchecked(year, ordinal) })
}

/// Attempt to create a `Date` from the ISO year, week, and weekday.
Expand Down Expand Up @@ -200,14 +216,21 @@ impl Date {
let ordinal = week as i16 * 7 + weekday.number_from_monday() as i16 - jan_4;

Ok(if ordinal <= 0 {
Self::__from_ordinal_date_unchecked(
year - 1,
(ordinal as u16).wrapping_add(days_in_year(year - 1)),
)
// Safety: `ordinal` is not zero.
unsafe {
Self::__from_ordinal_date_unchecked(
year - 1,
(ordinal as u16).wrapping_add(days_in_year(year - 1)),
)
}
} else if ordinal > days_in_year(year) as i16 {
Self::__from_ordinal_date_unchecked(year + 1, ordinal as u16 - days_in_year(year))
// Safety: `ordinal` is not zero.
unsafe {
Self::__from_ordinal_date_unchecked(year + 1, ordinal as u16 - days_in_year(year))
}
} else {
Self::__from_ordinal_date_unchecked(year, ordinal as _)
// Safety: `ordinal` is not zero.
unsafe { Self::__from_ordinal_date_unchecked(year, ordinal as _) }
})
}

Expand Down Expand Up @@ -267,7 +290,8 @@ impl Date {
cascade!(ordinal in 1..366 => year);
}

Self::__from_ordinal_date_unchecked(year, ordinal)
// Safety: `ordinal` is not zero.
unsafe { Self::__from_ordinal_date_unchecked(year, ordinal) }
}
// endregion constructors

Expand All @@ -281,7 +305,7 @@ impl Date {
/// assert_eq!(date!(2020 - 01 - 01).year(), 2020);
/// ```
pub const fn year(self) -> i32 {
self.value >> 9
self.value.get() >> 9
}

/// Get the month.
Expand Down Expand Up @@ -360,7 +384,7 @@ impl Date {
/// assert_eq!(date!(2019 - 12 - 31).ordinal(), 365);
/// ```
pub const fn ordinal(self) -> u16 {
(self.value & 0x1FF) as _
(self.value.get() & 0x1FF) as _
}

/// Get the ISO 8601 year and week number.
Expand Down Expand Up @@ -527,14 +551,16 @@ impl Date {
/// ```
pub const fn next_day(self) -> Option<Self> {
if self.ordinal() == 366 || (self.ordinal() == 365 && !is_leap_year(self.year())) {
if self.value == Self::MAX.value {
if self.value.get() == Self::MAX.value.get() {
None
} else {
Some(Self::__from_ordinal_date_unchecked(self.year() + 1, 1))
// Safety: `ordinal` is not zero.
unsafe { Some(Self::__from_ordinal_date_unchecked(self.year() + 1, 1)) }
}
} else {
Some(Self {
value: self.value + 1,
// Safety: `ordinal` is not zero.
value: unsafe { NonZeroI32::new_unchecked(self.value.get() + 1) },
})
}
}
Expand All @@ -561,15 +587,16 @@ impl Date {
pub const fn previous_day(self) -> Option<Self> {
if self.ordinal() != 1 {
Some(Self {
value: self.value - 1,
// Safety: `ordinal` is not zero.
value: unsafe { NonZeroI32::new_unchecked(self.value.get() - 1) },
})
} else if self.value == Self::MIN.value {
} else if self.value.get() == Self::MIN.value.get() {
None
} else {
Some(Self::__from_ordinal_date_unchecked(
self.year() - 1,
days_in_year(self.year() - 1),
))
// Safety: `ordinal` is not zero.
Some(unsafe {
Self::__from_ordinal_date_unchecked(self.year() - 1, days_in_year(self.year() - 1))
})
}
}

Expand Down Expand Up @@ -1035,11 +1062,15 @@ impl Date {

// Dates in January and February are unaffected by leap years.
if ordinal <= 59 {
return Ok(Self::__from_ordinal_date_unchecked(year, ordinal));
// Safety: `ordinal` is not zero.
return Ok(unsafe { Self::__from_ordinal_date_unchecked(year, ordinal) });
}

match (is_leap_year(self.year()), is_leap_year(year)) {
(false, false) | (true, true) => Ok(Self::__from_ordinal_date_unchecked(year, ordinal)),
(false, false) | (true, true) => {
// Safety: `ordinal` is not zero.
Ok(unsafe { Self::__from_ordinal_date_unchecked(year, ordinal) })
}
// February 29 does not exist in common years.
(true, false) if ordinal == 60 => Err(error::ComponentRange {
name: "day",
Expand All @@ -1050,10 +1081,12 @@ impl Date {
}),
// We're going from a common year to a leap year. Shift dates in March and later by
// one day.
(false, true) => Ok(Self::__from_ordinal_date_unchecked(year, ordinal + 1)),
// Safety: `ordinal` is not zero.
(false, true) => Ok(unsafe { Self::__from_ordinal_date_unchecked(year, ordinal + 1) }),
// We're going from a leap year to a common year. Shift dates in January and
// February by one day.
(true, false) => Ok(Self::__from_ordinal_date_unchecked(year, ordinal - 1)),
// Safety: `ordinal` is not zero.
(true, false) => Ok(unsafe { Self::__from_ordinal_date_unchecked(year, ordinal - 1) }),
}
}

Expand Down Expand Up @@ -1105,10 +1138,13 @@ impl Date {
}
}

Ok(Self::__from_ordinal_date_unchecked(
self.year(),
(self.ordinal() as i16 - self.day() as i16 + day as i16) as _,
))
// Safety: `ordinal` is not zero.
Ok(unsafe {
Self::__from_ordinal_date_unchecked(
self.year(),
(self.ordinal() as i16 - self.day() as i16 + day as i16) as _,
)
})
}
// endregion replacement
}
Expand Down
11 changes: 8 additions & 3 deletions time/src/date_time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,10 @@ pub(crate) const fn maybe_offset_from_offset<O: MaybeOffset>(
// endregion const trait methods hacks

/// The Julian day of the Unix epoch.
const UNIX_EPOCH_JULIAN_DAY: i32 = Date::__from_ordinal_date_unchecked(1970, 1).to_julian_day();
// Safety: `ordinal` is not zero.
#[allow(clippy::undocumented_unsafe_blocks)]
const UNIX_EPOCH_JULIAN_DAY: i32 =
unsafe { Date::__from_ordinal_date_unchecked(1970, 1) }.to_julian_day();

pub struct DateTime<O: MaybeOffset> {
pub(crate) date: Date,
Expand Down Expand Up @@ -232,7 +235,8 @@ impl DateTime<offset_kind::None> {

impl DateTime<offset_kind::Fixed> {
pub const UNIX_EPOCH: Self = Self {
date: Date::__from_ordinal_date_unchecked(1970, 1),
// Safety: `ordinal` is not zero.
date: unsafe { Date::__from_ordinal_date_unchecked(1970, 1) },
time: Time::MIDNIGHT,
offset: UtcOffset::UTC,
};
Expand Down Expand Up @@ -534,7 +538,8 @@ impl<O: MaybeOffset> DateTime<O> {
}

Some(DateTime {
date: Date::__from_ordinal_date_unchecked(year, ordinal),
// Safety: `ordinal` is not zero.
date: unsafe { Date::__from_ordinal_date_unchecked(year, ordinal) },
time,
offset,
})
Expand Down
3 changes: 2 additions & 1 deletion time/src/parsing/parsed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -736,7 +736,8 @@ impl TryFrom<Parsed> for Date {
/// Get the value needed to adjust the ordinal day for Sunday and Monday-based week
/// numbering.
const fn adjustment(year: i32) -> i16 {
match Date::__from_ordinal_date_unchecked(year, 1).weekday() {
// Safety: `ordinal` is not zero.
match unsafe { Date::__from_ordinal_date_unchecked(year, 1) }.weekday() {
Weekday::Monday => 7,
Weekday::Tuesday => 1,
Weekday::Wednesday => 2,
Expand Down

0 comments on commit 313f980

Please sign in to comment.