mod find;
#[doc(inline)]
#[cfg(feature = "alloc")]
pub use find::FoundDateTimeList;
#[doc(inline)]
pub use find::{FoundDateTimeKind, FoundDateTimeListRefMut};
use crate::constants::*;
use crate::datetime::find::find_date_time;
use crate::error::TzError;
use crate::error::datetime::DateTimeError;
use crate::timezone::{LocalTimeType, TimeZoneRef};
use crate::utils::{min, try_into_i32, try_into_i64};
use core::cmp::Ordering;
use core::fmt;
use core::ops::{Add, AddAssign, Sub, SubAssign};
use core::time::Duration;
#[cfg(feature = "std")]
use std::time::SystemTime;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct UtcDateTime {
year: i32,
month: u8,
month_day: u8,
hour: u8,
minute: u8,
second: u8,
nanoseconds: u32,
}
impl fmt::Display for UtcDateTime {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
format_date_time(f, self.year, self.month, self.month_day, self.hour, self.minute, self.second, self.nanoseconds, 0)
}
}
impl UtcDateTime {
pub const UNIX_EPOCH: Self = Self { year: 1970, month: 1, month_day: 1, hour: 0, minute: 0, second: 0, nanoseconds: 0 };
pub const MIN: Self = Self { year: i32::MIN, month: 1, month_day: 1, hour: 0, minute: 0, second: 0, nanoseconds: 0 };
pub const MAX: Self = Self { year: i32::MAX, month: 12, month_day: 31, hour: 23, minute: 59, second: 59, nanoseconds: 999_999_999 };
const MIN_UNIX_TIME: i64 = UtcDateTime::MIN.unix_time();
const MAX_UNIX_TIME: i64 = UtcDateTime::MAX.unix_time();
const fn check_unix_time(unix_time: i64) -> Result<(), TzError> {
if Self::MIN_UNIX_TIME <= unix_time && unix_time <= Self::MAX_UNIX_TIME { Ok(()) } else { Err(TzError::OutOfRange) }
}
pub const fn new(year: i32, month: u8, month_day: u8, hour: u8, minute: u8, second: u8, nanoseconds: u32) -> Result<Self, TzError> {
if year == i32::MAX && month == 12 && month_day == 31 && hour == 23 && minute == 59 && second == 60 {
return Err(TzError::OutOfRange);
}
if let Err(error) = check_date_time_inputs(year, month, month_day, hour, minute, second, nanoseconds) {
return Err(TzError::DateTime(error));
}
Ok(Self { year, month, month_day, hour, minute, second, nanoseconds })
}
pub const fn from_timespec(unix_time: i64, nanoseconds: u32) -> Result<Self, TzError> {
let seconds = match unix_time.checked_sub(UNIX_OFFSET_SECS) {
Some(seconds) => seconds,
None => return Err(TzError::OutOfRange),
};
let mut remaining_days = seconds / SECONDS_PER_DAY;
let mut remaining_seconds = seconds % SECONDS_PER_DAY;
if remaining_seconds < 0 {
remaining_seconds += SECONDS_PER_DAY;
remaining_days -= 1;
}
let mut cycles_400_years = remaining_days / DAYS_PER_400_YEARS;
remaining_days %= DAYS_PER_400_YEARS;
if remaining_days < 0 {
remaining_days += DAYS_PER_400_YEARS;
cycles_400_years -= 1;
}
let cycles_100_years = min(remaining_days / DAYS_PER_100_YEARS, 3);
remaining_days -= cycles_100_years * DAYS_PER_100_YEARS;
let cycles_4_years = min(remaining_days / DAYS_PER_4_YEARS, 24);
remaining_days -= cycles_4_years * DAYS_PER_4_YEARS;
let remaining_years = min(remaining_days / DAYS_PER_NORMAL_YEAR, 3);
remaining_days -= remaining_years * DAYS_PER_NORMAL_YEAR;
let mut year = OFFSET_YEAR + remaining_years + cycles_4_years * 4 + cycles_100_years * 100 + cycles_400_years * 400;
let mut month = 0;
while month < DAY_IN_MONTHS_LEAP_YEAR_FROM_MARCH.len() {
let days = DAY_IN_MONTHS_LEAP_YEAR_FROM_MARCH[month];
if remaining_days < days {
break;
}
remaining_days -= days;
month += 1;
}
month += 2;
if month >= MONTHS_PER_YEAR as usize {
month -= MONTHS_PER_YEAR as usize;
year += 1;
}
month += 1;
let month_day = 1 + remaining_days;
let hour = remaining_seconds / SECONDS_PER_HOUR;
let minute = (remaining_seconds / SECONDS_PER_MINUTE) % MINUTES_PER_HOUR;
let second = remaining_seconds % SECONDS_PER_MINUTE;
let year = match try_into_i32(year) {
Ok(year) => year,
Err(error) => return Err(error),
};
Ok(Self { year, month: month as u8, month_day: month_day as u8, hour: hour as u8, minute: minute as u8, second: second as u8, nanoseconds })
}
pub const fn from_total_nanoseconds(total_nanoseconds: i128) -> Result<Self, TzError> {
match total_nanoseconds_to_timespec(total_nanoseconds) {
Ok((unix_time, nanoseconds)) => Self::from_timespec(unix_time, nanoseconds),
Err(error) => Err(error),
}
}
pub const fn unix_time(&self) -> i64 {
unix_time(self.year, self.month, self.month_day, self.hour, self.minute, self.second)
}
pub const fn project(&self, time_zone_ref: TimeZoneRef<'_>) -> Result<DateTime, TzError> {
DateTime::from_timespec(self.unix_time(), self.nanoseconds, time_zone_ref)
}
pub const fn checked_add(&self, duration: Duration) -> Option<Self> {
let total_nanoseconds = self.total_nanoseconds() + duration.as_nanos() as i128;
match Self::from_total_nanoseconds(total_nanoseconds) {
Ok(x) => Some(x),
Err(_) => None,
}
}
pub const fn checked_sub(&self, duration: Duration) -> Option<Self> {
let total_nanoseconds = self.total_nanoseconds() - duration.as_nanos() as i128;
match Self::from_total_nanoseconds(total_nanoseconds) {
Ok(x) => Some(x),
Err(_) => None,
}
}
pub const fn duration_since(&self, earlier: Self) -> Result<Duration, Duration> {
let current_total_nanoseconds = self.total_nanoseconds();
let earlier_total_nanoseconds = earlier.total_nanoseconds();
duration_between(earlier_total_nanoseconds, current_total_nanoseconds)
}
#[cfg(feature = "std")]
pub fn now() -> Result<Self, TzError> {
SystemTime::now().try_into()
}
}
#[cfg(feature = "std")]
impl TryFrom<SystemTime> for UtcDateTime {
type Error = TzError;
fn try_from(time: SystemTime) -> Result<Self, Self::Error> {
Self::from_total_nanoseconds(crate::utils::system_time::total_nanoseconds(time))
}
}
#[cfg(feature = "std")]
impl From<UtcDateTime> for SystemTime {
fn from(utc_date_time: UtcDateTime) -> Self {
match duration_between(0, utc_date_time.total_nanoseconds()) {
Ok(duration) => SystemTime::UNIX_EPOCH + duration,
Err(duration) => SystemTime::UNIX_EPOCH - duration,
}
}
}
impl Add<Duration> for UtcDateTime {
type Output = UtcDateTime;
fn add(self, rhs: Duration) -> Self::Output {
self.checked_add(rhs).expect("attempt to add with overflow")
}
}
impl AddAssign<Duration> for UtcDateTime {
fn add_assign(&mut self, rhs: Duration) {
*self = *self + rhs
}
}
impl Sub<Duration> for UtcDateTime {
type Output = UtcDateTime;
fn sub(self, rhs: Duration) -> Self::Output {
self.checked_sub(rhs).expect("attempt to subtract with overflow")
}
}
impl SubAssign<Duration> for UtcDateTime {
fn sub_assign(&mut self, rhs: Duration) {
*self = *self - rhs
}
}
#[derive(Debug, Copy, Clone)]
pub struct DateTime {
year: i32,
month: u8,
month_day: u8,
hour: u8,
minute: u8,
second: u8,
local_time_type: LocalTimeType,
unix_time: i64,
nanoseconds: u32,
}
impl PartialEq for DateTime {
fn eq(&self, other: &Self) -> bool {
(self.unix_time, self.nanoseconds) == (other.unix_time, other.nanoseconds)
}
}
impl Eq for DateTime {}
impl PartialOrd for DateTime {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for DateTime {
fn cmp(&self, other: &Self) -> Ordering {
(self.unix_time, self.nanoseconds).cmp(&(other.unix_time, other.nanoseconds))
}
}
impl fmt::Display for DateTime {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let ut_offset = self.local_time_type().ut_offset();
format_date_time(f, self.year, self.month, self.month_day, self.hour, self.minute, self.second, self.nanoseconds, ut_offset)
}
}
impl DateTime {
#[allow(clippy::too_many_arguments)]
pub const fn new(
year: i32,
month: u8,
month_day: u8,
hour: u8,
minute: u8,
second: u8,
nanoseconds: u32,
local_time_type: LocalTimeType,
) -> Result<Self, TzError> {
if let Err(error) = check_date_time_inputs(year, month, month_day, hour, minute, second, nanoseconds) {
return Err(TzError::DateTime(error));
}
let unix_time = unix_time(year, month, month_day, hour, minute, second) - local_time_type.ut_offset() as i64;
if let Err(error) = UtcDateTime::check_unix_time(unix_time) {
return Err(error);
}
Ok(Self { year, month, month_day, hour, minute, second, local_time_type, unix_time, nanoseconds })
}
#[allow(clippy::too_many_arguments)]
#[cfg(feature = "alloc")]
pub fn find(
year: i32,
month: u8,
month_day: u8,
hour: u8,
minute: u8,
second: u8,
nanoseconds: u32,
time_zone_ref: TimeZoneRef<'_>,
) -> Result<FoundDateTimeList, TzError> {
let mut found_date_time_list = FoundDateTimeList::default();
find_date_time(&mut found_date_time_list, year, month, month_day, hour, minute, second, nanoseconds, time_zone_ref)?;
Ok(found_date_time_list)
}
#[allow(clippy::too_many_arguments)]
pub fn find_n<'a>(
buf: &'a mut [Option<FoundDateTimeKind>],
year: i32,
month: u8,
month_day: u8,
hour: u8,
minute: u8,
second: u8,
nanoseconds: u32,
time_zone_ref: TimeZoneRef<'_>,
) -> Result<FoundDateTimeListRefMut<'a>, TzError> {
let mut found_date_time_list = FoundDateTimeListRefMut::new(buf);
find_date_time(&mut found_date_time_list, year, month, month_day, hour, minute, second, nanoseconds, time_zone_ref)?;
Ok(found_date_time_list)
}
pub const fn from_timespec_and_local(unix_time: i64, nanoseconds: u32, local_time_type: LocalTimeType) -> Result<Self, TzError> {
let unix_time_with_offset = match unix_time.checked_add(local_time_type.ut_offset() as i64) {
Some(unix_time_with_offset) => unix_time_with_offset,
None => return Err(TzError::OutOfRange),
};
let utc_date_time_with_offset = match UtcDateTime::from_timespec(unix_time_with_offset, nanoseconds) {
Ok(utc_date_time_with_offset) => utc_date_time_with_offset,
Err(error) => return Err(error),
};
let UtcDateTime { year, month, month_day, hour, minute, second, nanoseconds } = utc_date_time_with_offset;
Ok(Self { year, month, month_day, hour, minute, second, local_time_type, unix_time, nanoseconds })
}
pub const fn from_timespec(unix_time: i64, nanoseconds: u32, time_zone_ref: TimeZoneRef<'_>) -> Result<Self, TzError> {
let local_time_type = match time_zone_ref.find_local_time_type(unix_time) {
Ok(&local_time_type) => local_time_type,
Err(error) => return Err(error),
};
Self::from_timespec_and_local(unix_time, nanoseconds, local_time_type)
}
pub const fn from_total_nanoseconds_and_local(total_nanoseconds: i128, local_time_type: LocalTimeType) -> Result<Self, TzError> {
match total_nanoseconds_to_timespec(total_nanoseconds) {
Ok((unix_time, nanoseconds)) => Self::from_timespec_and_local(unix_time, nanoseconds, local_time_type),
Err(error) => Err(error),
}
}
pub const fn from_total_nanoseconds(total_nanoseconds: i128, time_zone_ref: TimeZoneRef<'_>) -> Result<Self, TzError> {
match total_nanoseconds_to_timespec(total_nanoseconds) {
Ok((unix_time, nanoseconds)) => Self::from_timespec(unix_time, nanoseconds, time_zone_ref),
Err(error) => Err(error),
}
}
pub const fn project(&self, time_zone_ref: TimeZoneRef<'_>) -> Result<Self, TzError> {
Self::from_timespec(self.unix_time, self.nanoseconds, time_zone_ref)
}
pub const fn duration_since(&self, earlier: Self) -> Result<Duration, Duration> {
let current_total_nanoseconds = self.total_nanoseconds();
let earlier_total_nanoseconds = earlier.total_nanoseconds();
duration_between(earlier_total_nanoseconds, current_total_nanoseconds)
}
#[cfg(feature = "std")]
pub fn now(time_zone_ref: TimeZoneRef<'_>) -> Result<Self, TzError> {
let now = crate::utils::system_time::total_nanoseconds(SystemTime::now());
Self::from_total_nanoseconds(now, time_zone_ref)
}
}
impl TryFrom<DateTime> for UtcDateTime {
type Error = TzError;
fn try_from(date_time: DateTime) -> Result<Self, Self::Error> {
Self::from_timespec(date_time.unix_time, date_time.nanoseconds)
}
}
#[cfg(feature = "std")]
impl From<DateTime> for SystemTime {
fn from(date_time: DateTime) -> Self {
match duration_between(0, date_time.total_nanoseconds()) {
Ok(duration) => SystemTime::UNIX_EPOCH + duration,
Err(duration) => SystemTime::UNIX_EPOCH - duration,
}
}
}
macro_rules! impl_datetime {
() => {
#[inline]
pub const fn year(&self) -> i32 {
self.year
}
#[inline]
pub const fn month(&self) -> u8 {
self.month
}
#[inline]
pub const fn month_day(&self) -> u8 {
self.month_day
}
#[inline]
pub const fn hour(&self) -> u8 {
self.hour
}
#[inline]
pub const fn minute(&self) -> u8 {
self.minute
}
#[inline]
pub const fn second(&self) -> u8 {
self.second
}
#[inline]
pub const fn nanoseconds(&self) -> u32 {
self.nanoseconds
}
#[inline]
pub const fn week_day(&self) -> u8 {
week_day(self.year, self.month as usize, self.month_day as i64)
}
#[inline]
pub const fn year_day(&self) -> u16 {
year_day(self.year, self.month as usize, self.month_day as i64)
}
#[inline]
pub const fn total_nanoseconds(&self) -> i128 {
nanoseconds_since_unix_epoch(self.unix_time(), self.nanoseconds)
}
};
}
impl UtcDateTime {
impl_datetime!();
}
impl DateTime {
impl_datetime!();
#[inline]
pub const fn local_time_type(&self) -> &LocalTimeType {
&self.local_time_type
}
#[inline]
pub const fn unix_time(&self) -> i64 {
self.unix_time
}
}
#[inline]
const fn week_day(year: i32, month: usize, month_day: i64) -> u8 {
let days_since_unix_epoch = days_since_unix_epoch(year, month, month_day);
(4 + days_since_unix_epoch).rem_euclid(DAYS_PER_WEEK) as u8
}
#[inline]
const fn year_day(year: i32, month: usize, month_day: i64) -> u16 {
let leap = (month >= 3 && is_leap_year(year)) as i64;
(CUMUL_DAYS_IN_MONTHS_NORMAL_YEAR[month - 1] + leap + month_day - 1) as u16
}
#[inline]
pub(crate) const fn is_leap_year(year: i32) -> bool {
year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)
}
#[inline]
pub(crate) const fn days_since_unix_epoch(year: i32, month: usize, month_day: i64) -> i64 {
let is_leap_year = is_leap_year(year);
let year = year as i64;
let mut result = (year - 1970) * 365;
if year >= 1970 {
result += (year - 1968) / 4;
result -= (year - 1900) / 100;
result += (year - 1600) / 400;
if is_leap_year && month < 3 {
result -= 1;
}
} else {
result += (year - 1972) / 4;
result -= (year - 2000) / 100;
result += (year - 2000) / 400;
if is_leap_year && month >= 3 {
result += 1;
}
}
result += CUMUL_DAYS_IN_MONTHS_NORMAL_YEAR[month - 1] + month_day - 1;
result
}
#[inline]
const fn unix_time(year: i32, month: u8, month_day: u8, hour: u8, minute: u8, second: u8) -> i64 {
let mut result = days_since_unix_epoch(year, month as usize, month_day as i64);
result *= HOURS_PER_DAY;
result += hour as i64;
result *= MINUTES_PER_HOUR;
result += minute as i64;
result *= SECONDS_PER_MINUTE;
result += second as i64;
result
}
#[inline]
const fn nanoseconds_since_unix_epoch(unix_time: i64, nanoseconds: u32) -> i128 {
unix_time as i128 * NANOSECONDS_PER_SECOND as i128 + nanoseconds as i128
}
#[inline]
const fn total_nanoseconds_to_timespec(total_nanoseconds: i128) -> Result<(i64, u32), TzError> {
let unix_time = match try_into_i64(total_nanoseconds.div_euclid(NANOSECONDS_PER_SECOND as i128)) {
Ok(unix_time) => unix_time,
Err(error) => return Err(error),
};
let nanoseconds = total_nanoseconds.rem_euclid(NANOSECONDS_PER_SECOND as i128) as u32;
Ok((unix_time, nanoseconds))
}
#[inline]
const fn duration_between(before: i128, after: i128) -> Result<Duration, Duration> {
let total_nanoseconds_diff = after.abs_diff(before);
let secs = total_nanoseconds_diff / NANOSECONDS_PER_SECOND as u128;
let nanos = total_nanoseconds_diff % NANOSECONDS_PER_SECOND as u128;
let duration = Duration::new(secs as u64, nanos as u32);
if after >= before { Ok(duration) } else { Err(duration) }
}
const fn check_date_time_inputs(year: i32, month: u8, month_day: u8, hour: u8, minute: u8, second: u8, nanoseconds: u32) -> Result<(), DateTimeError> {
if !(1 <= month && month <= 12) {
return Err(DateTimeError::InvalidMonth);
}
if !(1 <= month_day && month_day <= 31) {
return Err(DateTimeError::InvalidMonthDay);
}
if hour > 23 {
return Err(DateTimeError::InvalidHour);
}
if minute > 59 {
return Err(DateTimeError::InvalidMinute);
}
if second > 60 {
return Err(DateTimeError::InvalidSecond);
}
if nanoseconds >= NANOSECONDS_PER_SECOND {
return Err(DateTimeError::InvalidNanoseconds);
}
let leap = is_leap_year(year) as i64;
let mut days_in_month = DAYS_IN_MONTHS_NORMAL_YEAR[month as usize - 1];
if month == 2 {
days_in_month += leap;
}
if month_day as i64 > days_in_month {
return Err(DateTimeError::InvalidMonthDay);
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn format_date_time(
f: &mut fmt::Formatter,
year: i32,
month: u8,
month_day: u8,
hour: u8,
minute: u8,
second: u8,
nanoseconds: u32,
ut_offset: i32,
) -> fmt::Result {
write!(f, "{year}-{month:02}-{month_day:02}T{hour:02}:{minute:02}:{second:02}.{nanoseconds:09}")?;
if ut_offset != 0 {
let ut_offset = ut_offset as i64;
let ut_offset_abs = ut_offset.abs();
let sign = if ut_offset < 0 { '-' } else { '+' };
let offset_hour = ut_offset_abs / SECONDS_PER_HOUR;
let offset_minute = (ut_offset_abs / SECONDS_PER_MINUTE) % MINUTES_PER_HOUR;
let offset_second = ut_offset_abs % SECONDS_PER_MINUTE;
write!(f, "{sign}{offset_hour:02}:{offset_minute:02}")?;
if offset_second != 0 {
write!(f, ":{offset_second:02}")?;
}
} else {
write!(f, "Z")?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "alloc")]
use crate::timezone::TimeZone;
#[cfg(feature = "alloc")]
pub(super) fn check_equal_date_time(x: &DateTime, y: &DateTime) {
assert_eq!(x.year(), y.year());
assert_eq!(x.month(), y.month());
assert_eq!(x.month_day(), y.month_day());
assert_eq!(x.hour(), y.hour());
assert_eq!(x.minute(), y.minute());
assert_eq!(x.second(), y.second());
assert_eq!(x.local_time_type(), y.local_time_type());
assert_eq!(x.unix_time(), y.unix_time());
assert_eq!(x.nanoseconds(), y.nanoseconds());
}
#[cfg(feature = "alloc")]
#[test]
fn test_date_time() -> Result<(), TzError> {
let time_zone_utc = TimeZone::utc();
let utc = LocalTimeType::utc();
let time_zone_cet = TimeZone::fixed(3600)?;
let cet = LocalTimeType::with_ut_offset(3600)?;
let time_zone_eet = TimeZone::fixed(7200)?;
let eet = LocalTimeType::with_ut_offset(7200)?;
#[cfg(feature = "std")]
{
assert_eq!(DateTime::now(time_zone_utc.as_ref())?.local_time_type().ut_offset(), 0);
assert_eq!(DateTime::now(time_zone_cet.as_ref())?.local_time_type().ut_offset(), 3600);
assert_eq!(DateTime::now(time_zone_eet.as_ref())?.local_time_type().ut_offset(), 7200);
}
let unix_times = &[
-93750523134,
-11670955134,
-11670868734,
-8515195134,
-8483659134,
-8389051134,
-8388964734,
951825666,
951912066,
983448066,
1078056066,
1078142466,
4107585666,
32540356866,
];
let nanoseconds_list = &[10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23];
#[rustfmt::skip]
let date_times_utc = &[
DateTime { year: -1001, month: 3, month_day: 1, hour: 12, minute: 1, second: 6, local_time_type: utc, unix_time: -93750523134, nanoseconds: 10 },
DateTime { year: 1600, month: 2, month_day: 29, hour: 12, minute: 1, second: 6, local_time_type: utc, unix_time: -11670955134, nanoseconds: 11 },
DateTime { year: 1600, month: 3, month_day: 1, hour: 12, minute: 1, second: 6, local_time_type: utc, unix_time: -11670868734, nanoseconds: 12 },
DateTime { year: 1700, month: 3, month_day: 1, hour: 12, minute: 1, second: 6, local_time_type: utc, unix_time: -8515195134, nanoseconds: 13 },
DateTime { year: 1701, month: 3, month_day: 1, hour: 12, minute: 1, second: 6, local_time_type: utc, unix_time: -8483659134, nanoseconds: 14 },
DateTime { year: 1704, month: 2, month_day: 29, hour: 12, minute: 1, second: 6, local_time_type: utc, unix_time: -8389051134, nanoseconds: 15 },
DateTime { year: 1704, month: 3, month_day: 1, hour: 12, minute: 1, second: 6, local_time_type: utc, unix_time: -8388964734, nanoseconds: 16 },
DateTime { year: 2000, month: 2, month_day: 29, hour: 12, minute: 1, second: 6, local_time_type: utc, unix_time: 951825666, nanoseconds: 17 },
DateTime { year: 2000, month: 3, month_day: 1, hour: 12, minute: 1, second: 6, local_time_type: utc, unix_time: 951912066, nanoseconds: 18 },
DateTime { year: 2001, month: 3, month_day: 1, hour: 12, minute: 1, second: 6, local_time_type: utc, unix_time: 983448066, nanoseconds: 19 },
DateTime { year: 2004, month: 2, month_day: 29, hour: 12, minute: 1, second: 6, local_time_type: utc, unix_time: 1078056066, nanoseconds: 20 },
DateTime { year: 2004, month: 3, month_day: 1, hour: 12, minute: 1, second: 6, local_time_type: utc, unix_time: 1078142466, nanoseconds: 21 },
DateTime { year: 2100, month: 3, month_day: 1, hour: 12, minute: 1, second: 6, local_time_type: utc, unix_time: 4107585666, nanoseconds: 22 },
DateTime { year: 3001, month: 3, month_day: 1, hour: 12, minute: 1, second: 6, local_time_type: utc, unix_time: 32540356866, nanoseconds: 23 },
];
#[rustfmt::skip]
let date_times_cet = &[
DateTime { year: -1001, month: 3, month_day: 1, hour: 13, minute: 1, second: 6, local_time_type: cet, unix_time: -93750523134, nanoseconds: 10 },
DateTime { year: 1600, month: 2, month_day: 29, hour: 13, minute: 1, second: 6, local_time_type: cet, unix_time: -11670955134, nanoseconds: 11 },
DateTime { year: 1600, month: 3, month_day: 1, hour: 13, minute: 1, second: 6, local_time_type: cet, unix_time: -11670868734, nanoseconds: 12 },
DateTime { year: 1700, month: 3, month_day: 1, hour: 13, minute: 1, second: 6, local_time_type: cet, unix_time: -8515195134, nanoseconds: 13 },
DateTime { year: 1701, month: 3, month_day: 1, hour: 13, minute: 1, second: 6, local_time_type: cet, unix_time: -8483659134, nanoseconds: 14 },
DateTime { year: 1704, month: 2, month_day: 29, hour: 13, minute: 1, second: 6, local_time_type: cet, unix_time: -8389051134, nanoseconds: 15 },
DateTime { year: 1704, month: 3, month_day: 1, hour: 13, minute: 1, second: 6, local_time_type: cet, unix_time: -8388964734, nanoseconds: 16 },
DateTime { year: 2000, month: 2, month_day: 29, hour: 13, minute: 1, second: 6, local_time_type: cet, unix_time: 951825666, nanoseconds: 17 },
DateTime { year: 2000, month: 3, month_day: 1, hour: 13, minute: 1, second: 6, local_time_type: cet, unix_time: 951912066, nanoseconds: 18 },
DateTime { year: 2001, month: 3, month_day: 1, hour: 13, minute: 1, second: 6, local_time_type: cet, unix_time: 983448066, nanoseconds: 19 },
DateTime { year: 2004, month: 2, month_day: 29, hour: 13, minute: 1, second: 6, local_time_type: cet, unix_time: 1078056066, nanoseconds: 20 },
DateTime { year: 2004, month: 3, month_day: 1, hour: 13, minute: 1, second: 6, local_time_type: cet, unix_time: 1078142466, nanoseconds: 21 },
DateTime { year: 2100, month: 3, month_day: 1, hour: 13, minute: 1, second: 6, local_time_type: cet, unix_time: 4107585666, nanoseconds: 22 },
DateTime { year: 3001, month: 3, month_day: 1, hour: 13, minute: 1, second: 6, local_time_type: cet, unix_time: 32540356866, nanoseconds: 23 },
];
#[rustfmt::skip]
let date_times_eet = &[
DateTime { year: -1001, month: 3, month_day: 1, hour: 14, minute: 1, second: 6, local_time_type: eet, unix_time: -93750523134, nanoseconds: 10 },
DateTime { year: 1600, month: 2, month_day: 29, hour: 14, minute: 1, second: 6, local_time_type: eet, unix_time: -11670955134, nanoseconds: 11 },
DateTime { year: 1600, month: 3, month_day: 1, hour: 14, minute: 1, second: 6, local_time_type: eet, unix_time: -11670868734, nanoseconds: 12 },
DateTime { year: 1700, month: 3, month_day: 1, hour: 14, minute: 1, second: 6, local_time_type: eet, unix_time: -8515195134, nanoseconds: 13 },
DateTime { year: 1701, month: 3, month_day: 1, hour: 14, minute: 1, second: 6, local_time_type: eet, unix_time: -8483659134, nanoseconds: 14 },
DateTime { year: 1704, month: 2, month_day: 29, hour: 14, minute: 1, second: 6, local_time_type: eet, unix_time: -8389051134, nanoseconds: 15 },
DateTime { year: 1704, month: 3, month_day: 1, hour: 14, minute: 1, second: 6, local_time_type: eet, unix_time: -8388964734, nanoseconds: 16 },
DateTime { year: 2000, month: 2, month_day: 29, hour: 14, minute: 1, second: 6, local_time_type: eet, unix_time: 951825666, nanoseconds: 17 },
DateTime { year: 2000, month: 3, month_day: 1, hour: 14, minute: 1, second: 6, local_time_type: eet, unix_time: 951912066, nanoseconds: 18 },
DateTime { year: 2001, month: 3, month_day: 1, hour: 14, minute: 1, second: 6, local_time_type: eet, unix_time: 983448066, nanoseconds: 19 },
DateTime { year: 2004, month: 2, month_day: 29, hour: 14, minute: 1, second: 6, local_time_type: eet, unix_time: 1078056066, nanoseconds: 20 },
DateTime { year: 2004, month: 3, month_day: 1, hour: 14, minute: 1, second: 6, local_time_type: eet, unix_time: 1078142466, nanoseconds: 21 },
DateTime { year: 2100, month: 3, month_day: 1, hour: 14, minute: 1, second: 6, local_time_type: eet, unix_time: 4107585666, nanoseconds: 22 },
DateTime { year: 3001, month: 3, month_day: 1, hour: 14, minute: 1, second: 6, local_time_type: eet, unix_time: 32540356866, nanoseconds: 23 },
];
for ((((&unix_time, &nanoseconds), date_time_utc), date_time_cet), date_time_eet) in
unix_times.iter().zip(nanoseconds_list).zip(date_times_utc).zip(date_times_cet).zip(date_times_eet)
{
let utc_date_time = UtcDateTime::from_timespec(unix_time, nanoseconds)?;
assert_eq!(UtcDateTime::from_timespec(utc_date_time.unix_time(), nanoseconds)?, utc_date_time);
assert_eq!(utc_date_time.year(), date_time_utc.year());
assert_eq!(utc_date_time.month(), date_time_utc.month());
assert_eq!(utc_date_time.month_day(), date_time_utc.month_day());
assert_eq!(utc_date_time.hour(), date_time_utc.hour());
assert_eq!(utc_date_time.minute(), date_time_utc.minute());
assert_eq!(utc_date_time.second(), date_time_utc.second());
assert_eq!(utc_date_time.nanoseconds(), date_time_utc.nanoseconds());
assert_eq!(utc_date_time.unix_time(), unix_time);
assert_eq!(date_time_utc.unix_time(), unix_time);
assert_eq!(date_time_cet.unix_time(), unix_time);
assert_eq!(date_time_eet.unix_time(), unix_time);
assert_eq!(date_time_utc, date_time_cet);
assert_eq!(date_time_utc, date_time_eet);
check_equal_date_time(&utc_date_time.project(time_zone_utc.as_ref())?, date_time_utc);
check_equal_date_time(&utc_date_time.project(time_zone_cet.as_ref())?, date_time_cet);
check_equal_date_time(&utc_date_time.project(time_zone_eet.as_ref())?, date_time_eet);
check_equal_date_time(&date_time_utc.project(time_zone_utc.as_ref())?, date_time_utc);
check_equal_date_time(&date_time_cet.project(time_zone_utc.as_ref())?, date_time_utc);
check_equal_date_time(&date_time_eet.project(time_zone_utc.as_ref())?, date_time_utc);
check_equal_date_time(&date_time_utc.project(time_zone_cet.as_ref())?, date_time_cet);
check_equal_date_time(&date_time_cet.project(time_zone_cet.as_ref())?, date_time_cet);
check_equal_date_time(&date_time_eet.project(time_zone_cet.as_ref())?, date_time_cet);
check_equal_date_time(&date_time_utc.project(time_zone_eet.as_ref())?, date_time_eet);
check_equal_date_time(&date_time_cet.project(time_zone_eet.as_ref())?, date_time_eet);
check_equal_date_time(&date_time_eet.project(time_zone_eet.as_ref())?, date_time_eet);
}
Ok(())
}
#[cfg(feature = "alloc")]
#[test]
fn test_date_time_leap_seconds() -> Result<(), TzError> {
let utc_date_time = UtcDateTime::new(1972, 6, 30, 23, 59, 60, 1000)?;
assert_eq!(UtcDateTime::from_timespec(utc_date_time.unix_time(), 1000)?, UtcDateTime::new(1972, 7, 1, 0, 0, 0, 1000)?);
let date_time = utc_date_time.project(TimeZone::fixed(-3600)?.as_ref())?;
let date_time_result = DateTime {
year: 1972,
month: 6,
month_day: 30,
hour: 23,
minute: 00,
second: 00,
local_time_type: LocalTimeType::with_ut_offset(-3600)?,
unix_time: 78796800,
nanoseconds: 1000,
};
check_equal_date_time(&date_time, &date_time_result);
Ok(())
}
#[cfg(feature = "alloc")]
#[test]
fn test_date_time_partial_eq_partial_ord() -> Result<(), TzError> {
let time_zone_utc = TimeZone::utc();
let time_zone_cet = TimeZone::fixed(3600)?;
let time_zone_eet = TimeZone::fixed(7200)?;
let utc_date_time_1 = UtcDateTime::from_timespec(1, 1)?;
let utc_date_time_2 = UtcDateTime::from_timespec(2, 1)?;
let utc_date_time_3 = UtcDateTime::from_timespec(3, 1)?;
let utc_date_time_4 = UtcDateTime::from_timespec(3, 1000)?;
let date_time_utc_1 = utc_date_time_1.project(time_zone_utc.as_ref())?;
let date_time_utc_2 = utc_date_time_2.project(time_zone_utc.as_ref())?;
let date_time_utc_3 = utc_date_time_3.project(time_zone_utc.as_ref())?;
let date_time_utc_4 = utc_date_time_4.project(time_zone_utc.as_ref())?;
let date_time_cet_1 = utc_date_time_1.project(time_zone_cet.as_ref())?;
let date_time_cet_2 = utc_date_time_2.project(time_zone_cet.as_ref())?;
let date_time_cet_3 = utc_date_time_3.project(time_zone_cet.as_ref())?;
let date_time_cet_4 = utc_date_time_4.project(time_zone_cet.as_ref())?;
let date_time_eet_1 = utc_date_time_1.project(time_zone_eet.as_ref())?;
let date_time_eet_2 = utc_date_time_2.project(time_zone_eet.as_ref())?;
let date_time_eet_3 = utc_date_time_3.project(time_zone_eet.as_ref())?;
let date_time_eet_4 = utc_date_time_4.project(time_zone_eet.as_ref())?;
assert_eq!(date_time_utc_1, date_time_cet_1);
assert_eq!(date_time_utc_1, date_time_eet_1);
assert_eq!(date_time_utc_2, date_time_cet_2);
assert_eq!(date_time_utc_2, date_time_eet_2);
assert_eq!(date_time_utc_3, date_time_cet_3);
assert_eq!(date_time_utc_3, date_time_eet_3);
assert_eq!(date_time_utc_4, date_time_cet_4);
assert_eq!(date_time_utc_4, date_time_eet_4);
assert_ne!(date_time_utc_1, date_time_utc_2);
assert_ne!(date_time_utc_1, date_time_utc_3);
assert_ne!(date_time_utc_1, date_time_utc_4);
assert_eq!(date_time_utc_1.partial_cmp(&date_time_cet_1), Some(Ordering::Equal));
assert_eq!(date_time_utc_1.partial_cmp(&date_time_eet_1), Some(Ordering::Equal));
assert_eq!(date_time_utc_2.partial_cmp(&date_time_cet_2), Some(Ordering::Equal));
assert_eq!(date_time_utc_2.partial_cmp(&date_time_eet_2), Some(Ordering::Equal));
assert_eq!(date_time_utc_3.partial_cmp(&date_time_cet_3), Some(Ordering::Equal));
assert_eq!(date_time_utc_3.partial_cmp(&date_time_eet_3), Some(Ordering::Equal));
assert_eq!(date_time_utc_4.partial_cmp(&date_time_cet_4), Some(Ordering::Equal));
assert_eq!(date_time_utc_4.partial_cmp(&date_time_eet_4), Some(Ordering::Equal));
assert_eq!(date_time_utc_1.partial_cmp(&date_time_utc_2), Some(Ordering::Less));
assert_eq!(date_time_utc_2.partial_cmp(&date_time_utc_3), Some(Ordering::Less));
assert_eq!(date_time_utc_3.partial_cmp(&date_time_utc_4), Some(Ordering::Less));
Ok(())
}
#[test]
fn test_date_time_sync_and_send() {
trait _AssertSyncSendStatic: Sync + Send + 'static {}
impl _AssertSyncSendStatic for DateTime {}
}
#[cfg(feature = "alloc")]
#[test]
fn test_date_time_ord() -> Result<(), TzError> {
let utc_date_time_1 = UtcDateTime::new(1972, 6, 30, 23, 59, 59, 1000)?;
let utc_date_time_2 = UtcDateTime::new(1972, 6, 30, 23, 59, 60, 1000)?;
let utc_date_time_3 = UtcDateTime::new(1972, 7, 1, 0, 0, 0, 1000)?;
let utc_date_time_4 = UtcDateTime::new(1972, 7, 1, 0, 0, 0, 1001)?;
assert_eq!(utc_date_time_1.cmp(&utc_date_time_1), Ordering::Equal);
assert_eq!(utc_date_time_1.cmp(&utc_date_time_2), Ordering::Less);
assert_eq!(utc_date_time_1.cmp(&utc_date_time_3), Ordering::Less);
assert_eq!(utc_date_time_1.cmp(&utc_date_time_4), Ordering::Less);
assert_eq!(utc_date_time_2.cmp(&utc_date_time_1), Ordering::Greater);
assert_eq!(utc_date_time_2.cmp(&utc_date_time_2), Ordering::Equal);
assert_eq!(utc_date_time_2.cmp(&utc_date_time_3), Ordering::Less);
assert_eq!(utc_date_time_2.cmp(&utc_date_time_4), Ordering::Less);
assert_eq!(utc_date_time_3.cmp(&utc_date_time_1), Ordering::Greater);
assert_eq!(utc_date_time_3.cmp(&utc_date_time_2), Ordering::Greater);
assert_eq!(utc_date_time_3.cmp(&utc_date_time_3), Ordering::Equal);
assert_eq!(utc_date_time_3.cmp(&utc_date_time_4), Ordering::Less);
assert_eq!(utc_date_time_4.cmp(&utc_date_time_1), Ordering::Greater);
assert_eq!(utc_date_time_4.cmp(&utc_date_time_2), Ordering::Greater);
assert_eq!(utc_date_time_4.cmp(&utc_date_time_3), Ordering::Greater);
assert_eq!(utc_date_time_4.cmp(&utc_date_time_4), Ordering::Equal);
assert_eq!(utc_date_time_1.cmp(&utc_date_time_1), utc_date_time_1.unix_time().cmp(&utc_date_time_1.unix_time()));
assert_eq!(utc_date_time_1.cmp(&utc_date_time_2), utc_date_time_1.unix_time().cmp(&utc_date_time_2.unix_time()));
assert_eq!(utc_date_time_1.cmp(&utc_date_time_3), utc_date_time_1.unix_time().cmp(&utc_date_time_3.unix_time()));
assert_eq!(utc_date_time_1.cmp(&utc_date_time_4), utc_date_time_1.unix_time().cmp(&utc_date_time_4.unix_time()));
assert_eq!(utc_date_time_2.cmp(&utc_date_time_1), utc_date_time_2.unix_time().cmp(&utc_date_time_1.unix_time()));
assert_eq!(utc_date_time_2.cmp(&utc_date_time_2), utc_date_time_2.unix_time().cmp(&utc_date_time_2.unix_time()));
assert_eq!(utc_date_time_3.cmp(&utc_date_time_1), utc_date_time_3.unix_time().cmp(&utc_date_time_1.unix_time()));
assert_eq!(utc_date_time_3.cmp(&utc_date_time_3), utc_date_time_3.unix_time().cmp(&utc_date_time_3.unix_time()));
assert_eq!(utc_date_time_4.cmp(&utc_date_time_1), utc_date_time_4.unix_time().cmp(&utc_date_time_1.unix_time()));
assert_eq!(utc_date_time_4.cmp(&utc_date_time_4), utc_date_time_4.unix_time().cmp(&utc_date_time_4.unix_time()));
let date_time_1 = utc_date_time_1.project(TimeZone::fixed(0)?.as_ref())?;
let date_time_2 = utc_date_time_2.project(TimeZone::fixed(3600)?.as_ref())?;
let date_time_3 = utc_date_time_3.project(TimeZone::fixed(7200)?.as_ref())?;
let date_time_4 = utc_date_time_4.project(TimeZone::fixed(10800)?.as_ref())?;
assert_eq!(date_time_1.cmp(&date_time_1), Ordering::Equal);
assert_eq!(date_time_1.cmp(&date_time_2), Ordering::Less);
assert_eq!(date_time_1.cmp(&date_time_3), Ordering::Less);
assert_eq!(date_time_1.cmp(&date_time_4), Ordering::Less);
assert_eq!(date_time_2.cmp(&date_time_1), Ordering::Greater);
assert_eq!(date_time_2.cmp(&date_time_2), Ordering::Equal);
assert_eq!(date_time_2.cmp(&date_time_3), Ordering::Equal);
assert_eq!(date_time_2.cmp(&date_time_4), Ordering::Less);
assert_eq!(date_time_3.cmp(&date_time_1), Ordering::Greater);
assert_eq!(date_time_3.cmp(&date_time_2), Ordering::Equal);
assert_eq!(date_time_3.cmp(&date_time_3), Ordering::Equal);
assert_eq!(date_time_3.cmp(&date_time_4), Ordering::Less);
assert_eq!(date_time_4.cmp(&date_time_1), Ordering::Greater);
assert_eq!(date_time_4.cmp(&date_time_2), Ordering::Greater);
assert_eq!(date_time_4.cmp(&date_time_3), Ordering::Greater);
assert_eq!(date_time_4.cmp(&date_time_4), Ordering::Equal);
Ok(())
}
#[cfg(feature = "alloc")]
#[test]
fn test_date_time_format() -> Result<(), TzError> {
use alloc::string::ToString;
let time_zones = [
TimeZone::fixed(-49550)?,
TimeZone::fixed(-5400)?,
TimeZone::fixed(-3600)?,
TimeZone::fixed(-1800)?,
TimeZone::fixed(0)?,
TimeZone::fixed(1800)?,
TimeZone::fixed(3600)?,
TimeZone::fixed(5400)?,
TimeZone::fixed(49550)?,
];
let utc_date_times = &[UtcDateTime::new(2000, 1, 2, 3, 4, 5, 0)?, UtcDateTime::new(2000, 1, 2, 3, 4, 5, 123_456_789)?];
let utc_date_time_strings = &["2000-01-02T03:04:05.000000000Z", "2000-01-02T03:04:05.123456789Z"];
let date_time_strings_list = &[
&[
"2000-01-01T13:18:15.000000000-13:45:50",
"2000-01-02T01:34:05.000000000-01:30",
"2000-01-02T02:04:05.000000000-01:00",
"2000-01-02T02:34:05.000000000-00:30",
"2000-01-02T03:04:05.000000000Z",
"2000-01-02T03:34:05.000000000+00:30",
"2000-01-02T04:04:05.000000000+01:00",
"2000-01-02T04:34:05.000000000+01:30",
"2000-01-02T16:49:55.000000000+13:45:50",
],
&[
"2000-01-01T13:18:15.123456789-13:45:50",
"2000-01-02T01:34:05.123456789-01:30",
"2000-01-02T02:04:05.123456789-01:00",
"2000-01-02T02:34:05.123456789-00:30",
"2000-01-02T03:04:05.123456789Z",
"2000-01-02T03:34:05.123456789+00:30",
"2000-01-02T04:04:05.123456789+01:00",
"2000-01-02T04:34:05.123456789+01:30",
"2000-01-02T16:49:55.123456789+13:45:50",
],
];
for ((utc_date_time, &utc_date_time_string), &date_time_strings) in utc_date_times.iter().zip(utc_date_time_strings).zip(date_time_strings_list) {
for (time_zone, &date_time_string) in time_zones.iter().zip(date_time_strings) {
assert_eq!(utc_date_time.to_string(), utc_date_time_string);
assert_eq!(utc_date_time.project(time_zone.as_ref())?.to_string(), date_time_string);
}
}
Ok(())
}
#[cfg(feature = "alloc")]
#[test]
fn test_date_time_duration() -> Result<(), TzError> {
let utc_date_time = UtcDateTime::new(1, 2, 3, 4, 5, 6, 789_012_345)?;
let date_time = utc_date_time.project(TimeZoneRef::utc())?;
let duration = Duration::new(24 * 3600 * 365, 999_999_999);
let added_utc_date_time = UtcDateTime::new(2, 2, 3, 4, 5, 7, 789_012_344)?;
let substracted_utc_date_time = UtcDateTime::new(0, 2, 4, 4, 5, 5, 789_012_346)?;
assert_eq!(utc_date_time.checked_add(duration), Some(added_utc_date_time));
assert_eq!(utc_date_time.checked_sub(duration), Some(substracted_utc_date_time));
assert_eq!(utc_date_time.duration_since(substracted_utc_date_time), Ok(duration));
assert_eq!(date_time.duration_since(substracted_utc_date_time.project(TimeZoneRef::utc())?), Ok(duration));
Ok(())
}
#[cfg(feature = "alloc")]
#[test]
fn test_date_time_overflow() -> Result<(), TzError> {
assert!(UtcDateTime::new(i32::MIN, 1, 1, 0, 0, 0, 0).is_ok());
assert!(UtcDateTime::new(i32::MAX, 12, 31, 23, 59, 59, 0).is_ok());
assert!(DateTime::new(i32::MIN, 1, 1, 0, 0, 0, 0, LocalTimeType::utc()).is_ok());
assert!(DateTime::new(i32::MAX, 12, 31, 23, 59, 59, 0, LocalTimeType::utc()).is_ok());
assert!(matches!(DateTime::new(i32::MIN, 1, 1, 0, 0, 0, 0, LocalTimeType::with_ut_offset(1)?), Err(TzError::OutOfRange)));
assert!(matches!(DateTime::new(i32::MAX, 12, 31, 23, 59, 59, 0, LocalTimeType::with_ut_offset(-1)?), Err(TzError::OutOfRange)));
assert!(matches!(UtcDateTime::new(i32::MAX, 12, 31, 23, 59, 60, 0), Err(TzError::OutOfRange)));
assert!(matches!(DateTime::new(i32::MAX, 12, 31, 23, 59, 60, 0, LocalTimeType::utc()), Err(TzError::OutOfRange)));
assert!(DateTime::new(i32::MAX, 12, 31, 23, 59, 60, 0, LocalTimeType::with_ut_offset(1)?).is_ok());
assert!(UtcDateTime::from_timespec(UtcDateTime::MIN_UNIX_TIME, 0).is_ok());
assert!(UtcDateTime::from_timespec(UtcDateTime::MAX_UNIX_TIME, 0).is_ok());
assert!(matches!(UtcDateTime::from_timespec(UtcDateTime::MIN_UNIX_TIME - 1, 0), Err(TzError::OutOfRange)));
assert!(matches!(UtcDateTime::from_timespec(UtcDateTime::MAX_UNIX_TIME + 1, 0), Err(TzError::OutOfRange)));
assert!(matches!(UtcDateTime::from_timespec(UtcDateTime::MIN_UNIX_TIME, 0)?.project(TimeZone::fixed(-1)?.as_ref()), Err(TzError::OutOfRange)));
assert!(matches!(UtcDateTime::from_timespec(UtcDateTime::MAX_UNIX_TIME, 0)?.project(TimeZone::fixed(1)?.as_ref()), Err(TzError::OutOfRange)));
assert!(matches!(UtcDateTime::from_timespec(i64::MIN, 0), Err(TzError::OutOfRange)));
assert!(matches!(UtcDateTime::from_timespec(i64::MAX, 0), Err(TzError::OutOfRange)));
assert!(DateTime::from_timespec(UtcDateTime::MIN_UNIX_TIME, 0, TimeZone::fixed(0)?.as_ref()).is_ok());
assert!(DateTime::from_timespec(UtcDateTime::MAX_UNIX_TIME, 0, TimeZone::fixed(0)?.as_ref()).is_ok());
assert!(matches!(DateTime::from_timespec(i64::MIN, 0, TimeZone::fixed(-1)?.as_ref()), Err(TzError::OutOfRange)));
assert!(matches!(DateTime::from_timespec(i64::MAX, 0, TimeZone::fixed(1)?.as_ref()), Err(TzError::OutOfRange)));
assert_eq!(UtcDateTime::MIN.checked_sub(Duration::from_nanos(1)), None);
assert_eq!(UtcDateTime::MAX.checked_add(Duration::from_nanos(1)), None);
assert_eq!(UtcDateTime::MIN.checked_sub(Duration::MAX), None);
assert_eq!(UtcDateTime::MAX.checked_add(Duration::MAX), None);
assert_eq!(UtcDateTime::MAX.duration_since(UtcDateTime::MIN), Ok(Duration::new(135536076801503999, 999999999)));
assert_eq!(UtcDateTime::MIN.duration_since(UtcDateTime::MAX), Err(Duration::new(135536076801503999, 999999999)));
Ok(())
}
#[test]
fn test_week_day() {
assert_eq!(week_day(1970, 1, 1), 4);
assert_eq!(week_day(2000, 1, 1), 6);
assert_eq!(week_day(2000, 2, 28), 1);
assert_eq!(week_day(2000, 2, 29), 2);
assert_eq!(week_day(2000, 3, 1), 3);
assert_eq!(week_day(2000, 12, 31), 0);
assert_eq!(week_day(2001, 1, 1), 1);
assert_eq!(week_day(2001, 2, 28), 3);
assert_eq!(week_day(2001, 3, 1), 4);
assert_eq!(week_day(2001, 12, 31), 1);
}
#[test]
fn test_year_day() {
assert_eq!(year_day(2000, 1, 1), 0);
assert_eq!(year_day(2000, 2, 28), 58);
assert_eq!(year_day(2000, 2, 29), 59);
assert_eq!(year_day(2000, 3, 1), 60);
assert_eq!(year_day(2000, 12, 31), 365);
assert_eq!(year_day(2001, 1, 1), 0);
assert_eq!(year_day(2001, 2, 28), 58);
assert_eq!(year_day(2001, 3, 1), 59);
assert_eq!(year_day(2001, 12, 31), 364);
}
#[test]
fn test_is_leap_year() {
assert!(is_leap_year(2000));
assert!(!is_leap_year(2001));
assert!(is_leap_year(2004));
assert!(!is_leap_year(2100));
assert!(!is_leap_year(2200));
assert!(!is_leap_year(2300));
assert!(is_leap_year(2400));
}
#[test]
fn test_days_since_unix_epoch() {
assert_eq!(days_since_unix_epoch(-1001, 3, 1), -1085076);
assert_eq!(days_since_unix_epoch(1600, 2, 29), -135081);
assert_eq!(days_since_unix_epoch(1600, 3, 1), -135080);
assert_eq!(days_since_unix_epoch(1700, 3, 1), -98556);
assert_eq!(days_since_unix_epoch(1701, 3, 1), -98191);
assert_eq!(days_since_unix_epoch(1704, 2, 29), -97096);
assert_eq!(days_since_unix_epoch(2000, 2, 29), 11016);
assert_eq!(days_since_unix_epoch(2000, 3, 1), 11017);
assert_eq!(days_since_unix_epoch(2001, 3, 1), 11382);
assert_eq!(days_since_unix_epoch(2004, 2, 29), 12477);
assert_eq!(days_since_unix_epoch(2100, 3, 1), 47541);
assert_eq!(days_since_unix_epoch(3001, 3, 1), 376624);
}
#[test]
fn test_nanoseconds_since_unix_epoch() {
assert_eq!(nanoseconds_since_unix_epoch(1, 1000), 1_000_001_000);
assert_eq!(nanoseconds_since_unix_epoch(0, 1000), 1000);
assert_eq!(nanoseconds_since_unix_epoch(-1, 1000), -999_999_000);
assert_eq!(nanoseconds_since_unix_epoch(-2, 1000), -1_999_999_000);
}
#[test]
fn test_total_nanoseconds_to_timespec() -> Result<(), TzError> {
assert!(matches!(total_nanoseconds_to_timespec(1_000_001_000), Ok((1, 1000))));
assert!(matches!(total_nanoseconds_to_timespec(1000), Ok((0, 1000))));
assert!(matches!(total_nanoseconds_to_timespec(-999_999_000), Ok((-1, 1000))));
assert!(matches!(total_nanoseconds_to_timespec(-1_999_999_000), Ok((-2, 1000))));
assert!(matches!(total_nanoseconds_to_timespec(i128::MAX), Err(TzError::OutOfRange)));
assert!(matches!(total_nanoseconds_to_timespec(i128::MIN), Err(TzError::OutOfRange)));
let min_total_nanoseconds = -9223372036854775808000000000;
let max_total_nanoseconds = 9223372036854775807999999999;
assert!(matches!(total_nanoseconds_to_timespec(min_total_nanoseconds), Ok((i64::MIN, 0))));
assert!(matches!(total_nanoseconds_to_timespec(max_total_nanoseconds), Ok((i64::MAX, 999999999))));
assert!(matches!(total_nanoseconds_to_timespec(min_total_nanoseconds - 1), Err(TzError::OutOfRange)));
assert!(matches!(total_nanoseconds_to_timespec(max_total_nanoseconds + 1), Err(TzError::OutOfRange)));
Ok(())
}
#[test]
fn test_duration_between() -> Result<(), TzError> {
assert_eq!(duration_between(1_234_567_890, 1_234_567_890), Ok(Duration::ZERO));
assert_eq!(duration_between(-1_000_001_000, 1_000_001_000), Ok(Duration::new(2, 2000)));
assert_eq!(duration_between(1_000_001_000, -1_000_001_000), Err(Duration::new(2, 2000)));
assert_eq!(
duration_between(UtcDateTime::MIN.total_nanoseconds(), UtcDateTime::MAX.total_nanoseconds()),
Ok(Duration::new(135536076801503999, 999999999))
);
Ok(())
}
#[test]
fn test_const() -> Result<(), TzError> {
use crate::timezone::{AlternateTime, LeapSecond, MonthWeekDay, RuleDay, Transition, TransitionRule};
macro_rules! unwrap {
($x:expr) => {
match $x {
Ok(x) => x,
Err(_) => panic!(),
}
};
}
const TIME_ZONE_REF: TimeZoneRef<'static> = unwrap!(TimeZoneRef::new(
&[
Transition::new(-2334101314, 1),
Transition::new(-1157283000, 2),
Transition::new(-1155436200, 1),
Transition::new(-880198200, 3),
Transition::new(-769395600, 4),
Transition::new(-765376200, 1),
Transition::new(-712150200, 5),
],
const {
&[
unwrap!(LocalTimeType::new(-37886, false, Some(b"LMT"))),
unwrap!(LocalTimeType::new(-37800, false, Some(b"HST"))),
unwrap!(LocalTimeType::new(-34200, true, Some(b"HDT"))),
unwrap!(LocalTimeType::new(-34200, true, Some(b"HWT"))),
unwrap!(LocalTimeType::new(-34200, true, Some(b"HPT"))),
unwrap!(LocalTimeType::new(-36000, false, Some(b"HST"))),
]
},
&[
LeapSecond::new(78796800, 1),
LeapSecond::new(94694401, 2),
LeapSecond::new(126230402, 3),
LeapSecond::new(157766403, 4),
LeapSecond::new(189302404, 5),
LeapSecond::new(220924805, 6),
],
const {
&Some(TransitionRule::Alternate(unwrap!(AlternateTime::new(
unwrap!(LocalTimeType::new(-36000, false, Some(b"HST"))),
unwrap!(LocalTimeType::new(-34200, true, Some(b"HPT"))),
RuleDay::MonthWeekDay(unwrap!(MonthWeekDay::new(10, 5, 0))),
93600,
RuleDay::MonthWeekDay(unwrap!(MonthWeekDay::new(3, 4, 4))),
7200,
))))
},
));
const UTC: TimeZoneRef<'static> = TimeZoneRef::utc();
const UNIX_EPOCH: UtcDateTime = unwrap!(UtcDateTime::from_timespec(0, 0));
const UTC_DATE_TIME: UtcDateTime = unwrap!(UtcDateTime::new(2000, 1, 1, 0, 0, 0, 1000));
const DATE_TIME: DateTime = unwrap!(DateTime::new(2000, 1, 1, 1, 0, 0, 1000, unwrap!(LocalTimeType::with_ut_offset(3600))));
const DATE_TIME_1: DateTime = unwrap!(UTC_DATE_TIME.project(TIME_ZONE_REF));
const DATE_TIME_2: DateTime = unwrap!(DATE_TIME_1.project(UTC));
const LOCAL_TIME_TYPE_1: &LocalTimeType = DATE_TIME_1.local_time_type();
const LOCAL_TIME_TYPE_2: &LocalTimeType = DATE_TIME_2.local_time_type();
assert_eq!(UNIX_EPOCH.unix_time(), 0);
assert_eq!(DATE_TIME.unix_time(), UTC_DATE_TIME.unix_time());
assert_eq!(DATE_TIME_2.unix_time(), UTC_DATE_TIME.unix_time());
assert_eq!(DATE_TIME_2.nanoseconds(), UTC_DATE_TIME.nanoseconds());
let date_time = UTC_DATE_TIME.project(TIME_ZONE_REF)?;
assert_eq!(date_time.local_time_type().time_zone_designation(), LOCAL_TIME_TYPE_1.time_zone_designation());
let date_time_1 = DateTime::from_timespec(UTC_DATE_TIME.unix_time(), 1000, TIME_ZONE_REF)?;
let date_time_2 = date_time_1.project(UTC)?;
assert_eq!(date_time, DATE_TIME_1);
assert_eq!(date_time_1, DATE_TIME_1);
assert_eq!(date_time_2, DATE_TIME_2);
let local_time_type_1 = date_time_1.local_time_type();
let local_time_type_2 = date_time_2.local_time_type();
assert_eq!(local_time_type_1.ut_offset(), LOCAL_TIME_TYPE_1.ut_offset());
assert_eq!(local_time_type_1.is_dst(), LOCAL_TIME_TYPE_1.is_dst());
assert_eq!(local_time_type_1.time_zone_designation(), LOCAL_TIME_TYPE_1.time_zone_designation());
assert_eq!(local_time_type_2.ut_offset(), LOCAL_TIME_TYPE_2.ut_offset());
assert_eq!(local_time_type_2.is_dst(), LOCAL_TIME_TYPE_2.is_dst());
assert_eq!(local_time_type_2.time_zone_designation(), LOCAL_TIME_TYPE_2.time_zone_designation());
Ok(())
}
}