Skip to content

Commit 83b24c9

Browse files
committed
Ensure Of is always valid
1 parent f0c8063 commit 83b24c9

File tree

2 files changed

+117
-71
lines changed

2 files changed

+117
-71
lines changed

src/naive/date.rs

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,8 @@ impl NaiveDate {
244244
}
245245
/// Makes a new `NaiveDate` from year and packed ordinal-flags, with a verification.
246246
fn from_of(year: i32, of: Of) -> Option<NaiveDate> {
247-
if (MIN_YEAR..=MAX_YEAR).contains(&year) && of.valid() {
248-
let Of(of) = of;
247+
if (MIN_YEAR..=MAX_YEAR).contains(&year) {
248+
let of = of.inner();
249249
Some(NaiveDate { ymdf: (year << 13) | (of as DateImpl) })
250250
} else {
251251
None
@@ -254,7 +254,7 @@ impl NaiveDate {
254254

255255
/// Makes a new `NaiveDate` from year and packed month-day-flags, with a verification.
256256
fn from_mdf(year: i32, mdf: Mdf) -> Option<NaiveDate> {
257-
NaiveDate::from_of(year, mdf.to_of())
257+
NaiveDate::from_of(year, mdf.to_of()?)
258258
}
259259

260260
/// Makes a new `NaiveDate` from the [calendar date](#calendar-date)
@@ -925,28 +925,24 @@ impl NaiveDate {
925925
/// Returns the packed ordinal-flags.
926926
#[inline]
927927
const fn of(&self) -> Of {
928-
Of((self.ymdf & 0b1_1111_1111_1111) as u32)
928+
Of::from_date_impl(self.ymdf)
929929
}
930930

931931
/// Makes a new `NaiveDate` with the packed month-day-flags changed.
932932
///
933933
/// Returns `None` when the resulting `NaiveDate` would be invalid.
934934
#[inline]
935935
fn with_mdf(&self, mdf: Mdf) -> Option<NaiveDate> {
936-
self.with_of(mdf.to_of())
936+
Some(self.with_of(mdf.to_of()?))
937937
}
938938

939939
/// Makes a new `NaiveDate` with the packed ordinal-flags changed.
940940
///
941941
/// Returns `None` when the resulting `NaiveDate` would be invalid.
942+
/// Does not check if the year flags match the year.
942943
#[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-
}
944+
const fn with_of(&self, of: Of) -> NaiveDate {
945+
NaiveDate { ymdf: (self.ymdf & !0b1_1111_1111_1111) | of.inner() as DateImpl }
950946
}
951947

952948
/// Makes a new `NaiveDate` for the next calendar date.
@@ -975,7 +971,10 @@ impl NaiveDate {
975971
#[inline]
976972
#[must_use]
977973
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))
974+
match self.of().succ() {
975+
Some(of) => Some(self.with_of(of)),
976+
None => NaiveDate::from_ymd_opt(self.year() + 1, 1, 1),
977+
}
979978
}
980979

981980
/// Makes a new `NaiveDate` for the previous calendar date.
@@ -1004,7 +1003,10 @@ impl NaiveDate {
10041003
#[inline]
10051004
#[must_use]
10061005
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))
1006+
match self.of().pred() {
1007+
Some(of) => Some(self.with_of(of)),
1008+
None => NaiveDate::from_ymd_opt(self.year() - 1, 12, 31),
1009+
}
10081010
}
10091011

10101012
/// Adds the `days` part of given `Duration` to the current date.
@@ -1623,7 +1625,7 @@ impl Datelike for NaiveDate {
16231625
/// ```
16241626
#[inline]
16251627
fn with_ordinal(&self, ordinal: u32) -> Option<NaiveDate> {
1626-
self.with_of(self.of().with_ordinal(ordinal)?)
1628+
self.of().with_ordinal(ordinal).map(|of| self.with_of(of))
16271629
}
16281630

16291631
/// Makes a new `NaiveDate` with the day of year (starting from 0) changed.
@@ -1648,7 +1650,7 @@ impl Datelike for NaiveDate {
16481650
#[inline]
16491651
fn with_ordinal0(&self, ordinal0: u32) -> Option<NaiveDate> {
16501652
let ordinal = ordinal0.checked_add(1)?;
1651-
self.with_of(self.of().with_ordinal(ordinal)?)
1653+
self.with_ordinal(ordinal)
16521654
}
16531655
}
16541656

src/naive/internals.rs

Lines changed: 99 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
use crate::Weekday;
1919
use core::{fmt, i32};
2020

21-
/// The internal date representation. This also includes the packed `Mdf` value.
21+
/// The internal date representation: `year << 13 | Of`
2222
pub(super) type DateImpl = i32;
2323

2424
pub(super) const MAX_YEAR: DateImpl = i32::MAX >> 13;
@@ -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;
@@ -265,58 +266,70 @@ const OL_TO_MDL: &[u8; MAX_OL as usize + 1] = &[
265266
///
266267
/// The whole bits except for the least 3 bits are referred as `Ol` (ordinal and leap flag),
267268
/// which is an index to the `OL_TO_MDL` lookup table.
269+
///
270+
/// The methods implemented on `Of` always return a valid value.
268271
#[derive(PartialEq, PartialOrd, Copy, Clone)]
269-
pub(super) struct Of(pub(crate) u32);
272+
pub(super) struct Of(u32);
270273

271274
impl Of {
272275
#[inline]
273276
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-
}
277+
let of = Of((ordinal << 4) | flags as u32);
278+
of.validate()
279+
}
280+
281+
pub(super) const fn from_date_impl(date_impl: DateImpl) -> Of {
282+
// We assume the value in the `DateImpl` is valid.
283+
Of((date_impl & 0b1_1111_1111_1111) as u32)
278284
}
279285

280286
#[inline]
281-
pub(super) const fn from_mdf(Mdf(mdf): Mdf) -> Of {
287+
pub(super) const fn from_mdf(Mdf(mdf): Mdf) -> Option<Of> {
282288
let mdl = mdf >> 3;
283-
if mdl <= MAX_MDL {
284-
// Array is indexed from `[1..=MAX_MDL]`, with a `0` index having a meaningless value.
285-
let v = MDL_TO_OL[mdl as usize];
286-
Of(mdf.wrapping_sub((v as i32 as u32 & 0x3ff) << 3))
287-
} else {
288-
// Panicking here would be reasonable, but we are just going on with a safe value.
289-
Of(0)
289+
if mdl > MAX_MDL {
290+
// Panicking on out-of-bounds indexing would be reasonable, but just return `None`.
291+
return None;
290292
}
293+
// Array is indexed from `[1..=MAX_MDL]`, with a `0` index having a meaningless value.
294+
let v = MDL_TO_OL[mdl as usize];
295+
let of = Of(mdf.wrapping_sub((v as i32 as u32 & 0x3ff) << 3));
296+
of.validate()
291297
}
292298

293299
#[inline]
294-
pub(super) const fn valid(&self) -> bool {
295-
let Of(of) = *self;
296-
let ol = of >> 3;
297-
ol >= MIN_OL && ol <= MAX_OL
300+
pub(super) const fn inner(&self) -> u32 {
301+
self.0
298302
}
299303

304+
/// Returns `(ordinal << 1) | leap-year-flag`.
300305
#[inline]
301-
pub(super) const fn ordinal(&self) -> u32 {
302-
let Of(of) = *self;
303-
of >> 4
306+
const fn ol(&self) -> u32 {
307+
self.0 >> 3
304308
}
305309

306310
#[inline]
307-
pub(super) const fn with_ordinal(&self, ordinal: u32) -> Option<Of> {
308-
if ordinal > 366 {
309-
return None;
311+
const fn validate(self) -> Option<Of> {
312+
let ol = self.ol();
313+
match ol >= MIN_OL && ol <= MAX_OL {
314+
true => Some(self),
315+
false => None,
310316
}
317+
}
311318

312-
let Of(of) = *self;
313-
Some(Of((of & 0b1111) | (ordinal << 4)))
319+
#[inline]
320+
pub(super) const fn ordinal(&self) -> u32 {
321+
self.0 >> 4
322+
}
323+
324+
#[inline]
325+
pub(super) const fn with_ordinal(&self, ordinal: u32) -> Option<Of> {
326+
let of = Of((ordinal << 4) | (self.0 & 0b1111));
327+
of.validate()
314328
}
315329

316330
#[inline]
317331
pub(super) const fn flags(&self) -> YearFlags {
318-
let Of(of) = *self;
319-
YearFlags((of & 0b1111) as u8)
332+
YearFlags((self.0 & 0b1111) as u8)
320333
}
321334

322335
#[inline]
@@ -339,16 +352,20 @@ impl Of {
339352
Mdf::from_of(*self)
340353
}
341354

355+
/// Returns an `Of` with the next day, or `None` if this is the last day of the year.
342356
#[inline]
343-
pub(super) const fn succ(&self) -> Of {
344-
let Of(of) = *self;
345-
Of(of + (1 << 4))
357+
pub(super) const fn succ(&self) -> Option<Of> {
358+
let of = Of(self.0 + (1 << 4));
359+
of.validate()
346360
}
347361

362+
/// Returns an `Of` with the previous day, or `None` if this is the first day of the year.
348363
#[inline]
349-
pub(super) const fn pred(&self) -> Of {
350-
let Of(of) = *self;
351-
Of(of - (1 << 4))
364+
pub(super) const fn pred(&self) -> Option<Of> {
365+
if self.ordinal() == 1 {
366+
return None;
367+
}
368+
Some(Of(self.0 - (1 << 4)))
352369
}
353370
}
354371

@@ -370,13 +387,17 @@ impl fmt::Debug for Of {
370387
/// The whole bits except for the least 3 bits are referred as `Mdl`
371388
/// (month, day of month and leap flag),
372389
/// which is an index to the `MDL_TO_OL` lookup table.
390+
///
391+
/// The methods implemented on `Mdf` do not always return a valid value.
392+
/// Dates than can't exist, like February 30, can still be represented.
393+
/// Use `Mdl::valid` to check whether the date is valid.
373394
#[derive(PartialEq, PartialOrd, Copy, Clone)]
374395
pub(super) struct Mdf(u32);
375396

376397
impl Mdf {
377398
#[inline]
378399
pub(super) const fn new(month: u32, day: u32, YearFlags(flags): YearFlags) -> Option<Mdf> {
379-
match month <= 12 && day <= 31 {
400+
match month >= 1 && month <= 12 && day >= 1 && day <= 31 {
380401
true => Some(Mdf((month << 9) | (day << 4) | flags as u32)),
381402
false => None,
382403
}
@@ -447,7 +468,7 @@ impl Mdf {
447468

448469
#[cfg_attr(feature = "cargo-clippy", allow(clippy::wrong_self_convention))]
449470
#[inline]
450-
pub(super) const fn to_of(&self) -> Of {
471+
pub(super) const fn to_of(&self) -> Option<Of> {
451472
Of::from_mdf(*self)
452473
}
453474
}
@@ -542,7 +563,7 @@ mod tests {
542563
};
543564

544565
assert!(
545-
of.valid() == expected,
566+
of.validate().is_some() == expected,
546567
"ordinal {} = {:?} should be {} for dominical year {:?}",
547568
ordinal,
548569
of,
@@ -662,8 +683,7 @@ mod tests {
662683
fn test_of_fields() {
663684
for &flags in FLAGS.iter() {
664685
for ordinal in range_inclusive(1u32, 366) {
665-
let of = Of::new(ordinal, flags).unwrap();
666-
if of.valid() {
686+
if let Some(of) = Of::new(ordinal, flags) {
667687
assert_eq!(of.ordinal(), ordinal);
668688
}
669689
}
@@ -676,14 +696,9 @@ mod tests {
676696
let of = Of::new(ordinal, flags).unwrap();
677697

678698
for ordinal in range_inclusive(0u32, 1024) {
679-
let of = match of.with_ordinal(ordinal) {
680-
Some(of) => of,
681-
None if ordinal > 366 => continue,
682-
None => panic!("failed to create Of with ordinal {}", ordinal),
683-
};
684-
685-
assert_eq!(of.valid(), Of::new(ordinal, flags).unwrap().valid());
686-
if of.valid() {
699+
let of = of.with_ordinal(ordinal);
700+
assert_eq!(of, Of::new(ordinal, flags));
701+
if let Some(of) = of {
687702
assert_eq!(of.ordinal(), ordinal);
688703
}
689704
}
@@ -808,25 +823,25 @@ mod tests {
808823
#[test]
809824
fn test_of_to_mdf() {
810825
for i in range_inclusive(0u32, 8192) {
811-
let of = Of(i);
812-
assert_eq!(of.valid(), of.to_mdf().valid());
826+
if let Some(of) = Of(i).validate() {
827+
assert!(of.to_mdf().valid());
828+
}
813829
}
814830
}
815831

816832
#[test]
817833
fn test_mdf_to_of() {
818834
for i in range_inclusive(0u32, 8192) {
819835
let mdf = Mdf(i);
820-
assert_eq!(mdf.valid(), mdf.to_of().valid());
836+
assert_eq!(mdf.valid(), mdf.to_of().is_some());
821837
}
822838
}
823839

824840
#[test]
825841
fn test_of_to_mdf_to_of() {
826842
for i in range_inclusive(0u32, 8192) {
827-
let of = Of(i);
828-
if of.valid() {
829-
assert_eq!(of, of.to_mdf().to_of());
843+
if let Some(of) = Of(i).validate() {
844+
assert_eq!(of, of.to_mdf().to_of().unwrap());
830845
}
831846
}
832847
}
@@ -836,11 +851,40 @@ mod tests {
836851
for i in range_inclusive(0u32, 8192) {
837852
let mdf = Mdf(i);
838853
if mdf.valid() {
839-
assert_eq!(mdf, mdf.to_of().to_mdf());
854+
assert_eq!(mdf, mdf.to_of().unwrap().to_mdf());
840855
}
841856
}
842857
}
843858

859+
#[test]
860+
fn test_invalid_returns_none() {
861+
let regular_year = YearFlags::from_year(2023);
862+
let leap_year = YearFlags::from_year(2024);
863+
assert!(Of::new(0, regular_year).is_none());
864+
assert!(Of::new(366, regular_year).is_none());
865+
assert!(Of::new(366, leap_year).is_some());
866+
assert!(Of::new(367, regular_year).is_none());
867+
868+
assert!(Mdf::new(0, 1, regular_year).is_none());
869+
assert!(Mdf::new(13, 1, regular_year).is_none());
870+
assert!(Mdf::new(1, 0, regular_year).is_none());
871+
assert!(Mdf::new(1, 32, regular_year).is_none());
872+
assert!(Mdf::new(2, 31, regular_year).is_some());
873+
874+
assert!(Of::from_mdf(Mdf::new(2, 30, regular_year).unwrap()).is_none());
875+
assert!(Of::from_mdf(Mdf::new(2, 30, leap_year).unwrap()).is_none());
876+
assert!(Of::from_mdf(Mdf::new(2, 29, regular_year).unwrap()).is_none());
877+
assert!(Of::from_mdf(Mdf::new(2, 29, leap_year).unwrap()).is_some());
878+
assert!(Of::from_mdf(Mdf::new(2, 28, regular_year).unwrap()).is_some());
879+
880+
assert!(Of::new(365, regular_year).unwrap().succ().is_none());
881+
assert!(Of::new(365, leap_year).unwrap().succ().is_some());
882+
assert!(Of::new(366, leap_year).unwrap().succ().is_none());
883+
884+
assert!(Of::new(1, regular_year).unwrap().pred().is_none());
885+
assert!(Of::new(1, leap_year).unwrap().pred().is_none());
886+
}
887+
844888
#[test]
845889
fn test_weekday_from_u32_mod7() {
846890
for i in 0..=1000 {

0 commit comments

Comments
 (0)