// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
//
// This module contains methods for compatibility with Go's `time` package.
//
// Copyright (c) 2009 The Go Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//    * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//    * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

use crate::*;
use core::fmt;
use core::iter;

use chrono::TimeZone;
use chrono::{
    format::{self, Fixed, Parsed},
    DateTime, Duration, FixedOffset, ParseResult,
};

const NANOSECOND: u64 = 1;
const MICROSECOND: u64 = 1000 * NANOSECOND;
const MILLISECOND: u64 = 1000 * MICROSECOND;
const SECOND: u64 = 1000 * MILLISECOND;
const MINUTE: u64 = 60 * SECOND;
const HOUR: u64 = 60 * MINUTE;

#[derive(Debug)]
pub enum ParseDurationError {
    InvalidDuration(String),
    UnknownUnit(String),
    Overflow,
}

impl fmt::Display for ParseDurationError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ParseDurationError::InvalidDuration(dur) => {
                write!(f, "invalid duration: {dur}")
            }
            ParseDurationError::UnknownUnit(unit) => {
                write!(f, "unknown unit: {unit}")
            }
            ParseDurationError::Overflow => {
                write!(f, "overflow")
            }
        }
    }
}

// Parses a duration string in the form of `10h12m45s`.
//
// Adapted from Go's `time.ParseDuration`:
// https://github.com/golang/go/blob/8db131082d08e497fd8e9383d0ff7715e1bef478/src/time/format.go#L1584-L1686
pub fn parse_duration(mut s: &str) -> Result<Duration, ParseDurationError> {
    // Input is in the format of `[-+]?([0-9]*(\.[0-9]*)?[a-z]+)+`
    let orig = s;

    // Consume [-+]?
    let neg = if s.starts_with('-') {
        s = &s[1..];
        true
    } else if s.starts_with('+') {
        s = &s[1..];
        false
    } else {
        false
    };

    // Special case: if all that is left is "0", this is zero.
    if s == "0" {
        return Ok(Duration::zero());
    }

    if s.is_empty() {
        return Err(ParseDurationError::InvalidDuration(orig.to_string()));
    }

    let mut dur = 0u64;

    while !s.is_empty() {
        // The next character must be [0-9.]
        if !(s.starts_with('.') || s.starts_with(|c: char| c.is_ascii_digit())) {
            return Err(ParseDurationError::InvalidDuration(orig.to_string()));
        }

        let previous_len = s.len();
        // v is the integers before the decimal point
        // Consume [0-9]*
        let (mut v, rem) = leading_int(s)?;
        s = rem;

        // whether we consumed anything before a period
        let pre = previous_len != s.len();

        // Consume (\.[0-9]*)?
        let mut post = false;
        let mut f = 0;
        let mut scale = 0.0;
        if !s.is_empty() && s.starts_with('.') {
            s = &s[1..];
            let previous_len = s.len();
            (f, scale, s) = leading_fraction(s);
            post = previous_len != s.len();
        }
        if !pre && !post {
            // no digits (e.g. ".s" or "-.s")
            return Err(ParseDurationError::InvalidDuration(orig.to_string()));
        }

        // Consume unit.
        let mut idx = 0;
        for (i, c) in s.char_indices() {
            if c == '.' || c.is_ascii_digit() {
                break;
            }
            idx = i;
        }

        let unit = match &s[..idx + 1] {
            "ns" => NANOSECOND,
            "us" => MICROSECOND,
            "µs" => MICROSECOND, // U+00B5 = micro symbol
            "μs" => MICROSECOND, // U+03BC = Greek letter mu
            "ms" => MILLISECOND,
            "s" => SECOND,
            "m" => MINUTE,
            "h" => HOUR,
            unkonwn => return Err(ParseDurationError::UnknownUnit(unkonwn.to_string())),
        };

        s = &s[idx + 1..];

        if v > ((1 << 63) / unit) {
            // overflow
            return Err(ParseDurationError::InvalidDuration(orig.to_string()));
        }
        v *= unit;
        if f > 0 {
            // f64 is needed to be nanosecond accurate for fractions of hours.
            // v >= 0 && (f*unit/scale) <= 3.6e+12 (ns/h, h is the largest unit)
            v += (f as f64 * (unit as f64 / scale)) as u64;
            if v > 1 << 63 {
                // overflow
                return Err(ParseDurationError::InvalidDuration(orig.to_string()));
            }
        }

        dur += v;
        if dur > 1 << 63 {
            return Err(ParseDurationError::InvalidDuration(orig.to_string()));
        }
    }

    if neg {
        let dur = dur as i64;
        if dur < 0 {
            return Ok(Duration::nanoseconds(dur));
        }
        return Ok(-Duration::nanoseconds(dur));
    }

    if dur > i64::MAX as u64 {
        return Err(ParseDurationError::InvalidDuration(orig.to_string()));
    }

    Ok(Duration::nanoseconds(dur as i64))
}

fn leading_int(s: &str) -> Result<(u64, &str), ParseDurationError> {
    let mut last_idx = 0;
    let mut num: u64 = 0;
    for (i, c) in s.char_indices() {
        last_idx = i;

        let n = match c.to_digit(10) {
            Some(n) => n as u64,
            None => break,
        };

        if num > ((1 << 63) / 10) {
            // overflow
            return Err(ParseDurationError::Overflow);
        }

        num = num * 10 + n;

        if num > 1 << 63 {
            // overflow
            return Err(ParseDurationError::Overflow);
        }
    }

    Ok((num, &s[last_idx..]))
}

fn leading_fraction(s: &str) -> (u64, f64, &str) {
    let mut num: u64 = 0;
    let mut scale = 1.0;
    let mut overflow = false;
    let mut last_idx = 0;
    for (i, c) in s.char_indices() {
        last_idx = i;

        let n = match c.to_digit(10) {
            Some(n) => n as u64,
            None => break,
        };

        if overflow {
            continue;
        }

        if num > (i64::MAX as u64 / 10) {
            // It's possible for overflow to give a positive number, so take care.
            overflow = true;
            continue;
        }

        let y = num * 10 + n;
        if y > 1 << 63 {
            overflow = true;
            continue;
        }

        num = y;
        scale *= 10.0;
    }

    (num, scale, &s[last_idx..])
}

#[derive(Debug, Clone, PartialEq, Eq)]
enum GoTimeFormatItemsMode {
    Parse,
    Format,
}

#[derive(Debug, Clone)]
struct GoTimeFormatItems<'a> {
    reminder: &'a str,
    queue: &'static [format::Item<'static>],
    mode: GoTimeFormatItemsMode,
}

impl<'a> GoTimeFormatItems<'a> {
    fn parse(reminder: &str) -> GoTimeFormatItems {
        GoTimeFormatItems {
            reminder,
            queue: &[],
            mode: GoTimeFormatItemsMode::Parse,
        }
    }

    fn format(reminder: &str) -> GoTimeFormatItems {
        GoTimeFormatItems {
            reminder,
            queue: &[],
            mode: GoTimeFormatItemsMode::Format,
        }
    }
}

impl<'a> Iterator for GoTimeFormatItems<'a> {
    type Item = format::Item<'a>;

    fn next(&mut self) -> Option<Self::Item> {
        use format::{Fixed::*, Item::*, Numeric, Pad};

        macro_rules! token {
            ($prefix:expr, $kind:expr $(, $queue:expr)*) => {
                if self.reminder.starts_with($prefix) {
                    self.reminder = &self.reminder[$prefix.len()..];
                    self.queue = &[$($queue),*];
                    return Some($kind);
                }
            };
        }

        fn is_fractional_seconds(val: &str) -> bool {
            // first char is either '.' or ','
            let mut chars = val.chars().skip(1);
            let Some(repeating @ ('0' | '9')) = chars.next() else {
                return false;
            };
            let next = chars.find(|c| c != &repeating);
            !matches!(next, Some('0'..='9'))
        }

        if let Some((item, reminder)) = self.queue.split_first() {
            self.queue = reminder;
            return Some(item.clone());
        }

        match self.reminder.chars().next() {
            // January, Jan
            Some('J') => {
                token!("January", Fixed(LongMonthName));
                token!("Jan", Fixed(ShortMonthName));
            }

            // Monday, Mon, MST
            Some('M') => {
                token!("Monday", Fixed(LongWeekdayName));
                token!("Mon", Fixed(ShortWeekdayName));
                token!("MST", Fixed(TimezoneName));
            }

            // 01, 02, 03, 04, 05, 06, 002
            Some('0') => {
                token!("002", Numeric(Numeric::Ordinal, Pad::Zero));
                token!("01", Numeric(Numeric::Month, Pad::Zero));
                token!("02", Numeric(Numeric::Day, Pad::Zero));
                token!("03", Numeric(Numeric::Hour12, Pad::Zero));
                token!("04", Numeric(Numeric::Minute, Pad::Zero));

                if self.reminder.starts_with("05") {
                    self.reminder = &self.reminder[2..];
                    if !self.reminder.starts_with('.') && self.mode == GoTimeFormatItemsMode::Parse
                    {
                        self.queue = &[Fixed(Nanosecond)];
                    }
                    return Some(Numeric(Numeric::Second, Pad::Zero));
                }

                token!("06", Numeric(Numeric::YearMod100, Pad::Zero));
            }

            // 15, 1
            Some('1') => {
                use Numeric::*;
                token!("15", Numeric(Hour, Pad::Zero));
                token!("1", Numeric(Month, Pad::None));
            }

            // 2006, 2
            Some('2') => {
                use Numeric::*;
                token!("2006", Numeric(Year, Pad::Zero));
                token!("2", Numeric(Day, Pad::None));
            }
            // _2, _2006, __2
            Some('_') => {
                use Numeric::*;
                token!("_2006", Literal("_"), Numeric(Year, Pad::None));
                token!("__2", Numeric(Ordinal, Pad::Space));
                token!("_2", Numeric(Day, Pad::Space));
            }

            Some('3') => {
                use Numeric::*;
                token!("3", Numeric(Hour12, Pad::None));
            }

            Some('4') => {
                use Numeric::*;
                token!("4", Numeric(Minute, Pad::None));
            }

            Some('5') => {
                token!("5", Numeric(Numeric::Second, Pad::None), Fixed(Nanosecond));
            }

            // PM
            Some('P') => {
                token!("PM", Fixed(UpperAmPm));
            }

            // pm
            Some('p') => {
                token!("pm", Fixed(LowerAmPm));
            }

            // -070000, -07:00:00, -0700, -07:00, -07
            Some('-') => {
                token!("-070000", Fixed(TimezoneOffsetDoubleColon));
                token!("-07:00:00", Fixed(TimezoneOffsetDoubleColon));

                token!("-0700", Fixed(TimezoneOffset));
                token!("-07:00", Fixed(TimezoneOffsetColon));

                token!("-07", Fixed(TimezoneOffsetTripleColon));

                token!("-", Literal("-"));
            }

            // Z070000, Z07:00:00, Z0700, Z07:00, Z07
            Some('Z') => {
                // token!("Z070000", Fixed(TimezoneOffsetDoubleColonZ));
                // token!("Z07:00:00", Fixed(TimezoneOffsetDoubleColonZ));

                token!("Z0700", Fixed(TimezoneOffsetZ));
                token!("Z07:00", Fixed(TimezoneOffsetColonZ));

                // token!("Z07", Fixed(TimezoneOffsetTripleColonZ));
            }

            // ,000, or .000, or ,999, or .999 - repeated digits for fractional seconds.
            Some('.' | ',') if is_fractional_seconds(self.reminder) => {
                token!(".000000000", Fixed(Nanosecond9));
                token!(".00000000", Fixed(Nanosecond));
                token!(".0000000", Fixed(Nanosecond));
                token!(".000000", Fixed(Nanosecond6));
                token!(".00000", Fixed(Nanosecond));
                token!(".0000", Fixed(Nanosecond));
                token!(".000", Fixed(Nanosecond3));
                token!(".00", Fixed(Nanosecond));
                token!(".0", Fixed(Nanosecond));
                token!(".999999999", Fixed(Nanosecond));
                token!(".99999999", Fixed(Nanosecond));
                token!(".9999999", Fixed(Nanosecond));
                token!(".999999", Fixed(Nanosecond));
                token!(".99999", Fixed(Nanosecond));
                token!(".9999", Fixed(Nanosecond));
                token!(".999", Fixed(Nanosecond));
                token!(".99", Fixed(Nanosecond));
                token!(".9", Fixed(Nanosecond));
                token!(".", Literal("."));

                token!(",000000000", Fixed(Nanosecond9));
                token!(",00000000", Fixed(Nanosecond));
                token!(",0000000", Fixed(Nanosecond));
                token!(",000000", Fixed(Nanosecond6));
                token!(",00000", Fixed(Nanosecond));
                token!(",0000", Fixed(Nanosecond));
                token!(",000", Fixed(Nanosecond3));
                token!(",00", Fixed(Nanosecond));
                token!(",0", Fixed(Nanosecond));
                token!(",999999999", Fixed(Nanosecond9));
                token!(",99999999", Fixed(Nanosecond));
                token!(",9999999", Fixed(Nanosecond));
                token!(",999999", Fixed(Nanosecond6));
                token!(",99999", Fixed(Nanosecond));
                token!(",9999", Fixed(Nanosecond));
                token!(",999", Fixed(Nanosecond3));
                token!(",99", Fixed(Nanosecond));
                token!(",9", Fixed(Nanosecond));
                token!(",", Literal(","));
            }
            Some(c) if c.is_whitespace() => {
                let next_non_ws = self
                    .reminder
                    .find(|c: char| !c.is_whitespace())
                    .unwrap_or(self.reminder.len());

                let literal = &self.reminder[..next_non_ws];
                token!(&literal, Space(literal));
            }

            Some(_) => {
                let literal = &self.reminder[..1];
                token!(&literal, Literal(literal));
            }

            None => {}
        }

        None
    }
}

// Adapted from chrono's `scan::timezone_offset_2822`:
// https://github.com/chronotope/chrono/blob/baa55d084784e4e88b5332efe8e96af794a52e8a/src/format/scan.rs#L285-L322
fn parse_legacy_timezone(parsed: &mut Parsed, val: &str) -> ParseResult<()> {
    let upto = val
        .as_bytes()
        .iter()
        .position(|&c| !c.is_ascii_alphabetic())
        .unwrap_or(val.len());
    if upto == 0 {
        return Ok(());
    }

    let name = &val.as_bytes()[..upto];
    if name.eq_ignore_ascii_case(b"gmt") || name.eq_ignore_ascii_case(b"ut") {
        parsed.set_offset(0)
    } else if name.eq_ignore_ascii_case(b"edt") {
        parsed.set_offset(-4 * 3600)
    } else if name.eq_ignore_ascii_case(b"est") || name.eq_ignore_ascii_case(b"cdt") {
        parsed.set_offset(-5 * 3600)
    } else if name.eq_ignore_ascii_case(b"cst") || name.eq_ignore_ascii_case(b"mdt") {
        parsed.set_offset(-6 * 3600)
    } else if name.eq_ignore_ascii_case(b"mst") || name.eq_ignore_ascii_case(b"pdt") {
        parsed.set_offset(-7 * 3600)
    } else if name.eq_ignore_ascii_case(b"pst") {
        parsed.set_offset(-8 * 3600)
    } else {
        Ok(())
    }
}

// Parses a date in Go's time format like 'Mon Jan _2 15:04:05 2006'.
pub fn parse(layout: &str, value: &str) -> ParseResult<DateTime<FixedOffset>> {
    let mut items = GoTimeFormatItems::parse(layout);
    let mut parsed = Parsed::new();
    let remainder = format::parse_and_remainder(
        &mut parsed,
        value,
        items
            .by_ref()
            .take_while(|i| !matches!(i, format::Item::Fixed(Fixed::TimezoneName))),
    )?;

    // The reason for splitting parsing procedure to two part is handling legacy
    // time zone names like EDT, EST etc. They are supported by chrono but not
    // exposed to us. As a workaround, we copied chrono's implementation to
    // `parse_legacy_timezone` function and whenever we encounter a
    // `Fixed::TimezoneName` we stop parsing with our regular parser,
    // parse timezone with `parse_legacy_timezone` and then continue parsing.
    if !remainder.is_empty() {
        parse_legacy_timezone(&mut parsed, remainder)?;

        format::parse(
            &mut parsed,
            remainder,
            iter::once(format::Item::Fixed(Fixed::TimezoneName)).chain(items),
        )?;
    }

    // Go's `time.Parse` allows missing years but chrono fails to parse them,
    // we're setting year field to `0` if year field is missing.
    if parsed.year.is_none()
        && parsed.year_div_100.is_none()
        && parsed.year_mod_100.is_none()
        && parsed.isoyear.is_none()
        && parsed.isoyear_div_100.is_none()
        && parsed.isoyear_mod_100.is_none()
        && parsed.timestamp.is_none()
    {
        parsed.set_year(0)?;
    }

    // Go's `time.Parse` allows missing time (hour, minute, second) but
    // chrono fails to parse them, we're setting time to `0` if time is missing.
    if parsed.hour_div_12.is_none()
        && parsed.hour_mod_12.is_none()
        && parsed.minute.is_none()
        && parsed.second.is_none()
    {
        parsed.set_hour(0)?;
        parsed.set_minute(0)?;
        parsed.set_second(0)?;
    }

    if parsed.offset.is_some() {
        parsed.to_datetime()
    } else {
        let naive = parsed.to_naive_datetime_with_offset(0)?;
        Ok(naive.and_utc().fixed_offset())
    }
}

// Formats a date in Go's time format like 'Mon Jan _2 15:04:05 2006'.
pub fn format<Tz: TimeZone>(date: DateTime<Tz>, fmt: &str) -> String
where
    Tz::Offset: fmt::Display,
{
    date.format_with_items(GoTimeFormatItems::format(fmt))
        .to_string()
}

#[cfg(test)]
mod tests {
    use chrono::{Datelike, Month, TimeZone, Timelike, Weekday};
    use chrono_tz::PST8PDT;

    use super::*;

    #[test]
    fn parses_durations() {
        // Test cases are copied from Go's `time.ParseDuration` tests:
        // https://github.com/golang/go/blob/8db131082d08e497fd8e9383d0ff7715e1bef478/src/time/time_test.go#L891-L951

        for (input, expected_dur) in [
            // simple
            ("0", Duration::zero()),
            ("5s", Duration::try_seconds(5).unwrap()),
            ("30s", Duration::try_seconds(30).unwrap()),
            ("1478s", Duration::try_seconds(1478).unwrap()),
            // sign
            ("-5s", -Duration::try_seconds(5).unwrap()),
            ("+5s", Duration::try_seconds(5).unwrap()),
            ("-0", Duration::zero()),
            ("+0", Duration::zero()),
            // decimal
            ("5.0s", Duration::try_seconds(5).unwrap()),
            (
                "5.6s",
                Duration::try_seconds(5).unwrap() + Duration::try_milliseconds(600).unwrap(),
            ),
            ("5.s", Duration::try_seconds(5).unwrap()),
            (".5s", Duration::try_milliseconds(500).unwrap()),
            ("1.0s", Duration::try_seconds(1).unwrap()),
            ("1.00s", Duration::try_seconds(1).unwrap()),
            (
                "1.004s",
                Duration::try_seconds(1).unwrap() + Duration::try_milliseconds(4).unwrap(),
            ),
            (
                "1.0040s",
                Duration::try_seconds(1).unwrap() + Duration::try_milliseconds(4).unwrap(),
            ),
            (
                "100.00100s",
                Duration::try_seconds(100).unwrap() + Duration::try_milliseconds(1).unwrap(),
            ),
            // different units
            ("10ns", Duration::nanoseconds(10)),
            ("11us", Duration::microseconds(11)),
            ("12µs", Duration::microseconds(12)), // U+00B5
            ("12μs", Duration::microseconds(12)), // U+03BC
            ("13ms", Duration::try_milliseconds(13).unwrap()),
            ("14s", Duration::try_seconds(14).unwrap()),
            ("15m", Duration::try_minutes(15).unwrap()),
            ("16h", Duration::try_hours(16).unwrap()),
            // composite durations
            (
                "3h30m",
                Duration::try_hours(3).unwrap() + Duration::try_minutes(30).unwrap(),
            ),
            (
                "10.5s4m",
                Duration::try_minutes(4).unwrap()
                    + Duration::try_seconds(10).unwrap()
                    + Duration::try_milliseconds(500).unwrap(),
            ),
            (
                "-2m3.4s",
                -(Duration::try_minutes(2).unwrap()
                    + Duration::try_seconds(3).unwrap()
                    + Duration::try_milliseconds(400).unwrap()),
            ),
            (
                "1h2m3s4ms5us6ns",
                Duration::try_hours(1).unwrap()
                    + Duration::try_minutes(2).unwrap()
                    + Duration::try_seconds(3).unwrap()
                    + Duration::try_milliseconds(4).unwrap()
                    + Duration::microseconds(5)
                    + Duration::nanoseconds(6),
            ),
            (
                "39h9m14.425s",
                Duration::try_hours(39).unwrap()
                    + Duration::try_minutes(9).unwrap()
                    + Duration::try_seconds(14).unwrap()
                    + Duration::try_milliseconds(425).unwrap(),
            ),
            // large value
            ("52763797000ns", Duration::nanoseconds(52763797000)),
            // more than 9 digits after decimal point, see https://golang.org/issue/6617
            ("0.3333333333333333333h", Duration::try_minutes(20).unwrap()),
            // 9007199254740993 = 1<<53+1 cannot be stored precisely in a float64
            ("9007199254740993ns", Duration::nanoseconds((1 << 53) + 1)),
            // largest duration that can be represented by int64 in nanoseconds
            ("9223372036854775807ns", Duration::nanoseconds(i64::MAX)),
            ("9223372036854775.807us", Duration::nanoseconds(i64::MAX)),
            (
                "9223372036s854ms775us807ns",
                Duration::nanoseconds(i64::MAX),
            ),
            ("-9223372036854775808ns", Duration::nanoseconds(i64::MIN)),
            ("-9223372036854775.808us", Duration::nanoseconds(i64::MIN)),
            (
                "-9223372036s854ms775us808ns",
                Duration::nanoseconds(i64::MIN),
            ),
            // largest negative value
            ("-9223372036854775808ns", Duration::nanoseconds(i64::MIN)),
            // largest negative round trip value, see https://golang.org/issue/48629
            ("-2562047h47m16.854775808s", Duration::nanoseconds(i64::MIN)),
            // huge string; issue 15011.
            (
                "0.100000000000000000000h",
                Duration::try_minutes(6).unwrap(),
            ),
            // This value tests the first overflow check in leadingFraction.
            (
                "0.830103483285477580700h",
                Duration::try_minutes(49).unwrap()
                    + Duration::try_seconds(48).unwrap()
                    + Duration::nanoseconds(372539827),
            ),
        ] {
            let dur = parse_duration(input).unwrap();
            assert_eq!(dur, expected_dur);
        }
    }

    #[test]
    fn parses_datetimes() {
        // Test cases are copied from Go's `time.Parse` tests:
        // https://github.com/golang/go/blob/e9b3ff15f40d6b258217b3467c662f816b078477/src/time/format_test.go#L266-L339

        struct ParseTest {
            name: String,
            format: String,
            value: String,
            has_tz: bool,       // contains a time zone
            has_wd: bool,       // contains a weekday
            year_sign: i32,     // sign of year, -1 indicates the year is not present in the format
            frac_digits: usize, // number of digits of fractional second
        }

        fn parse_test_case(
            name: &str,
            format: &str,
            value: &str,
            has_tz: bool,
            has_wd: bool,
            year_sign: i32,
            frac_digits: usize,
        ) -> ParseTest {
            ParseTest {
                name: name.to_string(),
                format: format.to_string(),
                value: value.to_string(),
                has_tz,
                has_wd,
                year_sign,
                frac_digits,
            }
        }

        fn check_time(time: DateTime<FixedOffset>, test_case: &ParseTest) {
            // The time should be Thu Feb  4 21:00:57 PST 2010
            if test_case.year_sign >= 0 {
                assert_eq!(test_case.year_sign * time.year(), 2010);
            }
            assert_eq!(time.month0(), Month::February as u32);
            assert_eq!(time.day(), 4);
            assert_eq!(time.hour(), 21);
            assert_eq!(time.minute(), 0);
            assert_eq!(time.second(), 57);

            let nanosec = "012345678"[..test_case.frac_digits].to_string()
                + &"000000000"[..9 - test_case.frac_digits];
            assert_eq!(time.nanosecond(), nanosec.parse::<u32>().unwrap());

            if test_case.has_tz {
                assert_eq!(time.timezone().local_minus_utc(), -28800);
            }

            if test_case.has_wd {
                assert_eq!(time.weekday(), Weekday::Thu);
            }
        }

        let test_cases = vec![
            parse_test_case(
                "ANSIC",
                ANSIC,
                "Thu Feb  4 21:00:57 2010",
                false,
                true,
                1,
                0,
            ),
            parse_test_case(
                "UnixDate",
                UNIX_DATE,
                "Thu Feb  4 21:00:57 PST 2010",
                true,
                true,
                1,
                0,
            ),
            parse_test_case(
                "RubyDate",
                RUBY_DATE,
                "Thu Feb 04 21:00:57 -0800 2010",
                true,
                true,
                1,
                0,
            ),
            parse_test_case(
                "RFC850",
                RFC850,
                "Thursday, 04-Feb-10 21:00:57 PST",
                true,
                true,
                1,
                0,
            ),
            parse_test_case(
                "RFC1123",
                RFC1123,
                "Thu, 04 Feb 2010 21:00:57 PST",
                true,
                true,
                1,
                0,
            ),
            // parse_test_case(
            //     "RFC1123",
            //     RFC1123,
            //     "Thu, 04 Feb 2010 22:00:57 PDT",
            //     true,
            //     true,
            //     1,
            //     0,
            // ),
            parse_test_case(
                "RFC1123Z",
                RFC1123Z,
                "Thu, 04 Feb 2010 21:00:57 -0800",
                true,
                true,
                1,
                0,
            ),
            parse_test_case(
                "RFC3339",
                RFC3339,
                "2010-02-04T21:00:57-08:00",
                true,
                false,
                1,
                0,
            ),
            // parse_test_case(
            //     "custom: \"2006-01-02 15:04:05-07\"",
            //     "2006-01-02 15:04:05-07",
            //     "2010-02-04 21:00:57-08",
            //     true,
            //     false,
            //     1,
            //     0,
            // ),
            // Optional fractional seconds.
            parse_test_case(
                "ANSIC",
                ANSIC,
                "Thu Feb  4 21:00:57.0 2010",
                false,
                true,
                1,
                1,
            ),
            parse_test_case(
                "UnixDate",
                UNIX_DATE,
                "Thu Feb  4 21:00:57.01 PST 2010",
                true,
                true,
                1,
                2,
            ),
            parse_test_case(
                "RubyDate",
                RUBY_DATE,
                "Thu Feb 04 21:00:57.012 -0800 2010",
                true,
                true,
                1,
                3,
            ),
            parse_test_case(
                "RFC850",
                RFC850,
                "Thursday, 04-Feb-10 21:00:57.0123 PST",
                true,
                true,
                1,
                4,
            ),
            parse_test_case(
                "RFC1123",
                RFC1123,
                "Thu, 04 Feb 2010 21:00:57.01234 PST",
                true,
                true,
                1,
                5,
            ),
            parse_test_case(
                "RFC1123Z",
                RFC1123Z,
                "Thu, 04 Feb 2010 21:00:57.01234 -0800",
                true,
                true,
                1,
                5,
            ),
            parse_test_case(
                "RFC3339",
                RFC3339,
                "2010-02-04T21:00:57.012345678-08:00",
                true,
                false,
                1,
                9,
            ),
            parse_test_case(
                "custom: \"2006-01-02 15:04:05\"",
                "2006-01-02 15:04:05",
                "2010-02-04 21:00:57.0",
                false,
                false,
                1,
                0,
            ),
            // Amount of white space should not matter.
            parse_test_case("ANSIC", ANSIC, "Thu Feb 4 21:00:57 2010", false, true, 1, 0),
            parse_test_case(
                "ANSIC",
                ANSIC,
                "Thu      Feb     4     21:00:57     2010",
                false,
                true,
                1,
                0,
            ),
            // Case should not matter
            parse_test_case("ANSIC", ANSIC, "THU FEB 4 21:00:57 2010", false, true, 1, 0),
            parse_test_case("ANSIC", ANSIC, "thu feb 4 21:00:57 2010", false, true, 1, 0),
            // Fractional seconds.
            parse_test_case(
                "millisecond:: dot separator",
                "Mon Jan _2 15:04:05.000 2006",
                "Thu Feb  4 21:00:57.012 2010",
                false,
                true,
                1,
                3,
            ),
            parse_test_case(
                "microsecond:: dot separator",
                "Mon Jan _2 15:04:05.000000 2006",
                "Thu Feb  4 21:00:57.012345 2010",
                false,
                true,
                1,
                6,
            ),
            parse_test_case(
                "nanosecond:: dot separator",
                "Mon Jan _2 15:04:05.000000000 2006",
                "Thu Feb  4 21:00:57.012345678 2010",
                false,
                true,
                1,
                9,
            ),
            parse_test_case(
                "millisecond:: comma separator",
                "Mon Jan _2 15:04:05,000 2006",
                "Thu Feb  4 21:00:57.012 2010",
                false,
                true,
                1,
                3,
            ),
            parse_test_case(
                "microsecond:: comma separator",
                "Mon Jan _2 15:04:05,000000 2006",
                "Thu Feb  4 21:00:57.012345 2010",
                false,
                true,
                1,
                6,
            ),
            parse_test_case(
                "nanosecond:: comma separator",
                "Mon Jan _2 15:04:05,000000000 2006",
                "Thu Feb  4 21:00:57.012345678 2010",
                false,
                true,
                1,
                9,
            ),
            // Leading zeros in other places should not be taken as fractional seconds.
            parse_test_case(
                "zero1",
                "2006.01.02.15.04.05.0",
                "2010.02.04.21.00.57.0",
                false,
                false,
                1,
                1,
            ),
            parse_test_case(
                "zero2",
                "2006.01.02.15.04.05.00",
                "2010.02.04.21.00.57.01",
                false,
                false,
                1,
                2,
            ),
            // Month and day names only match when not followed by a lower-case letter.
            // parse_test_case(
            //     "Janet",
            //     "Hi Janet, the Month is January: Jan _2 15:04:05 2006",
            //     "Hi Janet, the Month is February: Feb  4 21:00:57 2010",
            //     false,
            //     true,
            //     1,
            //     0,
            // ),
            // GMT with offset.
            // parse_test_case(
            //     "GMT-8",
            //     UNIX_DATE,
            //     "Fri Feb  5 05:00:57 GMT-8 2010",
            //     true,
            //     true,
            //     1,
            //     0,
            // ),
            // Accept any number of fractional second digits (including none) for .999...
            // In Go 1, .999... was completely ignored in the format, meaning the first two
            // cases would succeed, but the next four would not. Go 1.1 accepts all six.
            // decimal "." separator.
            parse_test_case(
                "",
                "2006-01-02 15:04:05.9999 -0700 MST",
                "2010-02-04 21:00:57 -0800 PST",
                true,
                false,
                1,
                0,
            ),
            parse_test_case(
                "",
                "2006-01-02 15:04:05.999999999 -0700 MST",
                "2010-02-04 21:00:57 -0800 PST",
                true,
                false,
                1,
                0,
            ),
            parse_test_case(
                "",
                "2006-01-02 15:04:05.9999 -0700 MST",
                "2010-02-04 21:00:57.0123 -0800 PST",
                true,
                false,
                1,
                4,
            ),
            parse_test_case(
                "",
                "2006-01-02 15:04:05.999999999 -0700 MST",
                "2010-02-04 21:00:57.0123 -0800 PST",
                true,
                false,
                1,
                4,
            ),
            parse_test_case(
                "",
                "2006-01-02 15:04:05.9999 -0700 MST",
                "2010-02-04 21:00:57.012345678 -0800 PST",
                true,
                false,
                1,
                9,
            ),
            parse_test_case(
                "",
                "2006-01-02 15:04:05.999999999 -0700 MST",
                "2010-02-04 21:00:57.012345678 -0800 PST",
                true,
                false,
                1,
                9,
            ),
            // comma "," separator.
            parse_test_case(
                "",
                "2006-01-02 15:04:05,9999 -0700 MST",
                "2010-02-04 21:00:57 -0800 PST",
                true,
                false,
                1,
                0,
            ),
            parse_test_case(
                "",
                "2006-01-02 15:04:05,999999999 -0700 MST",
                "2010-02-04 21:00:57 -0800 PST",
                true,
                false,
                1,
                0,
            ),
            parse_test_case(
                "",
                "2006-01-02 15:04:05,9999 -0700 MST",
                "2010-02-04 21:00:57.0123 -0800 PST",
                true,
                false,
                1,
                4,
            ),
            parse_test_case(
                "",
                "2006-01-02 15:04:05,999999999 -0700 MST",
                "2010-02-04 21:00:57.0123 -0800 PST",
                true,
                false,
                1,
                4,
            ),
            parse_test_case(
                "",
                "2006-01-02 15:04:05,9999 -0700 MST",
                "2010-02-04 21:00:57.012345678 -0800 PST",
                true,
                false,
                1,
                9,
            ),
            parse_test_case(
                "",
                "2006-01-02 15:04:05,999999999 -0700 MST",
                "2010-02-04 21:00:57.012345678 -0800 PST",
                true,
                false,
                1,
                9,
            ),
            // issue 4502.
            parse_test_case(
                "",
                STAMP_NANO,
                "Feb  4 21:00:57.012345678",
                false,
                false,
                -1,
                9,
            ),
            parse_test_case(
                "",
                "Jan _2 15:04:05.999",
                "Feb  4 21:00:57.012300000",
                false,
                false,
                -1,
                4,
            ),
            parse_test_case(
                "",
                "Jan _2 15:04:05.999",
                "Feb  4 21:00:57.012345678",
                false,
                false,
                -1,
                9,
            ),
            parse_test_case(
                "",
                "Jan _2 15:04:05.999999999",
                "Feb  4 21:00:57.0123",
                false,
                false,
                -1,
                4,
            ),
            parse_test_case(
                "",
                "Jan _2 15:04:05.999999999",
                "Feb  4 21:00:57.012345678",
                false,
                false,
                -1,
                9,
            ),
            // Day of year.
            parse_test_case(
                "",
                "2006-01-02 002 15:04:05",
                "2010-02-04 035 21:00:57",
                false,
                false,
                1,
                0,
            ),
            parse_test_case(
                "",
                "2006-01 002 15:04:05",
                "2010-02 035 21:00:57",
                false,
                false,
                1,
                0,
            ),
            parse_test_case(
                "",
                "2006-002 15:04:05",
                "2010-035 21:00:57",
                false,
                false,
                1,
                0,
            ),
            parse_test_case(
                "",
                "200600201 15:04:05",
                "201003502 21:00:57",
                false,
                false,
                1,
                0,
            ),
            // parse_test_case(
            //     "",
            //     "200600204 15:04:05",
            //     "201003504 21:00:57",
            //     false,
            //     false,
            //     1,
            //     0,
            // ),
        ];

        for tc in test_cases {
            std::println!("Test case {}", tc.name);
            let time = parse(&tc.format, &tc.value).unwrap();
            check_time(time, &tc);
        }
    }

    #[test]
    fn formats_datetimes() {
        // Test cases are copied from Go's `time.Format` tests:
        // https://github.com/golang/go/blob/e9b3ff15f40d6b258217b3467c662f816b078477/src/time/format_test.go#L144-L176

        struct FormatTest {
            name: String,
            format: String,
            result: String,
        }

        fn format_test_case(name: &str, format: &str, result: &str) -> FormatTest {
            FormatTest {
                name: name.to_string(),
                format: format.to_string(),
                result: result.to_string(),
            }
        }

        let test_cases = vec![
            format_test_case("ANSIC", ANSIC, "Wed Feb  4 21:00:57 2009"),
            format_test_case("UnixDate", UNIX_DATE, "Wed Feb  4 21:00:57 PST 2009"),
            format_test_case("RubyDate", RUBY_DATE, "Wed Feb 04 21:00:57 -0800 2009"),
            format_test_case("RFC822", RFC822, "04 Feb 09 21:00 PST"),
            format_test_case("RFC850", RFC850, "Wednesday, 04-Feb-09 21:00:57 PST"),
            format_test_case("RFC1123", RFC1123, "Wed, 04 Feb 2009 21:00:57 PST"),
            format_test_case("RFC1123Z", RFC1123Z, "Wed, 04 Feb 2009 21:00:57 -0800"),
            format_test_case("RFC3339", RFC3339, "2009-02-04T21:00:57-08:00"),
            // format_test_case(
            //     "RFC3339Nano",
            //     RFC3339_NANO,
            //     "2009-02-04T21:00:57.0123456-08:00",
            // ),
            format_test_case("Kitchen", KITCHEN, "9:00PM"),
            format_test_case("am/pm", "3pm", "9pm"),
            format_test_case("AM/PM", "3PM", "9PM"),
            format_test_case("two-digit year", "06 01 02", "09 02 04"),
            // Three-letter months and days must not be followed by lower-case letter.
            // format_test_case(
            //     "Janet",
            //     "Hi Janet, the Month is January",
            //     "Hi Janet, the Month is February",
            // ),
            // Time stamps, Fractional seconds.
            format_test_case("Stamp", STAMP, "Feb  4 21:00:57"),
            format_test_case("StampMilli", STAMP_MILLI, "Feb  4 21:00:57.012"),
            format_test_case("StampMicro", STAMP_MICRO, "Feb  4 21:00:57.012345"),
            format_test_case("StampNano", STAMP_NANO, "Feb  4 21:00:57.012345600"),
            format_test_case("DateTime", DATE_TIME, "2009-02-04 21:00:57"),
            format_test_case("DateOnly", DATE_ONLY, "2009-02-04"),
            format_test_case("TimeOnly", TIME_ONLY, "21:00:57"),
            format_test_case("YearDay", "Jan  2 002 __2 2", "Feb  4 035  35 4"),
            // format_test_case("Year", "2006 6 06 _6 __6 ___6", "2009 6 09 _6 __6 ___6"),
            // format_test_case("Month", "Jan January 1 01 _1", "Feb February 2 02 _2"),
            format_test_case("DayOfMonth", "2 02 _2 __2", "4 04  4  35"),
            format_test_case("DayOfWeek", "Mon Monday", "Wed Wednesday"),
            // format_test_case("Hour", "15 3 03 _3", "21 9 09 _9"),
            // format_test_case("Minute", "4 04 _4", "0 00 _0"),
            // format_test_case("Second", "5 05 _5", "57 57 _57"),
        ];

        // The numeric time represents Thu Feb  4 21:00:57.012345600 PST 2009
        let time = PST8PDT.timestamp_nanos(1233810057012345600);

        for tc in test_cases {
            std::println!("Test case {}", tc.name);
            let result = format(time, &tc.format);
            assert_eq!(result, tc.result);
        }
    }

    #[test]
    fn parses_date_only() {
        let time = parse("2006-01-02", "2020-02-02").unwrap();
        assert_eq!(time.year(), 2020);
        assert_eq!(time.month(), 2);
        assert_eq!(time.day(), 2);
    }

    const _LAYOUT: &str = "01/02 03:04:05PM '06 -0700"; // The reference time, in numerical order.
    const ANSIC: &str = "Mon Jan _2 15:04:05 2006";
    const UNIX_DATE: &str = "Mon Jan _2 15:04:05 MST 2006";
    const RUBY_DATE: &str = "Mon Jan 02 15:04:05 -0700 2006";
    const RFC822: &str = "02 Jan 06 15:04 MST";
    const _RFC822Z: &str = "02 Jan 06 15:04 -0700"; // RFC822 with numeric zone
    const RFC850: &str = "Monday, 02-Jan-06 15:04:05 MST";
    const RFC1123: &str = "Mon, 02 Jan 2006 15:04:05 MST";
    const RFC1123Z: &str = "Mon, 02 Jan 2006 15:04:05 -0700"; // RFC1123 with numeric zone
    const RFC3339: &str = "2006-01-02T15:04:05Z07:00";
    const _RFC3339_NANO: &str = "2006-01-02T15:04:05.999999999Z07:00";
    const KITCHEN: &str = "3:04PM";
    // Handy time stamps.
    const STAMP: &str = "Jan _2 15:04:05";
    const STAMP_MILLI: &str = "Jan _2 15:04:05.000";
    const STAMP_MICRO: &str = "Jan _2 15:04:05.000000";
    const STAMP_NANO: &str = "Jan _2 15:04:05.000000000";
    const DATE_TIME: &str = "2006-01-02 15:04:05";
    const DATE_ONLY: &str = "2006-01-02";
    const TIME_ONLY: &str = "15:04:05";
}
