Skip to content

Commit 261ec4d

Browse files
committed
Ensure Of is always valid
1 parent 616a90c commit 261ec4d

File tree

2 files changed

+110
-61
lines changed

2 files changed

+110
-61
lines changed

src/naive/date.rs

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -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: 95 additions & 48 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,20 +266,25 @@ 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;
283289
if mdl <= MAX_MDL {
284290
// Array is indexed from `[1..=MAX_MDL]`, with a `0` index having a meaningless value.
@@ -288,35 +294,45 @@ impl Of {
288294
// Panicking here would be reasonable, but we are just going on with a safe value.
289295
Of(0)
290296
}
297+
let v = MDL_TO_OL[mdl as usize];
298+
let of = Of(mdf.wrapping_sub((v as i32 as u32 & 0x3ff) << 3));
299+
of.validate()
291300
}
292301

293302
#[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
303+
pub(super) const fn inner(&self) -> u32 {
304+
self.0
298305
}
299306

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

306313
#[inline]
307-
pub(super) const fn with_ordinal(&self, ordinal: u32) -> Option<Of> {
308-
if ordinal > 366 {
309-
return None;
314+
const fn validate(self) -> Option<Of> {
315+
let ol = self.ol();
316+
match ol >= MIN_OL && ol <= MAX_OL {
317+
true => Some(self),
318+
false => None,
310319
}
320+
}
311321

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

316333
#[inline]
317334
pub(super) const fn flags(&self) -> YearFlags {
318-
let Of(of) = *self;
319-
YearFlags((of & 0b1111) as u8)
335+
YearFlags((self.0 & 0b1111) as u8)
320336
}
321337

322338
#[inline]
@@ -339,16 +355,20 @@ impl Of {
339355
Mdf::from_of(*self)
340356
}
341357

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

365+
/// Returns an `Of` with the previous day, or `None` if this is the first day of the year.
348366
#[inline]
349-
pub(super) const fn pred(&self) -> Of {
350-
let Of(of) = *self;
351-
Of(of - (1 << 4))
367+
pub(super) const fn pred(&self) -> Option<Of> {
368+
if self.ordinal() == 1 {
369+
return None;
370+
}
371+
Some(Of(self.0 - (1 << 4)))
352372
}
353373
}
354374

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

376400
impl Mdf {
377401
#[inline]
378402
pub(super) const fn new(month: u32, day: u32, YearFlags(flags): YearFlags) -> Option<Mdf> {
379-
match month <= 12 && day <= 31 {
403+
match month >= 1 && month <= 12 && day >= 1 && day <= 31 {
380404
true => Some(Mdf((month << 9) | (day << 4) | flags as u32)),
381405
false => None,
382406
}
@@ -447,7 +471,7 @@ impl Mdf {
447471

448472
#[cfg_attr(feature = "cargo-clippy", allow(clippy::wrong_self_convention))]
449473
#[inline]
450-
pub(super) const fn to_of(&self) -> Of {
474+
pub(super) const fn to_of(&self) -> Option<Of> {
451475
Of::from_mdf(*self)
452476
}
453477
}
@@ -542,7 +566,7 @@ mod tests {
542566
};
543567

544568
assert!(
545-
of.valid() == expected,
569+
of.validate().is_some() == expected,
546570
"ordinal {} = {:?} should be {} for dominical year {:?}",
547571
ordinal,
548572
of,
@@ -662,8 +686,7 @@ mod tests {
662686
fn test_of_fields() {
663687
for &flags in FLAGS.iter() {
664688
for ordinal in range_inclusive(1u32, 366) {
665-
let of = Of::new(ordinal, flags).unwrap();
666-
if of.valid() {
689+
if let Some(of) = Of::new(ordinal, flags) {
667690
assert_eq!(of.ordinal(), ordinal);
668691
}
669692
}
@@ -676,14 +699,9 @@ mod tests {
676699
let of = Of::new(ordinal, flags).unwrap();
677700

678701
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() {
702+
let of = of.with_ordinal(ordinal);
703+
assert_eq!(of, Of::new(ordinal, flags));
704+
if let Some(of) = of {
687705
assert_eq!(of.ordinal(), ordinal);
688706
}
689707
}
@@ -808,25 +826,25 @@ mod tests {
808826
#[test]
809827
fn test_of_to_mdf() {
810828
for i in range_inclusive(0u32, 8192) {
811-
let of = Of(i);
812-
assert_eq!(of.valid(), of.to_mdf().valid());
829+
if let Some(of) = Of(i).validate() {
830+
assert!(of.to_mdf().valid());
831+
}
813832
}
814833
}
815834

816835
#[test]
817836
fn test_mdf_to_of() {
818837
for i in range_inclusive(0u32, 8192) {
819838
let mdf = Mdf(i);
820-
assert_eq!(mdf.valid(), mdf.to_of().valid());
839+
assert_eq!(mdf.valid(), mdf.to_of().is_some());
821840
}
822841
}
823842

824843
#[test]
825844
fn test_of_to_mdf_to_of() {
826845
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());
846+
if let Some(of) = Of(i).validate() {
847+
assert_eq!(of, of.to_mdf().to_of().unwrap());
830848
}
831849
}
832850
}
@@ -836,11 +854,40 @@ mod tests {
836854
for i in range_inclusive(0u32, 8192) {
837855
let mdf = Mdf(i);
838856
if mdf.valid() {
839-
assert_eq!(mdf, mdf.to_of().to_mdf());
857+
assert_eq!(mdf, mdf.to_of().unwrap().to_mdf());
840858
}
841859
}
842860
}
843861

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

0 commit comments

Comments
 (0)