From d7d152eff1109794cef7e1515220c5edae67274f Mon Sep 17 00:00:00 2001 From: Kang Seonghoon Date: Tue, 7 Feb 2017 05:22:12 +0900 Subject: [PATCH] Avoid passing nanoseconds to the OS APIs at all. (#123) Windows still seems to have an issue---it does not accept 60 in the second field at all. Let's see if not passing it around improves the situation. --- src/offset/local.rs | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/src/offset/local.rs b/src/offset/local.rs index 8dd8b2e..dd037f8 100644 --- a/src/offset/local.rs +++ b/src/offset/local.rs @@ -49,7 +49,7 @@ fn datetime_to_timespec(d: &NaiveDateTime, local: bool) -> oldtime::Timespec { // the number 1 is arbitrary but should be non-zero to trigger `mktime`. let tm_utcoff = if local {1} else {0}; - let mut tm = oldtime::Tm { + let tm = oldtime::Tm { tm_sec: d.second() as i32, tm_min: d.minute() as i32, tm_hour: d.hour() as i32, @@ -60,15 +60,10 @@ fn datetime_to_timespec(d: &NaiveDateTime, local: bool) -> oldtime::Timespec { tm_yday: 0, // and this tm_isdst: -1, tm_utcoff: tm_utcoff, - tm_nsec: d.nanosecond() as i32, + // do not set this, OS APIs are heavily inconsistent in terms of leap second handling + tm_nsec: 0, }; - // adjustment for the leap second - if tm.tm_nsec >= 1_000_000_000 { - tm.tm_sec += 1; - tm.tm_nsec -= 1_000_000_000; - } - tm.to_timespec() } @@ -110,6 +105,7 @@ impl TimeZone for Local { 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()) } @@ -117,6 +113,7 @@ impl TimeZone for Local { 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() } @@ -129,18 +126,32 @@ impl TimeZone for Local { let midnight = self.from_local_datetime(&local.and_hms(0, 0, 0)); midnight.map(|datetime| Date::from_utc(*local, *datetime.offset())) } + fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { let timespec = datetime_to_timespec(local, true); - LocalResult::Single(tm_to_datetime(oldtime::at(timespec))) + + // datetime_to_timespec completely ignores leap seconds, so we need to adjust for them + let mut tm = oldtime::at(timespec); + assert_eq!(tm.tm_nsec, 0); + tm.tm_nsec = local.nanosecond() as i32; + + LocalResult::Single(tm_to_datetime(tm)) } fn from_utc_date(&self, utc: &NaiveDate) -> Date { let midnight = self.from_utc_datetime(&utc.and_hms(0, 0, 0)); Date::from_utc(*utc, *midnight.offset()) } + fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime { let timespec = datetime_to_timespec(utc, false); - tm_to_datetime(oldtime::at(timespec)) + + // datetime_to_timespec completely ignores leap seconds, so we need to adjust for them + let mut tm = oldtime::at(timespec); + assert_eq!(tm.tm_nsec, 0); + tm.tm_nsec = utc.nanosecond() as i32; + + tm_to_datetime(tm) } } @@ -158,6 +169,7 @@ mod tests { #[test] fn test_leap_second() { // issue #123 let today = Local::today(); + let dt = today.and_hms_milli(1, 2, 59, 1000); let timestr = dt.time().to_string(); // the OS API may or may not support the leap second, @@ -165,8 +177,10 @@ mod tests { assert!(timestr == "01:02:60" || timestr == "01:03:00", "unexpected timestr {:?}", timestr); - // this case, while unsupported by most APIs, should *not* panic. - let _ = today.and_hms_milli_opt(1, 2, 3, 1000); + let dt = today.and_hms_milli(1, 2, 3, 1234); + let timestr = dt.time().to_string(); + assert!(timestr == "01:02:03.234" || timestr == "01:02:04.234", + "unexpected timestr {:?}", timestr); } }