#![allow(deprecated)]
use crate::timezone;
use anyhow::{anyhow, Result};
use chrono::prelude::*;
use lazy_static::lazy_static;
use regex::Regex;

/// Parse struct has methods implemented parsers for accepted formats.
pub struct Parse<'z, Tz2> {
    tz: &'z Tz2,
    default_time: Option<NaiveTime>,
}

impl<'z, Tz2> Parse<'z, Tz2>
where
    Tz2: TimeZone,
{
    /// Create a new instrance of [`Parse`] with a custom parsing timezone that handles the
    /// datetime string without time offset.
    pub fn new(tz: &'z Tz2, default_time: Option<NaiveTime>) -> Self {
        Self { tz, default_time }
    }

    /// This method tries to parse the input datetime string with a list of accepted formats. See
    /// more exmaples from [`Parse`], [`crate::parse()`] and [`crate::parse_with_timezone()`].
    pub fn parse(&self, input: &str) -> Result<DateTime<Utc>> {
        self.unix_timestamp(input)
            .or_else(|| self.rfc2822(input))
            .or_else(|| self.ymd_family(input))
            .or_else(|| self.hms_family(input))
            .or_else(|| self.month_ymd(input))
            .or_else(|| self.month_mdy_family(input))
            .or_else(|| self.month_dmy_family(input))
            .or_else(|| self.slash_mdy_family(input))
            .or_else(|| self.slash_ymd_family(input))
            .or_else(|| self.dot_mdy_or_ymd(input))
            .or_else(|| self.mysql_log_timestamp(input))
            .or_else(|| self.chinese_ymd_family(input))
            .unwrap_or_else(|| Err(anyhow!("{} did not match any formats.", input)))
    }

    fn ymd_family(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        lazy_static! {
            static ref RE: Regex = Regex::new(r"^[0-9]{4}-[0-9]{2}").unwrap();
        }
        if !RE.is_match(input) {
            return None;
        }
        self.rfc3339(input)
            .or_else(|| self.postgres_timestamp(input))
            .or_else(|| self.ymd_hms(input))
            .or_else(|| self.ymd_hms_z(input))
            .or_else(|| self.ymd(input))
            .or_else(|| self.ymd_z(input))
    }

    fn hms_family(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        lazy_static! {
            static ref RE: Regex = Regex::new(r"^[0-9]{1,2}:[0-9]{2}").unwrap();
        }
        if !RE.is_match(input) {
            return None;
        }
        self.hms(input).or_else(|| self.hms_z(input))
    }

    fn month_mdy_family(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        lazy_static! {
            static ref RE: Regex = Regex::new(r"^[a-zA-Z]{3,9}\.?\s+[0-9]{1,2}").unwrap();
        }
        if !RE.is_match(input) {
            return None;
        }
        self.month_md_hms(input)
            .or_else(|| self.month_mdy_hms(input))
            .or_else(|| self.month_mdy_hms_z(input))
            .or_else(|| self.month_mdy(input))
    }

    fn month_dmy_family(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        lazy_static! {
            static ref RE: Regex = Regex::new(r"^[0-9]{1,2}\s+[a-zA-Z]{3,9}").unwrap();
        }
        if !RE.is_match(input) {
            return None;
        }
        self.month_dmy_hms(input).or_else(|| self.month_dmy(input))
    }

    fn slash_mdy_family(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        lazy_static! {
            static ref RE: Regex = Regex::new(r"^[0-9]{1,2}/[0-9]{1,2}").unwrap();
        }
        if !RE.is_match(input) {
            return None;
        }
        self.slash_mdy_hms(input).or_else(|| self.slash_mdy(input))
    }

    fn slash_ymd_family(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        lazy_static! {
            static ref RE: Regex = Regex::new(r"^[0-9]{4}/[0-9]{1,2}").unwrap();
        }
        if !RE.is_match(input) {
            return None;
        }
        self.slash_ymd_hms(input).or_else(|| self.slash_ymd(input))
    }

    fn chinese_ymd_family(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        lazy_static! {
            static ref RE: Regex = Regex::new(r"^[0-9]{4}年[0-9]{2}月").unwrap();
        }
        if !RE.is_match(input) {
            return None;
        }
        self.chinese_ymd_hms(input)
            .or_else(|| self.chinese_ymd(input))
    }

    // unix timestamp
    // - 1511648546
    // - 1620021848429
    // - 1620024872717915000
    fn unix_timestamp(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        lazy_static! {
            static ref RE: Regex = Regex::new(r"^[0-9]{10,19}$").unwrap();
        }
        if !RE.is_match(input) {
            return None;
        }

        input
            .parse::<i64>()
            .ok()
            .and_then(|timestamp| {
                match input.len() {
                    10 => Some(Utc.timestamp(timestamp, 0)),
                    13 => Some(Utc.timestamp_millis(timestamp)),
                    19 => Some(Utc.timestamp_nanos(timestamp)),
                    _ => None,
                }
                .map(|datetime| datetime.with_timezone(&Utc))
            })
            .map(Ok)
    }

    // rfc3339
    // - 2021-05-01T01:17:02.604456Z
    // - 2017-11-25T22:34:50Z
    fn rfc3339(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        DateTime::parse_from_rfc3339(input)
            .ok()
            .map(|parsed| parsed.with_timezone(&Utc))
            .map(Ok)
    }

    // rfc2822
    // - Wed, 02 Jun 2021 06:31:39 GMT
    fn rfc2822(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        DateTime::parse_from_rfc2822(input)
            .ok()
            .map(|parsed| parsed.with_timezone(&Utc))
            .map(Ok)
    }

    // postgres timestamp yyyy-mm-dd hh:mm:ss z
    // - 2019-11-29 08:08-08
    // - 2019-11-29 08:08:05-08
    // - 2021-05-02 23:31:36.0741-07
    // - 2021-05-02 23:31:39.12689-07
    // - 2019-11-29 08:15:47.624504-08
    // - 2017-07-19 03:21:51+00:00
    fn postgres_timestamp(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        lazy_static! {
            static ref RE: Regex = Regex::new(
                r"^[0-9]{4}-[0-9]{2}-[0-9]{2}\s+[0-9]{2}:[0-9]{2}(:[0-9]{2})?(\.[0-9]{1,9})?[+-:0-9]{3,6}$",
            )
            .unwrap();
        }
        if !RE.is_match(input) {
            return None;
        }

        DateTime::parse_from_str(input, "%Y-%m-%d %H:%M:%S%#z")
            .or_else(|_| DateTime::parse_from_str(input, "%Y-%m-%d %H:%M:%S%.f%#z"))
            .or_else(|_| DateTime::parse_from_str(input, "%Y-%m-%d %H:%M%#z"))
            .ok()
            .map(|parsed| parsed.with_timezone(&Utc))
            .map(Ok)
    }

    // yyyy-mm-dd hh:mm:ss
    // - 2014-04-26 05:24:37 PM
    // - 2021-04-30 21:14
    // - 2021-04-30 21:14:10
    // - 2021-04-30 21:14:10.052282
    // - 2014-04-26 17:24:37.123
    // - 2014-04-26 17:24:37.3186369
    // - 2012-08-03 18:31:59.257000000
    fn ymd_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        lazy_static! {
            static ref RE: Regex = Regex::new(
                r"^[0-9]{4}-[0-9]{2}-[0-9]{2}\s+[0-9]{2}:[0-9]{2}(:[0-9]{2})?(\.[0-9]{1,9})?\s*(am|pm|AM|PM)?$",
            )
            .unwrap();
        }
        if !RE.is_match(input) {
            return None;
        }

        self.tz
            .datetime_from_str(input, "%Y-%m-%d %H:%M:%S")
            .or_else(|_| self.tz.datetime_from_str(input, "%Y-%m-%d %H:%M"))
            .or_else(|_| self.tz.datetime_from_str(input, "%Y-%m-%d %H:%M:%S%.f"))
            .or_else(|_| self.tz.datetime_from_str(input, "%Y-%m-%d %I:%M:%S %P"))
            .or_else(|_| self.tz.datetime_from_str(input, "%Y-%m-%d %I:%M %P"))
            .ok()
            .map(|parsed| parsed.with_timezone(&Utc))
            .map(Ok)
    }

    // yyyy-mm-dd hh:mm:ss z
    // - 2017-11-25 13:31:15 PST
    // - 2017-11-25 13:31 PST
    // - 2014-12-16 06:20:00 UTC
    // - 2014-12-16 06:20:00 GMT
    // - 2014-04-26 13:13:43 +0800
    // - 2014-04-26 13:13:44 +09:00
    // - 2012-08-03 18:31:59.257000000 +0000
    // - 2015-09-30 18:48:56.35272715 UTC
    fn ymd_hms_z(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        lazy_static! {
            static ref RE: Regex = Regex::new(
                r"^[0-9]{4}-[0-9]{2}-[0-9]{2}\s+[0-9]{2}:[0-9]{2}(:[0-9]{2})?(\.[0-9]{1,9})?(?P<tz>\s*[+-:a-zA-Z0-9]{3,6})$",
            ).unwrap();
        }

        if !RE.is_match(input) {
            return None;
        }
        if let Some(caps) = RE.captures(input) {
            if let Some(matched_tz) = caps.name("tz") {
                let parse_from_str = NaiveDateTime::parse_from_str;
                return match timezone::parse(matched_tz.as_str().trim()) {
                    Ok(offset) => parse_from_str(input, "%Y-%m-%d %H:%M:%S %Z")
                        .or_else(|_| parse_from_str(input, "%Y-%m-%d %H:%M %Z"))
                        .or_else(|_| parse_from_str(input, "%Y-%m-%d %H:%M:%S%.f %Z"))
                        .ok()
                        .and_then(|parsed| offset.from_local_datetime(&parsed).single())
                        .map(|datetime| datetime.with_timezone(&Utc))
                        .map(Ok),
                    Err(err) => Some(Err(err)),
                };
            }
        }
        None
    }

    // yyyy-mm-dd
    // - 2021-02-21
    fn ymd(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        lazy_static! {
            static ref RE: Regex = Regex::new(r"^[0-9]{4}-[0-9]{2}-[0-9]{2}$").unwrap();
        }

        if !RE.is_match(input) {
            return None;
        }

        // set time to use
        let time = match self.default_time {
            Some(v) => v,
            None => Utc::now().with_timezone(self.tz).time(),
        };

        NaiveDate::parse_from_str(input, "%Y-%m-%d")
            .ok()
            .map(|parsed| parsed.and_time(time))
            .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
            .map(|at_tz| at_tz.with_timezone(&Utc))
            .map(Ok)
    }

    // yyyy-mm-dd z
    // - 2021-02-21 PST
    // - 2021-02-21 UTC
    // - 2020-07-20+08:00 (yyyy-mm-dd-07:00)
    fn ymd_z(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        lazy_static! {
            static ref RE: Regex =
                Regex::new(r"^[0-9]{4}-[0-9]{2}-[0-9]{2}(?P<tz>\s*[+-:a-zA-Z0-9]{3,6})$").unwrap();
        }
        if !RE.is_match(input) {
            return None;
        }

        if let Some(caps) = RE.captures(input) {
            if let Some(matched_tz) = caps.name("tz") {
                return match timezone::parse(matched_tz.as_str().trim()) {
                    Ok(offset) => {
                        // set time to use
                        let time = match self.default_time {
                            Some(v) => v,
                            None => Utc::now().with_timezone(&offset).time(),
                        };
                        NaiveDate::parse_from_str(input, "%Y-%m-%d %Z")
                            .ok()
                            .map(|parsed| parsed.and_time(time))
                            .and_then(|datetime| offset.from_local_datetime(&datetime).single())
                            .map(|at_tz| at_tz.with_timezone(&Utc))
                            .map(Ok)
                    }
                    Err(err) => Some(Err(err)),
                };
            }
        }
        None
    }

    // hh:mm:ss
    // - 01:06:06
    // - 4:00pm
    // - 6:00 AM
    fn hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        lazy_static! {
            static ref RE: Regex =
                Regex::new(r"^[0-9]{1,2}:[0-9]{2}(:[0-9]{2})?\s*(am|pm|AM|PM)?$").unwrap();
        }
        if !RE.is_match(input) {
            return None;
        }

        let now = Utc::now().with_timezone(self.tz);
        NaiveTime::parse_from_str(input, "%H:%M:%S")
            .or_else(|_| NaiveTime::parse_from_str(input, "%H:%M"))
            .or_else(|_| NaiveTime::parse_from_str(input, "%I:%M:%S %P"))
            .or_else(|_| NaiveTime::parse_from_str(input, "%I:%M %P"))
            .ok()
            .and_then(|parsed| now.date().and_time(parsed))
            .map(|datetime| datetime.with_timezone(&Utc))
            .map(Ok)
    }

    // hh:mm:ss z
    // - 01:06:06 PST
    // - 4:00pm PST
    // - 6:00 AM PST
    // - 6:00pm UTC
    fn hms_z(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        lazy_static! {
            static ref RE: Regex = Regex::new(
                r"^[0-9]{1,2}:[0-9]{2}(:[0-9]{2})?\s*(am|pm|AM|PM)?(?P<tz>\s+[+-:a-zA-Z0-9]{3,6})$",
            )
            .unwrap();
        }
        if !RE.is_match(input) {
            return None;
        }

        if let Some(caps) = RE.captures(input) {
            if let Some(matched_tz) = caps.name("tz") {
                return match timezone::parse(matched_tz.as_str().trim()) {
                    Ok(offset) => {
                        let now = Utc::now().with_timezone(&offset);
                        NaiveTime::parse_from_str(input, "%H:%M:%S %Z")
                            .or_else(|_| NaiveTime::parse_from_str(input, "%H:%M %Z"))
                            .or_else(|_| NaiveTime::parse_from_str(input, "%I:%M:%S %P %Z"))
                            .or_else(|_| NaiveTime::parse_from_str(input, "%I:%M %P %Z"))
                            .ok()
                            .map(|parsed| now.date().naive_local().and_time(parsed))
                            .and_then(|datetime| offset.from_local_datetime(&datetime).single())
                            .map(|at_tz| at_tz.with_timezone(&Utc))
                            .map(Ok)
                    }
                    Err(err) => Some(Err(err)),
                };
            }
        }
        None
    }

    // yyyy-mon-dd
    // - 2021-Feb-21
    fn month_ymd(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        lazy_static! {
            static ref RE: Regex = Regex::new(r"^[0-9]{4}-[a-zA-Z]{3,9}-[0-9]{2}$").unwrap();
        }
        if !RE.is_match(input) {
            return None;
        }

        // set time to use
        let time = match self.default_time {
            Some(v) => v,
            None => Utc::now().with_timezone(self.tz).time(),
        };

        NaiveDate::parse_from_str(input, "%Y-%m-%d")
            .or_else(|_| NaiveDate::parse_from_str(input, "%Y-%b-%d"))
            .ok()
            .map(|parsed| parsed.and_time(time))
            .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
            .map(|at_tz| at_tz.with_timezone(&Utc))
            .map(Ok)
    }

    // Mon dd hh:mm:ss
    // - May 6 at 9:24 PM
    // - May 27 02:45:27
    fn month_md_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        lazy_static! {
            static ref RE: Regex = Regex::new(
                r"^[a-zA-Z]{3}\s+[0-9]{1,2}\s*(at)?\s+[0-9]{1,2}:[0-9]{2}(:[0-9]{2})?\s*(am|pm|AM|PM)?$",
            )
            .unwrap();
        }
        if !RE.is_match(input) {
            return None;
        }

        let now = Utc::now().with_timezone(self.tz);
        let with_year = format!("{} {}", now.year(), input);
        self.tz
            .datetime_from_str(&with_year, "%Y %b %d at %I:%M %P")
            .or_else(|_| self.tz.datetime_from_str(&with_year, "%Y %b %d %H:%M:%S"))
            .ok()
            .map(|parsed| parsed.with_timezone(&Utc))
            .map(Ok)
    }

    // Mon dd, yyyy, hh:mm:ss
    // - May 8, 2009 5:57:51 PM
    // - September 17, 2012 10:09am
    // - September 17, 2012, 10:10:09
    fn month_mdy_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        lazy_static! {
            static ref RE: Regex = Regex::new(
                r"^[a-zA-Z]{3,9}\.?\s+[0-9]{1,2},\s+[0-9]{2,4},?\s+[0-9]{1,2}:[0-9]{2}(:[0-9]{2})?\s*(am|pm|AM|PM)?$",
            ).unwrap();
        }
        if !RE.is_match(input) {
            return None;
        }

        let dt = input.replace(", ", " ").replace(". ", " ");
        self.tz
            .datetime_from_str(&dt, "%B %d %Y %H:%M:%S")
            .or_else(|_| self.tz.datetime_from_str(&dt, "%B %d %Y %H:%M"))
            .or_else(|_| self.tz.datetime_from_str(&dt, "%B %d %Y %I:%M:%S %P"))
            .or_else(|_| self.tz.datetime_from_str(&dt, "%B %d %Y %I:%M %P"))
            .ok()
            .map(|at_tz| at_tz.with_timezone(&Utc))
            .map(Ok)
    }

    // Mon dd, yyyy hh:mm:ss z
    // - May 02, 2021 15:51:31 UTC
    // - May 02, 2021 15:51 UTC
    // - May 26, 2021, 12:49 AM PDT
    // - September 17, 2012 at 10:09am PST
    fn month_mdy_hms_z(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        lazy_static! {
            static ref RE: Regex = Regex::new(
                r"^[a-zA-Z]{3,9}\s+[0-9]{1,2},?\s+[0-9]{4}\s*,?(at)?\s+[0-9]{2}:[0-9]{2}(:[0-9]{2})?\s*(am|pm|AM|PM)?(?P<tz>\s+[+-:a-zA-Z0-9]{3,6})$",
            ).unwrap();
        }
        if !RE.is_match(input) {
            return None;
        }

        if let Some(caps) = RE.captures(input) {
            if let Some(matched_tz) = caps.name("tz") {
                let parse_from_str = NaiveDateTime::parse_from_str;
                return match timezone::parse(matched_tz.as_str().trim()) {
                    Ok(offset) => {
                        let dt = input.replace(',', "").replace("at", "");
                        parse_from_str(&dt, "%B %d %Y %H:%M:%S %Z")
                            .or_else(|_| parse_from_str(&dt, "%B %d %Y %H:%M %Z"))
                            .or_else(|_| parse_from_str(&dt, "%B %d %Y %I:%M:%S %P %Z"))
                            .or_else(|_| parse_from_str(&dt, "%B %d %Y %I:%M %P %Z"))
                            .ok()
                            .and_then(|parsed| offset.from_local_datetime(&parsed).single())
                            .map(|datetime| datetime.with_timezone(&Utc))
                            .map(Ok)
                    }
                    Err(err) => Some(Err(err)),
                };
            }
        }
        None
    }

    // Mon dd, yyyy
    // - May 25, 2021
    // - oct 7, 1970
    // - oct 7, 70
    // - oct. 7, 1970
    // - oct. 7, 70
    // - October 7, 1970
    fn month_mdy(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        lazy_static! {
            static ref RE: Regex =
                Regex::new(r"^[a-zA-Z]{3,9}\.?\s+[0-9]{1,2},\s+[0-9]{2,4}$").unwrap();
        }
        if !RE.is_match(input) {
            return None;
        }

        // set time to use
        let time = match self.default_time {
            Some(v) => v,
            None => Utc::now().with_timezone(self.tz).time(),
        };

        let dt = input.replace(", ", " ").replace(". ", " ");
        NaiveDate::parse_from_str(&dt, "%B %d %y")
            .or_else(|_| NaiveDate::parse_from_str(&dt, "%B %d %Y"))
            .ok()
            .map(|parsed| parsed.and_time(time))
            .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
            .map(|at_tz| at_tz.with_timezone(&Utc))
            .map(Ok)
    }

    // dd Mon yyyy hh:mm:ss
    // - 12 Feb 2006, 19:17
    // - 12 Feb 2006 19:17
    // - 14 May 2019 19:11:40.164
    fn month_dmy_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        lazy_static! {
            static ref RE: Regex = Regex::new(
                r"^[0-9]{1,2}\s+[a-zA-Z]{3,9}\s+[0-9]{2,4},?\s+[0-9]{1,2}:[0-9]{2}(:[0-9]{2})?(\.[0-9]{1,9})?$",
            ).unwrap();
        }
        if !RE.is_match(input) {
            return None;
        }

        let dt = input.replace(", ", " ");
        self.tz
            .datetime_from_str(&dt, "%d %B %Y %H:%M:%S")
            .or_else(|_| self.tz.datetime_from_str(&dt, "%d %B %Y %H:%M"))
            .or_else(|_| self.tz.datetime_from_str(&dt, "%d %B %Y %H:%M:%S%.f"))
            .or_else(|_| self.tz.datetime_from_str(&dt, "%d %B %Y %I:%M:%S %P"))
            .or_else(|_| self.tz.datetime_from_str(&dt, "%d %B %Y %I:%M %P"))
            .ok()
            .map(|at_tz| at_tz.with_timezone(&Utc))
            .map(Ok)
    }

    // dd Mon yyyy
    // - 7 oct 70
    // - 7 oct 1970
    // - 03 February 2013
    // - 1 July 2013
    fn month_dmy(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        lazy_static! {
            static ref RE: Regex =
                Regex::new(r"^[0-9]{1,2}\s+[a-zA-Z]{3,9}\s+[0-9]{2,4}$").unwrap();
        }
        if !RE.is_match(input) {
            return None;
        }

        // set time to use
        let time = match self.default_time {
            Some(v) => v,
            None => Utc::now().with_timezone(self.tz).time(),
        };

        NaiveDate::parse_from_str(input, "%d %B %y")
            .or_else(|_| NaiveDate::parse_from_str(input, "%d %B %Y"))
            .ok()
            .map(|parsed| parsed.and_time(time))
            .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
            .map(|at_tz| at_tz.with_timezone(&Utc))
            .map(Ok)
    }

    // mm/dd/yyyy hh:mm:ss
    // - 4/8/2014 22:05
    // - 04/08/2014 22:05
    // - 4/8/14 22:05
    // - 04/2/2014 03:00:51
    // - 8/8/1965 12:00:00 AM
    // - 8/8/1965 01:00:01 PM
    // - 8/8/1965 01:00 PM
    // - 8/8/1965 1:00 PM
    // - 8/8/1965 12:00 AM
    // - 4/02/2014 03:00:51
    // - 03/19/2012 10:11:59
    // - 03/19/2012 10:11:59.3186369
    fn slash_mdy_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        lazy_static! {
            static ref RE: Regex = Regex::new(
                r"^[0-9]{1,2}/[0-9]{1,2}/[0-9]{2,4}\s+[0-9]{1,2}:[0-9]{2}(:[0-9]{2})?(\.[0-9]{1,9})?\s*(am|pm|AM|PM)?$"
            )
            .unwrap();
        }
        if !RE.is_match(input) {
            return None;
        }

        self.tz
            .datetime_from_str(input, "%m/%d/%y %H:%M:%S")
            .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%y %H:%M"))
            .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%y %H:%M:%S%.f"))
            .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%y %I:%M:%S %P"))
            .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%y %I:%M %P"))
            .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%Y %H:%M:%S"))
            .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%Y %H:%M"))
            .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%Y %H:%M:%S%.f"))
            .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%Y %I:%M:%S %P"))
            .or_else(|_| self.tz.datetime_from_str(input, "%m/%d/%Y %I:%M %P"))
            .ok()
            .map(|at_tz| at_tz.with_timezone(&Utc))
            .map(Ok)
    }

    // mm/dd/yyyy
    // - 3/31/2014
    // - 03/31/2014
    // - 08/21/71
    // - 8/1/71
    fn slash_mdy(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        lazy_static! {
            static ref RE: Regex = Regex::new(r"^[0-9]{1,2}/[0-9]{1,2}/[0-9]{2,4}$").unwrap();
        }
        if !RE.is_match(input) {
            return None;
        }

        // set time to use
        let time = match self.default_time {
            Some(v) => v,
            None => Utc::now().with_timezone(self.tz).time(),
        };

        NaiveDate::parse_from_str(input, "%m/%d/%y")
            .or_else(|_| NaiveDate::parse_from_str(input, "%m/%d/%Y"))
            .ok()
            .map(|parsed| parsed.and_time(time))
            .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
            .map(|at_tz| at_tz.with_timezone(&Utc))
            .map(Ok)
    }

    // yyyy/mm/dd hh:mm:ss
    // - 2014/4/8 22:05
    // - 2014/04/08 22:05
    // - 2014/04/2 03:00:51
    // - 2014/4/02 03:00:51
    // - 2012/03/19 10:11:59
    // - 2012/03/19 10:11:59.3186369
    fn slash_ymd_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        lazy_static! {
            static ref RE: Regex = Regex::new(
                r"^[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}\s+[0-9]{1,2}:[0-9]{2}(:[0-9]{2})?(\.[0-9]{1,9})?\s*(am|pm|AM|PM)?$"
            )
            .unwrap();
        }
        if !RE.is_match(input) {
            return None;
        }

        self.tz
            .datetime_from_str(input, "%Y/%m/%d %H:%M:%S")
            .or_else(|_| self.tz.datetime_from_str(input, "%Y/%m/%d %H:%M"))
            .or_else(|_| self.tz.datetime_from_str(input, "%Y/%m/%d %H:%M:%S%.f"))
            .or_else(|_| self.tz.datetime_from_str(input, "%Y/%m/%d %I:%M:%S %P"))
            .or_else(|_| self.tz.datetime_from_str(input, "%Y/%m/%d %I:%M %P"))
            .ok()
            .map(|at_tz| at_tz.with_timezone(&Utc))
            .map(Ok)
    }

    // yyyy/mm/dd
    // - 2014/3/31
    // - 2014/03/31
    fn slash_ymd(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        lazy_static! {
            static ref RE: Regex = Regex::new(r"^[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}$").unwrap();
        }
        if !RE.is_match(input) {
            return None;
        }

        // set time to use
        let time = match self.default_time {
            Some(v) => v,
            None => Utc::now().with_timezone(self.tz).time(),
        };

        NaiveDate::parse_from_str(input, "%Y/%m/%d")
            .ok()
            .map(|parsed| parsed.and_time(time))
            .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
            .map(|at_tz| at_tz.with_timezone(&Utc))
            .map(Ok)
    }

    // mm.dd.yyyy
    // - 3.31.2014
    // - 03.31.2014
    // - 08.21.71
    // yyyy.mm.dd
    // - 2014.03.30
    // - 2014.03
    fn dot_mdy_or_ymd(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        lazy_static! {
            static ref RE: Regex = Regex::new(r"[0-9]{1,4}.[0-9]{1,4}[0-9]{1,4}").unwrap();
        }
        if !RE.is_match(input) {
            return None;
        }

        // set time to use
        let time = match self.default_time {
            Some(v) => v,
            None => Utc::now().with_timezone(self.tz).time(),
        };

        NaiveDate::parse_from_str(input, "%m.%d.%y")
            .or_else(|_| NaiveDate::parse_from_str(input, "%m.%d.%Y"))
            .or_else(|_| NaiveDate::parse_from_str(input, "%Y.%m.%d"))
            .or_else(|_| {
                NaiveDate::parse_from_str(&format!("{}.{}", input, Utc::now().day()), "%Y.%m.%d")
            })
            .ok()
            .map(|parsed| parsed.and_time(time))
            .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
            .map(|at_tz| at_tz.with_timezone(&Utc))
            .map(Ok)
    }

    // yymmdd hh:mm:ss mysql log
    // - 171113 14:14:20
    fn mysql_log_timestamp(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        lazy_static! {
            static ref RE: Regex = Regex::new(r"[0-9]{6}\s+[0-9]{2}:[0-9]{2}:[0-9]{2}").unwrap();
        }
        if !RE.is_match(input) {
            return None;
        }

        self.tz
            .datetime_from_str(input, "%y%m%d %H:%M:%S")
            .ok()
            .map(|at_tz| at_tz.with_timezone(&Utc))
            .map(Ok)
    }

    // chinese yyyy mm dd hh mm ss
    // - 2014年04月08日11时25分18秒
    fn chinese_ymd_hms(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        lazy_static! {
            static ref RE: Regex =
                Regex::new(r"^[0-9]{4}年[0-9]{2}月[0-9]{2}日[0-9]{2}时[0-9]{2}分[0-9]{2}秒$")
                    .unwrap();
        }
        if !RE.is_match(input) {
            return None;
        }

        self.tz
            .datetime_from_str(input, "%Y年%m月%d日%H时%M分%S秒")
            .ok()
            .map(|at_tz| at_tz.with_timezone(&Utc))
            .map(Ok)
    }

    // chinese yyyy mm dd
    // - 2014年04月08日
    fn chinese_ymd(&self, input: &str) -> Option<Result<DateTime<Utc>>> {
        lazy_static! {
            static ref RE: Regex = Regex::new(r"^[0-9]{4}年[0-9]{2}月[0-9]{2}日$").unwrap();
        }
        if !RE.is_match(input) {
            return None;
        }

        // set time to use
        let time = match self.default_time {
            Some(v) => v,
            None => Utc::now().with_timezone(self.tz).time(),
        };

        NaiveDate::parse_from_str(input, "%Y年%m月%d日")
            .ok()
            .map(|parsed| parsed.and_time(time))
            .and_then(|datetime| self.tz.from_local_datetime(&datetime).single())
            .map(|at_tz| at_tz.with_timezone(&Utc))
            .map(Ok)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn unix_timestamp() {
        let parse = Parse::new(&Utc, None);

        let test_cases = vec![
            ("0000000000", Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)),
            ("0000000000000", Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)),
            ("0000000000000000000", Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)),
            ("1511648546", Utc.ymd(2017, 11, 25).and_hms(22, 22, 26)),
            (
                "1620021848429",
                Utc.ymd(2021, 5, 3).and_hms_milli(6, 4, 8, 429),
            ),
            (
                "1620024872717915000",
                Utc.ymd(2021, 5, 3).and_hms_nano(6, 54, 32, 717915000),
            ),
        ];

        for &(input, want) in test_cases.iter() {
            assert_eq!(
                parse.unix_timestamp(input).unwrap().unwrap(),
                want,
                "unix_timestamp/{}",
                input
            )
        }
        assert!(parse.unix_timestamp("15116").is_none());
        assert!(parse
            .unix_timestamp("16200248727179150001620024872717915000")
            .is_none());
        assert!(parse.unix_timestamp("not-a-ts").is_none());
    }

    #[test]
    fn rfc3339() {
        let parse = Parse::new(&Utc, None);

        let test_cases = vec![
            (
                "2021-05-01T01:17:02.604456Z",
                Utc.ymd(2021, 5, 1).and_hms_nano(1, 17, 2, 604456000),
            ),
            (
                "2017-11-25T22:34:50Z",
                Utc.ymd(2017, 11, 25).and_hms(22, 34, 50),
            ),
        ];

        for &(input, want) in test_cases.iter() {
            assert_eq!(
                parse.rfc3339(input).unwrap().unwrap(),
                want,
                "rfc3339/{}",
                input
            )
        }
        assert!(parse.rfc3339("2017-11-25 22:34:50").is_none());
        assert!(parse.rfc3339("not-date-time").is_none());
    }

    #[test]
    fn rfc2822() {
        let parse = Parse::new(&Utc, None);

        let test_cases = vec![
            (
                "Wed, 02 Jun 2021 06:31:39 GMT",
                Utc.ymd(2021, 6, 2).and_hms(6, 31, 39),
            ),
            (
                "Wed, 02 Jun 2021 06:31:39 PDT",
                Utc.ymd(2021, 6, 2).and_hms(13, 31, 39),
            ),
        ];

        for &(input, want) in test_cases.iter() {
            assert_eq!(
                parse.rfc2822(input).unwrap().unwrap(),
                want,
                "rfc2822/{}",
                input
            )
        }
        assert!(parse.rfc2822("02 Jun 2021 06:31:39").is_none());
        assert!(parse.rfc2822("not-date-time").is_none());
    }

    #[test]
    fn postgres_timestamp() {
        let parse = Parse::new(&Utc, None);

        let test_cases = vec![
            (
                "2019-11-29 08:08-08",
                Utc.ymd(2019, 11, 29).and_hms(16, 8, 0),
            ),
            (
                "2019-11-29 08:08:05-08",
                Utc.ymd(2019, 11, 29).and_hms(16, 8, 5),
            ),
            (
                "2021-05-02 23:31:36.0741-07",
                Utc.ymd(2021, 5, 3).and_hms_micro(6, 31, 36, 74100),
            ),
            (
                "2021-05-02 23:31:39.12689-07",
                Utc.ymd(2021, 5, 3).and_hms_micro(6, 31, 39, 126890),
            ),
            (
                "2019-11-29 08:15:47.624504-08",
                Utc.ymd(2019, 11, 29).and_hms_micro(16, 15, 47, 624504),
            ),
            (
                "2017-07-19 03:21:51+00:00",
                Utc.ymd(2017, 7, 19).and_hms(3, 21, 51),
            ),
        ];

        for &(input, want) in test_cases.iter() {
            assert_eq!(
                parse.postgres_timestamp(input).unwrap().unwrap(),
                want,
                "postgres_timestamp/{}",
                input
            )
        }
        assert!(parse.postgres_timestamp("not-date-time").is_none());
    }

    #[test]
    fn ymd_hms() {
        let parse = Parse::new(&Utc, None);

        let test_cases = vec![
            ("2021-04-30 21:14", Utc.ymd(2021, 4, 30).and_hms(21, 14, 0)),
            (
                "2021-04-30 21:14:10",
                Utc.ymd(2021, 4, 30).and_hms(21, 14, 10),
            ),
            (
                "2021-04-30 21:14:10.052282",
                Utc.ymd(2021, 4, 30).and_hms_micro(21, 14, 10, 52282),
            ),
            (
                "2014-04-26 05:24:37 PM",
                Utc.ymd(2014, 4, 26).and_hms(17, 24, 37),
            ),
            (
                "2014-04-26 17:24:37.123",
                Utc.ymd(2014, 4, 26).and_hms_milli(17, 24, 37, 123),
            ),
            (
                "2014-04-26 17:24:37.3186369",
                Utc.ymd(2014, 4, 26).and_hms_nano(17, 24, 37, 318636900),
            ),
            (
                "2012-08-03 18:31:59.257000000",
                Utc.ymd(2012, 8, 3).and_hms_nano(18, 31, 59, 257000000),
            ),
        ];

        for &(input, want) in test_cases.iter() {
            assert_eq!(
                parse.ymd_hms(input).unwrap().unwrap(),
                want,
                "ymd_hms/{}",
                input
            )
        }
        assert!(parse.ymd_hms("not-date-time").is_none());
    }

    #[test]
    fn ymd_hms_z() {
        let parse = Parse::new(&Utc, None);

        let test_cases = vec![
            (
                "2017-11-25 13:31:15 PST",
                Utc.ymd(2017, 11, 25).and_hms(21, 31, 15),
            ),
            (
                "2017-11-25 13:31 PST",
                Utc.ymd(2017, 11, 25).and_hms(21, 31, 0),
            ),
            (
                "2014-12-16 06:20:00 UTC",
                Utc.ymd(2014, 12, 16).and_hms(6, 20, 0),
            ),
            (
                "2014-12-16 06:20:00 GMT",
                Utc.ymd(2014, 12, 16).and_hms(6, 20, 0),
            ),
            (
                "2014-04-26 13:13:43 +0800",
                Utc.ymd(2014, 4, 26).and_hms(5, 13, 43),
            ),
            (
                "2014-04-26 13:13:44 +09:00",
                Utc.ymd(2014, 4, 26).and_hms(4, 13, 44),
            ),
            (
                "2012-08-03 18:31:59.257000000 +0000",
                Utc.ymd(2012, 8, 3).and_hms_nano(18, 31, 59, 257000000),
            ),
            (
                "2015-09-30 18:48:56.35272715 UTC",
                Utc.ymd(2015, 9, 30).and_hms_nano(18, 48, 56, 352727150),
            ),
        ];

        for &(input, want) in test_cases.iter() {
            assert_eq!(
                parse.ymd_hms_z(input).unwrap().unwrap(),
                want,
                "ymd_hms_z/{}",
                input
            )
        }
        assert!(parse.ymd_hms_z("not-date-time").is_none());
    }

    #[test]
    fn ymd() {
        let parse = Parse::new(&Utc, Some(Utc::now().time()));

        let test_cases = vec![(
            "2021-02-21",
            Utc.ymd(2021, 2, 21).and_time(Utc::now().time()),
        )];

        for &(input, want) in test_cases.iter() {
            assert_eq!(
                parse
                    .ymd(input)
                    .unwrap()
                    .unwrap()
                    .trunc_subsecs(0)
                    .with_second(0)
                    .unwrap(),
                want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
                "ymd/{}",
                input
            )
        }
        assert!(parse.ymd("not-date-time").is_none());
    }

    #[test]
    fn ymd_z() {
        let parse = Parse::new(&Utc, None);
        let now_at_pst = Utc::now().with_timezone(&FixedOffset::west(8 * 3600));
        let now_at_cst = Utc::now().with_timezone(&FixedOffset::east(8 * 3600));

        let test_cases = vec![
            (
                "2021-02-21 PST",
                FixedOffset::west(8 * 3600)
                    .ymd(2021, 2, 21)
                    .and_time(now_at_pst.time())
                    .map(|dt| dt.with_timezone(&Utc)),
            ),
            (
                "2021-02-21 UTC",
                FixedOffset::west(0)
                    .ymd(2021, 2, 21)
                    .and_time(Utc::now().time())
                    .map(|dt| dt.with_timezone(&Utc)),
            ),
            (
                "2020-07-20+08:00",
                FixedOffset::east(8 * 3600)
                    .ymd(2020, 7, 20)
                    .and_time(now_at_cst.time())
                    .map(|dt| dt.with_timezone(&Utc)),
            ),
        ];

        for &(input, want) in test_cases.iter() {
            assert_eq!(
                parse
                    .ymd_z(input)
                    .unwrap()
                    .unwrap()
                    .trunc_subsecs(0)
                    .with_second(0)
                    .unwrap(),
                want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
                "ymd_z/{}",
                input
            )
        }
        assert!(parse.ymd_z("not-date-time").is_none());
    }

    #[test]
    fn hms() {
        let parse = Parse::new(&Utc, None);

        let test_cases = vec![
            (
                "01:06:06",
                Utc::now().date().and_time(NaiveTime::from_hms(1, 6, 6)),
            ),
            (
                "4:00pm",
                Utc::now().date().and_time(NaiveTime::from_hms(16, 0, 0)),
            ),
            (
                "6:00 AM",
                Utc::now().date().and_time(NaiveTime::from_hms(6, 0, 0)),
            ),
        ];

        for &(input, want) in test_cases.iter() {
            assert_eq!(
                parse.hms(input).unwrap().unwrap(),
                want.unwrap(),
                "hms/{}",
                input
            )
        }
        assert!(parse.hms("not-date-time").is_none());
    }

    #[test]
    fn hms_z() {
        let parse = Parse::new(&Utc, None);
        let now_at_pst = Utc::now().with_timezone(&FixedOffset::west(8 * 3600));

        let test_cases = vec![
            (
                "01:06:06 PST",
                FixedOffset::west(8 * 3600)
                    .from_local_date(&now_at_pst.date().naive_local())
                    .and_time(NaiveTime::from_hms(1, 6, 6))
                    .map(|dt| dt.with_timezone(&Utc)),
            ),
            (
                "4:00pm PST",
                FixedOffset::west(8 * 3600)
                    .from_local_date(&now_at_pst.date().naive_local())
                    .and_time(NaiveTime::from_hms(16, 0, 0))
                    .map(|dt| dt.with_timezone(&Utc)),
            ),
            (
                "6:00 AM PST",
                FixedOffset::west(8 * 3600)
                    .from_local_date(&now_at_pst.date().naive_local())
                    .and_time(NaiveTime::from_hms(6, 0, 0))
                    .map(|dt| dt.with_timezone(&Utc)),
            ),
            (
                "6:00pm UTC",
                FixedOffset::west(0)
                    .from_local_date(&Utc::now().date().naive_local())
                    .and_time(NaiveTime::from_hms(18, 0, 0))
                    .map(|dt| dt.with_timezone(&Utc)),
            ),
        ];

        for &(input, want) in test_cases.iter() {
            assert_eq!(
                parse.hms_z(input).unwrap().unwrap(),
                want.unwrap(),
                "hms_z/{}",
                input
            )
        }
        assert!(parse.hms_z("not-date-time").is_none());
    }

    #[test]
    fn month_ymd() {
        let parse = Parse::new(&Utc, None);

        let test_cases = vec![(
            "2021-Feb-21",
            Utc.ymd(2021, 2, 21).and_time(Utc::now().time()),
        )];

        for &(input, want) in test_cases.iter() {
            assert_eq!(
                parse
                    .month_ymd(input)
                    .unwrap()
                    .unwrap()
                    .trunc_subsecs(0)
                    .with_second(0)
                    .unwrap(),
                want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
                "month_ymd/{}",
                input
            )
        }
        assert!(parse.month_ymd("not-date-time").is_none());
    }

    #[test]
    fn month_md_hms() {
        let parse = Parse::new(&Utc, None);

        let test_cases = vec![
            (
                "May 6 at 9:24 PM",
                Utc.ymd(Utc::now().year(), 5, 6).and_hms(21, 24, 0),
            ),
            (
                "May 27 02:45:27",
                Utc.ymd(Utc::now().year(), 5, 27).and_hms(2, 45, 27),
            ),
        ];

        for &(input, want) in test_cases.iter() {
            assert_eq!(
                parse.month_md_hms(input).unwrap().unwrap(),
                want,
                "month_md_hms/{}",
                input
            )
        }
        assert!(parse.month_md_hms("not-date-time").is_none());
    }

    #[test]
    fn month_mdy_hms() {
        let parse = Parse::new(&Utc, None);

        let test_cases = vec![
            (
                "May 8, 2009 5:57:51 PM",
                Utc.ymd(2009, 5, 8).and_hms(17, 57, 51),
            ),
            (
                "September 17, 2012 10:09am",
                Utc.ymd(2012, 9, 17).and_hms(10, 9, 0),
            ),
            (
                "September 17, 2012, 10:10:09",
                Utc.ymd(2012, 9, 17).and_hms(10, 10, 9),
            ),
        ];

        for &(input, want) in test_cases.iter() {
            assert_eq!(
                parse.month_mdy_hms(input).unwrap().unwrap(),
                want,
                "month_mdy_hms/{}",
                input
            )
        }
        assert!(parse.month_mdy_hms("not-date-time").is_none());
    }

    #[test]
    fn month_mdy_hms_z() {
        let parse = Parse::new(&Utc, None);

        let test_cases = vec![
            (
                "May 02, 2021 15:51:31 UTC",
                Utc.ymd(2021, 5, 2).and_hms(15, 51, 31),
            ),
            (
                "May 02, 2021 15:51 UTC",
                Utc.ymd(2021, 5, 2).and_hms(15, 51, 0),
            ),
            (
                "May 26, 2021, 12:49 AM PDT",
                Utc.ymd(2021, 5, 26).and_hms(7, 49, 0),
            ),
            (
                "September 17, 2012 at 10:09am PST",
                Utc.ymd(2012, 9, 17).and_hms(18, 9, 0),
            ),
        ];

        for &(input, want) in test_cases.iter() {
            assert_eq!(
                parse.month_mdy_hms_z(input).unwrap().unwrap(),
                want,
                "month_mdy_hms_z/{}",
                input
            )
        }
        assert!(parse.month_mdy_hms_z("not-date-time").is_none());
    }

    #[test]
    fn month_mdy() {
        let parse = Parse::new(&Utc, None);

        let test_cases = vec![
            (
                "May 25, 2021",
                Utc.ymd(2021, 5, 25).and_time(Utc::now().time()),
            ),
            (
                "oct 7, 1970",
                Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
            ),
            (
                "oct 7, 70",
                Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
            ),
            (
                "oct. 7, 1970",
                Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
            ),
            (
                "oct. 7, 70",
                Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
            ),
            (
                "October 7, 1970",
                Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
            ),
        ];

        for &(input, want) in test_cases.iter() {
            assert_eq!(
                parse
                    .month_mdy(input)
                    .unwrap()
                    .unwrap()
                    .trunc_subsecs(0)
                    .with_second(0)
                    .unwrap(),
                want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
                "month_mdy/{}",
                input
            )
        }
        assert!(parse.month_mdy("not-date-time").is_none());
    }

    #[test]
    fn month_dmy_hms() {
        let parse = Parse::new(&Utc, None);

        let test_cases = vec![
            (
                "12 Feb 2006, 19:17",
                Utc.ymd(2006, 2, 12).and_hms(19, 17, 0),
            ),
            ("12 Feb 2006 19:17", Utc.ymd(2006, 2, 12).and_hms(19, 17, 0)),
            (
                "14 May 2019 19:11:40.164",
                Utc.ymd(2019, 5, 14).and_hms_milli(19, 11, 40, 164),
            ),
        ];

        for &(input, want) in test_cases.iter() {
            assert_eq!(
                parse.month_dmy_hms(input).unwrap().unwrap(),
                want,
                "month_dmy_hms/{}",
                input
            )
        }
        assert!(parse.month_dmy_hms("not-date-time").is_none());
    }

    #[test]
    fn month_dmy() {
        let parse = Parse::new(&Utc, None);

        let test_cases = vec![
            ("7 oct 70", Utc.ymd(1970, 10, 7).and_time(Utc::now().time())),
            (
                "7 oct 1970",
                Utc.ymd(1970, 10, 7).and_time(Utc::now().time()),
            ),
            (
                "03 February 2013",
                Utc.ymd(2013, 2, 3).and_time(Utc::now().time()),
            ),
            (
                "1 July 2013",
                Utc.ymd(2013, 7, 1).and_time(Utc::now().time()),
            ),
        ];

        for &(input, want) in test_cases.iter() {
            assert_eq!(
                parse
                    .month_dmy(input)
                    .unwrap()
                    .unwrap()
                    .trunc_subsecs(0)
                    .with_second(0)
                    .unwrap(),
                want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
                "month_dmy/{}",
                input
            )
        }
        assert!(parse.month_dmy("not-date-time").is_none());
    }

    #[test]
    fn slash_mdy_hms() {
        let parse = Parse::new(&Utc, None);

        let test_cases = vec![
            ("4/8/2014 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)),
            ("04/08/2014 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)),
            ("4/8/14 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)),
            ("04/2/2014 03:00:51", Utc.ymd(2014, 4, 2).and_hms(3, 0, 51)),
            ("8/8/1965 12:00:00 AM", Utc.ymd(1965, 8, 8).and_hms(0, 0, 0)),
            (
                "8/8/1965 01:00:01 PM",
                Utc.ymd(1965, 8, 8).and_hms(13, 0, 1),
            ),
            ("8/8/1965 01:00 PM", Utc.ymd(1965, 8, 8).and_hms(13, 0, 0)),
            ("8/8/1965 1:00 PM", Utc.ymd(1965, 8, 8).and_hms(13, 0, 0)),
            ("8/8/1965 12:00 AM", Utc.ymd(1965, 8, 8).and_hms(0, 0, 0)),
            ("4/02/2014 03:00:51", Utc.ymd(2014, 4, 2).and_hms(3, 0, 51)),
            (
                "03/19/2012 10:11:59",
                Utc.ymd(2012, 3, 19).and_hms(10, 11, 59),
            ),
            (
                "03/19/2012 10:11:59.3186369",
                Utc.ymd(2012, 3, 19).and_hms_nano(10, 11, 59, 318636900),
            ),
        ];

        for &(input, want) in test_cases.iter() {
            assert_eq!(
                parse.slash_mdy_hms(input).unwrap().unwrap(),
                want,
                "slash_mdy_hms/{}",
                input
            )
        }
        assert!(parse.slash_mdy_hms("not-date-time").is_none());
    }

    #[test]
    fn slash_mdy() {
        let parse = Parse::new(&Utc, None);

        let test_cases = vec![
            (
                "3/31/2014",
                Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
            ),
            (
                "03/31/2014",
                Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
            ),
            ("08/21/71", Utc.ymd(1971, 8, 21).and_time(Utc::now().time())),
            ("8/1/71", Utc.ymd(1971, 8, 1).and_time(Utc::now().time())),
        ];

        for &(input, want) in test_cases.iter() {
            assert_eq!(
                parse
                    .slash_mdy(input)
                    .unwrap()
                    .unwrap()
                    .trunc_subsecs(0)
                    .with_second(0)
                    .unwrap(),
                want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
                "slash_mdy/{}",
                input
            )
        }
        assert!(parse.slash_mdy("not-date-time").is_none());
    }

    #[test]
    fn slash_ymd_hms() {
        let parse = Parse::new(&Utc, None);

        let test_cases = vec![
            ("2014/4/8 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)),
            ("2014/04/08 22:05", Utc.ymd(2014, 4, 8).and_hms(22, 5, 0)),
            ("2014/04/2 03:00:51", Utc.ymd(2014, 4, 2).and_hms(3, 0, 51)),
            ("2014/4/02 03:00:51", Utc.ymd(2014, 4, 2).and_hms(3, 0, 51)),
            (
                "2012/03/19 10:11:59",
                Utc.ymd(2012, 3, 19).and_hms(10, 11, 59),
            ),
            (
                "2012/03/19 10:11:59.3186369",
                Utc.ymd(2012, 3, 19).and_hms_nano(10, 11, 59, 318636900),
            ),
        ];

        for &(input, want) in test_cases.iter() {
            assert_eq!(
                parse.slash_ymd_hms(input).unwrap().unwrap(),
                want,
                "slash_ymd_hms/{}",
                input
            )
        }
        assert!(parse.slash_ymd_hms("not-date-time").is_none());
    }

    #[test]
    fn slash_ymd() {
        let parse = Parse::new(&Utc, Some(Utc::now().time()));

        let test_cases = vec![
            (
                "2014/3/31",
                Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
            ),
            (
                "2014/03/31",
                Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
            ),
        ];

        for &(input, want) in test_cases.iter() {
            assert_eq!(
                parse
                    .slash_ymd(input)
                    .unwrap()
                    .unwrap()
                    .trunc_subsecs(0)
                    .with_second(0)
                    .unwrap(),
                want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
                "slash_ymd/{}",
                input
            )
        }
        assert!(parse.slash_ymd("not-date-time").is_none());
    }

    #[test]
    fn dot_mdy_or_ymd() {
        let parse = Parse::new(&Utc, Some(Utc::now().time()));

        let test_cases = vec![
            // mm.dd.yyyy
            (
                "3.31.2014",
                Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
            ),
            (
                "03.31.2014",
                Utc.ymd(2014, 3, 31).and_time(Utc::now().time()),
            ),
            ("08.21.71", Utc.ymd(1971, 8, 21).and_time(Utc::now().time())),
            // yyyy.mm.dd
            (
                "2014.03.30",
                Utc.ymd(2014, 3, 30).and_time(Utc::now().time()),
            ),
            (
                "2014.03",
                Utc.ymd(2014, 3, Utc::now().day())
                    .and_time(Utc::now().time()),
            ),
        ];

        for &(input, want) in test_cases.iter() {
            assert_eq!(
                parse
                    .dot_mdy_or_ymd(input)
                    .unwrap()
                    .unwrap()
                    .trunc_subsecs(0)
                    .with_second(0)
                    .unwrap(),
                want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
                "dot_mdy_or_ymd/{}",
                input
            )
        }
        assert!(parse.dot_mdy_or_ymd("not-date-time").is_none());
    }

    #[test]
    fn mysql_log_timestamp() {
        let parse = Parse::new(&Utc, None);

        let test_cases = vec![
            // yymmdd hh:mm:ss mysql log
            ("171113 14:14:20", Utc.ymd(2017, 11, 13).and_hms(14, 14, 20)),
        ];

        for &(input, want) in test_cases.iter() {
            assert_eq!(
                parse.mysql_log_timestamp(input).unwrap().unwrap(),
                want,
                "mysql_log_timestamp/{}",
                input
            )
        }
        assert!(parse.mysql_log_timestamp("not-date-time").is_none());
    }

    #[test]
    fn chinese_ymd_hms() {
        let parse = Parse::new(&Utc, None);

        let test_cases = vec![(
            "2014年04月08日11时25分18秒",
            Utc.ymd(2014, 4, 8).and_hms(11, 25, 18),
        )];

        for &(input, want) in test_cases.iter() {
            assert_eq!(
                parse.chinese_ymd_hms(input).unwrap().unwrap(),
                want,
                "chinese_ymd_hms/{}",
                input
            )
        }
        assert!(parse.chinese_ymd_hms("not-date-time").is_none());
    }

    #[test]
    fn chinese_ymd() {
        let parse = Parse::new(&Utc, Some(Utc::now().time()));

        let test_cases = vec![(
            "2014年04月08日",
            Utc.ymd(2014, 4, 8).and_time(Utc::now().time()),
        )];

        for &(input, want) in test_cases.iter() {
            assert_eq!(
                parse
                    .chinese_ymd(input)
                    .unwrap()
                    .unwrap()
                    .trunc_subsecs(0)
                    .with_second(0)
                    .unwrap(),
                want.unwrap().trunc_subsecs(0).with_second(0).unwrap(),
                "chinese_ymd/{}",
                input
            )
        }
        assert!(parse.chinese_ymd("not-date-time").is_none());
    }
}
