Skip to content

Commit cc22196

Browse files
committed
Ensure Of is always valid
1 parent 314d3d6 commit cc22196

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
@@ -243,8 +243,8 @@ impl NaiveDate {
243243
}
244244
/// Makes a new `NaiveDate` from year and packed ordinal-flags, with a verification.
245245
fn from_of(year: i32, of: Of) -> Option<NaiveDate> {
246-
if (MIN_YEAR..=MAX_YEAR).contains(&year) && of.valid() {
247-
let Of(of) = of;
246+
if (MIN_YEAR..=MAX_YEAR).contains(&year) {
247+
let of = of.inner();
248248
Some(NaiveDate { ymdf: (year << 13) | (of as DateImpl) })
249249
} else {
250250
None
@@ -253,7 +253,7 @@ impl NaiveDate {
253253

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

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

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

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

951947
/// Makes a new `NaiveDate` for the next calendar date.
@@ -974,7 +970,10 @@ impl NaiveDate {
974970
#[inline]
975971
#[must_use]
976972
pub fn succ_opt(&self) -> Option<NaiveDate> {
977-
self.with_of(self.of().succ()).or_else(|| NaiveDate::from_ymd_opt(self.year() + 1, 1, 1))
973+
match self.of().succ() {
974+
Some(of) => Some(self.with_of(of)),
975+
None => NaiveDate::from_ymd_opt(self.year() + 1, 1, 1),
976+
}
978977
}
979978

980979
/// Makes a new `NaiveDate` for the previous calendar date.
@@ -1003,7 +1002,10 @@ impl NaiveDate {
10031002
#[inline]
10041003
#[must_use]
10051004
pub fn pred_opt(&self) -> Option<NaiveDate> {
1006-
self.with_of(self.of().pred()).or_else(|| NaiveDate::from_ymd_opt(self.year() - 1, 12, 31))
1005+
match self.of().pred() {
1006+
Some(of) => Some(self.with_of(of)),
1007+
None => NaiveDate::from_ymd_opt(self.year() - 1, 12, 31),
1008+
}
10071009
}
10081010

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

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

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+
match self.ordinal() {
366+
1 => None,
367+
_ => Some(Of(self.0 - (1 << 4))),
368+
}
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 that 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)