initial `Offset` implementations.

This commit is contained in:
Kang Seonghoon 2014-07-29 15:41:07 +09:00
parent 07d98709df
commit 32c3ee85e8
5 changed files with 905 additions and 6 deletions

View File

@ -6,11 +6,12 @@
* ISO 8601 calendar date.
*/
use std::{fmt, num};
use std::{fmt, num, hash};
use num::Integer;
use duration::Duration;
use offset::{Offset, UTC};
use time::TimeZ;
use datetime::DateTimeZ;
use datetime::{DateTimeZ, DateTime};
use self::internals::{DateImpl, Of, Mdf, YearFlags};
@ -600,6 +601,264 @@ impl fmt::Show for DateZ {
}
}
/// ISO 8601 calendar date with timezone.
#[deriving(Clone)]
pub struct Date<Off> {
date: DateZ,
offset: Off,
}
/// The minimum possible `Date`.
pub static MIN: Date<UTC> = Date { date: MINZ, offset: UTC };
/// The maximum possible `Date`.
pub static MAX: Date<UTC> = Date { date: MAXZ, offset: UTC };
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.
#[inline]
pub fn from_utc(date: DateZ, offset: Off) -> Date<Off> {
Date { date: date, offset: offset }
}
/// Makes a new `DateTimeZ` from the current date and given `TimeZ`.
/// The offset in the current date is preserved.
///
/// Fails on invalid datetime.
#[inline]
pub fn and_time(&self, time: TimeZ) -> Option<DateTime<Off>> {
self.offset.from_local_datetime(&self.date.and_time(time)).single()
}
/// Makes a new `DateTimeZ` from the current date, hour, minute and second.
/// The offset in the current date is preserved.
///
/// Fails on invalid hour, minute and/or second.
#[inline]
pub fn and_hms(&self, hour: u32, min: u32, sec: u32) -> DateTime<Off> {
self.and_hms_opt(hour, min, sec).expect("invalid time")
}
/// Makes a new `DateTimeZ` from the current date, hour, minute and second.
/// The offset in the current date is preserved.
///
/// Returns `None` on invalid hour, minute and/or second.
#[inline]
pub fn and_hms_opt(&self, hour: u32, min: u32, sec: u32) -> Option<DateTime<Off>> {
TimeZ::from_hms_opt(hour, min, sec).and_then(|time| self.and_time(time))
}
/// Makes a new `DateTimeZ` from the current date, hour, minute, second and millisecond.
/// The millisecond part can exceed 1,000 in order to represent the leap second.
/// The offset in the current date is preserved.
///
/// Fails on invalid hour, minute, second and/or millisecond.
#[inline]
pub fn and_hms_milli(&self, hour: u32, min: u32, sec: u32, milli: u32) -> DateTime<Off> {
self.and_hms_milli_opt(hour, min, sec, milli).expect("invalid time")
}
/// Makes a new `DateTimeZ` from the current date, hour, minute, second and millisecond.
/// The millisecond part can exceed 1,000 in order to represent the leap second.
/// The offset in the current date is preserved.
///
/// Returns `None` on invalid hour, minute, second and/or millisecond.
#[inline]
pub fn and_hms_milli_opt(&self, hour: u32, min: u32, sec: u32,
milli: u32) -> Option<DateTime<Off>> {
TimeZ::from_hms_milli_opt(hour, min, sec, milli).and_then(|time| self.and_time(time))
}
/// Makes a new `DateTimeZ` from the current date, hour, minute, second and microsecond.
/// The microsecond part can exceed 1,000,000 in order to represent the leap second.
/// The offset in the current date is preserved.
///
/// Fails on invalid hour, minute, second and/or microsecond.
#[inline]
pub fn and_hms_micro(&self, hour: u32, min: u32, sec: u32, micro: u32) -> DateTime<Off> {
self.and_hms_micro_opt(hour, min, sec, micro).expect("invalid time")
}
/// Makes a new `DateTimeZ` from the current date, hour, minute, second and microsecond.
/// The microsecond part can exceed 1,000,000 in order to represent the leap second.
/// The offset in the current date is preserved.
///
/// Returns `None` on invalid hour, minute, second and/or microsecond.
#[inline]
pub fn and_hms_micro_opt(&self, hour: u32, min: u32, sec: u32,
micro: u32) -> Option<DateTime<Off>> {
TimeZ::from_hms_micro_opt(hour, min, sec, micro).and_then(|time| self.and_time(time))
}
/// Makes a new `DateTimeZ` from the current date, hour, minute, second and nanosecond.
/// The nanosecond part can exceed 1,000,000,000 in order to represent the leap second.
/// The offset in the current date is preserved.
///
/// Fails on invalid hour, minute, second and/or nanosecond.
#[inline]
pub fn and_hms_nano(&self, hour: u32, min: u32, sec: u32, nano: u32) -> DateTime<Off> {
self.and_hms_nano_opt(hour, min, sec, nano).expect("invalid time")
}
/// Makes a new `DateTimeZ` from the current date, hour, minute, second and nanosecond.
/// The nanosecond part can exceed 1,000,000,000 in order to represent the leap second.
/// The offset in the current date is preserved.
///
/// Returns `None` on invalid hour, minute, second and/or nanosecond.
#[inline]
pub fn and_hms_nano_opt(&self, hour: u32, min: u32, sec: u32,
nano: u32) -> Option<DateTime<Off>> {
TimeZ::from_hms_nano_opt(hour, min, sec, nano).and_then(|time| self.and_time(time))
}
/// Makes a new `Date` for the next date.
///
/// Fails when `self` is the last representable date.
#[inline]
pub fn succ(&self) -> Date<Off> {
self.succ_opt().expect("out of bound")
}
/// Makes a new `Date` for the next date.
///
/// Returns `None` when `self` is the last representable date.
#[inline]
pub fn succ_opt(&self) -> Option<Date<Off>> {
self.date.succ_opt().map(|date| Date::from_utc(date, self.offset.clone()))
}
/// Makes a new `Date` for the prior date.
///
/// Fails when `self` is the first representable date.
#[inline]
pub fn pred(&self) -> Date<Off> {
self.pred_opt().expect("out of bound")
}
/// Makes a new `Date` for the prior date.
///
/// Returns `None` when `self` is the first representable date.
#[inline]
pub fn pred_opt(&self) -> Option<Date<Off>> {
self.date.pred_opt().map(|date| Date::from_utc(date, self.offset.clone()))
}
/// Returns a view to the local date.
fn local(&self) -> DateZ {
self.offset.to_local_date(&self.date)
}
}
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() }
#[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())
}
#[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())
}
#[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())
}
#[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())
}
#[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())
}
#[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())
}
#[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())
}
}
impl num::Bounded for Date<UTC> {
#[inline] fn min_value() -> Date<UTC> { MIN }
#[inline] fn max_value() -> Date<UTC> { MAX }
}
impl<Off:Offset> PartialEq for Date<Off> {
fn eq(&self, other: &Date<Off>) -> bool { self.date == other.date }
}
impl<Off:Offset> Eq for Date<Off> {
}
impl<Off:Offset, Off2:Offset> Equiv<Date<Off2>> for Date<Off> {
fn equiv(&self, other: &Date<Off2>) -> bool { self.date == other.date }
}
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> {
fn cmp(&self, other: &Date<Off>) -> Ordering { self.date.cmp(&other.date) }
}
impl<Off:Offset> hash::Hash for Date<Off> {
fn hash(&self, state: &mut hash::sip::SipState) { self.date.hash(state) }
}
impl<Off:Offset> Add<Duration,Date<Off>> for Date<Off> {
fn add(&self, rhs: &Duration) -> Date<Off> {
Date { date: self.date + *rhs, offset: self.offset.clone() }
}
}
/*
// Rust issue #7590, the current coherence checker can't handle multiple Add impls
impl<Off:Offset> Add<Date<Off>,Date<Off>> for Duration {
#[inline]
fn add(&self, rhs: &Date<Off>) -> Date<Off> { rhs.add(self) }
}
*/
impl<Off:Offset, Off2:Offset> Sub<Date<Off2>,Duration> for Date<Off> {
fn sub(&self, rhs: &Date<Off2>) -> Duration {
self.date - rhs.date
}
}
impl<Off:Offset> fmt::Show for Date<Off> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}", self.local(), self.offset)
}
}
#[cfg(test)]
mod tests {
use super::{Datelike, DateZ, MIN_YEAR, MAX_YEAR};

View File

@ -6,10 +6,11 @@
* ISO 8601 date and time.
*/
use std::fmt;
use std::{fmt, hash};
use offset::Offset;
use duration::Duration;
use time::{Timelike, TimeZ};
use date::{Datelike, DateZ, Weekday};
use time::{Timelike, TimeZ, Time};
use date::{Datelike, DateZ, Date, Weekday};
/// ISO 8601 combined date and time without timezone.
#[deriving(PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
@ -224,6 +225,181 @@ impl fmt::Show for DateTimeZ {
}
}
/// ISO 8601 combined date and time with timezone.
#[deriving(Clone)]
pub struct DateTime<Off> {
datetime: DateTimeZ,
offset: 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.
#[inline]
pub fn from_utc(datetime: DateTimeZ, offset: Off) -> DateTime<Off> {
DateTime { datetime: datetime, offset: offset }
}
/// Retrieves a date component.
#[inline]
pub fn date(&self) -> Date<Off> {
Date::from_utc(self.datetime.date().clone(), self.offset.clone())
}
/// Retrieves a time component.
#[inline]
pub fn time(&self) -> Time<Off> {
Time::from_utc(self.datetime.time().clone(), self.offset.clone())
}
/// Returns the number of non-leap seconds since January 1, 1970 0:00:00 UTC.
#[inline]
pub fn num_seconds_from_unix_epoch(&self) -> i64 {
self.datetime.num_seconds_from_unix_epoch()
}
/// Returns a view to the local datetime.
fn local(&self) -> DateTimeZ {
self.offset.to_local_datetime(&self.datetime)
}
}
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() }
#[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())
}
#[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())
}
#[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())
}
#[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())
}
#[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())
}
#[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())
}
#[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())
}
}
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() }
#[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())
}
#[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())
}
#[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())
}
#[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())
}
}
impl<Off:Offset> PartialEq for DateTime<Off> {
fn eq(&self, other: &DateTime<Off>) -> bool { self.datetime == other.datetime }
}
impl<Off:Offset> Eq for DateTime<Off> {
}
impl<Off:Offset, Off2:Offset> Equiv<DateTime<Off2>> for DateTime<Off> {
fn equiv(&self, other: &DateTime<Off2>) -> bool { self.datetime == other.datetime }
}
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> {
fn cmp(&self, other: &DateTime<Off>) -> Ordering { self.datetime.cmp(&other.datetime) }
}
impl<Off:Offset> hash::Hash for DateTime<Off> {
fn hash(&self, state: &mut hash::sip::SipState) { self.datetime.hash(state) }
}
impl<Off:Offset> Add<Duration,DateTime<Off>> for DateTime<Off> {
fn add(&self, rhs: &Duration) -> DateTime<Off> {
DateTime { datetime: self.datetime + *rhs, offset: self.offset.clone() }
}
}
/*
// Rust issue #7590, the current coherence checker can't handle multiple Add impls
impl<Off:Offset> Add<DateTime<Off>,DateTime<Off>> for Duration {
#[inline]
fn add(&self, rhs: &DateTime<Off>) -> DateTime<Off> { rhs.add(self) }
}
*/
impl<Off:Offset, Off2:Offset> Sub<DateTime<Off2>,Duration> for DateTime<Off> {
fn sub(&self, rhs: &DateTime<Off2>) -> Duration {
self.datetime - rhs.datetime
}
}
impl<Off:Offset> fmt::Show for DateTime<Off> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}", self.local(), self.offset)
}
}
#[cfg(test)]
mod tests {
use super::DateTimeZ;
@ -268,5 +444,21 @@ mod tests {
assert_eq!(to_timestamp(2001, 9, 9, 1, 46, 40), 1_000_000_000);
assert_eq!(to_timestamp(2038, 1, 19, 3, 14, 7), 0x7fffffff);
}
#[test]
#[allow(uppercase_variables)]
fn test_datetime_offset() {
use offset::{Offset, UTC, FixedOffset};
let EDT = FixedOffset::east(4*60*60);
assert_eq!(UTC.ymd(2014, 5, 6).and_hms(7, 8, 9).to_string(),
"2014-05-06T07:08:09Z".to_string());
assert_eq!(EDT.ymd(2014, 5, 6).and_hms(7, 8, 9).to_string(),
"2014-05-06T07:08:09+04:00".to_string());
assert!(UTC.ymd(2014, 5, 6).and_hms(7, 8, 9).equiv(&EDT.ymd(2014, 5, 6).and_hms(11, 8, 9)));
assert_eq!(UTC.ymd(2014, 5, 6).and_hms(7, 8, 9) + Duration::seconds(3600 + 60 + 1),
UTC.ymd(2014, 5, 6).and_hms(8, 9, 10));
assert_eq!(UTC.ymd(2014, 5, 6).and_hms(7, 8, 9) - EDT.ymd(2014, 5, 6).and_hms(10, 11, 12),
Duration::seconds(3600 - 3*60 - 3));
}
}

View File

@ -21,6 +21,7 @@ pub use time::{Timelike, TimeZ};
pub use datetime::DateTimeZ;
pub mod duration;
pub mod offset;
pub mod date;
pub mod time;
pub mod datetime;

340
src/offset.rs Normal file
View File

@ -0,0 +1,340 @@
// This is a part of rust-chrono.
// Copyright (c) 2014, Kang Seonghoon.
// See README.md and LICENSE.txt for details.
/*!
* Offsets from the local time to UTC.
*/
use std::fmt;
use num::Integer;
use duration::Duration;
use date::{DateZ, Date, Weekday};
use time::{TimeZ, Time};
use datetime::{DateTimeZ, DateTime};
/// The conversion result from the local time to the timezone-aware datetime types.
pub enum LocalResult<T> {
/// Given local time representation is invalid.
/// This can occur when, for example, the positive timezone transition.
NoResult,
/// Given local time representation has a single unique result.
Single(T),
/// Given local time representation has multiple results and thus ambiguous.
/// This can occur when, for example, the negative timezone transition.
Ambiguous(T /*min*/, T /*max*/),
}
impl<T> LocalResult<T> {
/// Returns `Some` only when the conversion result is unique, or `None` otherwise.
pub fn single(self) -> Option<T> {
match self { Single(t) => Some(t), _ => None }
}
/// Returns `Some` for the earliest possible conversion result, or `None` if none.
pub fn earliest(self) -> Option<T> {
match self { Single(t) | Ambiguous(t,_) => Some(t), _ => None }
}
/// Returns `Some` for the latest possible conversion result, or `None` if none.
pub fn latest(self) -> Option<T> {
match self { Single(t) | Ambiguous(_,t) => Some(t), _ => None }
}
}
impl<T:fmt::Show> LocalResult<T> {
/// Returns the single unique conversion result, or fails accordingly.
pub fn unwrap(self) -> T {
match self {
NoResult => fail!("No such local time"),
Single(t) => t,
Ambiguous(t1,t2) => fail!("Ambiguous local time, ranging from {} to {}", t1, t2),
}
}
}
/// The offset from the local time to UTC.
pub trait Offset: Clone + fmt::Show {
/// 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.
///
/// The offset normally does not affect the date (unless it is between UTC-24 and UTC+24),
/// but it will propagate to the `DateTime` values constructed via this date.
///
/// Fails on the out-of-range date, invalid month and/or day.
fn ymd(&self, year: i32, month: u32, day: u32) -> Date<Self> {
self.ymd_opt(year, month, day).unwrap()
}
/// 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.
///
/// The offset normally does not affect the date (unless it is between UTC-24 and UTC+24),
/// but it will propagate to the `DateTime` values constructed via this date.
///
/// Returns `None` on the out-of-range date, invalid month and/or day.
fn ymd_opt(&self, year: i32, month: u32, day: u32) -> LocalResult<Date<Self>> {
match DateZ::from_ymd_opt(year, month, day) {
Some(d) => self.from_local_date(&d),
None => NoResult,
}
}
/// Makes a new `Date` from year, day of year (DOY or "ordinal") and the current offset.
/// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE.
///
/// The offset normally does not affect the date (unless it is between UTC-24 and UTC+24),
/// but it will propagate to the `DateTime` values constructed via this date.
///
/// Fails on the out-of-range date and/or invalid DOY.
fn yo(&self, year: i32, ordinal: u32) -> Date<Self> {
self.yo_opt(year, ordinal).unwrap()
}
/// Makes a new `Date` from year, day of year (DOY or "ordinal") and the current offset.
/// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE.
///
/// The offset normally does not affect the date (unless it is between UTC-24 and UTC+24),
/// but it will propagate to the `DateTime` values constructed via this date.
///
/// Returns `None` on the out-of-range date and/or invalid DOY.
fn yo_opt(&self, year: i32, ordinal: u32) -> LocalResult<Date<Self>> {
match DateZ::from_yo_opt(year, ordinal) {
Some(d) => self.from_local_date(&d),
None => NoResult,
}
}
/// Makes a new `Date` from ISO week date (year and week number), day of the week (DOW) and
/// the current offset.
/// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE.
/// The resulting `Date` may have a different year from the input year.
///
/// The offset normally does not affect the date (unless it is between UTC-24 and UTC+24),
/// but it will propagate to the `DateTime` values constructed via this date.
///
/// Fails on the out-of-range date and/or invalid week number.
fn isoywd(&self, year: i32, week: u32, weekday: Weekday) -> Date<Self> {
self.isoywd_opt(year, week, weekday).unwrap()
}
/// Makes a new `Date` from ISO week date (year and week number), day of the week (DOW) and
/// the current offset.
/// This assumes the proleptic Gregorian calendar, with the year 0 being 1 BCE.
/// The resulting `Date` may have a different year from the input year.
///
/// The offset normally does not affect the date (unless it is between UTC-24 and UTC+24),
/// but it will propagate to the `DateTime` values constructed via this date.
///
/// Returns `None` on the out-of-range date and/or invalid week number.
fn isoywd_opt(&self, year: i32, week: u32, weekday: Weekday) -> LocalResult<Date<Self>> {
match DateZ::from_isoywd_opt(year, week, weekday) {
Some(d) => self.from_local_date(&d),
None => NoResult,
}
}
/// Makes a new `Time` from hour, minute, second and the current offset.
///
/// Fails on invalid hour, minute and/or second.
fn hms(&self, hour: u32, min: u32, sec: u32) -> Time<Self> {
self.hms_opt(hour, min, sec).unwrap()
}
/// Makes a new `Time` from hour, minute, second and the current offset.
///
/// Returns `None` on invalid hour, minute and/or second.
fn hms_opt(&self, hour: u32, min: u32, sec: u32) -> LocalResult<Time<Self>> {
match TimeZ::from_hms_opt(hour, min, sec) {
Some(t) => self.from_local_time(&t),
None => NoResult,
}
}
/// Makes a new `Time` from hour, minute, second, millisecond and the current offset.
/// The millisecond part can exceed 1,000 in order to represent the leap second.
///
/// Fails on invalid hour, minute, second and/or millisecond.
fn hms_milli(&self, hour: u32, min: u32, sec: u32, milli: u32) -> Time<Self> {
self.hms_milli_opt(hour, min, sec, milli).unwrap()
}
/// Makes a new `Time` from hour, minute, second, millisecond and the current offset.
/// The millisecond part can exceed 1,000 in order to represent the leap second.
///
/// Returns `None` on invalid hour, minute, second and/or millisecond.
fn hms_milli_opt(&self, hour: u32, min: u32, sec: u32, milli: u32) -> LocalResult<Time<Self>> {
match TimeZ::from_hms_milli_opt(hour, min, sec, milli) {
Some(t) => self.from_local_time(&t),
None => NoResult,
}
}
/// Makes a new `Time` from hour, minute, second, microsecond and the current offset.
/// The microsecond part can exceed 1,000,000 in order to represent the leap second.
///
/// Fails on invalid hour, minute, second and/or microsecond.
fn hms_micro(&self, hour: u32, min: u32, sec: u32, micro: u32) -> Time<Self> {
self.hms_micro_opt(hour, min, sec, micro).unwrap()
}
/// Makes a new `Time` from hour, minute, second, microsecond and the current offset.
/// The microsecond part can exceed 1,000,000 in order to represent the leap second.
///
/// Returns `None` on invalid hour, minute, second and/or microsecond.
fn hms_micro_opt(&self, hour: u32, min: u32, sec: u32, micro: u32) -> LocalResult<Time<Self>> {
match TimeZ::from_hms_micro_opt(hour, min, sec, micro) {
Some(t) => self.from_local_time(&t),
None => NoResult,
}
}
/// Makes a new `Time` from hour, minute, second, nanosecond and the current offset.
/// The nanosecond part can exceed 1,000,000,000 in order to represent the leap second.
///
/// Fails on invalid hour, minute, second and/or nanosecond.
fn hms_nano(&self, hour: u32, min: u32, sec: u32, nano: u32) -> Time<Self> {
self.hms_nano_opt(hour, min, sec, nano).unwrap()
}
/// Makes a new `Time` from hour, minute, second, nanosecond and the current offset.
/// The nanosecond part can exceed 1,000,000,000 in order to represent the leap second.
///
/// Returns `None` on invalid hour, minute, second and/or nanosecond.
fn hms_nano_opt(&self, hour: u32, min: u32, sec: u32, nano: u32) -> LocalResult<Time<Self>> {
match TimeZ::from_hms_nano_opt(hour, min, sec, nano) {
Some(t) => self.from_local_time(&t),
None => NoResult,
}
}
/// Converts the local `DateZ` to the timezone-aware `Date` if possible.
fn from_local_date(&self, local: &DateZ) -> LocalResult<Date<Self>>;
/// Converts the local `TimeZ` to the timezone-aware `Time` if possible.
fn from_local_time(&self, local: &TimeZ) -> LocalResult<Time<Self>>;
/// Converts the local `DateTimeZ` to the timezone-aware `DateTime` if possible.
fn from_local_datetime(&self, local: &DateTimeZ) -> LocalResult<DateTime<Self>>;
/// Converts the UTC `DateZ` 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: &DateZ) -> DateZ;
/// Converts the UTC `TimeZ` 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: &TimeZ) -> TimeZ;
/// Converts the UTC `DateTimeZ` 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: &DateTimeZ) -> DateTimeZ;
}
/// The UTC timescale. This is the most efficient offset when you don't need the local time.
#[deriving(Clone)]
pub struct UTC;
impl Offset for UTC {
fn from_local_date(&self, local: &DateZ) -> LocalResult<Date<UTC>> {
Single(Date::from_utc(local.clone(), UTC))
}
fn from_local_time(&self, local: &TimeZ) -> LocalResult<Time<UTC>> {
Single(Time::from_utc(local.clone(), UTC))
}
fn from_local_datetime(&self, local: &DateTimeZ) -> LocalResult<DateTime<UTC>> {
Single(DateTime::from_utc(local.clone(), UTC))
}
fn to_local_date(&self, utc: &DateZ) -> DateZ { utc.clone() }
fn to_local_time(&self, utc: &TimeZ) -> TimeZ { utc.clone() }
fn to_local_datetime(&self, utc: &DateTimeZ) -> DateTimeZ { utc.clone() }
}
impl fmt::Show for UTC {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Z") }
}
/// The fixed offset, from UTC-23:59:59 to UTC+23:59:59.
#[deriving(Clone)]
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 from_local_date(&self, local: &DateZ) -> LocalResult<Date<FixedOffset>> {
Single(Date::from_utc(local.clone(), self.clone()))
}
fn from_local_time(&self, local: &TimeZ) -> LocalResult<Time<FixedOffset>> {
Single(Time::from_utc(*local + Duration::seconds(-self.local_minus_utc), self.clone()))
}
fn from_local_datetime(&self, local: &DateTimeZ) -> LocalResult<DateTime<FixedOffset>> {
Single(DateTime::from_utc(*local + Duration::seconds(-self.local_minus_utc), self.clone()))
}
fn to_local_date(&self, utc: &DateZ) -> DateZ {
utc.clone()
}
fn to_local_time(&self, utc: &TimeZ) -> TimeZ {
*utc + Duration::seconds(self.local_minus_utc)
}
fn to_local_datetime(&self, utc: &DateTimeZ) -> DateTimeZ {
*utc + Duration::seconds(self.local_minus_utc)
}
}
impl fmt::Show 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) = offset.div_mod_floor(&60);
let (hour, min) = mins.div_mod_floor(&60);
if sec == 0 {
write!(f, "{}{:02}:{:02}", sign, hour, min)
} else {
write!(f, "{}{:02}:{:02}:{:02}", sign, hour, min, sec)
}
}
}

View File

@ -6,8 +6,9 @@
* ISO 8601 time.
*/
use std::fmt;
use std::{fmt, hash};
use num::Integer;
use offset::Offset;
use duration::Duration;
/// The common set of methods for time component.
@ -254,6 +255,112 @@ impl fmt::Show for TimeZ {
}
}
/// ISO 8601 time with timezone.
#[deriving(Clone)]
pub struct Time<Off> {
time: TimeZ,
offset: 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.
#[inline]
pub fn from_utc(time: TimeZ, offset: Off) -> Time<Off> {
Time { time: time, offset: offset }
}
/// Returns a view to the local time.
fn local(&self) -> TimeZ {
self.offset.to_local_time(&self.time)
}
}
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() }
#[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())
}
#[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())
}
#[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())
}
#[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())
}
#[inline]
fn num_seconds_from_midnight(&self) -> u32 { self.local().num_seconds_from_midnight() }
}
impl<Off:Offset> PartialEq for Time<Off> {
fn eq(&self, other: &Time<Off>) -> bool { self.time == other.time }
}
impl<Off:Offset> Eq for Time<Off> {
}
impl<Off:Offset, Off2:Offset> Equiv<Time<Off2>> for Time<Off> {
fn equiv(&self, other: &Time<Off2>) -> bool { self.time == other.time }
}
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> {
fn cmp(&self, other: &Time<Off>) -> Ordering { self.time.cmp(&other.time) }
}
impl<Off:Offset> hash::Hash for Time<Off> {
fn hash(&self, state: &mut hash::sip::SipState) { self.time.hash(state) }
}
impl<Off:Offset> Add<Duration,Time<Off>> for Time<Off> {
fn add(&self, rhs: &Duration) -> Time<Off> {
Time { time: self.time + *rhs, offset: self.offset.clone() }
}
}
/*
// Rust issue #7590, the current coherence checker can't handle multiple Add impls
impl<Off:Offset> Add<Time<Off>,Time<Off>> for Duration {
#[inline]
fn add(&self, rhs: &Time<Off>) -> Time<Off> { rhs.add(self) }
}
*/
impl<Off:Offset, Off2:Offset> Sub<Time<Off2>,Duration> for Time<Off> {
fn sub(&self, rhs: &Time<Off2>) -> Duration {
self.time - rhs.time
}
}
impl<Off:Offset> fmt::Show for Time<Off> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}", self.local(), self.offset)
}
}
#[cfg(test)]
mod tests {
use super::{Timelike, TimeZ};