Skip to content

Commit e19b951

Browse files
committed
Put panicking API behind feature flag
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.
1 parent 6913768 commit e19b951

File tree

5 files changed

+91
-24
lines changed

5 files changed

+91
-24
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ license = "MIT OR Apache-2.0"
1414
default = ["std", "deprecated"]
1515
deprecated = []
1616
std = []
17+
panicking-api = []
1718

1819
[dependencies]
1920
serde = { version = "1", optional = true, default-features = false, features = ["derive", "alloc"] }

src/date.rs

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ pub const fn days_in_year(year: i32) -> u16 {
7272
/// ```
7373
#[inline(always)]
7474
pub fn weeks_in_year(year: i32) -> u8 {
75-
let weekday = Date::from_yo(year, 1).weekday();
75+
let weekday = Date::try_from_yo(year, 1)
76+
.expect("date is always valid")
77+
.weekday();
7678

7779
if (weekday == Thursday) || (weekday == Wednesday && is_leap_year(year)) {
7880
53
@@ -120,6 +122,8 @@ impl Date {
120122
/// Date::from_ymd(2019, 2, 29); // 2019 isn't a leap year.
121123
/// ```
122124
#[inline]
125+
#[cfg(feature = "panicking-api")]
126+
#[cfg_attr(doc, doc(cfg(feature = "panicking-api")))]
123127
pub fn from_ymd(year: i32, month: u8, day: u8) -> Self {
124128
/// Cumulative days through the beginning of a month in both common and
125129
/// leap years.
@@ -188,6 +192,8 @@ impl Date {
188192
/// Date::from_yo(2019, 366); // 2019 isn't a leap year.
189193
/// ```
190194
#[inline(always)]
195+
#[cfg(feature = "panicking-api")]
196+
#[cfg_attr(doc, doc(cfg(feature = "panicking-api")))]
191197
pub fn from_yo(year: i32, ordinal: u16) -> Self {
192198
assert_value_in_range!(ordinal in 1 => days_in_year(year), given year);
193199
Self { year, ordinal }
@@ -238,6 +244,8 @@ impl Date {
238244
/// Date::from_iso_ywd(2019, 53, Monday); // 2019 doesn't have 53 weeks.
239245
/// ```
240246
#[inline]
247+
#[cfg(feature = "panicking-api")]
248+
#[cfg_attr(doc, doc(cfg(feature = "panicking-api")))]
241249
pub fn from_iso_ywd(year: i32, week: u8, weekday: Weekday) -> Self {
242250
assert_value_in_range!(week in 1 => weeks_in_year(year), given year);
243251

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

282290
let ordinal = week as u16 * 7 + weekday.iso_weekday_number() as u16
283-
- (Self::from_yo(year, 4).weekday().iso_weekday_number() as u16 + 3);
291+
- (Self::try_from_yo(year, 4)
292+
.expect("date is always valid")
293+
.weekday()
294+
.iso_weekday_number() as u16
295+
+ 3);
284296

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

289301
let days_in_cur_year = days_in_year(year);
290302
if ordinal > days_in_cur_year {
291-
Ok(Self::from_yo(year + 1, ordinal - days_in_cur_year))
303+
Self::try_from_yo(year + 1, ordinal - days_in_cur_year)
292304
} else {
293-
Ok(Self::from_yo(year, ordinal))
305+
Self::try_from_yo(year, ordinal)
294306
}
295307
}
296308

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

664677
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
665-
Self::from_ymd(year as i32, month as u8, day as u8)
678+
Self::try_from_ymd(year as i32, month as u8, day as u8).expect("date is always valid")
666679
}
667680
}
668681

@@ -707,6 +720,8 @@ impl Date {
707720
/// );
708721
/// ```
709722
#[inline(always)]
723+
#[cfg(feature = "panicking-api")]
724+
#[cfg_attr(doc, doc(cfg(feature = "panicking-api")))]
710725
pub fn with_hms(self, hour: u8, minute: u8, second: u8) -> PrimitiveDateTime {
711726
PrimitiveDateTime::new(self, Time::from_hms(hour, minute, second))
712727
}
@@ -741,6 +756,8 @@ impl Date {
741756
/// );
742757
/// ```
743758
#[inline(always)]
759+
#[cfg(feature = "panicking-api")]
760+
#[cfg_attr(doc, doc(cfg(feature = "panicking-api")))]
744761
pub fn with_hms_milli(
745762
self,
746763
hour: u8,
@@ -789,6 +806,8 @@ impl Date {
789806
/// );
790807
/// ```
791808
#[inline(always)]
809+
#[cfg(feature = "panicking-api")]
810+
#[cfg_attr(doc, doc(cfg(feature = "panicking-api")))]
792811
pub fn with_hms_micro(
793812
self,
794813
hour: u8,
@@ -837,6 +856,8 @@ impl Date {
837856
/// );
838857
/// ```
839858
#[inline(always)]
859+
#[cfg(feature = "panicking-api")]
860+
#[cfg_attr(doc, doc(cfg(feature = "panicking-api")))]
840861
pub fn with_hms_nano(
841862
self,
842863
hour: u8,
@@ -924,7 +945,10 @@ impl Date {
924945
/// Monday-based week numbering.
925946
#[inline(always)]
926947
fn adjustment(year: i32) -> i16 {
927-
match Date::from_yo(year, 1).weekday() {
948+
match Date::try_from_yo(year, 1)
949+
.expect("date is always valid")
950+
.weekday()
951+
{
928952
Monday => 7,
929953
Tuesday => 1,
930954
Wednesday => 2,
@@ -936,29 +960,36 @@ impl Date {
936960
}
937961

938962
match items {
939-
items!(year, month, day) => Ok(Self::from_ymd(year, month.get(), day.get())),
940-
items!(year, ordinal_day) => Ok(Self::from_yo(year, ordinal_day.get())),
963+
items!(year, month, day) => Ok(Self::try_from_ymd(year, month.get(), day.get())
964+
.expect("components are checked when parsing")),
965+
items!(year, ordinal_day) => Ok(Self::try_from_yo(year, ordinal_day.get())
966+
.expect("components are checked when parsing")),
941967
items!(week_based_year, iso_week, weekday) => {
942-
Ok(Self::from_iso_ywd(week_based_year, iso_week.get(), weekday))
968+
Ok(
969+
Self::try_from_iso_ywd(week_based_year, iso_week.get(), weekday)
970+
.expect("components are checked when parsing"),
971+
)
943972
}
944-
items!(year, sunday_week, weekday) => Ok(Self::from_yo(
973+
items!(year, sunday_week, weekday) => Ok(Self::try_from_yo(
945974
year,
946975
#[allow(clippy::cast_sign_loss)]
947976
{
948977
(sunday_week as i16 * 7 + weekday.number_days_from_sunday() as i16
949978
- adjustment(year)
950979
+ 1) as u16
951980
},
952-
)),
953-
items!(year, monday_week, weekday) => Ok(Self::from_yo(
981+
)
982+
.expect("components are checked when parsing")),
983+
items!(year, monday_week, weekday) => Ok(Self::try_from_yo(
954984
year,
955985
#[allow(clippy::cast_sign_loss)]
956986
{
957987
(monday_week as i16 * 7 + weekday.number_days_from_monday() as i16
958988
- adjustment(year)
959989
+ 1) as u16
960990
},
961-
)),
991+
)
992+
.expect("components are checked when parsing")),
962993
_ => Err(ParseError::InsufficientInformation),
963994
}
964995
}

src/lib.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,20 @@
5252
//! time = { version = "0.2", default-features = false, features = ["deprecated"] }
5353
//! ```
5454
//!
55+
//! ## `panicking-api`
56+
//!
57+
//! Non-panicking APIs are provided, and should generally be preferred. However,
58+
//! there are some situations where avoiding `.unwrap()` may be desired. To
59+
//! enable these APIs, you need to use the `panicking-api` feature in your
60+
//! `Cargo.toml`, which is not enabled by default.
61+
//!
62+
//! Library authors should avoid using this feature.
63+
//!
64+
//! ```toml
65+
//! [dependencies]
66+
//! time = { version = "0.2", features = ["panicking-api"] }
67+
//! ```
68+
//!
5569
//! # Formatting
5670
//!
5771
//! Time's formatting behavior is based on `strftime` in C, though it is
@@ -156,6 +170,8 @@
156170
#[macro_use]
157171
extern crate alloc;
158172

173+
#[cfg(feature = "panicking-api")]
174+
#[cfg_attr(doc, doc(cfg(feature = "panicking-api")))]
159175
macro_rules! format_conditional {
160176
($conditional:ident) => {
161177
format!(concat!(stringify!($conditional), "={}"), $conditional)
@@ -170,6 +186,8 @@ macro_rules! format_conditional {
170186
}
171187

172188
/// Panic if the value is not in range.
189+
#[cfg(feature = "panicking-api")]
190+
#[cfg_attr(doc, doc(cfg(feature = "panicking-api")))]
173191
macro_rules! assert_value_in_range {
174192
($value:ident in $start:expr => $end:expr) => {
175193
if !($start..=$end).contains(&$value) {

src/primitive_date_time.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,8 @@ impl Ord for PrimitiveDateTime {
743743

744744
#[cfg(feature = "std")]
745745
impl From<SystemTime> for PrimitiveDateTime {
746+
// There is definitely some way to have this conversion be infallible, but
747+
// it won't be an issue for over 500 years.
746748
#[inline(always)]
747749
fn from(system_time: SystemTime) -> Self {
748750
let duration = match system_time.duration_since(SystemTime::UNIX_EPOCH) {

src/time.rs

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ impl Time {
8585
/// Time::from_hms(0, 0, 60); // 60 isn't a valid second.
8686
/// ```
8787
#[inline(always)]
88+
#[cfg(feature = "panicking-api")]
89+
#[cfg_attr(doc, doc(cfg(feature = "panicking-api")))]
8890
pub fn from_hms(hour: u8, minute: u8, second: u8) -> Self {
8991
assert_value_in_range!(hour in 0 => exclusive 24);
9092
assert_value_in_range!(minute in 0 => exclusive 60);
@@ -159,6 +161,8 @@ impl Time {
159161
/// Time::from_hms_milli(0, 0, 0, 1_000); // 1_000 isn't a valid millisecond.
160162
/// ```
161163
#[inline(always)]
164+
#[cfg(feature = "panicking-api")]
165+
#[cfg_attr(doc, doc(cfg(feature = "panicking-api")))]
162166
pub fn from_hms_milli(hour: u8, minute: u8, second: u8, millisecond: u16) -> Self {
163167
assert_value_in_range!(hour in 0 => exclusive 24);
164168
assert_value_in_range!(minute in 0 => exclusive 60);
@@ -241,6 +245,8 @@ impl Time {
241245
/// Time::from_hms_micro(0, 0, 0, 1_000_000); // 1_000_000 isn't a valid microsecond.
242246
/// ```
243247
#[inline(always)]
248+
#[cfg(feature = "panicking-api")]
249+
#[cfg_attr(doc, doc(cfg(feature = "panicking-api")))]
244250
pub fn from_hms_micro(hour: u8, minute: u8, second: u8, microsecond: u32) -> Self {
245251
assert_value_in_range!(hour in 0 => exclusive 24);
246252
assert_value_in_range!(minute in 0 => exclusive 60);
@@ -322,6 +328,8 @@ impl Time {
322328
/// Time::from_hms_nano(0, 0, 0, 1_000_000_000); // 1_000_000_000 isn't a valid nanosecond.
323329
/// ```
324330
#[inline(always)]
331+
#[cfg(feature = "panicking-api")]
332+
#[cfg_attr(doc, doc(cfg(feature = "panicking-api")))]
325333
pub fn from_hms_nano(hour: u8, minute: u8, second: u8, nanosecond: u32) -> Self {
326334
assert_value_in_range!(hour in 0 => exclusive 24);
327335
assert_value_in_range!(minute in 0 => exclusive 60);
@@ -567,18 +575,25 @@ impl Time {
567575
}
568576

569577
match items {
570-
items!(hour_24, minute, second) => Ok(Self::from_hms(hour_24, minute, second)),
571-
items!(hour_12, minute, second, am_pm) => Ok(Self::from_hms(
572-
hour_12_to_24(hour_12, am_pm),
573-
minute,
574-
second,
575-
)),
576-
items!(hour_24, minute) => Ok(Self::from_hms(hour_24, minute, 0)),
578+
items!(hour_24, minute, second) => Ok(Self::try_from_hms(hour_24, minute, second)
579+
.expect("components are checked when parsing")),
580+
items!(hour_12, minute, second, am_pm) => {
581+
Ok(
582+
Self::try_from_hms(hour_12_to_24(hour_12, am_pm), minute, second)
583+
.expect("components are checked when parsing"),
584+
)
585+
}
586+
items!(hour_24, minute) => Ok(Self::try_from_hms(hour_24, minute, 0)
587+
.expect("components are checked when parsing")),
577588
items!(hour_12, minute, am_pm) => {
578-
Ok(Self::from_hms(hour_12_to_24(hour_12, am_pm), minute, 0))
589+
Ok(Self::try_from_hms(hour_12_to_24(hour_12, am_pm), minute, 0)
590+
.expect("components are checked when parsing"))
591+
}
592+
items!(hour_24) => {
593+
Ok(Self::try_from_hms(hour_24, 0, 0).expect("components are checked when parsing"))
579594
}
580-
items!(hour_24) => Ok(Self::from_hms(hour_24, 0, 0)),
581-
items!(hour_12, am_pm) => Ok(Self::from_hms(hour_12_to_24(hour_12, am_pm), 0, 0)),
595+
items!(hour_12, am_pm) => Ok(Self::try_from_hms(hour_12_to_24(hour_12, am_pm), 0, 0)
596+
.expect("components are checked when parsing")),
582597
_ => Err(ParseError::InsufficientInformation),
583598
}
584599
}

0 commit comments

Comments
 (0)