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) } }