This is due to somewhat ambiguous semantics of `Date`. It cannot
really constructed without an intermediate `DateTime` much like
the removed `Time`, but it is much more useful than `Time` so
we need some reasonable meaning to it. This commit clarifies
that meaning and corrects some problems around it:

- The date itself is timezone-agnostic unless the timezone itself
  has an offset equal to or greater than one day. In all current
  time zones, the date conversion should be a no-op.

- The date may be attached some offset; that offset should have
  been occurred within the corresponding day in either the local
  time or the UTC.

- `TimeZone` is free to assign the offset within this constraint.
  For convenience, the current `Local` time zone assumes the local
  midnight or the UTC midnight.
This commit is contained in:
Kang Seonghoon 2015-03-03 02:26:23 +09:00
parent fae01ad74d
commit 2be6e14446
3 changed files with 39 additions and 8 deletions

View File

@ -350,10 +350,12 @@ impl<Tz: TimeZone> fmt::Display for Date<Tz> where Tz::Offset: fmt::Display {
mod tests {
use std::fmt;
use Datelike;
use duration::Duration;
use naive::date::NaiveDate;
use naive::datetime::NaiveDateTime;
use offset::{TimeZone, Offset, LocalResult};
use offset::local::Local;
#[derive(Copy, Clone, PartialEq, Eq)]
struct UTC1y; // same to UTC but with an offset of 365 days
@ -396,5 +398,10 @@ mod tests {
assert_eq!(format!("{:?}", UTC1y.ymd(2012, 3, 4).and_hms(5, 6, 7)),
"2012-03-04T05:06:07+8760:00".to_string());
}
#[test]
fn test_local_date_sanity_check() { // issue #27
assert_eq!(Local.ymd(2999, 12, 28).day(), 28);
}
}

View File

@ -378,23 +378,42 @@ mod tests {
#[test]
#[allow(non_snake_case)]
fn test_datetime_offset() {
let EST = FixedOffset::east(5*60*60);
let EDT = FixedOffset::east(4*60*60);
let EST = FixedOffset::west(5*60*60);
let EDT = FixedOffset::west(4*60*60);
let KST = FixedOffset::east(9*60*60);
assert_eq!(format!("{}", UTC.ymd(2014, 5, 6).and_hms(7, 8, 9)),
"2014-05-06 07:08:09 UTC");
assert_eq!(format!("{}", EDT.ymd(2014, 5, 6).and_hms(7, 8, 9)),
"2014-05-06 07:08:09 +04:00");
"2014-05-06 07:08:09 -04:00");
assert_eq!(format!("{}", KST.ymd(2014, 5, 6).and_hms(7, 8, 9)),
"2014-05-06 07:08:09 +09:00");
assert_eq!(format!("{:?}", UTC.ymd(2014, 5, 6).and_hms(7, 8, 9)),
"2014-05-06T07:08:09Z");
assert_eq!(format!("{:?}", EDT.ymd(2014, 5, 6).and_hms(7, 8, 9)),
"2014-05-06T07:08:09+04:00");
"2014-05-06T07:08:09-04:00");
assert_eq!(format!("{:?}", KST.ymd(2014, 5, 6).and_hms(7, 8, 9)),
"2014-05-06T07:08:09+09:00");
assert_eq!(UTC.ymd(2014, 5, 6).and_hms(7, 8, 9), EDT.ymd(2014, 5, 6).and_hms(11, 8, 9));
// edge cases
assert_eq!(format!("{:?}", UTC.ymd(2014, 5, 6).and_hms(0, 0, 0)),
"2014-05-06T00:00:00Z");
assert_eq!(format!("{:?}", EDT.ymd(2014, 5, 6).and_hms(0, 0, 0)),
"2014-05-06T00:00:00-04:00");
assert_eq!(format!("{:?}", KST.ymd(2014, 5, 6).and_hms(0, 0, 0)),
"2014-05-06T00:00:00+09:00");
assert_eq!(format!("{:?}", UTC.ymd(2014, 5, 6).and_hms(23, 59, 59)),
"2014-05-06T23:59:59Z");
assert_eq!(format!("{:?}", EDT.ymd(2014, 5, 6).and_hms(23, 59, 59)),
"2014-05-06T23:59:59-04:00");
assert_eq!(format!("{:?}", KST.ymd(2014, 5, 6).and_hms(23, 59, 59)),
"2014-05-06T23:59:59+09:00");
assert_eq!(UTC.ymd(2014, 5, 6).and_hms(7, 8, 9), EDT.ymd(2014, 5, 6).and_hms(3, 8, 9));
assert_eq!(UTC.ymd(2014, 5, 6).and_hms(7, 8, 9) + Duration::seconds(3600 + 60 + 1),
UTC.ymd(2014, 5, 6).and_hms(8, 9, 10));
assert_eq!(UTC.ymd(2014, 5, 6).and_hms(7, 8, 9) - EDT.ymd(2014, 5, 6).and_hms(10, 11, 12),
Duration::seconds(3600 - 3*60 - 3));
Duration::seconds(-7*3600 - 3*60 - 3));
assert_eq!(*UTC.ymd(2014, 5, 6).and_hms(7, 8, 9).offset(), UTC);
assert_eq!(*EDT.ymd(2014, 5, 6).and_hms(7, 8, 9).offset(), EDT);

View File

@ -95,7 +95,11 @@ impl TimeZone for Local {
// override them for avoiding redundant works
fn from_local_date(&self, local: &NaiveDate) -> LocalResult<Date<Local>> {
self.from_local_datetime(&local.and_hms(0, 0, 0)).map(|datetime| datetime.date())
// 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(0, 0, 0));
midnight.map(|datetime| Date::from_utc(*local, datetime.offset().clone()))
}
fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<DateTime<Local>> {
let timespec = datetime_to_timespec(local, true);
@ -103,7 +107,8 @@ impl TimeZone for Local {
}
fn from_utc_date(&self, utc: &NaiveDate) -> Date<Local> {
self.from_utc_datetime(&utc.and_hms(0, 0, 0)).date()
let midnight = self.from_utc_datetime(&utc.and_hms(0, 0, 0));
Date::from_utc(*utc, midnight.offset().clone())
}
fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime<Local> {
let timespec = datetime_to_timespec(utc, false);