extern crate time;
mod parse;
use std::rc::Rc;
use std::io;
use std::fmt;
use std::cmp::{Eq, PartialEq, Ord, PartialOrd, Ordering};
use std::ops::{Add, Sub};
pub struct Timezone {
pub name: String,
trans: Vec<Transition>,
trule: Option<TransRule>,
}
#[derive(Debug)]
struct Transition {
utc: i64,
ttype: Rc<Type>,
}
#[derive(Debug)]
enum TransRule {
Fixed(Type),
Alternate {
dst_start: GenericDay,
dst_stime: i32,
dst_end: GenericDay,
dst_etime: i32,
std: Type,
dst: Type,
},
}
#[derive(Debug)]
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) -> io::Result<Self> {
parse::load_timezone(timezone)
}
pub fn local() -> io::Result<Self> {
parse::load_timezone("localtime")
}
pub fn utc() -> Self {
Self::fixed(0)
}
pub fn fixed(sec: i32) -> Self {
Timezone {
name: "UTC".to_owned(),
trans: vec![Transition {
utc: std::i64::MIN,
ttype: Rc::new(Type {
off: sec,
is_dst: false,
abbr: "".to_owned(),
}),
}],
trule: None,
}
}
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: time::get_time(),
is_60th_sec: false,
}
}
pub fn parse(&self, s: &str, fmt: &str) -> Datetime {
let tm = time::strptime(s, fmt).unwrap();
self.datetime(tm.tm_year + 1900,
tm.tm_mon + 1,
tm.tm_mday,
tm.tm_hour,
tm.tm_min,
tm.tm_sec,
tm.tm_nsec)
}
pub fn unix(&self, stamp: i64, nano: i32) -> Datetime {
assert!(nano >= 0 && nano < 1_000_000_000);
Datetime {
tz: self,
stamp: time::Timespec {
sec: stamp,
nsec: nano,
},
is_60th_sec: false,
}
}
pub fn datetime(&self,
year: i32,
month: i32,
day: i32,
hour: i32,
minute: i32,
second: i32,
nano: i32)
-> Datetime {
let is_leap_year = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
assert!(month >= 1 && month <= 12);
assert!(hour <= 23);
assert!(minute <= 59);
assert!(second <= 60);
assert!(nano <= 999_999_999);
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!(),
};
assert!(day >= 1 && day <= max_day);
let utc_dt = time::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_utcoff: 0,
tm_nsec: nano as i32,
};
let mut stamp = utc_dt.to_timespec();
let utc_offset = self.offset(stamp.sec).off;
stamp.sec -= utc_offset as i64;
if second == 60 && year >= 1972 {
if let Err(_) = LEAP_SECONDS.binary_search(&(stamp.sec + 1)) {
panic!("not a valid leap second");
}
Datetime {
tz: self,
stamp: time::Timespec {
sec: stamp.sec + 1,
nsec: stamp.nsec,
},
is_60th_sec: true,
}
} else {
Datetime {
tz: self,
stamp: stamp,
is_60th_sec: false,
}
}
}
}
impl GenericDay {
fn cmp_t(&self, tm: &time::Tm) -> Ordering {
const DAYS_MONTH: &'static [i32] = &[0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
let is_leap_year = (tm.tm_year % 4 == 0 && tm.tm_year % 100 != 0) || tm.tm_year % 400 == 0;
match *self {
GenericDay::Julian0 { jday } => jday.cmp(&tm.tm_yday),
GenericDay::Julian1 { jday } => {
let yday = if is_leap_year && tm.tm_mon > 2 {
tm.tm_yday
} else {
tm.tm_yday + 1
};
jday.cmp(&yday)
}
GenericDay::MWDRule { month, week, wday } => {
if month < tm.tm_mon {
Ordering::Less
} else if month > tm.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.tm_mon == 2 {
29
} else {
DAYS_MONTH[tm.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<time::Tm> for GenericDay {
fn eq(&self, tm: &time::Tm) -> bool {
self.cmp_t(tm) == Ordering::Equal
}
}
impl PartialOrd<time::Tm> for GenericDay {
fn partial_cmp(&self, tm: &time::Tm) -> Option<Ordering> {
Some(self.cmp_t(tm))
}
}
impl PartialEq<GenericDay> for GenericDay {
fn eq(&self, other: &GenericDay) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl PartialOrd<GenericDay> for GenericDay {
fn partial_cmp(&self, other: &GenericDay) -> Option<Ordering> {
Some(self.cmp(other))
}
}
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 mut std_t = time::at_utc(time::Timespec { sec: stamp + std.off as i64, nsec: 0 });
std_t.tm_year += 1900;
std_t.tm_mon += 1;
let std_sec = std_t.tm_hour * 3600 + std_t.tm_min * 60 + std_t.tm_sec;
let mut dst_t = time::at_utc(time::Timespec { sec: stamp + dst.off as i64, nsec: 0 });
dst_t.tm_year += 1900;
dst_t.tm_mon += 1;
let dst_sec = dst_t.tm_hour * 3600 + dst_t.tm_min * 60 + dst_t.tm_sec;
if dst_start < dst_end {
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,
}
}
}
}
}
}
}
pub struct Datetime<'a> {
tz: &'a Timezone,
stamp: time::Timespec,
is_60th_sec: bool,
}
impl<'a> Datetime<'a> {
pub fn project<'b>(&self, tz: &'b Timezone) -> Datetime<'b> {
Datetime {
tz: tz,
stamp: self.stamp,
is_60th_sec: self.is_60th_sec,
}
}
fn tm(&self) -> time::Tm {
let offset = self.tz.offset(self.stamp.sec).off;
let local = time::Timespec {
sec: self.stamp.sec + offset as i64 +
if self.is_60th_sec {
-1
} else {
0
},
nsec: self.stamp.nsec,
};
let mut tm = time::at_utc(local);
tm.tm_utcoff = offset;
if self.is_60th_sec {
tm.tm_sec = 60;
}
tm
}
fn tm_to_date(tm: &time::Tm) -> (i32, i32, i32) {
(tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday)
}
fn tm_to_time(tm: &time::Tm) -> (i32, i32, i32, i32) {
(tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_nsec)
}
fn tm_to_weekday(tm: &time::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: &time::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();
Self::tm_to_time(&tm)
}
pub fn unix(&self) -> i64 {
self.stamp.sec
}
pub fn format(&self, fmt: &str) -> String {
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 => panic!("Unfinished formatting after %"),
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}", time.3 / 1_000_000).unwrap_or(()),
Some('6') => write!(out, "{:06}", time.3 / 1_000).unwrap_or(()),
Some('9') => write!(out, "{:09}", time.3).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) => panic!("unknown format control %{}", c),
}
}
Some(c) => out.push(c),
}
}
out
}
pub fn rfc3339(&self) -> String {
self.format("%Y-%m-%dT%H:%M:%S%x")
}
pub fn rfc2822(&self) -> String {
self.format("%a, %e %b %Y %H:%M:%S %z")
}
}
impl<'a> PartialEq<Datetime<'a>> for Datetime<'a> {
fn eq(&self, other: &Datetime) -> bool {
self.cmp(&other) == Ordering::Equal
}
}
impl<'a> Eq for Datetime<'a> {}
impl<'a> PartialOrd<Datetime<'a>> for Datetime<'a> {
fn partial_cmp(&self, other: &Datetime) -> Option<Ordering> {
let cmp = self.stamp.cmp(&other.stamp);
if let Ordering::Equal = cmp {
Some(self.is_60th_sec.cmp(&other.is_60th_sec))
} else {
Some(cmp)
}
}
}
impl<'a> Ord for Datetime<'a> {
fn cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(other).unwrap()
}
}
impl<'a> Add<Deltatime> for Datetime<'a> {
type Output = Datetime<'a>;
fn add(mut self, rhs: Deltatime) -> Self::Output {
match rhs.0 {
Delta::Nanoseconds(nano) => {
if nano < 0 {
let nano = -nano;
if self.stamp.nsec as i64 >= nano {
self.stamp.nsec -= nano as i32;
return self;
}
let nano = nano - self.stamp.nsec as i64;
let sec = nano / 1_000_000_000 + 1;
let nano_left = 1_000_000_000 - (nano - (sec - 1) * 1_000_000_000);
let mut t = self + Deltatime(Delta::Seconds(-sec));
t.stamp.nsec = nano_left as i32;
t
} else {
let nano = nano + self.stamp.nsec as i64;
if nano < 1_000_000_000 {
self.stamp.nsec = 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.stamp.nsec = nano as i32;
t
}
}
Delta::Seconds(mut sec) => {
if sec < 0 {
sec = -sec;
while sec != 0 {
match LEAP_SECONDS.binary_search(&self.stamp.sec) {
Err(0) => {
self.stamp.sec -= sec;
return self;
},
Err(s) => {
let prev_leap = LEAP_SECONDS[s-1];
let prev_leap_in_sec = self.stamp.sec - prev_leap;
if sec <= prev_leap_in_sec {
self.stamp.sec -= sec;
return self;
} else if sec == prev_leap_in_sec + 1 {
self.is_60th_sec = true;
self.stamp.sec = prev_leap;
return self;
} else {
self.stamp.sec = prev_leap - 1;
sec -= prev_leap_in_sec + 2;
}
},
Ok(_) => {
if self.is_60th_sec {
self.is_60th_sec = false;
self.stamp.sec -= 1;
} else {
self.is_60th_sec = true;
}
sec -= 1;
},
}
}
self
} else {
while sec != 0 {
match LEAP_SECONDS.binary_search(&self.stamp.sec) {
Err(s) if s == LEAP_SECONDS.len() => {
self.stamp.sec += sec;
return self;
}
Err(s) => {
let next_leap = LEAP_SECONDS[s];
let next_leap_in_sec = next_leap - self.stamp.sec;
if sec < next_leap_in_sec {
self.stamp.sec += sec;
return self;
} else if sec == next_leap_in_sec {
self.stamp.sec = next_leap;
self.is_60th_sec = true;
return self;
} else {
self.stamp.sec = next_leap;
sec -= next_leap_in_sec + 1;
}
}
Ok(_) => {
if self.is_60th_sec {
self.is_60th_sec = false;
} else {
self.stamp.sec += 1;
}
sec -= 1;
}
}
}
self
}
}
Delta::Days(day) => {
self.is_60th_sec = false;
self.stamp.sec += day * 86400;
self
}
}
}
}
impl<'a> Sub<Deltatime> for Datetime<'a> {
type Output = Datetime<'a>;
fn sub(self, rhs: Deltatime) -> Self::Output {
self +
Deltatime(match rhs.0 {
Delta::Nanoseconds(nano) => Delta::Nanoseconds(-nano),
Delta::Seconds(sec) => Delta::Seconds(-sec),
Delta::Days(day) => Delta::Days(-day),
})
}
}
impl<'a> fmt::Debug for Datetime<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}", self.tm().rfc3339())
}
}
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];
#[derive(Clone, Copy)]
pub struct Deltatime(Delta);
#[derive(Clone, Copy)]
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))
}
}
#[cfg(test)]
mod test {
use super::*;
use super::time;
use super::{GenericDay, Type, TransRule};
#[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)] {
let stamp = time::Timespec {
sec: stamp,
nsec: 0,
};
let dt = utc.datetime(y, m, d, h, mi, s, n);
assert_eq!(dt.stamp, stamp);
assert_eq!(dt.is_60th_sec, false);
}
}
#[test]
fn test_leap_second() {
let utc = Timezone::utc();
let t = utc.datetime(2015, 6, 30, 23, 59, 59, 0);
let t_leap = utc.datetime(2015, 6, 30, 23, 59, 60, 0);
assert_eq!(t.is_60th_sec, false);
assert_eq!(t_leap.is_60th_sec, true);
assert_eq!(t.stamp.sec + 1, t_leap.stamp.sec);
assert_eq!(t_leap.format("%S"), "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);
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);
assert_eq!(t.format("%Y-%m-%dT%H:%M:%S"), "2006-01-02T15:04:05");
assert_eq!(t.format("%3"), "123");
assert_eq!(t.format("%6"), "123456");
assert_eq!(t.format("%9"), "123456789");
assert_eq!(t.format("%z"), "+0100");
assert_eq!(t.format("%Z"), "CET");
assert_eq!(t.format("%w"), "1");
assert_eq!(t.format("%a"), "Mon");
assert_eq!(t.format("%A"), "Monday");
assert_eq!(t.format("%b"), "Jan");
assert_eq!(t.format("%B"), "January");
assert_eq!(t.format("%C"), "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");
let t_utc = t.project(&utc);
assert_eq!(t_utc.date(), (2006, 1, 2));
assert_eq!(t_utc.time(), (14, 4, 5, 0));
}
#[test]
fn test_generic_day() {
fn t(y: i32, m: i32, d: i32) -> time::Tm {
let tm = time::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_utcoff: 0,
tm_nsec: 0,
};
let t = tm.to_timespec();
let mut tm = time::at_utc(t);
tm.tm_year += 1900;
tm.tm_mon += 1;
tm
}
assert_eq!(GenericDay::Julian1 { jday: 59 }, t(2016, 2, 28));
assert_eq!(GenericDay::Julian1 { jday: 60 }, t(2016, 3, 1));
assert_eq!(GenericDay::Julian1 { jday: 365 }, t(2016, 12, 31));
assert_eq!(GenericDay::Julian1 { jday: 59 }, t(2015, 2, 28));
assert_eq!(GenericDay::Julian1 { jday: 60 }, t(2015, 3, 1));
assert_eq!(GenericDay::Julian1 { jday: 365 }, t(2015, 12, 31));
assert_eq!(GenericDay::Julian0 { jday: 58 }, t(2016, 2, 28));
assert_eq!(GenericDay::Julian0 { jday: 59 }, t(2016, 2, 29));
assert_eq!(GenericDay::Julian0 { jday: 60 }, t(2016, 3, 1));
assert_eq!(GenericDay::Julian0 { jday: 365 }, t(2016, 12, 31));
assert_eq!(GenericDay::Julian0 { jday: 58 }, t(2015, 2, 28));
assert_eq!(GenericDay::Julian0 { jday: 59 }, t(2015, 3, 1));
assert_eq!(GenericDay::Julian0 { jday: 364 }, t(2015, 12, 31));
assert_eq!(GenericDay::MWDRule {
month: 1,
week: 1,
wday: 0,
},
t(2016, 1, 3));
assert_eq!(GenericDay::MWDRule {
month: 1,
week: 2,
wday: 0,
},
t(2016, 1, 10));
assert_eq!(GenericDay::MWDRule {
month: 1,
week: 5,
wday: 0,
},
t(2016, 1, 31));
assert_eq!(GenericDay::MWDRule {
month: 1,
week: 1,
wday: 5,
},
t(2016, 1, 1));
assert_eq!(GenericDay::MWDRule {
month: 1,
week: 5,
wday: 4,
},
t(2016, 1, 28));
assert_eq!(GenericDay::MWDRule {
month: 1,
week: 4,
wday: 4,
},
t(2016, 1, 28));
}
#[test]
fn test_trans_rule() {
fn s(y: i32, mo: i32, d: i32, h: i32, m: i32, s: i32) -> i64 {
let tm = time::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_utcoff: 0,
tm_nsec: 0,
};
tm.to_timespec().sec
}
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();
let t = utc.datetime(2015, 6, 30, 23, 59, 59, 999_999_999);
let t = t + Deltatime::nanoseconds(2);
assert_eq!(t.date(), (2015, 6, 30));
assert_eq!(t.time(), (23, 59, 60, 1));
let t = utc.datetime(2015, 6, 30, 23, 59, 60, 1);
let t = t + Deltatime::nanoseconds(-2);
assert_eq!(t.date(), (2015, 6, 30));
assert_eq!(t.time(), (23, 59, 59, 999_999_999));
let t = utc.datetime(2015, 6, 30, 23, 59, 60, 200_000_000);
let t = t + Deltatime::nanoseconds(-300_000_000);
assert_eq!(t.date(), (2015, 6, 30));
assert_eq!(t.time(), (23, 59, 59, 900_000_000));
let t = utc.datetime(2015, 6, 30, 23, 59, 60, 1);
let t = t + Deltatime::nanoseconds(-1_000_000_002);
assert_eq!(t.date(), (2015, 6, 30));
assert_eq!(t.time(), (23, 59, 58, 999_999_999));
let t = utc.datetime(2015, 6, 30, 23, 59, 59, 0);
let t = t + Deltatime::seconds(1);
assert_eq!(t.date(), (2015, 6, 30));
assert_eq!(t.time(), (23, 59, 60, 0));
let t = utc.datetime(2015, 6, 30, 23, 59, 60, 0);
let t = t + Deltatime::seconds(-1);
assert_eq!(t.date(), (2015, 6, 30));
assert_eq!(t.time(), (23, 59, 59, 0));
let t = utc.datetime(2015, 6, 30, 23, 59, 60, 0);
let t = t + Deltatime::seconds(1);
assert_eq!(t.date(), (2015, 7, 1));
assert_eq!(t.time(), (0, 0, 0, 0));
let t = utc.datetime(2015, 7, 1, 0, 0, 0, 0);
let t = t + Deltatime::seconds(-1);
assert_eq!(t.date(), (2015, 6, 30));
assert_eq!(t.time(), (23, 59, 60, 0));
let t = utc.datetime(2015, 6, 30, 23, 59, 59, 0);
let t = t + Deltatime::seconds(2);
assert_eq!(t.date(), (2015, 7, 1));
assert_eq!(t.time(), (0, 0, 0, 0));
let t = utc.datetime(2015, 7, 1, 0, 0, 0, 0);
let t = t + Deltatime::seconds(-2);
assert_eq!(t.date(), (2015, 6, 30));
assert_eq!(t.time(), (23, 59, 59, 0));
let t = utc.datetime(2015, 7, 1, 0, 0, 0, 0);
let t = t + Deltatime::seconds(1);
assert_eq!(t.date(), (2015, 7, 1));
assert_eq!(t.time(), (0, 0, 1, 0));
let t = utc.datetime(2015, 7, 1, 0, 0, 1, 0);
let t = t + Deltatime::seconds(-1);
assert_eq!(t.date(), (2015, 7, 1));
assert_eq!(t.time(), (0, 0, 0, 0));
let t = utc.datetime(2015, 6, 30, 23, 59, 59, 0);
let t = t + Deltatime::seconds(3);
assert_eq!(t.date(), (2015, 7, 1));
assert_eq!(t.time(), (0, 0, 1, 0));
let t = utc.datetime(2015, 7, 1, 0, 0, 1, 0);
let t = t + Deltatime::seconds(-3);
assert_eq!(t.date(), (2015, 6, 30));
assert_eq!(t.time(), (23, 59, 59, 0));
let t = utc.datetime(2012, 1, 1, 0, 0, 0, 0);
let t = t + Deltatime::seconds(126230402); assert_eq!(t.date(), (2016, 1, 1));
assert_eq!(t.time(), (0, 0, 0, 0));
let t = utc.datetime(2016, 1, 1, 0, 0, 0, 0);
let t = t + Deltatime::seconds(-126230402);
assert_eq!(t.date(), (2012, 1, 1));
assert_eq!(t.time(), (0, 0, 0, 0));
let t = utc.datetime(2015, 6, 30, 23, 59, 60, 0);
let t = t + Deltatime::seconds(0);
assert_eq!(t.date(), (2015, 6, 30));
assert_eq!(t.time(), (23, 59, 60, 0));
let t = utc.datetime(2012, 6, 30, 23, 59, 60, 0);
let t = t + Deltatime::seconds(94608001);
assert_eq!(t.date(), (2015, 6, 30));
assert_eq!(t.time(), (23, 59, 60, 0));
let t = utc.datetime(2015, 6, 30, 23, 59, 60, 0);
let t = t + Deltatime::seconds(-94608001);
assert_eq!(t.date(), (2012, 6, 30));
assert_eq!(t.time(), (23, 59, 60, 0));
let t = utc.datetime(2016, 2, 29, 0, 0, 0, 0);
let t = t + Deltatime::days(1);
assert_eq!(t.date(), (2016, 3, 1));
assert_eq!(t.time(), (0, 0, 0, 0));
let t = utc.datetime(2016, 3, 1, 0, 0, 0, 0);
let t = t + Deltatime::days(-1);
assert_eq!(t.date(), (2016, 2, 29));
assert_eq!(t.time(), (0, 0, 0, 0));
let t = utc.datetime(2016, 1, 1, 0, 0, 0, 0);
let t = t + Deltatime::days(366);
assert_eq!(t.date(), (2017, 1, 1));
assert_eq!(t.time(), (0, 0, 0, 0));
let t = utc.datetime(2017, 1, 1, 0, 0, 0, 0);
let t = t + Deltatime::days(-366);
assert_eq!(t.date(), (2016, 1, 1));
assert_eq!(t.time(), (0, 0, 0, 0));
let t = utc.datetime(2015, 6, 30, 23, 59, 60, 0);
let t = t + Deltatime::days(1);
assert_eq!(t.date(), (2015, 7, 2));
assert_eq!(t.time(), (0, 0, 0, 0));
}
}