Fixes #27.
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:
parent
fae01ad74d
commit
2be6e14446
|
@ -350,10 +350,12 @@ impl<Tz: TimeZone> fmt::Display for Date<Tz> where Tz::Offset: fmt::Display {
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
use Datelike;
|
||||||
use duration::Duration;
|
use duration::Duration;
|
||||||
use naive::date::NaiveDate;
|
use naive::date::NaiveDate;
|
||||||
use naive::datetime::NaiveDateTime;
|
use naive::datetime::NaiveDateTime;
|
||||||
use offset::{TimeZone, Offset, LocalResult};
|
use offset::{TimeZone, Offset, LocalResult};
|
||||||
|
use offset::local::Local;
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
struct UTC1y; // same to UTC but with an offset of 365 days
|
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)),
|
assert_eq!(format!("{:?}", UTC1y.ymd(2012, 3, 4).and_hms(5, 6, 7)),
|
||||||
"2012-03-04T05:06:07+8760:00".to_string());
|
"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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -378,23 +378,42 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn test_datetime_offset() {
|
fn test_datetime_offset() {
|
||||||
let EST = FixedOffset::east(5*60*60);
|
let EST = FixedOffset::west(5*60*60);
|
||||||
let EDT = FixedOffset::east(4*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)),
|
assert_eq!(format!("{}", UTC.ymd(2014, 5, 6).and_hms(7, 8, 9)),
|
||||||
"2014-05-06 07:08:09 UTC");
|
"2014-05-06 07:08:09 UTC");
|
||||||
assert_eq!(format!("{}", EDT.ymd(2014, 5, 6).and_hms(7, 8, 9)),
|
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)),
|
assert_eq!(format!("{:?}", UTC.ymd(2014, 5, 6).and_hms(7, 8, 9)),
|
||||||
"2014-05-06T07:08:09Z");
|
"2014-05-06T07:08:09Z");
|
||||||
assert_eq!(format!("{:?}", EDT.ymd(2014, 5, 6).and_hms(7, 8, 9)),
|
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),
|
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));
|
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),
|
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!(*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);
|
assert_eq!(*EDT.ymd(2014, 5, 6).and_hms(7, 8, 9).offset(), EDT);
|
||||||
|
|
|
@ -95,7 +95,11 @@ impl TimeZone for Local {
|
||||||
|
|
||||||
// override them for avoiding redundant works
|
// override them for avoiding redundant works
|
||||||
fn from_local_date(&self, local: &NaiveDate) -> LocalResult<Date<Local>> {
|
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>> {
|
fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<DateTime<Local>> {
|
||||||
let timespec = datetime_to_timespec(local, true);
|
let timespec = datetime_to_timespec(local, true);
|
||||||
|
@ -103,7 +107,8 @@ impl TimeZone for Local {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_utc_date(&self, utc: &NaiveDate) -> Date<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> {
|
fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime<Local> {
|
||||||
let timespec = datetime_to_timespec(utc, false);
|
let timespec = datetime_to_timespec(utc, false);
|
||||||
|
|
Loading…
Reference in New Issue