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.
This commit is contained in:
Kang Seonghoon 2015-01-13 01:33:53 +09:00
parent 2e7a213818
commit e43cb62f10
9 changed files with 609 additions and 423 deletions

View File

@ -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<Off> {
pub struct Date<Off: Offset> {
date: NaiveDate,
offset: Off,
offset: Off::State,
}
/// The minimum possible `Date`.
@ -31,11 +32,13 @@ pub const MIN: Date<UTC> = Date { date: naive::date::MIN, offset: UTC };
/// The maximum possible `Date`.
pub const MAX: Date<UTC> = Date { date: naive::date::MAX, offset: UTC };
impl<Off:Offset> Date<Off> {
impl<Off: Offset> Date<Off> {
/// 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<Off> {
pub fn from_utc(date: NaiveDate, offset: Off::State) -> Date<Off> {
Date { date: date, offset: offset }
}
@ -45,8 +48,8 @@ impl<Off:Offset> Date<Off> {
/// Fails on invalid datetime.
#[inline]
pub fn and_time(&self, time: NaiveTime) -> Option<DateTime<Off>> {
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<Off:Offset> Date<Off> {
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<Off2:Offset>(&self, offset: Off2) -> Date<Off2> {
Date::from_utc(self.date, offset)
pub fn with_timezone<Off2: Offset>(&self, tz: &Off2) -> Date<Off2> {
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<Off: Offset + fmt::Display> Date<Off> {
/// Maps the local date to other date with given conversion function.
fn map_local<Off: Offset, F>(d: &Date<Off>, mut f: F) -> Option<Date<Off>>
where F: FnMut(NaiveDate) -> Option<NaiveDate> {
f(d.naive_local()).and_then(|date| d.timezone().from_local_date(&date).single())
}
impl<Off: Offset> Date<Off> 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<Off:Offset> Datelike for Date<Off> {
#[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<Off: Offset> Datelike for Date<Off> {
#[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<Date<Off>> {
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<Date<Off>> {
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<Date<Off>> {
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<Date<Off>> {
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<Date<Off>> {
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<Date<Off>> {
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<Date<Off>> {
self.local().with_ordinal0(ordinal0)
.and_then(|date| self.offset.from_local_date(&date).single())
map_local(self, |date| date.with_ordinal0(ordinal0))
}
}
impl<Off:Offset, Off2:Offset> PartialEq<Date<Off2>> for Date<Off> {
impl<Off: Offset, Off2: Offset> PartialEq<Date<Off2>> for Date<Off> {
fn eq(&self, other: &Date<Off2>) -> bool { self.date == other.date }
}
impl<Off:Offset> Eq for Date<Off> {
impl<Off: Offset> Eq for Date<Off> {
}
impl<Off:Offset> PartialOrd for Date<Off> {
impl<Off: Offset> PartialOrd for Date<Off> {
fn partial_cmp(&self, other: &Date<Off>) -> Option<Ordering> {
self.date.partial_cmp(&other.date)
}
}
impl<Off:Offset> Ord for Date<Off> {
impl<Off: Offset> Ord for Date<Off> {
fn cmp(&self, other: &Date<Off>) -> Ordering { self.date.cmp(&other.date) }
}
@ -265,7 +280,7 @@ impl<Off: Offset, H: hash::Hasher + hash::Writer> hash::Hash<H> for Date<Off> {
fn hash(&self, state: &mut H) { self.date.hash(state) }
}
impl<Off:Offset> Add<Duration> for Date<Off> {
impl<Off: Offset> Add<Duration> for Date<Off> {
type Output = Date<Off>;
fn add(self, rhs: Duration) -> Date<Off> {
@ -273,13 +288,13 @@ impl<Off:Offset> Add<Duration> for Date<Off> {
}
}
impl<Off:Offset, Off2:Offset> Sub<Date<Off2>> for Date<Off> {
impl<Off: Offset, Off2: Offset> Sub<Date<Off2>> for Date<Off> {
type Output = Duration;
fn sub(self, rhs: Date<Off2>) -> Duration { self.date - rhs.date }
}
impl<Off:Offset> Sub<Duration> for Date<Off> {
impl<Off: Offset> Sub<Duration> for Date<Off> {
type Output = Date<Off>;
#[inline]
@ -288,13 +303,13 @@ impl<Off:Offset> Sub<Duration> for Date<Off> {
impl<Off: Offset> fmt::Debug for Date<Off> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}{:?}", self.local(), self.offset)
write!(f, "{:?}{:?}", self.naive_local(), self.offset)
}
}
impl<Off: Offset + fmt::Display> fmt::Display for Date<Off> {
impl<Off: Offset> fmt::Display for Date<Off> 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<Date<UTC1y>> {
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<OneYear> {
LocalResult::Single(OneYear)
}
fn from_local_time(&self, local: &NaiveTime) -> LocalResult<Time<UTC1y>> {
LocalResult::Single(Time::from_utc(local.clone(), UTC1y))
fn state_from_local_time(&self, _local: &NaiveTime) -> LocalResult<OneYear> {
LocalResult::Single(OneYear)
}
fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<DateTime<UTC1y>> {
LocalResult::Single(DateTime::from_utc(*local - Duration::days(365), UTC1y))
fn state_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult<OneYear> {
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") }
}

View File

@ -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<Off> {
pub struct DateTime<Off: Offset> {
datetime: NaiveDateTime,
offset: Off,
offset: Off::State,
}
impl<Off:Offset> DateTime<Off> {
impl<Off: Offset> DateTime<Off> {
/// 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<Off> {
pub fn from_utc(datetime: NaiveDateTime, offset: Off::State) -> DateTime<Off> {
DateTime { datetime: datetime, offset: offset }
}
@ -51,134 +53,142 @@ impl<Off:Offset> DateTime<Off> {
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<Off2:Offset>(&self, offset: Off2) -> DateTime<Off2> {
DateTime::from_utc(self.datetime, offset)
pub fn with_timezone<Off2: Offset>(&self, tz: &Off2) -> DateTime<Off2> {
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<Off: Offset + fmt::Display> DateTime<Off> {
/// Maps the local datetime to other datetime with given conversion function.
fn map_local<Off: Offset, F>(dt: &DateTime<Off>, mut f: F) -> Option<DateTime<Off>>
where F: FnMut(NaiveDateTime) -> Option<NaiveDateTime> {
f(dt.naive_local()).and_then(|datetime| dt.timezone().from_local_datetime(&datetime).single())
}
impl<Off: Offset> DateTime<Off> 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<Off:Offset> Datelike for DateTime<Off> {
#[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<Off: Offset> Datelike for DateTime<Off> {
#[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<DateTime<Off>> {
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<DateTime<Off>> {
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<DateTime<Off>> {
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<DateTime<Off>> {
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<DateTime<Off>> {
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<DateTime<Off>> {
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<DateTime<Off>> {
self.local().with_ordinal0(ordinal0)
.and_then(|datetime| self.offset.from_local_datetime(&datetime).single())
map_local(self, |datetime| datetime.with_ordinal0(ordinal0))
}
}
impl<Off:Offset> Timelike for DateTime<Off> {
#[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<Off: Offset> Timelike for DateTime<Off> {
#[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<DateTime<Off>> {
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<DateTime<Off>> {
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<DateTime<Off>> {
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<DateTime<Off>> {
self.local().with_nanosecond(nano)
.and_then(|datetime| self.offset.from_local_datetime(&datetime).single())
map_local(self, |datetime| datetime.with_nanosecond(nano))
}
}
impl<Off:Offset, Off2:Offset> PartialEq<DateTime<Off2>> for DateTime<Off> {
impl<Off: Offset, Off2: Offset> PartialEq<DateTime<Off2>> for DateTime<Off> {
fn eq(&self, other: &DateTime<Off2>) -> bool { self.datetime == other.datetime }
}
impl<Off:Offset> Eq for DateTime<Off> {
impl<Off: Offset> Eq for DateTime<Off> {
}
impl<Off:Offset> PartialOrd for DateTime<Off> {
impl<Off: Offset> PartialOrd for DateTime<Off> {
fn partial_cmp(&self, other: &DateTime<Off>) -> Option<Ordering> {
self.datetime.partial_cmp(&other.datetime)
}
}
impl<Off:Offset> Ord for DateTime<Off> {
impl<Off: Offset> Ord for DateTime<Off> {
fn cmp(&self, other: &DateTime<Off>) -> Ordering { self.datetime.cmp(&other.datetime) }
}
@ -186,7 +196,7 @@ impl<Off: Offset, H: hash::Hasher + hash::Writer> hash::Hash<H> for DateTime<Off
fn hash(&self, state: &mut H) { self.datetime.hash(state) }
}
impl<Off:Offset> Add<Duration> for DateTime<Off> {
impl<Off: Offset> Add<Duration> for DateTime<Off> {
type Output = DateTime<Off>;
fn add(self, rhs: Duration) -> DateTime<Off> {
@ -194,13 +204,13 @@ impl<Off:Offset> Add<Duration> for DateTime<Off> {
}
}
impl<Off:Offset, Off2:Offset> Sub<DateTime<Off2>> for DateTime<Off> {
impl<Off: Offset, Off2: Offset> Sub<DateTime<Off2>> for DateTime<Off> {
type Output = Duration;
fn sub(self, rhs: DateTime<Off2>) -> Duration { self.datetime - rhs.datetime }
}
impl<Off:Offset> Sub<Duration> for DateTime<Off> {
impl<Off: Offset> Sub<Duration> for DateTime<Off> {
type Output = DateTime<Off>;
#[inline]
@ -209,13 +219,13 @@ impl<Off:Offset> Sub<Duration> for DateTime<Off> {
impl<Off: Offset> fmt::Debug for DateTime<Off> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}{:?}", self.local(), self.offset)
write!(f, "{:?}{:?}", self.naive_local(), self.offset)
}
}
impl<Off: Offset + fmt::Display> fmt::Display for DateTime<Off> {
impl<Off: Offset> fmt::Display for DateTime<Off> 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<Off: Offset + fmt::Display> fmt::Display for DateTime<Off> {
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());
}
}

View File

@ -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<Off>(date: Option<NaiveDate>, time: Option<NaiveTime>,
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 }
}
}

View File

@ -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> = UTC::now(); // e.g. `2014-11-28T12:45:59.324310806Z`
let local: DateTime<Local> = 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;

107
src/offset/fixed.rs Normal file
View File

@ -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<FixedOffset> {
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<FixedOffset> {
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<FixedOffset> {
LocalResult::Single(self.clone())
}
fn state_from_local_time(&self, _local: &NaiveTime) -> LocalResult<FixedOffset> {
LocalResult::Single(self.clone())
}
fn state_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult<FixedOffset> {
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) }
}

122
src/offset/local.rs Normal file
View File

@ -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<Local> {
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> {
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 {
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<FixedOffset> {
self.from_local_date(local).map(|&: date| *date.offset())
}
fn state_from_local_time(&self, local: &NaiveTime) -> LocalResult<FixedOffset> {
self.from_local_time(local).map(|&: time| *time.offset())
}
fn state_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<FixedOffset> {
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<Date<Local>> {
self.from_local_datetime(&local.and_hms(0, 0, 0)).map(|datetime| datetime.date())
}
fn from_local_time(&self, _local: &NaiveTime) -> LocalResult<Time<Local>> {
LocalResult::None // we have no information about this time
}
fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<DateTime<Local>> {
let timespec = datetime_to_timespec(local);
LocalResult::Single(tm_to_datetime(stdtime::at(timespec)))
}
fn from_utc_date(&self, utc: &NaiveDate) -> Date<Local> {
self.from_utc_datetime(&utc.and_hms(0, 0, 0)).date()
}
fn from_utc_time(&self, _utc: &NaiveTime) -> Time<Local> {
unimplemented!() // we have no information about this time
}
fn from_utc_datetime(&self, utc: &NaiveDateTime) -> DateTime<Local> {
let timespec = datetime_to_timespec(utc);
tm_to_datetime(stdtime::at_utc(timespec))
}
}

View File

@ -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<Offset>`
* 2. Converting the UTC `NaiveDateTime` to `DateTime<Offset>`
* 3. Converting `DateTime<Offset>` 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<T> LocalResult<T> {
pub fn latest(self) -> Option<T> {
match self { LocalResult::Single(t) | LocalResult::Ambiguous(_,t) => Some(t), _ => None }
}
/// Maps a `LocalResult<T>` into `LocalResult<U>` with given function.
pub fn map<U, F: FnMut(T) -> U>(self, mut f: F) -> LocalResult<U> {
match self {
LocalResult::None => LocalResult::None,
LocalResult::Single(v) => LocalResult::Single(f(v)),
LocalResult::Ambiguous(min, max) => LocalResult::Ambiguous(f(min), f(max)),
}
}
}
impl<Off:Offset> LocalResult<Date<Off>> {
impl<Off: Offset> LocalResult<Date<Off>> {
/// Makes a new `DateTime` from the current date and given `NaiveTime`.
/// The offset in the current date is preserved.
///
@ -136,8 +157,16 @@ impl<T: fmt::Debug> LocalResult<T> {
}
}
/// 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<Self::State>;
/// Creates the offset state(s) for given local `NaiveTime` if possible.
fn state_from_local_time(&self, local: &NaiveTime) -> LocalResult<Self::State>;
/// Creates the offset state(s) for given local `NaiveDateTime` if possible.
fn state_from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<Self::State>;
/// Converts the local `NaiveDate` to the timezone-aware `Date` if possible.
fn from_local_date(&self, local: &NaiveDate) -> LocalResult<Date<Self>>;
fn from_local_date(&self, local: &NaiveDate) -> LocalResult<Date<Self>> {
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<Time<Self>>;
fn from_local_time(&self, local: &NaiveTime) -> LocalResult<Time<Self>> {
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<DateTime<Self>>;
fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<DateTime<Self>> {
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<Self> {
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<Self> {
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> { UTC::now().date() }
/// Returns a `DateTime` which corresponds to the current date.
pub fn now() -> DateTime<UTC> {
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<Self> {
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<Date<UTC>> {
LocalResult::Single(Date::from_utc(local.clone(), UTC))
}
fn from_local_time(&self, local: &NaiveTime) -> LocalResult<Time<UTC>> {
LocalResult::Single(Time::from_utc(local.clone(), UTC))
}
fn from_local_datetime(&self, local: &NaiveDateTime) -> LocalResult<DateTime<UTC>> {
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<FixedOffset> {
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<FixedOffset> {
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<Date<FixedOffset>> {
LocalResult::Single(Date::from_utc(local.clone(), self.clone()))
}
fn from_local_time(&self, local: &NaiveTime) -> LocalResult<Time<FixedOffset>> {
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<DateTime<FixedOffset>> {
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<Local> {
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> {
Local::now().date()
}
/// Returns a `DateTime` which corresponds to the current date.
pub fn now() -> DateTime<Local> {
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<Date<Local>> {
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<Time<Local>> {
// 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<DateTime<Local>> {
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;

68
src/offset/utc.rs Normal file
View File

@ -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> { UTC::now().date() }
/// Returns a `DateTime` which corresponds to the current date.
pub fn now() -> DateTime<UTC> {
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<UTC> {
LocalResult::Single(UTC)
}
fn state_from_local_time(&self, _local: &NaiveTime) -> LocalResult<UTC> {
LocalResult::Single(UTC)
}
fn state_from_local_datetime(&self, _local: &NaiveDateTime) -> LocalResult<UTC> {
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") }
}

View File

@ -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<Off> {
pub struct Time<Off: Offset> {
time: NaiveTime,
offset: Off,
offset: Off::State,
}
impl<Off:Offset> Time<Off> {
impl<Off: Offset> Time<Off> {
/// 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<Off> {
pub fn from_utc(time: NaiveTime, offset: Off::State) -> Time<Off> {
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<Off2:Offset>(&self, offset: Off2) -> Time<Off2> {
Time::from_utc(self.time, offset)
pub fn with_timezone<Off2: Offset>(&self, tz: &Off2) -> Time<Off2> {
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<Off: Offset + fmt::Display> Time<Off> {
/// Maps the local time to other time with given conversion function.
fn map_local<Off: Offset, F>(t: &Time<Off>, mut f: F) -> Option<Time<Off>>
where F: FnMut(NaiveTime) -> Option<NaiveTime> {
f(t.naive_local()).and_then(|time| t.timezone().from_local_time(&time).single())
}
impl<Off: Offset> Time<Off> 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<Off:Offset> Timelike for Time<Off> {
#[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<Off: Offset> Timelike for Time<Off> {
#[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<Time<Off>> {
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<Time<Off>> {
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<Time<Off>> {
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<Time<Off>> {
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<Off:Offset, Off2:Offset> PartialEq<Time<Off2>> for Time<Off> {
impl<Off: Offset, Off2: Offset> PartialEq<Time<Off2>> for Time<Off> {
fn eq(&self, other: &Time<Off2>) -> bool { self.time == other.time }
}
impl<Off:Offset> Eq for Time<Off> {
impl<Off: Offset> Eq for Time<Off> {
}
impl<Off:Offset> PartialOrd for Time<Off> {
impl<Off: Offset> PartialOrd for Time<Off> {
fn partial_cmp(&self, other: &Time<Off>) -> Option<Ordering> {
self.time.partial_cmp(&other.time)
}
}
impl<Off:Offset> Ord for Time<Off> {
impl<Off: Offset> Ord for Time<Off> {
fn cmp(&self, other: &Time<Off>) -> Ordering { self.time.cmp(&other.time) }
}
@ -114,7 +131,7 @@ impl<Off: Offset, H: hash::Hasher + hash::Writer> hash::Hash<H> for Time<Off> {
fn hash(&self, state: &mut H) { self.time.hash(state) }
}
impl<Off:Offset> Add<Duration> for Time<Off> {
impl<Off: Offset> Add<Duration> for Time<Off> {
type Output = Time<Off>;
fn add(self, rhs: Duration) -> Time<Off> {
@ -122,13 +139,13 @@ impl<Off:Offset> Add<Duration> for Time<Off> {
}
}
impl<Off:Offset, Off2:Offset> Sub<Time<Off2>> for Time<Off> {
impl<Off: Offset, Off2: Offset> Sub<Time<Off2>> for Time<Off> {
type Output = Duration;
fn sub(self, rhs: Time<Off2>) -> Duration { self.time - rhs.time }
}
impl<Off:Offset> Sub<Duration> for Time<Off> {
impl<Off: Offset> Sub<Duration> for Time<Off> {
type Output = Time<Off>;
#[inline]
@ -137,13 +154,13 @@ impl<Off:Offset> Sub<Duration> for Time<Off> {
impl<Off: Offset> fmt::Debug for Time<Off> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}{:?}", self.local(), self.offset)
write!(f, "{:?}{:?}", self.naive_local(), self.offset)
}
}
impl<Off: Offset + fmt::Display> fmt::Display for Time<Off> {
impl<Off: Offset> fmt::Display for Time<Off> 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)
}
}