Skip to content

Commit 6540cbf

Browse files
committed
Ensure Of is always valid
1 parent 9167548 commit 6540cbf

File tree

2 files changed

+96
-83
lines changed

2 files changed

+96
-83
lines changed

src/naive/date.rs

+41-32
Original file line numberDiff line numberDiff line change
@@ -242,19 +242,29 @@ impl NaiveDate {
242242
pub(crate) fn weeks_from(&self, day: Weekday) -> i32 {
243243
(self.ordinal() as i32 - self.weekday().num_days_from(day) as i32 + 6) / 7
244244
}
245-
/// Makes a new `NaiveDate` from year and packed ordinal-flags, with a verification.
246-
fn from_of(year: i32, of: Of) -> Option<NaiveDate> {
247-
if (MIN_YEAR..=MAX_YEAR).contains(&year) && of.valid() {
248-
let Of(of) = of;
249-
Some(NaiveDate { ymdf: (year << 13) | (of as DateImpl) })
250-
} else {
251-
None
245+
246+
/// Makes a new `NaiveDate` from year, ordinal and flags.
247+
/// Does not check whether the flags are correct for the provided year.
248+
const fn from_of(year: i32, ordinal: u32, flags: YearFlags) -> Option<NaiveDate> {
249+
if year < MIN_YEAR || year > MAX_YEAR {
250+
return None; // Out-of-range
251+
}
252+
match Of::new(ordinal, flags) {
253+
Some(of) => Some(NaiveDate { ymdf: (year << 13) | (of.inner() as DateImpl) }),
254+
None => None, // Invalid: Ordinal outside of the nr of days in a year with those flags.
252255
}
253256
}
254257

255-
/// Makes a new `NaiveDate` from year and packed month-day-flags, with a verification.
256-
fn from_mdf(year: i32, mdf: Mdf) -> Option<NaiveDate> {
257-
NaiveDate::from_of(year, mdf.to_of())
258+
/// Makes a new `NaiveDate` from year and packed month-day-flags.
259+
/// Does not check whether the flags are correct for the provided year.
260+
const fn from_mdf(year: i32, mdf: Mdf) -> Option<NaiveDate> {
261+
if year < MIN_YEAR || year > MAX_YEAR {
262+
return None; // Out-of-range
263+
}
264+
match mdf.to_of() {
265+
Some(of) => Some(NaiveDate { ymdf: (year << 13) | (of.inner() as DateImpl) }),
266+
None => None, // Non-existing date
267+
}
258268
}
259269

260270
/// Makes a new `NaiveDate` from the [calendar date](#calendar-date)
@@ -325,7 +335,7 @@ impl NaiveDate {
325335
#[must_use]
326336
pub fn from_yo_opt(year: i32, ordinal: u32) -> Option<NaiveDate> {
327337
let flags = YearFlags::from_year(year);
328-
NaiveDate::from_of(year, Of::new(ordinal, flags)?)
338+
NaiveDate::from_of(year, ordinal, flags)
329339
}
330340

331341
/// Makes a new `NaiveDate` from the [ISO week date](#week-date)
@@ -394,20 +404,17 @@ impl NaiveDate {
394404
if weekord <= delta {
395405
// ordinal < 1, previous year
396406
let prevflags = YearFlags::from_year(year - 1);
397-
NaiveDate::from_of(
398-
year - 1,
399-
Of::new(weekord + prevflags.ndays() - delta, prevflags)?,
400-
)
407+
NaiveDate::from_of(year - 1, weekord + prevflags.ndays() - delta, prevflags)
401408
} else {
402409
let ordinal = weekord - delta;
403410
let ndays = flags.ndays();
404411
if ordinal <= ndays {
405412
// this year
406-
NaiveDate::from_of(year, Of::new(ordinal, flags)?)
413+
NaiveDate::from_of(year, ordinal, flags)
407414
} else {
408415
// ordinal > ndays, next year
409416
let nextflags = YearFlags::from_year(year + 1);
410-
NaiveDate::from_of(year + 1, Of::new(ordinal - ndays, nextflags)?)
417+
NaiveDate::from_of(year + 1, ordinal - ndays, nextflags)
411418
}
412419
}
413420
} else {
@@ -452,7 +459,7 @@ impl NaiveDate {
452459
let (year_div_400, cycle) = div_mod_floor(days, 146_097);
453460
let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle as u32);
454461
let flags = YearFlags::from_year_mod_400(year_mod_400 as i32);
455-
NaiveDate::from_of(year_div_400 * 400 + year_mod_400 as i32, Of::new(ordinal, flags)?)
462+
NaiveDate::from_of(year_div_400 * 400 + year_mod_400 as i32, ordinal, flags)
456463
}
457464

458465
/// Makes a new `NaiveDate` by counting the number of occurrences of a particular day-of-week
@@ -933,20 +940,16 @@ impl NaiveDate {
933940
/// Returns `None` when the resulting `NaiveDate` would be invalid.
934941
#[inline]
935942
fn with_mdf(&self, mdf: Mdf) -> Option<NaiveDate> {
936-
self.with_of(mdf.to_of())
943+
Some(self.with_of(mdf.to_of()?))
937944
}
938945

939946
/// Makes a new `NaiveDate` with the packed ordinal-flags changed.
940947
///
941948
/// Returns `None` when the resulting `NaiveDate` would be invalid.
949+
/// Does not check if the year flags match the year.
942950
#[inline]
943-
fn with_of(&self, of: Of) -> Option<NaiveDate> {
944-
if of.valid() {
945-
let Of(of) = of;
946-
Some(NaiveDate { ymdf: (self.ymdf & !0b1_1111_1111_1111) | of as DateImpl })
947-
} else {
948-
None
949-
}
951+
const fn with_of(&self, of: Of) -> NaiveDate {
952+
NaiveDate { ymdf: (self.ymdf & !0b1_1111_1111_1111) | of.inner() as DateImpl }
950953
}
951954

952955
/// Makes a new `NaiveDate` for the next calendar date.
@@ -975,7 +978,10 @@ impl NaiveDate {
975978
#[inline]
976979
#[must_use]
977980
pub fn succ_opt(&self) -> Option<NaiveDate> {
978-
self.with_of(self.of().succ()).or_else(|| NaiveDate::from_ymd_opt(self.year() + 1, 1, 1))
981+
match self.of().succ() {
982+
Some(of) => Some(self.with_of(of)),
983+
None => NaiveDate::from_ymd_opt(self.year() + 1, 1, 1),
984+
}
979985
}
980986

981987
/// Makes a new `NaiveDate` for the previous calendar date.
@@ -1004,7 +1010,10 @@ impl NaiveDate {
10041010
#[inline]
10051011
#[must_use]
10061012
pub fn pred_opt(&self) -> Option<NaiveDate> {
1007-
self.with_of(self.of().pred()).or_else(|| NaiveDate::from_ymd_opt(self.year() - 1, 12, 31))
1013+
match self.of().pred() {
1014+
Some(of) => Some(self.with_of(of)),
1015+
None => NaiveDate::from_ymd_opt(self.year() - 1, 12, 31),
1016+
}
10081017
}
10091018

10101019
/// Adds the `days` part of given `Duration` to the current date.
@@ -1036,7 +1045,7 @@ impl NaiveDate {
10361045

10371046
let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle as u32);
10381047
let flags = YearFlags::from_year_mod_400(year_mod_400 as i32);
1039-
NaiveDate::from_of(year_div_400 * 400 + year_mod_400 as i32, Of::new(ordinal, flags)?)
1048+
NaiveDate::from_of(year_div_400 * 400 + year_mod_400 as i32, ordinal, flags)
10401049
}
10411050

10421051
/// Subtracts the `days` part of given `Duration` from the current date.
@@ -1068,7 +1077,7 @@ impl NaiveDate {
10681077

10691078
let (year_mod_400, ordinal) = internals::cycle_to_yo(cycle as u32);
10701079
let flags = YearFlags::from_year_mod_400(year_mod_400 as i32);
1071-
NaiveDate::from_of(year_div_400 * 400 + year_mod_400 as i32, Of::new(ordinal, flags)?)
1080+
NaiveDate::from_of(year_div_400 * 400 + year_mod_400 as i32, ordinal, flags)
10721081
}
10731082

10741083
/// Subtracts another `NaiveDate` from the current date.
@@ -1623,7 +1632,7 @@ impl Datelike for NaiveDate {
16231632
/// ```
16241633
#[inline]
16251634
fn with_ordinal(&self, ordinal: u32) -> Option<NaiveDate> {
1626-
self.with_of(self.of().with_ordinal(ordinal)?)
1635+
self.of().with_ordinal(ordinal).map(|of| self.with_of(of))
16271636
}
16281637

16291638
/// Makes a new `NaiveDate` with the day of year (starting from 0) changed.
@@ -1648,7 +1657,7 @@ impl Datelike for NaiveDate {
16481657
#[inline]
16491658
fn with_ordinal0(&self, ordinal0: u32) -> Option<NaiveDate> {
16501659
let ordinal = ordinal0.checked_add(1)?;
1651-
self.with_of(self.of().with_ordinal(ordinal)?)
1660+
self.with_ordinal(ordinal)
16521661
}
16531662
}
16541663

src/naive/internals.rs

+55-51
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,9 @@ impl fmt::Debug for YearFlags {
172172
}
173173
}
174174

175+
// OL: (ordinal << 1) | leap year flag
175176
pub(super) const MIN_OL: u32 = 1 << 1;
176-
pub(super) const MAX_OL: u32 = 366 << 1; // larger than the non-leap last day `(365 << 1) | 1`
177+
pub(super) const MAX_OL: u32 = 366 << 1; // `(366 << 1) | 1` would be day 366 in a non-leap year
177178
pub(super) const MAX_MDL: u32 = (12 << 6) | (31 << 1) | 1;
178179

179180
const XX: i8 = -128;
@@ -271,51 +272,56 @@ pub(super) struct Of(pub(crate) u32);
271272
impl Of {
272273
#[inline]
273274
pub(super) const fn new(ordinal: u32, YearFlags(flags): YearFlags) -> Option<Of> {
274-
match ordinal <= 366 {
275-
true => Some(Of((ordinal << 4) | flags as u32)),
276-
false => None,
277-
}
275+
let of = Of((ordinal << 4) | flags as u32);
276+
of.validate()
278277
}
279278

280279
#[inline]
281-
pub(super) const fn from_mdf(Mdf(mdf): Mdf) -> Of {
280+
pub(super) const fn from_mdf(Mdf(mdf): Mdf) -> Option<Of> {
282281
let mdl = mdf >> 3;
283-
if mdl <= MAX_MDL {
284-
let v = MDL_TO_OL[mdl as usize];
285-
Of(mdf.wrapping_sub((v as i32 as u32 & 0x3ff) << 3))
286-
} else {
287-
// Panicking here would be reasonable, but we are just going on with a safe value.
288-
Of(0)
282+
if mdl > MAX_MDL {
283+
// Panicking on out-of-bounds indexing would be reasonable, but just return `None`.
284+
return None;
289285
}
286+
let v = MDL_TO_OL[mdl as usize];
287+
let of = Of(mdf.wrapping_sub((v as i32 as u32 & 0x3ff) << 3));
288+
of.validate()
290289
}
291290

292291
#[inline]
293-
pub(super) const fn valid(&self) -> bool {
294-
let Of(of) = *self;
295-
let ol = of >> 3;
296-
ol >= MIN_OL && ol <= MAX_OL
292+
pub(super) const fn inner(&self) -> u32 {
293+
self.0
297294
}
298295

296+
/// Returns `(ordinal << 1) | leap-year-flag`.
299297
#[inline]
300-
pub(super) const fn ordinal(&self) -> u32 {
301-
let Of(of) = *self;
302-
of >> 4
298+
pub(super) const fn ol(&self) -> u32 {
299+
self.0 >> 3
303300
}
304301

305302
#[inline]
306-
pub(super) const fn with_ordinal(&self, ordinal: u32) -> Option<Of> {
307-
if ordinal > 366 {
308-
return None;
303+
const fn validate(self) -> Option<Of> {
304+
let ol = self.ol();
305+
match ol >= MIN_OL && ol <= MAX_OL {
306+
true => Some(self),
307+
false => None,
309308
}
309+
}
310310

311-
let Of(of) = *self;
312-
Some(Of((of & 0b1111) | (ordinal << 4)))
311+
#[inline]
312+
pub(super) const fn ordinal(&self) -> u32 {
313+
self.0 >> 4
314+
}
315+
316+
#[inline]
317+
pub(super) const fn with_ordinal(&self, ordinal: u32) -> Option<Of> {
318+
let of = Of((ordinal << 4) | (self.0 & 0b1111));
319+
of.validate()
313320
}
314321

315322
#[inline]
316323
pub(super) const fn flags(&self) -> YearFlags {
317-
let Of(of) = *self;
318-
YearFlags((of & 0b1111) as u8)
324+
YearFlags((self.0 & 0b1111) as u8)
319325
}
320326

321327
#[inline]
@@ -338,16 +344,20 @@ impl Of {
338344
Mdf::from_of(*self)
339345
}
340346

347+
/// Returns an `Of` with the next day, or `None` if this is the last day of the year.
341348
#[inline]
342-
pub(super) const fn succ(&self) -> Of {
343-
let Of(of) = *self;
344-
Of(of + (1 << 4))
349+
pub(super) const fn succ(&self) -> Option<Of> {
350+
let of = Of(self.0 + (1 << 4));
351+
of.validate()
345352
}
346353

354+
/// Returns an `Of` with the previous day, or `None` if this is the first day of the year.
347355
#[inline]
348-
pub(super) const fn pred(&self) -> Of {
349-
let Of(of) = *self;
350-
Of(of - (1 << 4))
356+
pub(super) const fn pred(&self) -> Option<Of> {
357+
if self.ordinal() == 1 {
358+
return None;
359+
}
360+
Some(Of(self.0 - (1 << 4)))
351361
}
352362
}
353363

@@ -444,7 +454,7 @@ impl Mdf {
444454

445455
#[cfg_attr(feature = "cargo-clippy", allow(clippy::wrong_self_convention))]
446456
#[inline]
447-
pub(super) const fn to_of(&self) -> Of {
457+
pub(super) const fn to_of(&self) -> Option<Of> {
448458
Of::from_mdf(*self)
449459
}
450460
}
@@ -522,7 +532,7 @@ mod tests {
522532
};
523533

524534
assert!(
525-
of.valid() == expected,
535+
of.validate().is_some() == expected,
526536
"ordinal {} = {:?} should be {} for dominical year {:?}",
527537
ordinal,
528538
of,
@@ -642,8 +652,7 @@ mod tests {
642652
fn test_of_fields() {
643653
for &flags in FLAGS.iter() {
644654
for ordinal in range_inclusive(1u32, 366) {
645-
let of = Of::new(ordinal, flags).unwrap();
646-
if of.valid() {
655+
if let Some(of) = Of::new(ordinal, flags) {
647656
assert_eq!(of.ordinal(), ordinal);
648657
}
649658
}
@@ -656,14 +665,9 @@ mod tests {
656665
let of = Of::new(ordinal, flags).unwrap();
657666

658667
for ordinal in range_inclusive(0u32, 1024) {
659-
let of = match of.with_ordinal(ordinal) {
660-
Some(of) => of,
661-
None if ordinal > 366 => continue,
662-
None => panic!("failed to create Of with ordinal {}", ordinal),
663-
};
664-
665-
assert_eq!(of.valid(), Of::new(ordinal, flags).unwrap().valid());
666-
if of.valid() {
668+
let of = of.with_ordinal(ordinal);
669+
assert_eq!(of, Of::new(ordinal, flags));
670+
if let Some(of) = of {
667671
assert_eq!(of.ordinal(), ordinal);
668672
}
669673
}
@@ -788,25 +792,25 @@ mod tests {
788792
#[test]
789793
fn test_of_to_mdf() {
790794
for i in range_inclusive(0u32, 8192) {
791-
let of = Of(i);
792-
assert_eq!(of.valid(), of.to_mdf().valid());
795+
if let Some(of) = Of(i).validate() {
796+
assert!(of.to_mdf().valid());
797+
}
793798
}
794799
}
795800

796801
#[test]
797802
fn test_mdf_to_of() {
798803
for i in range_inclusive(0u32, 8192) {
799804
let mdf = Mdf(i);
800-
assert_eq!(mdf.valid(), mdf.to_of().valid());
805+
assert_eq!(mdf.valid(), mdf.to_of().is_some());
801806
}
802807
}
803808

804809
#[test]
805810
fn test_of_to_mdf_to_of() {
806811
for i in range_inclusive(0u32, 8192) {
807-
let of = Of(i);
808-
if of.valid() {
809-
assert_eq!(of, of.to_mdf().to_of());
812+
if let Some(of) = Of(i).validate() {
813+
assert_eq!(of, of.to_mdf().to_of().unwrap());
810814
}
811815
}
812816
}
@@ -816,7 +820,7 @@ mod tests {
816820
for i in range_inclusive(0u32, 8192) {
817821
let mdf = Mdf(i);
818822
if mdf.valid() {
819-
assert_eq!(mdf, mdf.to_of().to_mdf());
823+
assert_eq!(mdf, mdf.to_of().unwrap().to_mdf());
820824
}
821825
}
822826
}

0 commit comments

Comments
 (0)