Vendor dependencies

This commit is contained in:
2026-08-01 16:11:49 +03:00
parent 7f139a0241
commit 6b5e7f0f8b
29706 changed files with 9575646 additions and 0 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+273
View File
@@ -0,0 +1,273 @@
use crate::{
bounds::RangeError,
civil::{Date, Time},
macros::{ctry, unwrapr},
tz::Offset,
Timestamp,
};
/// A civil time of a day on a particular Gregorian date.
#[derive(Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct DateTime {
date: Date,
time: Time,
}
impl DateTime {
/// The minimum allowed Gregorian date and clock time.
pub const MIN: DateTime = DateTime { date: Date::MIN, time: Time::MIN };
/// The maximum allowed Gregorian date and clock time.
pub const MAX: DateTime = DateTime { date: Date::MAX, time: Time::MAX };
/// Creates a new civil datetime from its constituent components.
///
/// If any of the values are out of their supported ranges, then an
/// error is returned. Additionally, if `year`, `month` and `day` do not
/// correspond to a valid Gregorian date, then an error is returned.
#[inline]
pub const fn new(
year: i16,
month: i8,
day: i8,
hour: i8,
minute: i8,
second: i8,
subsec_nanosecond: i32,
) -> Result<DateTime, RangeError> {
let date = ctry!(Date::new(year, month, day));
let time = ctry!(Time::new(hour, minute, second, subsec_nanosecond));
Ok(DateTime::from_parts(date, time))
}
/// Creates a new `DateTime` from its [`Date`] and [`Time`] components.
#[inline]
pub const fn from_parts(date: Date, time: Time) -> DateTime {
DateTime { date, time }
}
/// Returns the Gregorian date component of this datetime.
#[inline]
pub const fn date(&self) -> Date {
self.date
}
/// Returns the civil time component of this datetime.
#[inline]
pub const fn time(&self) -> Time {
self.time
}
/// Adds the given number of seconds to this civil datetime.
///
/// This returns an error when the resulting datetime would exceed either
/// [`DateTime::MIN`] or [`DateTime::MAX`].
#[inline]
pub const fn checked_add_seconds(
&self,
seconds: i32,
) -> Result<DateTime, RangeError> {
let (second, added_days) =
ctry!(self.time().to_second().overflowing_add(seconds));
let date = ctry!(self.date().checked_add(added_days));
let time = unwrapr!(
second
.to_time()
.with_subsec_nanosecond(self.time().subsec_nanosecond()),
"subsec we started from hasn't change and must be valid",
);
Ok(DateTime::from_parts(date, time))
}
/// Like `DateTime::checked_add_seconds`, but arithmetic saturates to
/// either [`DateTime::MIN`] (when `seconds < 0`) or [`DateTime::MAX`]
/// (when `seconds > 0`).
#[inline]
pub const fn saturating_add_seconds(&self, seconds: i32) -> DateTime {
match self.checked_add_seconds(seconds) {
Ok(dt) => dt,
Err(_) => {
if seconds < 0 {
DateTime::MIN
} else {
DateTime::MAX
}
}
}
}
/// Converts this datetime, along with its offset from UTC, to a
/// corresponding Unix timestamp.
///
/// Note that unlike the reverse operation, [`Timestamp::to_datetime`],
/// this is fallible. This is by design. Namely, by making this routine
/// fallible at the boundaries, it permits the reverse operation to be
/// infallible. That is, all instants can be converted to a civil datetime
/// (appropriate for formatting), but not all civil datetimes combined with
/// all UTC offsets can be converted to an instant in time.
///
/// This errors when the timestamp returned would be outside the range
/// given by [`Timestamp::MIN`] and [`Timestamp::MAX`].
#[inline]
pub const fn to_timestamp(
&self,
offset: Offset,
) -> Result<Timestamp, RangeError> {
offset.to_timestamp(*self)
}
}
impl core::fmt::Debug for DateTime {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "{:?}T{:?}", self.date(), self.time())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn datetime(
year: i16,
month: i8,
day: i8,
hour: i8,
minute: i8,
second: i8,
subsec_nanosecond: i32,
) -> DateTime {
DateTime::new(
year,
month,
day,
hour,
minute,
second,
subsec_nanosecond,
)
.unwrap()
}
fn stamp(second: i64, subsec: i32) -> Timestamp {
Timestamp::new(second, subsec).unwrap()
}
fn offset(second: i32) -> Offset {
Offset::from_seconds(second).unwrap()
}
#[test]
fn checked_add_seconds() {
let dt = datetime(2026, 2, 25, 0, 0, 0, 0);
assert_eq!(
dt.checked_add_seconds(1),
Ok(datetime(2026, 2, 25, 0, 0, 1, 0))
);
assert_eq!(
dt.checked_add_seconds(86_399),
Ok(datetime(2026, 2, 25, 23, 59, 59, 0))
);
assert_eq!(
dt.checked_add_seconds(86_400),
Ok(datetime(2026, 2, 26, 0, 0, 0, 0))
);
assert_eq!(
dt.checked_add_seconds(-1),
Ok(datetime(2026, 2, 24, 23, 59, 59, 0))
);
assert_eq!(
dt.checked_add_seconds(-86_399),
Ok(datetime(2026, 2, 24, 0, 0, 1, 0))
);
assert_eq!(
dt.checked_add_seconds(-86_400),
Ok(datetime(2026, 2, 24, 0, 0, 0, 0))
);
let dt = datetime(2026, 2, 25, 0, 0, 0, 1);
assert_eq!(
dt.checked_add_seconds(1),
Ok(datetime(2026, 2, 25, 0, 0, 1, 1))
);
assert_eq!(
dt.checked_add_seconds(86_399),
Ok(datetime(2026, 2, 25, 23, 59, 59, 1))
);
assert_eq!(
dt.checked_add_seconds(86_400),
Ok(datetime(2026, 2, 26, 0, 0, 0, 1))
);
assert_eq!(
dt.checked_add_seconds(-1),
Ok(datetime(2026, 2, 24, 23, 59, 59, 1))
);
assert_eq!(
dt.checked_add_seconds(-86_399),
Ok(datetime(2026, 2, 24, 0, 0, 1, 1))
);
assert_eq!(
dt.checked_add_seconds(-86_400),
Ok(datetime(2026, 2, 24, 0, 0, 0, 1))
);
}
#[test]
fn to_timestamp_no_subsec() {
let dt = datetime(1970, 1, 1, 0, 0, 0, 0);
assert_eq!(dt.to_timestamp(offset(0)), Ok(stamp(0, 0)));
assert_eq!(dt.to_timestamp(offset(3600)), Ok(stamp(-3600, 0)));
assert_eq!(dt.to_timestamp(offset(-3600)), Ok(stamp(3600, 0)));
let dt = datetime(1969, 12, 31, 23, 30, 0, 0);
assert_eq!(dt.to_timestamp(offset(0)), Ok(stamp(-1800, 0)));
assert_eq!(dt.to_timestamp(offset(3600)), Ok(stamp(-5400, 0)));
assert_eq!(dt.to_timestamp(offset(-3600)), Ok(stamp(1800, 0)));
let dt = datetime(1970, 1, 1, 0, 30, 0, 0);
assert_eq!(dt.to_timestamp(offset(0)), Ok(stamp(1800, 0)));
assert_eq!(dt.to_timestamp(offset(3600)), Ok(stamp(-1800, 0)));
assert_eq!(dt.to_timestamp(offset(-3600)), Ok(stamp(5400, 0)));
}
#[test]
fn to_timestamp_with_subsec() {
let dt = datetime(1970, 1, 1, 0, 0, 0, 123);
assert_eq!(dt.to_timestamp(offset(0)), Ok(stamp(0, 123)));
assert_eq!(
dt.to_timestamp(offset(3600)),
Ok(stamp(-3599, -999_999_877))
);
assert_eq!(dt.to_timestamp(offset(-3600)), Ok(stamp(3600, 123)));
let dt = datetime(1969, 12, 31, 23, 30, 0, 123);
assert_eq!(dt.to_timestamp(offset(0)), Ok(stamp(-1799, -999_999_877)));
assert_eq!(
dt.to_timestamp(offset(3600)),
Ok(stamp(-5399, -999_999_877))
);
assert_eq!(dt.to_timestamp(offset(-3600)), Ok(stamp(1800, 123)));
let dt = datetime(1970, 1, 1, 0, 30, 0, 123);
assert_eq!(dt.to_timestamp(offset(0)), Ok(stamp(1800, 123)));
assert_eq!(
dt.to_timestamp(offset(3600)),
Ok(stamp(-1799, -999_999_877))
);
assert_eq!(dt.to_timestamp(offset(-3600)), Ok(stamp(5400, 123)));
}
#[test]
fn to_timestamp_err() {
let dt = datetime(-9999, 1, 1, 0, 0, 0, 0);
assert_eq!(dt.to_timestamp(Offset::MIN), Ok(Timestamp::MIN));
assert!(dt.to_timestamp(offset(Offset::MIN.seconds() + 1)).is_err());
let dt = datetime(9999, 12, 31, 23, 59, 59, 999_999_999);
assert_eq!(dt.to_timestamp(Offset::MAX), Ok(Timestamp::MAX));
assert!(dt.to_timestamp(offset(Offset::MAX.seconds() - 1)).is_err());
let dt = datetime(9999, 12, 31, 23, 59, 59, 0);
assert!(dt.to_timestamp(offset(Offset::MAX.seconds() - 1)).is_err());
}
}
+289
View File
@@ -0,0 +1,289 @@
/*!
Primitives for civil dates and times.
*/
pub use self::{
date::{Date, ISOWeekDate, UnixEpochDay},
datetime::DateTime,
time::{Time, TimeNanosecond, TimeSecond},
weekday::{Weekday, WeekdaysForward, WeekdaysReverse},
};
use crate::macros::unwrapr;
mod date;
mod datetime;
mod time;
mod weekday;
/// Creates a new `DateTime` value in a `const` context.
///
/// This is a convenience free function for [`DateTime::new`] that panics when
/// the given datetime is not valid. It is intended to provide a terse syntax
/// for constructing `DateTime` values from parameters that are known to be
/// valid.
///
/// # Panics
///
/// This routine panics when [`DateTime::new`] would return an error. That
/// is, when the given components do not correspond to a valid datetime.
/// Namely, all of the following must be true:
///
/// * The year must be in the range `-9999..=9999`.
/// * The month must be in the range `1..=12`.
/// * The day must be at least `1` and must be at most the number of days
/// in the corresponding month. So for example, `2024-02-29` is valid but
/// `2023-02-29` is not.
/// * `0 <= hour <= 23`
/// * `0 <= minute <= 59`
/// * `0 <= second <= 59`
/// * `0 <= subsec_nanosecond <= 999,999,999`
///
/// Similarly, when used in a const context, invalid parameters will prevent
/// your Rust program from compiling.
///
/// # Example
///
/// ```
/// use jiff_core::civil::datetime;
///
/// let dt = datetime(2024, 2, 29, 21, 30, 5, 123_456_789);
/// assert_eq!(dt.date().year(), 2024);
/// assert_eq!(dt.date().month(), 2);
/// assert_eq!(dt.date().day(), 29);
/// assert_eq!(dt.time().hour(), 21);
/// assert_eq!(dt.time().minute(), 30);
/// assert_eq!(dt.time().second(), 5);
/// assert_eq!(dt.time().millisecond(), 123);
/// assert_eq!(dt.time().microsecond(), 456);
/// assert_eq!(dt.time().nanosecond(), 789);
/// ```
#[inline]
pub const fn datetime(
year: i16,
month: i8,
day: i8,
hour: i8,
minute: i8,
second: i8,
subsec_nanosecond: i32,
) -> DateTime {
unwrapr!(
DateTime::new(
year,
month,
day,
hour,
minute,
second,
subsec_nanosecond,
),
"invalid datetime"
)
}
/// Creates a new `Date` value in a `const` context.
///
/// This is a convenience free function for [`Date::new`] that panics when
/// the given date is not valid. It is intended to provide a terse syntax for
/// constructing `Date` values from parameters that are known to be valid.
///
/// # Panics
///
/// This routine panics when [`Date::new`] would return an error. That is,
/// when the given year-month-day does not correspond to a valid date.
/// Namely, all of the following must be true:
///
/// * The year must be in the range `-9999..=9999`.
/// * The month must be in the range `1..=12`.
/// * The day must be at least `1` and must be at most the number of days
/// in the corresponding month. So for example, `2024-02-29` is valid but
/// `2023-02-29` is not.
///
/// Similarly, when used in a const context, invalid parameters will prevent
/// your Rust program from compiling.
///
/// # Example
///
/// ```
/// use jiff_core::civil::date;
///
/// let d = date(2024, 2, 29);
/// assert_eq!(d.year(), 2024);
/// assert_eq!(d.month(), 2);
/// assert_eq!(d.day(), 29);
/// ```
#[inline]
pub const fn date(year: i16, month: i8, day: i8) -> Date {
unwrapr!(Date::new(year, month, day), "invalid date")
}
/// Creates a new `Time` value in a `const` context.
///
/// This is a convenience free function for [`Time::new`] that panics when
/// the given time is not valid. It is intended to provide a terse syntax for
/// constructing `Time` values from parameters that are known to be valid.
///
/// # Panics
///
/// This panics if the given values do not correspond to a valid `Time`.
/// All of the following conditions must be true:
///
/// * `0 <= hour <= 23`
/// * `0 <= minute <= 59`
/// * `0 <= second <= 59`
/// * `0 <= subsec_nanosecond <= 999,999,999`
///
/// Similarly, when used in a const context, invalid parameters will
/// prevent your Rust program from compiling.
///
/// # Example
///
/// This shows an example of a valid time in a `const` context:
///
/// ```
/// use jiff_core::civil::time;
///
/// let t = time(21, 30, 5, 123_456_789);
/// assert_eq!(t.hour(), 21);
/// assert_eq!(t.minute(), 30);
/// assert_eq!(t.second(), 5);
/// assert_eq!(t.millisecond(), 123);
/// assert_eq!(t.microsecond(), 456);
/// assert_eq!(t.nanosecond(), 789);
/// assert_eq!(t.subsec_nanosecond(), 123_456_789);
/// ```
#[inline]
pub const fn time(
hour: i8,
minute: i8,
second: i8,
subsec_nanosecond: i32,
) -> Time {
unwrapr!(
Time::new(hour, minute, second, subsec_nanosecond),
"invalid time"
)
}
/// Returns true if and only if the given year is a leap year.
///
/// A leap year is a year with 366 days. Non-leap years have 365 days.
#[inline]
pub const fn is_leap_year(year: i16) -> bool {
// From: https://github.com/BurntSushi/jiff/pull/23
let d = if year % 25 != 0 { 4 } else { 16 };
(year % d) == 0
}
/// Return the number of days in the given year.
///
/// This is guaranteed to either return `365` or `366`.
#[inline]
pub const fn days_in_year(year: i16) -> i16 {
if is_leap_year(year) {
366
} else {
365
}
}
/// Return the number of days in the given month.
///
/// This is guaranteed to return a value in the range `1..=31`.
#[inline]
pub const fn days_in_month(year: i16, month: i8) -> i8 {
// From: https://github.com/BurntSushi/jiff/pull/23
if month == 2 {
if is_leap_year(year) {
29
} else {
28
}
} else {
30 | (month ^ month >> 3)
}
}
/// Returns true if the given ISO 8601 week date year is a "long" year or not.
///
/// A "long" year is a year with 53 weeks. Otherwise, it's a "short" year
/// with 52 weeks.
#[inline]
pub const fn is_long_iso_week_year(year: i16) -> bool {
// Inspired by: https://en.wikipedia.org/wiki/ISO_week_date#Weeks_per_year
let last =
unwrapr!(Date::new(year, 12, 31), "last day of year is always valid");
let weekday = last.weekday();
matches!(weekday, Weekday::Thursday)
|| (last.in_leap_year() && matches!(weekday, Weekday::Friday))
}
/// Returns the total number of weeks in the year of this ISO 8601 week
/// date.
///
/// It is guaranteed that the value returned is either 52 or 53. The
/// latter case occurs precisely when [`ISOWeekDate::in_long_year`]
/// returns `true`.
#[inline]
pub const fn weeks_in_iso_week_year(year: i16) -> i8 {
if is_long_iso_week_year(year) {
53
} else {
52
}
}
#[cfg(test)]
mod tests {
use super::*;
static LEAPS: &[i16] = &[-400, -104, -4, 0, 1904, 2000, 2004, 2024];
static NOT_LEAPS: &[i16] = &[-401, -100, -1, 1900, 1999, 2001, 2002, 2003];
#[test]
fn year_leap() {
for &y in LEAPS {
assert!(is_leap_year(y), "{y} should be a leap year");
}
for &y in NOT_LEAPS {
assert!(!is_leap_year(y), "{y} should NOT be a leap year");
}
}
#[test]
fn year_days() {
for &y in LEAPS {
assert_eq!(days_in_year(y), 366, "{y} should be a leap year");
}
for &y in NOT_LEAPS {
assert_eq!(days_in_year(y), 365, "{y} should NOT be a leap year");
}
}
#[test]
fn month_days() {
assert_eq!(days_in_month(2001, 1), 31);
assert_eq!(days_in_month(2001, 2), 28);
assert_eq!(days_in_month(2001, 3), 31);
assert_eq!(days_in_month(2001, 4), 30);
assert_eq!(days_in_month(2001, 5), 31);
assert_eq!(days_in_month(2001, 6), 30);
assert_eq!(days_in_month(2001, 7), 31);
assert_eq!(days_in_month(2001, 8), 31);
assert_eq!(days_in_month(2001, 9), 30);
assert_eq!(days_in_month(2001, 10), 31);
assert_eq!(days_in_month(2001, 11), 30);
assert_eq!(days_in_month(2001, 12), 31);
for &y in LEAPS {
assert_eq!(days_in_month(y, 2), 29, "{y} should be a leap year");
}
for &y in NOT_LEAPS {
assert_eq!(
days_in_month(y, 2),
28,
"{y} should NOT be a leap year"
);
}
}
}
+652
View File
@@ -0,0 +1,652 @@
use crate::{
bounds::{self as b, RangeError},
civil::{self, DateTime},
constants as c,
macros::{rbail, rtry, unwrapr},
};
/// The civil time of day.
///
/// This time's representation uses nanosecond precision. The full range of
/// clock values are `00:00:00.000000000` to `23:59:59.999999999` inclusive.
#[derive(Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Time {
hour: i8,
minute: i8,
second: i8,
subsec_nanosecond: i32,
}
impl Time {
/// The minimum allowed civil time.
///
/// This corresponds to midnight.
pub const MIN: Time =
Time { hour: 0, minute: 0, second: 0, subsec_nanosecond: 0 };
/// The maximum allowed civil time.
///
/// This corresponds to the last nanosecond in a civil day.
pub const MAX: Time = Time {
hour: 23,
minute: 59,
second: 59,
subsec_nanosecond: 999_999_999,
};
/// Creates a new civil time from its constituent components.
///
/// If any of the values are out of their supported ranges, then an error
/// is returned.
#[inline]
pub const fn new(
hour: i8,
minute: i8,
second: i8,
subsec_nanosecond: i32,
) -> Result<Time, RangeError> {
let hour = rtry!(b::Hour::checkc(hour as i64));
let minute = rtry!(b::Minute::checkc(minute as i64));
let second = rtry!(b::Second::checkc(second as i64));
let subsec_nanosecond =
rtry!(b::SubsecNanosecond::checkc(subsec_nanosecond as i64));
Ok(Time { hour, minute, second, subsec_nanosecond })
}
/// Returns the hour component of this civil time.
///
/// The value returned is guaranteed to be in the range specified by
/// [`Hour`](crate::bounds::Hour).
#[inline]
pub const fn hour(self) -> i8 {
self.hour
}
/// Returns the minute component of this civil time.
///
/// The value returned is guaranteed to be in the range specified by
/// [`Minute`](crate::bounds::Minute).
#[inline]
pub const fn minute(self) -> i8 {
self.minute
}
/// Returns the second component of this civil time.
///
/// The value returned is guaranteed to be in the range specified by
/// [`Second`](crate::bounds::Second).
#[inline]
pub const fn second(self) -> i8 {
self.second
}
/// Returns the "millisecond" component of this time.
///
/// The value returned is guaranteed to be in the range `0..=999`.
#[inline]
pub const fn millisecond(self) -> i16 {
(self.subsec_nanosecond() as u32 / c::NANOS_PER_MILLI_32 as u32) as i16
}
/// Returns the "microsecond" component of this time.
///
/// The value returned is guaranteed to be in the range `0..=999`.
#[inline]
pub const fn microsecond(self) -> i16 {
((self.subsec_nanosecond() as u32 / c::NANOS_PER_MICRO_32 as u32)
% c::MICROS_PER_MILLI_32 as u32) as i16
}
/// Returns the "nanosecond" component of this time.
///
/// The value returned is guaranteed to be in the range `0..=999`.
#[inline]
pub const fn nanosecond(self) -> i16 {
(self.subsec_nanosecond() as u32 % c::NANOS_PER_MICRO_32 as u32) as i16
}
/// Returns the fractional second (to nanosecond precision) component of
/// this civil time.
///
/// The value returned is guaranteed to be in the range specified by
/// [`SubsecNanosecond`](crate::bounds::SubsecNanosecond).
#[inline]
pub const fn subsec_nanosecond(self) -> i32 {
self.subsec_nanosecond
}
/// Returns this time with its subsecond component replaced with the three
/// given subsecond components.
///
/// If any of the given components are out of range (`0..=999`), then an
/// error is returned.
#[inline]
pub const fn with_subsec_parts(
self,
millisecond: i16,
microsecond: i16,
nanosecond: i16,
) -> Result<Time, RangeError> {
let millisecond = rtry!(b::Millisecond::checkc(millisecond as i64));
let microsecond = rtry!(b::Microsecond::checkc(microsecond as i64));
let nanosecond = rtry!(b::Nanosecond::checkc(nanosecond as i64));
let subsec_nanosecond = (millisecond as i32 * c::NANOS_PER_MILLI_32)
+ (microsecond as i32 * c::NANOS_PER_MICRO_32)
+ (nanosecond as i32);
Ok(Time { subsec_nanosecond, ..self })
}
/// Returns this time with its subsecond component replaced with the
/// nanosecond component given.
///
/// If the number of nanoseconds is out of range (`0..=999_999_999`), then
/// an error is returned.
#[inline]
pub const fn with_subsec_nanosecond(
self,
subsec_nanosecond: i32,
) -> Result<Time, RangeError> {
let subsec_nanosecond =
rtry!(b::SubsecNanosecond::checkc(subsec_nanosecond as i64));
Ok(Time { subsec_nanosecond, ..self })
}
/// Converts this civil time to a second value corresponding to the number
/// of seconds that has elapsed since midnight until this time. If this
/// time is midnight, then the second value returned is `0`.
///
/// Note that this drops any subsecond component on this civil time.
///
/// The value returned is guaranteed to be in the range specified by
/// [`CivilDaySecond`](crate::bounds::CivilDaySecond).
#[inline]
pub const fn to_second(self) -> TimeSecond {
let mut second: i32 = 0;
second += (self.hour() as i32) * 3600;
second += (self.minute() as i32) * 60;
second += self.second() as i32;
TimeSecond { second }
}
/// Converts this civil time to a nanosecond value corresponding to the
/// number of nanoseconds that has elapsed since midnight until this time.
/// If this time is midnight, then the nanosecond value returned is `0`.
///
/// The value returned is guaranteed to be in the range specified by
/// [`CivilDayNanosecond`](crate::bounds::CivilDayNanosecond).
#[inline]
pub const fn to_nanosecond(self) -> TimeNanosecond {
let mut nanosecond: i64 = 0;
nanosecond += (self.hour() as i64) * 3_600_000_000_000;
nanosecond += (self.minute() as i64) * 60_000_000_000;
nanosecond += (self.second() as i64) * 1_000_000_000;
nanosecond += self.subsec_nanosecond() as i64;
TimeNanosecond { nanosecond }
}
/// A convenience function for constructing a [`DateTime`] from this time
/// on the date given by its components.
///
/// # Panics
///
/// This routine panics when [`Date::new`](crate::civil::Date) with
/// the given inputs would return an error. That is, when the given
/// year-month-day does not correspond to a valid date. Namely, all of the
/// following must be true:
///
/// * The year must be in the range `-9999..=9999`.
/// * The month must be in the range `1..=12`.
/// * The day must be at least `1` and must be at most the number of days
/// in the corresponding month. So for example, `2024-02-29` is valid but
/// `2023-02-29` is not.
///
/// Similarly, when used in a const context, invalid parameters will
/// prevent your Rust program from compiling.
#[inline]
pub const fn on(self, year: i16, month: i8, day: i8) -> DateTime {
DateTime::from_parts(civil::date(year, month, day), self)
}
}
impl core::fmt::Debug for Time {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(
f,
"{:02}:{:02}:{:02}",
self.hour(),
self.minute(),
self.second()
)?;
let mut subsec = self.subsec_nanosecond();
if subsec == 0 {
return Ok(());
}
// This is really annoying. But we don't have Jiff's formatting
// facilities to handle this for us. We should also support precision
// settings from `Formatter`.
let mut buf = [b'0'; 9];
for i in (0..9).rev() {
buf[i] += (subsec % 10) as u8;
subsec /= 10;
}
let mut end = 9;
while end > 0 && buf[usize::from(end) - 1] == b'0' {
end -= 1;
}
// OK because `buf` only ever contains ASCII digits.
let fractional_digits = core::str::from_utf8(&buf[..end]).unwrap();
write!(f, ".{fractional_digits}")
}
}
#[cfg(test)]
impl quickcheck::Arbitrary for Time {
fn arbitrary(g: &mut quickcheck::Gen) -> Time {
let hour = b::Hour::arbitrary(g);
let minute = b::Minute::arbitrary(g);
let second = b::Second::arbitrary(g);
let subsec_nanosecond = b::SubsecNanosecond::arbitrary(g);
Time::new(hour, minute, second, subsec_nanosecond).unwrap()
}
fn shrink(&self) -> alloc::boxed::Box<dyn Iterator<Item = Time>> {
alloc::boxed::Box::new(
(
self.hour(),
self.minute(),
self.second(),
self.subsec_nanosecond(),
)
.shrink()
.filter_map(
|(hour, minute, second, subsec_nanosecond)| {
Time::new(hour, minute, second, subsec_nanosecond).ok()
},
),
)
}
}
/// Represents a single point in a civil day, to second precision.
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct TimeSecond {
second: i32,
}
impl TimeSecond {
/// Creates a new civil time from a given second value.
///
/// The value must correspond to the number of seconds elapsed since
/// the start of a civil day. It cannot exceed the length of a civil day
/// (in seconds).
#[inline]
pub const fn new(second: i32) -> Result<TimeSecond, RangeError> {
let second = rtry!(b::CivilDaySecond::checkc(second as i64));
Ok(TimeSecond { second })
}
/// Creates a new civil time from a given second value.
///
/// This panics when `second` exceeds the maximum number of seconds in a
/// single civil day.
#[inline]
pub const fn constant(second: i32) -> TimeSecond {
unwrapr!(TimeSecond::new(second), "invalid civil day second")
}
/// Returns the second value.
///
/// The value returned is guaranteed to be in the range specified by
/// [`CivilDaySecond`](crate::bounds::CivilDaySecond).
#[inline]
pub const fn second(self) -> i32 {
self.second
}
/// Adds the given number of seconds to this civil time and returns the
/// resulting civil time with any overflowing amount in units of civil
/// days.
///
/// This returns an error when integer overflow occurs. For example, when
/// `seconds` is `i32::MAX` and this civil time is any time other than
/// midnight.
///
/// Note that the number of days returned may exceed the range supported
/// by [`UnixEpochDay`](crate::civil::UnixEpochDay). Moreover, the number
/// of days returned may be negative. This occurs only when `seconds` is
/// negative enough to result in the time wrapping around at `0`.
#[inline]
pub const fn overflowing_add(
self,
seconds: i32,
) -> Result<(TimeSecond, i32), RangeError> {
let Some(sum) = self.second().checked_add(seconds) else {
rbail!(b::CivilDaySecond::error());
};
let days = sum.div_euclid(c::SECS_PER_CIVIL_DAY_32);
let rem = sum.rem_euclid(c::SECS_PER_CIVIL_DAY_32);
Ok((TimeSecond { second: rem }, days))
}
/// Converts this second representation of a civil time into the
/// components of a civil time.
///
/// The subsecond component on the `Time` returned is always `0`.
#[inline]
pub const fn to_time(&self) -> Time {
let mut second = self.second as u32;
let mut time = Time::MIN;
if second != 0 {
time.hour = (second / 3600) as i8;
second %= 3600;
if second != 0 {
time.minute = (second / 60) as i8;
time.second = (second % 60) as i8;
}
}
time
}
}
/// Represents a single point in a civil day, to nanosecond precision.
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct TimeNanosecond {
nanosecond: i64,
}
impl TimeNanosecond {
/// Creates a new civil time from a given nanosecond value.
///
/// The value must correspond to the number of nanoseconds elapsed since
/// the start of a civil day. It cannot exceed the length of a civil day
/// (in nanoseconds).
#[inline]
pub const fn new(nanosecond: i64) -> Result<TimeNanosecond, RangeError> {
let nanosecond =
rtry!(b::CivilDayNanosecond::checkc(nanosecond as i64));
Ok(TimeNanosecond { nanosecond })
}
/// Returns the nanosecond value.
///
/// The value returned is guaranteed to be in the range specified by
/// [`CivilDayNanosecond`](crate::bounds::CivilDayNanosecond).
#[inline]
pub const fn nanosecond(self) -> i64 {
self.nanosecond
}
/// Adds the given number of nanoseconds to this civil time and returns the
/// resulting civil time with any overflowing amount in units of civil
/// days.
///
/// This returns an error when integer overflow occurs. For example, when
/// `seconds` is `i64::MAX` and this civil time is any time other than
/// midnight.
///
/// Note that the number of days returned may exceed the range supported by
/// [`UnixEpochDay`](crate::civil::UnixEpochDay). Moreover, the number of
/// days returned may be negative. This occurs only when `nanoseconds` is
/// negative enough to result in the time wrapping around at `0`.
#[inline]
pub const fn overflowing_add(
self,
nanoseconds: i64,
) -> Result<(TimeNanosecond, i64), RangeError> {
let Some(sum) = self.nanosecond().checked_add(nanoseconds) else {
rbail!(b::CivilDayNanosecond::error());
};
let days = sum.div_euclid(c::NANOS_PER_CIVIL_DAY);
let rem = sum.rem_euclid(c::NANOS_PER_CIVIL_DAY);
Ok((TimeNanosecond { nanosecond: rem }, days))
}
/// Converts this second representation of a civil time into the
/// components of a civil time.
///
/// The subsecond component on the `Time` returned is always `0`.
#[inline]
pub const fn to_time(&self) -> Time {
let mut nanosecond = self.nanosecond as u64;
let mut time = Time::MIN;
if nanosecond != 0 {
time.hour = (nanosecond / 3_600_000_000_000) as i8;
nanosecond %= 3_600_000_000_000;
if nanosecond != 0 {
time.minute = (nanosecond / 60_000_000_000) as i8;
nanosecond %= 60_000_000_000;
if nanosecond != 0 {
time.second = (nanosecond / 1_000_000_000) as i8;
time.subsec_nanosecond =
(nanosecond % 1_000_000_000) as i32;
}
}
}
time
}
}
#[cfg(test)]
mod tests {
use super::*;
fn time(hour: i8, minute: i8, second: i8) -> Time {
timesub(hour, minute, second, 0)
}
fn timesub(hour: i8, minute: i8, second: i8, subsec: i32) -> Time {
Time::new(hour, minute, second, subsec).unwrap()
}
fn timesec(second: i32) -> TimeSecond {
TimeSecond::new(second).unwrap()
}
fn timenano(nanosecond: i64) -> TimeNanosecond {
TimeNanosecond::new(nanosecond).unwrap()
}
#[test]
fn time_to_second_various() {
let t = time(0, 0, 0);
assert_eq!(t.to_second().second(), 0);
let t = timesub(0, 0, 0, 1);
assert_eq!(t.to_second().second(), 0);
let t = timesub(0, 0, 0, 999_999_999);
assert_eq!(t.to_second().second(), 0);
let t = time(0, 0, 1);
assert_eq!(t.to_second().second(), 1);
let t = time(0, 1, 1);
assert_eq!(t.to_second().second(), 60 + 1);
let t = time(1, 1, 1);
assert_eq!(t.to_second().second(), 3600 + 60 + 1);
let t = time(23, 59, 59);
assert_eq!(t.to_second().second(), 86_399);
}
#[test]
fn second_to_time_various() {
let ts = timesec(0);
assert_eq!(ts.to_time(), time(0, 0, 0));
let ts = timesec(1);
assert_eq!(ts.to_time(), time(0, 0, 1));
let ts = timesec(60 + 1);
assert_eq!(ts.to_time(), time(0, 1, 1));
let ts = timesec(3600 + 60 + 1);
assert_eq!(ts.to_time(), time(1, 1, 1));
let ts = timesec(86_399);
assert_eq!(ts.to_time(), time(23, 59, 59));
}
#[test]
fn time_to_nanosecond_various() {
let t = timesub(0, 0, 0, 0);
assert_eq!(t.to_nanosecond().nanosecond(), 0);
let t = timesub(0, 0, 0, 1);
assert_eq!(t.to_nanosecond().nanosecond(), 1);
let t = timesub(0, 0, 0, 999_999_999);
assert_eq!(t.to_nanosecond().nanosecond(), 999_999_999);
let t = timesub(0, 0, 1, 1);
assert_eq!(t.to_nanosecond().nanosecond(), 1_000_000_000 + 1);
let t = timesub(0, 1, 1, 1);
assert_eq!(
t.to_nanosecond().nanosecond(),
(60 + 1) * 1_000_000_000 + 1
);
let t = timesub(1, 1, 1, 1);
assert_eq!(
t.to_nanosecond().nanosecond(),
(3600 + 60 + 1) * 1_000_000_000 + 1
);
let t = timesub(23, 59, 59, 1);
assert_eq!(t.to_nanosecond().nanosecond(), 86_399 * 1_000_000_000 + 1);
let t = timesub(23, 59, 59, 999_999_999);
assert_eq!(
t.to_nanosecond().nanosecond(),
86_399 * 1_000_000_000 + 999_999_999
);
}
#[test]
fn nanosecond_to_time_various() {
let ts = timenano(0);
assert_eq!(ts.to_time(), timesub(0, 0, 0, 0));
let ts = timenano(1);
assert_eq!(ts.to_time(), timesub(0, 0, 0, 1));
let ts = timenano(1_000_000_000 + 1);
assert_eq!(ts.to_time(), timesub(0, 0, 1, 1));
let ts = timenano(61 * 1_000_000_000 + 1);
assert_eq!(ts.to_time(), timesub(0, 1, 1, 1));
let ts = timenano((3600 + 60 + 1) * 1_000_000_000 + 1);
assert_eq!(ts.to_time(), timesub(1, 1, 1, 1));
let ts = timenano(86_399 * 1_000_000_000);
assert_eq!(ts.to_time(), timesub(23, 59, 59, 0));
let ts = timenano(86_399 * 1_000_000_000 + 1);
assert_eq!(ts.to_time(), timesub(23, 59, 59, 1));
let ts = timenano(86_399 * 1_000_000_000 + 999_999_999);
assert_eq!(ts.to_time(), timesub(23, 59, 59, 999_999_999));
}
#[test]
fn second_overflowing_add() {
let ts = timesec(0);
assert_eq!(ts.overflowing_add(86_399), Ok((timesec(86_399), 0)));
assert_eq!(ts.overflowing_add(86_400), Ok((timesec(0), 1)));
assert_eq!(ts.overflowing_add(86_401), Ok((timesec(1), 1)));
assert_eq!(ts.overflowing_add(i32::MAX), Ok((timesec(11_647), 24855)));
assert_eq!(ts.overflowing_add(-1), Ok((timesec(86_399), -1)));
assert_eq!(ts.overflowing_add(-86_399), Ok((timesec(1), -1)));
assert_eq!(ts.overflowing_add(-86_400), Ok((timesec(0), -1)));
assert_eq!(ts.overflowing_add(-86_401), Ok((timesec(86_399), -2)));
assert_eq!(
ts.overflowing_add(i32::MIN),
Ok((timesec(74_752), -24856))
);
let ts = timesec(86_399);
assert_eq!(
ts.overflowing_add(i32::MIN),
Ok((timesec(74_751), -24855))
);
let ts = timesec(1);
assert!(ts.overflowing_add(i32::MAX).is_err());
}
#[test]
fn nanosecond_overflowing_add() {
let ts = timenano(0);
assert_eq!(
ts.overflowing_add(86_399_000_000_000),
Ok((timenano(86_399_000_000_000), 0))
);
assert_eq!(
ts.overflowing_add(86_400_000_000_000),
Ok((timenano(0), 1))
);
assert_eq!(
ts.overflowing_add(86_401_000_000_000),
Ok((timenano(1_000_000_000), 1))
);
assert_eq!(
ts.overflowing_add(i64::MAX),
Ok((timenano(85_636_854_775_807), 106_751))
);
assert_eq!(
ts.overflowing_add(-1),
Ok((timenano(86_399_999_999_999), -1))
);
assert_eq!(
ts.overflowing_add(-1_000_000_000),
Ok((timenano(86_399_000_000_000), -1))
);
assert_eq!(
ts.overflowing_add(-86_399_000_000_000),
Ok((timenano(1_000_000_000), -1))
);
assert_eq!(
ts.overflowing_add(-86_400_000_000_000),
Ok((timenano(0), -1))
);
assert_eq!(
ts.overflowing_add(-86_401_000_000_000),
Ok((timenano(86_399_000_000_000), -2))
);
assert_eq!(
ts.overflowing_add(i64::MIN),
Ok((timenano(763_145_224_192), -106_752))
);
let ts = timenano(86_399_000_000_000);
assert_eq!(
ts.overflowing_add(i64::MIN),
Ok((timenano(762_145_224_192), -106_751))
);
let ts = timenano(1_000_000_000);
assert!(ts.overflowing_add(i64::MAX).is_err());
let ts = timenano(1);
assert!(ts.overflowing_add(i64::MAX).is_err());
}
quickcheck::quickcheck! {
fn prop_time_to_second_roundtrip(t: Time) -> bool {
let t = Time::new(t.hour(), t.minute(), t.second(), 0).unwrap();
t == t.to_second().to_time()
}
fn prop_time_to_nanosecond_roundtrip(t: Time) -> bool {
t == t.to_nanosecond().to_time()
}
}
}
+543
View File
@@ -0,0 +1,543 @@
use crate::{
bounds::{self as b, RangeError},
macros::{rbail, unwrapr},
};
/// A representation for the day of the week.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
#[allow(missing_docs)]
pub enum Weekday {
Monday = 1,
Tuesday = 2,
Wednesday = 3,
Thursday = 4,
Friday = 5,
Saturday = 6,
Sunday = 7,
}
impl Weekday {
/// Convert a 0-offset to a `Weekday`. Monday corresponds to offset `0` and
/// Sunday corresponds to offset `6`.
#[inline]
pub const fn from_monday_zero_offset(
offset: i8,
) -> Result<Weekday, RangeError> {
Ok(match offset {
0 => Weekday::Monday,
1 => Weekday::Tuesday,
2 => Weekday::Wednesday,
3 => Weekday::Thursday,
4 => Weekday::Friday,
5 => Weekday::Saturday,
6 => Weekday::Sunday,
_ => rbail!(b::WeekdayMondayZero::error()),
})
}
/// Convert a 1-offset to a `Weekday`. Monday corresponds to offset `1` and
/// Sunday corresponds to offset `7`.
#[inline]
pub const fn from_monday_one_offset(
offset: i8,
) -> Result<Weekday, RangeError> {
Ok(match offset {
1 => Weekday::Monday,
2 => Weekday::Tuesday,
3 => Weekday::Wednesday,
4 => Weekday::Thursday,
5 => Weekday::Friday,
6 => Weekday::Saturday,
7 => Weekday::Sunday,
_ => rbail!(b::WeekdayMondayOne::error()),
})
}
/// Convert a 0-offset to a `Weekday`. Sunday corresponds to offset `0` and
/// Saturday corresponds to offset `6`.
#[inline]
pub const fn from_sunday_zero_offset(
offset: i8,
) -> Result<Weekday, RangeError> {
Ok(match offset {
0 => Weekday::Sunday,
1 => Weekday::Monday,
2 => Weekday::Tuesday,
3 => Weekday::Wednesday,
4 => Weekday::Thursday,
5 => Weekday::Friday,
6 => Weekday::Saturday,
_ => rbail!(b::WeekdaySundayZero::error()),
})
}
/// Convert a 1-offset to a `Weekday`. Sunday corresponds to offset `1` and
/// Saturday corresponds to offset `7`.
#[inline]
pub const fn from_sunday_one_offset(
offset: i8,
) -> Result<Weekday, RangeError> {
Ok(match offset {
1 => Weekday::Sunday,
2 => Weekday::Monday,
3 => Weekday::Tuesday,
4 => Weekday::Wednesday,
5 => Weekday::Thursday,
6 => Weekday::Friday,
7 => Weekday::Saturday,
_ => rbail!(b::WeekdaySundayOne::error()),
})
}
/// Returns the weekday as a 0-offset. Monday corresponds to offset `0`
/// and Sunday corresponds to offset `6`.
#[inline]
pub const fn to_monday_zero_offset(self) -> i8 {
self.to_monday_one_offset() - 1
}
/// Returns the weekday as a 1-offset. Monday corresponds to offset `1`
/// and Sunday corresponds to offset `7`.
#[inline]
pub const fn to_monday_one_offset(self) -> i8 {
self as i8
}
/// Returns the weekday as a 0-offset. Sunday corresponds to offset `0`
/// and Saturday corresponds to offset `6`.
#[inline]
pub const fn to_sunday_zero_offset(self) -> i8 {
let offset = self.to_monday_one_offset();
if offset == 7 {
0
} else {
offset
}
}
/// Returns the weekday as a 1-offset. Sunday corresponds to offset `1`
/// and Saturday corresponds to offset `7`.
#[inline]
pub const fn to_sunday_one_offset(self) -> i8 {
self.to_sunday_zero_offset() + 1
}
/// Add the given number of days to this weekday, using wrapping arithmetic,
/// and return the resulting weekday.
///
/// Adding a multiple of `7` (including `0`) is guaranteed to produce the
/// same weekday as this one.
#[inline]
pub const fn wrapping_add(self, days: i64) -> Weekday {
let start = self.to_monday_zero_offset() as i64;
// We are careful to `rem_euclid` on `rhs` before doing
// wrapping arithmetic, otherwise the result is not
// correct. Namely, it would assume that, e.g., since
// `i64::MAX.rem_euclid(7)` is 0, then the next value would be
// `rem_euclid(7) == 1`. But `i64::MIN.rem_euclid(7)` is 6.
let end = (start.wrapping_add(days.rem_euclid(7)) % 7) as i8;
// Always valid because of the mod 7 above.
unwrapr!(
Weekday::from_monday_zero_offset(end),
"weekday is always 0..=6",
)
}
/// Subtract the given number of days from this weekday, using wrapping
/// arithmetic, and return the resulting weekday.
///
/// Subtracting a multiple of `7` (including `0`) is guaranteed to produce
/// the same weekday as this one.
#[inline]
pub const fn wrapping_sub(self, days: i64) -> Weekday {
// i64::MIN.rem_euclid(7) == 6
let days = match days.checked_neg() {
Some(days) => days,
None => -6,
};
self.wrapping_add(days)
}
/// Returns the next weekday, wrapping around at the end of week to the
/// beginning of the week.
///
/// This is a convenience routing for calling [`Weekday::wrapping_add`]
/// with a value of `1`.
#[inline]
pub const fn next(self) -> Weekday {
self.wrapping_add(1)
}
/// Returns the previous weekday, wrapping around at the beginning of week
/// to the end of the week.
///
/// This is a convenience routing for calling [`Weekday::wrapping_sub`]
/// with a value of `1`.
#[inline]
pub const fn previous(self) -> Weekday {
self.wrapping_sub(1)
}
/// Returns the number of days from `other` to this weekday.
///
/// Adding the returned number of days to `other` is guaranteed to sum to
/// this weekday. The number of days returned is guaranteed to be in the
/// range `0..=6`.
#[inline]
pub const fn since(self, other: Weekday) -> i8 {
(self.to_monday_zero_offset() - other.to_monday_zero_offset())
.rem_euclid(7)
}
/// Returns the number of days until `other` from this weekday.
///
/// Adding the returned number of days to this weekday is guaranteed to sum
/// to `other` weekday. The number of days returned is guaranteed to be in
/// the range `0..=6`.
#[inline]
pub const fn until(self, other: Weekday) -> i8 {
other.since(self)
}
/// Starting with this weekday, this returns an unending iterator that
/// cycles forward through the days of the week.
#[inline]
pub const fn cycle_forward(self) -> WeekdaysForward {
WeekdaysForward { next: self }
}
/// Starting with this weekday, this returns an unending iterator that
/// cycles backward through the days of the week.
#[inline]
pub const fn cycle_reverse(self) -> WeekdaysReverse {
WeekdaysReverse { next: self }
}
}
/// An unending iterator of the days of the week.
///
/// This iterator is created by calling [`Weekday::cycle_forward`].
#[derive(Clone, Debug)]
pub struct WeekdaysForward {
next: Weekday,
}
impl Iterator for WeekdaysForward {
type Item = Weekday;
#[inline]
fn next(&mut self) -> Option<Weekday> {
let next = self.next;
self.next = self.next.wrapping_add(1);
Some(next)
}
}
impl core::iter::FusedIterator for WeekdaysForward {}
/// An unending iterator of the days of the week in reverse.
///
/// This iterator is created by calling [`Weekday::cycle_reverse`].
#[derive(Clone, Debug)]
pub struct WeekdaysReverse {
next: Weekday,
}
impl Iterator for WeekdaysReverse {
type Item = Weekday;
#[inline]
fn next(&mut self) -> Option<Weekday> {
let next = self.next;
self.next = self.next.wrapping_sub(1);
Some(next)
}
}
impl core::iter::FusedIterator for WeekdaysReverse {}
#[cfg(test)]
impl quickcheck::Arbitrary for Weekday {
fn arbitrary(g: &mut quickcheck::Gen) -> Weekday {
let offset = b::WeekdayMondayZero::arbitrary(g);
Weekday::from_monday_zero_offset(offset).unwrap()
}
fn shrink(&self) -> alloc::boxed::Box<dyn Iterator<Item = Weekday>> {
alloc::boxed::Box::new(
self.to_monday_zero_offset()
.shrink()
.filter_map(|n| Weekday::from_monday_zero_offset(n).ok()),
)
}
}
#[cfg(test)]
mod tests {
use alloc::vec::Vec;
use super::*;
static WEEKDAYS: &[Weekday] = &[
Weekday::Monday,
Weekday::Tuesday,
Weekday::Wednesday,
Weekday::Thursday,
Weekday::Friday,
Weekday::Saturday,
Weekday::Sunday,
];
#[test]
fn weekday_from_monday_zero() {
use self::Weekday::*;
assert_eq!(Weekday::from_monday_zero_offset(0), Ok(Monday));
assert_eq!(Weekday::from_monday_zero_offset(1), Ok(Tuesday));
assert_eq!(Weekday::from_monday_zero_offset(2), Ok(Wednesday));
assert_eq!(Weekday::from_monday_zero_offset(3), Ok(Thursday));
assert_eq!(Weekday::from_monday_zero_offset(4), Ok(Friday));
assert_eq!(Weekday::from_monday_zero_offset(5), Ok(Saturday));
assert_eq!(Weekday::from_monday_zero_offset(6), Ok(Sunday));
}
#[test]
fn weekday_from_monday_one() {
use self::Weekday::*;
assert_eq!(Weekday::from_monday_one_offset(1), Ok(Monday));
assert_eq!(Weekday::from_monday_one_offset(2), Ok(Tuesday));
assert_eq!(Weekday::from_monday_one_offset(3), Ok(Wednesday));
assert_eq!(Weekday::from_monday_one_offset(4), Ok(Thursday));
assert_eq!(Weekday::from_monday_one_offset(5), Ok(Friday));
assert_eq!(Weekday::from_monday_one_offset(6), Ok(Saturday));
assert_eq!(Weekday::from_monday_one_offset(7), Ok(Sunday));
}
#[test]
fn weekday_from_sunday_zero() {
use self::Weekday::*;
assert_eq!(Weekday::from_sunday_zero_offset(1), Ok(Monday));
assert_eq!(Weekday::from_sunday_zero_offset(2), Ok(Tuesday));
assert_eq!(Weekday::from_sunday_zero_offset(3), Ok(Wednesday));
assert_eq!(Weekday::from_sunday_zero_offset(4), Ok(Thursday));
assert_eq!(Weekday::from_sunday_zero_offset(5), Ok(Friday));
assert_eq!(Weekday::from_sunday_zero_offset(6), Ok(Saturday));
assert_eq!(Weekday::from_sunday_zero_offset(0), Ok(Sunday));
}
#[test]
fn weekday_from_sunday_one() {
use self::Weekday::*;
assert_eq!(Weekday::from_sunday_one_offset(2), Ok(Monday));
assert_eq!(Weekday::from_sunday_one_offset(3), Ok(Tuesday));
assert_eq!(Weekday::from_sunday_one_offset(4), Ok(Wednesday));
assert_eq!(Weekday::from_sunday_one_offset(5), Ok(Thursday));
assert_eq!(Weekday::from_sunday_one_offset(6), Ok(Friday));
assert_eq!(Weekday::from_sunday_one_offset(7), Ok(Saturday));
assert_eq!(Weekday::from_sunday_one_offset(1), Ok(Sunday));
}
#[test]
fn weekday_to_monday_zero() {
for &weekday in WEEKDAYS {
assert_eq!(
Weekday::from_monday_zero_offset(
weekday.to_monday_zero_offset()
),
Ok(weekday)
);
}
}
#[test]
fn weekday_to_monday_one() {
for &weekday in WEEKDAYS {
assert_eq!(
Weekday::from_monday_one_offset(
weekday.to_monday_one_offset()
),
Ok(weekday)
);
}
}
#[test]
fn weekday_to_sunday_zero() {
for &weekday in WEEKDAYS {
assert_eq!(
Weekday::from_sunday_zero_offset(
weekday.to_sunday_zero_offset()
),
Ok(weekday)
);
}
}
#[test]
fn weekday_to_sunday_one() {
for &weekday in WEEKDAYS {
assert_eq!(
Weekday::from_sunday_one_offset(
weekday.to_sunday_one_offset()
),
Ok(weekday)
);
}
}
#[test]
fn weekday_wrapping_add() {
use self::Weekday::*;
assert_eq!(Sunday.wrapping_add(0), Sunday);
assert_eq!(Sunday.wrapping_add(1), Monday);
assert_eq!(Sunday.wrapping_add(2), Tuesday);
assert_eq!(Sunday.wrapping_add(3), Wednesday);
assert_eq!(Sunday.wrapping_add(4), Thursday);
assert_eq!(Sunday.wrapping_add(5), Friday);
assert_eq!(Sunday.wrapping_add(6), Saturday);
assert_eq!(Sunday.wrapping_add(7), Sunday);
assert_eq!(Wednesday.wrapping_add(0), Wednesday);
assert_eq!(Wednesday.wrapping_add(1), Thursday);
assert_eq!(Wednesday.wrapping_add(2), Friday);
assert_eq!(Wednesday.wrapping_add(3), Saturday);
assert_eq!(Wednesday.wrapping_add(4), Sunday);
assert_eq!(Wednesday.wrapping_add(5), Monday);
assert_eq!(Wednesday.wrapping_add(6), Tuesday);
assert_eq!(Wednesday.wrapping_add(7), Wednesday);
assert_eq!(Sunday.wrapping_add(-1), Saturday);
assert_eq!(Sunday.wrapping_add(-2), Friday);
assert_eq!(Sunday.wrapping_add(-3), Thursday);
assert_eq!(Sunday.wrapping_add(-4), Wednesday);
assert_eq!(Sunday.wrapping_add(-5), Tuesday);
assert_eq!(Sunday.wrapping_add(-6), Monday);
assert_eq!(Sunday.wrapping_add(-7), Sunday);
assert_eq!(Wednesday.wrapping_add(-1), Tuesday);
assert_eq!(Wednesday.wrapping_add(-2), Monday);
assert_eq!(Wednesday.wrapping_add(-3), Sunday);
assert_eq!(Wednesday.wrapping_add(-4), Saturday);
assert_eq!(Wednesday.wrapping_add(-5), Friday);
assert_eq!(Wednesday.wrapping_add(-6), Thursday);
assert_eq!(Wednesday.wrapping_add(-7), Wednesday);
// This caught a bug where our wrapping arithmetic in
// `Weekday::wrapping_add` was incorrect when overflow occurred.
assert_eq!(Tuesday.wrapping_add(9223372036854775807i64), Tuesday);
}
#[test]
fn weekday_wrapping_sub() {
use self::Weekday::*;
assert_eq!(Sunday.wrapping_sub(0), Sunday);
assert_eq!(Sunday.wrapping_sub(1), Saturday);
assert_eq!(Sunday.wrapping_sub(2), Friday);
assert_eq!(Sunday.wrapping_sub(3), Thursday);
assert_eq!(Sunday.wrapping_sub(4), Wednesday);
assert_eq!(Sunday.wrapping_sub(5), Tuesday);
assert_eq!(Sunday.wrapping_sub(6), Monday);
assert_eq!(Sunday.wrapping_sub(7), Sunday);
assert_eq!(Wednesday.wrapping_sub(0), Wednesday);
assert_eq!(Wednesday.wrapping_sub(1), Tuesday);
assert_eq!(Wednesday.wrapping_sub(2), Monday);
assert_eq!(Wednesday.wrapping_sub(3), Sunday);
assert_eq!(Wednesday.wrapping_sub(4), Saturday);
assert_eq!(Wednesday.wrapping_sub(5), Friday);
assert_eq!(Wednesday.wrapping_sub(6), Thursday);
assert_eq!(Wednesday.wrapping_sub(7), Wednesday);
assert_eq!(Sunday.wrapping_sub(-1), Monday);
assert_eq!(Sunday.wrapping_sub(-2), Tuesday);
assert_eq!(Sunday.wrapping_sub(-3), Wednesday);
assert_eq!(Sunday.wrapping_sub(-4), Thursday);
assert_eq!(Sunday.wrapping_sub(-5), Friday);
assert_eq!(Sunday.wrapping_sub(-6), Saturday);
assert_eq!(Sunday.wrapping_sub(-7), Sunday);
assert_eq!(Wednesday.wrapping_sub(-1), Thursday);
assert_eq!(Wednesday.wrapping_sub(-2), Friday);
assert_eq!(Wednesday.wrapping_sub(-3), Saturday);
assert_eq!(Wednesday.wrapping_sub(-4), Sunday);
assert_eq!(Wednesday.wrapping_sub(-5), Monday);
assert_eq!(Wednesday.wrapping_sub(-6), Tuesday);
assert_eq!(Wednesday.wrapping_sub(-7), Wednesday);
// This found a bug where we were negating the integer
// given and assuming it wouldn't panic.
assert_eq!(Monday.wrapping_sub(-9223372036854775805i64), Saturday);
assert_eq!(Monday.wrapping_sub(-9223372036854775806i64), Sunday);
assert_eq!(Monday.wrapping_sub(-9223372036854775807i64), Monday);
assert_eq!(Monday.wrapping_sub(-9223372036854775808i64), Tuesday);
}
#[test]
fn weekday_since() {
for &wd1 in WEEKDAYS {
for (distance, wd2) in wd1.cycle_forward().enumerate().take(7) {
assert_eq!(
usize::try_from(wd2.since(wd1)).unwrap(),
distance,
"{wd2:?} since {wd1:?} should be {distance}",
);
}
}
}
#[test]
fn weekday_until() {
for &wd1 in WEEKDAYS {
for (distance, wd2) in wd1.cycle_forward().enumerate().take(7) {
assert_eq!(
usize::try_from(wd1.until(wd2)).unwrap(),
distance,
"{wd1:?} until {wd2:?} should be {distance}",
);
}
}
}
#[test]
fn weekday_cycle_forward() {
assert_eq!(
WEEKDAYS,
Weekday::Monday.cycle_forward().take(7).collect::<Vec<_>>(),
);
}
#[test]
fn weekday_cycle_reverse() {
let mut got =
Weekday::Sunday.cycle_reverse().take(7).collect::<Vec<_>>();
got.reverse();
assert_eq!(WEEKDAYS, got);
}
quickcheck::quickcheck! {
fn prop_weekday_add_sub(wd: Weekday, n: i64) -> bool {
wd.wrapping_add(n).wrapping_sub(n) == wd
}
fn prop_weekday_since_until(wd1: Weekday, wd2: Weekday) -> bool {
wd1.until(wd2) == wd2.since(wd1)
}
fn prop_since_add_equals_self(wd1: Weekday, wd2: Weekday) -> bool {
let days = wd1.since(wd2);
wd2.wrapping_add(days.into()) == wd1
}
fn prop_until_add_equals_other(wd1: Weekday, wd2: Weekday) -> bool {
let days = wd1.until(wd2);
wd1.wrapping_add(days.into()) == wd2
}
}
}
+38
View File
@@ -0,0 +1,38 @@
/*!
This module defines a smattering of constants used in Jiff.
*/
#![allow(missing_docs)]
pub const DAYS_PER_CIVIL_WEEK: i64 = 7;
pub const HOURS_PER_CIVIL_DAY: i64 = 24;
pub const MINS_PER_CIVIL_DAY: i64 = HOURS_PER_CIVIL_DAY * MINS_PER_HOUR;
pub const MINS_PER_HOUR: i64 = 60;
pub const SECS_PER_CIVIL_WEEK: i64 = DAYS_PER_CIVIL_WEEK * SECS_PER_CIVIL_DAY;
pub const SECS_PER_CIVIL_DAY: i64 = HOURS_PER_CIVIL_DAY * SECS_PER_HOUR;
pub const SECS_PER_HOUR: i64 = SECS_PER_MIN * MINS_PER_HOUR;
pub const SECS_PER_MIN: i64 = 60;
pub const MILLIS_PER_CIVIL_DAY: i64 = SECS_PER_CIVIL_DAY * MILLIS_PER_SEC;
pub const MILLIS_PER_SEC: i64 = 1_000;
pub const MICROS_PER_CIVIL_DAY: i64 = SECS_PER_CIVIL_DAY * MICROS_PER_SEC;
pub const MICROS_PER_SEC: i64 = MILLIS_PER_SEC * MICROS_PER_MILLI;
pub const MICROS_PER_MILLI: i64 = 1_000;
pub const NANOS_PER_CIVIL_DAY: i64 = HOURS_PER_CIVIL_DAY * NANOS_PER_HOUR;
pub const NANOS_PER_HOUR: i64 = MINS_PER_HOUR * NANOS_PER_MIN;
pub const NANOS_PER_MIN: i64 = SECS_PER_MIN * NANOS_PER_SEC;
pub const NANOS_PER_SEC: i64 = MILLIS_PER_SEC * NANOS_PER_MILLI;
pub const NANOS_PER_MILLI: i64 = MICROS_PER_MILLI * NANOS_PER_MICRO;
pub const NANOS_PER_MICRO: i64 = 1_000;
pub const DAYS_PER_CIVIL_WEEK_32: i32 = 7;
pub const HOURS_PER_CIVIL_DAY_32: i32 = 24;
pub const MINS_PER_HOUR_32: i32 = 60;
pub const SECS_PER_CIVIL_DAY_32: i32 =
HOURS_PER_CIVIL_DAY_32 * SECS_PER_HOUR_32;
pub const SECS_PER_HOUR_32: i32 = SECS_PER_MIN_32 * MINS_PER_HOUR_32;
pub const SECS_PER_MIN_32: i32 = 60;
pub const MILLIS_PER_SEC_32: i32 = 1_000;
pub const MICROS_PER_MILLI_32: i32 = 1_000;
pub const NANOS_PER_SEC_32: i32 = MILLIS_PER_SEC_32 * NANOS_PER_MILLI_32;
pub const NANOS_PER_MILLI_32: i32 = MICROS_PER_MILLI_32 * NANOS_PER_MICRO_32;
pub const NANOS_PER_MICRO_32: i32 = 1_000;
+95
View File
@@ -0,0 +1,95 @@
/*!
A small collection of datetime primitives to support Jiff.
The primary motivation of this crate is as an implementation detail for
the [Jiff](https://docs.rs/jiff) crate. Indeed, if you're seeing this
documentation, Jiff is probably the crate you want, not this one.
# Motivation
The primary motivation for this crate's existence is so that the `jiff` and
`jiff-static` crates can share code. Prior to the birth of `jiff-core`,
there were some major hacks involved that permitted code sharing by way of
duplication. Therefore, some chunk of code was compiled twice if you depended
on both `jiff` and `jiff-static`.
A secondary motivation is that `jiff-core` provides a useful set of primitives
for datetime handling that others may find useful if they don't want to depend
on Jiff proper. For example, callers looking to convert between timestamps and
datetimes may do so with this crate:
```
use jiff_core::{civil, tz::Offset, Timestamp};
let ts = Timestamp::UNIX_EPOCH;
assert_eq!(ts.to_datetime(Offset::UTC).date(), civil::date(1970, 1, 1));
```
Callers can perform the reverse operation too:
```
use jiff_core::{civil, tz::Offset, Timestamp};
let datetime = civil::date(1970, 1, 1).at(0, 0, 0, 0);
assert_eq!(datetime.to_timestamp(Offset::UTC).unwrap(), Timestamp::UNIX_EPOCH);
```
The above operation is fallible because, like Jiff proper, not all civil
datetimes can be combined with all offsets to produce an instant within this
crate's valid bounds (which it shares with Jiff):
```
use jiff_core::{civil, tz::Offset, Timestamp};
let datetime = civil::date(9999, 12, 31).at(23, 59, 59, 999_999_999);
assert!(datetime.to_timestamp(Offset::UTC).is_err());
// The maximum datetime can be used to produce a timestamp only by using the
// maximum offset from UTC. If any other offset were permitted here, it would
// imply the ability to get a timestamp corresponding to a civil datetime in
// the year 10,000 CE. (And similar for the minimal datetime.)
assert_eq!(datetime.to_timestamp(Offset::MAX).unwrap(), Timestamp::MAX);
```
# What does this crate not do?
The major missing pieces from this crate are:
* Formatting and parsing, although callers may find the `Debug` trait
implementations of types in this crate to be useful.
* Convenient time zone aware handling.
* Platform integration with the [Time Zone Database].
* Any kind of duration type.
* Good documentation demonstrating proper usage of the crate.
* Maturity and stability. Users of this crate should expect more breaking
change releases than Jiff proper.
* There is no way to convert a `jiff-core` type directly into a `jiff` type
or vice versa. You must go through the proper constructors. These conversions
are intentionally missing so that `jiff-core` is not a public dependency of
`jiff`.
[Time Zone Database]: https://www.iana.org/time-zones
*/
#![no_std]
#![cfg_attr(docsrs_jiff, feature(doc_cfg))]
#![warn(missing_debug_implementations)]
#![deny(missing_docs)]
#[cfg(any(test, feature = "std"))]
extern crate std;
#[cfg(any(test, feature = "alloc"))]
extern crate alloc;
pub use self::timestamp::Timestamp;
#[macro_use]
mod logging;
mod macros;
pub mod bounds;
pub mod civil;
pub mod constants;
mod timestamp;
pub mod tz;
pub mod util;
+67
View File
@@ -0,0 +1,67 @@
// Some feature combinations result in some of these macros never being used.
// Which is fine. Just squash the warnings.
#![allow(dead_code, unused_macros)]
macro_rules! log {
($($tt:tt)*) => {
#[cfg(feature = "logging")]
{
$($tt)*
}
}
}
macro_rules! error {
($($tt:tt)*) => { log!(log::error!($($tt)*)) }
}
macro_rules! warn {
($($tt:tt)*) => { log!(log::warn!($($tt)*)) }
}
macro_rules! info {
($($tt:tt)*) => { log!(log::info!($($tt)*)) }
}
macro_rules! debug {
($($tt:tt)*) => { log!(log::debug!($($tt)*)) }
}
macro_rules! trace {
($($tt:tt)*) => { log!(log::trace!($($tt)*)) }
}
/// A copy of std's `dbg!` macro that doesn't do pretty printing.
///
/// This is nice because we usually want more compact output in this crate.
/// Also, because we don't import std's prelude, we have to use `std::dbg!`.
/// This macro definition makes it available as `dbg!`.
#[cfg(feature = "std")]
macro_rules! dbg {
() => {
std::eprintln!(
"[{}:{}:{}]",
$crate::file!(),
$crate::line!(),
$crate::column!(),
)
};
($val:expr $(,)?) => {
match $val {
tmp => {
std::eprintln!(
"[{}:{}:{}] {} = {:?}",
std::file!(),
std::line!(),
std::column!(),
std::stringify!($val),
&tmp,
);
tmp
}
}
};
($($val:expr),+ $(,)?) => {
($(dbg!($val)),+,)
};
}
+72
View File
@@ -0,0 +1,72 @@
#![allow(unused_macros, unused_imports)]
/*
/// Unwrap an `Option<T>` in a `const` context.
///
/// If it fails, panics with the given message.
macro_rules! unwrap {
($val:expr, $msg:expr$(,)?) => {
match $val {
Some(val) => val,
None => panic!($msg),
}
};
}
*/
/// Unwrap a `Result<T, E>` in a `const` context.
///
/// If it fails, panics with the given message.
macro_rules! unwrapr {
($val:expr, $msg:expr$(,)?) => {
match $val {
Ok(val) => val,
Err(_) => panic!($msg),
}
};
}
/// Get `T` out of `Result<T, E>` or return `Err(e)` in a `const` context.
///
/// No conversions are performed. This only works when used in a function
/// that itself returns `Result<..., E>`.
macro_rules! ctry {
($val:expr) => {
match $val {
Ok(val) => val,
Err(err) => return Err(err),
}
};
}
/// Convert the given error to a `RangeError` and return it.
///
/// `val` must be one of `RangeError`, `BoundsError` or `SpecialBoundsError`.
/// In particular, `val.into_range_error()` is called to convert to a
/// `RangeError`.
///
/// This only works when used in a function
/// that itself returns `Result<..., RangeError>`.
macro_rules! rbail {
($val:expr) => {
return Err($val.into_range_error())
};
}
/// Get `T` out of `Result<T, E>` or return `Err(e)` in a `const` context.
///
/// `E` must be one of `RangeError`, `BoundsError` or `SpecialBoundsError`. In
/// particular, `E::into_range_error` is called to convert to a `RangeError`.
///
/// This only works when used in a function
/// that itself returns `Result<..., RangeError>`.
macro_rules! rtry {
($val:expr) => {
match $val {
Ok(val) => val,
Err(err) => crate::macros::rbail!(err),
}
};
}
pub(crate) use {ctry, rbail, rtry, unwrapr};
+933
View File
@@ -0,0 +1,933 @@
use crate::{
bounds::{self as b, RangeError},
civil::DateTime,
constants as c,
macros::{rbail, rtry, unwrapr},
tz::Offset,
};
/// An instant in time represented as the number of nanoseconds since the Unix
/// epoch.
///
/// A timestamp is always in the Unix timescale with a UTC offset of zero.
#[derive(Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Timestamp {
second: i64,
nanosecond: i32,
}
impl Timestamp {
/// The minimum allow Unix timestamp.
pub const MIN: Timestamp = Timestamp {
second: b::UnixEpochSeconds::MIN,
nanosecond: b::SubsecNanosecond::MIN,
};
/// The maximum allow Unix timestamp.
pub const MAX: Timestamp = Timestamp {
second: b::UnixEpochSeconds::MAX,
nanosecond: b::SubsecNanosecond::MAX,
};
/// The Unix epoch represented as a timestamp.
pub const UNIX_EPOCH: Timestamp = Timestamp { second: 0, nanosecond: 0 };
/// Create a new timestamp from the given number of seconds and its
/// sub-second component.
///
/// This returns an error if `nanos` is not in the range specified by
/// [`SignedSubsecNanosecond`](b::SignedSubsecNanosecond). An error
/// is also returned when `secs` is not in the range specified by
/// [`UnixEpochSeconds`](b::UnixEpochSeconds).
#[inline]
pub const fn new(secs: i64, nanos: i32) -> Result<Timestamp, RangeError> {
let mut secs = rtry!(b::UnixEpochSeconds::checkc(secs));
let mut nanos = rtry!(b::SignedSubsecNanosecond::checkc(nanos as i64));
if secs == b::UnixEpochSeconds::MIN && nanos < 0 {
rbail!(b::UnixEpochSeconds::error());
}
// At this point, we're done if either unit is zero or if they have the
// same sign.
if nanos == 0 || secs == 0 || secs.signum() == (nanos.signum() as i64)
{
return Ok(Timestamp::new_unchecked(secs, nanos));
}
// Otherwise, the only work we have to do is to balance negative nanos
// into positive seconds, or positive nanos into negative seconds.
if secs < 0 {
debug_assert!(nanos > 0);
// Never wraps because adding +1 to a negative i64 never overflows.
//
// MSRV(1.79): Consider using `unchecked_add` here.
secs += 1;
// Never wraps because subtracting +1_000_000_000 from a positive
// i32 never overflows.
//
// MSRV(1.79): Consider using `unchecked_sub` here.
nanos -= c::NANOS_PER_SEC_32;
} else {
debug_assert!(secs > 0);
debug_assert!(nanos < 0);
// Never wraps because subtracting +1 from a positive i64 never
// overflows.
//
// MSRV(1.79): Consider using `unchecked_add` here.
secs -= 1;
// Never wraps because adding +1_000_000_000 to a negative i32
// never overflows.
//
// MSRV(1.79): Consider using `unchecked_add` here.
nanos += c::NANOS_PER_SEC_32;
}
Ok(Timestamp::new_unchecked(secs, nanos))
}
/// Creates a new `Timestamp` value in a `const` context.
///
/// This is identical to [`Timestamp::new`], except that it panics when
/// `Timestamp::new` would return an error. This can be more convenient in
/// a `const` context where unwrapping a `Result` is not ergonomic.
#[inline]
pub const fn constant(second: i64, nanosecond: i32) -> Timestamp {
unwrapr!(Timestamp::new(second, nanosecond), "invalid timestamp")
}
/// Creates a new `Timestamp` without bounds checks.
///
/// Note that this should not be made public *and* safe.
#[inline]
pub(crate) const fn new_unchecked(secs: i64, nanos: i32) -> Timestamp {
debug_assert!(b::UnixEpochSeconds::checkc(secs).is_ok());
debug_assert!(b::SignedSubsecNanosecond::checkc(nanos as i64).is_ok());
debug_assert!(secs != b::UnixEpochSeconds::MIN || nanos >= 0);
debug_assert!(
nanos == 0
|| secs == 0
|| secs.signum() == (nanos.signum() as i64)
);
Timestamp { second: secs, nanosecond: nanos }
}
/// Constructs a timestamp from seconds since the Unix epoch.
///
/// This is preferred to [`Timestamp::new`] when it is known that the
/// sub-second component is always `0`. In particular, this generates
/// less code and is likely to be faster.
///
/// An error is returned when `second` is not in the range specified by
/// [`UnixEpochSeconds`](b::UnixEpochSeconds).
#[inline]
pub const fn from_second(second: i64) -> Result<Timestamp, RangeError> {
let second = rtry!(b::UnixEpochSeconds::checkc(second));
Ok(Timestamp::new_unchecked(second, 0))
}
/// Constructs a timestamp from milliseconds since the Unix epoch.
///
/// An error is returned when `millisecond` is not in the range specified by
/// [`UnixEpochMilliseconds`](b::UnixEpochMilliseconds).
#[inline]
pub const fn from_millisecond(
millisecond: i64,
) -> Result<Timestamp, RangeError> {
let millisecond = rtry!(b::UnixEpochMilliseconds::checkc(millisecond));
// OK because MILLIS_PER_SEC!={-1,0}.
let secs = millisecond / c::MILLIS_PER_SEC;
// OK because MILLIS_PER_SEC!={-1,0} and because
// millis % MILLIS_PER_SEC can be at most 999, and 999 * 1_000_000
// never overflows i32.
let nanos =
(millisecond % c::MILLIS_PER_SEC) as i32 * c::NANOS_PER_MILLI_32;
// OK because we've already verified that `millisecond` is in range.
Ok(Timestamp::new_unchecked(secs, nanos))
}
/// Constructs a timestamp from microseconds since the Unix epoch.
///
/// An error is returned when `microsecond` is not in the range specified
/// by [`UnixEpochMicroseconds`](b::UnixEpochMicroseconds).
#[inline]
pub const fn from_microsecond(
microsecond: i64,
) -> Result<Timestamp, RangeError> {
let microsecond = rtry!(b::UnixEpochMicroseconds::checkc(microsecond));
// OK because MILLIS_PER_SEC!={-1,0}.
let secs = microsecond / c::MICROS_PER_SEC;
// OK because MILLIS_PER_SEC!={-1,0} and because
// millis % MILLIS_PER_SEC can be at most 999, and 999 * 1_000_000
// never overflows i32.
let nanos =
(microsecond % c::MICROS_PER_SEC) as i32 * c::NANOS_PER_MICRO_32;
// OK because we've already verified that `millisecond` is in range.
Ok(Timestamp::new_unchecked(secs, nanos))
}
/// Constructs a timestamp from nanoseconds since the Unix epoch.
///
/// An error is returned when `nanosecond` refers to a timestamp outside
/// of the range [`Timestamp::MIN`] to [`Timestamp::MAX`].
#[inline]
pub const fn from_nanosecond(
nanosecond: i128,
) -> Result<Timestamp, RangeError> {
const NANOS_PER_SEC: i128 = c::NANOS_PER_SEC as i128;
// OK because NANOS_PER_SEC!={-1,0}.
let secs = nanosecond / NANOS_PER_SEC;
// RUST: Use `i64::try_from` when available in `const`.
if !(i64::MIN as i128 <= secs && secs <= i64::MAX as i128) {
rbail!(b::SpecialBoundsError::UnixEpochNanoseconds);
}
let secs64 = secs as i64;
// OK because NANOS_PER_SEC!={-1,0}.
let nanosecond = (nanosecond % NANOS_PER_SEC) as i32;
Ok(Timestamp::new_unchecked(secs64, nanosecond))
}
/// Returns this timestamp as a number of seconds since the Unix epoch.
///
/// This only returns the number of whole seconds. That is, if there are
/// any fractional seconds in this timestamp, then they are truncated.
#[inline]
pub const fn as_second(self) -> i64 {
self.second
}
/// Returns this timestamp as a number of milliseconds since the Unix
/// epoch.
///
/// This only returns the number of whole milliseconds. That is, if there
/// are any fractional milliseconds in this timestamp, then they are
/// truncated.
#[inline]
pub const fn as_millisecond(self) -> i64 {
// OK because the range of `Timestamp` guarantees that its
// representation as milliseconds fits into an i64.
let millis = self.as_second() * c::MILLIS_PER_SEC;
// OK because subsec_millis maxes out at 999, and adding that to
// b::UnixSeconds::MAX*1_000 will never overflow an i64.
millis + (self.subsec_millisecond() as i64)
}
/// Returns this timestamp as a number of microseconds since the Unix
/// epoch.
///
/// This only returns the number of whole microseconds. That is, if there
/// are any fractional microseconds in this timestamp, then they are
/// truncated.
#[inline]
pub const fn as_microsecond(self) -> i64 {
// OK because the range of `Timestamp` guarantees that its
// representation as microseconds fits into an i64.
let micros = self.as_second() * c::MICROS_PER_SEC;
// OK because subsec_micros maxes out at 999_999, and adding that to
// b::UnixSeconds::MAX*1_000_000 will never overflow an i64.
micros + (self.subsec_microsecond() as i64)
}
/// Returns this timestamp as a number of nanoseconds since the Unix
/// epoch.
#[inline]
pub const fn as_nanosecond(self) -> i128 {
// OK because 1_000_000_000 times any i64 will never overflow i128.
let nanos = (self.second as i128) * (c::NANOS_PER_SEC as i128);
// OK because nanosecond maxes out at 999_999_999, and adding that to
// i64::MAX*1_000_000_000 will never overflow a i128.
nanos + (self.nanosecond as i128)
}
/// Returns the fractional second component of this timestamp in units of
/// microseconds.
///
/// The value returned is negative when the timestamp is negative. It is
/// guaranteed that the range of the value returned is in the inclusive
/// range `-999_999..=999_999`.
#[inline]
pub const fn subsec_millisecond(&self) -> i32 {
// OK because NANOS_PER_MILLI!={-1,0}.
self.nanosecond / c::NANOS_PER_MILLI_32
}
/// Returns the fractional second component of this timestamp in units of
/// milliseconds.
///
/// The value returned is negative when the timestamp is negative. It is
/// guaranteed that the range of the value returned is in the inclusive
/// range `-999..=999`.
#[inline]
pub const fn subsec_microsecond(&self) -> i32 {
// OK because NANOS_PER_MICRO!={-1,0}.
self.nanosecond / c::NANOS_PER_MICRO_32
}
/// Returns the fractional second component of this timestamp in units of
/// nanoseconds.
///
/// The value returned is negative when the timestamp is negative. It is
/// guaranteed that the range of the value returned is in the inclusive
/// range `-999,999,999..=999,999,999`.
#[inline]
pub const fn subsec_nanosecond(&self) -> i32 {
self.nanosecond
}
/// Returns a number that represents the sign of this timestamp.
///
/// * When [`Timestamp::is_zero`] is true, this returns `0`.
/// * When [`Timestamp::is_positive`] is true, this returns `1`.
/// * When [`Timestamp::is_negative`] is true, this returns `-1`.
///
/// The above cases are mutually exclusive.
///
/// # Example
///
/// ```
/// use jiff_core::Timestamp;
///
/// assert_eq!(0, Timestamp::UNIX_EPOCH.signum());
///
/// let ts = Timestamp::new(5, -999_999_999).unwrap();
/// assert_eq!(ts.signum(), 1);
/// // The mixed signs were normalized away!
/// assert_eq!(ts.as_second(), 4);
/// assert_eq!(ts.subsec_nanosecond(), 1);
///
/// // The same applies for negative timestamps.
/// let ts = Timestamp::new(-5, 999_999_999).unwrap();
/// assert_eq!(ts.signum(), -1);
/// assert_eq!(ts.as_second(), -4);
/// assert_eq!(ts.subsec_nanosecond(), -1);
/// ```
#[inline]
pub const fn signum(self) -> i8 {
if self.is_zero() {
0
} else if self.is_positive() {
1
} else {
debug_assert!(self.is_negative());
-1
}
}
/// Returns true if and only if this timestamp corresponds to the instant
/// in time known as the Unix epoch.
///
/// # Example
///
/// ```
/// use jiff_core::Timestamp;
///
/// assert!(Timestamp::UNIX_EPOCH.is_zero());
/// ```
#[inline]
pub const fn is_zero(self) -> bool {
self.second == 0 && self.nanosecond == 0
}
/// Returns true when this timestamp is positive. That is, after the Unix
/// epoch.
///
/// # Example
///
/// ```
/// use jiff_core::Timestamp;
///
/// let ts = Timestamp::new(0, 1).unwrap();
/// assert!(ts.is_positive());
/// ```
#[inline]
pub const fn is_positive(&self) -> bool {
self.second.is_positive() || self.nanosecond.is_positive()
}
/// Returns true when this timestamp is negative. That is, before the Unix
/// epoch.
///
/// # Example
///
/// ```
/// use jiff_core::Timestamp;
///
/// let ts = Timestamp::new(0, -1).unwrap();
/// assert!(ts.is_negative());
/// ```
#[inline]
pub const fn is_negative(&self) -> bool {
self.second.is_negative() || self.nanosecond.is_negative()
}
/// Converts a Unix timestamp with an offset to a Gregorian datetime.
///
/// The offset should correspond to the number of seconds required to
/// add to this timestamp to get the local time.
#[inline]
pub const fn to_datetime(&self, offset: Offset) -> DateTime {
offset.to_datetime(*self)
}
/// Add the given number of seconds and nanoseconds to this timestamp.
///
/// If this would result in a timestamp outside of its boundaries, then
/// this returns an error.
///
/// # Examples
///
/// ```
/// use jiff_core::Timestamp;
///
/// let mkts = |sec, nano| Timestamp::new(sec, nano).unwrap();
/// let ts = mkts(123, 0);
///
/// assert_eq!(ts.checked_add(1, 0), Ok(mkts(124, 0)));
/// assert_eq!(ts.checked_add(1, 1), Ok(mkts(124, 1)));
/// assert_eq!(ts.checked_add(1, -1), Ok(mkts(123, 999_999_999)));
/// assert_eq!(ts.checked_add(0, 1), Ok(mkts(123, 1)));
/// assert_eq!(ts.checked_add(0, -1), Ok(mkts(122, 999_999_999)));
/// assert_eq!(ts.checked_add(-1, 0), Ok(mkts(122, 0)));
/// assert_eq!(ts.checked_add(-1, 1), Ok(mkts(122, 1)));
/// assert_eq!(ts.checked_add(-1, -1), Ok(mkts(121, 999_999_999)));
///
/// assert_eq!(ts.checked_add(0, i32::MIN), Ok(mkts(121, -147_483_648)));
/// assert_eq!(ts.checked_add(1, i32::MIN), Ok(mkts(122, -147_483_648)));
/// assert_eq!(ts.checked_add(-1, i32::MIN), Ok(mkts(120, -147_483_648)));
/// assert_eq!(ts.checked_add(0, i32::MAX), Ok(mkts(125, 147_483_647)));
/// assert_eq!(ts.checked_add(1, i32::MAX), Ok(mkts(126, 147_483_647)));
/// assert_eq!(ts.checked_add(-1, i32::MAX), Ok(mkts(124, 147_483_647)));
///
/// assert!(ts.checked_add(i64::MAX, 0).is_err());
/// assert!(ts.checked_add(i64::MIN, 0).is_err());
///
/// let ts = Timestamp::UNIX_EPOCH;
/// let max = Timestamp::MAX.as_second();
/// assert!(ts.checked_add(max, 0).is_ok());
/// assert!(ts.checked_add(max + 1, 0).is_err());
/// assert!(ts.checked_add(max, 999_999_999).is_ok());
/// assert!(ts.checked_add(max, 1_000_000_000).is_err());
/// ```
#[inline]
pub const fn checked_add(
self,
mut seconds: i64,
mut nanos: i32,
) -> Result<Timestamp, RangeError> {
// When |nanos| exceeds 1 second, we balance the excess up to seconds.
if !(-c::NANOS_PER_SEC_32 < nanos && nanos < c::NANOS_PER_SEC_32) {
// Never wraps or panics because NANOS_PER_SEC!={0,-1}.
let addsecs = nanos / c::NANOS_PER_SEC_32;
seconds = match seconds.checked_add(addsecs as i64) {
Some(secs) => secs,
None => panic!(
"nanoseconds overflowed seconds in SignedDuration::new"
),
};
// Never wraps or panics because NANOS_PER_SEC!={0,-1}.
nanos = nanos % c::NANOS_PER_SEC_32;
}
self.checked_add_sensible(seconds, nanos)
}
/// Subtracts the given number of seconds and nanoseconds from this
/// timestamp.
///
/// # Examples
///
/// ```
/// use jiff_core::Timestamp;
///
/// let mkts = |sec, nano| Timestamp::new(sec, nano).unwrap();
/// let ts = mkts(123, 0);
///
/// assert_eq!(ts.checked_sub(1, 0), Ok(mkts(122, 0)));
/// assert_eq!(ts.checked_sub(1, 1), Ok(mkts(121, 999_999_999)));
/// assert_eq!(ts.checked_sub(1, -1), Ok(mkts(122, 1)));
/// assert_eq!(ts.checked_sub(0, 1), Ok(mkts(122, 999_999_999)));
/// assert_eq!(ts.checked_sub(0, -1), Ok(mkts(123, 1)));
/// assert_eq!(ts.checked_sub(-1, 0), Ok(mkts(124, 0)));
/// assert_eq!(ts.checked_sub(-1, 1), Ok(mkts(123, 999_999_999)));
/// assert_eq!(ts.checked_sub(-1, -1), Ok(mkts(124, 1)));
///
/// assert_eq!(ts.checked_sub(0, i32::MIN), Ok(mkts(125, 147_483_648)));
/// assert_eq!(ts.checked_sub(1, i32::MIN), Ok(mkts(124, 147_483_648)));
/// assert_eq!(ts.checked_sub(-1, i32::MIN), Ok(mkts(126, 147_483_648)));
/// assert_eq!(ts.checked_sub(0, i32::MAX), Ok(mkts(121, -147_483_647)));
/// assert_eq!(ts.checked_sub(1, i32::MAX), Ok(mkts(120, -147_483_647)));
/// assert_eq!(ts.checked_sub(-1, i32::MAX), Ok(mkts(122, -147_483_647)));
///
/// assert!(ts.checked_sub(i64::MAX, 0).is_err());
/// assert!(ts.checked_sub(i64::MIN, 0).is_err());
///
/// let ts = Timestamp::UNIX_EPOCH;
/// let min = Timestamp::MIN.as_second();
/// assert!(ts.checked_sub(-min, 0).is_ok());
/// assert!(ts.checked_sub(-(min - 1), 0).is_err());
/// assert!(ts.checked_sub(-min, 999_999_999).is_ok());
/// assert!(ts.checked_sub(-min, 1_000_000_000).is_err());
/// ```
#[inline]
pub const fn checked_sub(
self,
seconds: i64,
mut nanos: i32,
) -> Result<Timestamp, RangeError> {
let Some(mut seconds) = seconds.checked_neg() else {
rbail!(b::UnixEpochSeconds::error())
};
// When |nanos| exceeds 1 second, we balance the excess up to seconds.
if !(-c::NANOS_PER_SEC_32 < nanos && nanos < c::NANOS_PER_SEC_32) {
// Never wraps or panics because NANOS_PER_SEC!={0,-1}.
let addsecs = nanos / c::NANOS_PER_SEC_32;
seconds = match seconds.checked_sub(addsecs as i64) {
Some(secs) => secs,
None => panic!(
"nanoseconds overflowed seconds in SignedDuration::new"
),
};
// Never wraps or panics because NANOS_PER_SEC!={0,-1}.
nanos = nanos % c::NANOS_PER_SEC_32;
}
// Negating `nanos` here is OK because the above guarantees that it's
// in the inclusive range `[-999_999_999, 999_999_999]`.
self.checked_add(seconds, -nanos)
}
/// Implementation of `checked_add` that assumes `|nanos| < 1 second`.
#[inline]
const fn checked_add_sensible(
self,
seconds: i64,
nanos: i32,
) -> Result<Timestamp, RangeError> {
debug_assert!(
-c::NANOS_PER_SEC_32 < nanos && nanos < c::NANOS_PER_SEC_32
);
let mut second =
rtry!(b::UnixEpochSeconds::checked_add(self.as_second(), seconds));
// OK because we know both are in the inclusive range
// [-999_999_999, 999_999_999] per above math.
let mut nanosecond = self.nanosecond + nanos;
// When the nanosecond component is zero, we can ignore it and just
// return seconds as-is.
if nanosecond == 0 {
return Ok(Timestamp { second, nanosecond });
}
if nanosecond >= c::NANOS_PER_SEC_32 {
nanosecond -= c::NANOS_PER_SEC_32;
second = rtry!(b::UnixEpochSeconds::checked_add(second, 1));
} else if nanosecond <= -c::NANOS_PER_SEC_32 {
nanosecond += c::NANOS_PER_SEC_32;
second = rtry!(b::UnixEpochSeconds::checked_add(second, -1));
}
if second != 0
&& nanosecond != 0
&& second.signum() != (nanosecond.signum() as i64)
{
if second < 0 {
debug_assert!(nanosecond > 0);
// OK because second<0.
second += 1;
// OK because nanosecond>0.
nanosecond -= c::NANOS_PER_SEC_32;
} else {
debug_assert!(second > 0);
debug_assert!(nanosecond < 0);
// OK because second>0.
second -= 1;
// OK because nanosecond<0.
nanosecond += c::NANOS_PER_SEC_32;
}
}
Ok(Timestamp { second, nanosecond })
}
/// Add the given number of seconds to this timestamp.
///
/// If this would result in a timestamp outside of its boundaries, then
/// this returns an error.
///
/// The nanosecond component of the timestamp returned is guaranteed to
/// match the nanosecond component of `self`.
///
/// # Examples
///
/// ```
/// use jiff_core::Timestamp;
///
/// let mkts = |sec, nano| Timestamp::new(sec, nano).unwrap();
///
/// let ts = mkts(123, 0);
/// assert_eq!(ts.checked_add_seconds(0), Ok(mkts(123, 0)));
/// assert_eq!(ts.checked_add_seconds(1), Ok(mkts(124, 0)));
/// assert_eq!(ts.checked_add_seconds(-1), Ok(mkts(122, 0)));
///
/// let ts = mkts(123, 999_999_999);
/// assert_eq!(ts.checked_add_seconds(0), Ok(mkts(123, 999_999_999)));
/// assert_eq!(ts.checked_add_seconds(1), Ok(mkts(124, 999_999_999)));
/// assert_eq!(ts.checked_add_seconds(-1), Ok(mkts(122, 999_999_999)));
///
/// assert!(ts.checked_add_seconds(i64::MIN).is_err());
/// assert!(ts.checked_add_seconds(i64::MAX).is_err());
/// ```
#[inline]
pub const fn checked_add_seconds(
self,
seconds: i64,
) -> Result<Timestamp, RangeError> {
let second =
rtry!(b::UnixEpochSeconds::checked_add(self.as_second(), seconds));
Ok(Timestamp { second, ..self })
}
/// Subtracts the given number of seconds from this timestamp.
#[inline]
pub const fn checked_sub_seconds(
self,
seconds: i64,
) -> Result<Timestamp, RangeError> {
let Some(seconds) = seconds.checked_neg() else {
rbail!(b::UnixEpochSeconds::error())
};
self.checked_add_seconds(seconds)
}
}
impl core::fmt::Debug for Timestamp {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let dt = self.to_datetime(Offset::UTC);
core::fmt::Debug::fmt(&dt, f)?;
f.write_str("Z")
}
}
impl Default for Timestamp {
#[inline]
fn default() -> Timestamp {
Timestamp::UNIX_EPOCH
}
}
/// Adds a number of seconds to a `Timestamp`.
///
/// # Panics
///
/// When adding would result in a value outside the boundaries of a
/// `Timestamp`.
///
/// # Example
///
/// ```
/// use jiff_core::Timestamp;
///
/// let ts = Timestamp::new(123, 999_999_999).unwrap();;
/// assert_eq!(ts + 400, Timestamp::new(523, 999_999_999).unwrap());
/// ```
impl core::ops::Add<i64> for Timestamp {
type Output = Timestamp;
fn add(self, seconds: i64) -> Timestamp {
self.checked_add_seconds(seconds).unwrap()
}
}
/// Adds a number of seconds and nanoseconds to a `Timestamp`.
///
/// # Panics
///
/// When adding would result in a value outside the boundaries of a
/// `Timestamp`.
///
/// # Example
///
/// ```
/// use jiff_core::Timestamp;
///
/// let ts = Timestamp::new(123, 999_999_999).unwrap();;
/// assert_eq!(ts + (400, 1), Timestamp::new(524, 0).unwrap());
/// ```
impl core::ops::Add<(i64, i32)> for Timestamp {
type Output = Timestamp;
fn add(self, (seconds, nanoseconds): (i64, i32)) -> Timestamp {
self.checked_add(seconds, nanoseconds).unwrap()
}
}
/// Adds a number of seconds to a `Timestamp`.
///
/// # Panics
///
/// When adding would result in a value outside the boundaries of a
/// `Timestamp`.
impl core::ops::AddAssign<i64> for Timestamp {
#[inline]
fn add_assign(&mut self, rhs: i64) {
*self = *self + rhs;
}
}
/// Adds a number of seconds and nanoseconds to a `Timestamp`.
///
/// # Panics
///
/// When adding would result in a value outside the boundaries of a
/// `Timestamp`.
impl core::ops::AddAssign<(i64, i32)> for Timestamp {
#[inline]
fn add_assign(&mut self, rhs: (i64, i32)) {
*self = *self + rhs;
}
}
/// Subtracts a number of seconds from a `Timestamp`.
///
/// # Panics
///
/// When adding would result in a value outside the boundaries of a
/// `Timestamp`.
///
/// # Example
///
/// ```
/// use jiff_core::Timestamp;
///
/// let ts = Timestamp::new(523, 999_999_999).unwrap();;
/// assert_eq!(ts - 400, Timestamp::new(123, 999_999_999).unwrap());
/// ```
impl core::ops::Sub<i64> for Timestamp {
type Output = Timestamp;
fn sub(self, seconds: i64) -> Timestamp {
self.checked_sub_seconds(seconds).unwrap()
}
}
/// Subtracts a number of seconds and nanoseconds from a `Timestamp`.
///
/// # Panics
///
/// When adding would result in a value outside the boundaries of a
/// `Timestamp`.
///
/// # Example
///
/// ```
/// use jiff_core::Timestamp;
///
/// let ts = Timestamp::new(523, 999_999_999).unwrap();;
/// assert_eq!(ts - (400, 1), Timestamp::new(123, 999_999_998).unwrap());
/// ```
impl core::ops::Sub<(i64, i32)> for Timestamp {
type Output = Timestamp;
fn sub(self, (seconds, nanoseconds): (i64, i32)) -> Timestamp {
self.checked_sub(seconds, nanoseconds).unwrap()
}
}
/// Subtracts a number of seconds from a `Timestamp`.
///
/// # Panics
///
/// When subtracting would result in a value outside the boundaries of a
/// `Timestamp`.
impl core::ops::SubAssign<i64> for Timestamp {
#[inline]
fn sub_assign(&mut self, rhs: i64) {
*self = *self + rhs;
}
}
/// Subtracts a number of seconds and nanoseconds from a `Timestamp`.
///
/// # Panics
///
/// When subtracting would result in a value outside the boundaries of a
/// `Timestamp`.
impl core::ops::SubAssign<(i64, i32)> for Timestamp {
#[inline]
fn sub_assign(&mut self, rhs: (i64, i32)) {
*self = *self + rhs;
}
}
#[cfg(test)]
impl quickcheck::Arbitrary for Timestamp {
fn arbitrary(g: &mut quickcheck::Gen) -> Timestamp {
let secs = b::UnixEpochSeconds::arbitrary(g);
let mut nanos = b::SignedSubsecNanosecond::arbitrary(g);
// nanoseconds must be zero for the minimum second value,
// so just clamp it to 0.
if secs == b::UnixEpochSeconds::MIN && nanos < 0 {
nanos = 0;
}
Timestamp::new(secs, nanos).unwrap_or(Timestamp::UNIX_EPOCH)
}
fn shrink(&self) -> alloc::boxed::Box<dyn Iterator<Item = Self>> {
let secs = self.as_second();
let nanos = self.subsec_nanosecond();
alloc::boxed::Box::new((secs, nanos).shrink().filter_map(
|(secs, nanos)| {
let secs = b::UnixEpochSeconds::check(secs).ok()?;
let nanos = b::SignedSubsecNanosecond::check(nanos).ok()?;
if secs == b::UnixEpochSeconds::MIN && nanos > 0 {
None
} else {
Timestamp::new(secs, nanos).ok()
}
},
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[track_caller]
fn datetime(
year: i16,
month: i8,
day: i8,
hour: i8,
minute: i8,
second: i8,
subsec_nanosecond: i32,
) -> DateTime {
DateTime::new(
year,
month,
day,
hour,
minute,
second,
subsec_nanosecond,
)
.unwrap()
}
#[track_caller]
fn stamp(second: i64, subsec: i32) -> Timestamp {
Timestamp::new(second, subsec).unwrap()
}
#[track_caller]
fn offset(second: i32) -> Offset {
Offset::from_seconds(second).unwrap()
}
#[test]
fn new_ok() {
let ts = stamp(0, 0);
assert_eq!(ts, Timestamp::UNIX_EPOCH);
let ts = stamp(0, 123_000_000);
assert_eq!(ts.as_second(), 0);
assert_eq!(ts.subsec_nanosecond(), 123_000_000);
let ts = stamp(0, -123_000_000);
assert_eq!(ts.as_second(), 0);
assert_eq!(ts.subsec_nanosecond(), -123_000_000);
let ts = stamp(1, 0);
assert_eq!(ts.as_second(), 1);
assert_eq!(ts.subsec_nanosecond(), 0);
let ts = stamp(-1, 0);
assert_eq!(ts.as_second(), -1);
assert_eq!(ts.subsec_nanosecond(), 0);
let ts = stamp(1, 123_000_000);
assert_eq!(ts.as_second(), 1);
assert_eq!(ts.subsec_nanosecond(), 123_000_000);
let ts = stamp(-1, -123_000_000);
assert_eq!(ts.as_second(), -1);
assert_eq!(ts.subsec_nanosecond(), -123_000_000);
let ts = stamp(1, -123_000_000);
assert_eq!(ts.as_second(), 0);
assert_eq!(ts.subsec_nanosecond(), 877_000_000);
let ts = stamp(-1, 123_000_000);
assert_eq!(ts.as_second(), 0);
assert_eq!(ts.subsec_nanosecond(), -877_000_000);
let ts = stamp(-377705023201, 0);
assert_eq!(ts, Timestamp::MIN);
let ts = stamp(253402207200, 999_999_999);
assert_eq!(ts, Timestamp::MAX);
}
#[test]
fn new_err() {
assert!(Timestamp::new(0, 1_000_000_000).is_err());
assert!(Timestamp::new(0, -1_000_000_000).is_err());
assert!(Timestamp::new(1, 1_000_000_000).is_err());
assert!(Timestamp::new(1, -1_000_000_000).is_err());
assert!(Timestamp::new(-1, 1_000_000_000).is_err());
assert!(Timestamp::new(-1, -1_000_000_000).is_err());
assert!(Timestamp::new(0, i32::MAX).is_err());
assert!(Timestamp::new(0, i32::MIN).is_err());
assert!(Timestamp::new(-377705023201, -1).is_err());
assert!(Timestamp::new(253402207201, 0).is_err());
}
#[test]
fn to_datetime_no_subsec() {
let dt = datetime(1970, 1, 1, 0, 0, 0, 0);
assert_eq!(stamp(0, 0).to_datetime(offset(0)), dt);
assert_eq!(stamp(-3600, 0).to_datetime(offset(3600)), dt);
assert_eq!(stamp(3600, 0).to_datetime(offset(-3600)), dt);
let dt = datetime(1969, 12, 31, 23, 30, 0, 0);
assert_eq!(stamp(-1800, 0).to_datetime(offset(0)), dt);
assert_eq!(stamp(-5400, 0).to_datetime(offset(3600)), dt);
assert_eq!(stamp(1800, 0).to_datetime(offset(-3600)), dt);
let dt = datetime(1970, 1, 1, 0, 30, 0, 0);
assert_eq!(stamp(1800, 0).to_datetime(offset(0)), dt);
assert_eq!(stamp(-1800, 0).to_datetime(offset(3600)), dt);
assert_eq!(stamp(5400, 0).to_datetime(offset(-3600)), dt);
}
#[test]
fn to_datetime_with_subsec() {
let dt = datetime(1970, 1, 1, 0, 0, 0, 123);
assert_eq!(stamp(0, 123).to_datetime(offset(0)), dt);
assert_eq!(stamp(-3599, -999_999_877).to_datetime(offset(3600)), dt);
assert_eq!(stamp(3600, 123).to_datetime(offset(-3600)), dt);
let dt = datetime(1969, 12, 31, 23, 30, 0, 123);
assert_eq!(stamp(-1799, -999_999_877).to_datetime(offset(0)), dt);
assert_eq!(stamp(-5399, -999_999_877).to_datetime(offset(3600)), dt);
assert_eq!(stamp(1800, 123).to_datetime(offset(-3600)), dt);
let dt = datetime(1970, 1, 1, 0, 30, 0, 123);
assert_eq!(stamp(1800, 123).to_datetime(offset(0)), dt);
assert_eq!(stamp(-1799, -999_999_877).to_datetime(offset(3600)), dt);
assert_eq!(stamp(5400, 123).to_datetime(offset(-3600)), dt);
}
#[test]
fn to_datetime_limits() {
assert_eq!(Timestamp::MIN.to_datetime(Offset::MIN), DateTime::MIN);
assert_eq!(Timestamp::MAX.to_datetime(Offset::MAX), DateTime::MAX);
}
quickcheck::quickcheck! {
fn prop_timestamp_datetime_roundtrip(
ts: Timestamp,
offset: Offset
) -> bool {
let dt = ts.to_datetime(offset);
let got = dt.to_timestamp(offset).unwrap();
got == ts
}
}
}
+306
View File
@@ -0,0 +1,306 @@
/*!
Building blocks for supporting time zones.
*/
use crate::{util::SmallStr, Timestamp};
mod offset;
pub mod posix;
pub mod tzif;
pub use self::offset::{
AmbiguousError, AmbiguousOffset, AmbiguousTimestamp, Offset,
};
/// A limit on how much stack space we're willing to use for time zone
/// abbreviations.
///
/// POSIX says this:
///
/// > Indicate no less than three, nor more than {TZNAME_MAX}, bytes that are
/// > the designation for the standard (std) or the alternative (dst -such as
/// > Daylight Savings Time) timezone.
///
/// But it doesn't seem worth the trouble to query `TZNAME_MAX`. Interestingly,
/// IANA says:
///
/// > are 3 or more characters specifying the standard and daylight saving time
/// > (DST) zone abbreviations
///
/// Which implies that IANA thinks there is no limit. But that seems unwise.
/// Moreover, in practice, it seems like the `date` utility supports fairly
/// long abbreviations. On my mac (so, BSD `date` as I understand it):
///
/// ```text
/// $ TZ=ZZZ5YYYYYYYYYYYYYYYYYYYYY date
/// Sun Mar 17 20:05:58 YYYYYYYYYYYYYYYYYYYYY 2024
/// ```
///
/// And on my Linux machine (so, GNU `date`):
///
/// ```text
/// $ TZ=ZZZ5YYYYYYYYYYYYYYYYYYYYY date
/// Sun Mar 17 08:05:36 PM YYYYYYYYYYYYYYYYYYYYY 2024
/// ```
///
/// I don't know exactly what limit these programs use, but 30 seems good
/// enough?
///
/// Previously, I had been using 255 and stuffing the string in a `Box<str>`.
/// But as part of work on [#168], I was looking to remove allocation from as
/// many places as possible. And this was one candidate. But making room on the
/// stack for 255 byte abbreviations seemed gratuitous. So I picked something
/// smaller. If we come across an abbreviation bigger than this max, then we'll
/// error.
///
/// In environments with dynamic memory allocation, this maximum is just the
/// maximum number of bytes we're willing to spend on array-backed storage of
/// a time zone abbreviation. If we hit anything bigger, we'll use the heap.
///
/// In core-only environments, we use a bigger limit as mentioned above.
/// Anything bigger than this will result in a parse error.
///
/// [#168]: https://github.com/BurntSushi/jiff/issues/168
const TIME_ZONE_ABBREVIATION_MAX: usize = {
#[cfg(feature = "alloc")]
{
// 6 + 1 byte for the length gives us a nice 7 bytes total for the
// array.
6
}
#[cfg(not(feature = "alloc"))]
{
REASONABLE_ABBREVIATION_MAX
}
};
/// When a time zone abbreviation is bigger than this, we give up and error.
const REASONABLE_ABBREVIATION_MAX: usize = {
#[cfg(feature = "alloc")]
{
// Let this expand to a pretty unreasonable amount.
// We could make this even higher, but we should have some
// kind of limit.
255
}
#[cfg(not(feature = "alloc"))]
{
// We make this the same as the array capacity maximum in environments
// with dynamic memory allocation as a conservative choice.
//
// This seems short, but at time of writing, the maximum possible
// abbreviation in the tzdb is 5 bytes.
//
// Actually, we make this bigger so that an abbreviation can fit a
// full offset to second resolution. e.g., `+10:30:25`.
9
}
};
/// A limit on how much stack space we're willing to use for time zone
/// identifiers.
///
/// As of 2026-07-03, 32 is the length of the longest IANA time zone
/// identifier. Specifically, `America/Argentina/ComodRivadavia`. Anything
/// bigger than this will error in core-only environments and spill
/// over to the heap in all other environments. For environments with
/// a heap, we set a slightly smaller array to make the total size a
/// bit smaller (4 words on x86-64 instead of 5). This just means that
/// `America/Argentina/ComodRivadavia` will spill to the heap, but nothing else
/// should.
const TIME_ZONE_ID_MAX: usize = {
#[cfg(feature = "alloc")]
{
30
}
#[cfg(not(feature = "alloc"))]
{
32
}
};
/// A type that defines the storage for an IANA time zone identifier.
pub type TimeZoneId = SmallStr<TIME_ZONE_ID_MAX>;
/// A type that defines the storage for a time zone abbreviation.
///
/// For an abbreviation whose length is less than or equal to a certain
/// implementation defined number, it will be stored inline inside an array.
/// All other cases spill out into the heap. (Unless callers do not have
/// dynamic memory allocation, in which case, whatever tried to store the
/// time zone abbreviation will return an error. For example, parsing a POSIX
/// time zone transition rule will fail in that case.)
pub type Abbreviation = SmallStr<TIME_ZONE_ABBREVIATION_MAX>;
/// A representation a single time zone transition.
#[derive(Clone, Debug)]
pub struct Transition {
timestamp: Timestamp,
info: OffsetInfo,
}
impl Transition {
/// Returns the timestamp at which this transition began.
pub fn timestamp(&self) -> Timestamp {
self.timestamp
}
/// Returns the offset corresponding to this time zone transition. All
/// instants at and following this transition's timestamp (and before the
/// next transition's timestamp) need to apply this offset from UTC to get
/// the civil or "local" time in the corresponding time zone.
pub fn offset(&self) -> Offset {
self.info.offset()
}
/// Returns the time zone abbreviation corresponding to this time
/// zone transition.
pub fn abbreviation(&self) -> &Abbreviation {
&self.info.abbreviation()
}
/// Returns whether daylight saving time is enabled for this time zone
/// transition.
pub fn dst(&self) -> Dst {
self.info.dst()
}
/// Consumes this transition and returns the underlying `OffsetInfo`.
pub fn into_offset_info(self) -> OffsetInfo {
self.info
}
}
/// Information associated with an offset when doing a time zone transition
/// lookup.
///
/// Callers should generally only need the offset. This exposes additional
/// information such as the time zone abbreviation or whether a timestamp is in
/// daylight saving time or not.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct OffsetInfo {
offset: Offset,
abbreviation: Abbreviation,
dst: Dst,
}
impl OffsetInfo {
/// Returns the offset corresponding to this time zone transition. All
/// instants at and following this transition's timestamp (and before the
/// next transition's timestamp) need to apply this offset from UTC to get
/// the civil or "local" time in the corresponding time zone.
pub fn offset(&self) -> Offset {
self.offset
}
/// Returns the time zone abbreviation corresponding to this time
/// zone transition.
pub fn abbreviation(&self) -> &Abbreviation {
&self.abbreviation
}
/// Consumes this offset info and returns its abbreviation.
pub fn into_abbreviation(self) -> Abbreviation {
self.abbreviation
}
/// Returns whether daylight saving time is enabled for this time zone
/// transition.
pub fn dst(&self) -> Dst {
self.dst
}
}
/// An enum indicating whether a particular datetime is in daylight saving time
/// (DST) or not.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Dst {
/// DST is not in effect. In other words, standard time is in effect.
No,
/// DST is in effect.
Yes,
}
impl Dst {
/// Returns true when this value is equal to `Dst::Yes`.
pub fn is_dst(self) -> bool {
matches!(self, Dst::Yes)
}
/// Returns true when this value is equal to `Dst::No`.
///
/// `std` in this context refers to "standard time." That is, it is the
/// offset from UTC used when DST is not in effect.
pub fn is_std(self) -> bool {
matches!(self, Dst::No)
}
}
impl From<bool> for Dst {
fn from(is_dst: bool) -> Dst {
if is_dst {
Dst::Yes
} else {
Dst::No
}
}
}
/// Creates a new time zone offset in a `const` context from a given number
/// of hours.
///
/// Negative offsets correspond to time zones west of the prime meridian,
/// while positive offsets correspond to time zones east of the prime
/// meridian. Equivalently, in all cases, `civil-time - offset = UTC`.
///
/// The fallible non-const version of this constructor is
/// [`Offset::from_hours`].
///
/// This is a convenience free function for [`Offset::constant`]. It is
/// intended to provide a terse syntax for constructing `Offset` values from
/// a value that is known to be valid.
///
/// # Panics
///
/// This routine panics when the given number of hours is out of range.
/// Namely, `hours` must be in the range `-25..=25`.
///
/// Similarly, when used in a const context, an out of bounds hour will prevent
/// your Rust program from compiling.
///
/// # Example
///
/// ```
/// use jiff_core::tz::offset;
///
/// let o = offset(-5);
/// assert_eq!(o.seconds(), -18_000);
/// let o = offset(5);
/// assert_eq!(o.seconds(), 18_000);
/// ```
#[inline]
pub const fn offset(hours: i8) -> Offset {
Offset::constant(hours)
}
#[cfg(test)]
mod tests {
use super::*;
// Don't bother trying to test this on non-64 bit. It's too annoying to
// keep this test updated.
#[cfg(target_pointer_width = "64")]
#[test]
fn sizes() {
#[cfg(feature = "alloc")]
assert_eq!(24, core::mem::size_of::<Abbreviation>());
#[cfg(not(feature = "alloc"))]
assert_eq!(16, core::mem::size_of::<Abbreviation>());
#[cfg(feature = "alloc")]
assert_eq!(32, core::mem::size_of::<TimeZoneId>());
#[cfg(not(feature = "alloc"))]
assert_eq!(40, core::mem::size_of::<TimeZoneId>());
}
}
+770
View File
@@ -0,0 +1,770 @@
use crate::{
bounds::{self as b, RangeError},
civil::{self, DateTime},
constants as c,
macros::{rtry, unwrapr},
Timestamp,
};
/// A fixed offset, in seconds, from UTC.
#[derive(Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct Offset {
seconds: i32,
}
impl Offset {
/// The minimum possible offset from UTC.
pub const MIN: Offset = Offset { seconds: b::OffsetTotalSeconds::MIN };
/// The maximum possible offset from UTC.
pub const MAX: Offset = Offset { seconds: b::OffsetTotalSeconds::MAX };
/// The UTC offset.
pub const UTC: Offset = Offset { seconds: 0 };
/// The zero offset.
pub const ZERO: Offset = Offset { seconds: 0 };
/// Creates a new time zone offset in a `const` context from a given number
/// of hours.
#[inline]
pub const fn constant(hours: i8) -> Offset {
unwrapr!(Offset::from_hours(hours), "invalid time zone offset hours")
}
/// Creates a new time zone offset in a `const` context from a given number
/// of seconds.
#[inline]
pub const fn constant_seconds(seconds: i32) -> Offset {
unwrapr!(
Offset::from_seconds(seconds),
"invalid time zone offset seconds",
)
}
/// Creates a new time zone offset from a given number of hours.
///
/// Negative offsets correspond to time zones west of the prime meridian,
/// while positive offsets correspond to time zones east of the prime
/// meridian. Equivalently, in all cases, `civil-time - offset = UTC`.
#[inline]
pub const fn from_hours(hours: i8) -> Result<Offset, RangeError> {
Offset::from_seconds(hours as i32 * c::SECS_PER_HOUR_32)
}
/// Returns a new time zone offset from UTC given its representation in
/// seconds.
///
/// An error is also returned when `seconds` is not in the range specified
/// by [`OffsetTotalSeconds`](b::OffsetTotalSeconds).
#[inline]
pub const fn from_seconds(seconds: i32) -> Result<Offset, RangeError> {
let seconds = rtry!(b::OffsetTotalSeconds::checkc(seconds as i64));
Ok(Offset { seconds })
}
/// Returns the seconds value corresponding to this time zone offset.
#[inline]
pub const fn seconds(self) -> i32 {
self.seconds
}
/// Returns the negation of this offset.
///
/// A negative offset will become positive and vice versa. This is a no-op
/// if the offset is zero.
///
/// This never panics.
#[inline]
pub const fn negate(self) -> Offset {
// OK because of the boundaries we enforce. `seconds` can never be
// `i32::MIN`.
Offset { seconds: -self.seconds() }
}
/// Returns the "sign number" or "signum" of this offset.
///
/// The number returned is `-1` when this offset is negative,
/// `0` when this offset is zero and `1` when this span is positive.
#[inline]
pub const fn signum(self) -> i8 {
self.seconds().signum() as i8
}
/// Returns true if and only if this offset is positive.
///
/// This returns false when the offset is zero or negative.
#[inline]
pub const fn is_positive(self) -> bool {
self.seconds() > 0
}
/// Returns true if and only if this offset is less than zero.
///
/// This returns false when the offset is zero or positive.
#[inline]
pub const fn is_negative(self) -> bool {
self.seconds() < 0
}
/// Returns true if and only if this offset is zero.
///
/// Or equivalently, when this offset corresponds to [`Offset::UTC`].
#[inline]
pub const fn is_zero(self) -> bool {
self.seconds() == 0
}
/// Adds the given number of seconds to this offset.
///
/// If the resulting offset would be outside the an offset's boundaries,
/// an error is returned.
#[inline]
pub const fn checked_add(
self,
seconds: i32,
) -> Result<Offset, RangeError> {
let seconds =
rtry!(b::OffsetTotalSeconds::checked_add(self.seconds(), seconds));
Ok(Offset { seconds })
}
/// Subtracts the given number of seconds from this offset.
///
/// If the resulting offset would be outside the an offset's boundaries,
/// an error is returned.
#[inline]
pub const fn checked_sub(
self,
seconds: i32,
) -> Result<Offset, RangeError> {
let seconds =
rtry!(b::OffsetTotalSeconds::checked_add(self.seconds(), seconds));
Ok(Offset { seconds })
}
/// Returns the number of seconds from this offset to `other`.
#[inline]
pub const fn until(self, other: Offset) -> i32 {
other.seconds() - self.seconds()
}
/// Returns the number of seconds since this offset from `other`.
#[inline]
pub const fn since(self, other: Offset) -> i32 {
self.seconds() - other.seconds()
}
/// Converts a Unix timestamp with an offset to a Gregorian datetime.
///
/// The offset should correspond to the number of seconds required to
/// add to this timestamp to get the local time.
#[inline]
pub const fn to_datetime(self, timestamp: Timestamp) -> civil::DateTime {
let offset = self;
let second = timestamp.as_second();
let mut nanosecond = timestamp.subsec_nanosecond();
// Shift second comfortably into the postive domain
// so that division and remainder can use unsigned math
// which is much faster.
// 30 * 400 years: 12,000 yr range > [-9,999..1970]
// (146097 being the number of days per 400 years).
const DAY_SHIFT: i32 = 30 * 146097;
const SEC_SHIFT: i64 = (DAY_SHIFT as i64) * 86_400;
let pos_sec = (second + (offset.seconds() as i64) + SEC_SHIFT) as u64;
let mut epoch_day = (pos_sec / 86_400) as i32;
let mut second = (pos_sec % 86_400) as i32;
if nanosecond < 0 {
if second > 0 {
second -= 1;
nanosecond += 1_000_000_000;
} else {
epoch_day -= 1;
second += 86_399;
nanosecond += 1_000_000_000;
}
}
epoch_day -= DAY_SHIFT;
// We should check whether having unchecked APIs
// would be beneficial here. In particular, the
// math above, coupled with the ranges allowed on
// `Timestamp` and `Offset` (by design) guarantee
// that our resulting datetime will always be in
// range.
let date = unwrapr!(
civil::UnixEpochDay::new(epoch_day),
"always valid Unix epoch day",
)
.to_date();
let time = unwrapr!(
unwrapr!(
civil::TimeSecond::new(second),
"always valid civil second time"
)
.to_time()
.with_subsec_nanosecond(nanosecond),
"always valid civil subsecond"
);
civil::DateTime::from_parts(date, time)
}
/// Converts the given civil datetime to a timestamp using this offset.
///
/// # Errors
///
/// This returns an error if this would have returned a timestamp outside
/// of its minimum and maximum values.
#[inline]
pub const fn to_timestamp(
self,
dt: civil::DateTime,
) -> Result<Timestamp, RangeError> {
let offset = self;
let epoch_day = dt.date().to_unix_epoch_day().day();
let mut second = (epoch_day as i64) * c::SECS_PER_CIVIL_DAY
+ (dt.time().to_second().second() as i64);
let mut nanosecond = dt.time().subsec_nanosecond();
second -= offset.seconds() as i64;
if second < 0 && nanosecond != 0 {
second += 1;
nanosecond -= c::NANOS_PER_SEC_32;
}
let second = rtry!(b::UnixEpochSeconds::checkc(second));
Ok(Timestamp::new_unchecked(second, nanosecond))
}
}
impl Offset {
#[inline]
fn part_hours(self) -> i8 {
(self.seconds() / c::SECS_PER_HOUR_32) as i8
}
#[inline]
fn part_minutes(self) -> i8 {
((self.seconds() / c::SECS_PER_MIN_32) % c::MINS_PER_HOUR_32) as i8
}
#[inline]
fn part_seconds(self) -> i8 {
(self.seconds() % c::SECS_PER_MIN_32) as i8
}
}
/// Negate this offset.
///
/// A positive offset becomes negative and vice versa. This is a no-op for the
/// zero offset.
///
/// This never panics.
impl core::ops::Neg for Offset {
type Output = Offset;
#[inline]
fn neg(self) -> Offset {
self.negate()
}
}
/// Adds a number of seconds to an `Offset`.
///
/// # Panics
///
/// When adding would result in a value outside the boundaries of a
/// `Offset`.
impl core::ops::Add<i32> for Offset {
type Output = Offset;
fn add(self, seconds: i32) -> Offset {
self.checked_add(seconds).unwrap()
}
}
/// Adds a number of seconds into an `Offset`.
///
/// # Panics
///
/// When adding would result in a value outside the boundaries of a
/// `Offset`.
impl core::ops::AddAssign<i32> for Offset {
#[inline]
fn add_assign(&mut self, rhs: i32) {
*self = *self + rhs;
}
}
/// Subtracts a number of seconds from an `Offset`.
///
/// # Panics
///
/// When adding would result in a value outside the boundaries of a
/// `Offset`.
impl core::ops::Sub<i32> for Offset {
type Output = Offset;
fn sub(self, seconds: i32) -> Offset {
self.checked_sub(seconds).unwrap()
}
}
/// Subtracts a number of seconds from an `Offset` in place.
///
/// # Panics
///
/// When adding would result in a value outside the boundaries of a
/// `Offset`.
impl core::ops::SubAssign<i32> for Offset {
#[inline]
fn sub_assign(&mut self, rhs: i32) {
*self = *self - rhs;
}
}
impl core::fmt::Debug for Offset {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let sign = if self.is_negative() { "-" } else { "" };
write!(
f,
"{sign}{:02}:{:02}:{:02}",
self.part_hours().unsigned_abs(),
self.part_minutes().unsigned_abs(),
self.part_seconds().unsigned_abs(),
)
}
}
impl core::fmt::Display for Offset {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let sign = if self.is_negative() { "-" } else { "+" };
let hours = self.part_hours().unsigned_abs();
let minutes = self.part_minutes().unsigned_abs();
let seconds = self.part_seconds().unsigned_abs();
if hours == 0 && minutes == 0 && seconds == 0 {
f.write_str("+00")
} else if hours != 0 && minutes == 0 && seconds == 0 {
write!(f, "{sign}{hours:02}")
} else if minutes != 0 && seconds == 0 {
write!(f, "{sign}{hours:02}:{minutes:02}")
} else {
write!(f, "{sign}{hours:02}:{minutes:02}:{seconds:02}")
}
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for Offset {
fn format(&self, f: defmt::Formatter) {
let sign = if self.is_negative() { "-" } else { "" };
defmt::write!(
f,
"{=str}{=u8:02}:{=u8:02}:{=u8:02}",
sign,
self.part_hours().unsigned_abs(),
self.part_minutes().unsigned_abs(),
self.part_seconds().unsigned_abs(),
)
}
}
#[cfg(test)]
impl quickcheck::Arbitrary for Offset {
fn arbitrary(g: &mut quickcheck::Gen) -> Offset {
let secs = b::OffsetTotalSeconds::arbitrary(g);
Offset::from_seconds(secs).unwrap_or(Offset::UTC)
}
fn shrink(&self) -> alloc::boxed::Box<dyn Iterator<Item = Self>> {
let secs = self.seconds();
alloc::boxed::Box::new(secs.shrink().filter_map(|secs| {
let secs = b::OffsetTotalSeconds::check(secs).ok()?;
Offset::from_seconds(secs).ok()
}))
}
}
/// A possibly ambiguous [`Offset`].
///
/// One of three possibilities encoded by this type occurs when converting a
/// civil datetime into a specific instant in time. In rare cases, the civil
/// datetime can fall into a gap or a fold, in which case, one of two offsets
/// could be applicable. Or, perhaps, neither. Callers must decide how best to
/// handle these cases.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum AmbiguousOffset {
/// The offset for a particular civil datetime and time zone is
/// unambiguous.
///
/// This is the overwhelmingly common case. In general, the only time this
/// case does not occur is when there is a transition to a different time
/// zone (rare) or to/from daylight saving time (occurs for 1 hour twice
/// in year in many geographic locations).
Unambiguous {
/// The offset from UTC for the corresponding civil datetime given. The
/// offset is determined via the relevant time zone data, and in this
/// case, there is only one possible offset that could be applied to
/// the given civil datetime.
offset: Offset,
},
/// The offset for a particular civil datetime and time zone is ambiguous
/// because there is a gap.
///
/// This most commonly occurs when a civil datetime corresponds to an hour
/// that was "skipped" in a jump to DST (daylight saving time).
Gap {
/// The offset corresponding to the time before a gap.
///
/// For example, given a time zone of `America/Los_Angeles`, the offset
/// for time immediately preceding `2020-03-08T02:00:00` is `-08`.
before: Offset,
/// The offset corresponding to the later time in a gap.
///
/// For example, given a time zone of `America/Los_Angeles`, the offset
/// for time immediately following `2020-03-08T02:59:59` is `-07`.
after: Offset,
},
/// The offset for a particular civil datetime and time zone is ambiguous
/// because there is a fold.
///
/// This most commonly occurs when a civil datetime corresponds to an hour
/// that was "repeated" in a jump to standard time from DST (daylight
/// saving time).
Fold {
/// The offset corresponding to the earlier time in a fold.
///
/// For example, given a time zone of `America/Los_Angeles`, the offset
/// for time on the first `2020-11-01T01:00:00` is `-07`.
before: Offset,
/// The offset corresponding to the earlier time in a fold.
///
/// For example, given a time zone of `America/Los_Angeles`, the offset
/// for time on the second `2020-11-01T01:00:00` is `-08`.
after: Offset,
},
}
impl AmbiguousOffset {
#[inline]
pub(crate) const fn into_ambiguous_timestamp(
self,
dt: DateTime,
) -> AmbiguousTimestamp {
AmbiguousTimestamp { dt, offset: self }
}
}
/// A possibly ambiguous [`Timestamp`].
///
/// While this is called an ambiguous _timestamp_, the thing that is
/// actually ambiguous is the offset. That is, an ambiguous timestamp is
/// actually a pair of a [`civil::DateTime`](crate::civil::DateTime) and an
/// [`AmbiguousOffset`].
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct AmbiguousTimestamp {
dt: DateTime,
offset: AmbiguousOffset,
}
impl AmbiguousTimestamp {
/// Returns the civil datetime that was used to create this ambiguous
/// timestamp.
///
/// # Example
///
/// ```
/// use jiff_core::{civil::date, tz::posix};
///
/// let tz = posix::TimeZone::parse("EST5EDT,M3.2.0,M11.1.0").unwrap();
/// let dt = date(2024, 7, 10).at(17, 15, 0, 0);
/// let ts = tz.to_ambiguous_timestamp(dt);
/// assert_eq!(ts.datetime(), dt);
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[inline]
pub const fn datetime(&self) -> DateTime {
self.dt
}
/// Returns the possibly ambiguous offset that is the ultimate source of
/// ambiguity.
///
/// Most civil datetimes are not ambiguous, and thus, the offset will not
/// be ambiguous either. In this case, the offset returned will be the
/// [`AmbiguousOffset::Unambiguous`] variant.
///
/// But, not all civil datetimes are unambiguous. There are exactly two
/// cases where a civil datetime can be ambiguous: when a civil datetime
/// does not exist (a gap) or when a civil datetime is repeated (a fold).
/// In both such cases, the _offset_ is the thing that is ambiguous as
/// there are two possible choices for the offset in both cases: the offset
/// before the transition (whether it's a gap or a fold) or the offset
/// after the transition.
///
/// This type captures the fact that computing an offset from a civil
/// datetime in a particular time zone is in one of three possible states:
///
/// 1. It is unambiguous.
/// 2. It is ambiguous because there is a gap in time.
/// 3. It is ambiguous because there is a fold in time.
///
/// # Example
///
/// ```
/// use jiff_core::{civil::date, tz::{self, posix, AmbiguousOffset}};
///
/// let tz = posix::TimeZone::parse("EST5EDT,M3.2.0,M11.1.0").unwrap();
///
/// // Not ambiguous.
/// let dt = date(2024, 7, 15).at(17, 30, 0, 0);
/// let ts = tz.to_ambiguous_timestamp(dt);
/// assert_eq!(ts.offset(), AmbiguousOffset::Unambiguous {
/// offset: tz::offset(-4),
/// });
///
/// // Ambiguous because of a gap.
/// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
/// let ts = tz.to_ambiguous_timestamp(dt);
/// assert_eq!(ts.offset(), AmbiguousOffset::Gap {
/// before: tz::offset(-5),
/// after: tz::offset(-4),
/// });
///
/// // Ambiguous because of a fold.
/// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
/// let ts = tz.to_ambiguous_timestamp(dt);
/// assert_eq!(ts.offset(), AmbiguousOffset::Fold {
/// before: tz::offset(-4),
/// after: tz::offset(-5),
/// });
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[inline]
pub const fn offset(&self) -> AmbiguousOffset {
self.offset
}
/// Returns true if and only if this possibly ambiguous timestamp is
/// actually ambiguous.
///
/// This occurs precisely in cases when the offset is _not_
/// [`AmbiguousOffset::Unambiguous`].
///
/// # Example
///
/// ```
/// use jiff_core::{civil::date, tz::posix};
///
/// let tz = posix::TimeZone::parse("EST5EDT,M3.2.0,M11.1.0").unwrap();
///
/// // Not ambiguous.
/// let dt = date(2024, 7, 15).at(17, 30, 0, 0);
/// let ts = tz.to_ambiguous_timestamp(dt);
/// assert!(!ts.is_ambiguous());
///
/// // Ambiguous because of a gap.
/// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
/// let ts = tz.to_ambiguous_timestamp(dt);
/// assert!(ts.is_ambiguous());
///
/// // Ambiguous because of a fold.
/// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
/// let ts = tz.to_ambiguous_timestamp(dt);
/// assert!(ts.is_ambiguous());
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[inline]
pub const fn is_ambiguous(&self) -> bool {
!matches!(self.offset(), AmbiguousOffset::Unambiguous { .. })
}
/// Disambiguates this timestamp according to the "compatible" strategy.
///
/// If this timestamp is unambiguous, then this is a no-op.
///
/// The "compatible" strategy selects the offset corresponding to the civil
/// time after a gap, and the offset corresponding to the civil time before
/// a fold. This is what is specified in [RFC 5545].
///
/// [RFC 5545]: https://datatracker.ietf.org/doc/html/rfc5545
///
/// # Errors
///
/// This returns an error when the combination of the civil datetime
/// and offset would lead to a `Timestamp` outside of the
/// [`Timestamp::MIN`] and [`Timestamp::MAX`] limits. This only occurs
/// when the civil datetime is "close" to its own [`DateTime::MIN`]
/// and [`DateTime::MAX`] limits.
#[inline]
pub const fn compatible(self) -> Result<Timestamp, RangeError> {
let offset = match self.offset() {
AmbiguousOffset::Unambiguous { offset } => offset,
AmbiguousOffset::Gap { before, .. } => before,
AmbiguousOffset::Fold { before, .. } => before,
};
offset.to_timestamp(self.dt)
}
/// Disambiguates this timestamp according to the "earlier" strategy.
///
/// If this timestamp is unambiguous, then this is a no-op.
///
/// The "earlier" strategy selects the offset corresponding to the civil
/// time before a gap, and the offset corresponding to the civil time
/// before a fold.
///
/// # Errors
///
/// This returns an error when the combination of the civil datetime
/// and offset would lead to a `Timestamp` outside of the
/// [`Timestamp::MIN`] and [`Timestamp::MAX`] limits. This only occurs
/// when the civil datetime is "close" to its own [`DateTime::MIN`]
/// and [`DateTime::MAX`] limits.
#[inline]
pub const fn earlier(self) -> Result<Timestamp, RangeError> {
let offset = match self.offset() {
AmbiguousOffset::Unambiguous { offset } => offset,
AmbiguousOffset::Gap { after, .. } => after,
AmbiguousOffset::Fold { before, .. } => before,
};
offset.to_timestamp(self.dt)
}
/// Disambiguates this timestamp according to the "later" strategy.
///
/// If this timestamp is unambiguous, then this is a no-op.
///
/// The "later" strategy selects the offset corresponding to the civil
/// time after a gap, and the offset corresponding to the civil time
/// after a fold.
///
/// # Errors
///
/// This returns an error when the combination of the civil datetime
/// and offset would lead to a `Timestamp` outside of the
/// [`Timestamp::MIN`] and [`Timestamp::MAX`] limits. This only occurs
/// when the civil datetime is "close" to its own [`DateTime::MIN`]
/// and [`DateTime::MAX`] limits.
#[inline]
pub const fn later(self) -> Result<Timestamp, RangeError> {
let offset = match self.offset() {
AmbiguousOffset::Unambiguous { offset } => offset,
AmbiguousOffset::Gap { before, .. } => before,
AmbiguousOffset::Fold { after, .. } => after,
};
offset.to_timestamp(self.dt)
}
/// Disambiguates this timestamp according to the "reject" strategy.
///
/// If this timestamp is unambiguous, then this is a no-op.
///
/// The "reject" strategy always returns an error when the timestamp
/// is ambiguous.
///
/// # Errors
///
/// This returns an error when the combination of the civil datetime
/// and offset would lead to a `Timestamp` outside of the
/// [`Timestamp::MIN`] and [`Timestamp::MAX`] limits. This only occurs
/// when the civil datetime is "close" to its own [`DateTime::MIN`]
/// and [`DateTime::MAX`] limits.
///
/// This also returns an error when the timestamp is ambiguous.
///
/// # Example
///
/// ```
/// use jiff_core::{civil::date, tz::{posix, Offset}};
///
/// let tz = posix::TimeZone::parse("EST5EDT,M3.2.0,M11.1.0").unwrap();
///
/// // Not ambiguous.
/// let dt = date(2024, 7, 15).at(17, 30, 0, 0);
/// let ts = tz.to_ambiguous_timestamp(dt);
/// assert_eq!(
/// ts.later().unwrap().to_datetime(Offset::UTC),
/// date(2024, 7, 15).at(21, 30, 0, 0),
/// );
///
/// // Ambiguous because of a gap.
/// let dt = date(2024, 3, 10).at(2, 30, 0, 0);
/// let ts = tz.to_ambiguous_timestamp(dt);
/// assert!(ts.unambiguous().is_err());
///
/// // Ambiguous because of a fold.
/// let dt = date(2024, 11, 3).at(1, 30, 0, 0);
/// let ts = tz.to_ambiguous_timestamp(dt);
/// assert!(ts.unambiguous().is_err());
/// ```
#[inline]
pub const fn unambiguous(self) -> Result<Timestamp, AmbiguousError> {
let offset = match self.offset() {
AmbiguousOffset::Unambiguous { offset } => offset,
AmbiguousOffset::Gap { before, after } => {
return Err(AmbiguousError {
kind: AmbiguousErrorKind::BecauseGap { before, after },
});
}
AmbiguousOffset::Fold { before, after } => {
return Err(AmbiguousError {
kind: AmbiguousErrorKind::BecauseFold { before, after },
});
}
};
match offset.to_timestamp(self.dt) {
Ok(timestamp) => Ok(timestamp),
Err(range_error) => Err(AmbiguousError {
kind: AmbiguousErrorKind::Range(range_error),
}),
}
}
}
/// An error that occurs when an unmabiguous civil datetime is demanded.
///
/// This surfaces via the [`AmbiguousTimestamp::unambiguous`] API.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct AmbiguousError {
kind: AmbiguousErrorKind,
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum AmbiguousErrorKind {
Range(RangeError),
BecauseFold { before: Offset, after: Offset },
BecauseGap { before: Offset, after: Offset },
}
impl core::fmt::Display for AmbiguousError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::AmbiguousErrorKind::*;
match self.kind {
Range(ref err) => core::fmt::Display::fmt(err, f),
BecauseFold { before, after } => write!(
f,
"datetime is ambiguous since it falls into a \
fold between offsets {before} and {after}",
),
BecauseGap { before, after } => write!(
f,
"datetime is ambiguous since it falls into a \
gap between offsets {before} and {after}",
),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for AmbiguousError {}
File diff suppressed because it is too large Load Diff
+584
View File
@@ -0,0 +1,584 @@
/*!
Implements [TZif] time zone parsing and transition handling.
The TZif format is used by the [Time Zone Database].
The parser in this module is designed to handle untrusted input. That is, there
is no input that should cause it to panic or allocate memory in a way that is
not proportional to the size of the TZif data.
These binary files are the ones commonly found in Unix distributions in the
`/usr/share/zoneinfo` directory.
[Time Zone Database]: https://www.iana.org/time-zones
[TZif]: https://datatracker.ietf.org/doc/rfc9636/
*/
use crate::{
bounds::RangeError,
civil,
macros::unwrapr,
tz::{self, posix, Abbreviation, Dst, Offset},
util::MaybeStaticSlice,
};
#[cfg(feature = "alloc")]
mod parser;
mod query;
#[cfg(feature = "alloc")]
pub use self::parser::ParseError;
/// A representation of a possibly named time zone backed by [TZif] data.
///
/// It is useful to represent a named time zone as distinct from the time zone
/// itself because a name is not inherently part of the TZif data. Indeed, there
/// are a couple reasons why it's important to separate the name:
///
/// * It's possible that no meaningful name exists. For example, a
/// `/etc/localtime` file with no obvious mapping to a name. (e.g., No symlink
/// and no discoverable Time Zone Database.)
/// * Some IANA time zone identifiers correspond to different regions, but
/// share the same TZif data.
///
/// [TZif]: https://datatracker.ietf.org/doc/rfc9636/
#[derive(Clone, Debug, PartialEq)]
pub struct MaybeNamedTimeZone {
/// The name of this TZif time zone. e.g., `America/New_York`.
pub name: Option<tz::TimeZoneId>,
/// The time zone itself.
pub tz: TimeZone,
}
impl MaybeNamedTimeZone {
/// Returns the underlying time zone name as a string.
#[inline]
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
/// Returns a reference to the underlying time zone definition.
#[inline]
pub fn tz(&self) -> &TimeZone {
&self.tz
}
}
/// A representation of an unnamed time zone backed by [TZif] data.
///
/// Two time zones are considered equivalent when their CRC32 sums are
/// equivalent.
///
/// [TZif]: https://datatracker.ietf.org/doc/rfc9636/
#[derive(Clone, Debug)]
// This ensures the alignment of this type is always *at least* 8 bytes. This
// is required for the pointer tagging inside of `TimeZone` to be sound. At
// time of writing (2024-02-24), this explicit `repr` isn't required on 64-bit
// systems since the type definition is such that it will have an alignment of
// at least 8 bytes anyway. But this *is* required for 32-bit systems, where
// the type definition at present only has an alignment of 4 bytes.
#[repr(align(8))]
pub struct TimeZone {
/// An ASCII byte corresponding to the version number. So, 0x50 is '2'.
pub version: u8,
/// A CRC32 checksum of the underlying TZif data.
///
/// This, along with the time zone's IANA identifier, is used to provide a
/// "best effort" but also cheap notion of strict equality between two time
/// zones.
pub checksum: u32,
/// The time zone abbreviations referenced by local time types.
pub designations: MaybeStaticSlice<Abbreviation>,
/// A POSIX time zone used for determining time zone transitions after the
/// last transition in some TZif data.
///
/// This is technically optional, but is usually present.
pub posix_tz: Option<posix::TimeZone>,
/// The local time types in this TZif data.
///
/// Each local time type represents a distinct combination of offset,
/// abbreviation and whether the region is in daylight saving time or not.
pub types: MaybeStaticSlice<LocalTimeType>,
/// The concrete transitions that make up this time zone.
pub transitions: Transitions,
}
impl TimeZone {
/// Converts this unnamed time zone into a time zone with the given name.
#[inline]
pub fn into_named(self, name: tz::TimeZoneId) -> MaybeNamedTimeZone {
self.into_maybe_named(Some(name))
}
/// Converts this unnamed time zone into a time zone with the given
/// optional name.
#[inline]
pub fn into_maybe_named(
self,
name: Option<tz::TimeZoneId>,
) -> MaybeNamedTimeZone {
MaybeNamedTimeZone { name, tz: self }
}
}
impl PartialEq for TimeZone {
fn eq(&self, rhs: &TimeZone) -> bool {
self.checksum == rhs.checksum
}
}
/// A "local time type" from TZif data.
///
/// This is referenced by time zone transitions. It may be used by one or
/// more time zone transitions. This contains information about whether
/// the transition moves into DST, its time zone abbreviation, and most
/// importantly, the offset.
#[derive(Clone, Copy, Debug)]
pub struct LocalTimeType {
/// The offset from UTC.
pub offset: Offset,
/// Whether the region is considered to be in daylight saving time or not.
pub dst: Dst,
/// An index into `TimeZone::designations` corresponding to the time zone
/// abbreviation for this local time type.
pub designation: u8,
/// It's unclear to the author of Jiff what this is or what it's supposed
/// to be used for.
pub indicator: Indicator,
}
impl LocalTimeType {
fn designation(&self) -> usize {
usize::from(self.designation)
}
}
/// The possible indicator values for standard/wall and UT/local.
///
/// Their purpose, as of 2026-07-06, is unknown to Jiff's author. But they are
/// represented here for completeness.
// Note that UT+Wall is not allowed.
//
// I honestly have no earthly clue what they mean. I've read the section about
// them in RFC 8536 several times and I can't make sense of it. I've even
// looked at data files that have these set and still can't make sense of
// them. I've even looked at what other datetime libraries do with these, and
// they all seem to just ignore them. Like, WTF. I've spent the last couple
// months of my life steeped in time, and I just cannot figure this out. Am I
// just dumb?
//
// Anyway, we parse them, but otherwise ignore them because that's what all
// the cool kids do.
//
// The default is `LocalWall`, which also occurs when no indicators are
// present.
//
// I tried again and still don't get it. Here's a dump for `Pacific/Honolulu`:
//
// ```text
// $ ./scripts/jiff-debug tzif /usr/share/zoneinfo/Pacific/Honolulu
// TIME ZONE NAME
// /usr/share/zoneinfo/Pacific/Honolulu
// LOCAL TIME TYPES
// 000: offset=-10:31:26, is_dst=false, designation=LMT, indicator=local/wall
// 001: offset=-10:30, is_dst=false, designation=HST, indicator=local/wall
// 002: offset=-09:30, is_dst=true, designation=HDT, indicator=local/wall
// 003: offset=-09:30, is_dst=true, designation=HWT, indicator=local/wall
// 004: offset=-09:30, is_dst=true, designation=HPT, indicator=ut/std
// 005: offset=-10, is_dst=false, designation=HST, indicator=local/wall
// TRANSITIONS
// 0000: -9999-01-02T01:59:59 :: -377705023201 :: type=0, -10:31:26, is_dst=false, LMT, local/wall
// 0001: 1896-01-13T22:31:26 :: -2334101314 :: type=1, -10:30, is_dst=false, HST, local/wall
// 0002: 1933-04-30T12:30:00 :: -1157283000 :: type=2, -09:30, is_dst=true, HDT, local/wall
// 0003: 1933-05-21T21:30:00 :: -1155436200 :: type=1, -10:30, is_dst=false, HST, local/wall
// 0004: 1942-02-09T12:30:00 :: -880198200 :: type=3, -09:30, is_dst=true, HWT, local/wall
// 0005: 1945-08-14T23:00:00 :: -769395600 :: type=4, -09:30, is_dst=true, HPT, ut/std
// 0006: 1945-09-30T11:30:00 :: -765376200 :: type=1, -10:30, is_dst=false, HST, local/wall
// 0007: 1947-06-08T12:30:00 :: -712150200 :: type=5, -10, is_dst=false, HST, local/wall
// POSIX TIME ZONE STRING
// HST10
// ```
//
// See how type 004 has a ut/std indicator? What the fuck does that mean?
// All transitions are defined in terms of UTC. I confirmed this with `zdump`:
//
// ```text
// $ zdump -v Pacific/Honolulu | rg 1945
// Pacific/Honolulu Tue Aug 14 22:59:59 1945 UT = Tue Aug 14 13:29:59 1945 HWT isdst=1 gmtoff=-34200
// Pacific/Honolulu Tue Aug 14 23:00:00 1945 UT = Tue Aug 14 13:30:00 1945 HPT isdst=1 gmtoff=-34200
// Pacific/Honolulu Sun Sep 30 11:29:59 1945 UT = Sun Sep 30 01:59:59 1945 HPT isdst=1 gmtoff=-34200
// Pacific/Honolulu Sun Sep 30 11:30:00 1945 UT = Sun Sep 30 01:00:00 1945 HST isdst=0 gmtoff=-37800
// ```
//
// The times match up. All of them. The indicators don't seem to make a
// difference. I'm clearly missing something.
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug)]
pub enum Indicator {
LocalWall,
LocalStandard,
UTStandard,
}
/// The set of transitions in TZif data, laid out in column orientation.
///
/// The column orientation is used to make TZ lookups faster. Specifically,
/// for finding an offset for a timestamp, we do a binary search on
/// `timestamps`. For finding an offset for a local datetime, we do a binary
/// search on `civil_starts`. By making these two distinct sequences with
/// nothing else in them, we make them as small as possible and thus improve
/// cache locality.
///
/// All sequences in this type are in correspondence with one another. They
/// are all guaranteed to have the same length.
#[derive(Clone, Debug)]
pub struct Transitions {
/// The timestamp at which this transition begins.
pub timestamps: MaybeStaticSlice<Timestamp>,
/// The wall clock time for when a transition begins.
pub civil_starts: MaybeStaticSlice<DateTime>,
/// The wall clock time for when a transition ends.
///
/// This is equivalent to the corresponding entry in `civil_starts` when
/// the corresponding transition is neither a gap nor a fold. A transition
/// that isn't a gap or a fold keeps the offset the same but may change
/// something else, like the abbreviation or whether it's considered
/// daylight saving time.
pub civil_ends: MaybeStaticSlice<DateTime>,
/// Any other relevant data about a transition, such as its local type
/// index and the transition kind.
pub infos: MaybeStaticSlice<TransitionInfo>,
}
/// TZif transition info beyond the timestamp and civil datetime.
///
/// For example, this contains a transition's "local type index," which in
/// turn gives access to the offset (among other metadata) for that transition.
#[derive(Clone, Copy, Debug)]
pub struct TransitionInfo {
/// The index into the sequence of local time type records. This is what
/// provides the correct offset (from UTC) that is active beginning at
/// this transition.
pub type_index: u8,
/// The boundary condition for quickly determining if a given wall clock
/// time is ambiguous (i.e., falls in a gap or a fold).
pub kind: TransitionKind,
}
/// The kind of a transition.
///
/// This is used when trying to determine the offset for a local datetime. It
/// indicates how the corresponding civil datetimes in `civil_starts` and
/// `civil_ends` should be interpreted. That is, there are three possible
/// cases:
///
/// 1. The offset of this transition is equivalent to the offset of the
/// previous transition. That means there are no ambiguous civil datetimes
/// between the transitions. This can occur, e.g., when the time zone
/// abbreviation changes.
/// 2. The offset of the transition is greater than the offset of the previous
/// transition. That means there is a "gap" in local time between the
/// transitions. This typically corresponds to entering daylight saving time.
/// It is usually, but not always, 1 hour.
/// 3. The offset of the transition is less than the offset of the previous
/// transition. That means there is a "fold" in local time where time is
/// repeated. This typically corresponds to leaving daylight saving time. It
/// is usually, but not always, 1 hour.
///
/// # More explanation
///
/// This, when combined with `civil_starts` and `civil_ends` in
/// `Transitions`, explicitly represents ambiguous wall clock times that
/// occur at the boundaries of transitions.
///
/// The start of the wall clock time is always the earlier possible wall clock
/// time that could occur with this transition's corresponding offset. For a
/// gap, it's the previous transition's offset. For a fold, it's the current
/// transition's offset.
///
/// For example, DST for `America/New_York` began on `2024-03-10T07:00:00+00`.
/// The offset prior to this instant in time is `-05`, corresponding
/// to standard time (EST). Thus, in wall clock time, DST began at
/// `2024-03-10T02:00:00`. And since this is a DST transition that jumps ahead
/// an hour, the start of DST also corresponds to the start of a gap. That is,
/// the times `02:00:00` through `02:59:59` never appear on a clock for this
/// hour. The question is thus: which offset should we apply to `02:00:00`?
/// We could apply the offset from the earlier transition `-05` and get
/// `2024-03-10T01:00:00-05` (that's `2024-03-10T06:00:00+00`), or we could
/// apply the offset from the later transition `-04` and get
/// `2024-03-10T03:00:00-04` (that's `2024-03-10T07:00:00+00`).
///
/// So in the above, we would have a `Gap` variant where `start` (inclusive) is
/// `2024-03-10T02:00:00` and `end` (exclusive) is `2024-03-10T03:00:00`.
///
/// The fold case is the same idea, but where the same time is repeated.
/// For example, in `America/New_York`, standard time began on
/// `2024-11-03T06:00:00+00`. The offset prior to this instant in time
/// is `-04`, corresponding to DST (EDT). Thus, in wall clock time, DST
/// ended at `2024-11-03T02:00:00`. However, since this is a fold, the
/// actual set of ambiguous times begins at `2024-11-03T01:00:00` and
/// ends at `2024-11-03T01:59:59.999999999`. That is, the wall clock time
/// `2024-11-03T02:00:00` is unambiguous.
///
/// So in the fold case above, we would have a `Fold` variant where
/// `start` (inclusive) is `2024-11-03T01:00:00` and `end` (exclusive) is
/// `2024-11-03T02:00:00`.
///
/// Since this gets bundled in with the sorted sequence of transitions, we'll
/// use the "start" time in all three cases as our target of binary search.
/// Once we land on a transition, we'll know our given wall clock time is
/// greater than or equal to its start wall clock time. At that point, to
/// determine if there is ambiguity, we merely need to determine if the given
/// wall clock time is less than the corresponding `end` time. If it is, then
/// it falls in a gap or fold. Otherwise, it's unambiguous.
///
/// Note that we could compute these datetime values while searching for the
/// correct transition, but there's a fair bit of math involved in going
/// between timestamps (which is what TZif gives us) and calendar datetimes
/// (which is what we're given as input). It is also necessary that we offset
/// the timestamp given in TZif at some point, since it is in UTC and the
/// datetime given is in wall clock time. So I decided it would be worth
/// pre-computing what we need in terms of what the input is. This way, we
/// don't need to do any conversions, or indeed, any arithmetic at all, for
/// time zone lookups. We *could* store these as transitions, but then the
/// input datetime would need to be converted to a timestamp before searching
/// the transitions.
#[derive(Clone, Copy, Debug)]
pub enum TransitionKind {
/// This transition cannot possibly lead to an unambiguous offset because
/// its offset is equivalent to the offset of the previous transition.
///
/// Has an entry in `civil_starts`, but corresponding entry in `civil_ends`
/// is always zeroes (i.e., meaningless).
Unambiguous,
/// This occurs when this transition's offset is strictly greater than the
/// previous transition's offset. This effectively results in a "gap" of
/// time equal to the difference in the offsets between the two
/// transitions.
///
/// Has an entry in `civil_starts` for when the gap starts (inclusive) in
/// local time. Also has an entry in `civil_ends` for when the fold ends
/// (exclusive) in local time.
Gap,
/// This occurs when this transition's offset is strictly less than the
/// previous transition's offset. This results in a "fold" of time where
/// the two transitions have an overlap where it is ambiguous which one
/// applies given a wall clock time. In effect, a span of time equal to the
/// difference in the offsets is repeated.
///
/// Has an entry in `civil_starts` for when the fold starts (inclusive) in
/// local time. Also has an entry in `civil_ends` for when the fold ends
/// (exclusive) in local time.
Fold,
}
/// The representation for a timestamp used by the TZif implementation.
///
/// We don't use [`Timestamp`](crate::Timestamp) from the root of this crate
/// because TZif data doesn't require nanosecond resolution. Instead, this
/// representation uses only second resolution. This makes for a more compact
/// sequence of transitions, which means more data fits into cache and thus
/// faster binary search.
#[derive(Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct Timestamp {
second: i64,
}
impl Timestamp {
/// The minimum timestamp value.
pub const MIN: Timestamp = Timestamp::new(crate::Timestamp::MIN);
/// The maximum timestamp value.
pub const MAX: Timestamp = Timestamp::new(crate::Timestamp::MAX);
/// The zero value for a timestamp, which also corresponds to the Unix
/// epoch (`1970-01-01T00:00:00Z`).
pub const UNIX_EPOCH: Timestamp =
Timestamp::new(crate::Timestamp::UNIX_EPOCH);
/// Creates a new TZif timestamp from jiff-core's standard timestamp type.
///
/// Note that this completely ignores any subsecond component of the
/// provided timestamp.
pub const fn new(ts: crate::Timestamp) -> Timestamp {
Timestamp { second: ts.as_second() }
}
/// Creates a new `Timestamp` from a Unix timestamp integer value.
///
/// This returns an error if the value is not in the legal bounds for this
/// type.
pub const fn from_second(second: i64) -> Result<Timestamp, RangeError> {
match crate::Timestamp::from_second(second) {
Ok(ts) => Ok(Timestamp::new(ts)),
Err(err) => Err(err),
}
}
/// Returns the second value of this timestamp.
///
/// It is the number of seconds since the Unix epoch
/// (`1970-01-01T00:00:00Z`). Timestamps prior to the Unix epoch are
/// negative. Timestamps are the Unix epoch are `0`.
pub const fn as_second(self) -> i64 {
self.second
}
/// Adds the number of seconds to this timestamp, saturating at the minimum
/// or maximum legal value.
const fn saturating_add(self, seconds: i64) -> Timestamp {
let second = self.as_second() + seconds;
if second > Timestamp::MAX.as_second() {
Timestamp::MAX
} else if second < Timestamp::MIN.as_second() {
Timestamp::MIN
} else {
Timestamp { second }
}
}
/// Converts this timestamp to a civil datetime for the offset given.
pub const fn to_datetime(self, offset: Offset) -> DateTime {
DateTime::new(self.to_standard_timestamp().to_datetime(offset))
}
/// Converts this timestamp back to the "standard" timestamp.
///
/// Note that the timestamp returned here always has its nanosecond
/// component set to `0`.
pub const fn to_standard_timestamp(self) -> crate::Timestamp {
// OK because we don't provide a way to construct, mutate or
// change a `Timestamp` that drifts from the valid values of a
// `crate::Timestamp` (for its second component).
unwrapr!(
crate::Timestamp::from_second(self.as_second()),
"always in bounds"
)
}
}
impl core::fmt::Debug for Timestamp {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
core::fmt::Debug::fmt(&self.to_standard_timestamp(), f)
}
}
/// The representation for a civil datetime used by the TZif implementation.
///
/// We don't use [`civil::DateTime`] here because we specifically
/// do not need to represent fractional seconds. This lets us easily represent
/// what we need in 8 bytes.
///
/// Moreover, we pack the fields into a single `i64` to make comparisons
/// extremely cheap. This is especially useful since we do a binary search on
/// civil datetimes when determining the instant that corresponds to a civil
/// datetime.
#[derive(Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct DateTime {
bits: i64,
}
impl DateTime {
/// The minimum civil datetime value.
pub const MIN: DateTime = DateTime::new(civil::DateTime::from_parts(
civil::Date::MIN,
civil::Time::MIN,
));
/// The maximum civil datetime value.
pub const MAX: DateTime = DateTime::new(civil::DateTime::from_parts(
civil::Date::MAX,
civil::Time::MAX,
));
/// Creates a new TZif civil datetime from jiff-core's standard civil
/// datetime type.
///
/// Note that this completely ignores any fractional second component on
/// the provided datetime.
pub const fn new(dt: civil::DateTime) -> DateTime {
let (d, t) = (dt.date(), dt.time());
let mut bits = 0;
bits |= (d.year() as u64) << 48;
bits |= (d.month() as u64) << 40;
bits |= (d.day() as u64) << 32;
bits |= (t.hour() as u64) << 24;
bits |= (t.minute() as u64) << 16;
bits |= (t.second() as u64) << 8;
// The least significant 8 bits remain 0.
DateTime { bits: bits as i64 }
}
/// Returns the year component of this civil datetime.
pub const fn year(self) -> i16 {
(self.bits as u64 >> 48) as u16 as i16
}
/// Returns the month component of this civil datetime.
pub const fn month(self) -> i8 {
(self.bits as u64 >> 40) as u8 as i8
}
/// Returns the day component of this civil datetime.
pub const fn day(self) -> i8 {
(self.bits as u64 >> 32) as u8 as i8
}
/// Returns the hour component of this civil datetime.
pub const fn hour(self) -> i8 {
(self.bits as u64 >> 24) as u8 as i8
}
/// Returns the minute component of this civil datetime.
pub const fn minute(self) -> i8 {
(self.bits as u64 >> 16) as u8 as i8
}
/// Returns the second component of this civil datetime.
pub const fn second(self) -> i8 {
(self.bits as u64 >> 8) as u8 as i8
}
}
/// Creates a new bit packed datetime from jiff-core's standard datetime type.
///
/// Note that this completely ignores any fractional second component on the
/// provided datetime.
impl From<civil::DateTime> for DateTime {
fn from(dt: civil::DateTime) -> DateTime {
DateTime::new(dt)
}
}
impl core::fmt::Debug for DateTime {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
if !f.alternate() {
f.debug_struct("DateTime").field("bits", &self.bits).finish()
} else {
f.debug_tuple("DateTime")
.field(&format_args!(
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
self.year(),
self.month(),
self.day(),
self.hour(),
self.minute(),
self.second(),
))
.finish()
}
}
}
/// Returns true if the data might be in TZif format.
///
/// It is possible that this returns true even if the given data is not in TZif
/// format. However, it is impossible for this to return false when the given
/// data is TZif. That is, a false positive is allowed but a false negative is
/// not.
pub fn is_possibly_tzif(data: &[u8]) -> bool {
data.starts_with(b"TZif")
}
// If you're looking for tests, they can be found in Jiff's `tz::timezone`
// and `tz::tzif` modules.
File diff suppressed because it is too large Load Diff
+348
View File
@@ -0,0 +1,348 @@
use crate::{
civil::DateTime,
tz::{
posix, Abbreviation, AmbiguousOffset, AmbiguousTimestamp, Offset,
OffsetInfo, Transition,
},
Timestamp,
};
use super::{
DateTime as TzifDateTime, LocalTimeType, TimeZone,
Timestamp as TzifTimestamp, TransitionInfo as TzifTransitionInfo,
TransitionKind,
};
impl TimeZone {
/// Returns the appropriate time zone offset to use for the given
/// timestamp.
pub fn to_offset(&self, timestamp: Timestamp) -> Offset {
match self.to_local_time_type(timestamp) {
Ok(typ) => typ.offset,
Err(tz) => tz.to_offset(timestamp),
}
}
/// Returns the appropriate time zone offset to use for the given
/// timestamp.
///
/// This also includes whether the offset returned should be considered to
/// be DST or not, along with the time zone abbreviation (e.g., EST for
/// standard time in New York, and EDT for DST in New York).
pub fn to_offset_info(&self, timestamp: Timestamp) -> OffsetInfo {
let typ = match self.to_local_time_type(timestamp) {
Ok(typ) => typ,
Err(tz) => return tz.to_offset_info(timestamp),
};
// This clone will generally just be a memcpy. It only does a heap
// alloc when the designation is unusually long. (Which should be never
// for standard tzdata.)
let abbreviation = self.designation(typ).clone();
OffsetInfo { offset: typ.offset, abbreviation, dst: typ.dst }
}
/// Returns the local time type for the timestamp given.
///
/// If one could not be found, then this implies that the caller should
/// use the POSIX time zone returned in the error variant.
fn to_local_time_type(
&self,
timestamp: Timestamp,
) -> Result<&LocalTimeType, &posix::TimeZone> {
let timestamp = TzifTimestamp::new(timestamp);
// This is guaranteed because we always push at least one transition.
// This isn't guaranteed by TZif since it might have 0 transitions,
// but we always add a "dummy" first transition with our minimum
// `Timestamp` value. TZif doesn't do this because there is no
// universal minimum timestamp. (`i64::MIN` is a candidate, but that's
// likely to cause overflow in readers that don't do error checking.)
//
// The result of the dummy transition is that the code below is simpler
// with fewer special cases.
let timestamps = self.timestamps();
let last = *timestamps.last().expect("non-empty transitions");
let index = if timestamp > last {
timestamps.len() - 1
} else {
let search = self.timestamps().binary_search(&timestamp);
match search {
// Since the first transition is always Timestamp::MIN, it's
// impossible for any timestamp to sort before it.
Err(0) => {
unreachable!("impossible to come before Timestamp::MIN")
}
Ok(i) => i,
// i points to the position immediately after the matching
// timestamp. And since we know that i>0 because of the i==0
// check above, we can safely subtract 1.
Err(i) => i.checked_sub(1).expect("i is non-zero"),
}
};
// Our index is always in bounds. The only way it couldn't be is if
// binary search returns an Err(len) for a time greater than the
// maximum transition. But we account for that above by converting
// Err(len) to Err(len-1).
debug_assert!(index < timestamps.len());
// RFC 8536 says: "Local time for timestamps on or after the last
// transition is specified by the TZ string in the footer (Section 3.3)
// if present and nonempty; otherwise, it is unspecified."
//
// Subtracting 1 is OK because we know self.transitions is not empty.
let index = if index < timestamps.len() - 1 {
// This is the typical case in "fat" TZif files: we found a
// matching transition.
index
} else {
match self.posix_tz() {
// This is the typical case in "slim" TZif files, where the
// last transition is, as I understand it, the transition at
// which a consistent rule started that a POSIX TZ string can
// fully describe. For example, (as of 2024-03-27) the last
// transition in the "fat" America/New_York TZif file is
// in 2037, where as in the "slim" version it is 2007.
//
// This is likely why some things break with the "slim"
// version: they don't support POSIX TZ strings (or don't
// support them correctly).
Some(tz) => return Err(tz),
// This case is technically unspecified, but I think the
// typical thing to do is to just use the last transition.
// I'm not 100% sure on this one.
None => index,
}
};
Ok(self.local_time_type(index))
}
/// Returns a possibly ambiguous timestamp for the given civil datetime.
///
/// The given datetime should correspond to the "wall" clock time of what
/// humans use to tell time for this time zone.
///
/// Note that "ambiguous timestamp" is represented by the possible
/// selection of offsets that could be applied to the given datetime. In
/// general, it is only ambiguous around transitions to-and-from DST. The
/// ambiguity can arise as a "fold" (when a particular wall clock time is
/// repeated) or as a "gap" (when a particular wall clock time is skipped
/// entirely).
pub fn to_ambiguous_timestamp(&self, dt: DateTime) -> AmbiguousTimestamp {
// This implementation very nearly mirrors `to_local_time_type`
// above in the beginning: we do a binary search to find transition
// applicable for the given datetime. Except, we do it on wall clock
// times instead of timestamps. And in particular, each transition
// begins with a possibly ambiguous range of wall clock times
// corresponding to either a "gap" or "fold" in time.
let dtt = TzifDateTime::new(dt);
let (starts, ends) = (self.civil_starts(), self.civil_ends());
assert!(!starts.is_empty(), "transitions is non-empty");
let this_index = match starts.binary_search(&dtt) {
Err(0) => unreachable!("impossible to come before DateTime::MIN"),
Ok(i) => i,
Err(i) => i.checked_sub(1).expect("i is non-zero"),
};
debug_assert!(this_index < starts.len());
let this_offset = self.local_time_type(this_index).offset;
// This is a little tricky, but we need to check for ambiguous civil
// datetimes before possibly using the POSIX TZ string. Namely, a
// datetime could be ambiguous with respect to the last transition,
// and we should handle that according to the gap/fold determined for
// that transition. We cover this case in tests in tz/mod.rs for the
// Pacific/Honolulu time zone, whose last transition begins with a gap.
match self.transition_kind(this_index) {
TransitionKind::Gap if dtt < ends[this_index] => {
// A gap/fold can only appear when there exists a previous
// transition.
let prev_index = this_index.checked_sub(1).unwrap();
let prev_offset = self.local_time_type(prev_index).offset;
return AmbiguousOffset::Gap {
before: prev_offset,
after: this_offset,
}
.into_ambiguous_timestamp(dt);
}
TransitionKind::Fold if dtt < ends[this_index] => {
// A gap/fold can only appear when there exists a previous
// transition.
let prev_index = this_index.checked_sub(1).unwrap();
let prev_offset = self.local_time_type(prev_index).offset;
return AmbiguousOffset::Fold {
before: prev_offset,
after: this_offset,
}
.into_ambiguous_timestamp(dt);
}
_ => {}
}
// The datetime given is not ambiguous with respect to any of the
// transitions in the TZif data. But, if we matched at or after the
// last transition, then we need to use the POSIX TZ string (which
// could still return an ambiguous offset).
if this_index == starts.len() - 1 {
if let Some(tz) = self.posix_tz() {
return tz.to_ambiguous_timestamp(dt);
}
// This case is unspecified according to RFC 8536. It means that
// the given datetime exceeds all transitions *and* there is no
// POSIX TZ string. So this can happen in V1 files for example.
// But those should hopefully be essentially non-existent nowadays
// (2024-03). In any case, we just fall through to using the last
// transition, which does seem likely to be wrong ~half the time
// in time zones with DST. But there really isn't much else we can
// do I think.
}
AmbiguousOffset::Unambiguous { offset: this_offset }
.into_ambiguous_timestamp(dt)
}
/// Returns the timestamp of the most recent time zone transition prior
/// to the timestamp given. If one doesn't exist, `None` is returned.
pub fn previous_transition<'t>(
&'t self,
ts: Timestamp,
) -> Option<Transition> {
assert!(!self.timestamps().is_empty(), "transitions is non-empty");
let mut timestamp = TzifTimestamp::new(ts);
if ts.subsec_nanosecond() != 0 {
timestamp = timestamp.saturating_add(1);
}
let search = self.timestamps().binary_search(&timestamp);
let index = match search {
Ok(i) | Err(i) => i.checked_sub(1)?,
};
let index = if index == 0 {
// The first transition is a dummy that we insert, so if we land on
// it here, treat it as if it doesn't exist.
return None;
} else if index == self.timestamps().len() - 1 {
if let Some(ref posix_tz) = self.posix_tz() {
// Since the POSIX TZ must be consistent with the last
// transition, it must be the case that tzif_last <=
// posix_prev_trans in all cases. So the transition according
// to the POSIX TZ is always correct here.
//
// What if this returns `None` though? I'm not sure in which
// cases that could matter, and I think it might be a violation
// of the TZif format if it does.
//
// It can return `None`! In the case of a time zone that
// has eliminated DST, it might have historical time zone
// transitions but a POSIX time zone without DST. (For example,
// `America/Sao_Paulo`.) And thus, this would return `None`.
// So if it does, we pretend as if the POSIX time zone doesn't
// exist.
if let Some(trans) = posix_tz.previous_transition(ts) {
return Some(trans);
}
}
index
} else {
index
};
let timestamp = self.timestamps()[index];
let typ = self.local_time_type(index);
let info = OffsetInfo {
offset: typ.offset,
abbreviation: self.designation(typ).clone(),
dst: typ.dst,
};
Some(Transition { timestamp: timestamp.to_standard_timestamp(), info })
}
/// Returns the timestamp of the soonest time zone transition after the
/// timestamp given. If one doesn't exist, `None` is returned.
pub fn next_transition<'t>(&'t self, ts: Timestamp) -> Option<Transition> {
assert!(!self.timestamps().is_empty(), "transitions is non-empty");
let timestamp = TzifTimestamp::new(ts);
let search = self.timestamps().binary_search(&timestamp);
let index = match search {
Ok(i) => i.checked_add(1)?,
Err(i) => i,
};
let index = if index == 0 {
// The first transition is a dummy that we insert, so if we land on
// it here, treat it as if it doesn't exist.
return None;
} else if index >= self.timestamps().len() {
if let Some(posix_tz) = self.posix_tz() {
// Since the POSIX TZ must be consistent with the last
// transition, it must be the case that next.timestamp <=
// posix_next_tans in all cases. So the transition according to
// the POSIX TZ is always correct here.
//
// What if this returns `None` though? I'm not sure in which
// cases that could matter, and I think it might be a violation
// of the TZif format if it does.
//
// In the "previous" case above, this could return `None` even
// when there are historical time zone transitions in the case
// of a time zone eliminating DST (e.g., `America/Sao_Paulo`).
// But unlike the previous case, if we get `None` here, then
// that is the real answer because there are no other known
// future time zone transitions.
//
// 2025-05-05: OK, this could return `None` and this is fine.
// It happens for time zones that had DST but then stopped
// it at some point in the past. The POSIX time zone has no
// DST and thus returns `None`. That's fine. But there was a
// problem: we were using the POSIX time zone even when there
// was a historical time zone transition after the timestamp
// given. That was fixed by changing the condition when we get
// here: it can only happen when the timestamp given comes at
// or after all historical time zone transitions.
return posix_tz.next_transition(ts);
}
self.timestamps().len() - 1
} else {
index
};
let timestamp = self.timestamps()[index];
let typ = self.local_time_type(index);
let info = OffsetInfo {
offset: typ.offset,
abbreviation: self.designation(typ).clone(),
dst: typ.dst,
};
Some(Transition { timestamp: timestamp.to_standard_timestamp(), info })
}
fn local_time_type(&self, transition_index: usize) -> &LocalTimeType {
// OK because we require that `type_index` always points to a valid
// local time type.
&self.types()[usize::from(self.infos()[transition_index].type_index)]
}
fn transition_kind(&self, transition_index: usize) -> TransitionKind {
self.infos()[transition_index].kind
}
fn types(&self) -> &[LocalTimeType] {
&self.types
}
fn timestamps(&self) -> &[TzifTimestamp] {
&self.transitions.timestamps
}
fn civil_starts(&self) -> &[TzifDateTime] {
&self.transitions.civil_starts
}
fn civil_ends(&self) -> &[TzifDateTime] {
&self.transitions.civil_ends
}
fn infos(&self) -> &[TzifTransitionInfo] {
&self.transitions.infos
}
fn designation(&self, typ: &LocalTimeType) -> &Abbreviation {
// OK because every local time type is assigned a valid designation
// index while parsing or constructing this time zone.
&self.designations[typ.designation()]
}
fn posix_tz(&self) -> Option<&posix::TimeZone> {
self.posix_tz.as_ref()
}
}
+46
View File
@@ -0,0 +1,46 @@
use self::table::{TABLE, TABLE16};
mod table;
/// Returns the "masked" CRC32 checksum of the slice using the Castagnoli
/// polynomial.
///
/// This "masked" checksum is the same one used by the Snappy frame format.
/// Masking is supposed to make the checksum robust with respect to data that
/// contains the checksum itself.
pub(crate) fn sum(buf: &[u8]) -> u32 {
let sum = slice16(0, buf);
(sum.wrapping_shr(15) | sum.wrapping_shl(17)).wrapping_add(0xA282EAD8)
}
/// Returns the CRC32 checksum of `buf` using the Castagnoli polynomial.
///
/// This computes the checksum by looking at 16 bytes from the given slice
/// per iteration.
fn slice16(prev: u32, mut buf: &[u8]) -> u32 {
let mut crc: u32 = !prev;
while buf.len() >= 16 {
crc ^= u32::from_le_bytes(buf[..4].try_into().unwrap());
crc = TABLE16[0][usize::from(buf[15])]
^ TABLE16[1][usize::from(buf[14])]
^ TABLE16[2][usize::from(buf[13])]
^ TABLE16[3][usize::from(buf[12])]
^ TABLE16[4][usize::from(buf[11])]
^ TABLE16[5][usize::from(buf[10])]
^ TABLE16[6][usize::from(buf[9])]
^ TABLE16[7][usize::from(buf[8])]
^ TABLE16[8][usize::from(buf[7])]
^ TABLE16[9][usize::from(buf[6])]
^ TABLE16[10][usize::from(buf[5])]
^ TABLE16[11][usize::from(buf[4])]
^ TABLE16[12][usize::from((crc >> 24) as u8)]
^ TABLE16[13][usize::from((crc >> 16) as u8)]
^ TABLE16[14][usize::from((crc >> 8) as u8)]
^ TABLE16[15][usize::from((crc) as u8)];
buf = &buf[16..];
}
for &b in buf {
crc = TABLE[usize::from((crc as u8) ^ b)] ^ (crc >> 8);
}
!crc
}
+796
View File
@@ -0,0 +1,796 @@
// auto-generated by: jiff-cli generate crc32
pub(super) const TABLE: [u32; 256] = [
0, 4067132163, 3778769143, 324072436, 3348797215, 904991772, 648144872,
3570033899, 2329499855, 2024987596, 1809983544, 2575936315, 1296289744,
3207089363, 2893594407, 1578318884, 274646895, 3795141740, 4049975192,
51262619, 3619967088, 632279923, 922689671, 3298075524, 2592579488,
1760304291, 2075979607, 2312596564, 1562183871, 2943781820, 3156637768,
1313733451, 549293790, 3537243613, 3246849577, 871202090, 3878099393,
357341890, 102525238, 4101499445, 2858735121, 1477399826, 1264559846,
3107202533, 1845379342, 2677391885, 2361733625, 2125378298, 820201905,
3263744690, 3520608582, 598981189, 4151959214, 85089709, 373468761,
3827903834, 3124367742, 1213305469, 1526817161, 2842354314, 2107672161,
2412447074, 2627466902, 1861252501, 1098587580, 3004210879, 2688576843,
1378610760, 2262928035, 1955203488, 1742404180, 2511436119, 3416409459,
969524848, 714683780, 3639785095, 205050476, 4266873199, 3976438427,
526918040, 1361435347, 2739821008, 2954799652, 1114974503, 2529119692,
1691668175, 2005155131, 2247081528, 3690758684, 697762079, 986182379,
3366744552, 476452099, 3993867776, 4250756596, 255256311, 1640403810,
2477592673, 2164122517, 1922457750, 2791048317, 1412925310, 1197962378,
3037525897, 3944729517, 427051182, 170179418, 4165941337, 746937522,
3740196785, 3451792453, 1070968646, 1905808397, 2213795598, 2426610938,
1657317369, 3053634322, 1147748369, 1463399397, 2773627110, 4215344322,
153784257, 444234805, 3893493558, 1021025245, 3467647198, 3722505002,
797665321, 2197175160, 1889384571, 1674398607, 2443626636, 1164749927,
3070701412, 2757221520, 1446797203, 137323447, 4198817972, 3910406976,
461344835, 3484808360, 1037989803, 781091935, 3705997148, 2460548119,
1623424788, 1939049696, 2180517859, 1429367560, 2807687179, 3020495871,
1180866812, 410100952, 3927582683, 4182430767, 186734380, 3756733383,
763408580, 1053836080, 3434856499, 2722870694, 1344288421, 1131464017,
2971354706, 1708204729, 2545590714, 2229949006, 1988219213, 680717673,
3673779818, 3383336350, 1002577565, 4010310262, 493091189, 238226049,
4233660802, 2987750089, 1082061258, 1395524158, 2705686845, 1972364758,
2279892693, 2494862625, 1725896226, 952904198, 3399985413, 3656866545,
731699698, 4283874585, 222117402, 510512622, 3959836397, 3280807620,
837199303, 582374963, 3504198960, 68661723, 4135334616, 3844915500,
390545967, 1230274059, 3141532936, 2825850620, 1510247935, 2395924756,
2091215383, 1878366691, 2644384480, 3553878443, 565732008, 854102364,
3229815391, 340358836, 3861050807, 4117890627, 119113024, 1493875044,
2875275879, 3090270611, 1247431312, 2660249211, 1828433272, 2141937292,
2378227087, 3811616794, 291187481, 34330861, 4032846830, 615137029,
3603020806, 3314634738, 939183345, 1776939221, 2609017814, 2295496738,
2058945313, 2926798794, 1545135305, 1330124605, 3173225534, 4084100981,
17165430, 307568514, 3762199681, 888469610, 3332340585, 3587147933,
665062302, 2042050490, 2346497209, 2559330125, 1793573966, 3190661285,
1279665062, 1595330642, 2910671697,
];
pub(super) const TABLE16: [[u32; 256]; 16] = [
[
0, 4067132163, 3778769143, 324072436, 3348797215, 904991772,
648144872, 3570033899, 2329499855, 2024987596, 1809983544, 2575936315,
1296289744, 3207089363, 2893594407, 1578318884, 274646895, 3795141740,
4049975192, 51262619, 3619967088, 632279923, 922689671, 3298075524,
2592579488, 1760304291, 2075979607, 2312596564, 1562183871,
2943781820, 3156637768, 1313733451, 549293790, 3537243613, 3246849577,
871202090, 3878099393, 357341890, 102525238, 4101499445, 2858735121,
1477399826, 1264559846, 3107202533, 1845379342, 2677391885,
2361733625, 2125378298, 820201905, 3263744690, 3520608582, 598981189,
4151959214, 85089709, 373468761, 3827903834, 3124367742, 1213305469,
1526817161, 2842354314, 2107672161, 2412447074, 2627466902,
1861252501, 1098587580, 3004210879, 2688576843, 1378610760,
2262928035, 1955203488, 1742404180, 2511436119, 3416409459, 969524848,
714683780, 3639785095, 205050476, 4266873199, 3976438427, 526918040,
1361435347, 2739821008, 2954799652, 1114974503, 2529119692,
1691668175, 2005155131, 2247081528, 3690758684, 697762079, 986182379,
3366744552, 476452099, 3993867776, 4250756596, 255256311, 1640403810,
2477592673, 2164122517, 1922457750, 2791048317, 1412925310,
1197962378, 3037525897, 3944729517, 427051182, 170179418, 4165941337,
746937522, 3740196785, 3451792453, 1070968646, 1905808397, 2213795598,
2426610938, 1657317369, 3053634322, 1147748369, 1463399397,
2773627110, 4215344322, 153784257, 444234805, 3893493558, 1021025245,
3467647198, 3722505002, 797665321, 2197175160, 1889384571, 1674398607,
2443626636, 1164749927, 3070701412, 2757221520, 1446797203, 137323447,
4198817972, 3910406976, 461344835, 3484808360, 1037989803, 781091935,
3705997148, 2460548119, 1623424788, 1939049696, 2180517859,
1429367560, 2807687179, 3020495871, 1180866812, 410100952, 3927582683,
4182430767, 186734380, 3756733383, 763408580, 1053836080, 3434856499,
2722870694, 1344288421, 1131464017, 2971354706, 1708204729,
2545590714, 2229949006, 1988219213, 680717673, 3673779818, 3383336350,
1002577565, 4010310262, 493091189, 238226049, 4233660802, 2987750089,
1082061258, 1395524158, 2705686845, 1972364758, 2279892693,
2494862625, 1725896226, 952904198, 3399985413, 3656866545, 731699698,
4283874585, 222117402, 510512622, 3959836397, 3280807620, 837199303,
582374963, 3504198960, 68661723, 4135334616, 3844915500, 390545967,
1230274059, 3141532936, 2825850620, 1510247935, 2395924756,
2091215383, 1878366691, 2644384480, 3553878443, 565732008, 854102364,
3229815391, 340358836, 3861050807, 4117890627, 119113024, 1493875044,
2875275879, 3090270611, 1247431312, 2660249211, 1828433272,
2141937292, 2378227087, 3811616794, 291187481, 34330861, 4032846830,
615137029, 3603020806, 3314634738, 939183345, 1776939221, 2609017814,
2295496738, 2058945313, 2926798794, 1545135305, 1330124605,
3173225534, 4084100981, 17165430, 307568514, 3762199681, 888469610,
3332340585, 3587147933, 665062302, 2042050490, 2346497209, 2559330125,
1793573966, 3190661285, 1279665062, 1595330642, 2910671697,
],
[
0, 329422967, 658845934, 887597209, 1317691868, 1562966443,
1775194418, 2054015301, 2635383736, 2394315727, 3125932886,
2851302177, 3550388836, 3225172499, 4108030602, 3883469565,
1069937025, 744974838, 411091311, 186800408, 1901039709, 1659701290,
1443537075, 1168652484, 2731618873, 2977147470, 2241069783,
2520160928, 3965408229, 4294560658, 3407766283, 3636263804,
2139874050, 1814657909, 1489949676, 1265388443, 822182622, 581114537,
373600816, 98970183, 3802079418, 4047354061, 3319402580, 3598223395,
2887074150, 3216496913, 2337304968, 2566056447, 1078858371,
1408010996, 1728782957, 1957280282, 247755615, 493284136, 696337329,
975428550, 3713716539, 3472378188, 4196393429, 3921508770, 2479927527,
2154965136, 3029696521, 2805405822, 4279748100, 3971309171,
3629315818, 3421531805, 2979899352, 2722054063, 2530776886,
2239369025, 1644365244, 1906417099, 1162229074, 1457827109, 747201632,
1059847191, 197940366, 409914617, 3235002245, 3547377650, 3885434731,
4097154844, 2388153945, 2650459694, 2837276343, 3133144768,
1573319741, 1315204170, 2055455955, 1763794084, 323786209, 15601046,
873047311, 665533816, 2157716742, 2470362481, 2816021992, 3027996063,
3457565914, 3719617709, 3914560564, 4210158659, 495511230, 237665993,
986568272, 695160359, 1392674658, 1084235541, 1950857100, 1743073275,
3210335367, 2902150384, 2552030313, 2344516638, 4057183579,
3799067948, 3600188853, 3308527042, 575477567, 837783368, 84420561,
380288934, 1825011427, 2137386644, 1266828813, 1478549114, 4223924985,
3898696334, 3699821079, 3475264096, 3041499941, 2800419666,
2450303947, 2175677372, 1725380929, 1970643254, 1100089775,
1378914776, 677206173, 1006616810, 253257843, 482013188, 3288730488,
3617886991, 3812834198, 4041319393, 2324458148, 2569990867,
2915654218, 3194733117, 1494403264, 1253068983, 2119694382,
1844797529, 395880732, 70922603, 819829234, 595526021, 2219317755,
2548728204, 2735548693, 2964304226, 3401742375, 3647004752,
3985066185, 4263891134, 425515587, 184435252, 1041885869, 767259354,
1473690527, 1148462056, 1888717681, 1664160518, 3146639482,
2821681165, 2630408340, 2406105315, 4110911910, 3869577681,
3527588168, 3252691263, 647572418, 893105077, 31202092, 310281051,
1746094622, 2075251305, 1331067632, 1559552647, 81018109, 393651338,
596708371, 808686692, 1247698209, 1509737814, 1830514127, 2126116280,
2579562309, 2321704754, 3196440491, 2905036764, 3611991705,
3303540462, 4027559543, 3819779584, 991022460, 682841355, 475331986,
267806181, 1973136544, 1715025111, 1390320718, 1098646585, 2785349316,
3047659187, 2168471082, 2464327261, 3901714200, 4214093679,
3486146550, 3697854337, 2069880831, 1761429384, 1545269009,
1337489254, 903200291, 645342804, 311463629, 20059834, 3863682119,
4125721648, 3238931625, 3534533854, 2831252891, 3143886316,
2407812469, 2619790594, 1150955134, 1463334409, 1675566736,
1887274727, 168841122, 431151061, 760577868, 1056433979, 3650022854,
3391911345, 4274773288, 3983099231, 2533657626, 2225476717,
2957098228, 2749572227,
],
[
0, 2772537982, 1332695565, 3928932467, 2665391130, 1000289892,
3518101015, 1961911401, 944848581, 2635115707, 2000579784, 3531603638,
2794429151, 63834273, 3923822802, 1285642924, 1889697162, 3588485108,
1070411655, 2592914937, 4001159568, 1262308334, 2702412701, 72489443,
1223902031, 3987919153, 127668546, 2732426044, 3593332565, 1936487723,
2571285848, 1006839590, 3779394324, 1141205354, 2922096921, 191511399,
2140823310, 3671838064, 821366019, 2511642493, 3642082769, 2085902255,
2524616668, 859506082, 1204511179, 3800757173, 144978886, 2917507512,
2447804062, 883365088, 3733574803, 2076722925, 255337092, 2860101882,
1079472265, 3843482359, 2847389787, 217459237, 3872975446, 1134131240,
929635393, 2452131391, 2013679180, 3712474162, 3345318105, 1646531239,
2282410708, 759906474, 1505436867, 4244289213, 383022798, 3012945072,
4281646620, 1517628514, 2958814225, 354057839, 1642732038, 3299575928,
780486667, 2344934005, 3083337043, 310800173, 4171804510, 1575566624,
689527113, 2354629431, 1719012164, 3275200826, 2409022358, 718754280,
3237581211, 1706558437, 289957772, 3020551666, 1579627905, 4217808895,
639728589, 2204166579, 1766730176, 3423583166, 3103776727, 499010985,
4153445850, 1389436836, 510674184, 3140605814, 1360992005, 4099835259,
2158944530, 636449644, 3485578015, 1786782049, 1451427399, 4089615417,
434918474, 3165505076, 3361579613, 1830563875, 2268262480, 577987118,
1859270786, 3415452412, 566061711, 2231171313, 4027358360, 1431113446,
3210989205, 438459627, 2334619459, 778495293, 3293062478, 1628026672,
368694105, 2964865319, 1519812948, 4292285226, 3010873734, 372759544,
4229503883, 1498974709, 766045596, 2297004002, 1657257873, 3347459567,
4219800265, 1589942455, 3035257028, 296471226, 1700507347, 3222944941,
708115678, 2406837920, 3285464076, 1721083506, 2361091585, 704312447,
1560973334, 4165665384, 308658715, 3072610405, 1784908887, 3475119657,
621600346, 2152549412, 4106037325, 1375517235, 3151133248, 513009726,
1379054226, 4151517420, 492691615, 3088872161, 3438024328, 1772979446,
2206418053, 650303227, 448917981, 3212862371, 1437508560, 4042207662,
2216646087, 559859641, 3413116874, 1848743348, 579915544, 2278645094,
1845468437, 3367898987, 3159255810, 420477308, 4079040783, 1449175921,
1279457178, 3909314020, 53323159, 2792110057, 3533460352, 2011021822,
2649948557, 951227379, 1947453791, 3511835425, 998021970, 2654800172,
3939331397, 1334640443, 2778873672, 14921014, 1021348368, 2577471598,
1938806813, 3603843683, 2721984010, 125811828, 3981540359, 1209069177,
78755029, 2716870315, 1272899288, 4003427494, 2590970063, 1060012721,
3573564098, 1883361468, 2902854798, 138911472, 3798556291, 1193856253,
869836948, 2526624490, 2092432025, 3656804583, 2505519691, 806789173,
3661127750, 2138698296, 193566289, 2932343855, 1155974236, 3785840162,
3718541572, 2028331898, 2462786313, 931836279, 1132123422, 3862644576,
202737427, 2840860013, 3858059201, 1085595071, 2862226892, 266047410,
2066475995, 3731519909, 876919254, 2433035176,
],
[
0, 3712330424, 3211207553, 1646430521, 2065838579, 2791807819,
3292861042, 419477706, 4131677158, 721537374, 1227047015, 2489772767,
2372293141, 1344534701, 838955412, 4014267180, 3915690301, 874584965,
1443074748, 2336634884, 2454094030, 1325607542, 757179215, 4033087991,
522244827, 3261429859, 2689069402, 2097306594, 1677910824, 3108456848,
3680878761, 102787601, 3609531531, 174112307, 1749169930, 3037175218,
2886149496, 1900187584, 325060345, 3458575425, 560035693, 4230274517,
2651215084, 1128529492, 1514358430, 2265377830, 3844367647, 945934247,
1044489654, 3808694030, 2166785591, 1550003343, 1164153925,
2552643325, 4194613188, 658578812, 3355821648, 356543720, 2002970065,
2854702953, 3005740963, 1851940123, 205575202, 3506798234, 2879807463,
1994650975, 348224614, 3380926174, 3498339860, 230802604, 1877167509,
2997282605, 1575107585, 2158466745, 3800375168, 1069593912, 650120690,
4219840330, 2577870451, 1155695819, 1120071386, 2676442210,
4255501659, 551577571, 971038505, 3836048785, 2257058984, 1539462672,
3028716860, 1774397316, 199339709, 3601073157, 3483679951, 316741239,
1891868494, 2911254006, 2088979308, 2714165716, 3286526189, 513917525,
128023199, 3672428583, 3100006686, 1703146406, 2328307850, 1468170802,
899681035, 3907363251, 4058323321, 748729281, 1317157624, 2479329344,
2515008081, 1218597097, 713087440, 4156912488, 4005940130, 864051482,
1369630755, 2363966107, 1671666103, 3202757391, 3703880246, 25235598,
411150404, 3317957372, 2816904133, 2057511293, 1386268991, 2414175111,
3989301950, 813842438, 696449228, 4106703476, 2531646285, 1268806133,
2766449369, 2040594529, 461605208, 3334874080, 3754335018, 42152338,
1621211307, 3185840659, 3150215170, 1719784122, 77814659, 3655790907,
3236317681, 497279817, 2139187824, 2730803400, 1300241380, 2428875100,
4075239525, 799183581, 916597271, 3957817519, 2311391638, 1417716526,
2240142772, 1489008396, 987954741, 3886503053, 4272417863, 602031871,
1103155142, 2625987966, 1942077010, 2927891690, 3433471443, 300103531,
149131169, 3584435481, 3078925344, 1791035032, 1826712713, 2980365873,
3548794632, 247719344, 398679418, 3397842882, 2829352699, 1977734211,
2594508655, 1205904855, 633482478, 4169631318, 3783736988, 1019384868,
1591745821, 2208675749, 4177958616, 608386144, 1180808537, 2602835937,
2183440171, 1600195987, 1027835050, 3758501394, 256046398, 3523698566,
2955269823, 1835039751, 1952498893, 2837802613, 3406292812, 373444084,
274868197, 3441921373, 2936341604, 1916841692, 1799362070, 3053829294,
3559339415, 157458223, 3861267459, 996404923, 1497458562, 2214907194,
2634315248, 1078058824, 576935537, 4280745161, 774079059, 4083558635,
2437194194, 1275136874, 1426174880, 2286164248, 3932590113, 925055641,
3630686645, 86133517, 1728102964, 3125110924, 2739261510, 2113960702,
472052679, 3244775807, 3343332206, 436378070, 2015367407, 2774907479,
3160736413, 1629530149, 50471196, 3729230756, 822300808, 3964074544,
2388947721, 1394727345, 1243701627, 2539965379, 4115022586, 671344706,
],
[
0, 940666796, 1881333592, 1211347188, 3762667184, 3629437212,
2422694376, 2826309188, 3311864721, 4252394557, 3041252553,
2371140453, 623031585, 489937549, 1426090617, 1829832149, 2401395155,
3073576575, 4278238859, 3339833639, 1869078371, 1467396303, 524615739,
659845015, 1246063170, 1918109166, 979875098, 41343670, 2852181234,
2450635614, 3659664298, 3795018758, 464041303, 599382779, 1804233231,
1402668451, 4226616295, 3288097867, 2345653439, 3017718547,
3738156742, 3873362282, 2934792606, 2533101106, 1049231478, 110850010,
1319690030, 1991880834, 2492126340, 2895887144, 3836218332,
3703137392, 1959750196, 1289618840, 82687340, 1023204032, 1374543637,
1778167993, 567214157, 434008033, 2980602277, 2310606345, 3247095549,
4187738449, 928082606, 255853826, 1198765558, 2137184858, 3608466462,
4010130354, 2805336902, 2670159082, 4063650111, 3391557267,
2182397543, 3120943563, 309583759, 711110691, 1649475799, 1514172283,
3094547325, 2153931985, 3360805925, 4030774153, 1479980493,
1613224545, 672426645, 268764473, 2098462956, 1157984064, 221700020,
891793432, 2639380060, 2772488688, 3983761668, 3579973288, 754573305,
350793813, 1557856417, 1690988301, 3434897737, 4104982245, 3164519953,
2224017853, 3919500392, 3515857860, 2579237680, 2712495260, 165374680,
835323252, 2046408064, 1105779244, 2749087274, 2613760390, 3556335986,
3957853918, 1134428314, 2072997686, 868016066, 195932270, 1723643323,
1588451863, 379480803, 781124943, 2264468235, 3202901159, 4141601875,
3469392895, 1856165212, 1454619376, 511707652, 647062952, 2397531116,
3069576256, 4274369716, 3335838488, 2881871565, 2480189793,
3689349525, 3824578105, 1266704509, 1938886609, 1000521509, 62115977,
3783308431, 3650214691, 2443340759, 2847081595, 29690431, 970220947,
1911018855, 1240906443, 619167518, 485937330, 1422221382, 1825837034,
3298951598, 4239617538, 3028344566, 2358358362, 1963614219,
1293619111, 86556499, 1027199231, 2505039547, 2908664087, 3849126371,
3715919439, 2959960986, 2289828918, 3226449090, 4166966126,
1344853290, 1748613766, 537528946, 404448734, 4196925912, 3258543732,
2315968128, 2988159276, 443400040, 578605252, 1783586864, 1381896092,
1062144585, 123626981, 1332598033, 2004662973, 3742020857, 3877362517,
2938661793, 2537096205, 1509146610, 1642254430, 701587626, 297799430,
3115712834, 2175233774, 3381976602, 4052070838, 2626991203,
2760235983, 3971377979, 3567715479, 2094074579, 1153459583, 217306507,
887274023, 3604078113, 4005605773, 2800943481, 2665639637, 915693713,
243601213, 1186381769, 2124927077, 330749360, 732412444, 1670646504,
1535468868, 4092816128, 3420587180, 2211558488, 3149978612,
1113262757, 2051695881, 846845437, 174635601, 2719921173, 2584730553,
3527174989, 3928818913, 2268856628, 3207425688, 4145995372,
3473912256, 1736032132, 1600704552, 391864540, 793382768, 3447286646,
4117234906, 3176903726, 2236275586, 758961606, 355318378, 1562249886,
1695507762, 136208615, 806293323, 2017247167, 1076744211, 3898334807,
3494556155, 2558066959, 2691198627,
],
[
0, 4012927769, 3683426499, 884788186, 3002414967, 1573215342,
1769576372, 2252995757, 1611012127, 2402710278, 3146430684,
1421530053, 3539152744, 1036207217, 159354795, 3863995570, 3222024254,
792484647, 461410557, 4105239524, 1928922953, 2647223376, 2843060106,
1178979475, 2685020193, 1329218360, 2072414434, 2495013883, 318709590,
4258231375, 3379806101, 641979532, 2247366285, 1791262100, 1584969294,
2974342487, 922821114, 3627109091, 3968696633, 62777888, 3857845906,
180512139, 1048489553, 3511600456, 1460091365, 3090633468, 2357958950,
1673261631, 1173890739, 2865253802, 2658436720, 1900342633,
4144828868, 406682333, 746696967, 3283212830, 637419180, 3402519989,
4268924527, 289600886, 2534083035, 2017157826, 1283959064, 2746728961,
235166699, 3778294002, 3582524200, 985174065, 3169938588, 1405159301,
1736297567, 2286790470, 1845642228, 2167548141, 3046040375,
1522436142, 3707204739, 868687770, 125555776, 3897278297, 3456658389,
557318348, 361024278, 4206141455, 2096979106, 2479699899, 2809265249,
1212258168, 2920182730, 1094588627, 1971507977, 2595403792, 486229181,
4090179492, 3346523262, 675778407, 2347781478, 1690314367, 1350364581,
3209463484, 956660241, 3593801992, 3800685266, 230273483, 3958789497,
80100960, 813364666, 3746209443, 1493393934, 3056797975, 2190459597,
1841277396, 1274838360, 2764838465, 2423315867, 2134947458,
4178135599, 372842806, 579201772, 3451224565, 737830215, 3301576286,
4034315652, 524725917, 2567918128, 1983854889, 1115943667, 2914228714,
470333398, 4080590031, 3347322645, 682916876, 2935849121, 1104014264,
1970348130, 2587970427, 2081337289, 2470364368, 2810318602,
1219650579, 3472595134, 567014311, 360134781, 4198978404, 3691284456,
859106545, 126363435, 3904392242, 1861333151, 2176965510, 3044872284,
1515027269, 3154288631, 1395848430, 1737375540, 2294174765, 251111552,
3787965337, 3581610051, 978019162, 2583470427, 1993132610, 1114636696,
2906679937, 722048556, 3292134709, 4035262191, 531979766, 4193958212,
382390877, 578165127, 3443946142, 1259310643, 2755650858, 2424516336,
2142455273, 1508938085, 3066100348, 2189177254, 1833720511,
3943015954, 70634763, 814286545, 3753471432, 972458362, 3603358307,
3799656889, 222970528, 2332278285, 1681118484, 1351556814, 3216995799,
302836797, 4248602404, 3380628734, 649078759, 2700729162, 1338617939,
2071296905, 2487554192, 1913320482, 2637864763, 2844153057,
1186349048, 3237987157, 802138188, 460546966, 4098033807, 3523271683,
1026602778, 160201920, 3871086553, 1626729332, 2412085357, 3145288631,
1414078638, 2986787868, 1563864837, 1770677471, 2260340678, 15987563,
4022573170, 3682554792, 877607089, 2549676720, 2026410409, 1282693747,
2739155306, 621661639, 3393038046, 4269894916, 296814109, 4160676527,
416188854, 745685612, 3275893109, 1158403544, 2856042177, 2659677467,
1907826178, 1475660430, 3099894167, 2356701773, 1665663316,
3842113017, 171022048, 1049451834, 3518838307, 938660497, 3636640136,
3967709778, 55449931, 2231887334, 1782025983, 1586185509, 2981834300,
],
[
0, 1745038536, 3490077072, 3087365464, 2782971345, 3454265625,
1978047553, 501592201, 1311636819, 640602523, 2653660355, 4129851403,
3956095106, 2211320906, 1003184402, 1405636058, 2623273638,
4099462766, 1281205046, 610177022, 968572791, 1371018175, 3921503975,
2176731695, 3530950645, 3128240957, 40918629, 1785950893, 2006368804,
529919724, 2811272116, 3482564476, 1029407677, 1431875445, 3982350893,
2237593317, 2562410092, 4038617764, 1220354044, 549335860, 1937145582,
460673574, 2742036350, 3413314486, 3600202559, 3197474807, 110158511,
1855180391, 2701162779, 3372438995, 1896226955, 419761219, 81837258,
1826852866, 3571901786, 3169175954, 4012737608, 2267981952,
1059839448, 1462300944, 1254965657, 583953745, 2597001225, 4073206977,
2058815354, 313797554, 2863750890, 3266474530, 3747070635, 3075796579,
257006395, 1733474291, 882571817, 1553585889, 3835470777, 2359267185,
2440708088, 4185461552, 1098671720, 696208032, 3874291164, 2398081300,
921347148, 1592363140, 1124874253, 722408645, 2466931101, 4211690837,
2831220879, 3233950791, 2026330399, 281310679, 220317022, 1696786838,
3710360782, 3039080454, 1206682823, 804235279, 2548751703, 4293521823,
3792453910, 2316266974, 839522438, 1510552654, 163674516, 1640125788,
3653705732, 2982415564, 2887892037, 3290599565, 2082989525, 337955101,
3686235745, 3014939305, 196159473, 1672612665, 2119678896, 374642552,
2924601888, 3327315688, 2509931314, 4254707706, 1167907490, 765458026,
813319907, 1484352043, 3766230899, 2290037691, 4117630708, 2641175100,
627595108, 1298889644, 1351535397, 948824045, 2156434101, 3901472381,
3141792679, 3544244079, 1799727671, 54953727, 514012790, 1990204094,
3466948582, 2795914030, 1765143634, 20371610, 3107171778, 3509616906,
3436523907, 2765495627, 483616787, 1959806171, 655886593, 1327179209,
4145959057, 2669509721, 2197343440, 3942375448, 1392416064, 989706632,
3358952777, 2687934849, 406050009, 1882257425, 1842694296, 97936464,
3184726280, 3587194304, 2249748506, 3994770642, 1444817290,
1042089282, 603502027, 1274779907, 4093570139, 2617098387, 1416525807,
1013799719, 2221420159, 3966436023, 4052660798, 2576195318, 562621358,
1233897318, 440634044, 1916839540, 3393573676, 2722562020, 3215150957,
3617612709, 1873090301, 128334389, 2413365646, 3889833286, 1608470558,
937196758, 708430943, 1111154839, 4198471119, 2453453063, 3254056157,
2851592213, 301116749, 2045870469, 1679044876, 202841540, 3021105308,
3692119124, 327349032, 2072109024, 3280251576, 2877785712, 3059889913,
3730905649, 1717858153, 241648545, 1571753595, 900473523, 2376685547,
3853155107, 4165979050, 2420959074, 675910202, 1078640370, 2994899507,
3665929979, 1652872099, 176684907, 392318946, 2137088810, 3345225330,
2942778042, 4239357792, 2494323624, 749285104, 1151992376, 1498395313,
827104889, 2303322913, 3779774441, 786002069, 1188715613, 4276037893,
2531001805, 2335814980, 3812268428, 1530916052, 859619356, 1626639814,
150446350, 2968704086, 3639736478, 3306440727, 2903991519, 353505671,
2098281807,
],
[
0, 1228700967, 2457401934, 3678701417, 555582061, 1747058506,
3009771555, 4200137988, 1111164122, 185039357, 3494117012, 2575270835,
1663469239, 706411408, 4049501433, 3093430750, 2222328244, 3444208787,
370078714, 1597148893, 2775288793, 3965187838, 924021143, 2117012656,
3326938478, 2406576201, 1412822816, 487164423, 3880816387, 2926375460,
1965585741, 1007945834, 218129817, 1144789182, 2675482583, 3594838768,
740157428, 1696701139, 3194297786, 4149829789, 1329291587, 101129316,
3712195341, 2491409962, 1848042286, 656055817, 4234025312, 3043124295,
2306239533, 3226079498, 453940835, 1379068740, 2825645632, 3780612967,
974328846, 1932486953, 3410847991, 2188449232, 1496683193, 269086622,
3931171482, 2741802941, 2015891668, 823422451, 436259634, 1396487701,
2289578364, 3242478683, 991775071, 1914778744, 2842014481, 3763981878,
1480314856, 285717199, 3393402278, 2206156929, 2032553349, 807022754,
3948853195, 2724383468, 2658583174, 3612000161, 202258632, 1160922607,
3211477227, 4132912588, 756267685, 1680852866, 3696084572, 2507258747,
1312111634, 118047029, 4249895985, 3026991382, 1864941183, 638894936,
385920683, 1581044620, 2239255781, 3427019202, 907881670, 2132890081,
2758137480, 3982076847, 1429973617, 470275926, 3343077439, 2390699288,
1948657692, 1025135931, 3864973906, 2942480245, 2474026783,
3662338616, 17718609, 1211244662, 2993366386, 4216805461, 538173244,
1764729371, 3511526341, 2557599458, 1127569803, 168371372, 4031783336,
3110886543, 1646844902, 722773697, 872519268, 2101209923, 2792975402,
4014280973, 354161673, 1545627950, 2271538759, 3461911392, 1983550142,
1057419161, 3829557488, 2910721495, 1462178003, 505114100, 3311397533,
2355337146, 2960629712, 4182500087, 571434398, 1798510777, 2439650749,
3629539482, 51570675, 1244568276, 4065106698, 3144738349, 1614045508,
688397411, 3545307495, 2590860352, 1093264169, 135634446, 956429309,
1883082458, 2876836275, 3796202644, 404517264, 1361054903, 2321845214,
3277387513, 2067461927, 839289344, 3913420137, 2692640846, 1512535370,
320538733, 3361705732, 2170810915, 3178756681, 4098590574, 789512199,
1714650400, 2624223268, 3579184387, 236094058, 1194262349, 4283235987,
3060827060, 1832125661, 604535290, 3729882366, 2540503513, 1277789872,
85326743, 771841366, 1732059249, 3162089240, 4114995775, 253550395,
1176543772, 2640586101, 3562559570, 1815763340, 621159595, 4265780162,
3078545125, 1294457825, 68921030, 3747553711, 2523094152, 2859947234,
3813353925, 940551852, 1899221899, 2339034767, 3260459944, 420621505,
1345212902, 3897315384, 2708483359, 2050271862, 856217425, 3377582677,
2154671986, 1529423899, 303387964, 587282639, 1782400488, 2977546881,
4165320614, 35437218, 1260439429, 2422489324, 3646438859, 1631206421,
671498546, 4081239643, 3128867708, 1076346488, 152814431, 3529458742,
2606971153, 2809606523, 3997912156, 890227509, 2083763730, 2255139606,
3478572593, 336742744, 1563309183, 3846976929, 2893039750, 1999949807,
1040757448, 3293689804, 2372782827, 1445547394, 521482405,
],
[
0, 4097758792, 3985758817, 430902313, 3738157619, 720442491,
861804626, 3345010202, 3094606487, 1280124127, 1440884982, 2715614910,
1723609252, 2458052332, 2335042245, 2131967117, 1963693023,
2167752087, 2560248254, 1822728182, 2881769964, 1610254244,
1180011405, 2993380805, 3447218504, 960935680, 552188713, 3570888033,
330802043, 3884545331, 4263934234, 169389906, 3927386046, 506065398,
126287327, 4088933271, 886629773, 3236312005, 3645456364, 762833316,
1382339881, 2790916961, 3220508488, 1271650560, 2360022810,
2023080274, 1631243643, 2500074291, 2669458529, 1797415465,
1921871360, 2259925064, 1104377426, 3052249114, 2890050611,
1484552827, 661604086, 3545338046, 3405683863, 1052789471, 4188046533,
228479629, 338779812, 3759114476, 3519166861, 637333445, 1012130796,
3362600356, 252574654, 4214435318, 3801883103, 379778967, 1773259546,
2643402066, 2216694139, 1881065267, 3078490409, 1128324961,
1525666632, 2932933888, 2764679762, 1358396442, 1230532659,
3177621115, 2047240289, 2386083369, 2543301120, 1672045640, 481974469,
3901001357, 4046160548, 85284076, 3262487286, 910904510, 803487895,
3688535775, 1003822643, 3488335995, 3594830930, 578425370, 3843742720,
287574600, 143328865, 4239773737, 2208754852, 2006465260, 1849112261,
2584338573, 1567174295, 2841114847, 2969105654, 1153835710,
1323208172, 3135265700, 2739885965, 1467056581, 2417053663,
1680841111, 2105578942, 2310947830, 4138565499, 43231539, 456959258,
4009915218, 677559624, 3697044224, 3321063209, 835563873, 2791835115,
1381421987, 1274666890, 3217492418, 2024261592, 2358841744,
2503351737, 1627966449, 505149308, 3928301876, 4085919005, 129301333,
3235132751, 887808775, 759557934, 3648731494, 3546519092, 660422780,
1056066645, 3402406429, 229397511, 4187128399, 3762130534, 335763502,
1796236451, 2670637803, 2256649922, 1925146762, 3051333264,
1105293528, 1481538801, 2893064889, 1283400277, 3091330077,
2716792884, 1439706748, 2461065318, 1720596014, 2132883975,
2334125135, 4094480578, 3278474, 429722275, 3986939115, 717427441,
3741172921, 3344091280, 862723800, 963948938, 3444205506, 3571805163,
551271843, 3887821753, 327525873, 170568152, 4262756240, 2164736797,
1966708053, 1821809020, 2561167156, 1606975790, 2885048166,
2992200527, 1181191431, 2007645286, 2207574574, 2587616775,
1845833807, 2842033749, 1566255133, 1156850740, 2966090364,
3487158001, 1005000889, 575149200, 3598107352, 286657730, 3844659850,
4236760739, 146342123, 44150713, 4137646577, 4012930520, 453944208,
3698224522, 676379586, 838842347, 3317784995, 3134348590, 1324125030,
1464043343, 2742898951, 1679662877, 2418231637, 2307671420,
2108855092, 2646416344, 1770245520, 1881981369, 2215778289,
1131600363, 3075215267, 2934113162, 1524487618, 634317135, 3522182919,
3361682222, 1013048678, 4211157884, 255851828, 378597661, 3803064149,
3904276487, 478699087, 86463078, 4044981294, 913918516, 3259473020,
3689451605, 802571805, 1355119248, 2767957208, 3176440049, 1231713977,
2383067299, 2050256619, 1671127746, 2544219274,
],
[
0, 3411442597, 2470478267, 1477900830, 594376071, 3896184354,
2955801660, 2071695257, 1188752142, 2374799531, 3583666869, 516690192,
1706532489, 2934039852, 4143390514, 1033987223, 2377504284,
1189326265, 519395239, 3584240642, 2933433243, 1703860286, 1033380384,
4140718469, 3413064978, 3753655, 1479523497, 2474231564, 3892463765,
592720688, 2067974446, 2954146443, 512219849, 3587285356, 2378652530,
1183916247, 1038790478, 4139570411, 2930388725, 1711035728,
1482502599, 2466990690, 3407720572, 4967385, 2066760768, 2959491045,
3899704827, 589741662, 2469530837, 1482979184, 7507310, 3408197323,
2959046994, 2064188151, 589297897, 3897131852, 3588810715, 515808382,
1185441376, 2382241221, 4135948892, 1037298169, 1707414503,
2928896066, 1024439698, 4133080631, 2924426281, 1696157580, 509788181,
3577002928, 2367832494, 1182022155, 2077580956, 2961384761,
3902136103, 600024194, 1488464667, 2481868990, 3422071456, 11456773,
2965005198, 2079070251, 603644469, 3903625616, 2480346633, 1484877228,
9934770, 3418483735, 4133521536, 1027011365, 1696598331, 2926998174,
3574463751, 509314722, 1179483324, 2367358745, 596143963, 3906868478,
2965958368, 2073990469, 15014620, 3417530745, 2477103975, 1492377794,
1699906645, 2919563248, 4128376302, 1027898955, 1178595794,
2372504183, 3581898857, 506006476, 2923280711, 1701561058, 1031616764,
4130030425, 2370882752, 1174845285, 504384891, 3578148574, 3907473993,
598813164, 2074596338, 2968627287, 3414829006, 14441579, 1489675893,
2476531152, 2048879396, 2974355585, 3915377311, 571052346, 1500651171,
2451858694, 3392315160, 23389373, 1019576362, 4153670543, 2944729489,
1691580980, 531166637, 3573452296, 2364044310, 1203638195, 4155161912,
1023198877, 1693072515, 2948351782, 3569862847, 529642266, 1200048388,
2362520225, 2976929334, 2049322387, 573626253, 3915820072, 2451383217,
1498109972, 22913546, 3389774255, 1687728621, 2949565000, 4158140502,
1015958515, 1207288938, 2359541711, 3568649681, 534986356, 574773987,
3910410566, 2969754456, 2052366589, 19869540, 3396949185, 2456792799,
1496962426, 3912067057, 578493524, 2054022730, 2973474287, 3393196662,
18246099, 1493210061, 2455169128, 2952236287, 1688336218, 1018629444,
4158748385, 2358966648, 1204585181, 534411459, 3565945702, 1192287926,
2353440019, 3562037005, 520496296, 1685957425, 2938884244, 4147980938,
1013666095, 30029240, 3399241245, 2458563587, 1507643302, 581386303,
3924900762, 2984755588, 2058467873, 3399813290, 32731919, 1508215057,
2461266612, 3922230573, 580781704, 2055797910, 2984150835, 2357191588,
1193908225, 524247583, 3563657658, 2937230883, 1682238854, 1012012952,
4144262205, 1503069311, 2462154714, 3403122116, 25296481, 2063233528,
2980842077, 3921342531, 585927654, 525201265, 3558577364, 2349690570,
1197151599, 1008769782, 4151763283, 2942311245, 1681285352,
3559051875, 527739334, 1197626328, 2352228477, 4149192676, 1008327745,
1678714463, 2941869562, 2465741165, 1504592584, 28883158, 3404645235,
2979351786, 2059614031, 584437073, 3917723380,
],
[
0, 2540828609, 722442611, 3162402482, 1444885222, 3245262119,
2098244501, 3932249172, 2889770444, 995070477, 2268200127, 272632702,
4196489002, 1834000619, 3509505625, 1180645784, 1569766761,
3403762344, 1990140954, 3790525403, 193957775, 2633922638, 545265404,
3086082365, 4054767781, 1725902692, 3668001238, 1305523735,
2813454915, 817896834, 2361291568, 466584817, 3139533522, 743476499,
2418992033, 123671648, 3980281908, 2052046837, 3325153607, 1363158662,
387915550, 2154752223, 1007715949, 2875290028, 1090530808, 3597785657,
1779414155, 4252910410, 3870410683, 1908420730, 3451805384,
1523558665, 2964256093, 668926620, 2611047470, 214997999, 1250927223,
3724432822, 1635793668, 4143041733, 479236241, 2346805072, 933169634,
2700017187, 1940816725, 3839849620, 1486952998, 3486576103, 632402355,
2998945394, 247343296, 2580537089, 3750815385, 1222709592, 4104093674,
1676576811, 2307904639, 519971774, 2726317324, 905034445, 775831100,
3109014013, 87140175, 2453688462, 2015431898, 4015061787, 1395561897,
3294585448, 2181061616, 359771185, 2836382339, 1048458562, 3558828310,
1131323095, 4279300197, 1751189412, 3364878727, 1610485318,
3816841460, 1961989941, 2660289377, 165756064, 3047117330, 586065363,
1689361483, 4089473930, 1337853240, 3637506809, 850308781, 2782878060,
429995998, 2396045343, 2501854446, 40809263, 3188776349, 694233692,
3271587336, 1416724937, 3893358459, 2138970298, 958472482, 2924533475,
305051729, 2237616016, 1866339268, 4165985285, 1144097463, 3544218998,
3881633450, 1881993579, 3427966937, 1529047064, 2973905996, 640926605,
2588781887, 222059262, 1264804710, 3692205223, 1617754645, 4145876436,
494686592, 2316150337, 913557747, 2701279026, 3134045123, 767314946,
2445419184, 112448881, 3973220645, 2074312420, 3353153622, 1353508759,
385080847, 2172791246, 1039943548, 2861412541, 1089268969, 3617397544,
1810068890, 4237460059, 1551662200, 3406662585, 2004083979,
3758232266, 174280350, 2635250015, 560781293, 3055362092, 4030863796,
1731456629, 3679289543, 1279031046, 2791123794, 825023635, 2371007009,
438519264, 32293137, 2526885584, 719542370, 3180507043, 1475605495,
3229746230, 2096917124, 3951926597, 2916263133, 983782172, 2262646190,
296536687, 4224554555, 1824285178, 3502378824, 1202976905, 2498987519,
58880574, 3220970636, 680389453, 3270293273, 1436369112, 3923979882,
2123553195, 953016371, 2948339698, 331512128, 2226359937, 1859310293,
4188218644, 1172130726, 3534535783, 3378722966, 1578291031,
3798770149, 1964856868, 2675706480, 135134641, 3027473155, 587359426,
1700617562, 4063013531, 1314047017, 3642962920, 859991996, 2754844797,
407762639, 2403074318, 802357037, 3097692396, 81618526, 2477560223,
2043530699, 4005313034, 1388467384, 3316884345, 2213321441, 345861408,
2833449874, 1066595411, 3589515271, 1115840454, 4277940596,
1770899125, 1916944964, 3845371269, 1498274615, 3460050166, 610103458,
3006039907, 257092049, 2552438288, 3732678536, 1225642057, 4118003451,
1644316986, 2288194926, 521331375, 2741799965, 874347484,
],
[
0, 829543472, 1659086944, 1402109008, 3318173888, 4105602288,
2804218016, 2522164368, 2388842353, 3205694273, 3967909649,
3723537185, 1269139377, 2060735361, 692465617, 406322145, 422172691,
676858915, 2076864627, 1253811267, 3706620115, 3986644195, 3188531379,
2407330947, 2538278754, 2788875090, 4121470722, 3302585138,
1384931234, 1677560722, 812644290, 18752498, 844345382, 52585494,
1353717830, 1640090742, 4153729254, 3336910038, 2507622534,
2751896758, 3157354327, 2369827687, 3738357559, 4020443911,
2046179223, 1216865191, 454402039, 711216071, 729442357, 436976645,
1234813013, 2028475493, 4005378293, 3754749125, 2355007637,
3173991589, 2769862468, 2489936756, 3355121444, 4136289044,
1625288580, 1370373044, 37504996, 860722132, 1688690764, 1440134268,
105170988, 926227484, 2707435660, 2417091772, 3280181484, 4075965660,
3938826045, 3686032141, 2284199773, 3109538669, 788719613, 510866381,
1306611613, 2089851821, 2106505311, 1291807855, 527241279, 773637135,
3091851423, 2302164143, 3668590847, 3957036239, 4092358446,
3265116958, 2433730382, 2692617086, 908804078, 123399134, 1422432142,
1706640318, 1458884714, 1736770650, 873953290, 90614842, 2469626026,
2722256026, 4056950986, 3231841530, 3633710875, 3924284203,
3128274811, 2332326731, 491870171, 740328427, 2142437307, 1321413515,
1339885689, 2125257801, 759078937, 474969129, 2316982457, 3144387721,
3908694233, 3649578217, 3250577160, 4040035128, 2740746088,
2452464472, 75009992, 889805816, 1721444264, 1475015576, 3377381528,
4164883624, 2880268536, 2598157512, 210341976, 1039680616, 1852454968,
1595665416, 1194072041, 1985856473, 634371977, 348023737, 2196457257,
3013251865, 3758681929, 3514383225, 3496417419, 3776367803,
2995040491, 2213897435, 362825803, 617716859, 2000937003, 1177695259,
1577439226, 1869880266, 1021732762, 228045738, 2613223226, 2863876874,
4179703642, 3360744298, 4213010622, 3396117646, 2583615710,
2827947246, 1054482558, 262927438, 1547274270, 1833458734, 1971300303,
1141797887, 396103599, 653122463, 2964911887, 2177442623, 3529203567,
3811216223, 3795101869, 3544546461, 2161574093, 2980500733, 670300269,
377629789, 1158696973, 1952547901, 1817608156, 1562881004, 246798268,
1069810572, 2844864284, 2564881196, 3413280636, 4194521932,
2917769428, 2627237092, 3473541300, 4269530244, 1747906580,
1499407396, 181229684, 1002212420, 596342693, 318415765, 1097392069,
1880689653, 3863750501, 3611161429, 2226097925, 3051248437,
3032512711, 2243013879, 3592671399, 3880912023, 1896294407,
1081539639, 333742183, 580211799, 983740342, 198409094, 1480656854,
1764807654, 4284874614, 3457428294, 2642827030, 2901902118,
2679771378, 2932589762, 4250515602, 3425201314, 1518157874,
1795986434, 949938258, 166673506, 299419523, 547951539, 1933275107,
1112194003, 3558840131, 3849208691, 3069984547, 2274224915,
2257832161, 3085049041, 3832569985, 3573658801, 1129617441,
1915046929, 565653569, 281470065, 150019984, 964742048, 1779611632,
1533240256, 3442888528, 4232551264, 2950031152, 2661561088,
],
[
0, 819083365, 1638166730, 1366706351, 3276333460, 4087011825,
2733412702, 2453580091, 2206053849, 3014626748, 3805922579,
3524001142, 1077236813, 1894214696, 563160199, 289610978, 51846467,
868558118, 1655926153, 1382110700, 3227516119, 4035822770, 2717617181,
2435429496, 2154473626, 2964885759, 3788429392, 3508330549,
1126320398, 1945137515, 579221956, 307495329, 103692934, 922485475,
1737116236, 1465414185, 3311852306, 4122272631, 2764221400,
2484114365, 2236845919, 3045177146, 3841458069, 3559245808,
1176202955, 1992906414, 666836481, 393029220, 87631813, 904601504,
1688033039, 1414492010, 3329345105, 4137942580, 2815800987,
2533854974, 2252640796, 3063327353, 3890275030, 3610434227,
1158443912, 1977502701, 614990658, 343554855, 207385868, 1015958889,
1844970950, 1563049379, 3474232472, 4291210493, 2930828370,
2657279031, 2401353941, 3220437168, 4001738783, 3730278522,
1281958209, 2092636452, 768430475, 488597998, 256600143, 1067012138,
1861163141, 1581064416, 3422783963, 4241600958, 2913466641,
2641740148, 2352405910, 3169117683, 3985812828, 3711997241,
1333672962, 2141979751, 786058440, 503870637, 175263626, 983594991,
1809203008, 1526990629, 3376066078, 4192769659, 2828984020,
2555176625, 2299525715, 3118318134, 3903556249, 3631854332,
1246174151, 2056594338, 736324365, 456217448, 157635273, 968321708,
1757487619, 1477646950, 3391992669, 4211051320, 2877932439,
2606496754, 2316887824, 3133857653, 3955005402, 3681464255,
1229981316, 2038578913, 687109710, 405163563, 414771736, 678089341,
2031917778, 1238278839, 3689941900, 3944887273, 3126098758,
2324054819, 2613403585, 2870437796, 4200673035, 3400734574,
1485684309, 1751090736, 959041183, 167507706, 464516955, 729665342,
2047575953, 1255784436, 3639023311, 3895799466, 3108201989,
2308005472, 2563916418, 2818603751, 4185272904, 3382970925,
1536860950, 1799920499, 977195996, 183299001, 513200286, 776268027,
2134024276, 1340119089, 3722326282, 3976989039, 3162128832,
2359851429, 2649449799, 2906217762, 4233041293, 3432852968,
1587774675, 1852947638, 1057485849, 265669756, 495046109, 760477112,
2082848023, 1291289970, 3737726025, 3994752044, 3211615363,
2411685094, 2667345924, 2922266721, 4283959502, 3481940139,
1572116880, 1835442677, 1007741274, 214094143, 350527252, 607561585,
1967189982, 1167251387, 3618406016, 3883812581, 3053981258,
2262447663, 2543397581, 2806715048, 4131215879, 3337577058,
1423035225, 1677980476, 896908179, 94864374, 401834583, 656521778,
1985475229, 1183173368, 3569050563, 3832109990, 3038712585,
2244815724, 2492348302, 2757496811, 4113188676, 3321397025,
1472648730, 1729425023, 912434896, 112238261, 315270546, 572038647,
1936643416, 1136454973, 3514975238, 3780148323, 2955293900,
2163477673, 2444693579, 2707761198, 4027801729, 3233896676,
1392505311, 1647167930, 861634837, 59357552, 299743441, 554664116,
1887029275, 1085010046, 3533003077, 3796328736, 3006343567,
2212696554, 2459962632, 2725393773, 4077157826, 3285599655,
1374219420, 1631245561, 810327126, 10396723,
],
[
0, 1409766726, 2819533452, 4228513738, 1441866729, 32929455,
4261382501, 2851658787, 2883733458, 4293202580, 65858910, 1475065880,
4262683707, 2853450109, 1444794039, 35298289, 1378416981, 103787539,
4196815833, 2921399967, 131717820, 1407094778, 2950131760, 4224722294,
4190813831, 2915953601, 1371804683, 96682317, 2889588078, 4164732968,
70596578, 1345479332, 2756833962, 4032210924, 207575078, 1482165600,
4053848387, 2779218949, 1504607183, 229191305, 263435640, 1538580542,
2814189556, 4089072306, 1514313361, 239453143, 4065082397, 2789960027,
4135128063, 2726190777, 1584898419, 175174709, 2743609366, 4153376080,
193364634, 1602344924, 1570458669, 161225067, 4120241825, 2710746087,
141193156, 1550662274, 2690958664, 4100165646, 1297060773, 424199907,
3846256937, 2974182511, 415150156, 1287251210, 2964331200, 3837218694,
3870151799, 2997518641, 1319337723, 446966717, 3009214366, 3881542360,
458382610, 1330972756, 526871280, 1264598966, 3077161084, 3815675194,
1251363097, 512826463, 3801670549, 3063920339, 3028626722, 3766646884,
478906286, 1217188584, 3782479563, 3044236173, 1232774215, 494792961,
3911082255, 3172545609, 1091619715, 353869509, 3169796838, 3907524512,
350349418, 1088863532, 1123821277, 385577883, 3941764177, 3203782935,
386729268, 1124749426, 3204689848, 3942972158, 3140917338, 4013018396,
322450134, 1195337616, 4006067123, 3133206261, 1187582271, 315507833,
282386312, 1154714318, 3101324548, 3973914690, 1160117345, 287484199,
3979040493, 3106669483, 2594121546, 3466097164, 848399814, 1721161856,
3480093859, 2607370725, 1734389295, 862452585, 830300312, 1702507998,
2574502420, 3446972242, 1686913905, 814421559, 3431131645, 2558901435,
3367493151, 2628815705, 1622766739, 884875733, 2638675446, 3376523440,
893933434, 1632567868, 1666554317, 928173195, 3411751745, 2673632775,
916765220, 1654910818, 2661945512, 3400353262, 1053742560, 1791590566,
2529197932, 3267832362, 1799353865, 1060676431, 3274792069,
2536901059, 2502726194, 3240871796, 1025652926, 1764060664,
3235754459, 2497373341, 1758665559, 1020546577, 1827024053, 954300915,
3303573049, 2431636351, 957812572, 1829788186, 2434377168, 3307139222,
3338955623, 2466463265, 1862975979, 990745773, 2465548430, 3337756104,
989585922, 1862055748, 3620779247, 2211963305, 2145263203, 735660837,
2183239430, 3592864320, 707739018, 2116577484, 2083713853, 674604667,
3560724913, 2151353591, 700698836, 2110031250, 2177727064, 3586797342,
2247642554, 3523156220, 771155766, 2045882992, 3490278995, 2215525141,
2013775071, 738234777, 773458536, 2048745262, 2249498852, 3524523426,
2079009153, 804027591, 3555033869, 2279790155, 1937851973, 663098115,
3683642569, 2408102287, 644900268, 1920413930, 2390675232, 3665402470,
3630367127, 2355385553, 1886235419, 610991709, 2375164542, 3650451256,
631015666, 1906040244, 564772624, 1974397526, 2309428636, 3718267098,
1951964409, 543148479, 3696637557, 2287035187, 2320234690, 3729567108,
574968398, 1984038664, 3753564971, 2344455789, 2008314279, 598942945,
],
[
0, 1737424129, 3474848258, 2828207875, 2614592245, 4233723892,
1422555383, 860128758, 843281179, 1439534618, 4250831129, 2597352472,
2845110766, 3457813743, 1720257516, 17299181, 1686562358, 50862903,
2879069236, 3423985973, 4283524291, 2564790722, 810327745, 1472357312,
1455789357, 827025452, 2581098287, 4267086382, 3440515032, 2862410457,
34598362, 1702957275, 3373124716, 2927833453, 101725806, 1637796719,
1390038681, 894743448, 2647110811, 4199106970, 4216242039, 2629845622,
877872501, 1407039604, 1620655490, 118997123, 2944714624, 3356113537,
2911578714, 3389380443, 1654050904, 85470553, 912042159, 1372738990,
4181807789, 2664411052, 2680707393, 4165379136, 1356178243, 928734786,
69196724, 1670457013, 3405914550, 2894912695, 2549607977, 4034466600,
1491735595, 1063577898, 203451612, 1806602717, 3275593438, 2763221983,
2780077362, 3258605619, 1789486896, 220699185, 1046683591, 1508762310,
4051625413, 2532317380, 4084271135, 2499802398, 1013772829,
1541541660, 1755745002, 254310379, 2814079208, 3224735209, 3241310980,
2797372933, 237994246, 1772190727, 1525021169, 1030423792, 2516059123,
4067884786, 1593451077, 963960644, 2447890503, 4134085958, 3308101808,
2728615345, 170941106, 1841211315, 1824084318, 188197983, 2745477980,
3291108957, 4151235499, 2430611114, 947071401, 1610470568, 981255283,
1576155506, 4116790897, 2465186672, 2712356486, 3324361607,
1857469572, 154681733, 138393448, 1873889897, 3340914026, 2695671915,
2481468829, 4100376732, 1559613343, 997929630, 704883363, 1301108642,
3843969185, 2190519712, 2983471190, 3596277079, 2127155796, 424095573,
406903224, 2144216249, 3613205434, 2966675131, 2207734605, 3826886220,
1284153679, 721706062, 1317358741, 688632212, 2174269079, 3860220822,
3578973792, 3000775521, 441398370, 2109852003, 2093367182, 457753231,
3017524620, 3562354829, 3876699515, 2157920378, 671893369, 1333967480,
3809371855, 2223021006, 739482829, 1268605388, 2027545658, 525801787,
3083083320, 3494568761, 3511490004, 3066292437, 508620758, 2044596951,
1251645217, 756312608, 2240245027, 3792277538, 2273870073, 3758521848,
1217755899, 790333434, 475988492, 2077359885, 3544381454, 3033269519,
3050042338, 3527741155, 2060847584, 492369121, 773615895, 1234340886,
3774974741, 2257548820, 3186902154, 3665475979, 1927921288, 359089033,
639878783, 1101871998, 3913170045, 2393948540, 2411149201, 3896101520,
1084935571, 656683154, 341882212, 1944995941, 3682422630, 3170087527,
3648168636, 3204210621, 376395966, 1910613439, 1118117961, 623631688,
2377701963, 3929417546, 3945910695, 2361339046, 606874533, 1134745252,
1894142802, 392736339, 3220941136, 3631567953, 1962510566, 326596071,
3152311012, 3697971173, 4012771859, 2292250386, 540274705, 1203571984,
1186659325, 557057788, 2309423615, 3995729150, 3714939144, 3135472649,
309363466, 1979612683, 276786896, 2012320721, 3747779794, 3102501331,
2343103525, 3961917732, 1152718375, 591129382, 574365131, 1169350858,
3978422217, 2326731464, 3119226686, 3731186239, 1995859260, 293115965,
],
[
0, 4060876286, 3790892301, 335044851, 3322195179, 872980757,
670089702, 3590114328, 2313498407, 2078876377, 1745961514, 2585612244,
1340179404, 3186462258, 2920672961, 1545200447, 371599551, 3827967297,
4157752754, 98453580, 3491923028, 573475242, 836168025, 3285902503,
2680358808, 1842285158, 2117560981, 2352703339, 1506257779,
2882250381, 3090400894, 1245694848, 743199102, 3728759936, 3451403379,
1068773773, 3930652053, 407171179, 196907160, 4189101414, 2779348569,
1470460839, 1146950484, 3058769578, 1672336050, 2443304780,
2186919871, 1884664385, 980056513, 3362157631, 3684570316, 697440562,
4235121962, 241359060, 496678951, 4019631577, 3012515558, 1099127576,
1383807979, 2692167189, 1972107789, 2273834995, 2491389696,
1718852350, 1486398204, 2861870850, 3110916081, 1264632335,
2661027351, 1821377513, 2137547546, 2372169444, 3514666459, 594640933,
814342358, 3263556904, 393814320, 3849661646, 4136455229, 75579843,
1321110083, 3165816765, 2940921678, 1564928688, 2293900968,
2058758998, 1766738853, 2604811867, 3344672100, 894937242, 649054313,
3567502743, 23005583, 4082304113, 3769328770, 312961404, 1960113026,
2262367868, 2501942927, 1730974577, 3000000361, 1088180887,
1394881124, 2703769498, 4247903397, 255709531, 482718120, 4006198358,
993357902, 3375988144, 3670089027, 684527805, 1660083005, 2432620227,
2198255152, 1896528846, 2767615958, 1459255848, 1157765851,
3071153957, 3944215578, 421263844, 182688023, 4176450793, 756242673,
3743372559, 3437704700, 1055602690, 2972796408, 1128089606,
1355098357, 2731091211, 2000021779, 2235163885, 2529264670,
1691191776, 953445087, 3403179809, 3642755026, 724306476, 4275095092,
215796682, 522496825, 3978864327, 2803330375, 1427857593, 1189281866,
3035565492, 1628684716, 2468334674, 2162666657, 1928044895, 787628640,
3707654046, 3473289069, 1024074387, 3908497035, 452649845, 151159686,
4212035192, 2642220166, 1869683064, 2089384331, 2391110773,
1534703725, 2843063699, 3129857376, 1216469150, 345521057, 3868472927,
4117517996, 123755346, 3533477706, 546347700, 862517831, 3244619705,
2338012217, 2035757511, 1789874484, 2560842954, 1298108626,
3209927980, 2896952799, 1588064289, 46011166, 4038205152, 3813309971,
289829869, 3300573173, 917942795, 625922808, 3611483910, 3920226052,
463858426, 140347913, 4199647223, 799886319, 3718333969, 3461949154,
1012214556, 1615644707, 2453718493, 2176361774, 1941219536,
2789762248, 1413769526, 1203505605, 3048211515, 4287614907, 226738757,
511419062, 3967266632, 965436240, 3414650542, 3632205405, 712180643,
1986715804, 2221337954, 2543750545, 1704099951, 2960018551,
1113735561, 1369055610, 2744528004, 3320166010, 938064772, 605150071,
3592279689, 65084049, 4058847087, 3793057692, 270105186, 1275107677,
3188495523, 2918511696, 1610152366, 2315531702, 2013804616,
1810913467, 2583450949, 3552812741, 567251771, 842527688, 3225157174,
365376046, 3888857040, 4097007395, 104813277, 1512485346, 2821372956,
3151158511, 1239339281, 2619481353, 1848512759, 2111205380,
2413460986,
],
];
+587
View File
@@ -0,0 +1,587 @@
/*!
Small shared utilities used by Jiff.
*/
#[cfg(feature = "alloc")]
pub(crate) mod crc32;
/// A slice that is either `'static` or on the heap.
///
/// This is useful for representing a sequence of data that can be either
/// created at runtime and put on the heap (when dynamic memory allocation is
/// needed), or when it needs to be constructed at compile time. For example,
/// this is used to represent time zone transitions inside this crate's TZif
/// representation.
///
/// The downside of this type is that `T` cannot contain any borrows.
///
/// This is similar to `SmallStr`, but there is no array-only variant. As such,
/// this isn't intended for small data. (Indeed, time zone transitions can get
/// pretty long.) This also makes the API simpler: all constructors are
/// infallible.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MaybeStaticSlice<T: 'static> {
kind: MaybeStaticSliceKind<T>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
enum MaybeStaticSliceKind<T: 'static> {
Static(&'static [T]),
#[cfg(feature = "alloc")]
Heap(alloc::boxed::Box<[T]>),
}
impl<T: 'static> MaybeStaticSlice<T> {
/// Creates a new static slice from the data provided.
#[inline]
pub const fn statik(data: &'static [T]) -> MaybeStaticSlice<T> {
let kind = MaybeStaticSliceKind::Static(data);
MaybeStaticSlice { kind }
}
/// Creates a new slice on the heap from the data provided.
#[cfg(feature = "alloc")]
#[inline]
pub const fn heap(data: alloc::boxed::Box<[T]>) -> MaybeStaticSlice<T> {
let kind = MaybeStaticSliceKind::Heap(data);
MaybeStaticSlice { kind }
}
/// Returns the underlying data as a slice.
#[inline]
pub const fn as_slice(&self) -> &[T] {
match self.kind {
MaybeStaticSliceKind::Static(slice) => slice,
#[cfg(feature = "alloc")]
MaybeStaticSliceKind::Heap(ref slice) => slice,
}
}
}
impl<T: 'static> From<&'static [T]> for MaybeStaticSlice<T> {
#[inline]
fn from(data: &'static [T]) -> MaybeStaticSlice<T> {
MaybeStaticSlice::statik(data)
}
}
#[cfg(feature = "alloc")]
impl<T: 'static> From<alloc::boxed::Box<[T]>> for MaybeStaticSlice<T> {
#[inline]
fn from(data: alloc::boxed::Box<[T]>) -> MaybeStaticSlice<T> {
MaybeStaticSlice::heap(data)
}
}
impl<T: 'static> core::ops::Deref for MaybeStaticSlice<T> {
type Target = [T];
#[inline]
fn deref(&self) -> &[T] {
self.as_slice()
}
}
/// A "small" string that usually lives in an array.
///
/// When the string is too big, it spills over into the heap. Generally
/// speaking, this should only be used when spilling into the heap is
/// exceptionally rare. For example, for representing pathological user data
/// (like very long time zone abbreviations).
///
/// The `ARRAY_CAPACITY_MAX` parameter defines the maximum length of a string
/// that will fit in an array backed storage. This number cannot be any
/// bigger than `255`.
///
/// In core-only environments, this always uses array backed storage or static
/// data. Static data is only used when `SmallStr::from("some static string")`
/// is used to construct a `SmallStr`. This is useful when constructing static
/// data structures.
#[derive(Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct SmallStr<const ARRAY_CAPACITY_MAX: usize> {
kind: SmallStrKind<ARRAY_CAPACITY_MAX>,
}
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
enum SmallStrKind<const ARRAY_CAPACITY_MAX: usize> {
Array(ArrayStr<ARRAY_CAPACITY_MAX>),
// TODO: Reconsider this variant. I'm not sure it's really necessary, and
// it makes the size of all `SmallStr` values bigger.
Static(&'static str),
#[cfg(feature = "alloc")]
Heap(alloc::boxed::Box<str>),
}
impl<const ARRAY_CAPACITY_MAX: usize> SmallStr<ARRAY_CAPACITY_MAX> {
/// Creates a new array-or-heap backed string depending on the length.
///
/// In environments with dynamic memory allocation, this never returns
/// `None`. In core-only environments, this may return `None` when the
/// string length exceeds the maximum capacity.
#[inline]
pub fn new(s: &str) -> Option<SmallStr<ARRAY_CAPACITY_MAX>> {
SmallStr::try_array(s).or_else(|| {
#[cfg(not(feature = "alloc"))]
{
None
}
#[cfg(feature = "alloc")]
{
Some(SmallStr::from(alloc::boxed::Box::<str>::from(s)))
}
})
}
/// Like `new` but always spills to the heap when the given string does not
/// fit in this string's fixed capacity.
///
/// This is only available when the `alloc` crate feature is enabled.
#[cfg(feature = "alloc")]
#[inline]
pub fn new_or_heap(s: &str) -> SmallStr<ARRAY_CAPACITY_MAX> {
SmallStr::try_array(s).unwrap_or_else(|| {
SmallStr::from(alloc::boxed::Box::<str>::from(s))
})
}
/// Like `SmallStr::new`, but this never returns a string on the heap.
///
/// This is useful when you want a `SmallStr` that is guaranteed to never
/// be on the heap. For example, when constructing static data.
///
/// If the string exceeds the maximum capacity, then `None` is returned.
#[inline]
pub const fn try_array(s: &str) -> Option<SmallStr<ARRAY_CAPACITY_MAX>> {
let Some(astr) = ArrayStr::new(s) else { return None };
let kind = SmallStrKind::Array(astr);
Some(SmallStr { kind })
}
/// Like `SmallStr::new`, but this never returns a string on the heap.
///
/// This is useful when you want a `SmallStr` that is guaranteed to never
/// be on the heap. For example, when constructing static data.
///
/// # Panics
///
/// If the string exceeds the maximum capacity, then this routine panics.
#[inline]
pub const fn array(s: &str) -> SmallStr<ARRAY_CAPACITY_MAX> {
// MSRV(1.83): We can use `unwrap()` in a const context, so this
// routine isn't as necessary. But it's still nice.
let Some(astr) = ArrayStr::new(s) else { panic!("string too big") };
let kind = SmallStrKind::Array(astr);
SmallStr { kind }
}
/// Like `SmallStr::new`, but only accepts a static string.
///
/// This never fails.
#[inline]
pub const fn statik(s: &'static str) -> SmallStr<ARRAY_CAPACITY_MAX> {
let kind = SmallStrKind::Static(s);
SmallStr { kind }
}
/// Returns the maximum capacity for this small string's array storage.
#[inline]
pub const fn array_capacity_max() -> usize {
ARRAY_CAPACITY_MAX
}
/// Returns this small string as a string slice.
#[inline]
pub const fn as_str(&self) -> &str {
match self.kind {
SmallStrKind::Array(ref astr) => astr.as_str(),
SmallStrKind::Static(s) => s,
#[cfg(feature = "alloc")]
SmallStrKind::Heap(ref s) => s,
}
}
}
/// Return a `SmallStr` that is guaranteed to live as an array.
impl<const ARRAY_CAPACITY_MAX: usize> From<ArrayStr<ARRAY_CAPACITY_MAX>>
for SmallStr<ARRAY_CAPACITY_MAX>
{
#[inline]
fn from(s: ArrayStr<ARRAY_CAPACITY_MAX>) -> SmallStr<ARRAY_CAPACITY_MAX> {
let kind = SmallStrKind::Array(s);
SmallStr { kind }
}
}
/// Return a `SmallStr` that is guaranteed to live as a static string.
impl<const ARRAY_CAPACITY_MAX: usize> From<&'static str>
for SmallStr<ARRAY_CAPACITY_MAX>
{
#[inline]
fn from(s: &'static str) -> SmallStr<ARRAY_CAPACITY_MAX> {
SmallStr::statik(s)
}
}
/// Return a `SmallStr` that is guaranteed to live on the heap.
#[cfg(feature = "alloc")]
impl<const ARRAY_CAPACITY_MAX: usize> From<alloc::boxed::Box<str>>
for SmallStr<ARRAY_CAPACITY_MAX>
{
#[inline]
fn from(s: alloc::boxed::Box<str>) -> SmallStr<ARRAY_CAPACITY_MAX> {
let kind = SmallStrKind::Heap(s);
SmallStr { kind }
}
}
impl<const ARRAY_CAPACITY_MAX: usize> core::ops::Deref
for SmallStr<ARRAY_CAPACITY_MAX>
{
type Target = str;
#[inline]
fn deref(&self) -> &str {
SmallStr::<ARRAY_CAPACITY_MAX>::as_str(self)
}
}
impl<const ARRAY_CAPACITY_MAX: usize> AsRef<str>
for SmallStr<ARRAY_CAPACITY_MAX>
{
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<const ARRAY_CAPACITY_MAX: usize> PartialEq<str>
for SmallStr<ARRAY_CAPACITY_MAX>
{
#[inline]
fn eq(&self, rhs: &str) -> bool {
self.as_str() == rhs
}
}
impl<const ARRAY_CAPACITY_MAX: usize> PartialEq<&str>
for SmallStr<ARRAY_CAPACITY_MAX>
{
#[inline]
fn eq(&self, rhs: &&str) -> bool {
self.as_str() == *rhs
}
}
impl<const ARRAY_CAPACITY_MAX: usize> PartialEq<SmallStr<ARRAY_CAPACITY_MAX>>
for str
{
#[inline]
fn eq(&self, rhs: &SmallStr<ARRAY_CAPACITY_MAX>) -> bool {
self == rhs.as_str()
}
}
impl<const ARRAY_CAPACITY_MAX: usize> PartialOrd<str>
for SmallStr<ARRAY_CAPACITY_MAX>
{
#[inline]
fn partial_cmp(&self, rhs: &str) -> Option<core::cmp::Ordering> {
self.as_str().partial_cmp(rhs)
}
}
impl<const ARRAY_CAPACITY_MAX: usize> PartialOrd<&str>
for SmallStr<ARRAY_CAPACITY_MAX>
{
#[inline]
fn partial_cmp(&self, rhs: &&str) -> Option<core::cmp::Ordering> {
self.as_str().partial_cmp(*rhs)
}
}
impl<const ARRAY_CAPACITY_MAX: usize> PartialOrd<SmallStr<ARRAY_CAPACITY_MAX>>
for str
{
#[inline]
fn partial_cmp(
&self,
rhs: &SmallStr<ARRAY_CAPACITY_MAX>,
) -> Option<core::cmp::Ordering> {
self.partial_cmp(rhs.as_str())
}
}
impl<const ARRAY_CAPACITY_MAX: usize> core::fmt::Debug
for SmallStr<ARRAY_CAPACITY_MAX>
{
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
core::fmt::Debug::fmt(self.as_str(), f)
}
}
impl<const ARRAY_CAPACITY_MAX: usize> core::fmt::Display
for SmallStr<ARRAY_CAPACITY_MAX>
{
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
core::fmt::Display::fmt(self.as_str(), f)
}
}
#[cfg(feature = "defmt")]
impl<const ARRAY_CAPACITY_MAX: usize> defmt::Format
for SmallStr<ARRAY_CAPACITY_MAX>
{
#[inline]
fn format(&self, f: defmt::Formatter) {
defmt::write!(f, "{=str}", self.as_str())
}
}
/// A simple array-backed string type with a fixed capacity.
///
/// This is used by Jiff in lieu of a `Box<str>` for supporting core-only
/// environments without a dynamic memory allocator. For example, this is used
/// to represent time zone abbreviations which can be relied upon to be short.
///
/// `N` must be less than `256` so that its length can be represented by an
/// unsigned 8-bit integer.
///
/// An `ArrayStr` is guaranteed to be valid UTF-8.
#[derive(Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct ArrayStr<const N: usize> {
// If it's advantageous enough, we could use an array of uninitialized
// bytes. But it's not clear that it's worth doing. ---AG
/// The number of bytes used by the string in `bytes`.
///
/// (We could technically save this byte in some cases and use a NUL
/// terminator. For example, since we don't permit NUL bytes in POSIX time
/// zone abbreviation strings, but this is simpler and only one byte and
/// generalizes. And we're not really trying to micro-optimize the storage
/// requirements when we use these array strings. Or at least, I don't know
/// of a reason to.)
len: u8,
/// The UTF-8 bytes that make up the string.
///
/// This array---the entire array---is always valid UTF-8. And
/// the `0..self.len` sub-slice is also always valid UTF-8.
bytes: [u8; N],
}
impl<const N: usize> ArrayStr<N> {
/// Creates a new fixed capacity string.
///
/// If the given string exceeds `N` bytes, then this returns
/// `None`.
#[inline]
pub const fn new(s: &str) -> Option<ArrayStr<N>> {
let len = s.len();
if len > N {
return None;
}
let mut bytes = [0; N];
let mut i = 0;
while i < s.as_bytes().len() {
bytes[i] = s.as_bytes()[i];
i += 1;
}
// OK because we don't ever use anything bigger than u8::MAX for `N`.
// And we probably shouldn't, because that would be a pretty chunky
// array. If such a thing is needed, please file an issue to discuss.
debug_assert!(N <= u8::MAX as usize, "size of ArrayStr is too big");
Some(ArrayStr { len: len as u8, bytes })
}
/// Returns the capacity of this array string.
#[inline]
pub const fn capacity() -> usize {
N
}
/// Append the bytes given to the end of this string.
///
/// If the capacity would be exceeded, then this is a no-op and `false`
/// is returned. Otherwise, all of `s` is written to this array string and
/// `true` is returned.
#[inline]
pub fn push_str(&mut self, s: &str) -> bool {
let len = self.len as usize;
let Some(new_len) = len.checked_add(s.len()) else { return false };
if new_len > N {
return false;
}
let mut i = len;
while i < new_len {
self.bytes[i] = s.as_bytes()[i - len];
i += 1;
}
// OK because we don't ever use anything bigger than u8::MAX for `N`.
// And we probably shouldn't, because that would be a pretty chunky
// array. If such a thing is needed, please file an issue to discuss.
debug_assert!(N <= u8::MAX as usize, "size of ArrayStr is too big");
self.len = new_len as u8;
true
}
/// Returns this array string as a string slice.
#[inline]
pub const fn as_str(&self) -> &str {
// SAFETY: Firstly, the unchecked UTF-8 conversion is correct because
// the constructor and all mutators only accept `&str`. All mutators
// (just `push_str` at time of writing) only ever concatenates the
// given string to what is already there. Any UTF-8 string concatenated
// with any other UTF-8 string produces a UTF-8 string.
//
// Secondly, `self.bytes` is always valid and initialized. And
// `self.len` is managed as the length of bytes that make up the
// string.
unsafe {
core::str::from_utf8_unchecked(core::slice::from_raw_parts(
self.bytes.as_ptr(),
self.len as usize,
))
}
}
}
impl<const N: usize> core::ops::Deref for ArrayStr<N> {
type Target = str;
#[inline]
fn deref(&self) -> &str {
ArrayStr::<N>::as_str(self)
}
}
impl<const N: usize> AsRef<str> for ArrayStr<N> {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<const N: usize> PartialEq<str> for ArrayStr<N> {
#[inline]
fn eq(&self, rhs: &str) -> bool {
self.as_str() == rhs
}
}
impl<const N: usize> PartialEq<&str> for ArrayStr<N> {
#[inline]
fn eq(&self, rhs: &&str) -> bool {
self.as_str() == *rhs
}
}
impl<const N: usize> PartialEq<ArrayStr<N>> for str {
#[inline]
fn eq(&self, rhs: &ArrayStr<N>) -> bool {
self == rhs.as_str()
}
}
impl<const N: usize> PartialOrd<str> for ArrayStr<N> {
#[inline]
fn partial_cmp(&self, rhs: &str) -> Option<core::cmp::Ordering> {
self.as_str().partial_cmp(rhs)
}
}
impl<const N: usize> PartialOrd<&str> for ArrayStr<N> {
#[inline]
fn partial_cmp(&self, rhs: &&str) -> Option<core::cmp::Ordering> {
self.as_str().partial_cmp(*rhs)
}
}
impl<const N: usize> PartialOrd<ArrayStr<N>> for str {
#[inline]
fn partial_cmp(&self, rhs: &ArrayStr<N>) -> Option<core::cmp::Ordering> {
self.partial_cmp(rhs.as_str())
}
}
impl<const N: usize> core::fmt::Debug for ArrayStr<N> {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
core::fmt::Debug::fmt(self.as_str(), f)
}
}
impl<const N: usize> core::fmt::Display for ArrayStr<N> {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
core::fmt::Display::fmt(self.as_str(), f)
}
}
impl<const N: usize> core::fmt::Write for ArrayStr<N> {
#[inline]
fn write_str(&mut self, s: &str) -> core::fmt::Result {
if self.push_str(s) {
Ok(())
} else {
Err(core::fmt::Error)
}
}
}
#[cfg(feature = "defmt")]
impl<const N: usize> defmt::Format for ArrayStr<N> {
#[inline]
fn format(&self, f: defmt::Formatter) {
defmt::write!(f, "{=str}", self.as_str())
}
}
/// Parses an `OsStr` into a `&str` when `&[u8]` isn't easily available.
///
/// The main difference between this and `OsStr::to_str` is that this will
/// be a zero-cost conversion on Unix platforms to `&[u8]`. On Windows, this
/// will do UTF-8 validation and return an error if it's invalid UTF-8.
// MSRV(1.74): Use `OsStr::as_encoded_bytes` and delete this routine.
#[cfg(feature = "std")]
pub(crate) fn os_str_bytes<'o, O>(os_str: &'o O) -> Option<&'o [u8]>
where
O: ?Sized + AsRef<std::ffi::OsStr>,
{
let os_str = os_str.as_ref();
#[cfg(unix)]
{
use std::os::unix::ffi::OsStrExt;
Some(os_str.as_bytes())
}
#[cfg(not(unix))]
{
// It is suspect that we're doing UTF-8 validation and then throwing
// away the fact that we did UTF-8 validation. So this could lead
// to an extra UTF-8 check if the caller ultimately needs UTF-8. If
// that's important, we can add a new API that returns a `&str`. But it
// probably won't matter because an `OsStr` in this crate is usually
// just an environment variable.
os_str.to_str().map(|s| s.as_bytes())
}
}
#[cfg(test)]
mod tests {
use core::fmt::Write;
use super::*;
#[test]
fn fmt_write() {
let mut dst = ArrayStr::<5>::new("").unwrap();
assert!(write!(&mut dst, "abcd").is_ok());
assert!(write!(&mut dst, "e").is_ok());
assert!(write!(&mut dst, "f").is_err());
}
#[test]
fn array_str_size() {
assert_eq!(7, core::mem::size_of::<ArrayStr::<6>>());
}
}