hifitime 0.1.3

Precise date and time handling in Rust built on top of std::time::Duration with leap second support
Documentation
extern crate regex;

pub use super::TimeSystem;
use super::instant::{Duration, Era, Instant};
use super::{Errors, SECONDS_PER_DAY};
use std::fmt;
use std::ops::{Add, Neg, Sub};
use std::str::FromStr;

// There is no way to define a constant map in Rust (yet), so we're combining several structures
// to store when the leap seconds should be added. An updated list of leap seconds can be found
// here: https://www.ietf.org/timezones/data/leap-seconds.list .
const JANUARY_YEARS: [i32; 17] = [
    1972, 1973, 1974, 1975, 1976, 1977, 1978, 1979, 1980, 1988, 1990, 1991, 1996, 1999, 2006, 2009,
    2017,
];

const JULY_YEARS: [i32; 11] = [
    1972, 1981, 1982, 1983, 1985, 1992, 1993, 1994, 1997, 2012, 2015
];

const USUAL_DAYS_PER_MONTH: [u8; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
const USUAL_DAYS_PER_YEAR: f64 = 365.0;

/// `Offset` is an alias of Instant. It contains the same kind of information, but is used in the
/// context of defining an offset with respect to Utc.
pub type Offset = Instant;

/// Negates an Offset
///
/// # Examples
/// ```
/// use hifitime::datetime::Offset;
/// use hifitime::instant::Era;
///
/// assert_eq!(
///     -Offset::new(3600, 159, Era::Past),
///     Offset::new(3600, 159, Era::Present),
///     "Incorrect neg for Past offset"
/// );
///
/// assert_eq!(
///     -Offset::new(3600, 159, Era::Present),
///     Offset::new(3600, 159, Era::Past),
///     "Incorrect neg for Present offset"
/// );
/// ```
impl Neg for Offset {
    type Output = Offset;

    fn neg(self) -> Offset {
        let era = match self.era() {
            Era::Past => Era::Present,
            Era::Present => Era::Past,
        };
        Offset::new(self.secs(), self.nanos(), era)
    }
}

impl fmt::Display for Offset {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let sign = match self.era() {
            Era::Present => "+",
            Era::Past => "-",
        };
        let (hours, hours_fraction) = quorem(self.secs() as f64, 60.0 * 60.0);
        // Get the minutes and seconds by the exact number of seconds in a minute
        let (mins, _) = quorem(hours_fraction, 60.0);
        write!(f, "{:}{:02}:{:02}", sign, hours, mins)
    }
}

/// `FixedOffset` implements a time fixed offset of a certain number of hours with regard to UTC.
pub struct FixedOffset {}

impl FixedOffset {
    /// `east_with_hours` returns an eastward offset (i.e. "before" the UTC time)
    ///
    /// # Example
    /// ```
    /// use hifitime::datetime::FixedOffset;
    /// use hifitime::instant::Era;
    ///
    /// let whiskey_tz = FixedOffset::east_with_hours(10);
    /// assert_eq!(
    ///     whiskey_tz.secs(),
    ///     36000,
    ///     "Incorrect number of hours computed"
    /// );
    /// assert_eq!(
    ///     whiskey_tz.era(),
    ///     Era::Past,
    ///     "Incorrect era used"
    /// );
    /// ```
    pub fn east_with_hours(hours: u64) -> Offset {
        Offset::new(hours * 3600, 0, Era::Past)
    }

    /// `west_with_hours` returns an eastward offset (i.e. "before" the UTC time)
    ///
    /// # Example
    /// ```
    /// use hifitime::datetime::FixedOffset;
    /// use hifitime::instant::Era;
    ///
    /// let kilo_tz = FixedOffset::west_with_hours(10);
    /// assert_eq!(
    ///     kilo_tz.secs(),
    ///     36000,
    ///     "Incorrect number of hours computed"
    /// );
    /// assert_eq!(
    ///     kilo_tz.era(),
    ///     Era::Present,
    ///     "Incorrect era used"
    /// );
    /// ```
    pub fn west_with_hours(hours: u64) -> Offset {
        Offset::new(hours * 3600, 0, Era::Present)
    }
}

/// Datetime supports date time has used by most humans. All time zones are defined with
/// respect to UTC. Moreover, `Datetime` inherently supports the past leap seconds, as reported by the
/// IETF and NIST at [here](https://www.ietf.org/timezones/data/leap-seconds.list). NOTE: leap seconds
/// cannot be predicted! This module will be updated as soon as possible after a new leap second
/// has been announced.
/// **WARNING**: The historical oddities with calendars are not yet supported.
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
pub struct Datetime {
    year: i32,
    month: u8,
    day: u8,
    hour: u8,
    minute: u8,
    second: u8,
    nanos: u32,
    offset: Offset,
}

impl Datetime {
    /// Creates a new UTC-offsetted datetime, with support for all the leap seconds with respect to TAI.
    /// *NOTE:* UTC leap seconds may be confusing because several dates have the **same** number
    /// of seconds since TAI epoch.
    /// **WARNING:** Does not support automatic carry and will return an error if so.
    /// **WARNING:** Although `PartialOrd` is implemented for Utc, the ambiguity of leap seconds
    /// as explained elsewhere in this documentation may lead to odd results (cf. examples below).
    ///
    /// # Examples
    /// ```
    /// use hifitime::datetime::{Datetime, TimeSystem};
    /// use hifitime::instant::{Duration, Era, Instant};
    /// use hifitime::julian::ModifiedJulian;
    ///
    /// let epoch = Datetime::new(1900, 01, 01, 0, 0, 0, 0).expect("epoch failed");
    /// assert_eq!(
    ///     epoch.into_instant(),
    ///     Instant::new(0, 0, Era::Present),
    ///     "Incorrect Epoch computed"
    /// );
    ///
    /// assert_eq!(
    ///     Datetime::new(1971, 12, 31, 23, 59, 59, 0)
    ///         .expect("January 1972 leap second failed")
    ///         .into_instant(),
    ///     Instant::new(2272060799, 0, Era::Present),
    ///     "Incorrect January 1972 pre-leap second number computed"
    /// );
    /// assert_eq!(
    ///     Datetime::new(1971, 12, 31, 23, 59, 59, 0)
    ///         .expect("January 1972 1 second before leap second failed")
    ///         .into_instant(),
    ///     Datetime::new(1971, 12, 31, 23, 59, 60, 0)
    ///         .expect("January 1972 1 second before leap second failed")
    ///         .into_instant(),
    ///     "Incorrect January 1972 leap second number computed"
    /// );
    ///
    /// // Example of odd behavior when comparing/ordering dates using Utc or `into_instant`
    /// // Utc order claims (correctly) that the 60th second is _after_ the 59th. But the instant
    /// // is actually different because the 60th second is where we've inserted the leap second.
    /// assert!(
    ///     Datetime::new(1971, 12, 31, 23, 59, 59, 0).expect(
    ///         "January 1972 1 second before leap second failed",
    ///     ) <
    ///         Datetime::new(1971, 12, 31, 23, 59, 60, 0).expect(
    ///             "January 1972 1 second before leap second failed",
    ///         ),
    ///     "60th second should have a different instant than 59th second"
    /// );
    /// assert!(
    ///     Datetime::new(1971, 12, 31, 23, 59, 59, 0)
    ///         .expect("January 1972 1 second before leap second failed")
    ///         .into_instant() ==
    ///         Datetime::new(1971, 12, 31, 23, 59, 60, 0)
    ///             .expect("January 1972 1 second before leap second failed")
    ///             .into_instant(),
    ///     "60th second should have a different instant than 59th second"
    /// );
    /// // Hence one second after the leap second, we get the following behavior (note the change
    /// // from equality to less when comparing via instant).
    /// assert!(
    ///     Datetime::new(1971, 12, 31, 23, 59, 60, 0).expect(
    ///         "January 1972 1 second before leap second failed",
    ///     ) <
    ///         Datetime::new(1972, 01, 01, 00, 00, 00, 0).expect(
    ///             "January 1972 1 second before leap second failed",
    ///         ),
    ///     "60th second should have a different instant than 59th second"
    /// );
    /// assert!(
    ///     Datetime::new(1971, 12, 31, 23, 59, 60, 0)
    ///         .expect("January 1972 1 second before leap second failed")
    ///         .into_instant() <
    ///         Datetime::new(1972, 01, 01, 00, 00, 00, 0)
    ///             .expect("January 1972 1 second before leap second failed")
    ///             .into_instant(),
    ///     "60th second should have a different instant than 59th second"
    /// );
    ///
    /// let santa = Datetime::new(2017, 12, 25, 01, 02, 14, 0).expect("Xmas failed");
    ///
    /// assert_eq!(
    ///     santa.into_instant() + Duration::new(3600, 0),
    ///     Datetime::new(2017, 12, 25, 02, 02, 14, 0)
    ///         .expect("Xmas failed")
    ///         .into_instant(),
    ///     "Could not add one hour to Christmas"
    /// );
    /// assert_eq!(format!("{}", santa), "2017-12-25T01:02:14+00:00");
    /// assert_eq!(
    ///     ModifiedJulian::from_instant(santa.into_instant()).days,
    ///     58112.043217592596
    /// );
    /// assert_eq!(
    ///     ModifiedJulian::from_instant(santa.into_instant()).julian_days(),
    ///     2458112.5432175924
    /// );
    /// ```
    pub fn new(
        year: i32,
        month: u8,
        day: u8,
        hour: u8,
        minute: u8,
        second: u8,
        nanos: u32,
    ) -> Result<Datetime, Errors> {
        Datetime::with_offset(
            year,
            month,
            day,
            hour,
            minute,
            second,
            nanos,
            FixedOffset::west_with_hours(0),
        )
    }

    /// Creates a new Datetime with the specified UTC time offset. Works like `Datetime::new` in
    /// every way but it sets the UTC time offset to the one provided.
    ///
    /// # Examples
    /// ```
    /// use hifitime::datetime::{Datetime, FixedOffset, TimeSystem};
    ///
    /// let santa_ktz = Datetime::with_offset(
    ///     2017,
    ///     12,
    ///     25,
    ///     00,
    ///     00,
    ///     00,
    ///     00,
    ///     FixedOffset::west_with_hours(10),
    /// ).expect("Santa failed");
    /// assert_eq!(format!("{}", santa_ktz), "2017-12-25T00:00:00+10:00");

    /// let santa_wtz = Datetime::with_offset(
    ///     2017,
    ///     12,
    ///     25,
    ///     00,
    ///     00,
    ///     00,
    ///     00,
    ///     FixedOffset::east_with_hours(10),
    /// ).expect("Santa failed");
    /// assert_eq!(format!("{}", santa_wtz), "2017-12-25T00:00:00-10:00");
    /// assert!(
    ///     santa_wtz < santa_ktz,
    ///     "PartialOrd with different timezones failed"
    /// );
    /// assert!(
    ///     santa_wtz.into_instant() < santa_ktz.into_instant(),
    ///     "PartialOrd with different timezones failed via Instant"
    /// );
    /// assert_eq!(
    ///     format!("{}", santa_wtz.to_utc()),
    ///     "2017-12-24T14:00:00+00:00"
    /// );
    /// ```
    pub fn with_offset(
        year: i32,
        month: u8,
        day: u8,
        hour: u8,
        minute: u8,
        second: u8,
        nanos: u32,
        offset: Offset,
    ) -> Result<Datetime, Errors> {
        let max_seconds = if (month == 12 || month == 6)
            && day == USUAL_DAYS_PER_MONTH[month as usize - 1]
            && hour == 23 && minute == 59
            && ((month == 6 && JULY_YEARS.contains(&year))
                || (month == 12 && JANUARY_YEARS.contains(&(year + 1))))
        {
            60
        } else {
            59
        };
        // General incorrect date times
        if month == 0 || month > 12 || day == 0 || day > 31 || hour > 24 || minute > 59
            || second > max_seconds || f64::from(nanos) > 1e9
        {
            return Err(Errors::Carry);
        }
        if day > USUAL_DAYS_PER_MONTH[month as usize - 1] && (month != 2 || !is_leap_year(year)) {
            // Not in February or not a leap year
            return Err(Errors::Carry);
        }
        Ok(Datetime {
            year: year,
            month: month,
            day: day,
            hour: hour,
            minute: minute,
            second: second,
            nanos: nanos,
            offset: offset,
        })
    }

    /// Returns the year of this Datetime date time.
    pub fn year(&self) -> &i32 {
        &self.year
    }
    /// Returns the month of this Datetime date time.
    pub fn month(&self) -> &u8 {
        &self.month
    }
    /// Returns the day of this Datetime date time.
    pub fn day(&self) -> &u8 {
        &self.day
    }
    /// Returns the hour of this Datetime date time.
    pub fn hour(&self) -> &u8 {
        &self.hour
    }
    /// Returns the minute of this Datetime date time.
    pub fn minute(&self) -> &u8 {
        &self.minute
    }
    /// Returns the second of this Datetime date time.
    pub fn second(&self) -> &u8 {
        &self.second
    }
    /// Returns the nanoseconds of this Datetime date time.
    pub fn nanos(&self) -> &u32 {
        &self.nanos
    }

    /// Returns the offset of this Datetime date time.
    pub fn offset(&self) -> &Offset {
        &self.offset
    }
    /// Creates a new UTC date at midnight (i.e. hours = 0, mins = 0, secs = 0, nanos = 0)
    ///
    /// # Examples
    /// ```
    /// use hifitime::datetime::{Datetime, TimeSystem};
    /// use hifitime::instant::{Era, Instant};
    ///
    /// let epoch = Datetime::at_midnight(1900, 01, 01).expect("epoch failed");
    /// assert_eq!(
    ///     epoch.into_instant(),
    ///     Instant::new(0, 0, Era::Present),
    ///     "Incorrect Epoch computed"
    /// );
    ///
    /// assert_eq!(
    ///     Datetime::at_midnight(1972, 01, 01)
    ///         .expect("Post January 1972 leap second failed")
    ///         .into_instant(),
    ///     Instant::new(2272060800, 0, Era::Present),
    ///     "Incorrect January 1972 post-leap second number computed at midnight"
    /// );
    /// ```

    pub fn at_midnight(year: i32, month: u8, day: u8) -> Result<Datetime, Errors> {
        Ok(Datetime::new(year, month, day, 00, 00, 00, 00)?)
    }

    /// Creates a new UTC date at noon (i.e. hours = 12, mins = 0, secs = 0, nanos = 0)
    ///
    /// # Examples
    /// ```
    /// use hifitime::datetime::{Datetime, TimeSystem};
    /// use hifitime::instant::{Era, Instant};
    ///
    /// let epoch = Datetime::at_noon(1900, 01, 01).expect("epoch failed");
    /// assert_eq!(
    ///     epoch.into_instant(),
    ///     Instant::new(43200, 0, Era::Present),
    ///     "Incorrect Epoch computed"
    /// );
    ///
    /// assert_eq!(
    ///     Datetime::at_noon(1972, 01, 01)
    ///         .expect("Post January 1972 leap second failed")
    ///         .into_instant(),
    ///     Instant::new(2272104000, 0, Era::Present),
    ///     "Incorrect January 1972 post-leap second number computed at noon"
    /// );
    /// ```
    pub fn at_noon(year: i32, month: u8, day: u8) -> Result<Datetime, Errors> {
        Ok(Datetime::new(year, month, day, 12, 00, 00, 00)?)
    }

    pub fn to_utc(self) -> Datetime {
        self.to_offset(FixedOffset::east_with_hours(0))
    }

    pub fn to_offset(self, offset: Offset) -> Datetime {
        // Start by canceling the initial offset and then apply the desired one
        match offset.era() {
            Era::Past => Datetime::from_instant(self.into_instant() - offset.duration()),
            Era::Present => Datetime::from_instant(self.into_instant() + offset.duration()),
        }
    }
}

impl TimeSystem for Datetime {
    /// `from_instant` converts an Instant to a Datetime with an offset of Utc (i.e zero).
    fn from_instant(instant: Instant) -> Datetime {
        let (mut year, mut year_fraction) = quorem(instant.secs() as f64, 365.0 * SECONDS_PER_DAY);
        year = match instant.era() {
            Era::Past => 1900 - year,
            Era::Present => 1900 + year,
        };
        // Base calculation was on 365 days, so we need to remove one day in seconds per leap year
        // between 1900 and `year`
        for year in 1900..year {
            if is_leap_year(year) {
                year_fraction -= SECONDS_PER_DAY;
            }
        }

        // Get the month from the exact number of seconds between the start of the year and now
        let mut seconds_til_this_month = 0.0;
        let mut month = 1;
        if year_fraction < 0.0 {
            month = 12;
            year -= 1;
        } else {
            loop {
                seconds_til_this_month +=
                    SECONDS_PER_DAY * f64::from(USUAL_DAYS_PER_MONTH[(month - 1) as usize]);
                if is_leap_year(year) && month == 2 {
                    seconds_til_this_month += SECONDS_PER_DAY;
                }
                if seconds_til_this_month > year_fraction {
                    break;
                }
                month += 1;
            }
        }
        let mut days_this_month = USUAL_DAYS_PER_MONTH[(month - 1) as usize];
        if month == 2 && is_leap_year(year) {
            days_this_month += 1;
        }
        // Get the month fraction by the number of seconds in this month from the number of
        // seconds since the start of this month.
        let (_, month_fraction) = quorem(
            year_fraction - seconds_til_this_month,
            f64::from(days_this_month) * SECONDS_PER_DAY,
        );
        // Get the day by the exact number of seconds in a day
        let (mut day, day_fraction) = quorem(month_fraction, SECONDS_PER_DAY);
        if day < 0 {
            // Overflow backwards (this happens for end of year calculations)
            month -= 1;
            if month == 0 {
                month = 12;
                year -= 1;
            }
            day = i32::from(USUAL_DAYS_PER_MONTH[(month - 1) as usize]);
        }
        day += 1; // Otherwise the day count starts at 0
                  // Get the hours by the exact number of seconds in an hour
        let (hours, hours_fraction) = quorem(day_fraction, 60.0 * 60.0);
        // Get the minutes and seconds by the exact number of seconds in a minute
        let (mins, secs) = quorem(hours_fraction, 60.0);
        Datetime::new(
            year,
            month as u8,
            day as u8,
            hours as u8,
            mins as u8,
            secs as u8,
            instant.nanos(),
        ).expect("date computed from instant is invalid (past)")
    }

    /// `into_instant` returns an Instant from the Datetime while correcting for the offset.
    fn into_instant(self) -> Instant {
        let era = if self.year >= 1900 {
            Era::Present
        } else {
            Era::Past
        };

        let mut seconds_wrt_1900: f64 =
            f64::from((self.year - 1900).abs()) * SECONDS_PER_DAY * USUAL_DAYS_PER_YEAR;

        // Now add the seconds for all the years prior to the current year
        for year in 1900..self.year {
            if is_leap_year(year) {
                seconds_wrt_1900 += SECONDS_PER_DAY;
            }
        }
        // Add the seconds for the months prior to the current month
        for month in 0..self.month - 1 {
            seconds_wrt_1900 += SECONDS_PER_DAY * f64::from(USUAL_DAYS_PER_MONTH[(month) as usize]);
        }
        if is_leap_year(self.year) && self.month > 2 {
            // NOTE: If on 29th of February, then the day is not finished yet, and therefore
            // the extra seconds are added below as per a normal day.
            seconds_wrt_1900 += SECONDS_PER_DAY;
        }
        seconds_wrt_1900 += f64::from(self.day - 1) * SECONDS_PER_DAY
            + f64::from(self.hour) * 3600.0
            + f64::from(self.minute) * 60.0 + f64::from(self.second);
        if self.second == 60 {
            // Herein lies the whole ambiguity of leap seconds. Two different UTC dates exist at the
            // same number of second afters J1900.0.
            seconds_wrt_1900 -= 1.0;
        }
        match self.offset.era() {
            Era::Past => {
                Instant::new(seconds_wrt_1900 as u64, self.nanos as u32, era)
                    - self.offset.duration()
            }
            Era::Present => {
                Instant::new(seconds_wrt_1900 as u64, self.nanos as u32, era)
                    + self.offset.duration()
            }
        }
    }
}

impl fmt::Display for Datetime {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}{:}",
            self.year, self.month, self.day, self.hour, self.minute, self.second, self.offset
        )
    }
}

impl fmt::LowerHex for Datetime {
    /// Formats as human readable with date and time separated by a space and no offset.
    ///
    /// # Example
    /// ```
    /// use std::str::FromStr;
    /// use hifitime::datetime::{Datetime, FixedOffset};
    ///
    /// let dt =
    ///     Datetime::with_offset(2017, 1, 14, 0, 31, 55, 0, FixedOffset::east_with_hours(5)).unwrap();
    /// assert_eq!(format!("{:x}", dt), "2017-01-14 00:31:55");
    /// ```
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "{:04}-{:02}-{:02} {:02}:{:02}:{:02}",
            self.year, self.month, self.day, self.hour, self.minute, self.second
        )
    }
}

impl fmt::UpperHex for Datetime {
    /// Formats as ISO8601 but _without_ the offset.
    ///
    /// # Example
    /// ```
    /// use std::str::FromStr;
    /// use hifitime::datetime::{Datetime, FixedOffset};
    ///
    /// let dt =
    ///     Datetime::with_offset(2017, 1, 14, 0, 31, 55, 0, FixedOffset::east_with_hours(5)).unwrap();
    /// assert_eq!(format!("{:X}", dt), "2017-01-14T00:31:55");
    /// ```
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
            self.year, self.month, self.day, self.hour, self.minute, self.second
        )
    }
}

impl Add<Duration> for Datetime {
    type Output = Datetime;

    /// Adds a given `std::time::Duration` to a `Datetime`.
    ///
    /// # Examples
    /// ```
    /// use hifitime::datetime::Datetime;
    /// use std::time::Duration;
    /// let santa = Datetime::at_midnight(2017, 12, 25).unwrap();
    /// let santa_1h = Datetime::at_midnight(2017, 12, 25).unwrap() + Duration::new(3600, 0);
    /// assert_eq!(santa.hour() + &1, *santa_1h.hour());
    /// ```
    fn add(self, delta: Duration) -> Datetime {
        Datetime::from_instant(self.into_instant() + delta)
    }
}

impl Sub<Duration> for Datetime {
    type Output = Datetime;

    /// Adds a given `std::time::Duration` to a `Datetime`.
    ///
    /// # Examples
    /// ```
    /// use hifitime::datetime::Datetime;
    /// use std::time::Duration;
    /// let santa = Datetime::at_midnight(2017, 12, 25).unwrap();
    /// let santa_1h = Datetime::at_midnight(2017, 12, 25).unwrap() - Duration::new(3600, 0);
    /// assert_eq!(santa.day() - &1, *santa_1h.day()); // Day underflow
    /// assert_eq!(santa_1h.hour(), &23);
    /// ```
    fn sub(self, delta: Duration) -> Datetime {
        Datetime::from_instant(self.into_instant() - delta)
    }
}

impl FromStr for Datetime {
    type Err = Errors;

    /// Converts an ISO8601 Datetime representation with offset to a `Datetime` object with correct offset.
    /// The `T` which separates the date from the time can be replaced with a single whitespace character (`\W`).
    /// The offset is also optional, cf. the examples below.
    ///
    /// # Examples
    /// ```
    /// use std::str::FromStr;
    /// use hifitime::datetime::{Datetime, Offset};
    /// use hifitime::instant::Era;
    /// let offset = Offset::new(3600 * 2 + 60 * 15, 0, Era::Past);
    /// let dt = Datetime::with_offset(2017, 1, 14, 0, 31, 55, 0, offset).unwrap();
    /// assert_eq!(
    ///     dt,
    ///     Datetime::from_str("2017-01-14T00:31:55-02:15").unwrap()
    /// );
    /// assert_eq!(
    ///     dt,
    ///     Datetime::from_str("2017-01-14 00:31:55-02:15").unwrap()
    /// );
    /// let dt = Datetime::new(2017, 1, 14, 0, 31, 55, 0).unwrap();
    /// assert_eq!(
    ///     dt,
    ///     Datetime::from_str("2017-01-14T00:31:55").unwrap()
    /// );
    /// assert_eq!(
    ///     dt,
    ///     Datetime::from_str("2017-01-14 00:31:55").unwrap()
    /// );
    /// ```
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        use self::regex::Regex;
        lazy_static! {
            static ref RE: Regex = Regex::new(r"^(\d{4})-(\d{2})-(\d{2})(?:T|\W)(\d{2}):(\d{2}):(\d{2})(([\+|-]\d{2}):(\d{2}))?$").unwrap();
        }
        match RE.captures(s) {
            Some(cap) => {
                let offset = match cap.get(7) {
                    Some(_) => {
                        let offset_hours = cap.get(8).unwrap().as_str().to_owned().parse::<i32>()?;
                        let offset_mins = cap.get(9).unwrap().as_str().to_owned().parse::<i32>()?;
                        // Check if negative, and if so, multiply by negative seconds to get a positive number
                        if offset_hours < 0 {
                            Offset::new(
                                (-3600 * offset_hours + 60 * offset_mins) as u64,
                                0,
                                Era::Past,
                            )
                        } else {
                            Offset::new(
                                (3600 * offset_hours + 60 * offset_mins) as u64,
                                0,
                                Era::Present,
                            )
                        }
                    }
                    None => Offset::new(0, 0, Era::Present),
                };
                Datetime::with_offset(
                    cap[1].to_owned().parse::<i32>()?,
                    cap[2].to_owned().parse::<u8>()?,
                    cap[3].to_owned().parse::<u8>()?,
                    cap[4].to_owned().parse::<u8>()?,
                    cap[5].to_owned().parse::<u8>()?,
                    cap[6].to_owned().parse::<u8>()?,
                    0,
                    offset,
                )
            }
            None => Err(Errors::ParseError(
                "Input not in ISO8601 format with offset (e.g. 2018-01-27T00:41:55+03:00)"
                    .to_owned(),
            )),
        }
    }
}

/// `is_leap_year` returns whether the provided year is a leap year or not.
/// Tests for this function are part of the Datetime tests.
fn is_leap_year(year: i32) -> bool {
    (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
}

/// `quorem` returns a tuple of the quotient and the remainder a numerator and a denominator.
fn quorem(numerator: f64, denominator: f64) -> (i32, f64) {
    if denominator == 0.0 {
        panic!("cannot divide by zero");
    }
    let quotient = (numerator / denominator).floor() as i32;
    let remainder = numerator % denominator;
    if remainder >= 0.0 {
        (quotient, remainder)
    } else {
        (quotient - 1, remainder + denominator)
    }
}

#[test]
fn quorem_nominal_test() {
    assert_eq!(quorem(24.0, 6.0), (4, 0.0));
    assert_eq!(quorem(25.0, 6.0), (4, 1.0));
    assert_eq!(quorem(6.0, 6.0), (1, 0.0));
    assert_eq!(quorem(5.0, 6.0), (0, 5.0));
    assert_eq!(quorem(3540.0, 3600.0), (0, 3540.0));
    assert_eq!(quorem(3540.0, 60.0), (59, 0.0));
    assert_eq!(quorem(24.0, -6.0), (-4, 0.0));
    assert_eq!(quorem(-24.0, 6.0), (-4, 0.0));
    assert_eq!(quorem(-24.0, -6.0), (4, 0.0));
}

#[test]
#[should_panic]
fn quorem_nil_den_test() {
    assert_eq!(quorem(24.0, 0.0), (4, 0.0));
}