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.
This commit is contained in:
Kang Seonghoon 2017-02-07 05:22:12 +09:00
parent e9e7bdd99c
commit d7d152eff1
No known key found for this signature in database
GPG Key ID: 82440FABA6709020
1 changed files with 26 additions and 12 deletions

View File

@ -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`. // the number 1 is arbitrary but should be non-zero to trigger `mktime`.
let tm_utcoff = if local {1} else {0}; let tm_utcoff = if local {1} else {0};
let mut tm = oldtime::Tm { let tm = oldtime::Tm {
tm_sec: d.second() as i32, tm_sec: d.second() as i32,
tm_min: d.minute() as i32, tm_min: d.minute() as i32,
tm_hour: d.hour() 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_yday: 0, // and this
tm_isdst: -1, tm_isdst: -1,
tm_utcoff: tm_utcoff, 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() tm.to_timespec()
} }
@ -110,6 +105,7 @@ impl TimeZone for Local {
fn offset_from_local_date(&self, local: &NaiveDate) -> LocalResult<FixedOffset> { fn offset_from_local_date(&self, local: &NaiveDate) -> LocalResult<FixedOffset> {
self.from_local_date(local).map(|date| *date.offset()) self.from_local_date(local).map(|date| *date.offset())
} }
fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<FixedOffset> { fn offset_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<FixedOffset> {
self.from_local_datetime(local).map(|datetime| *datetime.offset()) 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 { fn offset_from_utc_date(&self, utc: &NaiveDate) -> FixedOffset {
*self.from_utc_date(utc).offset() *self.from_utc_date(utc).offset()
} }
fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> FixedOffset { fn offset_from_utc_datetime(&self, utc: &NaiveDateTime) -> FixedOffset {
*self.from_utc_datetime(utc).offset() *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)); let midnight = self.from_local_datetime(&local.and_hms(0, 0, 0));
midnight.map(|datetime| Date::from_utc(*local, *datetime.offset())) midnight.map(|datetime| Date::from_utc(*local, *datetime.offset()))
} }
fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<DateTime<Local>> { fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<DateTime<Local>> {
let timespec = datetime_to_timespec(local, true); 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<Local> { fn from_utc_date(&self, utc: &NaiveDate) -> Date<Local> {
let midnight = self.from_utc_datetime(&utc.and_hms(0, 0, 0)); let midnight = self.from_utc_datetime(&utc.and_hms(0, 0, 0));
Date::from_utc(*utc, *midnight.offset()) Date::from_utc(*utc, *midnight.offset())
} }
fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime<Local> { fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime<Local> {
let timespec = datetime_to_timespec(utc, false); 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] #[test]
fn test_leap_second() { // issue #123 fn test_leap_second() { // issue #123
let today = Local::today(); let today = Local::today();
let dt = today.and_hms_milli(1, 2, 59, 1000); let dt = today.and_hms_milli(1, 2, 59, 1000);
let timestr = dt.time().to_string(); let timestr = dt.time().to_string();
// the OS API may or may not support the leap second, // 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", assert!(timestr == "01:02:60" || timestr == "01:03:00",
"unexpected timestr {:?}", timestr); "unexpected timestr {:?}", timestr);
// this case, while unsupported by most APIs, should *not* panic. let dt = today.and_hms_milli(1, 2, 3, 1234);
let _ = today.and_hms_milli_opt(1, 2, 3, 1000); let timestr = dt.time().to_string();
assert!(timestr == "01:02:03.234" || timestr == "01:02:04.234",
"unexpected timestr {:?}", timestr);
} }
} }