#![cfg(test)]
#![cfg(not(windows))]
use {
core::{
cmp::Ordering,
ptr,
time::Duration,
},
crate::{DAY, GmtOffset, HOUR, MINUTE, Month, Result as CrateResult, Time, Weekday, month},
};
const UNIX_YEAR_DELTA: i32 = 1900;
#[test]
fn test_consts() {
assert_eq!(UNIX_YEAR_DELTA, 1900);
assert_eq!(super::ONE_YEAR, i64::try_from(DAY.checked_mul(365).unwrap()).unwrap());
assert_eq!(super::ONE_LEAP_YEAR, i64::try_from(DAY.checked_mul(366).unwrap()).unwrap());
{
let mut all_seconds = 0_i64;
for y in 0..400 {
all_seconds = all_seconds.checked_add(if super::is_leap_year(y) { super::ONE_LEAP_YEAR } else { super::ONE_YEAR }).unwrap();
}
assert_eq!(all_seconds, super::SECONDS_OF_400_YEARS);
}
{
let mut all_seconds = 0_i64;
for y in 0..1970 {
all_seconds = all_seconds.checked_add(if super::is_leap_year(y) { super::ONE_LEAP_YEAR } else { super::ONE_YEAR }).unwrap();
}
assert_eq!(all_seconds, super::SECONDS_OF_1970_YEARS);
}
}
#[test]
fn test_trying_times_into_unix_seconds() -> CrateResult<()> {
fn try_time_into_unix_seconds_using_libc(time: &Time) -> CrateResult<i64> {
let time = time.try_into_utc()?;
let mut tm = libc::tm {
tm_year: match time.year.checked_sub(UNIX_YEAR_DELTA.into()).map(|y| i32::try_from(y)) {
Some(Ok(tm_year)) => tm_year,
_ => return Err(err!("'{year}' cannot be converted into Unix year", year=time.year)),
},
tm_mon: time.month.to_unix(),
tm_mday: time.day.into(),
tm_hour: time.hour.into(),
tm_min: time.minute.into(),
tm_sec: time.second.into(),
tm_wday: 0, tm_yday: 0, tm_isdst: -1, tm_gmtoff: 0, tm_zone: ptr::null(),
};
match unsafe {
libc::mktime(&mut tm)
} {
-1 => Err(err!("libc::mktime() returned -1")),
unix_seconds => Ok(unix_seconds.checked_add(tm.tm_gmtoff).unwrap()),
}
}
for (year, month, day, hour, minute, second, gmt_offset) in vec![
(1900, Month::January, 31, 9, 0, 9, Some(123)),
(0, Month::January, 1, 0, 0, 0, None),
(4, Month::January, 1, 0, 0, 0, Some(-123)),
(100, Month::February, 21, 23, 59, 59, Some(456)),
(400, Month::February, 21, 23, 59, 59, Some(99)),
(400, Month::February, 21, 23, 59, 59, None),
(401, Month::February, 21, 23, 59, 59, None),
(1200, Month::February, 21, 23, 59, 59, Some(-99)),
(1201, Month::February, 21, 23, 59, 59, None),
(2020, Month::April, 1, 23, 59, 59, Some(-789)),
(-1, Month::January, 1, 9, 10, 11, None),
(-1, Month::January, 1, 0, 0, 0, None),
(-1, Month::January, 1, 0, 0, 0, Some(123)),
(-4, Month::February, 9, 23, 59, 59, Some(-789)),
(-100, Month::February, 9, 23, 59, 59, Some(-789)),
(-400, Month::February, 9, 23, 59, 59, Some(456)),
(-401, Month::February, 9, 23, 59, 59, None),
(-800, Month::February, 9, 23, 59, 59, Some(456)),
(-800, Month::February, 9, 23, 59, 59, None),
(-801, Month::February, 9, 23, 59, 59, Some(456)),
] {
let time = Time::make(year, month, day, hour, minute, second, gmt_offset)?;
assert_eq!(time.try_into_unix_seconds()?, try_time_into_unix_seconds_using_libc(&time)?);
}
let first = Time::make(-1200, Month::February, 1, 0, 0, 0, None)?;
let second = Time::make(-1201, Month::February, 1, 0, 0, 0, None)?;
assert_eq!(first.try_into_unix_seconds()? - second.try_into_unix_seconds()?, super::ONE_YEAR);
assert_eq!(try_time_into_unix_seconds_using_libc(&first)? - try_time_into_unix_seconds_using_libc(&second)?, super::ONE_YEAR);
Ok(())
}
#[test]
fn test_trying_unix_seconds_into_times() -> CrateResult<()> {
fn try_unix_seconds_into_time_using_libc(unix_seconds: i64) -> CrateResult<Time> {
let mut tm = libc::tm {
tm_year: 0, tm_mon: 0, tm_mday: 0,
tm_hour: 0, tm_min: 0, tm_sec: 0,
tm_wday: 0, tm_yday: 0, tm_isdst: 0, tm_gmtoff: 0, tm_zone: ptr::null(),
};
assert_eq!(false, unsafe {
libc::gmtime_r(&unix_seconds, &mut tm).is_null()
});
let year = i64::from(tm.tm_year) + i64::from(UNIX_YEAR_DELTA);
let day = u8::try_from(tm.tm_mday).unwrap();
let hour = u8::try_from(tm.tm_hour).unwrap();
let minute = u8::try_from(tm.tm_min).unwrap();
let second = u8::try_from(tm.tm_sec).unwrap();
Time::make(year, Month::try_from_unix(tm.tm_mon)?, day, hour, minute, second, None)
}
for unix_seconds in vec![
0, -1, 1, -99, 99, -super::SECONDS_OF_1970_YEARS, -super::SECONDS_OF_1970_YEARS + 1, -super::SECONDS_OF_1970_YEARS + 99,
super::SECONDS_OF_1970_YEARS, -super::SECONDS_OF_1970_YEARS - 1, -super::SECONDS_OF_1970_YEARS - 2,
-super::SECONDS_OF_1970_YEARS - 99, -super::SECONDS_OF_1970_YEARS * 9, -super::SECONDS_OF_1970_YEARS * 99,
-super::SECONDS_OF_1970_YEARS * 123, -super::SECONDS_OF_1970_YEARS * 456, -super::SECONDS_OF_1970_YEARS * 789,
] {
let first = super::try_unix_seconds_into_time(unix_seconds, None)?;
let second = try_unix_seconds_into_time_using_libc(unix_seconds)?;
assert_eq!(first, second);
}
Ok(())
}
#[test]
fn test_times() -> CrateResult<()> {
assert!(i64::try_from(DAY).is_ok());
assert!(i64::try_from(HOUR).is_ok());
assert!(i64::try_from(MINUTE).is_ok());
assert!(Time::make(0, Month::January, 1, 0, 0, 0, Some(super::MIN_GMT_OFFSET)).is_ok());
assert!(Time::make(0, Month::January, 1, 0, 0, 0, Some(super::MIN_GMT_OFFSET.checked_sub(1).unwrap())).is_err());
assert!(Time::make(0, Month::January, 1, 0, 0, 0, Some(super::MAX_GMT_OFFSET)).is_ok());
assert!(Time::make(0, Month::January, 1, 0, 0, 0, Some(super::MAX_GMT_OFFSET.checked_add(1).unwrap())).is_err());
for gmt_offset in [None, Some(GmtOffset::try_from(HOUR).unwrap()), Some(-GmtOffset::try_from(HOUR).unwrap())] {
assert_eq!(Time::make(2024, Month::February, 16, 0, 0, 0, gmt_offset)?.weekday(), Weekday::Friday);
}
#[cfg(all(feature="std", feature="libc"))] {
fn print_utc(utc: &Time) {
std::println!(" UTC: {:?}", utc);
}
fn print_local(local: &Time) {
std::println!("Local: {:?}", local);
}
let utc = Time::make_utc()?;
let local = Time::make_local()?;
print_utc(&utc);
print_utc(&local.try_into_utc()?);
print_local(&local);
print_local(&utc.try_into_local()?);
}
Ok(())
}
#[test]
#[cfg(feature="libc")]
fn test_trying_durations_into_local_times() -> CrateResult<()> {
let gmt_offset = super::load_gmt_offset()?.unwrap();
let gmt_offset_is_positive = gmt_offset >= 0;
let (_, hour, minute, second) = crate::duration_to_dhms(&Duration::from_secs(if gmt_offset_is_positive {
u64::try_from(gmt_offset).unwrap()
} else {
DAY.checked_sub(u64::try_from(gmt_offset.abs()).unwrap()).unwrap()
}));
let hour = u8::try_from(hour).unwrap();
let minute = u8::try_from(minute).unwrap();
let second = u8::try_from(second).unwrap();
let time = super::try_duration_into_local_time(&Duration::from_secs(0))?;
let (year, month, day) = if gmt_offset_is_positive {
(1970, Month::January, 1)
} else {
(1969, Month::December, 31)
};
assert_eq!(time.year, year);
assert_eq!(time.month, month);
assert_eq!(time.day, day);
assert_eq!(time.hour, hour);
assert_eq!(time.minute, minute);
assert_eq!(time.second, second);
assert_eq!(time.gmt_offset, Some(gmt_offset));
let time = super::try_duration_into_local_time(&Duration::from_secs(crate::WEEK))?;
let day = if gmt_offset_is_positive { 8 } else { 7 };
assert_eq!(time.year, 1970);
assert_eq!(time.month, Month::January);
assert_eq!(time.day, day);
assert_eq!(time.hour, hour);
assert_eq!(time.minute, minute);
assert_eq!(time.second, second);
assert_eq!(time.gmt_offset, Some(gmt_offset));
Ok(())
}
#[test]
fn test_leap_years() {
for y in &[1_i64, 2, 3, 5, 1700, 1800, 1900] {
assert!(super::is_leap_year(*y) == false);
}
for y in &[4_i64, 8, 1600, 2000] {
assert!(super::is_leap_year(*y));
}
assert_eq!(month::END_OF_FEBRUARY_IN_LEAP_YEARS, 29);
assert_eq!(month::END_OF_FEBRUARY_IN_COMMON_YEARS, 28);
}
#[test]
fn test_trying_durations_from_times() -> CrateResult<()> {
for (day, hour, minute, second, gmt_offset) in vec![(1_u8, 0_u8, 1_u8, 59_u8, 0_i32), (9, 1, 2, 59, 3600), (31, 23, 30, 1, -3600)] {
let duration = Duration::try_from(Time::make(1970, Month::January, day, hour, minute, second, Some(gmt_offset))?)?;
let secs = u64::from(day.checked_sub(1).unwrap()).checked_mul(DAY).unwrap()
+ u64::from(hour).checked_mul(HOUR).unwrap()
+ u64::from(minute).checked_mul(MINUTE).unwrap()
+ u64::from(second);
let secs = if gmt_offset >= 0 {
secs.checked_sub(u64::try_from(gmt_offset).unwrap())
} else {
secs.checked_add(u64::try_from(gmt_offset.abs()).unwrap())
};
assert_eq!(duration.as_secs(), secs.unwrap());
}
Ok(())
}
#[test]
fn test_trying_times_from_durations() -> CrateResult<()> {
for (unix_seconds, year, month, day, hour, minute, second) in vec![
(0_u64, 1970_i64, Month::January, 1_u8, 0_u8, 0_u8, 0_u8),
(10, 1970, Month::January, 1, 0, 0, 10),
] {
let time = Time::try_from(Duration::from_secs(unix_seconds))?;
assert_eq!(time.year, year);
assert_eq!(time.month, month);
assert_eq!(time.day, day);
assert_eq!(time.hour, hour);
assert_eq!(time.minute, minute);
assert_eq!(time.second, second);
assert!(time.gmt_offset.is_none());
}
Ok(())
}
#[test]
fn test_time_ordering() -> CrateResult<()> {
for (
first_year, first_month, first_day, first_hour, first_minute, first_second, first_gmt_offset,
second_year, second_month, second_day, second_hour, second_minute, second_second, second_gmt_offset,
ordering,
) in vec![
(
2020_i64, Month::January, 1_u8, 0_u8, 1_u8, 59_u8, 0_i32,
2020_i64, Month::January, 1_u8, 0_u8, 1_u8, 59_u8, 0_i32,
Ordering::Equal,
),
(
2020_i64, Month::January, 1_u8, 0_u8, 2_u8, 59_u8, 60_i32,
2020_i64, Month::January, 1_u8, 0_u8, 0_u8, 59_u8, -60_i32,
Ordering::Equal,
),
(
2020_i64, Month::January, 1_u8, 0_u8, 2_u8, 59_u8, 0_i32,
2020_i64, Month::January, 1_u8, 0_u8, 0_u8, 59_u8, -60_i32,
Ordering::Greater,
),
(
2020_i64, Month::January, 1_u8, 0_u8, 2_u8, 59_u8, 60_i32,
2020_i64, Month::January, 1_u8, 0_u8, 0_u8, 59_u8, -120_i32,
Ordering::Less,
),
(
2020_i64, Month::January, 1_u8, 0_u8, 0_u8, 0_u8, 60_i32,
2019_i64, Month::December, 31_u8, 23_u8, 59_u8, 59_u8, 0_i32,
Ordering::Less,
),
(
2020_i64, Month::January, 1_u8, 0_u8, 0_u8, 0_u8, 0_i32,
2019_i64, Month::December, 31_u8, 23_u8, 59_u8, 59_u8, 0_i32,
Ordering::Greater,
),
(
2020_i64, Month::January, 1_u8, 0_u8, 1_u8, 0_u8, 60_i32,
2019_i64, Month::December, 31_u8, 23_u8, 59_u8, 0_u8, -60_i32,
Ordering::Equal,
),
(
2020_i64, Month::March, 1_u8, 0_u8, 1_u8, 0_u8, 60_i32,
2020_i64, Month::March, 1_u8, 23_u8, 59_u8, 0_u8, -60_i32,
Ordering::Less,
),
(
2020_i64, Month::March, 2_u8, 0_u8, 1_u8, 0_u8, 60_i32,
2020_i64, Month::March, 1_u8, 23_u8, 59_u8, 0_u8, -60_i32,
Ordering::Equal,
),
(
2020_i64, Month::March, 1_u8, 0_u8, 1_u8, 0_u8, 60_i32,
2020_i64, Month::March, 2_u8, 23_u8, 59_u8, 0_u8, -60_i32,
Ordering::Less,
),
(
2020_i64, Month::April, 1_u8, 0_u8, 1_u8, 0_u8, 60_i32,
2020_i64, Month::March, 31_u8, 23_u8, 59_u8, 0_u8, -60_i32,
Ordering::Equal,
),
(
2020_i64, Month::March, 31_u8, 23_u8, 59_u8, 0_u8, -60_i32,
2020_i64, Month::April, 1_u8, 0_u8, 1_u8, 0_u8, 60_i32,
Ordering::Equal,
),
(
2020_i64, Month::March, 31_u8, 23_u8, 59_u8, 0_u8, -60_i32,
2020_i64, Month::May, 1_u8, 0_u8, 1_u8, 0_u8, 60_i32,
Ordering::Less,
),
(
2020_i64, Month::May, 1_u8, 0_u8, 1_u8, 0_u8, 60_i32,
2020_i64, Month::March, 31_u8, 23_u8, 59_u8, 0_u8, -60_i32,
Ordering::Greater,
),
] {
let first = Time::make(first_year, first_month, first_day, first_hour, first_minute, first_second, Some(first_gmt_offset))?;
let second = Time::make(second_year, second_month, second_day, second_hour, second_minute, second_second, Some(second_gmt_offset))?;
assert_eq!(first.cmp(&second), ordering);
}
Ok(())
}
#[test]
#[cfg(feature="libc")]
#[cfg(any(target_os="linux", target_os="l4re", target_os="android"))]
fn gmt_offset() {
let time = unsafe {
libc::time(ptr::null_mut())
};
assert_ne!(time, -1);
let mut local = libc::tm {
tm_year: 0, tm_mon: 0, tm_mday: 0,
tm_hour: 0, tm_min: 0, tm_sec: 0,
tm_wday: 0, tm_yday: 0, tm_isdst: 0, tm_gmtoff: 0,
tm_zone: ptr::null(),
};
let mut gmt = local.clone();
unsafe {
assert!(libc::localtime_r(&time, &mut local).is_null() == false);
}
unsafe {
assert!(libc::gmtime_r(&time, &mut gmt).is_null() == false);
}
let day_in_seconds = GmtOffset::try_from(DAY).unwrap();
let hour_in_seconds = GmtOffset::try_from(HOUR).unwrap();
let minute_in_seconds = GmtOffset::try_from(MINUTE).unwrap();
let seconds = |tm: &libc::tm| tm.tm_hour.checked_mul(hour_in_seconds)?.checked_add(tm.tm_min.checked_mul(minute_in_seconds)?);
let local_seconds = seconds(&local).unwrap();
let gmt_seconds = seconds(&gmt).unwrap();
let same_day = local.tm_mday == gmt.tm_mday;
let gmt_offset = match cmp_tm(&local, &gmt) {
Ordering::Equal => 0,
Ordering::Greater => if same_day {
local_seconds.checked_sub(gmt_seconds).unwrap()
} else {
local_seconds.checked_add(day_in_seconds.checked_sub(gmt_seconds).unwrap()).unwrap()
},
Ordering::Less => if same_day {
local_seconds.checked_sub(gmt_seconds).unwrap()
} else {
-gmt_seconds.checked_add(day_in_seconds.checked_sub(local_seconds).unwrap()).unwrap()
},
};
assert_eq!(local.tm_gmtoff, gmt_offset.into());
}
#[cfg(feature="libc")]
#[cfg(any(target_os="linux", target_os="l4re", target_os="android"))]
fn cmp_tm(first: &libc::tm, second: &libc::tm) -> Ordering {
first.tm_year.cmp(&second.tm_year).then_with(||
first.tm_mon.cmp(&second.tm_mon).then_with(||
first.tm_mday.cmp(&second.tm_mday).then_with(||
first.tm_hour.cmp(&second.tm_hour).then_with(||
first.tm_min.cmp(&second.tm_min).then_with(|| first.tm_sec.cmp(&second.tm_sec))
)
)
)
)
}