gregor/
lib.rs

1#![no_std]
2
3#[cfg(any(test, feature = "system_time"))] #[macro_use] extern crate std;
4
5mod num;
6#[cfg(feature = "system_time")] mod system_time;
7#[cfg(test)] mod tests;
8mod time_zones;
9
10use core::fmt;
11use num::positive_rem;
12use time_zones::days_since_unix;
13pub use time_zones::{TimeZone, LocalTimeConversionError, UnambiguousTimeZone, DaylightSaving,
14                     Utc, FixedOffsetFromUtc, CentralEurope};
15
16/// In seconds since 1970-01-01 00:00:00 UTC.
17#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
18pub struct UnixTimestamp(pub i64);
19
20#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
21pub struct DateTime<Tz: TimeZone> {
22    pub naive: NaiveDateTime,
23    pub time_zone: Tz,
24}
25
26/// A date and time without associated time zone information.
27#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
28pub struct NaiveDateTime {
29    /// Year number per ISO 8601.
30    ///
31    /// For example, 2016 AC is +2016, 1 AC is +1, 1 BC is 0, 2 BC is -1, etc.
32    pub year: i32,
33
34    pub month: Month,
35
36    /// 1st of the month is day 1
37    pub day: u8,
38
39    pub hour: u8,
40    pub minute: u8,
41    pub second: u8,
42}
43
44impl<Tz: fmt::Debug + TimeZone> fmt::Debug for DateTime<Tz> {
45    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
46        write!(formatter, "DateTime({:?}, {:?})", self.time_zone, self.naive)
47    }
48}
49
50impl fmt::Debug for NaiveDateTime {
51    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
52        write!(formatter, "{:04}-{:02}-{:02} {:02}:{:02}:{:02}",
53               self.year, self.month.to_number(), self.day,
54               self.hour, self.minute, self.second)
55    }
56}
57
58impl<Tz: TimeZone> DateTime<Tz> {
59    pub fn new(time_zone: Tz, year: i32, month: Month, day: u8,
60               hour: u8, minute: u8, second: u8)
61               -> Self {
62        DateTime {
63            naive: NaiveDateTime::new(year, month, day, hour, minute, second),
64            time_zone: time_zone,
65        }
66    }
67
68    pub fn year(&self) -> i32 { self.naive.year }
69    pub fn month(&self) -> Month { self.naive.month }
70    pub fn day(&self) -> u8 { self.naive.day }
71    pub fn hour(&self) -> u8 { self.naive.hour }
72    pub fn minute(&self) -> u8 { self.naive.minute }
73    pub fn second(&self) -> u8 { self.naive.second }
74
75    pub fn day_of_the_week(&self) -> DayOfTheWeek { self.naive.day_of_the_week() }
76
77    pub fn from_timestamp(t: UnixTimestamp, time_zone: Tz) -> Self {
78        DateTime {
79            naive: time_zone.from_timestamp(t),
80            time_zone: time_zone,
81        }
82    }
83
84    pub fn to_timestamp(&self) -> Result<UnixTimestamp, LocalTimeConversionError> {
85        self.time_zone.to_timestamp(&self.naive)
86    }
87
88    pub fn convert_time_zone<NewTz: TimeZone>(&self, new_time_zone: NewTz)
89                                              -> Result<DateTime<NewTz>, LocalTimeConversionError> {
90        Ok(DateTime::from_timestamp(try!(self.to_timestamp()), new_time_zone))
91    }
92}
93
94impl<Tz: UnambiguousTimeZone> DateTime<Tz> {
95    pub fn to_unambiguous_timestamp(&self) -> UnixTimestamp {
96        self.time_zone.to_unambiguous_timestamp(&self.naive)
97    }
98
99    pub fn convert_unambiguous_time_zone<NewTz: TimeZone>(&self, new_time_zone: NewTz) -> DateTime<NewTz> {
100        DateTime::from_timestamp(self.to_unambiguous_timestamp(), new_time_zone)
101    }
102}
103
104
105impl NaiveDateTime {
106    pub fn new(year: i32, month: Month, day: u8, hour: u8, minute: u8, second: u8) -> Self {
107        NaiveDateTime {
108            year: year,
109            month: month,
110            day: day,
111            hour: hour,
112            minute: minute,
113            second: second,
114        }
115    }
116
117    pub fn day_of_the_week(&self) -> DayOfTheWeek {
118        const JANUARY_1ST_1970: DayOfTheWeek = DayOfTheWeek::Thursday;
119        JANUARY_1ST_1970.add_days(days_since_unix(self))
120    }
121}
122
123impl<Tz: Default + TimeZone> From<UnixTimestamp> for DateTime<Tz> {
124    fn from(u: UnixTimestamp) -> Self {
125        let tz = Tz::default();
126        DateTime {
127            naive: tz.from_timestamp(u),
128            time_zone: tz,
129        }
130    }
131}
132
133impl<Tz: UnambiguousTimeZone> From<DateTime<Tz>> for UnixTimestamp {
134    fn from(datetime: DateTime<Tz>) -> Self {
135        datetime.time_zone.to_unambiguous_timestamp(&datetime.naive)
136    }
137}
138
139
140#[derive(Debug, Eq, PartialEq, Copy, Clone)]
141pub enum YearKind {
142    Common,
143    Leap,
144}
145
146impl From<i32> for YearKind {
147    fn from(year: i32) -> Self {
148        fn is_multiple(n: i32, divisor: i32) -> bool {
149            n % divisor == 0
150        }
151
152        if is_multiple(year, 4) && (!is_multiple(year, 100) || is_multiple(year, 400)) {
153            YearKind::Leap
154        } else {
155            YearKind::Common
156        }
157    }
158}
159
160macro_rules! declare_month {
161    ([ $((
162        $name: ident,
163        $number: expr,
164        $length_in_common_years: expr,
165        $length_in_leap_years: expr,
166        $first_day_in_common_years: expr,
167        $last_day_in_common_years: expr,
168        $first_day_in_leap_years: expr,
169        $last_day_in_leap_years: expr
170    )),+ ]) => {
171        #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
172        pub enum Month {
173            $(
174                $name = $number,
175            )+
176        }
177
178        impl Month {
179            /// Return the month from its number, between 1 and 12.
180            pub fn from_number(n: u8) -> Option<Self> {
181                match n {
182                    $(
183                        $number => Some(Month::$name),
184                    )+
185                    _ => None
186                }
187            }
188
189            /// Return the number of this month, between 1 and 12.
190            pub fn to_number(self) -> u8 {
191                match self {
192                    $(
193                        Month::$name => $number,
194                    )+
195                }
196            }
197
198            pub fn length(self, year_kind: YearKind) -> u8 {
199                match year_kind {
200                    YearKind::Common => match self {
201                        $(
202                            Month::$name => $length_in_common_years,
203                        )+
204                    },
205                    YearKind::Leap => match self {
206                        $(
207                            Month::$name => $length_in_leap_years,
208                        )+
209                    },
210                }
211            }
212
213            /// Days between Jan 1st and the first day of this month.
214            fn days_since_january_1st(self, year_kind: YearKind) -> i32 {
215                match year_kind {
216                    YearKind::Common => match self {
217                        $(
218                            Month::$name => $first_day_in_common_years,
219                        )+
220                    },
221                    YearKind::Leap => match self {
222                        $(
223                            Month::$name => $first_day_in_leap_years,
224                        )+
225                    },
226                }
227            }
228
229            /// In: 0 for Jan 1st, 365 or 366 for Dec 31.
230            /// Out: Month and day of the month (1 for the first day).
231            fn from_day_of_the_year(day: i32, year_kind: YearKind) -> (Month, u8) {
232                match year_kind {
233                    YearKind::Common => match day {
234                        $(
235                            $first_day_in_common_years ... $last_day_in_common_years => {
236                                (Month::$name, (day - $first_day_in_common_years + 1) as u8)
237                            }
238                        )+
239                        _ => panic!("Day #{} of the year is out of range", day)
240                    },
241                    YearKind::Leap => match day {
242                        $(
243                            $first_day_in_leap_years ... $last_day_in_leap_years => {
244                                (Month::$name, (day - $first_day_in_leap_years + 1) as u8)
245                            }
246                        )+
247                        _ => panic!("Day #{} of the year is out of range", day)
248                    },
249                }
250            }
251        }
252    }
253}
254
255macro_rules! declare_day_of_the_week {
256    ([ $((
257        $name: ident,
258        $number: expr
259    )),+ ]) => {
260        #[derive(Debug, Eq, PartialEq, Copy, Clone)]
261        pub enum DayOfTheWeek {
262            $(
263                $name = $number,
264            )+
265        }
266
267        impl DayOfTheWeek {
268            /// Return the day of the week from its number, where Monday to Sunday are 1 to 7
269            /// in accordance with ISO 8601.
270            pub fn from_iso_number(n: u8) -> Option<Self> {
271                match n {
272                    $(
273                        $number => Some(DayOfTheWeek::$name),
274                    )+
275                    _ => None
276                }
277            }
278
279            /// Return the number of this day of the week, where Monday to Sunday are 1 to 7
280            /// in accordance with ISO 8601.
281            pub fn to_iso_number(self) -> u8 {
282                match self {
283                    $(
284                        DayOfTheWeek::$name => $number,
285                    )+
286                }
287            }
288
289            // What day of the week is it this many days after this day of the week?
290            fn add_days(self, days: i32) -> Self {
291                let number = i32::from(self.to_iso_number()) + days;
292                let number = positive_rem((number - 1), 7) + 1;  // Normalize to 1...7
293                DayOfTheWeek::from_iso_number(number as u8).unwrap()
294            }
295        }
296    }
297}
298
299include!(concat!(env!("OUT_DIR"), "/generated_data.rs"));
300
301with_month_data!(declare_month);
302with_day_of_the_week_data!(declare_day_of_the_week);