Skip to content

Commit

Permalink
Put panicking API behind feature flag
Browse files Browse the repository at this point in the history
A number of places were changed to have panics in situations that are
unreachable. These will be replaced with internal methods that don't
perform any checking.
  • Loading branch information
jhpratt committed Dec 13, 2019
1 parent 6913768 commit e19b951
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 24 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ license = "MIT OR Apache-2.0"
default = ["std", "deprecated"]
deprecated = []
std = []
panicking-api = []

[dependencies]
serde = { version = "1", optional = true, default-features = false, features = ["derive", "alloc"] }
59 changes: 45 additions & 14 deletions src/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ pub const fn days_in_year(year: i32) -> u16 {
/// ```
#[inline(always)]
pub fn weeks_in_year(year: i32) -> u8 {
let weekday = Date::from_yo(year, 1).weekday();
let weekday = Date::try_from_yo(year, 1)
.expect("date is always valid")
.weekday();

if (weekday == Thursday) || (weekday == Wednesday && is_leap_year(year)) {
53
Expand Down Expand Up @@ -120,6 +122,8 @@ impl Date {
/// Date::from_ymd(2019, 2, 29); // 2019 isn't a leap year.
/// ```
#[inline]
#[cfg(feature = "panicking-api")]
#[cfg_attr(doc, doc(cfg(feature = "panicking-api")))]
pub fn from_ymd(year: i32, month: u8, day: u8) -> Self {
/// Cumulative days through the beginning of a month in both common and
/// leap years.
Expand Down Expand Up @@ -188,6 +192,8 @@ impl Date {
/// Date::from_yo(2019, 366); // 2019 isn't a leap year.
/// ```
#[inline(always)]
#[cfg(feature = "panicking-api")]
#[cfg_attr(doc, doc(cfg(feature = "panicking-api")))]
pub fn from_yo(year: i32, ordinal: u16) -> Self {
assert_value_in_range!(ordinal in 1 => days_in_year(year), given year);
Self { year, ordinal }
Expand Down Expand Up @@ -238,6 +244,8 @@ impl Date {
/// Date::from_iso_ywd(2019, 53, Monday); // 2019 doesn't have 53 weeks.
/// ```
#[inline]
#[cfg(feature = "panicking-api")]
#[cfg_attr(doc, doc(cfg(feature = "panicking-api")))]
pub fn from_iso_ywd(year: i32, week: u8, weekday: Weekday) -> Self {
assert_value_in_range!(week in 1 => weeks_in_year(year), given year);

Expand Down Expand Up @@ -280,17 +288,21 @@ impl Date {
ensure_value_in_range!(week in 1 => weeks_in_year(year), given year);

let ordinal = week as u16 * 7 + weekday.iso_weekday_number() as u16
- (Self::from_yo(year, 4).weekday().iso_weekday_number() as u16 + 3);
- (Self::try_from_yo(year, 4)
.expect("date is always valid")
.weekday()
.iso_weekday_number() as u16
+ 3);

if ordinal < 1 {
return Ok(Self::from_yo(year - 1, ordinal + days_in_year(year - 1)));
return Self::try_from_yo(year - 1, ordinal + days_in_year(year - 1));
}

let days_in_cur_year = days_in_year(year);
if ordinal > days_in_cur_year {
Ok(Self::from_yo(year + 1, ordinal - days_in_cur_year))
Self::try_from_yo(year + 1, ordinal - days_in_cur_year)
} else {
Ok(Self::from_yo(year, ordinal))
Self::try_from_yo(year, ordinal)
}
}

Expand Down Expand Up @@ -362,6 +374,7 @@ impl Date {
/// assert_eq!(Date::from_ymd(2019, 1, 1).month_day(), (1, 1));
/// assert_eq!(Date::from_ymd(2019, 12, 31).month_day(), (12, 31));
/// ```
// TODO Refactor to prove to the compiler that this can't panic.
#[inline]
pub fn month_day(self) -> (u8, u8) {
let mut ordinal = self.ordinal;
Expand Down Expand Up @@ -662,7 +675,7 @@ impl Date {
let year = (e / P) - Y + (N + M - month) / N;

#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
Self::from_ymd(year as i32, month as u8, day as u8)
Self::try_from_ymd(year as i32, month as u8, day as u8).expect("date is always valid")
}
}

Expand Down Expand Up @@ -707,6 +720,8 @@ impl Date {
/// );
/// ```
#[inline(always)]
#[cfg(feature = "panicking-api")]
#[cfg_attr(doc, doc(cfg(feature = "panicking-api")))]
pub fn with_hms(self, hour: u8, minute: u8, second: u8) -> PrimitiveDateTime {
PrimitiveDateTime::new(self, Time::from_hms(hour, minute, second))
}
Expand Down Expand Up @@ -741,6 +756,8 @@ impl Date {
/// );
/// ```
#[inline(always)]
#[cfg(feature = "panicking-api")]
#[cfg_attr(doc, doc(cfg(feature = "panicking-api")))]
pub fn with_hms_milli(
self,
hour: u8,
Expand Down Expand Up @@ -789,6 +806,8 @@ impl Date {
/// );
/// ```
#[inline(always)]
#[cfg(feature = "panicking-api")]
#[cfg_attr(doc, doc(cfg(feature = "panicking-api")))]
pub fn with_hms_micro(
self,
hour: u8,
Expand Down Expand Up @@ -837,6 +856,8 @@ impl Date {
/// );
/// ```
#[inline(always)]
#[cfg(feature = "panicking-api")]
#[cfg_attr(doc, doc(cfg(feature = "panicking-api")))]
pub fn with_hms_nano(
self,
hour: u8,
Expand Down Expand Up @@ -924,7 +945,10 @@ impl Date {
/// Monday-based week numbering.
#[inline(always)]
fn adjustment(year: i32) -> i16 {
match Date::from_yo(year, 1).weekday() {
match Date::try_from_yo(year, 1)
.expect("date is always valid")
.weekday()
{
Monday => 7,
Tuesday => 1,
Wednesday => 2,
Expand All @@ -936,29 +960,36 @@ impl Date {
}

match items {
items!(year, month, day) => Ok(Self::from_ymd(year, month.get(), day.get())),
items!(year, ordinal_day) => Ok(Self::from_yo(year, ordinal_day.get())),
items!(year, month, day) => Ok(Self::try_from_ymd(year, month.get(), day.get())
.expect("components are checked when parsing")),
items!(year, ordinal_day) => Ok(Self::try_from_yo(year, ordinal_day.get())
.expect("components are checked when parsing")),
items!(week_based_year, iso_week, weekday) => {
Ok(Self::from_iso_ywd(week_based_year, iso_week.get(), weekday))
Ok(
Self::try_from_iso_ywd(week_based_year, iso_week.get(), weekday)
.expect("components are checked when parsing"),
)
}
items!(year, sunday_week, weekday) => Ok(Self::from_yo(
items!(year, sunday_week, weekday) => Ok(Self::try_from_yo(
year,
#[allow(clippy::cast_sign_loss)]
{
(sunday_week as i16 * 7 + weekday.number_days_from_sunday() as i16
- adjustment(year)
+ 1) as u16
},
)),
items!(year, monday_week, weekday) => Ok(Self::from_yo(
)
.expect("components are checked when parsing")),
items!(year, monday_week, weekday) => Ok(Self::try_from_yo(
year,
#[allow(clippy::cast_sign_loss)]
{
(monday_week as i16 * 7 + weekday.number_days_from_monday() as i16
- adjustment(year)
+ 1) as u16
},
)),
)
.expect("components are checked when parsing")),
_ => Err(ParseError::InsufficientInformation),
}
}
Expand Down
18 changes: 18 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,20 @@
//! time = { version = "0.2", default-features = false, features = ["deprecated"] }
//! ```
//!
//! ## `panicking-api`
//!
//! Non-panicking APIs are provided, and should generally be preferred. However,
//! there are some situations where avoiding `.unwrap()` may be desired. To
//! enable these APIs, you need to use the `panicking-api` feature in your
//! `Cargo.toml`, which is not enabled by default.
//!
//! Library authors should avoid using this feature.
//!
//! ```toml
//! [dependencies]
//! time = { version = "0.2", features = ["panicking-api"] }
//! ```
//!
//! # Formatting
//!
//! Time's formatting behavior is based on `strftime` in C, though it is
Expand Down Expand Up @@ -156,6 +170,8 @@
#[macro_use]
extern crate alloc;

#[cfg(feature = "panicking-api")]
#[cfg_attr(doc, doc(cfg(feature = "panicking-api")))]
macro_rules! format_conditional {
($conditional:ident) => {
format!(concat!(stringify!($conditional), "={}"), $conditional)
Expand All @@ -170,6 +186,8 @@ macro_rules! format_conditional {
}

/// Panic if the value is not in range.
#[cfg(feature = "panicking-api")]
#[cfg_attr(doc, doc(cfg(feature = "panicking-api")))]
macro_rules! assert_value_in_range {
($value:ident in $start:expr => $end:expr) => {
if !($start..=$end).contains(&$value) {
Expand Down
2 changes: 2 additions & 0 deletions src/primitive_date_time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,8 @@ impl Ord for PrimitiveDateTime {

#[cfg(feature = "std")]
impl From<SystemTime> for PrimitiveDateTime {
// There is definitely some way to have this conversion be infallible, but
// it won't be an issue for over 500 years.
#[inline(always)]
fn from(system_time: SystemTime) -> Self {
let duration = match system_time.duration_since(SystemTime::UNIX_EPOCH) {
Expand Down
35 changes: 25 additions & 10 deletions src/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ impl Time {
/// Time::from_hms(0, 0, 60); // 60 isn't a valid second.
/// ```
#[inline(always)]
#[cfg(feature = "panicking-api")]
#[cfg_attr(doc, doc(cfg(feature = "panicking-api")))]
pub fn from_hms(hour: u8, minute: u8, second: u8) -> Self {
assert_value_in_range!(hour in 0 => exclusive 24);
assert_value_in_range!(minute in 0 => exclusive 60);
Expand Down Expand Up @@ -159,6 +161,8 @@ impl Time {
/// Time::from_hms_milli(0, 0, 0, 1_000); // 1_000 isn't a valid millisecond.
/// ```
#[inline(always)]
#[cfg(feature = "panicking-api")]
#[cfg_attr(doc, doc(cfg(feature = "panicking-api")))]
pub fn from_hms_milli(hour: u8, minute: u8, second: u8, millisecond: u16) -> Self {
assert_value_in_range!(hour in 0 => exclusive 24);
assert_value_in_range!(minute in 0 => exclusive 60);
Expand Down Expand Up @@ -241,6 +245,8 @@ impl Time {
/// Time::from_hms_micro(0, 0, 0, 1_000_000); // 1_000_000 isn't a valid microsecond.
/// ```
#[inline(always)]
#[cfg(feature = "panicking-api")]
#[cfg_attr(doc, doc(cfg(feature = "panicking-api")))]
pub fn from_hms_micro(hour: u8, minute: u8, second: u8, microsecond: u32) -> Self {
assert_value_in_range!(hour in 0 => exclusive 24);
assert_value_in_range!(minute in 0 => exclusive 60);
Expand Down Expand Up @@ -322,6 +328,8 @@ impl Time {
/// Time::from_hms_nano(0, 0, 0, 1_000_000_000); // 1_000_000_000 isn't a valid nanosecond.
/// ```
#[inline(always)]
#[cfg(feature = "panicking-api")]
#[cfg_attr(doc, doc(cfg(feature = "panicking-api")))]
pub fn from_hms_nano(hour: u8, minute: u8, second: u8, nanosecond: u32) -> Self {
assert_value_in_range!(hour in 0 => exclusive 24);
assert_value_in_range!(minute in 0 => exclusive 60);
Expand Down Expand Up @@ -567,18 +575,25 @@ impl Time {
}

match items {
items!(hour_24, minute, second) => Ok(Self::from_hms(hour_24, minute, second)),
items!(hour_12, minute, second, am_pm) => Ok(Self::from_hms(
hour_12_to_24(hour_12, am_pm),
minute,
second,
)),
items!(hour_24, minute) => Ok(Self::from_hms(hour_24, minute, 0)),
items!(hour_24, minute, second) => Ok(Self::try_from_hms(hour_24, minute, second)
.expect("components are checked when parsing")),
items!(hour_12, minute, second, am_pm) => {
Ok(
Self::try_from_hms(hour_12_to_24(hour_12, am_pm), minute, second)
.expect("components are checked when parsing"),
)
}
items!(hour_24, minute) => Ok(Self::try_from_hms(hour_24, minute, 0)
.expect("components are checked when parsing")),
items!(hour_12, minute, am_pm) => {
Ok(Self::from_hms(hour_12_to_24(hour_12, am_pm), minute, 0))
Ok(Self::try_from_hms(hour_12_to_24(hour_12, am_pm), minute, 0)
.expect("components are checked when parsing"))
}
items!(hour_24) => {
Ok(Self::try_from_hms(hour_24, 0, 0).expect("components are checked when parsing"))
}
items!(hour_24) => Ok(Self::from_hms(hour_24, 0, 0)),
items!(hour_12, am_pm) => Ok(Self::from_hms(hour_12_to_24(hour_12, am_pm), 0, 0)),
items!(hour_12, am_pm) => Ok(Self::try_from_hms(hour_12_to_24(hour_12, am_pm), 0, 0)
.expect("components are checked when parsing")),
_ => Err(ParseError::InsufficientInformation),
}
}
Expand Down

0 comments on commit e19b951

Please sign in to comment.