#![allow(
clippy::arithmetic_side_effects,
clippy::unseparated_literal_suffix,
clippy::pattern_type_mismatch
)]
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")
}
}
}
}
pub fn parse_duration(mut s: &str) -> Result<Duration, ParseDurationError> {
let orig = s;
let neg = if s.starts_with('-') {
s = &s[1..];
true
} else if s.starts_with('+') {
s = &s[1..];
false
} else {
false
};
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() {
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();
let (mut v, rem) = leading_int(s)?;
s = rem;
let pre = previous_len != s.len();
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 {
return Err(ParseDurationError::InvalidDuration(orig.to_string()));
}
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, "μs" => MICROSECOND, "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) {
return Err(ParseDurationError::InvalidDuration(orig.to_string()));
}
v *= unit;
if f > 0 {
v += (f as f64 * (unit as f64 / scale)) as u64;
if v > 1 << 63 {
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) {
return Err(ParseDurationError::Overflow);
}
num = num * 10 + n;
if num > 1 << 63 {
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) {
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 GoTimeFormatItems<'_> {
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 {
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() {
Some('J') => {
token!("January", Fixed(LongMonthName));
token!("Jan", Fixed(ShortMonthName));
}
Some('M') => {
token!("Monday", Fixed(LongWeekdayName));
token!("Mon", Fixed(ShortWeekdayName));
token!("MST", Fixed(TimezoneName));
}
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));
}
Some('1') => {
use Numeric::*;
token!("15", Numeric(Hour, Pad::Zero));
token!("1", Numeric(Month, Pad::None));
}
Some('2') => {
use Numeric::*;
token!("2006", Numeric(Year, Pad::Zero));
token!("2", Numeric(Day, Pad::None));
}
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));
}
Some('P') => {
token!("PM", Fixed(UpperAmPm));
}
Some('p') => {
token!("pm", Fixed(LowerAmPm));
}
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("-"));
}
Some('Z') => {
token!("Z0700", Fixed(TimezoneOffsetZ));
token!("Z07:00", Fixed(TimezoneOffsetColonZ));
}
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
}
}
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(())
}
}
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))),
)?;
if !remainder.is_empty() {
parse_legacy_timezone(&mut parsed, remainder)?;
format::parse(
&mut parsed,
remainder,
iter::once(format::Item::Fixed(Fixed::TimezoneName)).chain(items),
)?;
}
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)?;
}
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())
}
}
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 {
#![allow(
clippy::unwrap_used,
clippy::unused_trait_names,
clippy::as_conversions
)] use chrono::{Datelike, Month, TimeZone, Timelike, Weekday};
use chrono_tz::PST8PDT;
use super::*;
#[test]
fn parses_durations() {
for (input, expected_dur) in [
("0", Duration::zero()),
("5s", Duration::try_seconds(5).unwrap()),
("30s", Duration::try_seconds(30).unwrap()),
("1478s", Duration::try_seconds(1478).unwrap()),
("-5s", -Duration::try_seconds(5).unwrap()),
("+5s", Duration::try_seconds(5).unwrap()),
("-0", Duration::zero()),
("+0", Duration::zero()),
("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(),
),
("10ns", Duration::nanoseconds(10)),
("11us", Duration::microseconds(11)),
("12µs", Duration::microseconds(12)), ("12μs", Duration::microseconds(12)), ("13ms", Duration::try_milliseconds(13).unwrap()),
("14s", Duration::try_seconds(14).unwrap()),
("15m", Duration::try_minutes(15).unwrap()),
("16h", Duration::try_hours(16).unwrap()),
(
"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(),
),
("52763797000ns", Duration::nanoseconds(52763797000)),
("0.3333333333333333333h", Duration::try_minutes(20).unwrap()),
("9007199254740993ns", Duration::nanoseconds((1 << 53) + 1)),
("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),
),
("-9223372036854775808ns", Duration::nanoseconds(i64::MIN)),
("-2562047h47m16.854775808s", Duration::nanoseconds(i64::MIN)),
(
"0.100000000000000000000h",
Duration::try_minutes(6).unwrap(),
),
(
"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() {
struct ParseTest {
name: String,
format: String,
value: String,
has_tz: bool, has_wd: bool, year_sign: i32, frac_digits: usize, }
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) {
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(
"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(
"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,
),
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,
),
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),
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,
),
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,
),
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,
),
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,
),
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,
),
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,
),
];
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() {
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("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"),
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("DayOfMonth", "2 02 _2 __2", "4 04 4 35"),
format_test_case("DayOfWeek", "Mon Monday", "Wed Wednesday"),
];
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"; 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"; 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"; 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";
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";
}