Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 1 addition & 39 deletions src/datetime/tests.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
use std::time::{SystemTime, UNIX_EPOCH};

use super::DateTime;
use crate::naive::{NaiveDate, NaiveTime};
#[cfg(feature = "clock")]
use crate::offset::Local;
use crate::offset::{FixedOffset, TimeZone, Utc};
use crate::oldtime::Duration;
#[cfg(feature = "clock")]
use crate::Datelike;
use crate::{Days, LocalResult, Months, NaiveDateTime};
use crate::{Datelike, Days, LocalResult, Months, NaiveDate, NaiveDateTime, NaiveTime};

#[derive(Clone)]
struct DstTester;
Expand Down Expand Up @@ -950,38 +947,3 @@ fn test_datetime_sub_assign_local() {
assert_eq!(datetime_sub, datetime - Duration::days(i))
}
}

#[test]
#[cfg(target_os = "windows")]
fn test_from_naive_date_time_windows() {
let min_year = NaiveDate::from_ymd_opt(1601, 1, 3).unwrap().and_hms_opt(0, 0, 0).unwrap();

let max_year = NaiveDate::from_ymd_opt(30827, 12, 29).unwrap().and_hms_opt(23, 59, 59).unwrap();

let too_low_year =
NaiveDate::from_ymd_opt(1600, 12, 29).unwrap().and_hms_opt(23, 59, 59).unwrap();

let too_high_year = NaiveDate::from_ymd_opt(30829, 1, 3).unwrap().and_hms_opt(0, 0, 0).unwrap();

let _ = Local.from_utc_datetime(&min_year);
let _ = Local.from_utc_datetime(&max_year);

let _ = Local.from_local_datetime(&min_year);
let _ = Local.from_local_datetime(&max_year);

let local_too_low = Local.from_local_datetime(&too_low_year);
let local_too_high = Local.from_local_datetime(&too_high_year);

assert_eq!(local_too_low, LocalResult::None);
assert_eq!(local_too_high, LocalResult::None);

let err = std::panic::catch_unwind(|| {
Local.from_utc_datetime(&too_low_year);
});
assert!(err.is_err());

let err = std::panic::catch_unwind(|| {
Local.from_utc_datetime(&too_high_year);
});
assert!(err.is_err());
}
64 changes: 63 additions & 1 deletion src/naive/datetime/tests.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use super::NaiveDateTime;
#[cfg(feature = "clock")]
use crate::offset::Local;
use crate::oldtime::Duration;
use crate::NaiveDate;
use crate::{Datelike, FixedOffset, Utc};
use crate::{Datelike, FixedOffset, LocalResult, TimeZone, Utc};
use std::i64;

#[test]
Expand Down Expand Up @@ -341,3 +343,63 @@ fn test_and_timezone() {
assert_eq!(dt_offset.naive_local(), ndt);
assert_eq!(dt_offset.timezone(), offset_tz);
}

#[test]
#[cfg(target_os = "windows")]
fn test_from_naive_date_time_windows() {
let min_year = NaiveDate::from_ymd_opt(1601, 1, 3).unwrap().and_hms_opt(0, 0, 0).unwrap();

let max_year = NaiveDate::from_ymd_opt(30827, 12, 29).unwrap().and_hms_opt(23, 59, 59).unwrap();

let too_low_year =
NaiveDate::from_ymd_opt(1600, 12, 29).unwrap().and_hms_opt(23, 59, 59).unwrap();

let too_high_year = NaiveDate::from_ymd_opt(30829, 1, 3).unwrap().and_hms_opt(0, 0, 0).unwrap();

let _ = Local.from_utc_datetime(&min_year);
let _ = Local.from_utc_datetime(&max_year);

let _ = Local.from_local_datetime(&min_year);
let _ = Local.from_local_datetime(&max_year);

let local_too_low = Local.from_local_datetime(&too_low_year);
let local_too_high = Local.from_local_datetime(&too_high_year);

assert_ne!(local_too_low, LocalResult::None);
assert_ne!(local_too_high, LocalResult::None);

let _ = Local.from_utc_datetime(&too_low_year);
let _ = Local.from_utc_datetime(&too_high_year);
}

#[test]
#[cfg(target_os = "windows")]
fn test_windows_extreme_years() {
let way_to_high =
NaiveDate::from_ymd_opt(262143, 2, 28).unwrap().and_hms_opt(23, 59, 59).unwrap();
let way_to_low =
NaiveDate::from_ymd_opt(-262144, 2, 28).unwrap().and_hms_opt(23, 59, 59).unwrap();

let _ = Local.from_utc_datetime(&way_to_high);
let _ = Local.from_utc_datetime(&way_to_low);

let local_too_high = Local.from_local_datetime(&way_to_high);
let local_too_low = Local.from_local_datetime(&way_to_low);

assert_ne!(local_too_high, LocalResult::None);
assert_ne!(local_too_low, LocalResult::None);
}

#[test]
#[cfg(target_os = "windows")]
fn test_windows_naive_date_leap_year() {
let leap = NaiveDate::from_ymd_opt(1600, 2, 28).unwrap().and_hms_opt(23, 59, 59).unwrap();

let mut leap_year = Local.from_utc_datetime(&leap);
leap_year += Duration::days(1);

let leap_day = NaiveDate::from_ymd_opt(1600, 2, 29).unwrap().and_hms_opt(23, 59, 59).unwrap();
let next_day = Local.from_utc_datetime(&leap_day);

assert_eq!(leap_year, next_day);
}
66 changes: 50 additions & 16 deletions src/offset/local/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,21 @@ macro_rules! windows_sys_call {

const HECTONANOSECS_IN_SEC: i64 = 10_000_000;
const HECTONANOSEC_TO_UNIX_EPOCH: i64 = 11_644_473_600 * HECTONANOSECS_IN_SEC;
const NANOSECONDS_IN_MILLI: u32 = 1_000_000;
const SYSTEMTIME_MIN_YEAR: i32 = 1601;
const SYSTEMTIME_MAX_YEAR: i32 = 30827;

pub(super) fn now() -> DateTime<Local> {
LocalSysTime::local().datetime()
}

/// Converts a local `NaiveDateTime` to the `time::Timespec`.
pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> LocalResult<DateTime<Local>> {
let naive_sys_time = system_time_from_naive_date_time(d);
let (naive_sys_time, shifted) = system_time_from_naive_date_time(d);

let local_sys_time = match local {
false => LocalSysTime::from_utc_time(naive_sys_time),
true => LocalSysTime::from_local_time(naive_sys_time),
false => LocalSysTime::from_utc_time(naive_sys_time, shifted),
true => LocalSysTime::from_local_time(naive_sys_time, shifted),
};

if let Ok(local) = local_sys_time {
Expand All @@ -59,9 +62,19 @@ pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> LocalResult<Date
LocalResult::None
}

/// Internal representation of a local windows' `SYSTEMTIME` with offset and other conversion fields
struct LocalSysTime {
/// The inner SYSTEMTIME
inner: SYSTEMTIME,
/// The offset value from UTC
offset: i32,
/// Denotes the multiple of 400 year intervals shifted to create a valid year value. The
/// value is stored as +/- depending on whether the initial value was lower (-) or higher (+).
///
/// An example case would be the year 1100. 1100 is not a valid SYSTEMTIME year value, so
/// it is shifted by 2 400-year intervals to 1900, which is a valid year value. The initial
/// value would then reverted by `LocalSysTime::datetime()` by calculating 1900 + (-2 * 400).
shifted: i32,
}

impl LocalSysTime {
Expand All @@ -72,41 +85,61 @@ impl LocalSysTime {
// is initialized.
let st = unsafe { now.assume_init() };

Self::from_local_time(st).expect("Current local time must exist")
Self::from_local_time(st, 0).expect("Current local time must exist")
}

fn from_utc_time(utc_time: SYSTEMTIME) -> Result<Self, Error> {
fn from_utc_time(utc_time: SYSTEMTIME, shifted: i32) -> Result<Self, Error> {
let local_time = utc_to_local_time(&utc_time)?;
let utc_secs = system_time_as_unix_seconds(&utc_time)?;
let local_secs = system_time_as_unix_seconds(&local_time)?;
let offset = (local_secs - utc_secs) as i32;
Ok(Self { inner: local_time, offset })
Ok(Self { inner: local_time, offset, shifted })
}

fn from_local_time(local_time: SYSTEMTIME) -> Result<Self, Error> {
fn from_local_time(local_time: SYSTEMTIME, shifted: i32) -> Result<Self, Error> {
let utc_time = local_to_utc_time(&local_time)?;
let utc_secs = system_time_as_unix_seconds(&utc_time)?;
let local_secs = system_time_as_unix_seconds(&local_time)?;
let offset = (local_secs - utc_secs) as i32;
Ok(Self { inner: local_time, offset })
Ok(Self { inner: local_time, offset, shifted })
}

fn datetime(self) -> DateTime<Local> {
let st = self.inner;

let date =
NaiveDate::from_ymd_opt(st.wYear as i32, st.wMonth as u32, st.wDay as u32).unwrap();
let time = NaiveTime::from_hms(st.wHour as u32, st.wMinute as u32, st.wSecond as u32);
// Revert date back to invalid value if the value had been shifted.
let year = st.wYear as i32 + (400 * self.shifted);

let date = NaiveDate::from_ymd_opt(year, st.wMonth as u32, st.wDay as u32).unwrap();
let time = NaiveTime::from_hms_milli_opt(
st.wHour as u32,
st.wMinute as u32,
st.wSecond as u32,
st.wMilliseconds as u32,
)
.unwrap();

let offset = FixedOffset::east_opt(self.offset).unwrap();
DateTime::from_utc(date.and_time(time) - offset, offset)
}
}

fn system_time_from_naive_date_time(dt: &NaiveDateTime) -> SYSTEMTIME {
SYSTEMTIME {
fn system_time_from_naive_date_time(dt: &NaiveDateTime) -> (SYSTEMTIME, i32) {
// Compute year to handle invalid Window dates allowed by `NaiveDateTime`
let (year, shifted) = if dt.year() < SYSTEMTIME_MIN_YEAR {
// PANICS: `abs_diff` should be panic-safe here as `NaiveDateTime`
// has a MIN_YEAR of i32::MIN >> 13
let interval = ((dt.year().abs_diff(SYSTEMTIME_MIN_YEAR - 1) / 400) + 1) as i32;
((dt.year() + (400 * interval)) as u16, 0 - interval)
} else if dt.year() > SYSTEMTIME_MAX_YEAR {
let interval = ((dt.year() - SYSTEMTIME_MAX_YEAR) / 400) + 1;
((dt.year() - (400 * interval)) as u16, interval)
} else {
(dt.year() as u16, 0)
};
let sys_time = SYSTEMTIME {
// Valid values: 1601-30827
wYear: dt.year() as u16,
wYear: year,
// Valid values:1-12
wMonth: dt.month() as u16,
// Valid values: 0-6, starting Sunday.
Expand All @@ -122,8 +155,9 @@ fn system_time_from_naive_date_time(dt: &NaiveDateTime) -> SYSTEMTIME {
// Valid values: 0-59
wSecond: dt.second() as u16,
// Valid values: 0-999
wMilliseconds: 0,
}
wMilliseconds: (dt.nanosecond() / NANOSECONDS_IN_MILLI) as u16,
};
(sys_time, shifted)
}

pub(crate) fn local_to_utc_time(local: &SYSTEMTIME) -> Result<SYSTEMTIME, Error> {
Expand Down