use std::cmp::{PartialEq, Ordering};
use std::fmt;
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use std::ops::{Add, Sub};
use std::convert::TryFrom;
#[cfg(feature = "serde")]
use serde::{Serialize, Deserialize};
use crate::calendar::{Calendar, Julian, Gregorian};
use crate::duration::Duration;
use crate::error::Error;
use crate::standard::Standard;
#[derive(Clone, Copy)] #[cfg_attr(feature ="serde", derive(Serialize, Deserialize))]
pub struct DateTime<C: Calendar, S: Standard> {
packed: u64,
attos: u64,
_cal: PhantomData<C>,
_std: PhantomData<S>
}
const YEAR_BITS: u64 = 0xFFFF_FFFF_0000_0000;
const SECOND_BITS: u64 = 0x0000_0000_FC00_0000;
const MINUTE_BITS: u64 = 0x0000_0000_03F0_0000;
const HOUR_BITS: u64 = 0x0000_0000_000F_8000;
const DAY0_BITS: u64 = 0x0000_0000_0000_7C00;
const _RESERVED_BITS: u64 = 0x0000_0000_0000_03F0;
const MONTH0_BITS: u64 = 0x0000_0000_0000_000F;
const YEAR_OFFSET: usize = 32;
const SECOND_OFFSET: usize = 26;
const MINUTE_OFFSET: usize = 20;
const HOUR_OFFSET: usize = 15;
const DAY0_OFFSET: usize = 10;
const MONTH0_OFFSET: usize = 0;
#[inline]
fn pack(packed: &mut u64, bits: u64, offset: usize, value: u64) {
*packed &= !bits; *packed |= value<<offset; }
#[inline]
fn pack_without_clearing(packed: &mut u64, offset: usize, value: u64) {
*packed |= value<<offset; }
#[inline]
const fn unpack(packed: u64, bits: u64, offset: usize) -> u64 {
(packed & bits) >> offset
}
impl<C: Calendar, S: Standard> DateTime<C, S> {
#[allow(clippy::cast_sign_loss)]
#[allow(clippy::cast_lossless)]
#[must_use]
pub unsafe fn new_unchecked(year: i32, month: u8, day: u8,
hour: u8, minute: u8, second: u8,
attosecond: u64)
-> Self
{
let mut packed: u64 = 0;
pack_without_clearing(&mut packed, YEAR_OFFSET, year as u64);
pack_without_clearing(&mut packed, SECOND_OFFSET, second as u64);
pack_without_clearing(&mut packed, MINUTE_OFFSET, minute as u64);
pack_without_clearing(&mut packed, HOUR_OFFSET, hour as u64);
pack_without_clearing(&mut packed, DAY0_OFFSET, (day-1) as u64);
pack_without_clearing(&mut packed, MONTH0_OFFSET, (month-1) as u64);
Self {
packed,
attos: attosecond,
_cal: PhantomData,
_std: PhantomData,
}
}
#[allow(clippy::manual_range_contains)]
pub fn new(year: i32, month: u8, day: u8,
hour: u8, minute: u8, second: u8,
attosecond: u64)
-> Result<Self, Error>
{
if month<1 || month>12 { return Err(Error::RangeError); }
if day<1 || day>C::month_days(month, year) { return Err(Error::RangeError); }
if hour>23 { return Err(Error::RangeError); }
if minute>59 { return Err(Error::RangeError); }
if second>60 { return Err(Error::RangeError); }
if attosecond>999_999_999_999_999_999 { return Err(Error::RangeError); }
Ok(unsafe {
Self::new_unchecked(year, month, day,
hour, minute, second, attosecond)
})
}
#[allow(clippy::manual_range_contains)]
pub fn new_bc(bc_year: i32, month: u8, day: u8,
hour: u8, minute: u8, second: u8,
attosecond: u64)
-> Result<Self, Error>
{
let year = 1-bc_year;
Self::new(year, month, day, hour, minute, second, attosecond)
}
#[must_use]
#[allow(clippy::cast_sign_loss)]
#[allow(clippy::cast_possible_truncation)]
pub fn new_abnormal(mut year: i32, month: i64, day: i64,
mut hour: i64, mut minute: i64, mut second: i64,
mut attosecond: i64)
-> Self
{
use crate::divmod_i64;
let mut month0 = month - 1;
let mut day0 = day - 1;
let (div, modulus) = divmod_i64(attosecond, 1_000_000_000_000_000_000);
second += div;
attosecond = modulus;
assert!(attosecond >= 0);
assert!(attosecond < 1_000_000_000_000_000_000);
let (div, modulus) = divmod_i64( second, 60 );
minute += div;
second = modulus;
assert!(second >= 0);
assert!(second < 60);
let (div, modulus) = divmod_i64( minute, 60 );
hour += div;
minute = modulus;
assert!(minute >= 0);
assert!(minute < 60);
let (div, modulus) = divmod_i64( hour, 24 );
day0 += div;
hour = modulus;
assert!(hour >= 0);
assert!(hour < 24);
let (div, modulus) = divmod_i64( month0, 12 );
year += div as i32;
month0 = modulus;
assert!(month0 >= 0);
assert!(month0 < 12);
let dn = C::day_number(year,
(month0+1).try_into().unwrap(),
day0+1).unwrap();
let (y,m,d) = C::from_day_number(dn).unwrap();
unsafe {
Self::new_unchecked(y, m, d,
hour as u8, minute as u8, second as u8, attosecond as u64)
}
}
pub fn from_day_number(day_number: i64) -> Result<Self, Error> {
let (year, month, day) = C::from_day_number(day_number)?;
unsafe { Ok(Self::new_unchecked(year, month, day, 0, 0, 0, 0)) }
}
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_precision_loss)]
#[allow(clippy::cast_sign_loss)]
pub fn from_day_number_and_fraction(day_number: i64, day_fraction: f64)
-> Result<Self, Error>
{
if day_fraction<0.0 { return Err(Error::RangeError); }
if day_fraction>=1.0 { return Err(Error::RangeError); }
let (year, month, day) = C::from_day_number(day_number)?;
let (hour, min, sec, atto) = {
const FACTOR: i64 = 100_000_000_000_000;
let parts = (((FACTOR * 86400) as f64) * day_fraction) as i64;
let mut s = parts / FACTOR;
let atto = parts % FACTOR * 10000;
let mut m = s / 60;
s %= 60;
assert!(s<60);
let h = m / 60;
assert!(h<24);
m %= 60;
assert!(m<60);
(h as u8,
m as u8,
s as u8,
atto as u64)
};
Ok(unsafe {
Self::new_unchecked(year, month, day, hour, min, sec, atto)
})
}
#[must_use]
pub fn from_duration_from_epoch(duration: Duration) -> Self {
Self::new_abnormal(1, 1, 1, 0, 0, duration.secs, duration.attos)
}
#[allow(clippy::cast_possible_truncation)]
#[must_use]
#[inline]
pub fn year(&self) -> i32 {
unpack(self.packed, YEAR_BITS, YEAR_OFFSET) as i32
}
#[allow(clippy::cast_possible_truncation)]
#[must_use]
#[inline]
pub fn year_bc(&self) -> i32 {
1 - self.year()
}
#[allow(clippy::cast_possible_truncation)]
#[must_use]
#[inline]
pub fn month(&self) -> u8 {
unpack(self.packed, MONTH0_BITS, MONTH0_OFFSET) as u8 + 1
}
#[allow(clippy::cast_possible_truncation)]
#[must_use]
#[inline]
pub fn month0(&self) -> u8 {
unpack(self.packed, MONTH0_BITS, MONTH0_OFFSET) as u8
}
#[allow(clippy::cast_possible_truncation)]
#[must_use]
#[inline]
pub fn day(&self) -> u8 {
unpack(self.packed, DAY0_BITS, DAY0_OFFSET) as u8 + 1
}
#[allow(clippy::cast_possible_truncation)]
#[must_use]
#[inline]
pub fn day0(&self) -> u8 {
unpack(self.packed, DAY0_BITS, DAY0_OFFSET) as u8
}
#[allow(clippy::cast_possible_truncation)]
#[must_use]
#[inline]
pub fn hour(&self) -> u8 {
unpack(self.packed, HOUR_BITS, HOUR_OFFSET) as u8
}
#[allow(clippy::cast_possible_truncation)]
#[must_use]
#[inline]
pub fn minute(&self) -> u8 {
unpack(self.packed, MINUTE_BITS, MINUTE_OFFSET) as u8
}
#[allow(clippy::cast_possible_truncation)]
#[must_use]
#[inline]
pub fn second(&self) -> u8 {
unpack(self.packed, SECOND_BITS, SECOND_OFFSET) as u8
}
#[must_use]
#[inline]
pub fn attosecond(&self) -> u64 {
self.attos
}
#[must_use]
#[inline]
pub fn date(&self) -> (i32, u8, u8) {
(self.year(), self.month(), self.day())
}
#[must_use]
#[inline]
pub fn time(&self) -> (u8, u8, u8, u64) {
(self.hour(), self.minute(), self.second(), self.attosecond())
}
#[inline]
#[allow(clippy::cast_sign_loss)]
pub fn set_year(&mut self, year: i32) {
pack(&mut self.packed, YEAR_BITS, YEAR_OFFSET, year as u64);
}
#[inline]
#[allow(clippy::cast_sign_loss)]
pub fn set_year_bc(&mut self, year_bc: i32) {
let year = 1 - year_bc;
pack(&mut self.packed, YEAR_BITS, YEAR_OFFSET, year as u64);
}
#[allow(clippy::manual_range_contains)]
pub fn set_month(&mut self, month: u8) -> Result<(), Error> {
if month<1 || month>12 {
return Err(Error::RangeError);
}
if self.day() > C::month_days(month, self.year()) {
return Err(Error::RangeError);
}
pack(&mut self.packed, MONTH0_BITS, MONTH0_OFFSET, u64::from(month-1));
Ok(())
}
pub fn set_day(&mut self, day: u8) -> Result<(), Error> {
if day<1 || day> C::month_days(self.month(), self.year()) {
return Err(Error::RangeError);
}
pack(&mut self.packed, DAY0_BITS, DAY0_OFFSET, u64::from(day-1));
Ok(())
}
pub fn set_hour(&mut self, hour: u8) -> Result<(), Error> {
if hour>23 {
return Err(Error::RangeError);
}
pack(&mut self.packed, HOUR_BITS, HOUR_OFFSET, u64::from(hour));
Ok(())
}
pub fn set_minute(&mut self, minute: u8) -> Result<(), Error> {
if minute>59 {
return Err(Error::RangeError);
}
pack(&mut self.packed, MINUTE_BITS, MINUTE_OFFSET, u64::from(minute));
Ok(())
}
pub fn set_second(&mut self, second: u8) -> Result<(), Error> {
if second>60 {
return Err(Error::RangeError);
}
pack(&mut self.packed, SECOND_BITS, SECOND_OFFSET, u64::from(second));
Ok(())
}
pub fn set_attosecond(&mut self, attosecond: u64) -> Result<(), Error> {
if attosecond>1_000_000_000_000_000_000 {
return Err(Error::RangeError);
}
self.attos = attosecond;
Ok(())
}
pub fn set_date(&mut self, date: (i32, u8, u8)) -> Result<(), Error> {
self.set_year(date.0);
self.set_month(date.1)?;
self.set_day(date.2)?;
Ok(())
}
pub fn set_time(&mut self, date: (u8, u8, u8, u64)) -> Result<(), Error> {
self.set_hour(date.0)?;
self.set_minute(date.1)?;
self.set_second(date.2)?;
self.set_attosecond(date.3)?;
Ok(())
}
#[must_use]
pub fn day_number(&self) -> i64 {
C::day_number(self.year(), self.month(), i64::from(self.day())).unwrap()
}
#[allow(clippy::cast_precision_loss)]
#[must_use]
pub fn day_fraction(&self) -> f64 {
const FACTOR: u64 = 100_000_000_000_000;
( u64::from(self.hour()) * 3600 * FACTOR
+ u64::from(self.minute()) * 60 * FACTOR
+ u64::from(self.second()) * FACTOR
+ (self.attosecond()/10000) as u64)
as f64 / 8_640_000_000_000_000_000.
}
#[must_use]
pub fn duration_from_epoch(&self) -> Duration {
let dn = self.day_number();
let seconds = dn * 86400
+ i64::from(self.hour()) * 3600
+ i64::from(self.minute()) * 60
+ i64::from(self.second());
Duration::new(seconds, i64::try_from(self.attosecond()).unwrap())
}
}
impl<C: Calendar, S: Standard> fmt::Debug for DateTime<C, S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:018} {} {}",
self.year(), self.month(), self.day(), self.hour(), self.minute(), self.second(),
self.attosecond(), C::name(), S::abbrev())
}
}
impl<C: Calendar, S: Standard> fmt::Display for DateTime<C, S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:018} {} {}",
self.year(), self.month(), self.day(), self.hour(), self.minute(), self.second(),
self.attosecond(), C::name(), S::abbrev())
}
}
impl<C: Calendar, S: Standard> Add<Duration> for DateTime<C, S> {
type Output = Self;
#[allow(clippy::cast_possible_wrap)]
fn add(self, rhs: Duration) -> Self {
Self::new_abnormal(self.year(), i64::from(self.month()), i64::from(self.day()),
i64::from(self.hour()), i64::from(self.minute()),
i64::from(self.second()) + rhs.seconds_part(),
self.attosecond() as i64 + rhs.attos_part() as i64)
}
}
impl<C: Calendar, S: Standard> Sub<Duration> for DateTime<C, S> {
type Output = Self;
#[allow(clippy::cast_possible_wrap)]
fn sub(self, rhs: Duration) -> Self {
Self::new_abnormal(self.year(), i64::from(self.month()), i64::from(self.day()),
i64::from(self.hour()), i64::from(self.minute()),
i64::from(self.second()) - rhs.seconds_part(),
self.attosecond() as i64 - rhs.attos_part() as i64)
}
}
impl<C: Calendar, S: Standard> Sub for DateTime<C, S> {
type Output = Duration;
#[allow(clippy::cast_possible_wrap)]
fn sub(self, other: Self) -> Duration {
let secs =
(self.day_number() - other.day_number()) * 86400
+ (i64::from(self.hour()) - i64::from(other.hour())) * 3600
+ (i64::from(self.minute()) - i64::from(other.minute())) * 60
+ (i64::from(self.second()) - i64::from(other.second()));
let attos = self.attosecond() as i64 - other.attosecond() as i64;
Duration::new(secs, attos) }
}
impl<C: Calendar, S: Standard> PartialEq<Self> for DateTime<C, S> {
fn eq(&self, other: &Self) -> bool {
self.packed == other.packed && self.attos == other.attos
}
}
impl<C: Calendar, S: Standard> Eq for DateTime<C, S> { }
impl<C: Calendar, S: Standard> Ord for DateTime<C, S> {
fn cmp(&self, other: &Self) -> Ordering {
if self.year() != other.year() { return self.year().cmp(&other.year()) }
if self.month() != other.month() { return self.month().cmp(&other.month()) }
if self.day() != other.day() { return self.day().cmp(&other.day()) }
if self.hour() != other.hour() { return self.hour().cmp(&other.hour()) }
if self.minute() != other.minute() { return self.minute().cmp(&other.minute()) }
if self.second() != other.second() { return self.second().cmp(&other.second()) }
self.attosecond().cmp(&other.attosecond())
}
}
impl<C: Calendar, S: Standard> PartialOrd<Self> for DateTime<C, S> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<C: Calendar, S: Standard> Hash for DateTime<C, S> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.packed.hash(state);
self.attos.hash(state);
}
}
unsafe impl<C: Calendar, S: Standard> Send for DateTime<C, S> {}
impl<S: Standard> TryFrom<DateTime<Gregorian, S>> for DateTime<Julian, S> {
type Error = Error;
fn try_from(input: DateTime<Gregorian, S>) -> Result<Self, Self::Error> {
let dn = input.day_number() + 2;
let mut r = Self::from_day_number(dn)?;
r.set_time(input.time())?;
Ok(r)
}
}
impl<S: Standard> TryFrom<DateTime<Julian, S>> for DateTime<Gregorian, S> {
type Error = Error;
fn try_from(input: DateTime<Julian, S>) -> Result<Self, Self::Error> {
let dn = input.day_number() - 2;
let mut r = Self::from_day_number(dn)?;
r.set_time(input.time())?;
Ok(r)
}
}
#[cfg(test)]
mod test {
use super::DateTime;
use crate::calendar::{Gregorian, Julian};
use crate::duration::Duration;
use crate::standard::Tt;
#[test]
fn test_range_errors() {
crate::setup_logging();
assert!(DateTime::<Gregorian, Tt>::new(2000, 0, 31, 0, 0, 0, 0).is_err());
assert!(DateTime::<Gregorian, Tt>::new(2000, 13, 31, 0, 0, 0, 0).is_err());
assert!(DateTime::<Gregorian, Tt>::new(2000, 6, 0, 0, 0, 0, 0).is_err());
assert!(DateTime::<Gregorian, Tt>::new(2000, 6, 31, 0, 0, 0, 0).is_err());
assert!(DateTime::<Gregorian, Tt>::new(2000, 7, 32, 0, 0, 0, 0).is_err());
assert!(DateTime::<Gregorian, Tt>::new(2003, 2, 29, 0, 0, 0, 0).is_err());
assert!(DateTime::<Gregorian, Tt>::new(2004, 2, 29, 24, 0, 0, 0).is_err());
assert!(DateTime::<Gregorian, Tt>::new(2004, 2, 29, 0, 60, 0, 0).is_err());
assert!(DateTime::<Gregorian, Tt>::new(2004, 2, 29, 0, 0, 61, 0).is_err());
assert!(DateTime::<Gregorian, Tt>::new(2004, 2, 29, 0, 0, 0, 1_000_000_000_000_000_000).is_err());
let _ = DateTime::<Gregorian, Tt>::new_abnormal(0, 1, 31, 0, 0, 0, 0);
let _ = DateTime::<Gregorian, Tt>::new_abnormal(2000, 0, 31, 0, 0, 0, 0);
let _ = DateTime::<Gregorian, Tt>::new_abnormal(2000, 13, 31, 0, 0, 0, 0);
let _ = DateTime::<Gregorian, Tt>::new_abnormal(2000, 6, 0, 0, 0, 0, 0);
let _ = DateTime::<Gregorian, Tt>::new_abnormal(2000, 6, 31, 0, 0, 0, 0);
let _ = DateTime::<Gregorian, Tt>::new_abnormal(2000, 7, 32, 0, 0, 0, 0);
let _ = DateTime::<Gregorian, Tt>::new_abnormal(2003, 2, 29, 0, 0, 0, 0);
let _ = DateTime::<Gregorian, Tt>::new_abnormal(2004, 2, 29, 24, 0, 0, 0);
let _ = DateTime::<Gregorian, Tt>::new_abnormal(2004, 2, 29, 0, 60, 0, 0);
let _ = DateTime::<Gregorian, Tt>::new_abnormal(2004, 2, 29, 0, 0, 61, 0);
let _ = DateTime::<Gregorian, Tt>::new_abnormal(2004, 2, 29, 0, 0, 0, 1_000_000_000_000_000_000);
}
#[test]
fn test_normalize() {
crate::setup_logging();
let dt = DateTime::<Gregorian, Tt>::new_abnormal(1900,1,1,0,0,2272060800,0);
assert_eq!(dt.year(), 1972);
assert_eq!(dt.month(), 1);
assert_eq!(dt.day(), 1);
assert_eq!(dt.hour(), 0);
assert_eq!(dt.minute(), 0);
assert_eq!(dt.second(), 0);
assert_eq!(dt.attosecond(), 0);
let dt = DateTime::<Gregorian, Tt>::new_abnormal(1900,1,1,0,0,2303683200,0);
assert_eq!(dt.year(), 1973);
assert_eq!(dt.month(), 1);
assert_eq!(dt.day(), 1);
assert_eq!(dt.hour(), 0);
assert_eq!(dt.minute(), 0);
assert_eq!(dt.second(), 0);
assert_eq!(dt.attosecond(), 0);
let dt = DateTime::<Gregorian, Tt>::new_abnormal(1972, 2, 29, 25, 0, 0, 0);
assert_eq!(dt.month(), 3); assert_eq!(dt.day(), 1); assert_eq!(dt.hour(), 1);
let dt = DateTime::<Gregorian, Tt>::new_abnormal(2000, 1-11, 1+(365-31), -12, 60*12, 0, 0);
assert_eq!(dt.year(), 2000);
assert_eq!(dt.month(), 1);
assert_eq!(dt.day(), 1);
assert_eq!(dt.hour(), 0);
assert_eq!(dt.minute(), 0);
assert_eq!(dt.second(), 0);
assert_eq!(dt.attosecond(), 0);
let dt = DateTime::<Gregorian, Tt>::new_abnormal(2000, 1-60, 1+(365*4 + 366), 0, 0, 0, 0);
assert_eq!(dt.year(), 2000);
assert_eq!(dt.month(), 1);
assert_eq!(dt.day(), 1);
let dt = DateTime::<Gregorian, Tt>::new_abnormal(1970, 12, 31, 25, 0, 0, 0);
assert_eq!(dt.year(), 1971);
assert_eq!(dt.month(), 1);
assert_eq!(dt.day(), 1);
assert_eq!(dt.hour(), 1);
}
#[test]
fn test_day_number() {
crate::setup_logging();
let dt = DateTime::<Gregorian, Tt>::new(1,1,1,0,0,0,0).unwrap(); assert_eq!(dt.day_number(), 0);
let dt2 = DateTime::<Gregorian, Tt>::from_day_number(dt.day_number()).unwrap();
assert_eq!(dt,dt2);
let dt = DateTime::<Gregorian, Tt>::new(2000,1,1,0,0,0,0).unwrap();
assert_eq!(dt.day_number(), 730119);
let dt2 = DateTime::<Gregorian, Tt>::from_day_number(dt.day_number()).unwrap();
assert_eq!(dt,dt2);
assert_eq!(dt2.day_number(), dt.day_number())
}
#[test]
fn test_day_fraction() {
crate::setup_logging();
use float_cmp::ApproxEq;
let g1 = DateTime::<Gregorian, Tt>::new(2000, 1, 1, 12, 0, 0, 0).unwrap();
assert!( g1.day_fraction().approx_eq(0.5, (0.0, 1) ));
let g2 = DateTime::<Gregorian, Tt>::new(2000, 1, 1, 18, 0, 0, 0).unwrap();
assert!( g2.day_fraction().approx_eq(0.75, (0.0, 1) ));
let g3 = DateTime::<Gregorian, Tt>::new(2000, 1, 1, 0, 0, 1, 0).unwrap();
assert!( g3.day_fraction().approx_eq(1./86400., (0.0, 1) ));
let g4 = DateTime::<Gregorian, Tt>::from_day_number_and_fraction(g1.day_number(), 0.75).unwrap();
assert_eq!(g4, g2);
let g4 = DateTime::<Gregorian, Tt>::from_day_number_and_fraction(g1.day_number(), 19./97.).unwrap();
assert!(g4.day_fraction().approx_eq(19./97., (0.0, 1) ));
}
#[test]
fn test_extractors() {
crate::setup_logging();
let g = DateTime::<Gregorian, Tt>::new(1965, 3, 7, 14, 29, 42, 500_000_000_000_000_000).unwrap();
assert_eq!( g.year(), 1965 );
assert_eq!( g.month(), 3 );
assert_eq!( g.month0(), 2 );
assert_eq!( g.day(), 7 );
assert_eq!( g.day0(), 6 );
assert_eq!( g.hour(), 14);
assert_eq!( g.minute(), 29);
assert_eq!( g.second(), 42);
assert_eq!( g.attosecond(), 500_000_000_000_000_000);
}
#[test]
fn test_setters() {
crate::setup_logging();
let mut g = DateTime::<Gregorian, Tt>::new(1965, 3, 7, 14, 29, 42, 500_000_000_000_000_000).unwrap();
g.set_year(1921);
assert_eq!( g.year(), 1921 );
g.set_month(1).unwrap();
assert_eq!( g.month(), 1 );
g.set_day(17).unwrap();
assert_eq!( g.day(), 17 );
g.set_hour(3).unwrap();
assert_eq!( g.hour(), 3 );
g.set_minute(55).unwrap();
assert_eq!( g.minute(), 55 );
g.set_second(51).unwrap();
assert_eq!( g.second(), 51 );
g.set_attosecond(123_456_789_012_345_678).unwrap();
assert_eq!( g.attosecond(), 123_456_789_012_345_678 );
let h = DateTime::<Gregorian, Tt>::new(1921, 1, 17, 3, 55, 51, 123_456_789_012_345_678).unwrap();
assert_eq!(g, h);
let mut g = DateTime::<Gregorian, Tt>::new(1997, 3, 30, 17, 24, 06, 2340897).unwrap();
assert!(g.set_month(2).is_err());
assert_eq!(g.month(), 3);
assert!(g.set_day(28).is_ok());
assert!(g.set_month(2).is_ok());
assert_eq!(g.month(), 2);
assert_eq!(g.day(), 28);
}
#[test]
fn test_comparison() {
crate::setup_logging();
let g = DateTime::<Gregorian, Tt>::new(1965, 3, 7, 14, 29, 42, 500_000_000_000_000_000).unwrap();
let h = DateTime::<Gregorian, Tt>::new(1966, 1, 17, 3, 55, 51, 123_456_789_012_345_678).unwrap();
let i = DateTime::<Gregorian, Tt>::new(1966, 3, 7, 14, 29, 42, 500_000_000_000_000_000).unwrap();
let j = DateTime::<Gregorian, Tt>::new(1966, 3, 7, 14, 29, 42, 500_000_000_000_000_000).unwrap();
assert!( g < h );
assert!( h < i );
assert!( i == j);
}
#[test]
fn test_math() {
crate::setup_logging();
let g = DateTime::<Gregorian, Tt>::new(1996, 3, 2, 0, 0, 0, 50).unwrap();
let week_less_150ns = Duration::new(86400 * 7, 150);
let earlier = g - week_less_150ns;
assert_eq!(earlier.year(), 1996);
assert_eq!(earlier.month(), 2);
assert_eq!(earlier.day(), 23);
assert_eq!(earlier.hour(), 23);
assert_eq!(earlier.minute(), 59);
assert_eq!(earlier.second(), 59);
assert_eq!(earlier.attosecond(), 1_000_000_000_000_000_000 - 100);
let g1 = DateTime::<Gregorian, Tt>::new(2000, 1, 1, 0, 0, 0, 0).unwrap();
let g2 = DateTime::<Gregorian, Tt>::new(2001, 2, 2, 1, 3, 5, 11).unwrap();
let diff = g2 - g1;
assert_eq!(diff.seconds_part(),
366*86400 + 31*86400 + 1*86400 + 1*3600 + 3*60 + 5);
assert_eq!(diff.attos_part(), 11);
}
#[test]
fn test_print_extremes() {
crate::setup_logging();
let min = DateTime::<Gregorian, Tt>::new(std::i32::MIN, 1, 1, 0, 0, 0, 0).unwrap();
info!("Min gregorian: {}", min);
let max = DateTime::<Gregorian, Tt>::new(std::i32::MAX, 12, 31, 23, 59, 59, 999_999_999_999_999_999).unwrap();
info!("Max gregorian: {}", max);
}
#[test]
fn test_bc_day_numbers() {
crate::setup_logging();
let mar1 = DateTime::<Gregorian, Tt>::new(0,3,1,0,0,0,0).unwrap();
let feb29 = DateTime::<Gregorian, Tt>::new(0,2,29,0,0,0,0).unwrap();
let feb28 = DateTime::<Gregorian, Tt>::new(0,2,28,0,0,0,0).unwrap();
assert_eq!(mar1.day_number(), -306);
assert_eq!(feb29.day_number(), -307);
assert_eq!(feb28.day_number(), -308);
let mar1x = DateTime::<Gregorian, Tt>::from_day_number(-306).unwrap();
let feb29x = DateTime::<Gregorian, Tt>::from_day_number(-307).unwrap();
let feb28x = DateTime::<Gregorian, Tt>::from_day_number(-308).unwrap();
assert_eq!(mar1, mar1x);
assert_eq!(feb29, feb29x);
assert_eq!(feb28, feb28x);
}
#[test]
fn test_convert_calendar() {
crate::setup_logging();
let j = DateTime::<Julian, Tt>::new(1582,10,5,0,0,0,0).unwrap();
let g = DateTime::<Gregorian, Tt>::new(1582,10,15,0,0,0,0).unwrap();
let j2: DateTime<Julian, Tt> = TryFrom::try_from(g).unwrap();
assert_eq!(j, j2);
let g2: DateTime<Gregorian, Tt> = TryFrom::try_from(j).unwrap();
assert_eq!(g, g2);
let j = DateTime::<Julian, Tt>::new(1582,10,4,0,0,0,0).unwrap();
let g = DateTime::<Gregorian, Tt>::new(1582,10,14,0,0,0,0).unwrap();
let j2: DateTime<Julian, Tt> = TryFrom::try_from(g).unwrap();
assert_eq!(j, j2);
let g2: DateTime<Gregorian, Tt> = TryFrom::try_from(j).unwrap();
assert_eq!(g, g2);
let j = DateTime::<Julian, Tt>::new(-4713,1,1,0,0,0,0).unwrap();
let g = DateTime::<Gregorian, Tt>::new(-4714,11,24,0,0,0,0).unwrap();
let j2: DateTime<Julian, Tt> = TryFrom::try_from(g).unwrap();
assert_eq!(j, j2);
let g2: DateTime<Gregorian, Tt> = TryFrom::try_from(j).unwrap();
assert_eq!(g, g2);
let j = DateTime::<Julian, Tt>::new(1,1,3,0,0,0,0).unwrap();
let g = DateTime::<Gregorian, Tt>::new(1,1,1,0,0,0,0).unwrap();
let j2: DateTime<Julian, Tt> = TryFrom::try_from(g).unwrap();
assert_eq!(j, j2);
let g2: DateTime<Gregorian, Tt> = TryFrom::try_from(j).unwrap();
assert_eq!(g, g2);
let j = DateTime::<Julian, Tt>::new(1,1,1,0,0,0,0).unwrap();
let g = DateTime::<Gregorian, Tt>::new(0,12,30,0,0,0,0).unwrap();
let j2: DateTime<Julian, Tt> = TryFrom::try_from(g).unwrap();
assert_eq!(j, j2);
let g2: DateTime<Gregorian, Tt> = TryFrom::try_from(j).unwrap();
assert_eq!(g, g2);
}
#[test]
fn test_epoch_duration() {
crate::setup_logging();
let g = DateTime::<Gregorian, Tt>::new(1582,10,14,0,0,0,0).unwrap();
let h = DateTime::<Gregorian, Tt>::from_duration_from_epoch(g.duration_from_epoch());
assert_eq!(g, h);
let g = DateTime::<Julian, Tt>::new(1582,10,14,11,0,5,130).unwrap();
let h = DateTime::<Julian, Tt>::from_duration_from_epoch(g.duration_from_epoch());
assert_eq!(g, h);
}
}