#[macro_use]
extern crate nom;
extern crate libc;
mod parse;
mod iter;
mod sys;
pub use iter::{Every, Range};
use std::sync::Arc;
use std::io;
use std::fmt;
use std::ptr;
use std::error;
use std::cmp::{Eq, PartialEq, Ord, PartialOrd, Ordering};
use std::ops::{Add, Sub, Neg};
use std::str::FromStr;
use nom::{digit, alpha, eof, IResult};
pub struct Timezone {
trans: Vec<Transition>,
trule: Option<TransRule>,
}
#[derive(Debug)]
struct Transition {
utc: i64,
ttype: Arc<Type>,
}
#[derive(Debug, Eq, PartialEq)]
enum TransRule {
Fixed(Type),
Alternate {
dst_start: GenericDay,
dst_stime: i32,
dst_end: GenericDay,
dst_etime: i32,
std: Type,
dst: Type,
},
}
#[derive(Debug, Eq, PartialEq)]
enum GenericDay {
Julian0 {
jday: i32,
},
Julian1 {
jday: i32,
},
MWDRule {
month: i32,
week: i32,
wday: i32,
},
}
#[derive(Debug, PartialEq, Eq, Clone)]
struct Type {
off: i32,
is_dst: bool,
abbr: String,
}
impl Timezone {
pub fn new(timezone: &str) -> Result<Self, TzError> {
parse::load_timezone(timezone)
}
pub fn local() -> Result<Self, TzError> {
parse::load_timezone("localtime")
}
pub fn utc() -> Self {
Self::fixed(0)
}
pub fn fixed(sec: i32) -> Self {
Timezone {
trans: vec![Transition {
utc: std::i64::MIN,
ttype: Arc::new(Type {
off: sec,
is_dst: false,
abbr: "".to_owned(),
}),
}],
trule: None,
}
}
pub fn posix(posix: &str) -> Result<Self, TzError> {
match posixtz(posix) {
IResult::Done(_, rule) => {
Ok(Timezone {
trans: vec![],
trule: Some(rule),
})
}
_ => Err(TzError::InvalidPosixTz),
}
}
fn offset(&self, stamp: i64) -> &Type {
let idx = match self.trans.binary_search_by(|t| t.utc.cmp(&stamp)) {
Err(i) if i == self.trans.len() => return self.offset_trule(stamp),
Err(0) => 0,
Err(i) => i - 1,
Ok(i) => i,
};
&self.trans[idx].ttype
}
fn offset_trule(&self, stamp: i64) -> &Type {
match self.trule {
None => &self.trans.last().unwrap().ttype,
Some(ref rule) => rule.get_type(stamp),
}
}
pub fn now(&self) -> Datetime {
Datetime {
tz: self,
stamp: Timespec::now(),
}
}
pub fn parse(&self, s: &str, fmt: &str) -> Result<Datetime, InputError> {
let mut tm = libc::tm {
tm_sec: 0,
tm_min: 0,
tm_hour: 0,
tm_mday: 1,
tm_mon: 0,
tm_year: 0,
tm_wday: 0,
tm_yday: 0,
tm_isdst: 0,
tm_gmtoff: 0,
tm_zone: sys::empty_tm_zone(),
};
let s = try!(std::ffi::CString::new(s).map_err(|_| InputError::InvalidFormat));
let fmt = try!(std::ffi::CString::new(fmt).map_err(|_| InputError::InvalidFormat));
let ret = unsafe {
strptime(s.as_ptr() as *const libc::c_char,
fmt.as_ptr() as *const libc::c_char,
&mut tm)
};
if ret == ptr::null() {
Err(InputError::InvalidFormat)
} else {
self.datetime(tm.tm_year + 1900,
tm.tm_mon + 1,
tm.tm_mday,
tm.tm_hour,
tm.tm_min,
tm.tm_sec,
0)
}
}
pub fn unix(&self, stamp: i64, nano: i32) -> Result<Datetime, InputError> {
Ok(Datetime {
tz: self,
stamp: try!(Timespec::unix(stamp, nano)),
})
}
pub fn datetime(&self,
year: i32,
month: i32,
day: i32,
hour: i32,
minute: i32,
second: i32,
nano: i32)
-> Result<Datetime, InputError> {
let is_leap_year = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
if month < 1 || month > 12 {
return Err(InputError::InvalidMonth);
} else if hour < 0 || hour > 23 {
return Err(InputError::InvalidHour);
} else if minute < 0 || minute > 59 {
return Err(InputError::InvalidMinute);
} else if second < 0 || second > 60 {
return Err(InputError::InvalidSecond);
} else if nano < 0 || nano > 999_999_999 {
return Err(InputError::InvalidNano);
}
let max_day = match month {
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
4 | 6 | 9 | 11 => 30,
2 if is_leap_year => 30,
2 => 29,
_ => unreachable!(),
};
if day < 1 || day > max_day {
return Err(InputError::InvalidDay);
}
let utc_dt = libc::tm {
tm_sec: if second == 60 {
59
} else {
second as i32
},
tm_min: minute as i32,
tm_hour: hour as i32,
tm_mday: day as i32,
tm_mon: (month - 1) as i32,
tm_year: (year - 1900) as i32,
tm_wday: 0,
tm_yday: 0,
tm_isdst: 0,
tm_gmtoff: 0,
tm_zone: sys::empty_tm_zone(),
};
let mut sec = tm_to_stamp(&utc_dt);
let utc_offset = self.offset(sec).off;
sec -= utc_offset as i64;
if second == 60 {
if let Err(_) = LEAP_SECONDS.binary_search(&(sec + 1)) {
return Err(InputError::InvalidLeapSecond);
}
Ok(Datetime {
tz: self,
stamp: Timespec {
sec: sec + 1,
nano: nano,
leaping: true,
},
})
} else {
Ok(Datetime {
tz: self,
stamp: Timespec {
sec: sec,
nano: nano,
leaping: false,
},
})
}
}
}
named!(num<&str, i32>, map_res!(digit, FromStr::from_str));
named!(tzabbr<&str, &str>, alt!(
delimited!(
tag_s!("<"),
take_until_s!(">"),
tag_s!(">")
) |
alpha
));
named!(hhmmss<&str, i32>,
chain!(
hour: num ~
sec: opt!(complete!(chain!(
tag_s!(":") ~
min: num ~
sec: opt!(complete!(chain!(
tag_s!(":") ~
sec: num,
|| { sec }
))),
|| { min * 60 + sec.unwrap_or(0) }
))),
|| { hour * 3600 + sec.unwrap_or(0) }
)
);
named!(signed_hhmmss<&str, i32>, chain!(
sign: chain!(
s: alt!(tag_s!("+") | tag_s!("-"))?,
|| { if let Some("-") = s { -1 } else { 1 } }
) ~
sec: hhmmss,
|| { sign * sec }
));
named!(genday<&str, GenericDay>,
alt!(
chain!(
tag_s!("J") ~
j: num,
|| { GenericDay::Julian1 { jday: j } }
) |
chain!(
tag_s!("M") ~
m: num ~
tag_s!(".") ~
w: num ~
tag_s!(".") ~
d: num,
|| { GenericDay::MWDRule { month: m, week: w, wday: d } }
) |
num => { |j| { GenericDay::Julian0 { jday: j } } }
)
);
named!(posixtz<&str, TransRule>, alt!(
chain!(
std_abbr: tzabbr ~
std_off: signed_hhmmss ~
eof,
|| {
TransRule::Fixed(
Type{
off: -std_off,
is_dst: false,
abbr: std_abbr.to_owned(),
}
)
}
) |
chain!(
std_abbr: tzabbr ~
std_off: signed_hhmmss ~
dst_abbr: tzabbr ~
dst_off: opt!(complete!(signed_hhmmss)) ~
tag_s!(",") ~
dst_start: genday ~
dst_stime: opt!(complete!(preceded!(tag_s!("/"), hhmmss))) ~
tag_s!(",") ~
dst_end: genday ~
dst_etime: opt!(complete!(preceded!(tag_s!("/"), hhmmss))) ~
eof,
|| {
TransRule::Alternate {
dst_start: dst_start,
dst_stime: dst_stime.unwrap_or(2 * 3600),
dst_end: dst_end,
dst_etime: dst_etime.unwrap_or(2 * 3600),
std: Type {
off: -std_off,
is_dst: false,
abbr: std_abbr.to_owned(),
},
dst: Type {
off: -dst_off.unwrap_or(std_off - 3600),
is_dst: true,
abbr: dst_abbr.to_owned(),
},
}
}
)
));
impl GenericDay {
fn cmp_t(&self, tm: &libc::tm) -> Ordering {
const DAYS_MONTH: &'static [i32] = &[0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
let tm_year = tm.tm_year + 1900;
let tm_mon = tm.tm_mon + 1;
let is_leap_year = (tm_year % 4 == 0 && tm_year % 100 != 0) || tm_year % 400 == 0;
match *self {
GenericDay::Julian0 { jday } => jday.cmp(&tm.tm_yday),
GenericDay::Julian1 { jday } => {
let yday = if is_leap_year && tm_mon > 2 {
tm.tm_yday
} else {
tm.tm_yday + 1
};
jday.cmp(&yday)
}
GenericDay::MWDRule { month, week, wday } => {
if month < tm_mon {
Ordering::Less
} else if month > tm_mon {
Ordering::Greater
} else {
let wday_on_first = (tm.tm_wday - tm.tm_mday + 1 + 5 * 7) % 7;
let days_in_month = if is_leap_year && tm_mon == 2 {
29
} else {
DAYS_MONTH[tm_mon as usize]
};
let mut matching_week = 0;
let mut last_matching_day = 0;
for d in 0..days_in_month {
if (wday_on_first + d) % 7 == wday {
last_matching_day = d;
matching_week += 1;
}
if matching_week == week {
break;
}
}
(last_matching_day + 1).cmp(&tm.tm_mday)
}
}
}
}
fn approx_month(&self) -> i32 {
match *self {
GenericDay::Julian0 { jday } => jday / 30,
GenericDay::Julian1 { jday } => jday / 30,
GenericDay::MWDRule { month, .. } => month,
}
}
fn cmp(&self, other: &GenericDay) -> Ordering {
self.approx_month().cmp(&other.approx_month())
}
}
impl PartialEq<libc::tm> for GenericDay {
fn eq(&self, tm: &libc::tm) -> bool {
self.cmp_t(tm) == Ordering::Equal
}
}
impl PartialOrd<libc::tm> for GenericDay {
fn partial_cmp(&self, tm: &libc::tm) -> Option<Ordering> {
Some(self.cmp_t(tm))
}
}
impl TransRule {
fn get_type(&self, stamp: i64) -> &Type {
match *self {
TransRule::Fixed(ref t) => t,
TransRule::Alternate { ref dst_start, dst_stime, ref dst_end, dst_etime, ref std, ref dst } => {
let std_t = stamp_to_tm(stamp + std.off as i64);
let std_sec = std_t.tm_hour * 3600 + std_t.tm_min * 60 + std_t.tm_sec;
let dst_t = stamp_to_tm(stamp + dst.off as i64);
let dst_sec = dst_t.tm_hour * 3600 + dst_t.tm_min * 60 + dst_t.tm_sec;
if dst_start.cmp(&dst_end) == Ordering::Less {
match (dst_start.cmp_t(&std_t), dst_stime.cmp(&std_sec)) {
(Ordering::Greater, _) => std,
(Ordering::Equal, Ordering::Greater) => std,
(Ordering::Equal, _) => dst,
(_, _) => match (dst_end.cmp_t(&dst_t), dst_etime.cmp(&dst_sec)) {
(Ordering::Greater, _) => dst,
(Ordering::Equal, Ordering::Greater) => dst,
(Ordering::Equal, _) => std,
(_, _) => std,
},
}
} else {
match (dst_end.cmp_t(&dst_t), dst_etime.cmp(&dst_sec)) {
(Ordering::Greater, _) => dst,
(Ordering::Equal, Ordering::Greater) => dst,
(Ordering::Equal, _) => std,
(_, _) => match (dst_start.cmp_t(&std_t), dst_stime.cmp(&std_sec)) {
(Ordering::Greater, _) => std,
(Ordering::Equal, Ordering::Greater) => std,
(Ordering::Equal, _) => dst,
(_, _) => dst,
}
}
}
}
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct Timespec {
sec: i64,
nano: i32,
leaping: bool,
}
impl Timespec {
pub fn now() -> Timespec {
let (tv_sec, tv_nsec) = sys::get_time();
Timespec {
sec: tv_sec,
nano: tv_nsec as i32,
leaping: false,
}
}
pub fn unix(stamp: i64, nano: i32) -> Result<Timespec, InputError> {
if nano < 0 || nano > 999_999_999 {
return Err(InputError::InvalidNano);
}
Ok(Timespec {
sec: stamp,
nano: nano,
leaping: false,
})
}
pub fn seconds(&self) -> i64 {
self.sec
}
pub fn nanoseconds(&self) -> i32 {
self.nano
}
pub fn to_datetime<'a>(&self, tz: &'a Timezone) -> Datetime<'a> {
Datetime {
tz: tz,
stamp: *self,
}
}
}
impl PartialEq<Timespec> for Timespec {
fn eq(&self, other: &Timespec) -> bool {
self.cmp(&other) == Ordering::Equal
}
}
impl Eq for Timespec {}
impl PartialOrd<Timespec> for Timespec {
fn partial_cmp(&self, other: &Timespec) -> Option<Ordering> {
let cmp_sec = self.sec.cmp(&other.sec);
if let Ordering::Equal = cmp_sec {
let cmp_leap = other.leaping.cmp(&self.leaping);
if let Ordering::Equal = cmp_leap {
Some(self.nano.cmp(&other.nano))
} else {
Some(cmp_leap)
}
} else {
Some(cmp_sec)
}
}
}
impl Ord for Timespec {
fn cmp(&self, other: &Timespec) -> Ordering {
self.partial_cmp(other).unwrap()
}
}
impl Add<Deltatime> for Timespec {
type Output = Timespec;
fn add(mut self, rhs: Deltatime) -> Self::Output {
match rhs.0 {
Delta::Nanoseconds(nano) => {
if nano < 0 {
let nano = -nano;
if self.nano as i64 >= nano {
self.nano -= nano as i32;
return self;
}
let nano = nano - self.nano as i64;
let (sec, nano) = {
let s = nano / 1_000_000_000 + 1;
let n = s * 1_000_000_000 - nano;
if n == 1_000_000_000 {
(s - 1, 0)
} else {
(s, n)
}
};
let mut t = self + Deltatime(Delta::Seconds(-sec));
t.nano = nano as i32;
t
} else {
let nano = nano + self.nano as i64;
if nano < 1_000_000_000 {
self.nano = nano as i32;
return self;
}
let sec = nano / 1_000_000_000;
let nano = nano - sec * 1_000_000_000;
let mut t = self + Deltatime(Delta::Seconds(sec));
t.nano = nano as i32;
t
}
}
Delta::Seconds(mut sec) => {
if sec < 0 {
sec = -sec;
while sec != 0 {
match LEAP_SECONDS.binary_search(&self.sec) {
Err(0) => {
self.sec -= sec;
return self;
}
Err(s) => {
let prev_leap = LEAP_SECONDS[s - 1];
let prev_leap_in_sec = self.sec - prev_leap;
if sec <= prev_leap_in_sec {
self.sec -= sec;
return self;
} else if sec == prev_leap_in_sec + 1 {
self.leaping = true;
self.sec = prev_leap;
return self;
} else {
self.sec = prev_leap - 1;
sec -= prev_leap_in_sec + 2;
}
}
Ok(_) => {
if self.leaping {
self.leaping = false;
self.sec -= 1;
} else {
self.leaping = true;
}
sec -= 1;
}
}
}
self
} else {
while sec != 0 {
match LEAP_SECONDS.binary_search(&self.sec) {
Err(s) if s == LEAP_SECONDS.len() => {
self.sec += sec;
return self;
}
Err(s) => {
let next_leap = LEAP_SECONDS[s];
let next_leap_in_sec = next_leap - self.sec;
if sec < next_leap_in_sec {
self.sec += sec;
return self;
} else if sec == next_leap_in_sec {
self.sec = next_leap;
self.leaping = true;
return self;
} else {
self.sec = next_leap;
sec -= next_leap_in_sec + 1;
}
}
Ok(_) => {
if self.leaping {
self.leaping = false;
} else {
self.sec += 1;
}
sec -= 1;
}
}
}
self
}
}
Delta::Days(day) => {
self.leaping = false;
self.sec += day * 86400;
self
}
}
}
}
impl Sub<Deltatime> for Timespec {
type Output = Timespec;
fn sub(self, rhs: Deltatime) -> Self::Output {
self + (-rhs)
}
}
impl Sub<Timespec> for Timespec {
type Output = Deltatime;
fn sub(self, rhs: Timespec) -> Self::Output {
if self < rhs {
return -(rhs - self);
}
let add_sec = match (LEAP_SECONDS.binary_search(&rhs.sec),
LEAP_SECONDS.binary_search(&self.sec)) {
(Err(i), Err(j)) => (j - i) as i64,
(Err(i), Ok(j)) => {
(j - i) as i64 +
if self.leaping {
0i64
} else {
1i64
}
}
(Ok(i), Err(j)) => {
(j - i) as i64 +
if rhs.leaping {
0i64
} else {
-1i64
}
}
(Ok(i), Ok(j)) => {
(j - i) as i64 +
if rhs.leaping {
0i64
} else {
-1i64
} +
if self.leaping {
0i64
} else {
1i64
}
}
};
Deltatime::nanoseconds((self.sec - rhs.sec + add_sec) * 1_000_000_000 +
(self.nano - rhs.nano) as i64)
}
}
#[derive(Clone, Copy)]
pub struct Datetime<'a> {
tz: &'a Timezone,
stamp: Timespec,
}
impl<'a> Datetime<'a> {
pub fn project<'b>(&self, tz: &'b Timezone) -> Datetime<'b> {
Datetime {
tz: tz,
stamp: self.stamp,
}
}
fn tm(&self) -> libc::tm {
let offset = self.tz.offset(self.stamp.sec).off;
let mut tm = stamp_to_tm(self.stamp.sec + offset as i64 +
if self.stamp.leaping {
-1
} else {
0
});
if self.stamp.leaping {
tm.tm_sec = 60;
}
tm
}
fn tm_to_date(tm: &libc::tm) -> (i32, i32, i32) {
(tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday)
}
fn tm_to_time(tm: &libc::tm) -> (i32, i32, i32) {
(tm.tm_hour, tm.tm_min, tm.tm_sec)
}
fn tm_to_weekday(tm: &libc::tm) -> &'static str {
match tm.tm_wday {
0 => "Sunday",
1 => "Monday",
2 => "Tuesday",
3 => "Wednesday",
4 => "Thursday",
5 => "Friday",
6 => "Saturday",
_ => unreachable!(),
}
}
fn tm_to_month(tm: &libc::tm) -> &'static str {
match tm.tm_mon {
0 => "January",
1 => "February",
2 => "March",
3 => "April",
4 => "May",
5 => "June",
6 => "July",
7 => "August",
8 => "September",
9 => "October",
10 => "November",
11 => "December",
_ => unreachable!(),
}
}
pub fn date(&self) -> (i32, i32, i32) {
let tm = self.tm();
Self::tm_to_date(&tm)
}
pub fn time(&self) -> (i32, i32, i32, i32) {
let tm = self.tm();
let (h, m, s) = Self::tm_to_time(&tm);
(h, m, s, self.stamp.nano)
}
pub fn unix(&self) -> i64 {
self.stamp.sec
}
pub fn to_timespec(&self) -> Timespec {
self.stamp
}
pub fn format(&self, fmt: &str) -> Result<String, FmtError> {
use std::fmt::Write;
let mut out = String::with_capacity(2 * fmt.len());
let tm = self.tm();
let date = Self::tm_to_date(&tm);
let time = Self::tm_to_time(&tm);
let off = self.tz.offset(self.stamp.sec);
let mut chars = fmt.chars();
loop {
match chars.next() {
None => break,
Some('%') => {
match chars.next() {
None => return Err(FmtError::UnexpectedEndOfString),
Some('%') => out.push('%'),
Some('Y') => write!(out, "{:04}", date.0).unwrap_or(()),
Some('m') => write!(out, "{:02}", date.1).unwrap_or(()),
Some('d') => write!(out, "{:02}", date.2).unwrap_or(()),
Some('e') => write!(out, "{}", date.2).unwrap_or(()),
Some('H') => write!(out, "{:02}", time.0).unwrap_or(()),
Some('M') => write!(out, "{:02}", time.1).unwrap_or(()),
Some('S') => write!(out, "{:02}", time.2).unwrap_or(()),
Some('3') => {
write!(out, "{:03}", self.stamp.nano / 1_000_000).unwrap_or(())
}
Some('6') => write!(out, "{:06}", self.stamp.nano / 1_000).unwrap_or(()),
Some('9') => write!(out, "{:09}", self.stamp.nano).unwrap_or(()),
Some('x') => {
write!(out, "{:+03}:{:02}", off.off / 3600, off.off % 3600 / 60)
.unwrap_or(())
}
Some('z') => {
write!(out, "{:+03}{:02}", off.off / 3600, off.off % 3600 / 60)
.unwrap_or(())
}
Some('Z') => write!(out, "{}", off.abbr).unwrap_or(()),
Some('w') => write!(out, "{}", tm.tm_wday).unwrap_or(()),
Some('a') => {
write!(out, "{}", &Self::tm_to_weekday(&tm)[..3]).unwrap_or(())
}
Some('A') => write!(out, "{}", Self::tm_to_weekday(&tm)).unwrap_or(()),
Some('b') => write!(out, "{}", &Self::tm_to_month(&tm)[..3]).unwrap_or(()),
Some('B') => write!(out, "{}", Self::tm_to_month(&tm)).unwrap_or(()),
Some('C') => write!(out, "{}", date.0 / 100).unwrap_or(()),
Some(c) => return Err(FmtError::InvalidFormatter(c)),
}
}
Some(c) => out.push(c),
}
}
Ok(out)
}
pub fn rfc3339(&self) -> String {
self.format("%Y-%m-%dT%H:%M:%S%x").unwrap()
}
pub fn rfc2822(&self) -> String {
self.format("%a, %e %b %Y %H:%M:%S %z").unwrap()
}
}
impl<'a> PartialEq<Datetime<'a>> for Datetime<'a> {
fn eq(&self, other: &Datetime) -> bool {
self.stamp.eq(&other.stamp)
}
}
impl<'a> Eq for Datetime<'a> {}
impl<'a> PartialOrd<Datetime<'a>> for Datetime<'a> {
fn partial_cmp(&self, other: &Datetime) -> Option<Ordering> {
self.stamp.partial_cmp(&other.stamp)
}
}
impl<'a> Ord for Datetime<'a> {
fn cmp(&self, other: &Datetime) -> Ordering {
self.stamp.cmp(&other.stamp)
}
}
impl<'a> Add<Deltatime> for Datetime<'a> {
type Output = Datetime<'a>;
fn add(self, rhs: Deltatime) -> Self::Output {
Datetime {
tz: self.tz,
stamp: self.stamp + rhs,
}
}
}
impl<'a> Sub<Deltatime> for Datetime<'a> {
type Output = Datetime<'a>;
fn sub(self, rhs: Deltatime) -> Self::Output {
Datetime {
tz: self.tz,
stamp: self.stamp - rhs,
}
}
}
impl<'a> Sub<Datetime<'a>> for Datetime<'a> {
type Output = Deltatime;
fn sub(self, rhs: Datetime<'a>) -> Self::Output {
self.stamp - rhs.stamp
}
}
impl<'a> fmt::Debug for Datetime<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}", self.rfc3339())
}
}
pub enum TzError {
IOError(io::Error),
InvalidTzFile,
InvalidPosixTz,
UnsupportedTzFile,
}
impl fmt::Debug for TzError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
use std::error::Error;
write!(fmt, "{}", self.description())
}
}
impl fmt::Display for TzError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
use std::error::Error;
write!(fmt, "{}", self.description())
}
}
impl error::Error for TzError {
fn description(&self) -> &str {
match *self {
TzError::IOError(_) => "io error",
TzError::InvalidTzFile => "invalid TZ file",
TzError::InvalidPosixTz => "invalid POSIX TZ",
TzError::UnsupportedTzFile => "unsupported TZ file",
}
}
}
impl std::convert::From<io::Error> for TzError {
fn from(e: io::Error) -> Self {
TzError::IOError(e)
}
}
pub enum InputError {
InvalidMonth,
InvalidDay,
InvalidHour,
InvalidMinute,
InvalidSecond,
InvalidNano,
InvalidLeapSecond,
InvalidFormat,
}
impl fmt::Debug for InputError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
use std::error::Error;
write!(fmt, "{}", self.description())
}
}
impl fmt::Display for InputError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
use std::error::Error;
write!(fmt, "{}", self.description())
}
}
impl error::Error for InputError {
fn description(&self) -> &str {
match *self {
InputError::InvalidMonth => "invalid month",
InputError::InvalidDay => "invalid day",
InputError::InvalidHour => "invalid hour",
InputError::InvalidMinute => "invalid minute",
InputError::InvalidSecond => "invalid second",
InputError::InvalidNano => "invalid nanosecond",
InputError::InvalidLeapSecond => "invalid leap second",
InputError::InvalidFormat => "invalid format",
}
}
}
pub enum FmtError {
UnexpectedEndOfString,
InvalidFormatter(char),
}
impl fmt::Debug for FmtError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
use std::error::Error;
write!(fmt, "{}", self.description())
}
}
impl fmt::Display for FmtError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
use std::error::Error;
write!(fmt, "{}", self.description())
}
}
impl error::Error for FmtError {
fn description(&self) -> &str {
match *self {
FmtError::UnexpectedEndOfString => "unexpected end of string",
FmtError::InvalidFormatter(_) => "invalid formatter",
}
}
}
const NTP_TO_UNIX: i64 = 2208988800;
const LEAP_SECONDS: &'static [i64] = &[2272060800 - NTP_TO_UNIX,
2287785600 - NTP_TO_UNIX,
2303683200 - NTP_TO_UNIX,
2335219200 - NTP_TO_UNIX,
2366755200 - NTP_TO_UNIX,
2398291200 - NTP_TO_UNIX,
2429913600 - NTP_TO_UNIX,
2461449600 - NTP_TO_UNIX,
2492985600 - NTP_TO_UNIX,
2524521600 - NTP_TO_UNIX,
2571782400 - NTP_TO_UNIX,
2603318400 - NTP_TO_UNIX,
2634854400 - NTP_TO_UNIX,
2698012800 - NTP_TO_UNIX,
2776982400 - NTP_TO_UNIX,
2840140800 - NTP_TO_UNIX,
2871676800 - NTP_TO_UNIX,
2918937600 - NTP_TO_UNIX,
2950473600 - NTP_TO_UNIX,
2982009600 - NTP_TO_UNIX,
3029443200 - NTP_TO_UNIX,
3076704000 - NTP_TO_UNIX,
3124137600 - NTP_TO_UNIX,
3345062400 - NTP_TO_UNIX,
3439756800 - NTP_TO_UNIX,
3550089600 - NTP_TO_UNIX,
3644697600 - NTP_TO_UNIX,
3692217600 - NTP_TO_UNIX];
#[derive(Clone, Copy, Debug)]
pub struct Deltatime(Delta);
#[derive(Clone, Copy, Debug)]
enum Delta {
Nanoseconds(i64),
Seconds(i64),
Days(i64),
}
impl Deltatime {
pub fn nanoseconds(n: i64) -> Self {
Deltatime(Delta::Nanoseconds(n))
}
pub fn microseconds(n: i64) -> Self {
Deltatime(Delta::Nanoseconds(n * 1_000))
}
pub fn milliseconds(n: i64) -> Self {
Deltatime(Delta::Nanoseconds(n * 1_000_000))
}
pub fn seconds(n: i64) -> Self {
Deltatime(Delta::Seconds(n))
}
pub fn minutes(n: i64) -> Self {
Deltatime(Delta::Seconds(n * 60))
}
pub fn hours(n: i64) -> Self {
Deltatime(Delta::Seconds(n * 3600))
}
pub fn days(n: i64) -> Self {
Deltatime(Delta::Days(n))
}
pub fn as_nanoseconds(&self) -> i64 {
match self.0 {
Delta::Nanoseconds(n) => n,
Delta::Seconds(n) => n * 1_000_000_000,
Delta::Days(n) => n * 86400 * 1_000_000_000,
}
}
pub fn as_microseconds(&self) -> i64 {
self.as_nanoseconds() / 1_000
}
pub fn as_milliseconds(&self) -> i64 {
self.as_nanoseconds() / 1_000_000
}
pub fn as_seconds(&self) -> i64 {
match self.0 {
Delta::Nanoseconds(n) => n / 1_000_000_000,
Delta::Seconds(n) => n,
Delta::Days(n) => n * 86400,
}
}
pub fn as_minutes(&self) -> i64 {
self.as_seconds() / 60
}
pub fn as_hours(&self) -> i64 {
self.as_seconds() / 3_600
}
pub fn as_days(&self) -> i64 {
match self.0 {
Delta::Nanoseconds(n) => n / 1_000_000_000 / 86400,
Delta::Seconds(n) => n / 86400,
Delta::Days(n) => n,
}
}
fn cmp_delta(&self, rhs: &Deltatime) -> Ordering {
self.as_nanoseconds().cmp(&rhs.as_nanoseconds())
}
}
impl PartialEq<Deltatime> for Deltatime {
fn eq(&self, other: &Deltatime) -> bool {
self.cmp_delta(&other) == Ordering::Equal
}
}
impl Eq for Deltatime {}
impl PartialOrd<Deltatime> for Deltatime {
fn partial_cmp(&self, other: &Deltatime) -> Option<Ordering> {
Some(self.cmp_delta(&other))
}
}
impl Ord for Deltatime {
fn cmp(&self, other: &Self) -> Ordering {
self.cmp_delta(other)
}
}
impl Neg for Deltatime {
type Output = Deltatime;
fn neg(self) -> Self::Output {
Deltatime(match self.0 {
Delta::Nanoseconds(n) => Delta::Nanoseconds(-n),
Delta::Seconds(n) => Delta::Seconds(-n),
Delta::Days(n) => Delta::Days(-n),
})
}
}
fn stamp_to_tm(sec: i64) -> libc::tm {
let mut tm = libc::tm {
tm_sec: 0,
tm_min: 0,
tm_hour: 0,
tm_mday: 0,
tm_mon: 0,
tm_year: 0,
tm_wday: 0,
tm_yday: 0,
tm_isdst: 0,
tm_gmtoff: 0,
tm_zone: sys::empty_tm_zone(),
};
#[cfg(target_pointer_width="32")]
fn gmtime_r(sec: i64, tm: &mut libc::tm) {
unsafe { libc::gmtime_r(&(sec as i32), tm) };
}
#[cfg(target_pointer_width="64")]
fn gmtime_r(sec: i64, tm: &mut libc::tm) {
unsafe { libc::gmtime_r(&sec, tm) };
}
gmtime_r(sec, &mut tm);
tm
}
fn tm_to_stamp(tm: &libc::tm) -> i64 {
const CUM_DAYS: &'static [i64; 12] = &[0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
let year = 1900 + tm.tm_year as i64;
let mut result = (year - 1970) * 365 + CUM_DAYS[tm.tm_mon as usize];
result += (year - 1968) / 4;
result -= (year - 1900) / 100;
result += (year - 1600) / 400;
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) && tm.tm_mon < 2 {
result -= 1;
}
result += tm.tm_mday as i64 - 1;
result *= 24;
result += tm.tm_hour as i64;
result *= 60;
result += tm.tm_min as i64;
result *= 60;
result += tm.tm_sec as i64;
result
}
extern "C" {
fn strptime(s: *const libc::c_char,
fmt: *const libc::c_char,
tm: *mut libc::tm)
-> *const libc::c_char;
}
#[cfg(test)]
mod test {
extern crate nom;
extern crate libc;
use super::{GenericDay, Type, TransRule, Deltatime, Timezone, Timespec};
use super::{tm_to_stamp, stamp_to_tm, posixtz};
use super::sys;
#[test]
fn test_datetime() {
let utc = Timezone::utc();
for &((y, m, d, h, mi, s, n), stamp) in &[((1970, 1, 1, 0, 0, 0, 0), 0),
((1970, 1, 1, 1, 0, 0, 0), 3600),
((2016, 1, 1, 0, 0, 0, 0), 1451606400),
((1969, 12, 31, 23, 59, 59, 999_999_999), -1),
((1969, 12, 31, 23, 59, 59, 0), -1),
((1969, 12, 31, 23, 59, 58, 0), -2)] {
let dt = utc.datetime(y, m, d, h, mi, s, n).unwrap();
assert_eq!(dt.stamp.sec, stamp);
assert_eq!(dt.stamp.leaping, false);
}
}
#[test]
fn test_leap_second() {
let utc = Timezone::utc();
let t = utc.datetime(2015, 6, 30, 23, 59, 59, 0).unwrap();
let t_leap = utc.datetime(2015, 6, 30, 23, 59, 60, 0).unwrap();
assert_eq!(t.stamp.leaping, false);
assert_eq!(t_leap.stamp.leaping, true);
assert_eq!(t.stamp.sec + 1, t_leap.stamp.sec);
assert_eq!(t_leap.format("%S").unwrap(), "60");
assert_eq!(t_leap.date(), (2015, 6, 30));
assert_eq!(t_leap.time(), (23, 59, 60, 0));
assert_eq!(t_leap.unix(), 1435708800);
assert!(t_leap != t);
}
#[test]
fn test_tz_projection() {
let utc = Timezone::utc();
let seoul = Timezone::new("Asia/Seoul").unwrap();
let new_york = Timezone::new("America/New_York").unwrap();
let t = utc.datetime(2016, 1, 15, 20, 0, 0, 0).unwrap();
let t_seoul = t.project(&seoul);
let t_ny = t.project(&new_york);
assert_eq!(t.date(), (2016, 1, 15));
assert_eq!(t.time(), (20, 0, 0, 0));
assert_eq!(t_seoul, t);
assert_eq!(t_seoul.date(), (2016, 1, 16));
assert_eq!(t_seoul.time(), (5, 0, 0, 0));
assert_eq!(t_ny, t);
assert_eq!(t_ny.date(), (2016, 1, 15));
assert_eq!(t_ny.time(), (15, 0, 0, 0));
}
#[test]
fn test_format() {
let paris = Timezone::new("Europe/Paris").unwrap();
let t = paris.datetime(2006, 1, 2, 15, 4, 5, 123_456_789).unwrap();
assert_eq!(t.format("%Y-%m-%dT%H:%M:%S").unwrap(),
"2006-01-02T15:04:05");
assert_eq!(t.format("%3").unwrap(), "123");
assert_eq!(t.format("%6").unwrap(), "123456");
assert_eq!(t.format("%9").unwrap(), "123456789");
assert_eq!(t.format("%z").unwrap(), "+0100");
assert_eq!(t.format("%Z").unwrap(), "CET");
assert_eq!(t.format("%w").unwrap(), "1");
assert_eq!(t.format("%a").unwrap(), "Mon");
assert_eq!(t.format("%A").unwrap(), "Monday");
assert_eq!(t.format("%b").unwrap(), "Jan");
assert_eq!(t.format("%B").unwrap(), "January");
assert_eq!(t.format("%C").unwrap(), "20");
assert_eq!(t.rfc3339(), "2006-01-02T15:04:05+01:00");
assert_eq!(t.rfc2822(), "Mon, 2 Jan 2006 15:04:05 +0100");
}
#[test]
fn test_parse() {
let paris = Timezone::new("Europe/Paris").unwrap();
let utc = Timezone::utc();
let t = paris.parse("2006-01-02T15:04:05", "%Y-%m-%dT%H:%M:%S").unwrap();
let t_utc = t.project(&utc);
assert_eq!(t_utc.date(), (2006, 1, 2));
assert_eq!(t_utc.time(), (14, 4, 5, 0));
paris.parse("2006-01", &"%Y-%m-%d"[..5]).unwrap();
paris.parse(&"2006-01-02"[..7], "%Y-%m").unwrap();
}
#[test]
fn test_generic_day() {
fn t(y: i32, m: i32, d: i32) -> libc::tm {
let tm = libc::tm {
tm_sec: 0,
tm_min: 0,
tm_hour: 0,
tm_mday: d,
tm_mon: m - 1,
tm_year: y - 1900,
tm_wday: 0,
tm_yday: 0,
tm_isdst: 0,
tm_gmtoff: 0,
tm_zone: sys::empty_tm_zone(),
};
let stamp = tm_to_stamp(&tm);
stamp_to_tm(stamp)
}
for &(ref gen, ref tm) in &[(GenericDay::Julian1 { jday: 59 }, t(2016, 2, 28)),
(GenericDay::Julian1 { jday: 60 }, t(2016, 3, 1)),
(GenericDay::Julian1 { jday: 365 }, t(2016, 12, 31)),
(GenericDay::Julian1 { jday: 59 }, t(2015, 2, 28)),
(GenericDay::Julian1 { jday: 60 }, t(2015, 3, 1)),
(GenericDay::Julian1 { jday: 365 }, t(2015, 12, 31)),
(GenericDay::Julian0 { jday: 58 }, t(2016, 2, 28)),
(GenericDay::Julian0 { jday: 59 }, t(2016, 2, 29)),
(GenericDay::Julian0 { jday: 60 }, t(2016, 3, 1)),
(GenericDay::Julian0 { jday: 365 }, t(2016, 12, 31)),
(GenericDay::Julian0 { jday: 58 }, t(2015, 2, 28)),
(GenericDay::Julian0 { jday: 59 }, t(2015, 3, 1)),
(GenericDay::Julian0 { jday: 364 }, t(2015, 12, 31)),
(GenericDay::MWDRule {
month: 1,
week: 1,
wday: 0,
},
t(2016, 1, 3)),
(GenericDay::MWDRule {
month: 1,
week: 2,
wday: 0,
},
t(2016, 1, 10)),
(GenericDay::MWDRule {
month: 1,
week: 5,
wday: 0,
},
t(2016, 1, 31)),
(GenericDay::MWDRule {
month: 1,
week: 1,
wday: 5,
},
t(2016, 1, 1)),
(GenericDay::MWDRule {
month: 1,
week: 5,
wday: 4,
},
t(2016, 1, 28)),
(GenericDay::MWDRule {
month: 1,
week: 4,
wday: 4,
},
t(2016, 1, 28))] {
if gen != tm {
panic!("{:?} != {:04}-{:02}-{:02}",
gen,
tm.tm_year,
tm.tm_mon,
tm.tm_mday);
}
}
}
#[test]
fn test_trans_rule() {
fn s(y: i32, mo: i32, d: i32, h: i32, m: i32, s: i32) -> i64 {
let tm = libc::tm {
tm_sec: s,
tm_min: m,
tm_hour: h,
tm_mday: d,
tm_mon: mo - 1,
tm_year: y - 1900,
tm_wday: 0,
tm_yday: 0,
tm_isdst: 0,
tm_gmtoff: 0,
tm_zone: sys::empty_tm_zone(),
};
tm_to_stamp(&tm)
}
let kst = Type {
off: 9 * 3600,
is_dst: false,
abbr: "KST".to_owned(),
};
let seoul_rule = TransRule::Fixed(kst.clone());
assert_eq!(seoul_rule.get_type(s(2016, 1, 1, 0, 0, 0)), &kst);
assert_eq!(seoul_rule.get_type(s(2016, 7, 1, 0, 0, 0)), &kst);
assert_eq!(seoul_rule.get_type(s(2016, 10, 1, 0, 0, 0)), &kst);
let cet = Type {
off: 1 * 3600,
is_dst: false,
abbr: "CET".to_owned(),
};
let cest = Type {
off: 2 * 3600,
is_dst: true,
abbr: "CEST".to_owned(),
};
let paris_rule = TransRule::Alternate {
dst_start: GenericDay::MWDRule {
month: 3,
week: 5,
wday: 0,
},
dst_stime: 2 * 3600,
dst_end: GenericDay::MWDRule {
month: 10,
week: 5,
wday: 0,
},
dst_etime: 3 * 3600,
std: cet.clone(),
dst: cest.clone(),
};
assert_eq!(paris_rule.get_type(s(2016, 1, 1, 0, 0, 0)), &cet);
assert_eq!(paris_rule.get_type(s(2016, 3, 27, 0, 59, 59)), &cet);
assert_eq!(paris_rule.get_type(s(2016, 3, 27, 1, 0, 0)), &cest);
assert_eq!(paris_rule.get_type(s(2016, 10, 30, 0, 59, 59)), &cest);
assert_eq!(paris_rule.get_type(s(2016, 10, 30, 1, 0, 0)), &cet);
assert_eq!(paris_rule.get_type(s(2016, 12, 31, 0, 0, 0)), &cet);
let aest = Type {
off: 10 * 3600,
is_dst: false,
abbr: "AEST".to_owned(),
};
let aedt = Type {
off: 11 * 3600,
is_dst: true,
abbr: "AEDT".to_owned(),
};
let sydney_rule = TransRule::Alternate {
dst_start: GenericDay::MWDRule {
month: 10,
week: 1,
wday: 0,
},
dst_stime: 2 * 3600,
dst_end: GenericDay::MWDRule {
month: 4,
week: 1,
wday: 0,
},
dst_etime: 3 * 3600,
std: aest.clone(),
dst: aedt.clone(),
};
assert_eq!(sydney_rule.get_type(s(2016, 1, 1, 0, 0, 0)), &aedt);
assert_eq!(sydney_rule.get_type(s(2016, 4, 2, 15, 59, 59)), &aedt);
assert_eq!(sydney_rule.get_type(s(2016, 4, 2, 16, 0, 0)), &aest);
assert_eq!(sydney_rule.get_type(s(2016, 10, 1, 15, 59, 59)), &aest);
assert_eq!(sydney_rule.get_type(s(2016, 10, 1, 16, 0, 0)), &aedt);
assert_eq!(sydney_rule.get_type(s(2016, 12, 31, 0, 0, 0)), &aedt);
let est = Type {
off: -5 * 3600,
is_dst: false,
abbr: "EST".to_owned(),
};
let edt = Type {
off: -4 * 3600,
is_dst: true,
abbr: "EDT".to_owned(),
};
let newyork_rule = TransRule::Alternate {
dst_start: GenericDay::MWDRule {
month: 3,
week: 2,
wday: 0,
},
dst_stime: 2 * 3600,
dst_end: GenericDay::MWDRule {
month: 11,
week: 1,
wday: 0,
},
dst_etime: 2 * 3600,
std: est.clone(),
dst: edt.clone(),
};
assert_eq!(newyork_rule.get_type(s(2016, 1, 1, 0, 0, 0)), &est);
assert_eq!(newyork_rule.get_type(s(2016, 3, 13, 6, 59, 59)), &est);
assert_eq!(newyork_rule.get_type(s(2016, 3, 13, 7, 0, 0)), &edt);
assert_eq!(newyork_rule.get_type(s(2016, 11, 6, 5, 59, 59)), &edt);
assert_eq!(newyork_rule.get_type(s(2016, 11, 6, 6, 0, 0)), &est);
assert_eq!(newyork_rule.get_type(s(2016, 12, 31, 0, 0, 0)), &est);
}
#[test]
fn test_add_deltatime() {
let utc = Timezone::utc();
for &(dt, delta, res) in &[
((2015, 6, 30, 23, 59, 59, 999_999_999),
Deltatime::nanoseconds(2),
(2015, 6, 30, 23, 59, 60, 1)),
((2015, 6, 30, 23, 59, 60, 1),
Deltatime::nanoseconds(-2),
(2015, 6, 30, 23, 59, 59, 999_999_999)),
((2015, 6, 30, 23, 59, 60, 200_000_000),
Deltatime::nanoseconds(-300_000_000),
(2015, 6, 30, 23, 59, 59, 900_000_000)),
((2015, 6, 30, 23, 59, 60, 1),
Deltatime::nanoseconds(-1_000_000_002),
(2015, 6, 30, 23, 59, 58, 999_999_999)),
((2016, 1, 1, 0, 0, 0, 0),
Deltatime::nanoseconds(1_500_000_000),
(2016, 1, 1, 0, 0, 1, 500_000_000)),
((2016, 1, 1, 0, 0, 1, 500_000_000),
Deltatime::nanoseconds(-1_500_000_001),
(2015, 12, 31, 23, 59, 59, 999_999_999)),
((2016, 1, 1, 0, 0, 1, 500_000_000),
Deltatime::nanoseconds(-1_500_000_000),
(2016, 1, 1, 0, 0, 0, 0)),
((2016, 1, 1, 0, 0, 0, 500_000_000),
Deltatime::nanoseconds(500_000_001),
(2016, 1, 1, 0, 0, 1, 1)),
((2016, 1, 1, 0, 0, 1, 1),
Deltatime::nanoseconds(-500_000_001),
(2016, 1, 1, 0, 0, 0, 500_000_000)),
((2015, 6, 30, 23, 59, 59, 0),
Deltatime::seconds(1),
(2015, 6, 30, 23, 59, 60, 0)),
((2015, 6, 30, 23, 59, 60, 0),
Deltatime::seconds(-1),
(2015, 6, 30, 23, 59, 59, 0)),
((2015, 6, 30, 23, 59, 60, 0),
Deltatime::seconds(1),
(2015, 7, 1, 0, 0, 0, 0)),
((2015, 7, 1, 0, 0, 0, 0),
Deltatime::seconds(-1),
(2015, 6, 30, 23, 59, 60, 0)),
((2015, 6, 30, 23, 59, 59, 0),
Deltatime::seconds(2),
(2015, 7, 1, 0, 0, 0, 0)),
((2015, 7, 1, 0, 0, 0, 0),
Deltatime::seconds(-2),
(2015, 6, 30, 23, 59, 59, 0)),
((2015, 7, 1, 0, 0, 0, 0),
Deltatime::seconds(1),
(2015, 7, 1, 0, 0, 1, 0)),
((2015, 7, 1, 0, 0, 1, 0),
Deltatime::seconds(-1),
(2015, 7, 1, 0, 0, 0, 0)),
((2015, 6, 30, 23, 59, 59, 0),
Deltatime::seconds(3),
(2015, 7, 1, 0, 0, 1, 0)),
((2015, 7, 1, 0, 0, 1, 0),
Deltatime::seconds(-3),
(2015, 6, 30, 23, 59, 59, 0)),
((2012, 1, 1, 0, 0, 0, 0),
Deltatime::seconds(126230402),
(2016, 1, 1, 0, 0, 0, 0)),
((2016, 1, 1, 0, 0, 0, 0),
Deltatime::seconds(-126230402),
(2012, 1, 1, 0, 0, 0, 0)),
((1969, 12, 31, 23, 59, 59, 0),
Deltatime::seconds(1),
(1970, 1, 1, 0, 0, 0, 0)),
((1970, 1, 1, 0, 0, 0, 0),
Deltatime::seconds(-1),
(1969, 12, 31, 23, 59, 59, 0)),
((2015, 6, 30, 23, 59, 60, 0),
Deltatime::seconds(0),
(2015, 6, 30, 23, 59, 60, 0)),
((2012, 6, 30, 23, 59, 60, 0),
Deltatime::seconds(94608001),
(2015, 6, 30, 23, 59, 60, 0)),
((2015, 6, 30, 23, 59, 60, 0),
Deltatime::seconds(-94608001),
(2012, 6, 30, 23, 59, 60, 0)),
((2016, 2, 29, 0, 0, 0, 0),
Deltatime::days(1),
(2016, 3, 1, 0, 0, 0, 0)),
((2016, 3, 1, 0, 0, 0, 0),
Deltatime::days(-1),
(2016, 2, 29, 0, 0, 0, 0)),
((2016, 1, 1, 0, 0, 0, 0),
Deltatime::days(366),
(2017, 1, 1, 0, 0, 0, 0)),
((2017, 1, 1, 0, 0, 0, 0),
Deltatime::days(-366),
(2016, 1, 1, 0, 0, 0, 0)),
((2015, 6, 30, 23, 59, 60, 0),
Deltatime::days(1),
(2015, 7, 2, 0, 0, 0, 0))] {
let t = utc.datetime(dt.0, dt.1, dt.2, dt.3, dt.4, dt.5, dt.6).unwrap();
let t = t + delta;
assert_eq!(t.date(), (res.0, res.1, res.2));
assert_eq!(t.time(), (res.3, res.4, res.5, res.6));
}
}
#[test]
fn test_delta_conversion() {
let d = Deltatime::nanoseconds(1);
assert_eq!(d.as_nanoseconds(), 1);
assert_eq!(d.as_microseconds(), 0);
assert_eq!(d.as_milliseconds(), 0);
assert_eq!(d.as_seconds(), 0);
assert_eq!(d.as_minutes(), 0);
assert_eq!(d.as_hours(), 0);
assert_eq!(d.as_days(), 0);
let d = Deltatime::seconds(1);
assert_eq!(d.as_nanoseconds(), 1_000_000_000);
assert_eq!(d.as_microseconds(), 1_000_000);
assert_eq!(d.as_milliseconds(), 1_000);
assert_eq!(d.as_seconds(), 1);
assert_eq!(d.as_minutes(), 0);
assert_eq!(d.as_hours(), 0);
assert_eq!(d.as_days(), 0);
let d = Deltatime::days(1);
assert_eq!(d.as_nanoseconds(), 1_000_000_000 * 86400);
assert_eq!(d.as_microseconds(), 1_000_000 * 86400);
assert_eq!(d.as_milliseconds(), 1_000 * 86400);
assert_eq!(d.as_seconds(), 1 * 86400);
assert_eq!(d.as_minutes(), 1440);
assert_eq!(d.as_hours(), 24);
assert_eq!(d.as_days(), 1);
}
#[test]
fn test_cmp_datetimes() {
let utc = Timezone::utc();
let t0 = utc.datetime(2015, 1, 1, 0, 0, 0, 0).unwrap();
let t1 = utc.datetime(2015, 1, 1, 0, 0, 0, 1).unwrap();
assert!(t0 != t1);
assert!(t0 < t1);
assert!(t0 <= t1);
let t0 = utc.datetime(2015, 1, 1, 0, 0, 0, 0).unwrap();
let t1 = utc.datetime(2015, 1, 1, 0, 0, 1, 0).unwrap();
assert!(t0 < t1);
let t0 = utc.datetime(2015, 6, 30, 23, 59, 60, 1).unwrap();
let t1 = utc.datetime(2015, 7, 1, 0, 0, 0, 0).unwrap();
assert!(t0 < t1);
}
#[test]
fn test_sub_datetimes() {
let utc = Timezone::utc();
let t0 = utc.datetime(2015, 1, 1, 0, 0, 0, 0).unwrap();
let t1 = utc.datetime(2015, 1, 2, 0, 0, 0, 0).unwrap();
assert_eq!(t1 - t0, Deltatime::days(1));
assert_eq!(t0 - t1, Deltatime::days(-1));
let t0 = utc.datetime(2015, 6, 30, 23, 59, 59, 0).unwrap();
let t1 = utc.datetime(2015, 7, 1, 0, 0, 1, 0).unwrap();
assert_eq!(t1 - t0, Deltatime::seconds(3));
assert_eq!(t0 - t1, Deltatime::seconds(-3));
let t0 = utc.datetime(2015, 6, 30, 23, 59, 59, 0).unwrap();
let t1 = utc.datetime(2015, 6, 30, 23, 59, 60, 0).unwrap();
assert_eq!(t1 - t0, Deltatime::seconds(1));
assert_eq!(t0 - t1, Deltatime::seconds(-1));
let t0 = utc.datetime(2015, 6, 30, 23, 59, 59, 0).unwrap();
let t1 = utc.datetime(2015, 7, 1, 0, 0, 0, 0).unwrap();
assert_eq!(t1 - t0, Deltatime::seconds(2));
assert_eq!(t0 - t1, Deltatime::seconds(-2));
let t0 = utc.datetime(2015, 6, 30, 23, 59, 60, 0).unwrap();
let t1 = utc.datetime(2015, 7, 1, 0, 0, 1, 0).unwrap();
assert_eq!(t1 - t0, Deltatime::seconds(2));
assert_eq!(t0 - t1, Deltatime::seconds(-2));
let t0 = utc.datetime(2015, 7, 1, 0, 0, 0, 0).unwrap();
let t1 = utc.datetime(2015, 7, 1, 0, 0, 1, 0).unwrap();
assert_eq!(t1 - t0, Deltatime::seconds(1));
assert_eq!(t0 - t1, Deltatime::seconds(-1));
let t0 = utc.datetime(2015, 6, 30, 23, 59, 60, 0).unwrap();
let t1 = utc.datetime(2015, 7, 1, 0, 0, 0, 0).unwrap();
assert_eq!(t1 - t0, Deltatime::seconds(1));
assert_eq!(t0 - t1, Deltatime::seconds(-1));
let t0 = utc.datetime(2015, 6, 30, 23, 59, 60, 0).unwrap();
let t1 = utc.datetime(2015, 6, 30, 23, 59, 60, 0).unwrap();
assert_eq!(t1 - t0, Deltatime::seconds(0));
assert_eq!(t0 - t1, Deltatime::seconds(0));
let t0 = utc.datetime(2015, 7, 1, 0, 0, 0, 0).unwrap();
let t1 = utc.datetime(2015, 7, 1, 0, 0, 0, 0).unwrap();
assert_eq!(t1 - t0, Deltatime::seconds(0));
assert_eq!(t0 - t1, Deltatime::seconds(0));
}
#[test]
fn test_posixtz() {
fn mwd(m: i32, w: i32, d: i32) -> GenericDay {
GenericDay::MWDRule {
month: m,
week: w,
wday: d,
}
}
fn std(off_h: i32, abbr: &str) -> Type {
Type {
off: off_h * 3600,
is_dst: false,
abbr: abbr.to_owned(),
}
}
fn dst(off_h: i32, abbr: &str) -> Type {
Type {
off: off_h * 3600,
is_dst: true,
abbr: abbr.to_owned(),
}
}
for &(sample, ref expected) in &[("AEST-10AEDT,M10.1.0,M4.1.0/3",
TransRule::Alternate {
dst_start: mwd(10, 1, 0),
dst_stime: 2 * 3600,
dst_end: mwd(4, 1, 0),
dst_etime: 3 * 3600,
std: std(10, "AEST"),
dst: dst(11, "AEDT"),
}),
("CST6CDT,M4.1.0,M10.5.0",
TransRule::Alternate {
dst_start: mwd(4, 1, 0),
dst_stime: 2 * 3600,
dst_end: mwd(10, 5, 0),
dst_etime: 2 * 3600,
std: std(-6, "CST"),
dst: dst(-5, "CDT"),
}),
("<+07>-7", TransRule::Fixed(std(7, "+07"))),
("MSK-3", TransRule::Fixed(std(3, "MSK"))),
("ART3", TransRule::Fixed(std(-3, "ART"))),
("WET0WEST,M3.5.0/1,M10.5.0",
TransRule::Alternate {
dst_start: mwd(3, 5, 0),
dst_stime: 1 * 3600,
dst_end: mwd(10, 5, 0),
dst_etime: 2 * 3600,
std: std(0, "WET"),
dst: dst(1, "WEST"),
})] {
match posixtz(sample) {
nom::IResult::Done(_, ref r) => assert_eq!(*r, *expected),
_ => panic!("{} != {:?}", sample, expected),
}
}
}
#[test]
fn test_datetime_timespec_conv() {
let t = Timespec::now();
let utc = Timezone::utc();
let utc2 = Timezone::fixed(2 * 3600);
let dt_utc = t.to_datetime(&utc);
let dt_utc2 = t.to_datetime(&utc2);
let t0 = dt_utc.to_timespec();
let t2 = dt_utc2.to_timespec();
assert_eq!(t, t0);
assert_eq!(t0, t2);
}
}