From e43cb62f10b85f427a27628991001e851ab0d272 Mon Sep 17 00:00:00 2001 From: Kang Seonghoon Date: Tue, 13 Jan 2015 01:33:53 +0900 Subject: [PATCH] initial implementation of `Offset` redesign (#11). - We have splitted `Offset` into `Offset` and `OffsetState` (name changes in consideration). The former is used to construct and convert local or UTC date, and the latter is used to store the UTC offset inside constructed values. Some offsets are their own states as well. - This uses lots of associated types which implementation is still in flux. Currently it crashes with debuginfo enabled. We've temporarily disabled debuginfo from `Cargo.toml`. - This technically allows a conversion to the local time, but not yet tested. --- src/date.rs | 153 ++++++++------- src/datetime.rs | 139 +++++++------ src/format.rs | 10 +- src/lib.rs | 21 +- src/offset/fixed.rs | 107 +++++++++++ src/offset/local.rs | 122 ++++++++++++ src/{offset.rs => offset/mod.rs} | 321 ++++++++----------------------- src/offset/utc.rs | 68 +++++++ src/time.rs | 91 +++++---- 9 files changed, 609 insertions(+), 423 deletions(-) create mode 100644 src/offset/fixed.rs create mode 100644 src/offset/local.rs rename src/{offset.rs => offset/mod.rs} (59%) create mode 100644 src/offset/utc.rs diff --git a/src/date.rs b/src/date.rs index 7580899..2514d7b 100644 --- a/src/date.rs +++ b/src/date.rs @@ -12,7 +12,8 @@ use std::ops::{Add, Sub}; use {Weekday, Datelike}; use duration::Duration; -use offset::{Offset, UTC}; +use offset::{Offset, OffsetState}; +use offset::utc::UTC; use naive; use naive::date::NaiveDate; use naive::time::NaiveTime; @@ -21,9 +22,9 @@ use format::DelayedFormat; /// ISO 8601 calendar date with timezone. #[derive(Clone)] -pub struct Date { +pub struct Date { date: NaiveDate, - offset: Off, + offset: Off::State, } /// The minimum possible `Date`. @@ -31,11 +32,13 @@ pub const MIN: Date = Date { date: naive::date::MIN, offset: UTC }; /// The maximum possible `Date`. pub const MAX: Date = Date { date: naive::date::MAX, offset: UTC }; -impl Date { +impl Date { /// Makes a new `Date` with given *UTC* date and offset. /// The local date should be constructed via the `Offset` trait. + // + // note: this constructor is purposedly not named to `new` to discourage the direct usage. #[inline] - pub fn from_utc(date: NaiveDate, offset: Off) -> Date { + pub fn from_utc(date: NaiveDate, offset: Off::State) -> Date { Date { date: date, offset: offset } } @@ -45,8 +48,8 @@ impl Date { /// Fails on invalid datetime. #[inline] pub fn and_time(&self, time: NaiveTime) -> Option> { - let localdt = self.offset.to_local_date(&self.date).and_time(time); - self.offset.from_local_datetime(&localdt).single() + let localdt = self.naive_local().and_time(time); + self.timezone().from_local_datetime(&localdt).single() } /// Makes a new `DateTime` from the current date, hour, minute and second. @@ -162,102 +165,114 @@ impl Date { self.date.pred_opt().map(|date| Date::from_utc(date, self.offset.clone())) } + /// Retrieves an associated offset state. + #[inline] + pub fn offset<'a>(&'a self) -> &'a Off::State { + &self.offset + } + /// Retrieves an associated offset. #[inline] - pub fn offset<'a>(&'a self) -> &'a Off { - &self.offset + pub fn timezone(&self) -> Off { + Offset::from_state(&self.offset) } /// Changes the associated offset. /// This does not change the actual `Date` (but will change the string representation). #[inline] - pub fn with_offset(&self, offset: Off2) -> Date { - Date::from_utc(self.date, offset) + pub fn with_timezone(&self, tz: &Off2) -> Date { + tz.from_utc_date(&self.date) } - /// Returns a view to the local date. - fn local(&self) -> NaiveDate { - self.offset.to_local_date(&self.date) + /// Returns a view to the naive UTC date. + #[inline] + pub fn naive_utc(&self) -> NaiveDate { + self.date + } + + /// Returns a view to the naive local date. + #[inline] + pub fn naive_local(&self) -> NaiveDate { + self.date + self.offset.local_minus_utc() } } -impl Date { +/// Maps the local date to other date with given conversion function. +fn map_local(d: &Date, mut f: F) -> Option> + where F: FnMut(NaiveDate) -> Option { + f(d.naive_local()).and_then(|date| d.timezone().from_local_date(&date).single()) +} + +impl Date where Off::State: fmt::Display { /// Formats the date in the specified format string. /// See the `format` module on the supported escape sequences. #[inline] pub fn format<'a>(&'a self, fmt: &'a str) -> DelayedFormat<'a> { - DelayedFormat::new_with_offset(Some(self.local()), None, &self.offset, fmt) + DelayedFormat::new_with_offset(Some(self.naive_local()), None, &self.offset, fmt) } } -impl Datelike for Date { - #[inline] fn year(&self) -> i32 { self.local().year() } - #[inline] fn month(&self) -> u32 { self.local().month() } - #[inline] fn month0(&self) -> u32 { self.local().month0() } - #[inline] fn day(&self) -> u32 { self.local().day() } - #[inline] fn day0(&self) -> u32 { self.local().day0() } - #[inline] fn ordinal(&self) -> u32 { self.local().ordinal() } - #[inline] fn ordinal0(&self) -> u32 { self.local().ordinal0() } - #[inline] fn weekday(&self) -> Weekday { self.local().weekday() } - #[inline] fn isoweekdate(&self) -> (i32, u32, Weekday) { self.local().isoweekdate() } +impl Datelike for Date { + #[inline] fn year(&self) -> i32 { self.naive_local().year() } + #[inline] fn month(&self) -> u32 { self.naive_local().month() } + #[inline] fn month0(&self) -> u32 { self.naive_local().month0() } + #[inline] fn day(&self) -> u32 { self.naive_local().day() } + #[inline] fn day0(&self) -> u32 { self.naive_local().day0() } + #[inline] fn ordinal(&self) -> u32 { self.naive_local().ordinal() } + #[inline] fn ordinal0(&self) -> u32 { self.naive_local().ordinal0() } + #[inline] fn weekday(&self) -> Weekday { self.naive_local().weekday() } + #[inline] fn isoweekdate(&self) -> (i32, u32, Weekday) { self.naive_local().isoweekdate() } #[inline] fn with_year(&self, year: i32) -> Option> { - self.local().with_year(year) - .and_then(|date| self.offset.from_local_date(&date).single()) + map_local(self, |date| date.with_year(year)) } #[inline] fn with_month(&self, month: u32) -> Option> { - self.local().with_month(month) - .and_then(|date| self.offset.from_local_date(&date).single()) + map_local(self, |date| date.with_month(month)) } #[inline] fn with_month0(&self, month0: u32) -> Option> { - self.local().with_month0(month0) - .and_then(|date| self.offset.from_local_date(&date).single()) + map_local(self, |date| date.with_month0(month0)) } #[inline] fn with_day(&self, day: u32) -> Option> { - self.local().with_day(day) - .and_then(|date| self.offset.from_local_date(&date).single()) + map_local(self, |date| date.with_day(day)) } #[inline] fn with_day0(&self, day0: u32) -> Option> { - self.local().with_day0(day0) - .and_then(|date| self.offset.from_local_date(&date).single()) + map_local(self, |date| date.with_day0(day0)) } #[inline] fn with_ordinal(&self, ordinal: u32) -> Option> { - self.local().with_ordinal(ordinal) - .and_then(|date| self.offset.from_local_date(&date).single()) + map_local(self, |date| date.with_ordinal(ordinal)) } #[inline] fn with_ordinal0(&self, ordinal0: u32) -> Option> { - self.local().with_ordinal0(ordinal0) - .and_then(|date| self.offset.from_local_date(&date).single()) + map_local(self, |date| date.with_ordinal0(ordinal0)) } } -impl PartialEq> for Date { +impl PartialEq> for Date { fn eq(&self, other: &Date) -> bool { self.date == other.date } } -impl Eq for Date { +impl Eq for Date { } -impl PartialOrd for Date { +impl PartialOrd for Date { fn partial_cmp(&self, other: &Date) -> Option { self.date.partial_cmp(&other.date) } } -impl Ord for Date { +impl Ord for Date { fn cmp(&self, other: &Date) -> Ordering { self.date.cmp(&other.date) } } @@ -265,7 +280,7 @@ impl hash::Hash for Date { fn hash(&self, state: &mut H) { self.date.hash(state) } } -impl Add for Date { +impl Add for Date { type Output = Date; fn add(self, rhs: Duration) -> Date { @@ -273,13 +288,13 @@ impl Add for Date { } } -impl Sub> for Date { +impl Sub> for Date { type Output = Duration; fn sub(self, rhs: Date) -> Duration { self.date - rhs.date } } -impl Sub for Date { +impl Sub for Date { type Output = Date; #[inline] @@ -288,13 +303,13 @@ impl Sub for Date { impl fmt::Debug for Date { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}{:?}", self.local(), self.offset) + write!(f, "{:?}{:?}", self.naive_local(), self.offset) } } -impl fmt::Display for Date { +impl fmt::Display for Date where Off::State: fmt::Display { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}{}", self.local(), self.offset) + write!(f, "{}{}", self.naive_local(), self.offset) } } @@ -306,35 +321,39 @@ mod tests { use naive::date::NaiveDate; use naive::time::NaiveTime; use naive::datetime::NaiveDateTime; - use super::Date; - use time::Time; - use datetime::DateTime; - use offset::{Offset, LocalResult}; + use offset::{Offset, OffsetState, LocalResult}; #[derive(Copy, Clone, PartialEq, Eq)] struct UTC1y; // same to UTC but with an offset of 365 days + #[derive(Copy, Clone, PartialEq, Eq)] + struct OneYear; + impl Offset for UTC1y { - fn local_minus_utc(&self) -> Duration { Duration::zero() } + type State = OneYear; - fn from_local_date(&self, local: &NaiveDate) -> LocalResult> { - LocalResult::Single(Date::from_utc(*local - Duration::days(365), UTC1y)) + fn from_state(_state: &OneYear) -> UTC1y { UTC1y } + + fn state_from_local_date(&self, _local: &NaiveDate) -> LocalResult { + LocalResult::Single(OneYear) } - fn from_local_time(&self, local: &NaiveTime) -> LocalResult> { - LocalResult::Single(Time::from_utc(local.clone(), UTC1y)) + fn state_from_local_time(&self, _local: &NaiveTime) -> LocalResult { + LocalResult::Single(OneYear) } - fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { - LocalResult::Single(DateTime::from_utc(*local - Duration::days(365), UTC1y)) + fn state_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult { + LocalResult::Single(OneYear) } - fn to_local_date(&self, utc: &NaiveDate) -> NaiveDate { *utc + Duration::days(365) } - fn to_local_time(&self, utc: &NaiveTime) -> NaiveTime { utc.clone() } - fn to_local_datetime(&self, utc: &NaiveDateTime) -> NaiveDateTime { - *utc + Duration::days(365) - } + fn state_from_utc_date(&self, _utc: &NaiveDate) -> OneYear { OneYear } + fn state_from_utc_time(&self, _utc: &NaiveTime) -> OneYear { OneYear } + fn state_from_utc_datetime(&self, _utc: &NaiveDateTime) -> OneYear { OneYear } } - impl fmt::Debug for UTC1y { + impl OffsetState for OneYear { + fn local_minus_utc(&self) -> Duration { Duration::days(365) } + } + + impl fmt::Debug for OneYear { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "+8760:00") } } diff --git a/src/datetime.rs b/src/datetime.rs index 25b00a6..ad0c96d 100644 --- a/src/datetime.rs +++ b/src/datetime.rs @@ -11,7 +11,7 @@ use std::cmp::Ordering; use std::ops::{Add, Sub}; use {Weekday, Timelike, Datelike}; -use offset::Offset; +use offset::{Offset, OffsetState}; use duration::Duration; use naive::datetime::NaiveDateTime; use time::Time; @@ -20,16 +20,18 @@ use format::DelayedFormat; /// ISO 8601 combined date and time with timezone. #[derive(Clone)] -pub struct DateTime { +pub struct DateTime { datetime: NaiveDateTime, - offset: Off, + offset: Off::State, } -impl DateTime { +impl DateTime { /// Makes a new `DateTime` with given *UTC* datetime and offset. /// The local datetime should be constructed via the `Offset` trait. + // + // note: this constructor is purposedly not named to `new` to discourage the direct usage. #[inline] - pub fn from_utc(datetime: NaiveDateTime, offset: Off) -> DateTime { + pub fn from_utc(datetime: NaiveDateTime, offset: Off::State) -> DateTime { DateTime { datetime: datetime, offset: offset } } @@ -51,134 +53,142 @@ impl DateTime { self.datetime.num_seconds_from_unix_epoch() } + /// Retrieves an associated offset state. + #[inline] + pub fn offset<'a>(&'a self) -> &'a Off::State { + &self.offset + } + /// Retrieves an associated offset. #[inline] - pub fn offset<'a>(&'a self) -> &'a Off { - &self.offset + pub fn timezone(&self) -> Off { + Offset::from_state(&self.offset) } /// Changes the associated offset. /// This does not change the actual `DateTime` (but will change the string representation). #[inline] - pub fn with_offset(&self, offset: Off2) -> DateTime { - DateTime::from_utc(self.datetime, offset) + pub fn with_timezone(&self, tz: &Off2) -> DateTime { + tz.from_utc_datetime(&self.datetime) } - /// Returns a view to the local datetime. - fn local(&self) -> NaiveDateTime { - self.offset.to_local_datetime(&self.datetime) + /// Returns a view to the naive UTC datetime. + #[inline] + pub fn naive_utc(&self) -> NaiveDateTime { + self.datetime + } + + /// Returns a view to the naive local datetime. + #[inline] + pub fn naive_local(&self) -> NaiveDateTime { + self.datetime + self.offset.local_minus_utc() } } -impl DateTime { +/// Maps the local datetime to other datetime with given conversion function. +fn map_local(dt: &DateTime, mut f: F) -> Option> + where F: FnMut(NaiveDateTime) -> Option { + f(dt.naive_local()).and_then(|datetime| dt.timezone().from_local_datetime(&datetime).single()) +} + +impl DateTime where Off::State: fmt::Display { /// Formats the combined date and time in the specified format string. /// See the `format` module on the supported escape sequences. #[inline] pub fn format<'a>(&'a self, fmt: &'a str) -> DelayedFormat<'a> { - let local = self.local(); + let local = self.naive_local(); DelayedFormat::new_with_offset(Some(local.date()), Some(local.time()), &self.offset, fmt) } } -impl Datelike for DateTime { - #[inline] fn year(&self) -> i32 { self.local().year() } - #[inline] fn month(&self) -> u32 { self.local().month() } - #[inline] fn month0(&self) -> u32 { self.local().month0() } - #[inline] fn day(&self) -> u32 { self.local().day() } - #[inline] fn day0(&self) -> u32 { self.local().day0() } - #[inline] fn ordinal(&self) -> u32 { self.local().ordinal() } - #[inline] fn ordinal0(&self) -> u32 { self.local().ordinal0() } - #[inline] fn weekday(&self) -> Weekday { self.local().weekday() } - #[inline] fn isoweekdate(&self) -> (i32, u32, Weekday) { self.local().isoweekdate() } +impl Datelike for DateTime { + #[inline] fn year(&self) -> i32 { self.naive_local().year() } + #[inline] fn month(&self) -> u32 { self.naive_local().month() } + #[inline] fn month0(&self) -> u32 { self.naive_local().month0() } + #[inline] fn day(&self) -> u32 { self.naive_local().day() } + #[inline] fn day0(&self) -> u32 { self.naive_local().day0() } + #[inline] fn ordinal(&self) -> u32 { self.naive_local().ordinal() } + #[inline] fn ordinal0(&self) -> u32 { self.naive_local().ordinal0() } + #[inline] fn weekday(&self) -> Weekday { self.naive_local().weekday() } + #[inline] fn isoweekdate(&self) -> (i32, u32, Weekday) { self.naive_local().isoweekdate() } #[inline] fn with_year(&self, year: i32) -> Option> { - self.local().with_year(year) - .and_then(|datetime| self.offset.from_local_datetime(&datetime).single()) + map_local(self, |datetime| datetime.with_year(year)) } #[inline] fn with_month(&self, month: u32) -> Option> { - self.local().with_month(month) - .and_then(|datetime| self.offset.from_local_datetime(&datetime).single()) + map_local(self, |datetime| datetime.with_month(month)) } #[inline] fn with_month0(&self, month0: u32) -> Option> { - self.local().with_month0(month0) - .and_then(|datetime| self.offset.from_local_datetime(&datetime).single()) + map_local(self, |datetime| datetime.with_month0(month0)) } #[inline] fn with_day(&self, day: u32) -> Option> { - self.local().with_day(day) - .and_then(|datetime| self.offset.from_local_datetime(&datetime).single()) + map_local(self, |datetime| datetime.with_day(day)) } #[inline] fn with_day0(&self, day0: u32) -> Option> { - self.local().with_day0(day0) - .and_then(|datetime| self.offset.from_local_datetime(&datetime).single()) + map_local(self, |datetime| datetime.with_day0(day0)) } #[inline] fn with_ordinal(&self, ordinal: u32) -> Option> { - self.local().with_ordinal(ordinal) - .and_then(|datetime| self.offset.from_local_datetime(&datetime).single()) + map_local(self, |datetime| datetime.with_ordinal(ordinal)) } #[inline] fn with_ordinal0(&self, ordinal0: u32) -> Option> { - self.local().with_ordinal0(ordinal0) - .and_then(|datetime| self.offset.from_local_datetime(&datetime).single()) + map_local(self, |datetime| datetime.with_ordinal0(ordinal0)) } } -impl Timelike for DateTime { - #[inline] fn hour(&self) -> u32 { self.local().hour() } - #[inline] fn minute(&self) -> u32 { self.local().minute() } - #[inline] fn second(&self) -> u32 { self.local().second() } - #[inline] fn nanosecond(&self) -> u32 { self.local().nanosecond() } +impl Timelike for DateTime { + #[inline] fn hour(&self) -> u32 { self.naive_local().hour() } + #[inline] fn minute(&self) -> u32 { self.naive_local().minute() } + #[inline] fn second(&self) -> u32 { self.naive_local().second() } + #[inline] fn nanosecond(&self) -> u32 { self.naive_local().nanosecond() } #[inline] fn with_hour(&self, hour: u32) -> Option> { - self.local().with_hour(hour) - .and_then(|datetime| self.offset.from_local_datetime(&datetime).single()) + map_local(self, |datetime| datetime.with_hour(hour)) } #[inline] fn with_minute(&self, min: u32) -> Option> { - self.local().with_minute(min) - .and_then(|datetime| self.offset.from_local_datetime(&datetime).single()) + map_local(self, |datetime| datetime.with_minute(min)) } #[inline] fn with_second(&self, sec: u32) -> Option> { - self.local().with_second(sec) - .and_then(|datetime| self.offset.from_local_datetime(&datetime).single()) + map_local(self, |datetime| datetime.with_second(sec)) } #[inline] fn with_nanosecond(&self, nano: u32) -> Option> { - self.local().with_nanosecond(nano) - .and_then(|datetime| self.offset.from_local_datetime(&datetime).single()) + map_local(self, |datetime| datetime.with_nanosecond(nano)) } } -impl PartialEq> for DateTime { +impl PartialEq> for DateTime { fn eq(&self, other: &DateTime) -> bool { self.datetime == other.datetime } } -impl Eq for DateTime { +impl Eq for DateTime { } -impl PartialOrd for DateTime { +impl PartialOrd for DateTime { fn partial_cmp(&self, other: &DateTime) -> Option { self.datetime.partial_cmp(&other.datetime) } } -impl Ord for DateTime { +impl Ord for DateTime { fn cmp(&self, other: &DateTime) -> Ordering { self.datetime.cmp(&other.datetime) } } @@ -186,7 +196,7 @@ impl hash::Hash for DateTime Add for DateTime { +impl Add for DateTime { type Output = DateTime; fn add(self, rhs: Duration) -> DateTime { @@ -194,13 +204,13 @@ impl Add for DateTime { } } -impl Sub> for DateTime { +impl Sub> for DateTime { type Output = Duration; fn sub(self, rhs: DateTime) -> Duration { self.datetime - rhs.datetime } } -impl Sub for DateTime { +impl Sub for DateTime { type Output = DateTime; #[inline] @@ -209,13 +219,13 @@ impl Sub for DateTime { impl fmt::Debug for DateTime { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}{:?}", self.local(), self.offset) + write!(f, "{:?}{:?}", self.naive_local(), self.offset) } } -impl fmt::Display for DateTime { +impl fmt::Display for DateTime where Off::State: fmt::Display { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} {}", self.local(), self.offset) + write!(f, "{} {}", self.naive_local(), self.offset) } } @@ -223,7 +233,10 @@ impl fmt::Display for DateTime { mod tests { use {Datelike}; use duration::Duration; - use offset::{Offset, UTC, Local, FixedOffset}; + use offset::Offset; + use offset::utc::UTC; + use offset::local::Local; + use offset::fixed::FixedOffset; #[test] #[allow(non_snake_case)] @@ -255,7 +268,7 @@ mod tests { fn test_datetime_fmt_with_local() { // if we are not around the year boundary, local and UTC date should have the same year let dt = Local::now().with_month(5).unwrap(); - assert_eq!(dt.format("%Y").to_string(), dt.with_offset(UTC).format("%Y").to_string()); + assert_eq!(dt.format("%Y").to_string(), dt.with_timezone(&UTC).format("%Y").to_string()); } } diff --git a/src/format.rs b/src/format.rs index 1417e10..d9271e5 100644 --- a/src/format.rs +++ b/src/format.rs @@ -10,7 +10,7 @@ use std::fmt; use {Datelike, Timelike}; use duration::Duration; -use offset::Offset; +use offset::OffsetState; use naive::date::NaiveDate; use naive::time::NaiveTime; @@ -198,11 +198,11 @@ impl<'a> DelayedFormat<'a> { DelayedFormat { date: date, time: time, off: None, fmt: fmt } } - /// Makes a new `DelayedFormat` value out of local date and time and UTC offset. + /// Makes a new `DelayedFormat` value out of local date and time with offset state. pub fn new_with_offset(date: Option, time: Option, - offset: &Off, fmt: &'a str) -> DelayedFormat<'a> - where Off: Offset + fmt::Display { - let name_and_diff = (offset.to_string(), offset.local_minus_utc()); + state: &Off, fmt: &'a str) -> DelayedFormat<'a> + where Off: OffsetState + fmt::Display { + let name_and_diff = (state.to_string(), state.local_minus_utc()); DelayedFormat { date: date, time: time, off: Some(name_and_diff), fmt: fmt } } } diff --git a/src/lib.rs b/src/lib.rs index 354f91a..edf58b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,7 +41,7 @@ You can get the current date and time in the UTC timezone (`UTC::now()`) or in the local timezone (`Local::now()`). ~~~~ {.rust} -use chrono::{UTC, Local, DateTime}; +use chrono::*; let utc: DateTime = UTC::now(); // e.g. `2014-11-28T12:45:59.324310806Z` let local: DateTime = Local::now(); // e.g. `2014-11-28T21:45:59.324310806+09:00` @@ -53,7 +53,7 @@ This is a bit verbose due to Rust's lack of function and method overloading, but in turn we get a rich combination of initialization methods. ~~~~ {.rust} -use chrono::{UTC, Offset, Weekday, LocalResult}; +use chrono::*; let dt = UTC.ymd(2014, 7, 8).and_hms(9, 10, 11); // `2014-07-08T09:10:11Z` // July 8 is 188th day of the year 2014 (`o` for "ordinal") @@ -78,13 +78,12 @@ Addition and subtraction is also supported. The following illustrates most supported operations to the date and time: ~~~~ {.rust} -# /* we intentionally fake the datetime... -use chrono::{UTC, Local, Datelike, Timelike, Weekday, Duration}; +use chrono::*; +# /* we intentionally fake the datetime... // assume this returned `2014-11-28T21:45:59.324310806+09:00`: let dt = Local::now(); # */ // up to here. we now define a fixed datetime for the illustrative purpose. -# use chrono::{UTC, FixedOffset, Offset, Datelike, Timelike, Weekday, Duration}; # let dt = FixedOffset::east(9*3600).ymd(2014, 11, 28).and_hms_nano(21, 45, 59, 324310806); // property accessors @@ -98,7 +97,7 @@ assert_eq!(dt.num_days_from_ce(), 735565); // the number of days from and includ // offset accessor and manipulation assert_eq!(dt.offset().local_minus_utc(), Duration::hours(9)); -assert_eq!(dt.with_offset(UTC), UTC.ymd(2014, 11, 28).and_hms_nano(12, 45, 59, 324310806)); +assert_eq!(dt.with_timezone(&UTC), UTC.ymd(2014, 11, 28).and_hms_nano(12, 45, 59, 324310806)); // a sample of property manipulations (validates dynamically) assert_eq!(dt.with_day(29).unwrap().weekday(), Weekday::Sat); // 2014-11-29 is Saturday @@ -119,7 +118,7 @@ which format is equivalent to the familiar `strftime` format. The default `to_string` method and `{:?}` specifier also give a reasonable representation. ~~~~ {.rust} -use chrono::{UTC, Offset}; +use chrono::*; let dt = UTC.ymd(2014, 11, 28).and_hms(12, 0, 9); assert_eq!(dt.format("%Y-%m-%d %H:%M:%S").to_string(), "2014-11-28 12:00:09"); @@ -138,7 +137,7 @@ Most operations available to `DateTime` are also available to `Date` and `Time` whenever appropriate. ~~~~ {.rust} -use chrono::{UTC, Local, Offset, LocalResult, Datelike, Weekday}; +use chrono::*; # // these *may* fail, but only very rarely. just rerun the test if you were that unfortunate ;) assert_eq!(UTC::today(), UTC::now().date()); @@ -192,8 +191,10 @@ Advanced offset handling and date/time parsing is not yet supported (but is plan extern crate "time" as stdtime; pub use duration::Duration; -pub use offset::{Offset, LocalResult}; -pub use offset::{UTC, FixedOffset, Local}; +pub use offset::{Offset, OffsetState, LocalResult}; +pub use offset::utc::UTC; +pub use offset::fixed::FixedOffset; +pub use offset::local::Local; pub use naive::date::NaiveDate; pub use naive::time::NaiveTime; pub use naive::datetime::NaiveDateTime; diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs new file mode 100644 index 0000000..3162104 --- /dev/null +++ b/src/offset/fixed.rs @@ -0,0 +1,107 @@ +// This is a part of rust-chrono. +// Copyright (c) 2015, Kang Seonghoon. +// See README.md and LICENSE.txt for details. + +/*! + * The time zone which has a fixed offset from UTC. + */ + +use std::fmt; + +use div::div_mod_floor; +use duration::Duration; +use naive::date::NaiveDate; +use naive::time::NaiveTime; +use naive::datetime::NaiveDateTime; +use super::{Offset, OffsetState, LocalResult}; + +/// The fixed offset (and also state), from UTC-23:59:59 to UTC+23:59:59. +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct FixedOffset { + local_minus_utc: i32, +} + +impl FixedOffset { + /// Makes a new `FixedOffset` for the Eastern Hemisphere with given timezone difference. + /// The negative `secs` means the Western Hemisphere. + /// + /// Fails on the out-of-bound `secs`. + pub fn east(secs: i32) -> FixedOffset { + FixedOffset::east_opt(secs).expect("FixedOffset::east out of bounds") + } + + /// Makes a new `FixedOffset` for the Eastern Hemisphere with given timezone difference. + /// The negative `secs` means the Western Hemisphere. + /// + /// Returns `None` on the out-of-bound `secs`. + pub fn east_opt(secs: i32) -> Option { + if -86400 < secs && secs < 86400 { + Some(FixedOffset { local_minus_utc: secs }) + } else { + None + } + } + + /// Makes a new `FixedOffset` for the Western Hemisphere with given timezone difference. + /// The negative `secs` means the Eastern Hemisphere. + /// + /// Fails on the out-of-bound `secs`. + pub fn west(secs: i32) -> FixedOffset { + FixedOffset::west_opt(secs).expect("FixedOffset::west out of bounds") + } + + /// Makes a new `FixedOffset` for the Western Hemisphere with given timezone difference. + /// The negative `secs` means the Eastern Hemisphere. + /// + /// Returns `None` on the out-of-bound `secs`. + pub fn west_opt(secs: i32) -> Option { + if -86400 < secs && secs < 86400 { + Some(FixedOffset { local_minus_utc: -secs }) + } else { + None + } + } +} + +impl Offset for FixedOffset { + type State = FixedOffset; + + fn from_state(state: &FixedOffset) -> FixedOffset { state.clone() } + + fn state_from_local_date(&self, _local: &NaiveDate) -> LocalResult { + LocalResult::Single(self.clone()) + } + fn state_from_local_time(&self, _local: &NaiveTime) -> LocalResult { + LocalResult::Single(self.clone()) + } + fn state_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult { + LocalResult::Single(self.clone()) + } + + fn state_from_utc_date(&self, _utc: &NaiveDate) -> FixedOffset { self.clone() } + fn state_from_utc_time(&self, _utc: &NaiveTime) -> FixedOffset { self.clone() } + fn state_from_utc_datetime(&self, _utc: &NaiveDateTime) -> FixedOffset { self.clone() } +} + +impl OffsetState for FixedOffset { + fn local_minus_utc(&self) -> Duration { Duration::seconds(self.local_minus_utc as i64) } +} + +impl fmt::Debug for FixedOffset { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let offset = self.local_minus_utc; + let (sign, offset) = if offset < 0 {('-', -offset)} else {('+', offset)}; + let (mins, sec) = div_mod_floor(offset, 60); + let (hour, min) = div_mod_floor(mins, 60); + if sec == 0 { + write!(f, "{}{:02}:{:02}", sign, hour, min) + } else { + write!(f, "{}{:02}:{:02}:{:02}", sign, hour, min, sec) + } + } +} + +impl fmt::Display for FixedOffset { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(self, f) } +} + diff --git a/src/offset/local.rs b/src/offset/local.rs new file mode 100644 index 0000000..7991458 --- /dev/null +++ b/src/offset/local.rs @@ -0,0 +1,122 @@ +// This is a part of rust-chrono. +// Copyright (c) 2015, Kang Seonghoon. +// See README.md and LICENSE.txt for details. + +/*! + * The local (system) time zone. + */ + +use stdtime; + +use {Datelike, Timelike}; +use duration::Duration; +use naive::date::NaiveDate; +use naive::time::NaiveTime; +use naive::datetime::NaiveDateTime; +use date::Date; +use time::Time; +use datetime::DateTime; +use super::{Offset, LocalResult}; +use super::fixed::FixedOffset; + +/// Converts a `time::Tm` struct into the timezone-aware `DateTime`. +/// This assumes that `time` is working correctly, i.e. any error is fatal. +fn tm_to_datetime(mut tm: stdtime::Tm) -> DateTime { + if tm.tm_sec >= 60 { + tm.tm_sec = 59; + tm.tm_nsec += (tm.tm_sec - 59) * 1_000_000_000; + } + + // from_yo is more efficient than from_ymd (since it's the internal representation). + 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(tm.tm_utcoff); + DateTime::from_utc(date.and_time(time) + Duration::seconds(-tm.tm_utcoff as i64), offset) +} + +/// Converts a local `NaiveDateTime` to the `time::Timespec`. +fn datetime_to_timespec(d: &NaiveDateTime) -> stdtime::Timespec { + let tm = stdtime::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, + tm_utcoff: 1, // this is arbitrary but should be nonzero + // in order to make `to_timespec` use `rust_mktime` internally. + tm_nsec: d.nanosecond() as i32, + }; + tm.to_timespec() +} + +/// The local timescale. This is implemented via the standard `time` crate. +#[derive(Copy, Clone)] +pub struct Local; + +impl Local { + /// Returns a `Date` which corresponds to the current date. + pub fn today() -> Date { + Local::now().date() + } + + /// Returns a `DateTime` which corresponds to the current date. + pub fn now() -> DateTime { + tm_to_datetime(stdtime::now()) + } +} + +impl Offset for Local { + type State = FixedOffset; + + fn from_state(_state: &FixedOffset) -> Local { Local } + + // they are easier to define in terms of the finished date and time unlike other offsets + fn state_from_local_date(&self, local: &NaiveDate) -> LocalResult { + self.from_local_date(local).map(|&: date| *date.offset()) + } + fn state_from_local_time(&self, local: &NaiveTime) -> LocalResult { + self.from_local_time(local).map(|&: time| *time.offset()) + } + fn state_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult { + self.from_local_datetime(local).map(|&: datetime| *datetime.offset()) + } + + fn state_from_utc_date(&self, utc: &NaiveDate) -> FixedOffset { + *self.from_utc_date(utc).offset() + } + fn state_from_utc_time(&self, utc: &NaiveTime) -> FixedOffset { + *self.from_utc_time(utc).offset() + } + fn state_from_utc_datetime(&self, utc: &NaiveDateTime) -> FixedOffset { + *self.from_utc_datetime(utc).offset() + } + + // override them for avoiding redundant works + fn from_local_date(&self, local: &NaiveDate) -> LocalResult> { + self.from_local_datetime(&local.and_hms(0, 0, 0)).map(|datetime| datetime.date()) + } + fn from_local_time(&self, _local: &NaiveTime) -> LocalResult> { + LocalResult::None // we have no information about this time + } + fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { + let timespec = datetime_to_timespec(local); + LocalResult::Single(tm_to_datetime(stdtime::at(timespec))) + } + + fn from_utc_date(&self, utc: &NaiveDate) -> Date { + self.from_utc_datetime(&utc.and_hms(0, 0, 0)).date() + } + fn from_utc_time(&self, _utc: &NaiveTime) -> Time { + unimplemented!() // we have no information about this time + } + fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime { + let timespec = datetime_to_timespec(utc); + tm_to_datetime(stdtime::at_utc(timespec)) + } +} + diff --git a/src/offset.rs b/src/offset/mod.rs similarity index 59% rename from src/offset.rs rename to src/offset/mod.rs index 6d61a24..1271ca0 100644 --- a/src/offset.rs +++ b/src/offset/mod.rs @@ -4,13 +4,25 @@ /*! * Offsets from the local time to UTC. + * + * There are three operations provided by the `Offset` trait: + * + * 1. Converting the local `NaiveDateTime` to `DateTime` + * 2. Converting the UTC `NaiveDateTime` to `DateTime` + * 3. Converting `DateTime` to the local `NaiveDateTime` + * + * 1 is used for constructors. 2 is used for the `with_offset` method of date and time types. + * 3 is used for other methods, e.g. `year()` or `format()`, and provided by an associated type + * which implements `OffsetState` (which then passed to `Offset` for actual implementations). + * Technically speaking `Offset` has a total knowledge about given timescale, + * but `OffsetState` is used as a cache to avoid the repeated conversion + * and provides implementations for 1 and 3. + * An `Offset` instance can be reconstructed from the corresponding `OffsetState` instance. */ use std::fmt; -use stdtime; -use {Weekday, Datelike, Timelike}; -use div::div_mod_floor; +use Weekday; use duration::Duration; use naive::date::NaiveDate; use naive::time::NaiveTime; @@ -47,9 +59,18 @@ impl LocalResult { pub fn latest(self) -> Option { match self { LocalResult::Single(t) | LocalResult::Ambiguous(_,t) => Some(t), _ => None } } + + /// Maps a `LocalResult` into `LocalResult` with given function. + pub fn map U>(self, mut f: F) -> LocalResult { + match self { + LocalResult::None => LocalResult::None, + LocalResult::Single(v) => LocalResult::Single(f(v)), + LocalResult::Ambiguous(min, max) => LocalResult::Ambiguous(f(min), f(max)), + } + } } -impl LocalResult> { +impl LocalResult> { /// Makes a new `DateTime` from the current date and given `NaiveTime`. /// The offset in the current date is preserved. /// @@ -136,8 +157,16 @@ impl LocalResult { } } +/// The offset state. +pub trait OffsetState: Sized + Clone + fmt::Debug { + /// Returns the offset from UTC to the local time stored in the offset state. + fn local_minus_utc(&self) -> Duration; +} + /// The offset from the local time to UTC. -pub trait Offset: Clone + fmt::Debug { +pub trait Offset: Sized { + type State: OffsetState; + /// Makes a new `Date` from year, month, day and the current offset. /// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE. /// @@ -291,258 +320,68 @@ pub trait Offset: Clone + fmt::Debug { } } - /// Returns the *current* offset from UTC to the local time. - fn local_minus_utc(&self) -> Duration; + /// Reconstructs the offset from the offset state. + fn from_state(state: &Self::State) -> Self; + + /// Creates the offset state(s) for given local `NaiveDate` if possible. + fn state_from_local_date(&self, local: &NaiveDate) -> LocalResult; + + /// Creates the offset state(s) for given local `NaiveTime` if possible. + fn state_from_local_time(&self, local: &NaiveTime) -> LocalResult; + + /// Creates the offset state(s) for given local `NaiveDateTime` if possible. + fn state_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult; /// Converts the local `NaiveDate` to the timezone-aware `Date` if possible. - fn from_local_date(&self, local: &NaiveDate) -> LocalResult>; + fn from_local_date(&self, local: &NaiveDate) -> LocalResult> { + self.state_from_local_date(local).map(|state| { + Date::from_utc(*local - state.local_minus_utc(), state) + }) + } /// Converts the local `NaiveTime` to the timezone-aware `Time` if possible. - fn from_local_time(&self, local: &NaiveTime) -> LocalResult>; + fn from_local_time(&self, local: &NaiveTime) -> LocalResult> { + self.state_from_local_time(local).map(|state| { + Time::from_utc(*local - state.local_minus_utc(), state) + }) + } /// Converts the local `NaiveDateTime` to the timezone-aware `DateTime` if possible. - fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult>; + fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { + self.state_from_local_datetime(local).map(|state| { + DateTime::from_utc(*local - state.local_minus_utc(), state) + }) + } + + /// Creates the offset state for given UTC `NaiveDate`. This cannot fail. + fn state_from_utc_date(&self, utc: &NaiveDate) -> Self::State; + + /// Creates the offset state for given UTC `NaiveTime`. This cannot fail. + fn state_from_utc_time(&self, utc: &NaiveTime) -> Self::State; + + /// Creates the offset state for given UTC `NaiveDateTime`. This cannot fail. + fn state_from_utc_datetime(&self, utc: &NaiveDateTime) -> Self::State; /// Converts the UTC `NaiveDate` to the local time. /// The UTC is continuous and thus this cannot fail (but can give the duplicate local time). - fn to_local_date(&self, utc: &NaiveDate) -> NaiveDate; + fn from_utc_date(&self, utc: &NaiveDate) -> Date { + Date::from_utc(utc.clone(), self.state_from_utc_date(utc)) + } /// Converts the UTC `NaiveTime` to the local time. /// The UTC is continuous and thus this cannot fail (but can give the duplicate local time). - fn to_local_time(&self, utc: &NaiveTime) -> NaiveTime; + fn from_utc_time(&self, utc: &NaiveTime) -> Time { + Time::from_utc(utc.clone(), self.state_from_utc_time(utc)) + } /// Converts the UTC `NaiveDateTime` to the local time. /// The UTC is continuous and thus this cannot fail (but can give the duplicate local time). - fn to_local_datetime(&self, utc: &NaiveDateTime) -> NaiveDateTime; -} - -/// The UTC timescale. This is the most efficient offset when you don't need the local time. -#[derive(Copy, Clone, PartialEq, Eq)] -pub struct UTC; - -impl UTC { - /// Returns a `Date` which corresponds to the current date. - pub fn today() -> Date { UTC::now().date() } - - /// Returns a `DateTime` which corresponds to the current date. - pub fn now() -> DateTime { - let spec = stdtime::get_time(); - let naive = NaiveDateTime::from_num_seconds_from_unix_epoch(spec.sec, spec.nsec as u32); - DateTime::from_utc(naive, UTC) + fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime { + DateTime::from_utc(utc.clone(), self.state_from_utc_datetime(utc)) } } -impl Offset for UTC { - fn local_minus_utc(&self) -> Duration { Duration::zero() } - - fn from_local_date(&self, local: &NaiveDate) -> LocalResult> { - LocalResult::Single(Date::from_utc(local.clone(), UTC)) - } - fn from_local_time(&self, local: &NaiveTime) -> LocalResult> { - LocalResult::Single(Time::from_utc(local.clone(), UTC)) - } - fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { - LocalResult::Single(DateTime::from_utc(local.clone(), UTC)) - } - - fn to_local_date(&self, utc: &NaiveDate) -> NaiveDate { utc.clone() } - fn to_local_time(&self, utc: &NaiveTime) -> NaiveTime { utc.clone() } - fn to_local_datetime(&self, utc: &NaiveDateTime) -> NaiveDateTime { utc.clone() } -} - -impl fmt::Debug for UTC { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Z") } -} - -impl fmt::Display for UTC { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "UTC") } -} - -/// The fixed offset, from UTC-23:59:59 to UTC+23:59:59. -#[derive(Copy, Clone, PartialEq, Eq)] -pub struct FixedOffset { - local_minus_utc: i32, -} - -impl FixedOffset { - /// Makes a new `FixedOffset` for the Eastern Hemisphere with given timezone difference. - /// The negative `secs` means the Western Hemisphere. - /// - /// Fails on the out-of-bound `secs`. - pub fn east(secs: i32) -> FixedOffset { - FixedOffset::east_opt(secs).expect("FixedOffset::east out of bounds") - } - - /// Makes a new `FixedOffset` for the Eastern Hemisphere with given timezone difference. - /// The negative `secs` means the Western Hemisphere. - /// - /// Returns `None` on the out-of-bound `secs`. - pub fn east_opt(secs: i32) -> Option { - if -86400 < secs && secs < 86400 { - Some(FixedOffset { local_minus_utc: secs }) - } else { - None - } - } - - /// Makes a new `FixedOffset` for the Western Hemisphere with given timezone difference. - /// The negative `secs` means the Eastern Hemisphere. - /// - /// Fails on the out-of-bound `secs`. - pub fn west(secs: i32) -> FixedOffset { - FixedOffset::west_opt(secs).expect("FixedOffset::west out of bounds") - } - - /// Makes a new `FixedOffset` for the Western Hemisphere with given timezone difference. - /// The negative `secs` means the Eastern Hemisphere. - /// - /// Returns `None` on the out-of-bound `secs`. - pub fn west_opt(secs: i32) -> Option { - if -86400 < secs && secs < 86400 { - Some(FixedOffset { local_minus_utc: -secs }) - } else { - None - } - } -} - -impl Offset for FixedOffset { - fn local_minus_utc(&self) -> Duration { Duration::seconds(self.local_minus_utc as i64) } - - fn from_local_date(&self, local: &NaiveDate) -> LocalResult> { - LocalResult::Single(Date::from_utc(local.clone(), self.clone())) - } - fn from_local_time(&self, local: &NaiveTime) -> LocalResult> { - let t = Time::from_utc(*local + Duration::seconds(-self.local_minus_utc as i64), - self.clone()); - LocalResult::Single(t) - } - fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { - let dt = DateTime::from_utc(*local + Duration::seconds(-self.local_minus_utc as i64), - self.clone()); - LocalResult::Single(dt) - } - - fn to_local_date(&self, utc: &NaiveDate) -> NaiveDate { - utc.clone() - } - fn to_local_time(&self, utc: &NaiveTime) -> NaiveTime { - *utc + Duration::seconds(self.local_minus_utc as i64) - } - fn to_local_datetime(&self, utc: &NaiveDateTime) -> NaiveDateTime { - *utc + Duration::seconds(self.local_minus_utc as i64) - } -} - -impl fmt::Debug for FixedOffset { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let offset = self.local_minus_utc; - let (sign, offset) = if offset < 0 {('-', -offset)} else {('+', offset)}; - let (mins, sec) = div_mod_floor(offset, 60); - let (hour, min) = div_mod_floor(mins, 60); - if sec == 0 { - write!(f, "{}{:02}:{:02}", sign, hour, min) - } else { - write!(f, "{}{:02}:{:02}:{:02}", sign, hour, min, sec) - } - } -} - -impl fmt::Display for FixedOffset { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(self, f) } -} - -/// The local timescale. This is implemented via the standard `time` crate. -#[derive(Copy, Clone)] -pub struct Local { - cached: FixedOffset, -} - -impl Local { - /// Converts a `time::Tm` struct into the timezone-aware `DateTime`. - /// This assumes that `time` is working correctly, i.e. any error is fatal. - fn tm_to_datetime(mut tm: stdtime::Tm) -> DateTime { - if tm.tm_sec >= 60 { - tm.tm_sec = 59; - tm.tm_nsec += (tm.tm_sec - 59) * 1_000_000_000; - } - - // from_yo is more efficient than from_ymd (since it's the internal representation). - 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 = Local { cached: FixedOffset::east(tm.tm_utcoff) }; - DateTime::from_utc(date.and_time(time) + Duration::seconds(-tm.tm_utcoff as i64), offset) - } - - /// Converts a local `NaiveDateTime` to the `time::Timespec`. - fn datetime_to_timespec(d: &NaiveDateTime) -> stdtime::Timespec { - let tm = stdtime::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, - tm_utcoff: 1, // this is arbitrary but should be nonzero - // in order to make `to_timespec` use `rust_mktime` internally. - tm_nsec: d.nanosecond() as i32, - }; - tm.to_timespec() - } - - /// Returns a `Date` which corresponds to the current date. - pub fn today() -> Date { - Local::now().date() - } - - /// Returns a `DateTime` which corresponds to the current date. - pub fn now() -> DateTime { - Local::tm_to_datetime(stdtime::now()) - } -} - -impl Offset for Local { - fn local_minus_utc(&self) -> Duration { self.cached.local_minus_utc() } - - fn from_local_date(&self, local: &NaiveDate) -> LocalResult> { - match self.from_local_datetime(&local.and_hms(0, 0, 0)) { - LocalResult::None => LocalResult::None, - LocalResult::Single(dt) => LocalResult::Single(dt.date()), - LocalResult::Ambiguous(min, max) => { - let min = min.date(); - let max = max.date(); - if min == max {LocalResult::Single(min)} else {LocalResult::Ambiguous(min, max)} - } - } - } - - fn from_local_time(&self, local: &NaiveTime) -> LocalResult> { - // XXX we don't have enough information here, so we assume that the timezone remains same - LocalResult::Single(Time::from_utc(local.clone(), self.clone())) - } - - fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult> { - let timespec = Local::datetime_to_timespec(local); - LocalResult::Single(Local::tm_to_datetime(stdtime::at(timespec))) - } - - fn to_local_date(&self, utc: &NaiveDate) -> NaiveDate { self.cached.to_local_date(utc) } - fn to_local_time(&self, utc: &NaiveTime) -> NaiveTime { self.cached.to_local_time(utc) } - fn to_local_datetime(&self, utc: &NaiveDateTime) -> NaiveDateTime { - self.cached.to_local_datetime(utc) - } -} - -impl fmt::Debug for Local { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.cached.fmt(f) } -} - -impl fmt::Display for Local { - // TODO this should be a tz name whenever available - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.cached.fmt(f) } -} +pub mod utc; +pub mod fixed; +pub mod local; diff --git a/src/offset/utc.rs b/src/offset/utc.rs new file mode 100644 index 0000000..229355a --- /dev/null +++ b/src/offset/utc.rs @@ -0,0 +1,68 @@ +// This is a part of rust-chrono. +// Copyright (c) 2015, Kang Seonghoon. +// See README.md and LICENSE.txt for details. + +/*! + * The UTC (Coordinated Universal Time) time zone. + */ + +use std::fmt; +use stdtime; + +use duration::Duration; +use naive::date::NaiveDate; +use naive::time::NaiveTime; +use naive::datetime::NaiveDateTime; +use date::Date; +use datetime::DateTime; +use super::{Offset, OffsetState, LocalResult}; + +/// The UTC offset. This is the most efficient offset when you don't need the local time. +/// It is also used as an offset state (which is also a dummy type). +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct UTC; + +impl UTC { + /// Returns a `Date` which corresponds to the current date. + pub fn today() -> Date { UTC::now().date() } + + /// Returns a `DateTime` which corresponds to the current date. + pub fn now() -> DateTime { + let spec = stdtime::get_time(); + let naive = NaiveDateTime::from_num_seconds_from_unix_epoch(spec.sec, spec.nsec as u32); + DateTime::from_utc(naive, UTC) + } +} + +impl Offset for UTC { + type State = UTC; + + fn from_state(_state: &UTC) -> UTC { UTC } + + fn state_from_local_date(&self, _local: &NaiveDate) -> LocalResult { + LocalResult::Single(UTC) + } + fn state_from_local_time(&self, _local: &NaiveTime) -> LocalResult { + LocalResult::Single(UTC) + } + fn state_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult { + LocalResult::Single(UTC) + } + + fn state_from_utc_date(&self, _utc: &NaiveDate) -> UTC { UTC } + fn state_from_utc_time(&self, _utc: &NaiveTime) -> UTC { UTC } + fn state_from_utc_datetime(&self, _utc: &NaiveDateTime) -> UTC { UTC} +} + +impl OffsetState for UTC { + fn local_minus_utc(&self) -> Duration { Duration::zero() } +} + +impl fmt::Debug for UTC { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Z") } +} + +impl fmt::Display for UTC { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "UTC") } +} + diff --git a/src/time.rs b/src/time.rs index ded3b95..965d113 100644 --- a/src/time.rs +++ b/src/time.rs @@ -11,102 +11,119 @@ use std::cmp::Ordering; use std::ops::{Add, Sub}; use Timelike; -use offset::Offset; +use offset::{Offset, OffsetState}; use duration::Duration; use naive::time::NaiveTime; use format::DelayedFormat; /// ISO 8601 time with timezone. #[derive(Clone)] -pub struct Time { +pub struct Time { time: NaiveTime, - offset: Off, + offset: Off::State, } -impl Time { +impl Time { /// Makes a new `Time` with given *UTC* time and offset. /// The local time should be constructed via the `Offset` trait. + // + // note: this constructor is purposedly not named to `new` to discourage the direct usage. #[inline] - pub fn from_utc(time: NaiveTime, offset: Off) -> Time { + pub fn from_utc(time: NaiveTime, offset: Off::State) -> Time { Time { time: time, offset: offset } } /// Retrieves an associated offset. #[inline] - pub fn offset<'a>(&'a self) -> &'a Off { + pub fn offset<'a>(&'a self) -> &'a Off::State { &self.offset } + /// Retrieves an associated offset. + #[inline] + pub fn timezone(&self) -> Off { + Offset::from_state(&self.offset) + } + /// Changes the associated offset. /// This does not change the actual `Time` (but will change the string representation). #[inline] - pub fn with_offset(&self, offset: Off2) -> Time { - Time::from_utc(self.time, offset) + pub fn with_timezone(&self, tz: &Off2) -> Time { + tz.from_utc_time(&self.time) } - /// Returns a view to the local time. - fn local(&self) -> NaiveTime { - self.offset.to_local_time(&self.time) + /// Returns a view to the naive UTC time. + #[inline] + pub fn naive_utc(&self) -> NaiveTime { + self.time + } + + /// Returns a view to the naive local time. + #[inline] + pub fn naive_local(&self) -> NaiveTime { + self.time + self.offset.local_minus_utc() } } -impl Time { +/// Maps the local time to other time with given conversion function. +fn map_local(t: &Time, mut f: F) -> Option> + where F: FnMut(NaiveTime) -> Option { + f(t.naive_local()).and_then(|time| t.timezone().from_local_time(&time).single()) +} + +impl Time where Off::State: fmt::Display { /// Formats the time in the specified format string. /// See the `format` module on the supported escape sequences. #[inline] pub fn format<'a>(&'a self, fmt: &'a str) -> DelayedFormat<'a> { - DelayedFormat::new_with_offset(None, Some(self.local()), &self.offset, fmt) + DelayedFormat::new_with_offset(None, Some(self.naive_local()), &self.offset, fmt) } } -impl Timelike for Time { - #[inline] fn hour(&self) -> u32 { self.local().hour() } - #[inline] fn minute(&self) -> u32 { self.local().minute() } - #[inline] fn second(&self) -> u32 { self.local().second() } - #[inline] fn nanosecond(&self) -> u32 { self.local().nanosecond() } +impl Timelike for Time { + #[inline] fn hour(&self) -> u32 { self.naive_local().hour() } + #[inline] fn minute(&self) -> u32 { self.naive_local().minute() } + #[inline] fn second(&self) -> u32 { self.naive_local().second() } + #[inline] fn nanosecond(&self) -> u32 { self.naive_local().nanosecond() } #[inline] fn with_hour(&self, hour: u32) -> Option> { - self.local().with_hour(hour) - .and_then(|time| self.offset.from_local_time(&time).single()) + map_local(self, |time| time.with_hour(hour)) } #[inline] fn with_minute(&self, min: u32) -> Option> { - self.local().with_minute(min) - .and_then(|time| self.offset.from_local_time(&time).single()) + map_local(self, |time| time.with_minute(min)) } #[inline] fn with_second(&self, sec: u32) -> Option> { - self.local().with_second(sec) - .and_then(|time| self.offset.from_local_time(&time).single()) + map_local(self, |time| time.with_second(sec)) } #[inline] fn with_nanosecond(&self, nano: u32) -> Option> { - self.local().with_nanosecond(nano) - .and_then(|time| self.offset.from_local_time(&time).single()) + map_local(self, |time| time.with_nanosecond(nano)) } #[inline] - fn num_seconds_from_midnight(&self) -> u32 { self.local().num_seconds_from_midnight() } + fn num_seconds_from_midnight(&self) -> u32 { self.naive_local().num_seconds_from_midnight() } } -impl PartialEq> for Time { +impl PartialEq> for Time { fn eq(&self, other: &Time) -> bool { self.time == other.time } } -impl Eq for Time { +impl Eq for Time { } -impl PartialOrd for Time { +impl PartialOrd for Time { fn partial_cmp(&self, other: &Time) -> Option { self.time.partial_cmp(&other.time) } } -impl Ord for Time { +impl Ord for Time { fn cmp(&self, other: &Time) -> Ordering { self.time.cmp(&other.time) } } @@ -114,7 +131,7 @@ impl hash::Hash for Time { fn hash(&self, state: &mut H) { self.time.hash(state) } } -impl Add for Time { +impl Add for Time { type Output = Time; fn add(self, rhs: Duration) -> Time { @@ -122,13 +139,13 @@ impl Add for Time { } } -impl Sub> for Time { +impl Sub> for Time { type Output = Duration; fn sub(self, rhs: Time) -> Duration { self.time - rhs.time } } -impl Sub for Time { +impl Sub for Time { type Output = Time; #[inline] @@ -137,13 +154,13 @@ impl Sub for Time { impl fmt::Debug for Time { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}{:?}", self.local(), self.offset) + write!(f, "{:?}{:?}", self.naive_local(), self.offset) } } -impl fmt::Display for Time { +impl fmt::Display for Time where Off::State: fmt::Display { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}{}", self.local(), self.offset) + write!(f, "{}{}", self.naive_local(), self.offset) } }