From 2b7a068be87a6e21b8c9f68cad769d7c9c75b760 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 5 May 2023 11:08:07 +0200 Subject: [PATCH] Only implement the `offset_from_` methods on Local --- src/offset/local/mod.rs | 65 +++-------- src/offset/local/stub.rs | 216 +----------------------------------- src/offset/local/unix.rs | 38 +++---- src/offset/local/windows.rs | 65 +++++------ 4 files changed, 63 insertions(+), 321 deletions(-) diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 74aa902b57..7f3f0f9488 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -8,7 +8,7 @@ use rkyv::{Archive, Deserialize, Serialize}; use super::fixed::FixedOffset; use super::{LocalResult, TimeZone}; -use crate::naive::{NaiveDate, NaiveDateTime}; +use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; #[allow(deprecated)] use crate::Date; use crate::{DateTime, Utc}; @@ -104,33 +104,10 @@ impl TimeZone for Local { Local } - // they are easier to define in terms of the finished date and time unlike other offsets #[allow(deprecated)] fn offset_from_local_date(&self, local: &NaiveDate) -> LocalResult { - self.from_local_date(local).map(|date| *date.offset()) - } - - fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult { - self.from_local_datetime(local).map(|datetime| *datetime.offset()) - } - - #[allow(deprecated)] - fn offset_from_utc_date(&self, utc: &NaiveDate) -> FixedOffset { - *self.from_utc_date(utc).offset() - } - - fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> FixedOffset { - *self.from_utc_datetime(utc).offset() - } - - // override them for avoiding redundant works - #[allow(deprecated)] - fn from_local_date(&self, local: &NaiveDate) -> LocalResult> { - // this sounds very strange, but required for keeping `TimeZone::ymd` sane. - // in the other words, we use the offset at the local midnight - // but keep the actual date unaltered (much like `FixedOffset`). - let midnight = self.from_local_datetime(&local.and_hms_opt(0, 0, 0).unwrap()); - midnight.map(|datetime| Date::from_utc(*local, *datetime.offset())) + // Get the offset at local midnight. + self.offset_from_local_datetime(&local.and_time(NaiveTime::MIN)) } #[cfg(all( @@ -138,14 +115,9 @@ impl TimeZone for Local { feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi")) ))] - fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { - let mut local = local.clone(); - // Get the offset from the js runtime - let offset = - FixedOffset::west_opt((js_sys::Date::new_0().get_timezone_offset() as i32) * 60) - .unwrap(); - local -= crate::Duration::seconds(offset.local_minus_utc() as i64); - LocalResult::Single(DateTime::from_utc(local, offset)) + fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult { + let offset = js_sys::Date::new_0().get_timezone_offset(); + LocalResult::Single(FixedOffset::west_opt((offset as i32) * 60).unwrap()) } #[cfg(not(all( @@ -153,14 +125,14 @@ impl TimeZone for Local { feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi")) )))] - fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { - inner::naive_to_local(local, true) + fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult { + inner::offset_from_local_datetime(local) } #[allow(deprecated)] - fn from_utc_date(&self, utc: &NaiveDate) -> Date { - let midnight = self.from_utc_datetime(&utc.and_hms_opt(0, 0, 0).unwrap()); - Date::from_utc(*utc, *midnight.offset()) + fn offset_from_utc_date(&self, utc: &NaiveDate) -> FixedOffset { + // Get the offset at midnight. + self.offset_from_utc_datetime(&utc.and_time(NaiveTime::MIN)) } #[cfg(all( @@ -168,12 +140,9 @@ impl TimeZone for Local { feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi")) ))] - fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime { - // Get the offset from the js runtime - let offset = - FixedOffset::west_opt((js_sys::Date::new_0().get_timezone_offset() as i32) * 60) - .unwrap(); - DateTime::from_utc(*utc, offset) + fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> FixedOffset { + let offset = js_sys::Date::new_0().get_timezone_offset(); + LocalResult::Single(FixedOffset::west_opt((offset as i32) * 60).unwrap()) } #[cfg(not(all( @@ -181,10 +150,8 @@ impl TimeZone for Local { feature = "wasmbind", not(any(target_os = "emscripten", target_os = "wasi")) )))] - fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime { - // this is OK to unwrap as getting local time from a UTC - // timestamp is never ambiguous - inner::naive_to_local(utc, false).unwrap() + fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> FixedOffset { + inner::offset_from_utc_datetime(utc).unwrap() } } diff --git a/src/offset/local/stub.rs b/src/offset/local/stub.rs index 3b42717d82..38ef680773 100644 --- a/src/offset/local/stub.rs +++ b/src/offset/local/stub.rs @@ -8,218 +8,12 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use std::time::{SystemTime, UNIX_EPOCH}; +use crate::{FixedOffset, LocalResult, NaiveDateTime}; -use super::{FixedOffset, Local}; -use crate::{DateTime, Datelike, LocalResult, NaiveDate, NaiveDateTime, NaiveTime, Timelike}; - -/// Converts a local `NaiveDateTime` to the `time::Timespec`. -#[cfg(not(all( - target_arch = "wasm32", - feature = "wasmbind", - not(any(target_os = "emscripten", target_os = "wasi")) -)))] -pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> LocalResult> { - let tm = Tm { - tm_sec: d.second() as i32, - tm_min: d.minute() as i32, - tm_hour: d.hour() as i32, - tm_mday: d.day() as i32, - tm_mon: d.month0() as i32, // yes, C is that strange... - tm_year: d.year() - 1900, // this doesn't underflow, we know that d is `NaiveDateTime`. - tm_wday: 0, // to_local ignores this - tm_yday: 0, // and this - tm_isdst: -1, - // This seems pretty fake? - tm_utcoff: if local { 1 } else { 0 }, - // do not set this, OS APIs are heavily inconsistent in terms of leap second handling - tm_nsec: 0, - }; - - let spec = Timespec { - sec: match local { - false => utc_tm_to_time(&tm), - true => local_tm_to_time(&tm), - }, - nsec: tm.tm_nsec, - }; - - // Adjust for leap seconds - let mut tm = spec.local(); - assert_eq!(tm.tm_nsec, 0); - tm.tm_nsec = d.nanosecond() as i32; - - LocalResult::Single(tm_to_datetime(tm)) -} - -/// Converts a `time::Tm` struct into the timezone-aware `DateTime`. -/// This assumes that `time` is working correctly, i.e. any error is fatal. -#[cfg(not(all( - target_arch = "wasm32", - feature = "wasmbind", - not(any(target_os = "emscripten", target_os = "wasi")) -)))] -fn tm_to_datetime(mut tm: Tm) -> DateTime { - if tm.tm_sec >= 60 { - tm.tm_nsec += (tm.tm_sec - 59) * 1_000_000_000; - tm.tm_sec = 59; - } - - let date = NaiveDate::from_yo(tm.tm_year + 1900, tm.tm_yday as u32 + 1); - let time = NaiveTime::from_hms_nano( - tm.tm_hour as u32, - tm.tm_min as u32, - tm.tm_sec as u32, - tm.tm_nsec as u32, - ); - - let offset = FixedOffset::east_opt(tm.tm_utcoff).unwrap(); - DateTime::from_utc(date.and_time(time) - offset, offset) -} - -/// A record specifying a time value in seconds and nanoseconds, where -/// nanoseconds represent the offset from the given second. -/// -/// For example a timespec of 1.2 seconds after the beginning of the epoch would -/// be represented as {sec: 1, nsec: 200000000}. -struct Timespec { - sec: i64, - nsec: i32, -} - -impl Timespec { - /// Converts this timespec into the system's local time. - fn local(self) -> Tm { - let mut tm = Tm { - tm_sec: 0, - tm_min: 0, - tm_hour: 0, - tm_mday: 0, - tm_mon: 0, - tm_year: 0, - tm_wday: 0, - tm_yday: 0, - tm_isdst: 0, - tm_utcoff: 0, - tm_nsec: 0, - }; - time_to_local_tm(self.sec, &mut tm); - tm.tm_nsec = self.nsec; - tm - } -} - -/// Holds a calendar date and time broken down into its components (year, month, -/// day, and so on), also called a broken-down time value. -// FIXME: use c_int instead of i32? -#[repr(C)] -pub(super) struct Tm { - /// Seconds after the minute - [0, 60] - tm_sec: i32, - - /// Minutes after the hour - [0, 59] - tm_min: i32, - - /// Hours after midnight - [0, 23] - tm_hour: i32, - - /// Day of the month - [1, 31] - tm_mday: i32, - - /// Months since January - [0, 11] - tm_mon: i32, - - /// Years since 1900 - tm_year: i32, - - /// Days since Sunday - [0, 6]. 0 = Sunday, 1 = Monday, ..., 6 = Saturday. - tm_wday: i32, - - /// Days since January 1 - [0, 365] - tm_yday: i32, - - /// Daylight Saving Time flag. - /// - /// This value is positive if Daylight Saving Time is in effect, zero if - /// Daylight Saving Time is not in effect, and negative if this information - /// is not available. - tm_isdst: i32, - - /// Identifies the time zone that was used to compute this broken-down time - /// value, including any adjustment for Daylight Saving Time. This is the - /// number of seconds east of UTC. For example, for U.S. Pacific Daylight - /// Time, the value is `-7*60*60 = -25200`. - tm_utcoff: i32, - - /// Nanoseconds after the second - [0, 109 - 1] - tm_nsec: i32, -} - -fn time_to_tm(ts: i64, tm: &mut Tm) { - let leapyear = |year| -> bool { year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) }; - - static YTAB: [[i64; 12]; 2] = [ - [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], - [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], - ]; - - let mut year = 1970; - - let dayclock = ts % 86400; - let mut dayno = ts / 86400; - - tm.tm_sec = (dayclock % 60) as i32; - tm.tm_min = ((dayclock % 3600) / 60) as i32; - tm.tm_hour = (dayclock / 3600) as i32; - tm.tm_wday = ((dayno + 4) % 7) as i32; - loop { - let yearsize = if leapyear(year) { 366 } else { 365 }; - if dayno >= yearsize { - dayno -= yearsize; - year += 1; - } else { - break; - } - } - tm.tm_year = (year - 1900) as i32; - tm.tm_yday = dayno as i32; - let mut mon = 0; - while dayno >= YTAB[if leapyear(year) { 1 } else { 0 }][mon] { - dayno -= YTAB[if leapyear(year) { 1 } else { 0 }][mon]; - mon += 1; - } - tm.tm_mon = mon as i32; - tm.tm_mday = dayno as i32 + 1; - tm.tm_isdst = 0; -} - -fn tm_to_time(tm: &Tm) -> i64 { - let mut y = tm.tm_year as i64 + 1900; - let mut m = tm.tm_mon as i64 + 1; - if m <= 2 { - y -= 1; - m += 12; - } - let d = tm.tm_mday as i64; - let h = tm.tm_hour as i64; - let mi = tm.tm_min as i64; - let s = tm.tm_sec as i64; - (365 * y + y / 4 - y / 100 + y / 400 + 3 * (m + 1) / 5 + 30 * m + d - 719561) * 86400 - + 3600 * h - + 60 * mi - + s -} - -pub(super) fn time_to_local_tm(sec: i64, tm: &mut Tm) { - // FIXME: Add timezone logic - time_to_tm(sec, tm); -} - -pub(super) fn utc_tm_to_time(tm: &Tm) -> i64 { - tm_to_time(tm) +pub(super) fn offset_from_utc_datetime(_utc_time: &NaiveDateTime) -> LocalResult { + LocalResult::Single(FixedOffset::east_opt(0).unwrap()) } -pub(super) fn local_tm_to_time(tm: &Tm) -> i64 { - // FIXME: Add timezone logic - tm_to_time(tm) +pub(super) fn offset_from_local_datetime(_local_time: &NaiveDateTime) -> LocalResult { + LocalResult::Single(FixedOffset::east_opt(0).unwrap()) } diff --git a/src/offset/local/unix.rs b/src/offset/local/unix.rs index d933cfb1d6..ce96a6e3bb 100644 --- a/src/offset/local/unix.rs +++ b/src/offset/local/unix.rs @@ -11,10 +11,18 @@ use std::{cell::RefCell, collections::hash_map, env, fs, hash::Hasher, time::SystemTime}; use super::tz_info::TimeZone; -use super::{DateTime, FixedOffset, Local, NaiveDateTime}; +use super::{FixedOffset, NaiveDateTime}; use crate::{Datelike, LocalResult}; -pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> LocalResult> { +pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> LocalResult { + offset(utc, false) +} + +pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> LocalResult { + offset(local, true) +} + +fn offset(d: &NaiveDateTime, local: bool) -> LocalResult { TZ_INFO.with(|maybe_cache| { maybe_cache.borrow_mut().get_or_insert_with(Cache::default).offset(*d, local) }) @@ -96,7 +104,7 @@ fn current_zone(var: Option<&str>) -> TimeZone { } impl Cache { - fn offset(&mut self, d: NaiveDateTime, local: bool) -> LocalResult> { + fn offset(&mut self, d: NaiveDateTime, local: bool) -> LocalResult { let now = SystemTime::now(); match now.duration_since(self.last_checked) { @@ -148,32 +156,16 @@ impl Cache { .offset(); return match FixedOffset::east_opt(offset) { - Some(offset) => LocalResult::Single(DateTime::from_utc(d, offset)), + Some(offset) => LocalResult::Single(offset), None => LocalResult::None, }; } // we pass through the year as the year of a local point in time must either be valid in that locale, or - // the entire time was skipped in which case we will return LocalResult::None anywa. - match self - .zone + // the entire time was skipped in which case we will return LocalResult::None anyway. + self.zone .find_local_time_type_from_local(d.timestamp(), d.year()) .expect("unable to select local time type") - { - LocalResult::None => LocalResult::None, - LocalResult::Ambiguous(early, late) => { - let early_offset = FixedOffset::east_opt(early.offset()).unwrap(); - let late_offset = FixedOffset::east_opt(late.offset()).unwrap(); - - LocalResult::Ambiguous( - DateTime::from_utc(d - early_offset, early_offset), - DateTime::from_utc(d - late_offset, late_offset), - ) - } - LocalResult::Single(tt) => { - let offset = FixedOffset::east_opt(tt.offset()).unwrap(); - LocalResult::Single(DateTime::from_utc(d - offset, offset)) - } - } + .map(|o| FixedOffset::east_opt(o.offset()).unwrap()) } } diff --git a/src/offset/local/windows.rs b/src/offset/local/windows.rs index 455a707839..84585170c2 100644 --- a/src/offset/local/windows.rs +++ b/src/offset/local/windows.rs @@ -19,8 +19,8 @@ use winapi::um::timezoneapi::{ SystemTimeToFileTime, SystemTimeToTzSpecificLocalTime, TzSpecificLocalTimeToSystemTime, }; -use super::{FixedOffset, Local}; -use crate::{DateTime, Datelike, LocalResult, NaiveDate, NaiveDateTime, NaiveTime, Timelike}; +use super::FixedOffset; +use crate::{Datelike, LocalResult, NaiveDateTime, Timelike}; /// This macro calls a Windows API FFI and checks whether the function errored with the provided error_id. If an error returns, /// the macro will return an `Error::last_os_error()`. @@ -39,54 +39,43 @@ 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; +pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> LocalResult { + offset(utc, false) +} + +pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> LocalResult { + offset(local, true) +} + /// Converts a local `NaiveDateTime` to the `time::Timespec`. -pub(super) fn naive_to_local(d: &NaiveDateTime, local: bool) -> LocalResult> { +pub(super) fn offset(d: &NaiveDateTime, local: bool) -> LocalResult { let naive_sys_time = 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 => from_utc_time(naive_sys_time), + true => from_local_time(naive_sys_time), }; - if let Ok(local) = local_sys_time { - return LocalResult::Single(local.datetime()); + if let Ok(offset) = local_sys_time { + return LocalResult::Single(offset); } LocalResult::None } -struct LocalSysTime { - inner: SYSTEMTIME, - offset: i32, +fn from_utc_time(utc_time: SYSTEMTIME) -> Result { + 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(FixedOffset::east_opt(offset).unwrap()) } -impl LocalSysTime { - fn from_utc_time(utc_time: SYSTEMTIME) -> Result { - 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 }) - } - - fn from_local_time(local_time: SYSTEMTIME) -> Result { - 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 }) - } - - fn datetime(self) -> DateTime { - 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_opt(st.wHour as u32, st.wMinute as u32, st.wSecond as u32).unwrap(); - - let offset = FixedOffset::east_opt(self.offset).unwrap(); - DateTime::from_utc(date.and_time(time) - offset, offset) - } +fn from_local_time(local_time: SYSTEMTIME) -> Result { + 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(FixedOffset::east_opt(offset).unwrap()) } fn system_time_from_naive_date_time(dt: &NaiveDateTime) -> SYSTEMTIME {