diff --git a/src/format/scan.rs b/src/format/scan.rs index 7334a3b2ed..94dd8f6238 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -404,10 +404,10 @@ fn test_rfc2822_comments() { ("( x ( x ) x ( x ) x )", Ok("")), ]; - for (test_in, expected) in testdata { + for (test_in, expected) in &testdata { let actual = comment_2822(test_in).map(|(s, _)| s); assert_eq!( - expected, actual, + expected, &actual, "{:?} expected to produce {:?}, but produced {:?}.", test_in, expected, actual ); diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 84305bf8f5..0f3ec4dec9 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -12,6 +12,7 @@ use num_integer::div_mod_floor; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; +use self::u31::U31; #[cfg(any(feature = "alloc", feature = "std", test))] use crate::format::DelayedFormat; use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; @@ -24,6 +25,8 @@ mod serde; #[cfg(test)] mod tests; +mod u31; + /// ISO 8601 time without timezone. /// Allows for the nanosecond precision and optional leap second representation. /// @@ -187,7 +190,7 @@ mod tests; #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))] pub struct NaiveTime { secs: u32, - frac: u32, + frac: U31, } impl NaiveTime { @@ -392,7 +395,8 @@ impl NaiveTime { return None; } let secs = hour * 3600 + min * 60 + sec; - Some(NaiveTime { secs, frac: nano }) + let frac = unsafe { U31::new_unchecked(nano) }; + Some(NaiveTime { secs, frac }) } /// Makes a new `NaiveTime` from the number of seconds since midnight and nanosecond. @@ -443,7 +447,8 @@ impl NaiveTime { if secs >= 86_400 || nano >= 2_000_000_000 { return None; } - Some(NaiveTime { secs, frac: nano }) + let frac = unsafe { U31::new_unchecked(nano) }; + Some(NaiveTime { secs, frac }) } /// Parses a string with the specified format string and returns a new `NaiveTime`. @@ -534,7 +539,7 @@ impl NaiveTime { /// ``` pub fn overflowing_add_signed(&self, mut rhs: TimeDelta) -> (NaiveTime, i64) { let mut secs = self.secs; - let mut frac = self.frac; + let mut frac = self.frac.get(); // check if `self` is a leap second and adding `rhs` would escape that leap second. // if it's the case, update `self` and `rhs` to involve no leap second; @@ -551,6 +556,7 @@ impl NaiveTime { } else { frac = (i64::from(frac) + rhs.num_nanoseconds().unwrap()) as u32; debug_assert!(frac < 2_000_000_000); + let frac = unsafe { U31::new_unchecked(frac) }; return (NaiveTime { secs, frac }, 0); } } @@ -592,7 +598,8 @@ impl NaiveTime { } debug_assert!((0..86_400).contains(&secs)); - (NaiveTime { secs: secs as u32, frac: frac as u32 }, morerhssecs) + let frac = unsafe { U31::new_unchecked(frac as u32) }; + (NaiveTime { secs: secs as u32, frac }, morerhssecs) } /// Subtracts given `TimeDelta` from the current time, @@ -688,12 +695,12 @@ impl NaiveTime { use core::cmp::Ordering; let secs = i64::from(self.secs) - i64::from(rhs.secs); - let frac = i64::from(self.frac) - i64::from(rhs.frac); + let frac = i64::from(self.frac.get()) - i64::from(rhs.frac.get()); // `secs` may contain a leap second yet to be counted let adjust = match self.secs.cmp(&rhs.secs) { Ordering::Greater => { - if rhs.frac >= 1_000_000_000 { + if rhs.frac.get() >= 1_000_000_000 { 1 } else { 0 @@ -701,7 +708,7 @@ impl NaiveTime { } Ordering::Equal => 0, Ordering::Less => { - if self.frac >= 1_000_000_000 { + if self.frac.get() >= 1_000_000_000 { -1 } else { 0 @@ -798,8 +805,8 @@ impl NaiveTime { (hour, min, sec) } - pub(super) const MIN: Self = Self { secs: 0, frac: 0 }; - pub(super) const MAX: Self = Self { secs: 23 * 3600 + 59 * 60 + 59, frac: 999_999_999 }; + pub(super) const MIN: Self = Self { secs: 0, frac: U31::ZERO }; + pub(super) const MAX: Self = Self { secs: 23 * 3600 + 59 * 60 + 59, frac: U31::SEC_MINUS_1 }; } impl Timelike for NaiveTime { @@ -884,7 +891,7 @@ impl Timelike for NaiveTime { /// ``` #[inline] fn nanosecond(&self) -> u32 { - self.frac + self.frac.get() } /// Makes a new `NaiveTime` with the hour number changed. @@ -988,7 +995,8 @@ impl Timelike for NaiveTime { if nano >= 2_000_000_000 { return None; } - Some(NaiveTime { frac: nano, ..*self }) + let frac = unsafe { U31::new_unchecked(nano) }; + Some(NaiveTime { frac, ..*self }) } /// Returns the number of non-leap seconds past the last midnight. @@ -1222,11 +1230,9 @@ impl Sub for NaiveTime { impl fmt::Debug for NaiveTime { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let (hour, min, sec) = self.hms(); - let (sec, nano) = if self.frac >= 1_000_000_000 { - (sec + 1, self.frac - 1_000_000_000) - } else { - (sec, self.frac) - }; + let frac = self.frac.get(); + let (sec, nano) = + if frac >= 1_000_000_000 { (sec + 1, frac - 1_000_000_000) } else { (sec, frac) }; write!(f, "{:02}:{:02}:{:02}", hour, min, sec)?; if nano == 0 { diff --git a/src/naive/time/u31.rs b/src/naive/time/u31.rs new file mode 100644 index 0000000000..2e117d91c0 --- /dev/null +++ b/src/naive/time/u31.rs @@ -0,0 +1,285 @@ +use core::{cmp, fmt, hash, mem}; + +/// A 31-bit unsigned integer +#[cfg(target_endian = "little")] +#[allow(unreachable_pub)] // public through `rkyv::Archive` +#[repr(C)] +#[derive(Copy, Clone)] +pub struct U31 { + align: [u32; 0], + a: u8, + b: u8, + c: u8, + d: MaxByte, +} + +/// A 31-bit unsigned integer +#[cfg(target_endian = "big")] +#[allow(unreachable_pub)] // public through `rkyv::Archive` +#[repr(C)] +#[derive(Copy, Clone)] +pub(crate) struct U31 { + align: [u32; 0], + d: MaxByte, + c: u8, + b: u8, + a: u8, +} + +impl U31 { + pub(crate) const ZERO: U31 = U31 { align: [], a: 0, b: 0, c: 0, d: MaxByte::V0 }; + + pub(crate) const SEC_MINUS_1: Self = { + // [int(x, 16) for x in reversed(re.findall('..', f'{999_999_999:x}'))] + U31 { align: [], a: 255, b: 201, c: 154, d: MaxByte::V59 } + }; + + /// SAFETY: the caller has to ensure that the value is in range + #[inline] + pub(crate) unsafe fn new_unchecked(value: u32) -> Self { + mem::transmute(value) + } + + #[inline] + pub(crate) fn get(self) -> u32 { + unsafe { mem::transmute(self) } + } +} + +impl fmt::Debug for U31 { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.get().fmt(f) + } +} + +impl fmt::Display for U31 { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.get().fmt(f) + } +} + +impl cmp::PartialEq for U31 { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.get() == other.get() + } +} + +impl cmp::PartialOrd for U31 { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + self.get().partial_cmp(&other.get()) + } +} + +impl cmp::Eq for U31 {} + +impl cmp::Ord for U31 { + #[inline] + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.get().cmp(&other.get()) + } +} + +impl hash::Hash for U31 { + fn hash(&self, state: &mut H) { + self.get().hash(state); + } +} + +impl Default for U31 { + #[inline] + fn default() -> Self { + Self::ZERO + } +} + +#[allow(dead_code)] +#[repr(u8)] +#[derive(Copy, Clone)] +pub(crate) enum MaxByte { + V0 = 0, + V1 = 1, + V2 = 2, + V3 = 3, + V4 = 4, + V5 = 5, + V6 = 6, + V7 = 7, + V8 = 8, + V9 = 9, + V10 = 10, + V11 = 11, + V12 = 12, + V13 = 13, + V14 = 14, + V15 = 15, + V16 = 16, + V17 = 17, + V18 = 18, + V19 = 19, + V20 = 20, + V21 = 21, + V22 = 22, + V23 = 23, + V24 = 24, + V25 = 25, + V26 = 26, + V27 = 27, + V28 = 28, + V29 = 29, + V30 = 30, + V31 = 31, + V32 = 32, + V33 = 33, + V34 = 34, + V35 = 35, + V36 = 36, + V37 = 37, + V38 = 38, + V39 = 39, + V40 = 40, + V41 = 41, + V42 = 42, + V43 = 43, + V44 = 44, + V45 = 45, + V46 = 46, + V47 = 47, + V48 = 48, + V49 = 49, + V50 = 50, + V51 = 51, + V52 = 52, + V53 = 53, + V54 = 54, + V55 = 55, + V56 = 56, + V57 = 57, + V58 = 58, + V59 = 59, + V60 = 60, + V61 = 61, + V62 = 62, + V63 = 63, + V64 = 64, + V65 = 65, + V66 = 66, + V67 = 67, + V68 = 68, + V69 = 69, + V70 = 70, + V71 = 71, + V72 = 72, + V73 = 73, + V74 = 74, + V75 = 75, + V76 = 76, + V77 = 77, + V78 = 78, + V79 = 79, + V80 = 80, + V81 = 81, + V82 = 82, + V83 = 83, + V84 = 84, + V85 = 85, + V86 = 86, + V87 = 87, + V88 = 88, + V89 = 89, + V90 = 90, + V91 = 91, + V92 = 92, + V93 = 93, + V94 = 94, + V95 = 95, + V96 = 96, + V97 = 97, + V98 = 98, + V99 = 99, + V100 = 100, + V101 = 101, + V102 = 102, + V103 = 103, + V104 = 104, + V105 = 105, + V106 = 106, + V107 = 107, + V108 = 108, + V109 = 109, + V110 = 110, + V111 = 111, + V112 = 112, + V113 = 113, + V114 = 114, + V115 = 115, + V116 = 116, + V117 = 117, + V118 = 118, + V119 = 119, + V120 = 120, + V121 = 121, + V122 = 122, + V123 = 123, + V124 = 124, + V125 = 125, + V126 = 126, + V127 = 127, +} + +#[cfg(feature = "rkyv")] +const _: () = { + use rkyv::{Archive, Archived, Deserialize, Fallible, Resolver, Serialize}; + + #[allow(missing_debug_implementations)] // we cannot know if `Archived` implements `Debug` + #[allow(unreachable_pub)] + pub struct ArchivedU31(Archived); + + #[allow(missing_debug_implementations)] // we cannot know if `Resolver` implements `Debug` + #[allow(unreachable_pub)] + pub struct U31Resolver(Resolver); + + impl Archive for U31 { + type Archived = ArchivedU31; + + type Resolver = U31Resolver; + + #[inline] + unsafe fn resolve(&self, pos: usize, resolver: Self::Resolver, out: *mut Self::Archived) { + let nanos: &u32 = mem::transmute(&self); + Archive::resolve(nanos, pos, resolver.0, out.cast()); + } + } + + impl Deserialize for Archived { + #[inline] + fn deserialize(&self, deserializer: &mut D) -> Result::Error> { + let nanos = Deserialize::::deserialize(&self.0, deserializer)?; + Ok(unsafe { U31::new_unchecked(nanos) }) + } + } + + impl Serialize for U31 { + #[inline] + fn serialize(&self, serializer: &mut S) -> Result::Error> { + let nanos: &u32 = unsafe { mem::transmute(&self) }; + let nanos = Serialize::::serialize(nanos, serializer)?; + Ok(U31Resolver(nanos)) + } + } +}; + +#[cfg(test)] +#[test] +fn test_niche_optimization() { + use core::mem::size_of; + + use crate::{DateTime, NaiveDateTime, NaiveTime, Utc}; + + assert_eq!(size_of::(), size_of::>()); + assert_eq!(size_of::(), size_of::>()); + assert_eq!(size_of::>(), size_of::>>()); +}