Skip to content

Commit

Permalink
Make more functions const fn
Browse files Browse the repository at this point in the history
Some of these need some small hacks, but it's worth the benefit. Aside
from formatting (which requires allocation) and arithmetic (which
requires const trait impls), just about everything can now be done at
compile time.
  • Loading branch information
jhpratt committed Sep 20, 2020
1 parent d2da516 commit 71b7fc1
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 59 deletions.
21 changes: 18 additions & 3 deletions CHANGELOG.md
Expand Up @@ -11,9 +11,24 @@ Versioning].

### Changed

Implementation details of some error types have been exposed. This means that
data about a component being out of range can be directly obtained, while an
invalid offset or conversion error is guaranteed to be a zero-sized type.
- Implementation details of some error types have been exposed. This means that
data about a component being out of range can be directly obtained, while an
invalid offset or conversion error is guaranteed to be a zero-sized type.
- The following functions are `const fn` on rustc ≥ 1.46:
- `Date::try_from_iso_ywd`
- `Date::iso_year_week`
- `Date::week`
- `Date::sunday_based_week`
- `Date::monday_based_week`
- `Date::try_with_hms`
- `Date::try_with_hms_milli`
- `Date::try_with_hms_micro`
- `Date::try_with_hms_nano`
- `PrimitiveDateTime::iso_year_week`
- `PrimitiveDateTime::week`
- `PrimitiveDateTime::sunday_based_week`
- `PrimitiveDateTime::monday_based_week`
- `util::weeks_in_year`

## 0.2.20 [2019-09-16]

Expand Down
141 changes: 101 additions & 40 deletions src/date.rs
Expand Up @@ -246,7 +246,10 @@ impl Date {
/// # use time::{Date, Weekday::*};
/// assert!(Date::try_from_iso_ywd(2019, 53, Monday).is_err()); // 2019 doesn't have 53 weeks.
/// ```
pub fn try_from_iso_ywd(
///
/// This function is `const fn` when using rustc >= 1.46.
#[const_fn("1.46")]
pub const fn try_from_iso_ywd(
year: i32,
week: u8,
weekday: Weekday,
Expand Down Expand Up @@ -407,16 +410,19 @@ impl Date {
/// assert_eq!(date!(2020-12-31).iso_year_week(), (2020, 53));
/// assert_eq!(date!(2021-01-01).iso_year_week(), (2020, 53));
/// ```
pub fn iso_year_week(self) -> (i32, u8) {
///
/// This function is `const fn` when using rustc >= 1.46.
#[const_fn("1.46")]
pub const fn iso_year_week(self) -> (i32, u8) {
let (year, ordinal) = self.as_yo();

let weekday = self.weekday();
let week = ((ordinal + 10 - weekday.iso_weekday_number() as u16) / 7) as u8;

match week {
match ((ordinal + 10 - self.iso_weekday_number() as u16) / 7) as u8 {
0 => (year - 1, weeks_in_year(year - 1)),
53 if weeks_in_year(year) == 52 => (year + 1, 1),
_ => (year, week),
_ => (
year,
((ordinal + 10 - self.iso_weekday_number() as u16) / 7) as u8,
),
}
}

Expand All @@ -432,7 +438,10 @@ impl Date {
/// assert_eq!(date!(2020-12-31).week(), 53);
/// assert_eq!(date!(2021-01-01).week(), 53);
/// ```
pub fn week(self) -> u8 {
///
/// This function is `const fn` when using rustc >= 1.46.
#[const_fn("1.46")]
pub const fn week(self) -> u8 {
self.iso_year_week().1
}

Expand All @@ -447,8 +456,11 @@ impl Date {
/// assert_eq!(date!(2020-12-31).sunday_based_week(), 52);
/// assert_eq!(date!(2021-01-01).sunday_based_week(), 0);
/// ```
pub fn sunday_based_week(self) -> u8 {
((self.ordinal() as i16 - self.weekday().number_days_from_sunday() as i16 + 6) / 7) as u8
///
/// This function is `const fn` when using rustc >= 1.46.
#[const_fn("1.46")]
pub const fn sunday_based_week(self) -> u8 {
((self.ordinal() as i16 - self.number_days_from_sunday() as i16 + 6) / 7) as u8
}

/// Get the week number where week 1 begins on the first Monday.
Expand All @@ -462,8 +474,11 @@ impl Date {
/// assert_eq!(date!(2020-12-31).monday_based_week(), 52);
/// assert_eq!(date!(2021-01-01).monday_based_week(), 0);
/// ```
pub fn monday_based_week(self) -> u8 {
((self.ordinal() as i16 - self.weekday().number_days_from_monday() as i16 + 6) / 7) as u8
///
/// This function is `const fn` when using rustc >= 1.46.
#[const_fn("1.46")]
pub const fn monday_based_week(self) -> u8 {
((self.ordinal() as i16 - self.number_days_from_monday() as i16 + 6) / 7) as u8
}

/// Get the year, month, and day.
Expand Down Expand Up @@ -494,6 +509,52 @@ impl Date {
(self.year(), self.ordinal())
}

/// Get the ISO weekday number.
///
/// This is equivalent to calling `.weekday().iso_weekday_number()`, but is
/// usable in `const` contexts.
#[const_fn("1.46")]
pub(crate) const fn iso_weekday_number(self) -> u8 {
self.number_days_from_monday() + 1
}

/// Get the number of days from Sunday.
///
/// This is equivalent to calling `.weekday().number_days_from_sunday()`,
/// but is usable in `const` contexts.
#[const_fn("1.46")]
pub(crate) const fn number_days_from_sunday(self) -> u8 {
self.iso_weekday_number() % 7
}

/// Get the number of days from Monday.
///
/// This is equivalent to calling `.weekday().number_days_from_monday()`,
/// but is usable in `const` contexts.
#[const_fn("1.46")]
pub(crate) const fn number_days_from_monday(self) -> u8 {
let (year, month, day) = self.as_ymd();

let (month, adjusted_year) = if month < 3 {
(month + 12, year - 1)
} else {
(month, year)
};

let raw_weekday =
(day as i32 + (13 * (month as i32 + 1)) / 5 + adjusted_year + adjusted_year / 4
- adjusted_year / 100
+ adjusted_year / 400)
% 7
- 2;

if raw_weekday < 0 {
(raw_weekday + 7) as u8
} else {
raw_weekday as u8
}
}

/// Get the weekday.
///
/// This current uses [Zeller's congruence](https://en.wikipedia.org/wiki/Zeller%27s_congruence)
Expand All @@ -515,26 +576,14 @@ impl Date {
/// assert_eq!(date!(2019-12-01).weekday(), Sunday);
/// ```
pub fn weekday(self) -> Weekday {
let (year, month, day) = self.as_ymd();

let (month, adjusted_year) = if month < 3 {
(month + 12, year - 1)
} else {
(month, year)
};

match (day as i32 + (13 * (month as i32 + 1)) / 5 + adjusted_year + adjusted_year / 4
- adjusted_year / 100
+ adjusted_year / 400)
.rem_euclid(7)
{
0 => Weekday::Saturday,
1 => Weekday::Sunday,
2 => Weekday::Monday,
3 => Weekday::Tuesday,
4 => Weekday::Wednesday,
5 => Weekday::Thursday,
6 => Weekday::Friday,
match self.number_days_from_monday() {
0 => Weekday::Monday,
1 => Weekday::Tuesday,
2 => Weekday::Wednesday,
3 => Weekday::Thursday,
4 => Weekday::Friday,
5 => Weekday::Saturday,
6 => Weekday::Sunday,
// FIXME The compiler isn't able to optimize this away. See
// rust-lang/rust#66993.
_ => unreachable!("A value mod 7 is always in the range 0..7"),
Expand Down Expand Up @@ -723,15 +772,18 @@ impl Date {
/// assert!(date!(1970-01-01).try_with_hms(0, 0, 0).is_ok());
/// assert!(date!(1970-01-01).try_with_hms(24, 0, 0).is_err());
/// ```
pub fn try_with_hms(
///
/// This function is `const fn` when using rustc >= 1.46.
#[const_fn("1.46")]
pub const fn try_with_hms(
self,
hour: u8,
minute: u8,
second: u8,
) -> Result<PrimitiveDateTime, error::ComponentRange> {
Ok(PrimitiveDateTime::new(
self,
Time::try_from_hms(hour, minute, second)?,
const_try!(Time::try_from_hms(hour, minute, second)),
))
}

Expand Down Expand Up @@ -774,7 +826,10 @@ impl Date {
/// assert!(date!(1970-01-01).try_with_hms_milli(0, 0, 0, 0).is_ok());
/// assert!(date!(1970-01-01).try_with_hms_milli(24, 0, 0, 0).is_err());
/// ```
pub fn try_with_hms_milli(
///
/// This function is `const fn` when using rustc >= 1.46.
#[const_fn("1.46")]
pub const fn try_with_hms_milli(
self,
hour: u8,
minute: u8,
Expand All @@ -783,7 +838,7 @@ impl Date {
) -> Result<PrimitiveDateTime, error::ComponentRange> {
Ok(PrimitiveDateTime::new(
self,
Time::try_from_hms_milli(hour, minute, second, millisecond)?,
const_try!(Time::try_from_hms_milli(hour, minute, second, millisecond)),
))
}

Expand Down Expand Up @@ -830,7 +885,10 @@ impl Date {
/// .try_with_hms_micro(24, 0, 0, 0)
/// .is_err());
/// ```
pub fn try_with_hms_micro(
///
/// This function is `const fn` when using rustc >= 1.46.
#[const_fn("1.46")]
pub const fn try_with_hms_micro(
self,
hour: u8,
minute: u8,
Expand All @@ -839,7 +897,7 @@ impl Date {
) -> Result<PrimitiveDateTime, error::ComponentRange> {
Ok(PrimitiveDateTime::new(
self,
Time::try_from_hms_micro(hour, minute, second, microsecond)?,
const_try!(Time::try_from_hms_micro(hour, minute, second, microsecond)),
))
}

Expand Down Expand Up @@ -878,7 +936,10 @@ impl Date {
/// assert!(date!(1970-01-01).try_with_hms_nano(0, 0, 0, 0).is_ok());
/// assert!(date!(1970-01-01).try_with_hms_nano(24, 0, 0, 0).is_err());
/// ```
pub fn try_with_hms_nano(
///
/// This function is `const fn` when using rustc >= 1.46.
#[const_fn("1.46")]
pub const fn try_with_hms_nano(
self,
hour: u8,
minute: u8,
Expand All @@ -887,7 +948,7 @@ impl Date {
) -> Result<PrimitiveDateTime, error::ComponentRange> {
Ok(PrimitiveDateTime::new(
self,
Time::try_from_hms_nano(hour, minute, second, nanosecond)?,
const_try!(Time::try_from_hms_nano(hour, minute, second, nanosecond)),
))
}
}
Expand Down
31 changes: 23 additions & 8 deletions src/internals.rs
Expand Up @@ -11,6 +11,7 @@
#![doc(hidden)]
#![allow(missing_debug_implementations, missing_copy_implementations)]

use const_fn::const_fn;
use crate::{days_in_year, is_leap_year, Weekday};

pub struct Time;
Expand Down Expand Up @@ -59,15 +60,17 @@ impl Date {
}

// reduce duplication
pub(crate) fn from_iso_ywd_unchecked(year: i32, week: u8, weekday: Weekday) -> crate::Date {
let ordinal = week as u16 * 7 + weekday.iso_weekday_number() as u16
- (Self::from_yo_unchecked(year, 4)
.weekday()
.iso_weekday_number() as u16
+ 3);
#[const_fn("1.46")]
pub(crate) const fn from_iso_ywd_unchecked(
year: i32,
week: u8,
weekday: Weekday,
) -> crate::Date {
let (ordinal, overflow) = (week as u16 * 7 + weekday.iso_weekday_number() as u16)
.overflowing_sub(jan_weekday(year, 4) as u16 + 4);

if ordinal < 1 {
return Self::from_yo_unchecked(year - 1, ordinal + days_in_year(year - 1));
if overflow || ordinal == 0 {
return Self::from_yo_unchecked(year - 1, ordinal.wrapping_add(days_in_year(year - 1)));
}

let days_in_cur_year = days_in_year(year);
Expand All @@ -78,3 +81,15 @@ impl Date {
}
}
}

/// Obtain the ISO weekday number of a day in January.
#[const_fn("1.46")]
pub(crate) const fn jan_weekday(year: i32, ordinal: i32) -> u8 {
let adj_year = year - 1;
let rem = (ordinal + adj_year + adj_year / 4 - adj_year / 100 + adj_year / 400 + 6) % 7;
if rem < 0 {
(rem + 7) as u8
} else {
rem as u8
}
}
13 changes: 13 additions & 0 deletions src/lib.rs
Expand Up @@ -264,6 +264,19 @@ macro_rules! ensure_value_in_range {
}};
}

/// Try to unwrap an expression, returning if not possible.
///
/// This is similar to the `?` operator, but does not perform `.into()`. Because
/// of this, it is usable in `const` contexts.
macro_rules! const_try {
($e:expr) => {
match $e {
Ok(value) => value,
Err(error) => return Err(error),
}
};
}

/// The `Date` struct and its associated `impl`s.
mod date;
/// The `Duration` struct and its associated `impl`s.
Expand Down
20 changes: 16 additions & 4 deletions src/primitive_date_time.rs
Expand Up @@ -251,7 +251,10 @@ impl PrimitiveDateTime {
/// assert_eq!(date!(2020-12-31).midnight().iso_year_week(), (2020, 53));
/// assert_eq!(date!(2021-01-01).midnight().iso_year_week(), (2020, 53));
/// ```
pub fn iso_year_week(self) -> (i32, u8) {
///
/// This function is `const fn` when using rustc >= 1.46.
#[const_fn("1.46")]
pub const fn iso_year_week(self) -> (i32, u8) {
self.date().iso_year_week()
}

Expand All @@ -267,7 +270,10 @@ impl PrimitiveDateTime {
/// assert_eq!(date!(2020-12-31).midnight().week(), 53);
/// assert_eq!(date!(2021-01-01).midnight().week(), 53);
/// ```
pub fn week(self) -> u8 {
///
/// This function is `const fn` when using rustc >= 1.46.
#[const_fn("1.46")]
pub const fn week(self) -> u8 {
self.date().week()
}

Expand All @@ -282,7 +288,10 @@ impl PrimitiveDateTime {
/// assert_eq!(date!(2020-12-31).midnight().sunday_based_week(), 52);
/// assert_eq!(date!(2021-01-01).midnight().sunday_based_week(), 0);
/// ```
pub fn sunday_based_week(self) -> u8 {
///
/// This function is `const fn` when using rustc >= 1.46.
#[const_fn("1.46")]
pub const fn sunday_based_week(self) -> u8 {
self.date().sunday_based_week()
}

Expand All @@ -297,7 +306,10 @@ impl PrimitiveDateTime {
/// assert_eq!(date!(2020-12-31).midnight().monday_based_week(), 52);
/// assert_eq!(date!(2021-01-01).midnight().monday_based_week(), 0);
/// ```
pub fn monday_based_week(self) -> u8 {
///
/// This function is `const fn` when using rustc >= 1.46.
#[const_fn("1.46")]
pub const fn monday_based_week(self) -> u8 {
self.date().monday_based_week()
}

Expand Down

0 comments on commit 71b7fc1

Please sign in to comment.