RPM build fix (reverted CI changes which will need to be un-reverted or made conditional) and vendor Rust dependencies to make builds much faster in any CI system.
This commit is contained in:
33
zeroidc/vendor/chrono/src/format/locales.rs
vendored
Normal file
33
zeroidc/vendor/chrono/src/format/locales.rs
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
use pure_rust_locales::{locale_match, Locale};
|
||||
|
||||
pub(crate) fn short_months(locale: Locale) -> &'static [&'static str] {
|
||||
locale_match!(locale => LC_TIME::ABMON)
|
||||
}
|
||||
|
||||
pub(crate) fn long_months(locale: Locale) -> &'static [&'static str] {
|
||||
locale_match!(locale => LC_TIME::MON)
|
||||
}
|
||||
|
||||
pub(crate) fn short_weekdays(locale: Locale) -> &'static [&'static str] {
|
||||
locale_match!(locale => LC_TIME::ABDAY)
|
||||
}
|
||||
|
||||
pub(crate) fn long_weekdays(locale: Locale) -> &'static [&'static str] {
|
||||
locale_match!(locale => LC_TIME::DAY)
|
||||
}
|
||||
|
||||
pub(crate) fn am_pm(locale: Locale) -> &'static [&'static str] {
|
||||
locale_match!(locale => LC_TIME::AM_PM)
|
||||
}
|
||||
|
||||
pub(crate) fn d_fmt(locale: Locale) -> &'static str {
|
||||
locale_match!(locale => LC_TIME::D_FMT)
|
||||
}
|
||||
|
||||
pub(crate) fn d_t_fmt(locale: Locale) -> &'static str {
|
||||
locale_match!(locale => LC_TIME::D_T_FMT)
|
||||
}
|
||||
|
||||
pub(crate) fn t_fmt(locale: Locale) -> &'static str {
|
||||
locale_match!(locale => LC_TIME::T_FMT)
|
||||
}
|
||||
938
zeroidc/vendor/chrono/src/format/mod.rs
vendored
Normal file
938
zeroidc/vendor/chrono/src/format/mod.rs
vendored
Normal file
@@ -0,0 +1,938 @@
|
||||
// This is a part of Chrono.
|
||||
// See README.md and LICENSE.txt for details.
|
||||
|
||||
//! Formatting (and parsing) utilities for date and time.
|
||||
//!
|
||||
//! This module provides the common types and routines to implement,
|
||||
//! for example, [`DateTime::format`](../struct.DateTime.html#method.format) or
|
||||
//! [`DateTime::parse_from_str`](../struct.DateTime.html#method.parse_from_str) methods.
|
||||
//! For most cases you should use these high-level interfaces.
|
||||
//!
|
||||
//! Internally the formatting and parsing shares the same abstract **formatting items**,
|
||||
//! which are just an [`Iterator`](https://doc.rust-lang.org/std/iter/trait.Iterator.html) of
|
||||
//! the [`Item`](./enum.Item.html) type.
|
||||
//! They are generated from more readable **format strings**;
|
||||
//! currently Chrono supports [one built-in syntax closely resembling
|
||||
//! C's `strftime` format](./strftime/index.html).
|
||||
|
||||
#![allow(ellipsis_inclusive_range_patterns)]
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::boxed::Box;
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::string::{String, ToString};
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
use core::borrow::Borrow;
|
||||
use core::fmt;
|
||||
use core::str::FromStr;
|
||||
#[cfg(any(feature = "std", test))]
|
||||
use std::error::Error;
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
use naive::{NaiveDate, NaiveTime};
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
use offset::{FixedOffset, Offset};
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
use {Datelike, Timelike};
|
||||
use {Month, ParseMonthError, ParseWeekdayError, Weekday};
|
||||
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
pub(crate) mod locales;
|
||||
|
||||
pub use self::parse::parse;
|
||||
pub use self::parsed::Parsed;
|
||||
pub use self::strftime::StrftimeItems;
|
||||
/// L10n locales.
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
pub use pure_rust_locales::Locale;
|
||||
|
||||
#[cfg(not(feature = "unstable-locales"))]
|
||||
#[derive(Debug)]
|
||||
struct Locale;
|
||||
|
||||
/// An uninhabited type used for `InternalNumeric` and `InternalFixed` below.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
enum Void {}
|
||||
|
||||
/// Padding characters for numeric items.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum Pad {
|
||||
/// No padding.
|
||||
None,
|
||||
/// Zero (`0`) padding.
|
||||
Zero,
|
||||
/// Space padding.
|
||||
Space,
|
||||
}
|
||||
|
||||
/// Numeric item types.
|
||||
/// They have associated formatting width (FW) and parsing width (PW).
|
||||
///
|
||||
/// The **formatting width** is the minimal width to be formatted.
|
||||
/// If the number is too short, and the padding is not [`Pad::None`](./enum.Pad.html#variant.None),
|
||||
/// then it is left-padded.
|
||||
/// If the number is too long or (in some cases) negative, it is printed as is.
|
||||
///
|
||||
/// The **parsing width** is the maximal width to be scanned.
|
||||
/// The parser only tries to consume from one to given number of digits (greedily).
|
||||
/// It also trims the preceding whitespace if any.
|
||||
/// It cannot parse the negative number, so some date and time cannot be formatted then
|
||||
/// parsed with the same formatting items.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub enum Numeric {
|
||||
/// Full Gregorian year (FW=4, PW=∞).
|
||||
/// May accept years before 1 BCE or after 9999 CE, given an initial sign.
|
||||
Year,
|
||||
/// Gregorian year divided by 100 (century number; FW=PW=2). Implies the non-negative year.
|
||||
YearDiv100,
|
||||
/// Gregorian year modulo 100 (FW=PW=2). Cannot be negative.
|
||||
YearMod100,
|
||||
/// Year in the ISO week date (FW=4, PW=∞).
|
||||
/// May accept years before 1 BCE or after 9999 CE, given an initial sign.
|
||||
IsoYear,
|
||||
/// Year in the ISO week date, divided by 100 (FW=PW=2). Implies the non-negative year.
|
||||
IsoYearDiv100,
|
||||
/// Year in the ISO week date, modulo 100 (FW=PW=2). Cannot be negative.
|
||||
IsoYearMod100,
|
||||
/// Month (FW=PW=2).
|
||||
Month,
|
||||
/// Day of the month (FW=PW=2).
|
||||
Day,
|
||||
/// Week number, where the week 1 starts at the first Sunday of January (FW=PW=2).
|
||||
WeekFromSun,
|
||||
/// Week number, where the week 1 starts at the first Monday of January (FW=PW=2).
|
||||
WeekFromMon,
|
||||
/// Week number in the ISO week date (FW=PW=2).
|
||||
IsoWeek,
|
||||
/// Day of the week, where Sunday = 0 and Saturday = 6 (FW=PW=1).
|
||||
NumDaysFromSun,
|
||||
/// Day of the week, where Monday = 1 and Sunday = 7 (FW=PW=1).
|
||||
WeekdayFromMon,
|
||||
/// Day of the year (FW=PW=3).
|
||||
Ordinal,
|
||||
/// Hour number in the 24-hour clocks (FW=PW=2).
|
||||
Hour,
|
||||
/// Hour number in the 12-hour clocks (FW=PW=2).
|
||||
Hour12,
|
||||
/// The number of minutes since the last whole hour (FW=PW=2).
|
||||
Minute,
|
||||
/// The number of seconds since the last whole minute (FW=PW=2).
|
||||
Second,
|
||||
/// The number of nanoseconds since the last whole second (FW=PW=9).
|
||||
/// Note that this is *not* left-aligned;
|
||||
/// see also [`Fixed::Nanosecond`](./enum.Fixed.html#variant.Nanosecond).
|
||||
Nanosecond,
|
||||
/// The number of non-leap seconds since the midnight UTC on January 1, 1970 (FW=1, PW=∞).
|
||||
/// For formatting, it assumes UTC upon the absence of time zone offset.
|
||||
Timestamp,
|
||||
|
||||
/// Internal uses only.
|
||||
///
|
||||
/// This item exists so that one can add additional internal-only formatting
|
||||
/// without breaking major compatibility (as enum variants cannot be selectively private).
|
||||
Internal(InternalNumeric),
|
||||
}
|
||||
|
||||
/// An opaque type representing numeric item types for internal uses only.
|
||||
pub struct InternalNumeric {
|
||||
_dummy: Void,
|
||||
}
|
||||
|
||||
impl Clone for InternalNumeric {
|
||||
fn clone(&self) -> Self {
|
||||
match self._dummy {}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for InternalNumeric {
|
||||
fn eq(&self, _other: &InternalNumeric) -> bool {
|
||||
match self._dummy {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for InternalNumeric {}
|
||||
|
||||
impl fmt::Debug for InternalNumeric {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "<InternalNumeric>")
|
||||
}
|
||||
}
|
||||
|
||||
/// Fixed-format item types.
|
||||
///
|
||||
/// They have their own rules of formatting and parsing.
|
||||
/// Otherwise noted, they print in the specified cases but parse case-insensitively.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub enum Fixed {
|
||||
/// Abbreviated month names.
|
||||
///
|
||||
/// Prints a three-letter-long name in the title case, reads the same name in any case.
|
||||
ShortMonthName,
|
||||
/// Full month names.
|
||||
///
|
||||
/// Prints a full name in the title case, reads either a short or full name in any case.
|
||||
LongMonthName,
|
||||
/// Abbreviated day of the week names.
|
||||
///
|
||||
/// Prints a three-letter-long name in the title case, reads the same name in any case.
|
||||
ShortWeekdayName,
|
||||
/// Full day of the week names.
|
||||
///
|
||||
/// Prints a full name in the title case, reads either a short or full name in any case.
|
||||
LongWeekdayName,
|
||||
/// AM/PM.
|
||||
///
|
||||
/// Prints in lower case, reads in any case.
|
||||
LowerAmPm,
|
||||
/// AM/PM.
|
||||
///
|
||||
/// Prints in upper case, reads in any case.
|
||||
UpperAmPm,
|
||||
/// An optional dot plus one or more digits for left-aligned nanoseconds.
|
||||
/// May print nothing, 3, 6 or 9 digits according to the available accuracy.
|
||||
/// See also [`Numeric::Nanosecond`](./enum.Numeric.html#variant.Nanosecond).
|
||||
Nanosecond,
|
||||
/// Same as [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 3.
|
||||
Nanosecond3,
|
||||
/// Same as [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 6.
|
||||
Nanosecond6,
|
||||
/// Same as [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 9.
|
||||
Nanosecond9,
|
||||
/// Timezone name.
|
||||
///
|
||||
/// It does not support parsing, its use in the parser is an immediate failure.
|
||||
TimezoneName,
|
||||
/// Offset from the local time to UTC (`+09:00` or `-04:00` or `+00:00`).
|
||||
///
|
||||
/// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace.
|
||||
/// The offset is limited from `-24:00` to `+24:00`,
|
||||
/// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range.
|
||||
TimezoneOffsetColon,
|
||||
/// Offset from the local time to UTC (`+09:00` or `-04:00` or `Z`).
|
||||
///
|
||||
/// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace,
|
||||
/// and `Z` can be either in upper case or in lower case.
|
||||
/// The offset is limited from `-24:00` to `+24:00`,
|
||||
/// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range.
|
||||
TimezoneOffsetColonZ,
|
||||
/// Same as [`TimezoneOffsetColon`](#variant.TimezoneOffsetColon) but prints no colon.
|
||||
/// Parsing allows an optional colon.
|
||||
TimezoneOffset,
|
||||
/// Same as [`TimezoneOffsetColonZ`](#variant.TimezoneOffsetColonZ) but prints no colon.
|
||||
/// Parsing allows an optional colon.
|
||||
TimezoneOffsetZ,
|
||||
/// RFC 2822 date and time syntax. Commonly used for email and MIME date and time.
|
||||
RFC2822,
|
||||
/// RFC 3339 & ISO 8601 date and time syntax.
|
||||
RFC3339,
|
||||
|
||||
/// Internal uses only.
|
||||
///
|
||||
/// This item exists so that one can add additional internal-only formatting
|
||||
/// without breaking major compatibility (as enum variants cannot be selectively private).
|
||||
Internal(InternalFixed),
|
||||
}
|
||||
|
||||
/// An opaque type representing fixed-format item types for internal uses only.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct InternalFixed {
|
||||
val: InternalInternal,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum InternalInternal {
|
||||
/// Same as [`TimezoneOffsetColonZ`](#variant.TimezoneOffsetColonZ), but
|
||||
/// allows missing minutes (per [ISO 8601][iso8601]).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If you try to use this for printing.
|
||||
///
|
||||
/// [iso8601]: https://en.wikipedia.org/wiki/ISO_8601#Time_offsets_from_UTC
|
||||
TimezoneOffsetPermissive,
|
||||
/// Same as [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 3 and there is no leading dot.
|
||||
Nanosecond3NoDot,
|
||||
/// Same as [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 6 and there is no leading dot.
|
||||
Nanosecond6NoDot,
|
||||
/// Same as [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 9 and there is no leading dot.
|
||||
Nanosecond9NoDot,
|
||||
}
|
||||
|
||||
/// A single formatting item. This is used for both formatting and parsing.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub enum Item<'a> {
|
||||
/// A literally printed and parsed text.
|
||||
Literal(&'a str),
|
||||
/// Same as `Literal` but with the string owned by the item.
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
OwnedLiteral(Box<str>),
|
||||
/// Whitespace. Prints literally but reads zero or more whitespace.
|
||||
Space(&'a str),
|
||||
/// Same as `Space` but with the string owned by the item.
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
OwnedSpace(Box<str>),
|
||||
/// Numeric item. Can be optionally padded to the maximal length (if any) when formatting;
|
||||
/// the parser simply ignores any padded whitespace and zeroes.
|
||||
Numeric(Numeric, Pad),
|
||||
/// Fixed-format item.
|
||||
Fixed(Fixed),
|
||||
/// Issues a formatting error. Used to signal an invalid format string.
|
||||
Error,
|
||||
}
|
||||
|
||||
macro_rules! lit {
|
||||
($x:expr) => {
|
||||
Item::Literal($x)
|
||||
};
|
||||
}
|
||||
macro_rules! sp {
|
||||
($x:expr) => {
|
||||
Item::Space($x)
|
||||
};
|
||||
}
|
||||
macro_rules! num {
|
||||
($x:ident) => {
|
||||
Item::Numeric(Numeric::$x, Pad::None)
|
||||
};
|
||||
}
|
||||
macro_rules! num0 {
|
||||
($x:ident) => {
|
||||
Item::Numeric(Numeric::$x, Pad::Zero)
|
||||
};
|
||||
}
|
||||
macro_rules! nums {
|
||||
($x:ident) => {
|
||||
Item::Numeric(Numeric::$x, Pad::Space)
|
||||
};
|
||||
}
|
||||
macro_rules! fix {
|
||||
($x:ident) => {
|
||||
Item::Fixed(Fixed::$x)
|
||||
};
|
||||
}
|
||||
macro_rules! internal_fix {
|
||||
($x:ident) => {
|
||||
Item::Fixed(Fixed::Internal(InternalFixed { val: InternalInternal::$x }))
|
||||
};
|
||||
}
|
||||
|
||||
/// An error from the `parse` function.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
|
||||
pub struct ParseError(ParseErrorKind);
|
||||
|
||||
/// The category of parse error
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
|
||||
enum ParseErrorKind {
|
||||
/// Given field is out of permitted range.
|
||||
OutOfRange,
|
||||
|
||||
/// There is no possible date and time value with given set of fields.
|
||||
///
|
||||
/// This does not include the out-of-range conditions, which are trivially invalid.
|
||||
/// It includes the case that there are one or more fields that are inconsistent to each other.
|
||||
Impossible,
|
||||
|
||||
/// Given set of fields is not enough to make a requested date and time value.
|
||||
///
|
||||
/// Note that there *may* be a case that given fields constrain the possible values so much
|
||||
/// that there is a unique possible value. Chrono only tries to be correct for
|
||||
/// most useful sets of fields however, as such constraint solving can be expensive.
|
||||
NotEnough,
|
||||
|
||||
/// The input string has some invalid character sequence for given formatting items.
|
||||
Invalid,
|
||||
|
||||
/// The input string has been prematurely ended.
|
||||
TooShort,
|
||||
|
||||
/// All formatting items have been read but there is a remaining input.
|
||||
TooLong,
|
||||
|
||||
/// There was an error on the formatting string, or there were non-supported formating items.
|
||||
BadFormat,
|
||||
}
|
||||
|
||||
/// Same as `Result<T, ParseError>`.
|
||||
pub type ParseResult<T> = Result<T, ParseError>;
|
||||
|
||||
impl fmt::Display for ParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self.0 {
|
||||
ParseErrorKind::OutOfRange => write!(f, "input is out of range"),
|
||||
ParseErrorKind::Impossible => write!(f, "no possible date and time matching input"),
|
||||
ParseErrorKind::NotEnough => write!(f, "input is not enough for unique date and time"),
|
||||
ParseErrorKind::Invalid => write!(f, "input contains invalid characters"),
|
||||
ParseErrorKind::TooShort => write!(f, "premature end of input"),
|
||||
ParseErrorKind::TooLong => write!(f, "trailing input"),
|
||||
ParseErrorKind::BadFormat => write!(f, "bad or unsupported format string"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "std", test))]
|
||||
impl Error for ParseError {
|
||||
#[allow(deprecated)]
|
||||
fn description(&self) -> &str {
|
||||
"parser error, see to_string() for details"
|
||||
}
|
||||
}
|
||||
|
||||
// to be used in this module and submodules
|
||||
const OUT_OF_RANGE: ParseError = ParseError(ParseErrorKind::OutOfRange);
|
||||
const IMPOSSIBLE: ParseError = ParseError(ParseErrorKind::Impossible);
|
||||
const NOT_ENOUGH: ParseError = ParseError(ParseErrorKind::NotEnough);
|
||||
const INVALID: ParseError = ParseError(ParseErrorKind::Invalid);
|
||||
const TOO_SHORT: ParseError = ParseError(ParseErrorKind::TooShort);
|
||||
const TOO_LONG: ParseError = ParseError(ParseErrorKind::TooLong);
|
||||
const BAD_FORMAT: ParseError = ParseError(ParseErrorKind::BadFormat);
|
||||
|
||||
/// Formats single formatting item
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
pub fn format_item<'a>(
|
||||
w: &mut fmt::Formatter,
|
||||
date: Option<&NaiveDate>,
|
||||
time: Option<&NaiveTime>,
|
||||
off: Option<&(String, FixedOffset)>,
|
||||
item: &Item<'a>,
|
||||
) -> fmt::Result {
|
||||
let mut result = String::new();
|
||||
format_inner(&mut result, date, time, off, item, None)?;
|
||||
w.pad(&result)
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
fn format_inner<'a>(
|
||||
result: &mut String,
|
||||
date: Option<&NaiveDate>,
|
||||
time: Option<&NaiveTime>,
|
||||
off: Option<&(String, FixedOffset)>,
|
||||
item: &Item<'a>,
|
||||
_locale: Option<Locale>,
|
||||
) -> fmt::Result {
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
let (short_months, long_months, short_weekdays, long_weekdays, am_pm, am_pm_lowercase) = {
|
||||
let locale = _locale.unwrap_or(Locale::POSIX);
|
||||
let am_pm = locales::am_pm(locale);
|
||||
(
|
||||
locales::short_months(locale),
|
||||
locales::long_months(locale),
|
||||
locales::short_weekdays(locale),
|
||||
locales::long_weekdays(locale),
|
||||
am_pm,
|
||||
&[am_pm[0].to_lowercase(), am_pm[1].to_lowercase()],
|
||||
)
|
||||
};
|
||||
#[cfg(not(feature = "unstable-locales"))]
|
||||
let (short_months, long_months, short_weekdays, long_weekdays, am_pm, am_pm_lowercase) = {
|
||||
(
|
||||
&["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
|
||||
&[
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December",
|
||||
],
|
||||
&["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
|
||||
&["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
|
||||
&["AM", "PM"],
|
||||
&["am", "pm"],
|
||||
)
|
||||
};
|
||||
|
||||
use core::fmt::Write;
|
||||
use div::{div_floor, mod_floor};
|
||||
|
||||
match *item {
|
||||
Item::Literal(s) | Item::Space(s) => result.push_str(s),
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => result.push_str(s),
|
||||
|
||||
Item::Numeric(ref spec, ref pad) => {
|
||||
use self::Numeric::*;
|
||||
|
||||
let week_from_sun = |d: &NaiveDate| {
|
||||
(d.ordinal() as i32 - d.weekday().num_days_from_sunday() as i32 + 7) / 7
|
||||
};
|
||||
let week_from_mon = |d: &NaiveDate| {
|
||||
(d.ordinal() as i32 - d.weekday().num_days_from_monday() as i32 + 7) / 7
|
||||
};
|
||||
|
||||
let (width, v) = match *spec {
|
||||
Year => (4, date.map(|d| i64::from(d.year()))),
|
||||
YearDiv100 => (2, date.map(|d| div_floor(i64::from(d.year()), 100))),
|
||||
YearMod100 => (2, date.map(|d| mod_floor(i64::from(d.year()), 100))),
|
||||
IsoYear => (4, date.map(|d| i64::from(d.iso_week().year()))),
|
||||
IsoYearDiv100 => (2, date.map(|d| div_floor(i64::from(d.iso_week().year()), 100))),
|
||||
IsoYearMod100 => (2, date.map(|d| mod_floor(i64::from(d.iso_week().year()), 100))),
|
||||
Month => (2, date.map(|d| i64::from(d.month()))),
|
||||
Day => (2, date.map(|d| i64::from(d.day()))),
|
||||
WeekFromSun => (2, date.map(|d| i64::from(week_from_sun(d)))),
|
||||
WeekFromMon => (2, date.map(|d| i64::from(week_from_mon(d)))),
|
||||
IsoWeek => (2, date.map(|d| i64::from(d.iso_week().week()))),
|
||||
NumDaysFromSun => (1, date.map(|d| i64::from(d.weekday().num_days_from_sunday()))),
|
||||
WeekdayFromMon => (1, date.map(|d| i64::from(d.weekday().number_from_monday()))),
|
||||
Ordinal => (3, date.map(|d| i64::from(d.ordinal()))),
|
||||
Hour => (2, time.map(|t| i64::from(t.hour()))),
|
||||
Hour12 => (2, time.map(|t| i64::from(t.hour12().1))),
|
||||
Minute => (2, time.map(|t| i64::from(t.minute()))),
|
||||
Second => (2, time.map(|t| i64::from(t.second() + t.nanosecond() / 1_000_000_000))),
|
||||
Nanosecond => (9, time.map(|t| i64::from(t.nanosecond() % 1_000_000_000))),
|
||||
Timestamp => (
|
||||
1,
|
||||
match (date, time, off) {
|
||||
(Some(d), Some(t), None) => Some(d.and_time(*t).timestamp()),
|
||||
(Some(d), Some(t), Some(&(_, off))) => {
|
||||
Some((d.and_time(*t) - off).timestamp())
|
||||
}
|
||||
(_, _, _) => None,
|
||||
},
|
||||
),
|
||||
|
||||
// for the future expansion
|
||||
Internal(ref int) => match int._dummy {},
|
||||
};
|
||||
|
||||
if let Some(v) = v {
|
||||
if (spec == &Year || spec == &IsoYear) && !(0 <= v && v < 10_000) {
|
||||
// non-four-digit years require an explicit sign as per ISO 8601
|
||||
match *pad {
|
||||
Pad::None => write!(result, "{:+}", v),
|
||||
Pad::Zero => write!(result, "{:+01$}", v, width + 1),
|
||||
Pad::Space => write!(result, "{:+1$}", v, width + 1),
|
||||
}
|
||||
} else {
|
||||
match *pad {
|
||||
Pad::None => write!(result, "{}", v),
|
||||
Pad::Zero => write!(result, "{:01$}", v, width),
|
||||
Pad::Space => write!(result, "{:1$}", v, width),
|
||||
}
|
||||
}?
|
||||
} else {
|
||||
return Err(fmt::Error); // insufficient arguments for given format
|
||||
}
|
||||
}
|
||||
|
||||
Item::Fixed(ref spec) => {
|
||||
use self::Fixed::*;
|
||||
|
||||
/// Prints an offset from UTC in the format of `+HHMM` or `+HH:MM`.
|
||||
/// `Z` instead of `+00[:]00` is allowed when `allow_zulu` is true.
|
||||
fn write_local_minus_utc(
|
||||
result: &mut String,
|
||||
off: FixedOffset,
|
||||
allow_zulu: bool,
|
||||
use_colon: bool,
|
||||
) -> fmt::Result {
|
||||
let off = off.local_minus_utc();
|
||||
if !allow_zulu || off != 0 {
|
||||
let (sign, off) = if off < 0 { ('-', -off) } else { ('+', off) };
|
||||
if use_colon {
|
||||
write!(result, "{}{:02}:{:02}", sign, off / 3600, off / 60 % 60)
|
||||
} else {
|
||||
write!(result, "{}{:02}{:02}", sign, off / 3600, off / 60 % 60)
|
||||
}
|
||||
} else {
|
||||
result.push_str("Z");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
let ret =
|
||||
match *spec {
|
||||
ShortMonthName => date.map(|d| {
|
||||
result.push_str(short_months[d.month0() as usize]);
|
||||
Ok(())
|
||||
}),
|
||||
LongMonthName => date.map(|d| {
|
||||
result.push_str(long_months[d.month0() as usize]);
|
||||
Ok(())
|
||||
}),
|
||||
ShortWeekdayName => date.map(|d| {
|
||||
result
|
||||
.push_str(short_weekdays[d.weekday().num_days_from_sunday() as usize]);
|
||||
Ok(())
|
||||
}),
|
||||
LongWeekdayName => date.map(|d| {
|
||||
result.push_str(long_weekdays[d.weekday().num_days_from_sunday() as usize]);
|
||||
Ok(())
|
||||
}),
|
||||
LowerAmPm => time.map(|t| {
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(useless_asref))]
|
||||
{
|
||||
result.push_str(if t.hour12().0 {
|
||||
am_pm_lowercase[1].as_ref()
|
||||
} else {
|
||||
am_pm_lowercase[0].as_ref()
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}),
|
||||
UpperAmPm => time.map(|t| {
|
||||
result.push_str(if t.hour12().0 { am_pm[1] } else { am_pm[0] });
|
||||
Ok(())
|
||||
}),
|
||||
Nanosecond => time.map(|t| {
|
||||
let nano = t.nanosecond() % 1_000_000_000;
|
||||
if nano == 0 {
|
||||
Ok(())
|
||||
} else if nano % 1_000_000 == 0 {
|
||||
write!(result, ".{:03}", nano / 1_000_000)
|
||||
} else if nano % 1_000 == 0 {
|
||||
write!(result, ".{:06}", nano / 1_000)
|
||||
} else {
|
||||
write!(result, ".{:09}", nano)
|
||||
}
|
||||
}),
|
||||
Nanosecond3 => time.map(|t| {
|
||||
let nano = t.nanosecond() % 1_000_000_000;
|
||||
write!(result, ".{:03}", nano / 1_000_000)
|
||||
}),
|
||||
Nanosecond6 => time.map(|t| {
|
||||
let nano = t.nanosecond() % 1_000_000_000;
|
||||
write!(result, ".{:06}", nano / 1_000)
|
||||
}),
|
||||
Nanosecond9 => time.map(|t| {
|
||||
let nano = t.nanosecond() % 1_000_000_000;
|
||||
write!(result, ".{:09}", nano)
|
||||
}),
|
||||
Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => time
|
||||
.map(|t| {
|
||||
let nano = t.nanosecond() % 1_000_000_000;
|
||||
write!(result, "{:03}", nano / 1_000_000)
|
||||
}),
|
||||
Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => time
|
||||
.map(|t| {
|
||||
let nano = t.nanosecond() % 1_000_000_000;
|
||||
write!(result, "{:06}", nano / 1_000)
|
||||
}),
|
||||
Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => time
|
||||
.map(|t| {
|
||||
let nano = t.nanosecond() % 1_000_000_000;
|
||||
write!(result, "{:09}", nano)
|
||||
}),
|
||||
TimezoneName => off.map(|&(ref name, _)| {
|
||||
result.push_str(name);
|
||||
Ok(())
|
||||
}),
|
||||
TimezoneOffsetColon => {
|
||||
off.map(|&(_, off)| write_local_minus_utc(result, off, false, true))
|
||||
}
|
||||
TimezoneOffsetColonZ => {
|
||||
off.map(|&(_, off)| write_local_minus_utc(result, off, true, true))
|
||||
}
|
||||
TimezoneOffset => {
|
||||
off.map(|&(_, off)| write_local_minus_utc(result, off, false, false))
|
||||
}
|
||||
TimezoneOffsetZ => {
|
||||
off.map(|&(_, off)| write_local_minus_utc(result, off, true, false))
|
||||
}
|
||||
Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => {
|
||||
panic!("Do not try to write %#z it is undefined")
|
||||
}
|
||||
RFC2822 =>
|
||||
// same as `%a, %d %b %Y %H:%M:%S %z`
|
||||
{
|
||||
if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) {
|
||||
let sec = t.second() + t.nanosecond() / 1_000_000_000;
|
||||
write!(
|
||||
result,
|
||||
"{}, {:02} {} {:04} {:02}:{:02}:{:02} ",
|
||||
short_weekdays[d.weekday().num_days_from_sunday() as usize],
|
||||
d.day(),
|
||||
short_months[d.month0() as usize],
|
||||
d.year(),
|
||||
t.hour(),
|
||||
t.minute(),
|
||||
sec
|
||||
)?;
|
||||
Some(write_local_minus_utc(result, off, false, false))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
RFC3339 =>
|
||||
// same as `%Y-%m-%dT%H:%M:%S%.f%:z`
|
||||
{
|
||||
if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) {
|
||||
// reuse `Debug` impls which already print ISO 8601 format.
|
||||
// this is faster in this way.
|
||||
write!(result, "{:?}T{:?}", d, t)?;
|
||||
Some(write_local_minus_utc(result, off, false, true))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match ret {
|
||||
Some(ret) => ret?,
|
||||
None => return Err(fmt::Error), // insufficient arguments for given format
|
||||
}
|
||||
}
|
||||
|
||||
Item::Error => return Err(fmt::Error),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tries to format given arguments with given formatting items.
|
||||
/// Internally used by `DelayedFormat`.
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
pub fn format<'a, I, B>(
|
||||
w: &mut fmt::Formatter,
|
||||
date: Option<&NaiveDate>,
|
||||
time: Option<&NaiveTime>,
|
||||
off: Option<&(String, FixedOffset)>,
|
||||
items: I,
|
||||
) -> fmt::Result
|
||||
where
|
||||
I: Iterator<Item = B> + Clone,
|
||||
B: Borrow<Item<'a>>,
|
||||
{
|
||||
let mut result = String::new();
|
||||
for item in items {
|
||||
format_inner(&mut result, date, time, off, item.borrow(), None)?;
|
||||
}
|
||||
w.pad(&result)
|
||||
}
|
||||
|
||||
mod parsed;
|
||||
|
||||
// due to the size of parsing routines, they are in separate modules.
|
||||
mod parse;
|
||||
mod scan;
|
||||
|
||||
pub mod strftime;
|
||||
|
||||
/// A *temporary* object which can be used as an argument to `format!` or others.
|
||||
/// This is normally constructed via `format` methods of each date and time type.
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
#[derive(Debug)]
|
||||
pub struct DelayedFormat<I> {
|
||||
/// The date view, if any.
|
||||
date: Option<NaiveDate>,
|
||||
/// The time view, if any.
|
||||
time: Option<NaiveTime>,
|
||||
/// The name and local-to-UTC difference for the offset (timezone), if any.
|
||||
off: Option<(String, FixedOffset)>,
|
||||
/// An iterator returning formatting items.
|
||||
items: I,
|
||||
/// Locale used for text.
|
||||
locale: Option<Locale>,
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> DelayedFormat<I> {
|
||||
/// Makes a new `DelayedFormat` value out of local date and time.
|
||||
pub fn new(date: Option<NaiveDate>, time: Option<NaiveTime>, items: I) -> DelayedFormat<I> {
|
||||
DelayedFormat { date: date, time: time, off: None, items: items, locale: None }
|
||||
}
|
||||
|
||||
/// Makes a new `DelayedFormat` value out of local date and time and UTC offset.
|
||||
pub fn new_with_offset<Off>(
|
||||
date: Option<NaiveDate>,
|
||||
time: Option<NaiveTime>,
|
||||
offset: &Off,
|
||||
items: I,
|
||||
) -> DelayedFormat<I>
|
||||
where
|
||||
Off: Offset + fmt::Display,
|
||||
{
|
||||
let name_and_diff = (offset.to_string(), offset.fix());
|
||||
DelayedFormat {
|
||||
date: date,
|
||||
time: time,
|
||||
off: Some(name_and_diff),
|
||||
items: items,
|
||||
locale: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a new `DelayedFormat` value out of local date and time and locale.
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
pub fn new_with_locale(
|
||||
date: Option<NaiveDate>,
|
||||
time: Option<NaiveTime>,
|
||||
items: I,
|
||||
locale: Locale,
|
||||
) -> DelayedFormat<I> {
|
||||
DelayedFormat { date: date, time: time, off: None, items: items, locale: Some(locale) }
|
||||
}
|
||||
|
||||
/// Makes a new `DelayedFormat` value out of local date and time, UTC offset and locale.
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
pub fn new_with_offset_and_locale<Off>(
|
||||
date: Option<NaiveDate>,
|
||||
time: Option<NaiveTime>,
|
||||
offset: &Off,
|
||||
items: I,
|
||||
locale: Locale,
|
||||
) -> DelayedFormat<I>
|
||||
where
|
||||
Off: Offset + fmt::Display,
|
||||
{
|
||||
let name_and_diff = (offset.to_string(), offset.fix());
|
||||
DelayedFormat {
|
||||
date: date,
|
||||
time: time,
|
||||
off: Some(name_and_diff),
|
||||
items: items,
|
||||
locale: Some(locale),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> fmt::Display for DelayedFormat<I> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
{
|
||||
if let Some(locale) = self.locale {
|
||||
return format_localized(
|
||||
f,
|
||||
self.date.as_ref(),
|
||||
self.time.as_ref(),
|
||||
self.off.as_ref(),
|
||||
self.items.clone(),
|
||||
locale,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
format(f, self.date.as_ref(), self.time.as_ref(), self.off.as_ref(), self.items.clone())
|
||||
}
|
||||
}
|
||||
|
||||
// this implementation is here only because we need some private code from `scan`
|
||||
|
||||
/// Parsing a `str` into a `Weekday` uses the format [`%W`](./format/strftime/index.html).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ~~~~
|
||||
/// use chrono::Weekday;
|
||||
///
|
||||
/// assert_eq!("Sunday".parse::<Weekday>(), Ok(Weekday::Sun));
|
||||
/// assert!("any day".parse::<Weekday>().is_err());
|
||||
/// ~~~~
|
||||
///
|
||||
/// The parsing is case-insensitive.
|
||||
///
|
||||
/// ~~~~
|
||||
/// # use chrono::Weekday;
|
||||
/// assert_eq!("mON".parse::<Weekday>(), Ok(Weekday::Mon));
|
||||
/// ~~~~
|
||||
///
|
||||
/// Only the shortest form (e.g. `sun`) and the longest form (e.g. `sunday`) is accepted.
|
||||
///
|
||||
/// ~~~~
|
||||
/// # use chrono::Weekday;
|
||||
/// assert!("thurs".parse::<Weekday>().is_err());
|
||||
/// ~~~~
|
||||
impl FromStr for Weekday {
|
||||
type Err = ParseWeekdayError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if let Ok(("", w)) = scan::short_or_long_weekday(s) {
|
||||
Ok(w)
|
||||
} else {
|
||||
Err(ParseWeekdayError { _dummy: () })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Formats single formatting item
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
pub fn format_item_localized<'a>(
|
||||
w: &mut fmt::Formatter,
|
||||
date: Option<&NaiveDate>,
|
||||
time: Option<&NaiveTime>,
|
||||
off: Option<&(String, FixedOffset)>,
|
||||
item: &Item<'a>,
|
||||
locale: Locale,
|
||||
) -> fmt::Result {
|
||||
let mut result = String::new();
|
||||
format_inner(&mut result, date, time, off, item, Some(locale))?;
|
||||
w.pad(&result)
|
||||
}
|
||||
|
||||
/// Tries to format given arguments with given formatting items.
|
||||
/// Internally used by `DelayedFormat`.
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
pub fn format_localized<'a, I, B>(
|
||||
w: &mut fmt::Formatter,
|
||||
date: Option<&NaiveDate>,
|
||||
time: Option<&NaiveTime>,
|
||||
off: Option<&(String, FixedOffset)>,
|
||||
items: I,
|
||||
locale: Locale,
|
||||
) -> fmt::Result
|
||||
where
|
||||
I: Iterator<Item = B> + Clone,
|
||||
B: Borrow<Item<'a>>,
|
||||
{
|
||||
let mut result = String::new();
|
||||
for item in items {
|
||||
format_inner(&mut result, date, time, off, item.borrow(), Some(locale))?;
|
||||
}
|
||||
w.pad(&result)
|
||||
}
|
||||
|
||||
/// Parsing a `str` into a `Month` uses the format [`%W`](./format/strftime/index.html).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ~~~~
|
||||
/// use chrono::Month;
|
||||
///
|
||||
/// assert_eq!("January".parse::<Month>(), Ok(Month::January));
|
||||
/// assert!("any day".parse::<Month>().is_err());
|
||||
/// ~~~~
|
||||
///
|
||||
/// The parsing is case-insensitive.
|
||||
///
|
||||
/// ~~~~
|
||||
/// # use chrono::Month;
|
||||
/// assert_eq!("fEbruARy".parse::<Month>(), Ok(Month::February));
|
||||
/// ~~~~
|
||||
///
|
||||
/// Only the shortest form (e.g. `jan`) and the longest form (e.g. `january`) is accepted.
|
||||
///
|
||||
/// ~~~~
|
||||
/// # use chrono::Month;
|
||||
/// assert!("septem".parse::<Month>().is_err());
|
||||
/// assert!("Augustin".parse::<Month>().is_err());
|
||||
/// ~~~~
|
||||
impl FromStr for Month {
|
||||
type Err = ParseMonthError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if let Ok(("", w)) = scan::short_or_long_month0(s) {
|
||||
match w {
|
||||
0 => Ok(Month::January),
|
||||
1 => Ok(Month::February),
|
||||
2 => Ok(Month::March),
|
||||
3 => Ok(Month::April),
|
||||
4 => Ok(Month::May),
|
||||
5 => Ok(Month::June),
|
||||
6 => Ok(Month::July),
|
||||
7 => Ok(Month::August),
|
||||
8 => Ok(Month::September),
|
||||
9 => Ok(Month::October),
|
||||
10 => Ok(Month::November),
|
||||
11 => Ok(Month::December),
|
||||
_ => Err(ParseMonthError { _dummy: () }),
|
||||
}
|
||||
} else {
|
||||
Err(ParseMonthError { _dummy: () })
|
||||
}
|
||||
}
|
||||
}
|
||||
934
zeroidc/vendor/chrono/src/format/parse.rs
vendored
Normal file
934
zeroidc/vendor/chrono/src/format/parse.rs
vendored
Normal file
@@ -0,0 +1,934 @@
|
||||
// This is a part of Chrono.
|
||||
// Portions copyright (c) 2015, John Nagle.
|
||||
// See README.md and LICENSE.txt for details.
|
||||
|
||||
//! Date and time parsing routines.
|
||||
|
||||
#![allow(deprecated)]
|
||||
|
||||
use core::borrow::Borrow;
|
||||
use core::str;
|
||||
use core::usize;
|
||||
|
||||
use super::scan;
|
||||
use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad, Parsed};
|
||||
use super::{ParseError, ParseErrorKind, ParseResult};
|
||||
use super::{BAD_FORMAT, INVALID, NOT_ENOUGH, OUT_OF_RANGE, TOO_LONG, TOO_SHORT};
|
||||
use {DateTime, FixedOffset, Weekday};
|
||||
|
||||
fn set_weekday_with_num_days_from_sunday(p: &mut Parsed, v: i64) -> ParseResult<()> {
|
||||
p.set_weekday(match v {
|
||||
0 => Weekday::Sun,
|
||||
1 => Weekday::Mon,
|
||||
2 => Weekday::Tue,
|
||||
3 => Weekday::Wed,
|
||||
4 => Weekday::Thu,
|
||||
5 => Weekday::Fri,
|
||||
6 => Weekday::Sat,
|
||||
_ => return Err(OUT_OF_RANGE),
|
||||
})
|
||||
}
|
||||
|
||||
fn set_weekday_with_number_from_monday(p: &mut Parsed, v: i64) -> ParseResult<()> {
|
||||
p.set_weekday(match v {
|
||||
1 => Weekday::Mon,
|
||||
2 => Weekday::Tue,
|
||||
3 => Weekday::Wed,
|
||||
4 => Weekday::Thu,
|
||||
5 => Weekday::Fri,
|
||||
6 => Weekday::Sat,
|
||||
7 => Weekday::Sun,
|
||||
_ => return Err(OUT_OF_RANGE),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a str, ())> {
|
||||
macro_rules! try_consume {
|
||||
($e:expr) => {{
|
||||
let (s_, v) = $e?;
|
||||
s = s_;
|
||||
v
|
||||
}};
|
||||
}
|
||||
|
||||
// an adapted RFC 2822 syntax from Section 3.3 and 4.3:
|
||||
//
|
||||
// date-time = [ day-of-week "," ] date 1*S time *S
|
||||
// day-of-week = *S day-name *S
|
||||
// day-name = "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun"
|
||||
// date = day month year
|
||||
// day = *S 1*2DIGIT *S
|
||||
// month = 1*S month-name 1*S
|
||||
// month-name = "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" /
|
||||
// "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec"
|
||||
// year = *S 2*DIGIT *S
|
||||
// time = time-of-day 1*S zone
|
||||
// time-of-day = hour ":" minute [ ":" second ]
|
||||
// hour = *S 2DIGIT *S
|
||||
// minute = *S 2DIGIT *S
|
||||
// second = *S 2DIGIT *S
|
||||
// zone = ( "+" / "-" ) 4DIGIT /
|
||||
// "UT" / "GMT" / ; same as +0000
|
||||
// "EST" / "CST" / "MST" / "PST" / ; same as -0500 to -0800
|
||||
// "EDT" / "CDT" / "MDT" / "PDT" / ; same as -0400 to -0700
|
||||
// 1*(%d65-90 / %d97-122) ; same as -0000
|
||||
//
|
||||
// some notes:
|
||||
//
|
||||
// - quoted characters can be in any mixture of lower and upper cases.
|
||||
//
|
||||
// - we do not recognize a folding white space (FWS) or comment (CFWS).
|
||||
// for our purposes, instead, we accept any sequence of Unicode
|
||||
// white space characters (denoted here to `S`). any actual RFC 2822
|
||||
// parser is expected to parse FWS and/or CFWS themselves and replace
|
||||
// it with a single SP (`%x20`); this is legitimate.
|
||||
//
|
||||
// - two-digit year < 50 should be interpreted by adding 2000.
|
||||
// two-digit year >= 50 or three-digit year should be interpreted
|
||||
// by adding 1900. note that four-or-more-digit years less than 1000
|
||||
// are *never* affected by this rule.
|
||||
//
|
||||
// - mismatching day-of-week is always an error, which is consistent to
|
||||
// Chrono's own rules.
|
||||
//
|
||||
// - zones can range from `-9959` to `+9959`, but `FixedOffset` does not
|
||||
// support offsets larger than 24 hours. this is not *that* problematic
|
||||
// since we do not directly go to a `DateTime` so one can recover
|
||||
// the offset information from `Parsed` anyway.
|
||||
|
||||
s = s.trim_left();
|
||||
|
||||
if let Ok((s_, weekday)) = scan::short_weekday(s) {
|
||||
if !s_.starts_with(',') {
|
||||
return Err(INVALID);
|
||||
}
|
||||
s = &s_[1..];
|
||||
parsed.set_weekday(weekday)?;
|
||||
}
|
||||
|
||||
s = s.trim_left();
|
||||
parsed.set_day(try_consume!(scan::number(s, 1, 2)))?;
|
||||
s = scan::space(s)?; // mandatory
|
||||
parsed.set_month(1 + i64::from(try_consume!(scan::short_month0(s))))?;
|
||||
s = scan::space(s)?; // mandatory
|
||||
|
||||
// distinguish two- and three-digit years from four-digit years
|
||||
let prevlen = s.len();
|
||||
let mut year = try_consume!(scan::number(s, 2, usize::MAX));
|
||||
let yearlen = prevlen - s.len();
|
||||
match (yearlen, year) {
|
||||
(2, 0...49) => {
|
||||
year += 2000;
|
||||
} // 47 -> 2047, 05 -> 2005
|
||||
(2, 50...99) => {
|
||||
year += 1900;
|
||||
} // 79 -> 1979
|
||||
(3, _) => {
|
||||
year += 1900;
|
||||
} // 112 -> 2012, 009 -> 1909
|
||||
(_, _) => {} // 1987 -> 1987, 0654 -> 0654
|
||||
}
|
||||
parsed.set_year(year)?;
|
||||
|
||||
s = scan::space(s)?; // mandatory
|
||||
parsed.set_hour(try_consume!(scan::number(s, 2, 2)))?;
|
||||
s = scan::char(s.trim_left(), b':')?.trim_left(); // *S ":" *S
|
||||
parsed.set_minute(try_consume!(scan::number(s, 2, 2)))?;
|
||||
if let Ok(s_) = scan::char(s.trim_left(), b':') {
|
||||
// [ ":" *S 2DIGIT ]
|
||||
parsed.set_second(try_consume!(scan::number(s_, 2, 2)))?;
|
||||
}
|
||||
|
||||
s = scan::space(s)?; // mandatory
|
||||
if let Some(offset) = try_consume!(scan::timezone_offset_2822(s)) {
|
||||
// only set the offset when it is definitely known (i.e. not `-0000`)
|
||||
parsed.set_offset(i64::from(offset))?;
|
||||
}
|
||||
|
||||
Ok((s, ()))
|
||||
}
|
||||
|
||||
fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a str, ())> {
|
||||
macro_rules! try_consume {
|
||||
($e:expr) => {{
|
||||
let (s_, v) = $e?;
|
||||
s = s_;
|
||||
v
|
||||
}};
|
||||
}
|
||||
|
||||
// an adapted RFC 3339 syntax from Section 5.6:
|
||||
//
|
||||
// date-fullyear = 4DIGIT
|
||||
// date-month = 2DIGIT ; 01-12
|
||||
// date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on month/year
|
||||
// time-hour = 2DIGIT ; 00-23
|
||||
// time-minute = 2DIGIT ; 00-59
|
||||
// time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second rules
|
||||
// time-secfrac = "." 1*DIGIT
|
||||
// time-numoffset = ("+" / "-") time-hour ":" time-minute
|
||||
// time-offset = "Z" / time-numoffset
|
||||
// partial-time = time-hour ":" time-minute ":" time-second [time-secfrac]
|
||||
// full-date = date-fullyear "-" date-month "-" date-mday
|
||||
// full-time = partial-time time-offset
|
||||
// date-time = full-date "T" full-time
|
||||
//
|
||||
// some notes:
|
||||
//
|
||||
// - quoted characters can be in any mixture of lower and upper cases.
|
||||
//
|
||||
// - it may accept any number of fractional digits for seconds.
|
||||
// for Chrono, this means that we should skip digits past first 9 digits.
|
||||
//
|
||||
// - unlike RFC 2822, the valid offset ranges from -23:59 to +23:59.
|
||||
// note that this restriction is unique to RFC 3339 and not ISO 8601.
|
||||
// since this is not a typical Chrono behavior, we check it earlier.
|
||||
|
||||
parsed.set_year(try_consume!(scan::number(s, 4, 4)))?;
|
||||
s = scan::char(s, b'-')?;
|
||||
parsed.set_month(try_consume!(scan::number(s, 2, 2)))?;
|
||||
s = scan::char(s, b'-')?;
|
||||
parsed.set_day(try_consume!(scan::number(s, 2, 2)))?;
|
||||
|
||||
s = match s.as_bytes().first() {
|
||||
Some(&b't') | Some(&b'T') => &s[1..],
|
||||
Some(_) => return Err(INVALID),
|
||||
None => return Err(TOO_SHORT),
|
||||
};
|
||||
|
||||
parsed.set_hour(try_consume!(scan::number(s, 2, 2)))?;
|
||||
s = scan::char(s, b':')?;
|
||||
parsed.set_minute(try_consume!(scan::number(s, 2, 2)))?;
|
||||
s = scan::char(s, b':')?;
|
||||
parsed.set_second(try_consume!(scan::number(s, 2, 2)))?;
|
||||
if s.starts_with('.') {
|
||||
let nanosecond = try_consume!(scan::nanosecond(&s[1..]));
|
||||
parsed.set_nanosecond(nanosecond)?;
|
||||
}
|
||||
|
||||
let offset = try_consume!(scan::timezone_offset_zulu(s, |s| scan::char(s, b':')));
|
||||
if offset <= -86_400 || offset >= 86_400 {
|
||||
return Err(OUT_OF_RANGE);
|
||||
}
|
||||
parsed.set_offset(i64::from(offset))?;
|
||||
|
||||
Ok((s, ()))
|
||||
}
|
||||
|
||||
/// Tries to parse given string into `parsed` with given formatting items.
|
||||
/// Returns `Ok` when the entire string has been parsed (otherwise `parsed` should not be used).
|
||||
/// There should be no trailing string after parsing;
|
||||
/// use a stray [`Item::Space`](./enum.Item.html#variant.Space) to trim whitespaces.
|
||||
///
|
||||
/// This particular date and time parser is:
|
||||
///
|
||||
/// - Greedy. It will consume the longest possible prefix.
|
||||
/// For example, `April` is always consumed entirely when the long month name is requested;
|
||||
/// it equally accepts `Apr`, but prefers the longer prefix in this case.
|
||||
///
|
||||
/// - Padding-agnostic (for numeric items).
|
||||
/// The [`Pad`](./enum.Pad.html) field is completely ignored,
|
||||
/// so one can prepend any number of whitespace then any number of zeroes before numbers.
|
||||
///
|
||||
/// - (Still) obeying the intrinsic parsing width. This allows, for example, parsing `HHMMSS`.
|
||||
pub fn parse<'a, I, B>(parsed: &mut Parsed, s: &str, items: I) -> ParseResult<()>
|
||||
where
|
||||
I: Iterator<Item = B>,
|
||||
B: Borrow<Item<'a>>,
|
||||
{
|
||||
parse_internal(parsed, s, items).map(|_| ()).map_err(|(_s, e)| e)
|
||||
}
|
||||
|
||||
fn parse_internal<'a, 'b, I, B>(
|
||||
parsed: &mut Parsed,
|
||||
mut s: &'b str,
|
||||
items: I,
|
||||
) -> Result<&'b str, (&'b str, ParseError)>
|
||||
where
|
||||
I: Iterator<Item = B>,
|
||||
B: Borrow<Item<'a>>,
|
||||
{
|
||||
macro_rules! try_consume {
|
||||
($e:expr) => {{
|
||||
match $e {
|
||||
Ok((s_, v)) => {
|
||||
s = s_;
|
||||
v
|
||||
}
|
||||
Err(e) => return Err((s, e)),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
for item in items {
|
||||
match *item.borrow() {
|
||||
Item::Literal(prefix) => {
|
||||
if s.len() < prefix.len() {
|
||||
return Err((s, TOO_SHORT));
|
||||
}
|
||||
if !s.starts_with(prefix) {
|
||||
return Err((s, INVALID));
|
||||
}
|
||||
s = &s[prefix.len()..];
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
Item::OwnedLiteral(ref prefix) => {
|
||||
if s.len() < prefix.len() {
|
||||
return Err((s, TOO_SHORT));
|
||||
}
|
||||
if !s.starts_with(&prefix[..]) {
|
||||
return Err((s, INVALID));
|
||||
}
|
||||
s = &s[prefix.len()..];
|
||||
}
|
||||
|
||||
Item::Space(_) => {
|
||||
s = s.trim_left();
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
Item::OwnedSpace(_) => {
|
||||
s = s.trim_left();
|
||||
}
|
||||
|
||||
Item::Numeric(ref spec, ref _pad) => {
|
||||
use super::Numeric::*;
|
||||
type Setter = fn(&mut Parsed, i64) -> ParseResult<()>;
|
||||
|
||||
let (width, signed, set): (usize, bool, Setter) = match *spec {
|
||||
Year => (4, true, Parsed::set_year),
|
||||
YearDiv100 => (2, false, Parsed::set_year_div_100),
|
||||
YearMod100 => (2, false, Parsed::set_year_mod_100),
|
||||
IsoYear => (4, true, Parsed::set_isoyear),
|
||||
IsoYearDiv100 => (2, false, Parsed::set_isoyear_div_100),
|
||||
IsoYearMod100 => (2, false, Parsed::set_isoyear_mod_100),
|
||||
Month => (2, false, Parsed::set_month),
|
||||
Day => (2, false, Parsed::set_day),
|
||||
WeekFromSun => (2, false, Parsed::set_week_from_sun),
|
||||
WeekFromMon => (2, false, Parsed::set_week_from_mon),
|
||||
IsoWeek => (2, false, Parsed::set_isoweek),
|
||||
NumDaysFromSun => (1, false, set_weekday_with_num_days_from_sunday),
|
||||
WeekdayFromMon => (1, false, set_weekday_with_number_from_monday),
|
||||
Ordinal => (3, false, Parsed::set_ordinal),
|
||||
Hour => (2, false, Parsed::set_hour),
|
||||
Hour12 => (2, false, Parsed::set_hour12),
|
||||
Minute => (2, false, Parsed::set_minute),
|
||||
Second => (2, false, Parsed::set_second),
|
||||
Nanosecond => (9, false, Parsed::set_nanosecond),
|
||||
Timestamp => (usize::MAX, false, Parsed::set_timestamp),
|
||||
|
||||
// for the future expansion
|
||||
Internal(ref int) => match int._dummy {},
|
||||
};
|
||||
|
||||
s = s.trim_left();
|
||||
let v = if signed {
|
||||
if s.starts_with('-') {
|
||||
let v = try_consume!(scan::number(&s[1..], 1, usize::MAX));
|
||||
0i64.checked_sub(v).ok_or((s, OUT_OF_RANGE))?
|
||||
} else if s.starts_with('+') {
|
||||
try_consume!(scan::number(&s[1..], 1, usize::MAX))
|
||||
} else {
|
||||
// if there is no explicit sign, we respect the original `width`
|
||||
try_consume!(scan::number(s, 1, width))
|
||||
}
|
||||
} else {
|
||||
try_consume!(scan::number(s, 1, width))
|
||||
};
|
||||
set(parsed, v).map_err(|e| (s, e))?;
|
||||
}
|
||||
|
||||
Item::Fixed(ref spec) => {
|
||||
use super::Fixed::*;
|
||||
|
||||
match spec {
|
||||
&ShortMonthName => {
|
||||
let month0 = try_consume!(scan::short_month0(s));
|
||||
parsed.set_month(i64::from(month0) + 1).map_err(|e| (s, e))?;
|
||||
}
|
||||
|
||||
&LongMonthName => {
|
||||
let month0 = try_consume!(scan::short_or_long_month0(s));
|
||||
parsed.set_month(i64::from(month0) + 1).map_err(|e| (s, e))?;
|
||||
}
|
||||
|
||||
&ShortWeekdayName => {
|
||||
let weekday = try_consume!(scan::short_weekday(s));
|
||||
parsed.set_weekday(weekday).map_err(|e| (s, e))?;
|
||||
}
|
||||
|
||||
&LongWeekdayName => {
|
||||
let weekday = try_consume!(scan::short_or_long_weekday(s));
|
||||
parsed.set_weekday(weekday).map_err(|e| (s, e))?;
|
||||
}
|
||||
|
||||
&LowerAmPm | &UpperAmPm => {
|
||||
if s.len() < 2 {
|
||||
return Err((s, TOO_SHORT));
|
||||
}
|
||||
let ampm = match (s.as_bytes()[0] | 32, s.as_bytes()[1] | 32) {
|
||||
(b'a', b'm') => false,
|
||||
(b'p', b'm') => true,
|
||||
_ => return Err((s, INVALID)),
|
||||
};
|
||||
parsed.set_ampm(ampm).map_err(|e| (s, e))?;
|
||||
s = &s[2..];
|
||||
}
|
||||
|
||||
&Nanosecond | &Nanosecond3 | &Nanosecond6 | &Nanosecond9 => {
|
||||
if s.starts_with('.') {
|
||||
let nano = try_consume!(scan::nanosecond(&s[1..]));
|
||||
parsed.set_nanosecond(nano).map_err(|e| (s, e))?;
|
||||
}
|
||||
}
|
||||
|
||||
&Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => {
|
||||
if s.len() < 3 {
|
||||
return Err((s, TOO_SHORT));
|
||||
}
|
||||
let nano = try_consume!(scan::nanosecond_fixed(s, 3));
|
||||
parsed.set_nanosecond(nano).map_err(|e| (s, e))?;
|
||||
}
|
||||
|
||||
&Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => {
|
||||
if s.len() < 6 {
|
||||
return Err((s, TOO_SHORT));
|
||||
}
|
||||
let nano = try_consume!(scan::nanosecond_fixed(s, 6));
|
||||
parsed.set_nanosecond(nano).map_err(|e| (s, e))?;
|
||||
}
|
||||
|
||||
&Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => {
|
||||
if s.len() < 9 {
|
||||
return Err((s, TOO_SHORT));
|
||||
}
|
||||
let nano = try_consume!(scan::nanosecond_fixed(s, 9));
|
||||
parsed.set_nanosecond(nano).map_err(|e| (s, e))?;
|
||||
}
|
||||
|
||||
&TimezoneName => {
|
||||
try_consume!(scan::timezone_name_skip(s));
|
||||
}
|
||||
|
||||
&TimezoneOffsetColon | &TimezoneOffset => {
|
||||
let offset = try_consume!(scan::timezone_offset(
|
||||
s.trim_left(),
|
||||
scan::colon_or_space
|
||||
));
|
||||
parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?;
|
||||
}
|
||||
|
||||
&TimezoneOffsetColonZ | &TimezoneOffsetZ => {
|
||||
let offset = try_consume!(scan::timezone_offset_zulu(
|
||||
s.trim_left(),
|
||||
scan::colon_or_space
|
||||
));
|
||||
parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?;
|
||||
}
|
||||
&Internal(InternalFixed {
|
||||
val: InternalInternal::TimezoneOffsetPermissive,
|
||||
}) => {
|
||||
let offset = try_consume!(scan::timezone_offset_permissive(
|
||||
s.trim_left(),
|
||||
scan::colon_or_space
|
||||
));
|
||||
parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?;
|
||||
}
|
||||
|
||||
&RFC2822 => try_consume!(parse_rfc2822(parsed, s)),
|
||||
&RFC3339 => try_consume!(parse_rfc3339(parsed, s)),
|
||||
}
|
||||
}
|
||||
|
||||
Item::Error => {
|
||||
return Err((s, BAD_FORMAT));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if there are trailling chars, it is an error
|
||||
if !s.is_empty() {
|
||||
Err((s, TOO_LONG))
|
||||
} else {
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl str::FromStr for DateTime<FixedOffset> {
|
||||
type Err = ParseError;
|
||||
|
||||
fn from_str(s: &str) -> ParseResult<DateTime<FixedOffset>> {
|
||||
const DATE_ITEMS: &'static [Item<'static>] = &[
|
||||
Item::Numeric(Numeric::Year, Pad::Zero),
|
||||
Item::Space(""),
|
||||
Item::Literal("-"),
|
||||
Item::Numeric(Numeric::Month, Pad::Zero),
|
||||
Item::Space(""),
|
||||
Item::Literal("-"),
|
||||
Item::Numeric(Numeric::Day, Pad::Zero),
|
||||
];
|
||||
const TIME_ITEMS: &'static [Item<'static>] = &[
|
||||
Item::Numeric(Numeric::Hour, Pad::Zero),
|
||||
Item::Space(""),
|
||||
Item::Literal(":"),
|
||||
Item::Numeric(Numeric::Minute, Pad::Zero),
|
||||
Item::Space(""),
|
||||
Item::Literal(":"),
|
||||
Item::Numeric(Numeric::Second, Pad::Zero),
|
||||
Item::Fixed(Fixed::Nanosecond),
|
||||
Item::Space(""),
|
||||
Item::Fixed(Fixed::TimezoneOffsetZ),
|
||||
Item::Space(""),
|
||||
];
|
||||
|
||||
let mut parsed = Parsed::new();
|
||||
match parse_internal(&mut parsed, s, DATE_ITEMS.iter()) {
|
||||
Err((remainder, e)) if e.0 == ParseErrorKind::TooLong => {
|
||||
if remainder.starts_with('T') || remainder.starts_with(' ') {
|
||||
parse(&mut parsed, &remainder[1..], TIME_ITEMS.iter())?;
|
||||
} else {
|
||||
Err(INVALID)?;
|
||||
}
|
||||
}
|
||||
Err((_s, e)) => Err(e)?,
|
||||
Ok(_) => Err(NOT_ENOUGH)?,
|
||||
};
|
||||
parsed.to_datetime()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_parse() {
|
||||
use super::IMPOSSIBLE;
|
||||
use super::*;
|
||||
|
||||
// workaround for Rust issue #22255
|
||||
fn parse_all(s: &str, items: &[Item]) -> ParseResult<Parsed> {
|
||||
let mut parsed = Parsed::new();
|
||||
parse(&mut parsed, s, items.iter())?;
|
||||
Ok(parsed)
|
||||
}
|
||||
|
||||
macro_rules! check {
|
||||
($fmt:expr, $items:expr; $err:tt) => (
|
||||
assert_eq!(parse_all($fmt, &$items), Err($err))
|
||||
);
|
||||
($fmt:expr, $items:expr; $($k:ident: $v:expr),*) => (#[allow(unused_mut)] {
|
||||
let mut expected = Parsed::new();
|
||||
$(expected.$k = Some($v);)*
|
||||
assert_eq!(parse_all($fmt, &$items), Ok(expected))
|
||||
});
|
||||
}
|
||||
|
||||
// empty string
|
||||
check!("", []; );
|
||||
check!(" ", []; TOO_LONG);
|
||||
check!("a", []; TOO_LONG);
|
||||
|
||||
// whitespaces
|
||||
check!("", [sp!("")]; );
|
||||
check!(" ", [sp!("")]; );
|
||||
check!("\t", [sp!("")]; );
|
||||
check!(" \n\r \n", [sp!("")]; );
|
||||
check!("a", [sp!("")]; TOO_LONG);
|
||||
|
||||
// literal
|
||||
check!("", [lit!("a")]; TOO_SHORT);
|
||||
check!(" ", [lit!("a")]; INVALID);
|
||||
check!("a", [lit!("a")]; );
|
||||
check!("aa", [lit!("a")]; TOO_LONG);
|
||||
check!("A", [lit!("a")]; INVALID);
|
||||
check!("xy", [lit!("xy")]; );
|
||||
check!("xy", [lit!("x"), lit!("y")]; );
|
||||
check!("x y", [lit!("x"), lit!("y")]; INVALID);
|
||||
check!("xy", [lit!("x"), sp!(""), lit!("y")]; );
|
||||
check!("x y", [lit!("x"), sp!(""), lit!("y")]; );
|
||||
|
||||
// numeric
|
||||
check!("1987", [num!(Year)]; year: 1987);
|
||||
check!("1987 ", [num!(Year)]; TOO_LONG);
|
||||
check!("0x12", [num!(Year)]; TOO_LONG); // `0` is parsed
|
||||
check!("x123", [num!(Year)]; INVALID);
|
||||
check!("2015", [num!(Year)]; year: 2015);
|
||||
check!("0000", [num!(Year)]; year: 0);
|
||||
check!("9999", [num!(Year)]; year: 9999);
|
||||
check!(" \t987", [num!(Year)]; year: 987);
|
||||
check!("5", [num!(Year)]; year: 5);
|
||||
check!("5\0", [num!(Year)]; TOO_LONG);
|
||||
check!("\05", [num!(Year)]; INVALID);
|
||||
check!("", [num!(Year)]; TOO_SHORT);
|
||||
check!("12345", [num!(Year), lit!("5")]; year: 1234);
|
||||
check!("12345", [nums!(Year), lit!("5")]; year: 1234);
|
||||
check!("12345", [num0!(Year), lit!("5")]; year: 1234);
|
||||
check!("12341234", [num!(Year), num!(Year)]; year: 1234);
|
||||
check!("1234 1234", [num!(Year), num!(Year)]; year: 1234);
|
||||
check!("1234 1235", [num!(Year), num!(Year)]; IMPOSSIBLE);
|
||||
check!("1234 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID);
|
||||
check!("1234x1234", [num!(Year), lit!("x"), num!(Year)]; year: 1234);
|
||||
check!("1234xx1234", [num!(Year), lit!("x"), num!(Year)]; INVALID);
|
||||
check!("1234 x 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID);
|
||||
|
||||
// signed numeric
|
||||
check!("-42", [num!(Year)]; year: -42);
|
||||
check!("+42", [num!(Year)]; year: 42);
|
||||
check!("-0042", [num!(Year)]; year: -42);
|
||||
check!("+0042", [num!(Year)]; year: 42);
|
||||
check!("-42195", [num!(Year)]; year: -42195);
|
||||
check!("+42195", [num!(Year)]; year: 42195);
|
||||
check!(" -42195", [num!(Year)]; year: -42195);
|
||||
check!(" +42195", [num!(Year)]; year: 42195);
|
||||
check!(" - 42", [num!(Year)]; INVALID);
|
||||
check!(" + 42", [num!(Year)]; INVALID);
|
||||
check!("-", [num!(Year)]; TOO_SHORT);
|
||||
check!("+", [num!(Year)]; TOO_SHORT);
|
||||
|
||||
// unsigned numeric
|
||||
check!("345", [num!(Ordinal)]; ordinal: 345);
|
||||
check!("+345", [num!(Ordinal)]; INVALID);
|
||||
check!("-345", [num!(Ordinal)]; INVALID);
|
||||
check!(" 345", [num!(Ordinal)]; ordinal: 345);
|
||||
check!(" +345", [num!(Ordinal)]; INVALID);
|
||||
check!(" -345", [num!(Ordinal)]; INVALID);
|
||||
|
||||
// various numeric fields
|
||||
check!("1234 5678",
|
||||
[num!(Year), num!(IsoYear)];
|
||||
year: 1234, isoyear: 5678);
|
||||
check!("12 34 56 78",
|
||||
[num!(YearDiv100), num!(YearMod100), num!(IsoYearDiv100), num!(IsoYearMod100)];
|
||||
year_div_100: 12, year_mod_100: 34, isoyear_div_100: 56, isoyear_mod_100: 78);
|
||||
check!("1 2 3 4 5 6",
|
||||
[num!(Month), num!(Day), num!(WeekFromSun), num!(WeekFromMon), num!(IsoWeek),
|
||||
num!(NumDaysFromSun)];
|
||||
month: 1, day: 2, week_from_sun: 3, week_from_mon: 4, isoweek: 5, weekday: Weekday::Sat);
|
||||
check!("7 89 01",
|
||||
[num!(WeekdayFromMon), num!(Ordinal), num!(Hour12)];
|
||||
weekday: Weekday::Sun, ordinal: 89, hour_mod_12: 1);
|
||||
check!("23 45 6 78901234 567890123",
|
||||
[num!(Hour), num!(Minute), num!(Second), num!(Nanosecond), num!(Timestamp)];
|
||||
hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6, nanosecond: 78_901_234,
|
||||
timestamp: 567_890_123);
|
||||
|
||||
// fixed: month and weekday names
|
||||
check!("apr", [fix!(ShortMonthName)]; month: 4);
|
||||
check!("Apr", [fix!(ShortMonthName)]; month: 4);
|
||||
check!("APR", [fix!(ShortMonthName)]; month: 4);
|
||||
check!("ApR", [fix!(ShortMonthName)]; month: 4);
|
||||
check!("April", [fix!(ShortMonthName)]; TOO_LONG); // `Apr` is parsed
|
||||
check!("A", [fix!(ShortMonthName)]; TOO_SHORT);
|
||||
check!("Sol", [fix!(ShortMonthName)]; INVALID);
|
||||
check!("Apr", [fix!(LongMonthName)]; month: 4);
|
||||
check!("Apri", [fix!(LongMonthName)]; TOO_LONG); // `Apr` is parsed
|
||||
check!("April", [fix!(LongMonthName)]; month: 4);
|
||||
check!("Aprill", [fix!(LongMonthName)]; TOO_LONG);
|
||||
check!("Aprill", [fix!(LongMonthName), lit!("l")]; month: 4);
|
||||
check!("Aprl", [fix!(LongMonthName), lit!("l")]; month: 4);
|
||||
check!("April", [fix!(LongMonthName), lit!("il")]; TOO_SHORT); // do not backtrack
|
||||
check!("thu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu);
|
||||
check!("Thu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu);
|
||||
check!("THU", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu);
|
||||
check!("tHu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu);
|
||||
check!("Thursday", [fix!(ShortWeekdayName)]; TOO_LONG); // `Thu` is parsed
|
||||
check!("T", [fix!(ShortWeekdayName)]; TOO_SHORT);
|
||||
check!("The", [fix!(ShortWeekdayName)]; INVALID);
|
||||
check!("Nop", [fix!(ShortWeekdayName)]; INVALID);
|
||||
check!("Thu", [fix!(LongWeekdayName)]; weekday: Weekday::Thu);
|
||||
check!("Thur", [fix!(LongWeekdayName)]; TOO_LONG); // `Thu` is parsed
|
||||
check!("Thurs", [fix!(LongWeekdayName)]; TOO_LONG); // ditto
|
||||
check!("Thursday", [fix!(LongWeekdayName)]; weekday: Weekday::Thu);
|
||||
check!("Thursdays", [fix!(LongWeekdayName)]; TOO_LONG);
|
||||
check!("Thursdays", [fix!(LongWeekdayName), lit!("s")]; weekday: Weekday::Thu);
|
||||
check!("Thus", [fix!(LongWeekdayName), lit!("s")]; weekday: Weekday::Thu);
|
||||
check!("Thursday", [fix!(LongWeekdayName), lit!("rsday")]; TOO_SHORT); // do not backtrack
|
||||
|
||||
// fixed: am/pm
|
||||
check!("am", [fix!(LowerAmPm)]; hour_div_12: 0);
|
||||
check!("pm", [fix!(LowerAmPm)]; hour_div_12: 1);
|
||||
check!("AM", [fix!(LowerAmPm)]; hour_div_12: 0);
|
||||
check!("PM", [fix!(LowerAmPm)]; hour_div_12: 1);
|
||||
check!("am", [fix!(UpperAmPm)]; hour_div_12: 0);
|
||||
check!("pm", [fix!(UpperAmPm)]; hour_div_12: 1);
|
||||
check!("AM", [fix!(UpperAmPm)]; hour_div_12: 0);
|
||||
check!("PM", [fix!(UpperAmPm)]; hour_div_12: 1);
|
||||
check!("Am", [fix!(LowerAmPm)]; hour_div_12: 0);
|
||||
check!(" Am", [fix!(LowerAmPm)]; INVALID);
|
||||
check!("ame", [fix!(LowerAmPm)]; TOO_LONG); // `am` is parsed
|
||||
check!("a", [fix!(LowerAmPm)]; TOO_SHORT);
|
||||
check!("p", [fix!(LowerAmPm)]; TOO_SHORT);
|
||||
check!("x", [fix!(LowerAmPm)]; TOO_SHORT);
|
||||
check!("xx", [fix!(LowerAmPm)]; INVALID);
|
||||
check!("", [fix!(LowerAmPm)]; TOO_SHORT);
|
||||
|
||||
// fixed: dot plus nanoseconds
|
||||
check!("", [fix!(Nanosecond)]; ); // no field set, but not an error
|
||||
check!("4", [fix!(Nanosecond)]; TOO_LONG); // never consumes `4`
|
||||
check!("4", [fix!(Nanosecond), num!(Second)]; second: 4);
|
||||
check!(".0", [fix!(Nanosecond)]; nanosecond: 0);
|
||||
check!(".4", [fix!(Nanosecond)]; nanosecond: 400_000_000);
|
||||
check!(".42", [fix!(Nanosecond)]; nanosecond: 420_000_000);
|
||||
check!(".421", [fix!(Nanosecond)]; nanosecond: 421_000_000);
|
||||
check!(".42195", [fix!(Nanosecond)]; nanosecond: 421_950_000);
|
||||
check!(".421950803", [fix!(Nanosecond)]; nanosecond: 421_950_803);
|
||||
check!(".421950803547", [fix!(Nanosecond)]; nanosecond: 421_950_803);
|
||||
check!(".000000003547", [fix!(Nanosecond)]; nanosecond: 3);
|
||||
check!(".000000000547", [fix!(Nanosecond)]; nanosecond: 0);
|
||||
check!(".", [fix!(Nanosecond)]; TOO_SHORT);
|
||||
check!(".4x", [fix!(Nanosecond)]; TOO_LONG);
|
||||
check!(". 4", [fix!(Nanosecond)]; INVALID);
|
||||
check!(" .4", [fix!(Nanosecond)]; TOO_LONG); // no automatic trimming
|
||||
|
||||
// fixed: nanoseconds without the dot
|
||||
check!("", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT);
|
||||
check!("0", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT);
|
||||
check!("4", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT);
|
||||
check!("42", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT);
|
||||
check!("421", [internal_fix!(Nanosecond3NoDot)]; nanosecond: 421_000_000);
|
||||
check!("42143", [internal_fix!(Nanosecond3NoDot), num!(Second)]; nanosecond: 421_000_000, second: 43);
|
||||
check!("42195", [internal_fix!(Nanosecond3NoDot)]; TOO_LONG);
|
||||
check!("4x", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT);
|
||||
check!(" 4", [internal_fix!(Nanosecond3NoDot)]; INVALID);
|
||||
check!(".421", [internal_fix!(Nanosecond3NoDot)]; INVALID);
|
||||
|
||||
check!("", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT);
|
||||
check!("0", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT);
|
||||
check!("42195", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT);
|
||||
check!("421950", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 421_950_000);
|
||||
check!("000003", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 3000);
|
||||
check!("000000", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 0);
|
||||
check!("4x", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT);
|
||||
check!(" 4", [internal_fix!(Nanosecond6NoDot)]; INVALID);
|
||||
check!(".42100", [internal_fix!(Nanosecond6NoDot)]; INVALID);
|
||||
|
||||
check!("", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT);
|
||||
check!("42195", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT);
|
||||
check!("421950803", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 421_950_803);
|
||||
check!("000000003", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 3);
|
||||
check!("42195080354", [internal_fix!(Nanosecond9NoDot), num!(Second)]; nanosecond: 421_950_803, second: 54); // don't skip digits that come after the 9
|
||||
check!("421950803547", [internal_fix!(Nanosecond9NoDot)]; TOO_LONG);
|
||||
check!("000000000", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 0);
|
||||
check!("00000000x", [internal_fix!(Nanosecond9NoDot)]; INVALID);
|
||||
check!(" 4", [internal_fix!(Nanosecond9NoDot)]; INVALID);
|
||||
check!(".42100000", [internal_fix!(Nanosecond9NoDot)]; INVALID);
|
||||
|
||||
// fixed: timezone offsets
|
||||
check!("+00:00", [fix!(TimezoneOffset)]; offset: 0);
|
||||
check!("-00:00", [fix!(TimezoneOffset)]; offset: 0);
|
||||
check!("+00:01", [fix!(TimezoneOffset)]; offset: 60);
|
||||
check!("-00:01", [fix!(TimezoneOffset)]; offset: -60);
|
||||
check!("+00:30", [fix!(TimezoneOffset)]; offset: 30 * 60);
|
||||
check!("-00:30", [fix!(TimezoneOffset)]; offset: -30 * 60);
|
||||
check!("+04:56", [fix!(TimezoneOffset)]; offset: 296 * 60);
|
||||
check!("-04:56", [fix!(TimezoneOffset)]; offset: -296 * 60);
|
||||
check!("+24:00", [fix!(TimezoneOffset)]; offset: 24 * 60 * 60);
|
||||
check!("-24:00", [fix!(TimezoneOffset)]; offset: -24 * 60 * 60);
|
||||
check!("+99:59", [fix!(TimezoneOffset)]; offset: (100 * 60 - 1) * 60);
|
||||
check!("-99:59", [fix!(TimezoneOffset)]; offset: -(100 * 60 - 1) * 60);
|
||||
check!("+00:59", [fix!(TimezoneOffset)]; offset: 59 * 60);
|
||||
check!("+00:60", [fix!(TimezoneOffset)]; OUT_OF_RANGE);
|
||||
check!("+00:99", [fix!(TimezoneOffset)]; OUT_OF_RANGE);
|
||||
check!("#12:34", [fix!(TimezoneOffset)]; INVALID);
|
||||
check!("12:34", [fix!(TimezoneOffset)]; INVALID);
|
||||
check!("+12:34 ", [fix!(TimezoneOffset)]; TOO_LONG);
|
||||
check!(" +12:34", [fix!(TimezoneOffset)]; offset: 754 * 60);
|
||||
check!("\t -12:34", [fix!(TimezoneOffset)]; offset: -754 * 60);
|
||||
check!("", [fix!(TimezoneOffset)]; TOO_SHORT);
|
||||
check!("+", [fix!(TimezoneOffset)]; TOO_SHORT);
|
||||
check!("+1", [fix!(TimezoneOffset)]; TOO_SHORT);
|
||||
check!("+12", [fix!(TimezoneOffset)]; TOO_SHORT);
|
||||
check!("+123", [fix!(TimezoneOffset)]; TOO_SHORT);
|
||||
check!("+1234", [fix!(TimezoneOffset)]; offset: 754 * 60);
|
||||
check!("+12345", [fix!(TimezoneOffset)]; TOO_LONG);
|
||||
check!("+12345", [fix!(TimezoneOffset), num!(Day)]; offset: 754 * 60, day: 5);
|
||||
check!("Z", [fix!(TimezoneOffset)]; INVALID);
|
||||
check!("z", [fix!(TimezoneOffset)]; INVALID);
|
||||
check!("Z", [fix!(TimezoneOffsetZ)]; offset: 0);
|
||||
check!("z", [fix!(TimezoneOffsetZ)]; offset: 0);
|
||||
check!("Y", [fix!(TimezoneOffsetZ)]; INVALID);
|
||||
check!("Zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0);
|
||||
check!("zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0);
|
||||
check!("+1234ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 754 * 60);
|
||||
check!("+12:34ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 754 * 60);
|
||||
check!("Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0);
|
||||
check!("z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0);
|
||||
check!("+12:00", [internal_fix!(TimezoneOffsetPermissive)]; offset: 12 * 60 * 60);
|
||||
check!("+12", [internal_fix!(TimezoneOffsetPermissive)]; offset: 12 * 60 * 60);
|
||||
check!("CEST 5", [fix!(TimezoneName), lit!(" "), num!(Day)]; day: 5);
|
||||
|
||||
// some practical examples
|
||||
check!("2015-02-04T14:37:05+09:00",
|
||||
[num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"),
|
||||
num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)];
|
||||
year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2,
|
||||
minute: 37, second: 5, offset: 32400);
|
||||
check!("20150204143705567",
|
||||
[num!(Year), num!(Month), num!(Day),
|
||||
num!(Hour), num!(Minute), num!(Second), internal_fix!(Nanosecond3NoDot)];
|
||||
year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2,
|
||||
minute: 37, second: 5, nanosecond: 567000000);
|
||||
check!("Mon, 10 Jun 2013 09:32:37 GMT",
|
||||
[fix!(ShortWeekdayName), lit!(","), sp!(" "), num!(Day), sp!(" "),
|
||||
fix!(ShortMonthName), sp!(" "), num!(Year), sp!(" "), num!(Hour), lit!(":"),
|
||||
num!(Minute), lit!(":"), num!(Second), sp!(" "), lit!("GMT")];
|
||||
year: 2013, month: 6, day: 10, weekday: Weekday::Mon,
|
||||
hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37);
|
||||
check!("Sun Aug 02 13:39:15 CEST 2020",
|
||||
[fix!(ShortWeekdayName), sp!(" "), fix!(ShortMonthName), sp!(" "),
|
||||
num!(Day), sp!(" "), num!(Hour), lit!(":"), num!(Minute), lit!(":"),
|
||||
num!(Second), sp!(" "), fix!(TimezoneName), sp!(" "), num!(Year)];
|
||||
year: 2020, month: 8, day: 2, weekday: Weekday::Sun,
|
||||
hour_div_12: 1, hour_mod_12: 1, minute: 39, second: 15);
|
||||
check!("20060102150405",
|
||||
[num!(Year), num!(Month), num!(Day), num!(Hour), num!(Minute), num!(Second)];
|
||||
year: 2006, month: 1, day: 2, hour_div_12: 1, hour_mod_12: 3, minute: 4, second: 5);
|
||||
check!("3:14PM",
|
||||
[num!(Hour12), lit!(":"), num!(Minute), fix!(LowerAmPm)];
|
||||
hour_div_12: 1, hour_mod_12: 3, minute: 14);
|
||||
check!("12345678901234.56789",
|
||||
[num!(Timestamp), lit!("."), num!(Nanosecond)];
|
||||
nanosecond: 56_789, timestamp: 12_345_678_901_234);
|
||||
check!("12345678901234.56789",
|
||||
[num!(Timestamp), fix!(Nanosecond)];
|
||||
nanosecond: 567_890_000, timestamp: 12_345_678_901_234);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_rfc2822() {
|
||||
use super::NOT_ENOUGH;
|
||||
use super::*;
|
||||
use offset::FixedOffset;
|
||||
use DateTime;
|
||||
|
||||
// Test data - (input, Ok(expected result after parse and format) or Err(error code))
|
||||
let testdates = [
|
||||
("Tue, 20 Jan 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // normal case
|
||||
("Fri, 2 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // folding whitespace
|
||||
("Fri, 02 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // leading zero
|
||||
("20 Jan 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // no day of week
|
||||
("20 JAN 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // upper case month
|
||||
("Tue, 20 Jan 2015 17:35 -0800", Ok("Tue, 20 Jan 2015 17:35:00 -0800")), // no second
|
||||
("11 Sep 2001 09:45:00 EST", Ok("Tue, 11 Sep 2001 09:45:00 -0500")),
|
||||
("30 Feb 2015 17:35:20 -0800", Err(OUT_OF_RANGE)), // bad day of month
|
||||
("Tue, 20 Jan 2015", Err(TOO_SHORT)), // omitted fields
|
||||
("Tue, 20 Avr 2015 17:35:20 -0800", Err(INVALID)), // bad month name
|
||||
("Tue, 20 Jan 2015 25:35:20 -0800", Err(OUT_OF_RANGE)), // bad hour
|
||||
("Tue, 20 Jan 2015 7:35:20 -0800", Err(INVALID)), // bad # of digits in hour
|
||||
("Tue, 20 Jan 2015 17:65:20 -0800", Err(OUT_OF_RANGE)), // bad minute
|
||||
("Tue, 20 Jan 2015 17:35:90 -0800", Err(OUT_OF_RANGE)), // bad second
|
||||
("Tue, 20 Jan 2015 17:35:20 -0890", Err(OUT_OF_RANGE)), // bad offset
|
||||
("6 Jun 1944 04:00:00Z", Err(INVALID)), // bad offset (zulu not allowed)
|
||||
("Tue, 20 Jan 2015 17:35:20 HAS", Err(NOT_ENOUGH)), // bad named time zone
|
||||
];
|
||||
|
||||
fn rfc2822_to_datetime(date: &str) -> ParseResult<DateTime<FixedOffset>> {
|
||||
let mut parsed = Parsed::new();
|
||||
parse(&mut parsed, date, [Item::Fixed(Fixed::RFC2822)].iter())?;
|
||||
parsed.to_datetime()
|
||||
}
|
||||
|
||||
fn fmt_rfc2822_datetime(dt: DateTime<FixedOffset>) -> String {
|
||||
dt.format_with_items([Item::Fixed(Fixed::RFC2822)].iter()).to_string()
|
||||
}
|
||||
|
||||
// Test against test data above
|
||||
for &(date, checkdate) in testdates.iter() {
|
||||
let d = rfc2822_to_datetime(date); // parse a date
|
||||
let dt = match d {
|
||||
// did we get a value?
|
||||
Ok(dt) => Ok(fmt_rfc2822_datetime(dt)), // yes, go on
|
||||
Err(e) => Err(e), // otherwise keep an error for the comparison
|
||||
};
|
||||
if dt != checkdate.map(|s| s.to_string()) {
|
||||
// check for expected result
|
||||
panic!(
|
||||
"Date conversion failed for {}\nReceived: {:?}\nExpected: {:?}",
|
||||
date, dt, checkdate
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn parse_rfc850() {
|
||||
use {TimeZone, Utc};
|
||||
|
||||
static RFC850_FMT: &'static str = "%A, %d-%b-%y %T GMT";
|
||||
|
||||
let dt_str = "Sunday, 06-Nov-94 08:49:37 GMT";
|
||||
let dt = Utc.ymd(1994, 11, 6).and_hms(8, 49, 37);
|
||||
|
||||
// Check that the format is what we expect
|
||||
assert_eq!(dt.format(RFC850_FMT).to_string(), dt_str);
|
||||
|
||||
// Check that it parses correctly
|
||||
assert_eq!(Ok(dt), Utc.datetime_from_str("Sunday, 06-Nov-94 08:49:37 GMT", RFC850_FMT));
|
||||
|
||||
// Check that the rest of the weekdays parse correctly (this test originally failed because
|
||||
// Sunday parsed incorrectly).
|
||||
let testdates = [
|
||||
(Utc.ymd(1994, 11, 7).and_hms(8, 49, 37), "Monday, 07-Nov-94 08:49:37 GMT"),
|
||||
(Utc.ymd(1994, 11, 8).and_hms(8, 49, 37), "Tuesday, 08-Nov-94 08:49:37 GMT"),
|
||||
(Utc.ymd(1994, 11, 9).and_hms(8, 49, 37), "Wednesday, 09-Nov-94 08:49:37 GMT"),
|
||||
(Utc.ymd(1994, 11, 10).and_hms(8, 49, 37), "Thursday, 10-Nov-94 08:49:37 GMT"),
|
||||
(Utc.ymd(1994, 11, 11).and_hms(8, 49, 37), "Friday, 11-Nov-94 08:49:37 GMT"),
|
||||
(Utc.ymd(1994, 11, 12).and_hms(8, 49, 37), "Saturday, 12-Nov-94 08:49:37 GMT"),
|
||||
];
|
||||
|
||||
for val in &testdates {
|
||||
assert_eq!(Ok(val.0), Utc.datetime_from_str(val.1, RFC850_FMT));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_rfc3339() {
|
||||
use super::*;
|
||||
use offset::FixedOffset;
|
||||
use DateTime;
|
||||
|
||||
// Test data - (input, Ok(expected result after parse and format) or Err(error code))
|
||||
let testdates = [
|
||||
("2015-01-20T17:35:20-08:00", Ok("2015-01-20T17:35:20-08:00")), // normal case
|
||||
("1944-06-06T04:04:00Z", Ok("1944-06-06T04:04:00+00:00")), // D-day
|
||||
("2001-09-11T09:45:00-08:00", Ok("2001-09-11T09:45:00-08:00")),
|
||||
("2015-01-20T17:35:20.001-08:00", Ok("2015-01-20T17:35:20.001-08:00")),
|
||||
("2015-01-20T17:35:20.000031-08:00", Ok("2015-01-20T17:35:20.000031-08:00")),
|
||||
("2015-01-20T17:35:20.000000004-08:00", Ok("2015-01-20T17:35:20.000000004-08:00")),
|
||||
("2015-01-20T17:35:20.000000000452-08:00", Ok("2015-01-20T17:35:20-08:00")), // too small
|
||||
("2015-02-30T17:35:20-08:00", Err(OUT_OF_RANGE)), // bad day of month
|
||||
("2015-01-20T25:35:20-08:00", Err(OUT_OF_RANGE)), // bad hour
|
||||
("2015-01-20T17:65:20-08:00", Err(OUT_OF_RANGE)), // bad minute
|
||||
("2015-01-20T17:35:90-08:00", Err(OUT_OF_RANGE)), // bad second
|
||||
("2015-01-20T17:35:20-24:00", Err(OUT_OF_RANGE)), // bad offset
|
||||
];
|
||||
|
||||
fn rfc3339_to_datetime(date: &str) -> ParseResult<DateTime<FixedOffset>> {
|
||||
let mut parsed = Parsed::new();
|
||||
parse(&mut parsed, date, [Item::Fixed(Fixed::RFC3339)].iter())?;
|
||||
parsed.to_datetime()
|
||||
}
|
||||
|
||||
fn fmt_rfc3339_datetime(dt: DateTime<FixedOffset>) -> String {
|
||||
dt.format_with_items([Item::Fixed(Fixed::RFC3339)].iter()).to_string()
|
||||
}
|
||||
|
||||
// Test against test data above
|
||||
for &(date, checkdate) in testdates.iter() {
|
||||
let d = rfc3339_to_datetime(date); // parse a date
|
||||
let dt = match d {
|
||||
// did we get a value?
|
||||
Ok(dt) => Ok(fmt_rfc3339_datetime(dt)), // yes, go on
|
||||
Err(e) => Err(e), // otherwise keep an error for the comparison
|
||||
};
|
||||
if dt != checkdate.map(|s| s.to_string()) {
|
||||
// check for expected result
|
||||
panic!(
|
||||
"Date conversion failed for {}\nReceived: {:?}\nExpected: {:?}",
|
||||
date, dt, checkdate
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
1283
zeroidc/vendor/chrono/src/format/parsed.rs
vendored
Normal file
1283
zeroidc/vendor/chrono/src/format/parsed.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
350
zeroidc/vendor/chrono/src/format/scan.rs
vendored
Normal file
350
zeroidc/vendor/chrono/src/format/scan.rs
vendored
Normal file
@@ -0,0 +1,350 @@
|
||||
// This is a part of Chrono.
|
||||
// See README.md and LICENSE.txt for details.
|
||||
|
||||
/*!
|
||||
* Various scanning routines for the parser.
|
||||
*/
|
||||
|
||||
#![allow(deprecated)]
|
||||
|
||||
use super::{ParseResult, INVALID, OUT_OF_RANGE, TOO_SHORT};
|
||||
use Weekday;
|
||||
|
||||
/// Returns true when two slices are equal case-insensitively (in ASCII).
|
||||
/// Assumes that the `pattern` is already converted to lower case.
|
||||
fn equals(s: &str, pattern: &str) -> bool {
|
||||
let mut xs = s.as_bytes().iter().map(|&c| match c {
|
||||
b'A'...b'Z' => c + 32,
|
||||
_ => c,
|
||||
});
|
||||
let mut ys = pattern.as_bytes().iter().cloned();
|
||||
loop {
|
||||
match (xs.next(), ys.next()) {
|
||||
(None, None) => return true,
|
||||
(None, _) | (_, None) => return false,
|
||||
(Some(x), Some(y)) if x != y => return false,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to parse the non-negative number from `min` to `max` digits.
|
||||
///
|
||||
/// The absence of digits at all is an unconditional error.
|
||||
/// More than `max` digits are consumed up to the first `max` digits.
|
||||
/// Any number that does not fit in `i64` is an error.
|
||||
#[inline]
|
||||
pub fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)> {
|
||||
assert!(min <= max);
|
||||
|
||||
// We are only interested in ascii numbers, so we can work with the `str` as bytes. We stop on
|
||||
// the first non-numeric byte, which may be another ascii character or beginning of multi-byte
|
||||
// UTF-8 character.
|
||||
let bytes = s.as_bytes();
|
||||
if bytes.len() < min {
|
||||
return Err(TOO_SHORT);
|
||||
}
|
||||
|
||||
let mut n = 0i64;
|
||||
for (i, c) in bytes.iter().take(max).cloned().enumerate() {
|
||||
// cloned() = copied()
|
||||
if c < b'0' || b'9' < c {
|
||||
if i < min {
|
||||
return Err(INVALID);
|
||||
} else {
|
||||
return Ok((&s[i..], n));
|
||||
}
|
||||
}
|
||||
|
||||
n = match n.checked_mul(10).and_then(|n| n.checked_add((c - b'0') as i64)) {
|
||||
Some(n) => n,
|
||||
None => return Err(OUT_OF_RANGE),
|
||||
};
|
||||
}
|
||||
|
||||
Ok((&s[::core::cmp::min(max, bytes.len())..], n))
|
||||
}
|
||||
|
||||
/// Tries to consume at least one digits as a fractional second.
|
||||
/// Returns the number of whole nanoseconds (0--999,999,999).
|
||||
pub fn nanosecond(s: &str) -> ParseResult<(&str, i64)> {
|
||||
// record the number of digits consumed for later scaling.
|
||||
let origlen = s.len();
|
||||
let (s, v) = number(s, 1, 9)?;
|
||||
let consumed = origlen - s.len();
|
||||
|
||||
// scale the number accordingly.
|
||||
static SCALE: [i64; 10] =
|
||||
[0, 100_000_000, 10_000_000, 1_000_000, 100_000, 10_000, 1_000, 100, 10, 1];
|
||||
let v = v.checked_mul(SCALE[consumed]).ok_or(OUT_OF_RANGE)?;
|
||||
|
||||
// if there are more than 9 digits, skip next digits.
|
||||
let s = s.trim_left_matches(|c: char| '0' <= c && c <= '9');
|
||||
|
||||
Ok((s, v))
|
||||
}
|
||||
|
||||
/// Tries to consume a fixed number of digits as a fractional second.
|
||||
/// Returns the number of whole nanoseconds (0--999,999,999).
|
||||
pub fn nanosecond_fixed(s: &str, digits: usize) -> ParseResult<(&str, i64)> {
|
||||
// record the number of digits consumed for later scaling.
|
||||
let (s, v) = number(s, digits, digits)?;
|
||||
|
||||
// scale the number accordingly.
|
||||
static SCALE: [i64; 10] =
|
||||
[0, 100_000_000, 10_000_000, 1_000_000, 100_000, 10_000, 1_000, 100, 10, 1];
|
||||
let v = v.checked_mul(SCALE[digits]).ok_or(OUT_OF_RANGE)?;
|
||||
|
||||
Ok((s, v))
|
||||
}
|
||||
|
||||
/// Tries to parse the month index (0 through 11) with the first three ASCII letters.
|
||||
pub fn short_month0(s: &str) -> ParseResult<(&str, u8)> {
|
||||
if s.len() < 3 {
|
||||
return Err(TOO_SHORT);
|
||||
}
|
||||
let buf = s.as_bytes();
|
||||
let month0 = match (buf[0] | 32, buf[1] | 32, buf[2] | 32) {
|
||||
(b'j', b'a', b'n') => 0,
|
||||
(b'f', b'e', b'b') => 1,
|
||||
(b'm', b'a', b'r') => 2,
|
||||
(b'a', b'p', b'r') => 3,
|
||||
(b'm', b'a', b'y') => 4,
|
||||
(b'j', b'u', b'n') => 5,
|
||||
(b'j', b'u', b'l') => 6,
|
||||
(b'a', b'u', b'g') => 7,
|
||||
(b's', b'e', b'p') => 8,
|
||||
(b'o', b'c', b't') => 9,
|
||||
(b'n', b'o', b'v') => 10,
|
||||
(b'd', b'e', b'c') => 11,
|
||||
_ => return Err(INVALID),
|
||||
};
|
||||
Ok((&s[3..], month0))
|
||||
}
|
||||
|
||||
/// Tries to parse the weekday with the first three ASCII letters.
|
||||
pub fn short_weekday(s: &str) -> ParseResult<(&str, Weekday)> {
|
||||
if s.len() < 3 {
|
||||
return Err(TOO_SHORT);
|
||||
}
|
||||
let buf = s.as_bytes();
|
||||
let weekday = match (buf[0] | 32, buf[1] | 32, buf[2] | 32) {
|
||||
(b'm', b'o', b'n') => Weekday::Mon,
|
||||
(b't', b'u', b'e') => Weekday::Tue,
|
||||
(b'w', b'e', b'd') => Weekday::Wed,
|
||||
(b't', b'h', b'u') => Weekday::Thu,
|
||||
(b'f', b'r', b'i') => Weekday::Fri,
|
||||
(b's', b'a', b't') => Weekday::Sat,
|
||||
(b's', b'u', b'n') => Weekday::Sun,
|
||||
_ => return Err(INVALID),
|
||||
};
|
||||
Ok((&s[3..], weekday))
|
||||
}
|
||||
|
||||
/// Tries to parse the month index (0 through 11) with short or long month names.
|
||||
/// It prefers long month names to short month names when both are possible.
|
||||
pub fn short_or_long_month0(s: &str) -> ParseResult<(&str, u8)> {
|
||||
// lowercased month names, minus first three chars
|
||||
static LONG_MONTH_SUFFIXES: [&'static str; 12] =
|
||||
["uary", "ruary", "ch", "il", "", "e", "y", "ust", "tember", "ober", "ember", "ember"];
|
||||
|
||||
let (mut s, month0) = short_month0(s)?;
|
||||
|
||||
// tries to consume the suffix if possible
|
||||
let suffix = LONG_MONTH_SUFFIXES[month0 as usize];
|
||||
if s.len() >= suffix.len() && equals(&s[..suffix.len()], suffix) {
|
||||
s = &s[suffix.len()..];
|
||||
}
|
||||
|
||||
Ok((s, month0))
|
||||
}
|
||||
|
||||
/// Tries to parse the weekday with short or long weekday names.
|
||||
/// It prefers long weekday names to short weekday names when both are possible.
|
||||
pub fn short_or_long_weekday(s: &str) -> ParseResult<(&str, Weekday)> {
|
||||
// lowercased weekday names, minus first three chars
|
||||
static LONG_WEEKDAY_SUFFIXES: [&'static str; 7] =
|
||||
["day", "sday", "nesday", "rsday", "day", "urday", "day"];
|
||||
|
||||
let (mut s, weekday) = short_weekday(s)?;
|
||||
|
||||
// tries to consume the suffix if possible
|
||||
let suffix = LONG_WEEKDAY_SUFFIXES[weekday.num_days_from_monday() as usize];
|
||||
if s.len() >= suffix.len() && equals(&s[..suffix.len()], suffix) {
|
||||
s = &s[suffix.len()..];
|
||||
}
|
||||
|
||||
Ok((s, weekday))
|
||||
}
|
||||
|
||||
/// Tries to consume exactly one given character.
|
||||
pub fn char(s: &str, c1: u8) -> ParseResult<&str> {
|
||||
match s.as_bytes().first() {
|
||||
Some(&c) if c == c1 => Ok(&s[1..]),
|
||||
Some(_) => Err(INVALID),
|
||||
None => Err(TOO_SHORT),
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to consume one or more whitespace.
|
||||
pub fn space(s: &str) -> ParseResult<&str> {
|
||||
let s_ = s.trim_left();
|
||||
if s_.len() < s.len() {
|
||||
Ok(s_)
|
||||
} else if s.is_empty() {
|
||||
Err(TOO_SHORT)
|
||||
} else {
|
||||
Err(INVALID)
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes any number (including zero) of colon or spaces.
|
||||
pub fn colon_or_space(s: &str) -> ParseResult<&str> {
|
||||
Ok(s.trim_left_matches(|c: char| c == ':' || c.is_whitespace()))
|
||||
}
|
||||
|
||||
/// Tries to parse `[-+]\d\d` continued by `\d\d`. Return an offset in seconds if possible.
|
||||
///
|
||||
/// The additional `colon` may be used to parse a mandatory or optional `:`
|
||||
/// between hours and minutes, and should return either a new suffix or `Err` when parsing fails.
|
||||
pub fn timezone_offset<F>(s: &str, consume_colon: F) -> ParseResult<(&str, i32)>
|
||||
where
|
||||
F: FnMut(&str) -> ParseResult<&str>,
|
||||
{
|
||||
timezone_offset_internal(s, consume_colon, false)
|
||||
}
|
||||
|
||||
fn timezone_offset_internal<F>(
|
||||
mut s: &str,
|
||||
mut consume_colon: F,
|
||||
allow_missing_minutes: bool,
|
||||
) -> ParseResult<(&str, i32)>
|
||||
where
|
||||
F: FnMut(&str) -> ParseResult<&str>,
|
||||
{
|
||||
fn digits(s: &str) -> ParseResult<(u8, u8)> {
|
||||
let b = s.as_bytes();
|
||||
if b.len() < 2 {
|
||||
Err(TOO_SHORT)
|
||||
} else {
|
||||
Ok((b[0], b[1]))
|
||||
}
|
||||
}
|
||||
let negative = match s.as_bytes().first() {
|
||||
Some(&b'+') => false,
|
||||
Some(&b'-') => true,
|
||||
Some(_) => return Err(INVALID),
|
||||
None => return Err(TOO_SHORT),
|
||||
};
|
||||
s = &s[1..];
|
||||
|
||||
// hours (00--99)
|
||||
let hours = match digits(s)? {
|
||||
(h1 @ b'0'...b'9', h2 @ b'0'...b'9') => i32::from((h1 - b'0') * 10 + (h2 - b'0')),
|
||||
_ => return Err(INVALID),
|
||||
};
|
||||
s = &s[2..];
|
||||
|
||||
// colons (and possibly other separators)
|
||||
s = consume_colon(s)?;
|
||||
|
||||
// minutes (00--59)
|
||||
// if the next two items are digits then we have to add minutes
|
||||
let minutes = if let Ok(ds) = digits(s) {
|
||||
match ds {
|
||||
(m1 @ b'0'...b'5', m2 @ b'0'...b'9') => i32::from((m1 - b'0') * 10 + (m2 - b'0')),
|
||||
(b'6'...b'9', b'0'...b'9') => return Err(OUT_OF_RANGE),
|
||||
_ => return Err(INVALID),
|
||||
}
|
||||
} else if allow_missing_minutes {
|
||||
0
|
||||
} else {
|
||||
return Err(TOO_SHORT);
|
||||
};
|
||||
s = match s.len() {
|
||||
len if len >= 2 => &s[2..],
|
||||
len if len == 0 => s,
|
||||
_ => return Err(TOO_SHORT),
|
||||
};
|
||||
|
||||
let seconds = hours * 3600 + minutes * 60;
|
||||
Ok((s, if negative { -seconds } else { seconds }))
|
||||
}
|
||||
|
||||
/// Same as `timezone_offset` but also allows for `z`/`Z` which is the same as `+00:00`.
|
||||
pub fn timezone_offset_zulu<F>(s: &str, colon: F) -> ParseResult<(&str, i32)>
|
||||
where
|
||||
F: FnMut(&str) -> ParseResult<&str>,
|
||||
{
|
||||
let bytes = s.as_bytes();
|
||||
match bytes.first() {
|
||||
Some(&b'z') | Some(&b'Z') => Ok((&s[1..], 0)),
|
||||
Some(&b'u') | Some(&b'U') => {
|
||||
if bytes.len() >= 3 {
|
||||
let (b, c) = (bytes[1], bytes[2]);
|
||||
match (b | 32, c | 32) {
|
||||
(b't', b'c') => Ok((&s[3..], 0)),
|
||||
_ => Err(INVALID),
|
||||
}
|
||||
} else {
|
||||
Err(INVALID)
|
||||
}
|
||||
}
|
||||
_ => timezone_offset(s, colon),
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as `timezone_offset` but also allows for `z`/`Z` which is the same as
|
||||
/// `+00:00`, and allows missing minutes entirely.
|
||||
pub fn timezone_offset_permissive<F>(s: &str, colon: F) -> ParseResult<(&str, i32)>
|
||||
where
|
||||
F: FnMut(&str) -> ParseResult<&str>,
|
||||
{
|
||||
match s.as_bytes().first() {
|
||||
Some(&b'z') | Some(&b'Z') => Ok((&s[1..], 0)),
|
||||
_ => timezone_offset_internal(s, colon, true),
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as `timezone_offset` but also allows for RFC 2822 legacy timezones.
|
||||
/// May return `None` which indicates an insufficient offset data (i.e. `-0000`).
|
||||
pub fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option<i32>)> {
|
||||
// tries to parse legacy time zone names
|
||||
let upto = s
|
||||
.as_bytes()
|
||||
.iter()
|
||||
.position(|&c| match c {
|
||||
b'a'...b'z' | b'A'...b'Z' => false,
|
||||
_ => true,
|
||||
})
|
||||
.unwrap_or_else(|| s.len());
|
||||
if upto > 0 {
|
||||
let name = &s[..upto];
|
||||
let s = &s[upto..];
|
||||
let offset_hours = |o| Ok((s, Some(o * 3600)));
|
||||
if equals(name, "gmt") || equals(name, "ut") {
|
||||
offset_hours(0)
|
||||
} else if equals(name, "edt") {
|
||||
offset_hours(-4)
|
||||
} else if equals(name, "est") || equals(name, "cdt") {
|
||||
offset_hours(-5)
|
||||
} else if equals(name, "cst") || equals(name, "mdt") {
|
||||
offset_hours(-6)
|
||||
} else if equals(name, "mst") || equals(name, "pdt") {
|
||||
offset_hours(-7)
|
||||
} else if equals(name, "pst") {
|
||||
offset_hours(-8)
|
||||
} else {
|
||||
Ok((s, None)) // recommended by RFC 2822: consume but treat it as -0000
|
||||
}
|
||||
} else {
|
||||
let (s_, offset) = timezone_offset(s, |s| Ok(s))?;
|
||||
Ok((s_, Some(offset)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to consume everyting until next whitespace-like symbol.
|
||||
/// Does not provide any offset information from the consumed data.
|
||||
pub fn timezone_name_skip(s: &str) -> ParseResult<(&str, ())> {
|
||||
Ok((s.trim_left_matches(|c: char| !c.is_whitespace()), ()))
|
||||
}
|
||||
649
zeroidc/vendor/chrono/src/format/strftime.rs
vendored
Normal file
649
zeroidc/vendor/chrono/src/format/strftime.rs
vendored
Normal file
@@ -0,0 +1,649 @@
|
||||
// This is a part of Chrono.
|
||||
// See README.md and LICENSE.txt for details.
|
||||
|
||||
/*!
|
||||
`strftime`/`strptime`-inspired date and time formatting syntax.
|
||||
|
||||
## Specifiers
|
||||
|
||||
The following specifiers are available both to formatting and parsing.
|
||||
|
||||
| Spec. | Example | Description |
|
||||
|-------|----------|----------------------------------------------------------------------------|
|
||||
| | | **DATE SPECIFIERS:** |
|
||||
| `%Y` | `2001` | The full proleptic Gregorian year, zero-padded to 4 digits. [^1] |
|
||||
| `%C` | `20` | The proleptic Gregorian year divided by 100, zero-padded to 2 digits. [^2] |
|
||||
| `%y` | `01` | The proleptic Gregorian year modulo 100, zero-padded to 2 digits. [^2] |
|
||||
| | | |
|
||||
| `%m` | `07` | Month number (01--12), zero-padded to 2 digits. |
|
||||
| `%b` | `Jul` | Abbreviated month name. Always 3 letters. |
|
||||
| `%B` | `July` | Full month name. Also accepts corresponding abbreviation in parsing. |
|
||||
| `%h` | `Jul` | Same as `%b`. |
|
||||
| | | |
|
||||
| `%d` | `08` | Day number (01--31), zero-padded to 2 digits. |
|
||||
| `%e` | ` 8` | Same as `%d` but space-padded. Same as `%_d`. |
|
||||
| | | |
|
||||
| `%a` | `Sun` | Abbreviated weekday name. Always 3 letters. |
|
||||
| `%A` | `Sunday` | Full weekday name. Also accepts corresponding abbreviation in parsing. |
|
||||
| `%w` | `0` | Sunday = 0, Monday = 1, ..., Saturday = 6. |
|
||||
| `%u` | `7` | Monday = 1, Tuesday = 2, ..., Sunday = 7. (ISO 8601) |
|
||||
| | | |
|
||||
| `%U` | `28` | Week number starting with Sunday (00--53), zero-padded to 2 digits. [^3] |
|
||||
| `%W` | `27` | Same as `%U`, but week 1 starts with the first Monday in that year instead.|
|
||||
| | | |
|
||||
| `%G` | `2001` | Same as `%Y` but uses the year number in ISO 8601 week date. [^4] |
|
||||
| `%g` | `01` | Same as `%y` but uses the year number in ISO 8601 week date. [^4] |
|
||||
| `%V` | `27` | Same as `%U` but uses the week number in ISO 8601 week date (01--53). [^4] |
|
||||
| | | |
|
||||
| `%j` | `189` | Day of the year (001--366), zero-padded to 3 digits. |
|
||||
| | | |
|
||||
| `%D` | `07/08/01` | Month-day-year format. Same as `%m/%d/%y`. |
|
||||
| `%x` | `07/08/01` | Locale's date representation (e.g., 12/31/99). |
|
||||
| `%F` | `2001-07-08` | Year-month-day format (ISO 8601). Same as `%Y-%m-%d`. |
|
||||
| `%v` | ` 8-Jul-2001` | Day-month-year format. Same as `%e-%b-%Y`. |
|
||||
| | | |
|
||||
| | | **TIME SPECIFIERS:** |
|
||||
| `%H` | `00` | Hour number (00--23), zero-padded to 2 digits. |
|
||||
| `%k` | ` 0` | Same as `%H` but space-padded. Same as `%_H`. |
|
||||
| `%I` | `12` | Hour number in 12-hour clocks (01--12), zero-padded to 2 digits. |
|
||||
| `%l` | `12` | Same as `%I` but space-padded. Same as `%_I`. |
|
||||
| | | |
|
||||
| `%P` | `am` | `am` or `pm` in 12-hour clocks. |
|
||||
| `%p` | `AM` | `AM` or `PM` in 12-hour clocks. |
|
||||
| | | |
|
||||
| `%M` | `34` | Minute number (00--59), zero-padded to 2 digits. |
|
||||
| `%S` | `60` | Second number (00--60), zero-padded to 2 digits. [^5] |
|
||||
| `%f` | `026490000` | The fractional seconds (in nanoseconds) since last whole second. [^8] |
|
||||
| `%.f` | `.026490`| Similar to `.%f` but left-aligned. These all consume the leading dot. [^8] |
|
||||
| `%.3f`| `.026` | Similar to `.%f` but left-aligned but fixed to a length of 3. [^8] |
|
||||
| `%.6f`| `.026490` | Similar to `.%f` but left-aligned but fixed to a length of 6. [^8] |
|
||||
| `%.9f`| `.026490000` | Similar to `.%f` but left-aligned but fixed to a length of 9. [^8] |
|
||||
| `%3f` | `026` | Similar to `%.3f` but without the leading dot. [^8] |
|
||||
| `%6f` | `026490` | Similar to `%.6f` but without the leading dot. [^8] |
|
||||
| `%9f` | `026490000` | Similar to `%.9f` but without the leading dot. [^8] |
|
||||
| | | |
|
||||
| `%R` | `00:34` | Hour-minute format. Same as `%H:%M`. |
|
||||
| `%T` | `00:34:60` | Hour-minute-second format. Same as `%H:%M:%S`. |
|
||||
| `%X` | `00:34:60` | Locale's time representation (e.g., 23:13:48). |
|
||||
| `%r` | `12:34:60 AM` | Hour-minute-second format in 12-hour clocks. Same as `%I:%M:%S %p`. |
|
||||
| | | |
|
||||
| | | **TIME ZONE SPECIFIERS:** |
|
||||
| `%Z` | `ACST` | Local time zone name. Skips all non-whitespace characters during parsing. [^9] |
|
||||
| `%z` | `+0930` | Offset from the local time to UTC (with UTC being `+0000`). |
|
||||
| `%:z` | `+09:30` | Same as `%z` but with a colon. |
|
||||
| `%#z` | `+09` | *Parsing only:* Same as `%z` but allows minutes to be missing or present. |
|
||||
| | | |
|
||||
| | | **DATE & TIME SPECIFIERS:** |
|
||||
|`%c`|`Sun Jul 8 00:34:60 2001`|Locale's date and time (e.g., Thu Mar 3 23:05:25 2005). |
|
||||
| `%+` | `2001-07-08T00:34:60.026490+09:30` | ISO 8601 / RFC 3339 date & time format. [^6] |
|
||||
| | | |
|
||||
| `%s` | `994518299` | UNIX timestamp, the number of seconds since 1970-01-01 00:00 UTC. [^7]|
|
||||
| | | |
|
||||
| | | **SPECIAL SPECIFIERS:** |
|
||||
| `%t` | | Literal tab (`\t`). |
|
||||
| `%n` | | Literal newline (`\n`). |
|
||||
| `%%` | | Literal percent sign. |
|
||||
|
||||
It is possible to override the default padding behavior of numeric specifiers `%?`.
|
||||
This is not allowed for other specifiers and will result in the `BAD_FORMAT` error.
|
||||
|
||||
Modifier | Description
|
||||
-------- | -----------
|
||||
`%-?` | Suppresses any padding including spaces and zeroes. (e.g. `%j` = `012`, `%-j` = `12`)
|
||||
`%_?` | Uses spaces as a padding. (e.g. `%j` = `012`, `%_j` = ` 12`)
|
||||
`%0?` | Uses zeroes as a padding. (e.g. `%e` = ` 9`, `%0e` = `09`)
|
||||
|
||||
Notes:
|
||||
|
||||
[^1]: `%Y`:
|
||||
Negative years are allowed in formatting but not in parsing.
|
||||
|
||||
[^2]: `%C`, `%y`:
|
||||
This is floor division, so 100 BCE (year number -99) will print `-1` and `99` respectively.
|
||||
|
||||
[^3]: `%U`:
|
||||
Week 1 starts with the first Sunday in that year.
|
||||
It is possible to have week 0 for days before the first Sunday.
|
||||
|
||||
[^4]: `%G`, `%g`, `%V`:
|
||||
Week 1 is the first week with at least 4 days in that year.
|
||||
Week 0 does not exist, so this should be used with `%G` or `%g`.
|
||||
|
||||
[^5]: `%S`:
|
||||
It accounts for leap seconds, so `60` is possible.
|
||||
|
||||
[^6]: `%+`: Same as `%Y-%m-%dT%H:%M:%S%.f%:z`, i.e. 0, 3, 6 or 9 fractional
|
||||
digits for seconds and colons in the time zone offset.
|
||||
<br>
|
||||
<br>
|
||||
The typical `strftime` implementations have different (and locale-dependent)
|
||||
formats for this specifier. While Chrono's format for `%+` is far more
|
||||
stable, it is best to avoid this specifier if you want to control the exact
|
||||
output.
|
||||
|
||||
[^7]: `%s`:
|
||||
This is not padded and can be negative.
|
||||
For the purpose of Chrono, it only accounts for non-leap seconds
|
||||
so it slightly differs from ISO C `strftime` behavior.
|
||||
|
||||
[^8]: `%f`, `%.f`, `%.3f`, `%.6f`, `%.9f`, `%3f`, `%6f`, `%9f`:
|
||||
<br>
|
||||
The default `%f` is right-aligned and always zero-padded to 9 digits
|
||||
for the compatibility with glibc and others,
|
||||
so it always counts the number of nanoseconds since the last whole second.
|
||||
E.g. 7ms after the last second will print `007000000`,
|
||||
and parsing `7000000` will yield the same.
|
||||
<br>
|
||||
<br>
|
||||
The variant `%.f` is left-aligned and print 0, 3, 6 or 9 fractional digits
|
||||
according to the precision.
|
||||
E.g. 70ms after the last second under `%.f` will print `.070` (note: not `.07`),
|
||||
and parsing `.07`, `.070000` etc. will yield the same.
|
||||
Note that they can print or read nothing if the fractional part is zero or
|
||||
the next character is not `.`.
|
||||
<br>
|
||||
<br>
|
||||
The variant `%.3f`, `%.6f` and `%.9f` are left-aligned and print 3, 6 or 9 fractional digits
|
||||
according to the number preceding `f`.
|
||||
E.g. 70ms after the last second under `%.3f` will print `.070` (note: not `.07`),
|
||||
and parsing `.07`, `.070000` etc. will yield the same.
|
||||
Note that they can read nothing if the fractional part is zero or
|
||||
the next character is not `.` however will print with the specified length.
|
||||
<br>
|
||||
<br>
|
||||
The variant `%3f`, `%6f` and `%9f` are left-aligned and print 3, 6 or 9 fractional digits
|
||||
according to the number preceding `f`, but without the leading dot.
|
||||
E.g. 70ms after the last second under `%3f` will print `070` (note: not `07`),
|
||||
and parsing `07`, `070000` etc. will yield the same.
|
||||
Note that they can read nothing if the fractional part is zero.
|
||||
|
||||
[^9]: `%Z`:
|
||||
Offset will not be populated from the parsed data, nor will it be validated.
|
||||
Timezone is completely ignored. Similar to the glibc `strptime` treatment of
|
||||
this format code.
|
||||
<br>
|
||||
<br>
|
||||
It is not possible to reliably convert from an abbreviation to an offset,
|
||||
for example CDT can mean either Central Daylight Time (North America) or
|
||||
China Daylight Time.
|
||||
*/
|
||||
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
use super::{locales, Locale};
|
||||
use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad};
|
||||
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
type Fmt<'a> = Vec<Item<'a>>;
|
||||
#[cfg(not(feature = "unstable-locales"))]
|
||||
type Fmt<'a> = &'static [Item<'static>];
|
||||
|
||||
static D_FMT: &'static [Item<'static>] =
|
||||
&[num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)];
|
||||
static D_T_FMT: &'static [Item<'static>] = &[
|
||||
fix!(ShortWeekdayName),
|
||||
sp!(" "),
|
||||
fix!(ShortMonthName),
|
||||
sp!(" "),
|
||||
nums!(Day),
|
||||
sp!(" "),
|
||||
num0!(Hour),
|
||||
lit!(":"),
|
||||
num0!(Minute),
|
||||
lit!(":"),
|
||||
num0!(Second),
|
||||
sp!(" "),
|
||||
num0!(Year),
|
||||
];
|
||||
static T_FMT: &'static [Item<'static>] =
|
||||
&[num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)];
|
||||
|
||||
/// Parsing iterator for `strftime`-like format strings.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct StrftimeItems<'a> {
|
||||
/// Remaining portion of the string.
|
||||
remainder: &'a str,
|
||||
/// If the current specifier is composed of multiple formatting items (e.g. `%+`),
|
||||
/// parser refers to the statically reconstructed slice of them.
|
||||
/// If `recons` is not empty they have to be returned earlier than the `remainder`.
|
||||
recons: Fmt<'a>,
|
||||
/// Date format
|
||||
d_fmt: Fmt<'a>,
|
||||
/// Date and time format
|
||||
d_t_fmt: Fmt<'a>,
|
||||
/// Time format
|
||||
t_fmt: Fmt<'a>,
|
||||
}
|
||||
|
||||
impl<'a> StrftimeItems<'a> {
|
||||
/// Creates a new parsing iterator from the `strftime`-like format string.
|
||||
pub fn new(s: &'a str) -> StrftimeItems<'a> {
|
||||
Self::with_remainer(s)
|
||||
}
|
||||
|
||||
/// Creates a new parsing iterator from the `strftime`-like format string.
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
pub fn new_with_locale(s: &'a str, locale: Locale) -> StrftimeItems<'a> {
|
||||
let d_fmt = StrftimeItems::new(locales::d_fmt(locale)).collect();
|
||||
let d_t_fmt = StrftimeItems::new(locales::d_t_fmt(locale)).collect();
|
||||
let t_fmt = StrftimeItems::new(locales::t_fmt(locale)).collect();
|
||||
|
||||
StrftimeItems {
|
||||
remainder: s,
|
||||
recons: Vec::new(),
|
||||
d_fmt: d_fmt,
|
||||
d_t_fmt: d_t_fmt,
|
||||
t_fmt: t_fmt,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "unstable-locales"))]
|
||||
fn with_remainer(s: &'a str) -> StrftimeItems<'a> {
|
||||
static FMT_NONE: &'static [Item<'static>; 0] = &[];
|
||||
|
||||
StrftimeItems {
|
||||
remainder: s,
|
||||
recons: FMT_NONE,
|
||||
d_fmt: D_FMT,
|
||||
d_t_fmt: D_T_FMT,
|
||||
t_fmt: T_FMT,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
fn with_remainer(s: &'a str) -> StrftimeItems<'a> {
|
||||
StrftimeItems {
|
||||
remainder: s,
|
||||
recons: Vec::new(),
|
||||
d_fmt: D_FMT.to_vec(),
|
||||
d_t_fmt: D_T_FMT.to_vec(),
|
||||
t_fmt: T_FMT.to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const HAVE_ALTERNATES: &'static str = "z";
|
||||
|
||||
impl<'a> Iterator for StrftimeItems<'a> {
|
||||
type Item = Item<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Item<'a>> {
|
||||
// we have some reconstructed items to return
|
||||
if !self.recons.is_empty() {
|
||||
let item;
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
{
|
||||
item = self.recons.remove(0);
|
||||
}
|
||||
#[cfg(not(feature = "unstable-locales"))]
|
||||
{
|
||||
item = self.recons[0].clone();
|
||||
self.recons = &self.recons[1..];
|
||||
}
|
||||
return Some(item);
|
||||
}
|
||||
|
||||
match self.remainder.chars().next() {
|
||||
// we are done
|
||||
None => None,
|
||||
|
||||
// the next item is a specifier
|
||||
Some('%') => {
|
||||
self.remainder = &self.remainder[1..];
|
||||
|
||||
macro_rules! next {
|
||||
() => {
|
||||
match self.remainder.chars().next() {
|
||||
Some(x) => {
|
||||
self.remainder = &self.remainder[x.len_utf8()..];
|
||||
x
|
||||
}
|
||||
None => return Some(Item::Error), // premature end of string
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let spec = next!();
|
||||
let pad_override = match spec {
|
||||
'-' => Some(Pad::None),
|
||||
'0' => Some(Pad::Zero),
|
||||
'_' => Some(Pad::Space),
|
||||
_ => None,
|
||||
};
|
||||
let is_alternate = spec == '#';
|
||||
let spec = if pad_override.is_some() || is_alternate { next!() } else { spec };
|
||||
if is_alternate && !HAVE_ALTERNATES.contains(spec) {
|
||||
return Some(Item::Error);
|
||||
}
|
||||
|
||||
macro_rules! recons {
|
||||
[$head:expr, $($tail:expr),+ $(,)*] => ({
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
{
|
||||
self.recons.clear();
|
||||
$(self.recons.push($tail);)+
|
||||
}
|
||||
#[cfg(not(feature = "unstable-locales"))]
|
||||
{
|
||||
const RECONS: &'static [Item<'static>] = &[$($tail),+];
|
||||
self.recons = RECONS;
|
||||
}
|
||||
$head
|
||||
})
|
||||
}
|
||||
|
||||
macro_rules! recons_from_slice {
|
||||
($slice:expr) => {{
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
{
|
||||
self.recons.clear();
|
||||
self.recons.extend_from_slice(&$slice[1..]);
|
||||
}
|
||||
#[cfg(not(feature = "unstable-locales"))]
|
||||
{
|
||||
self.recons = &$slice[1..];
|
||||
}
|
||||
$slice[0].clone()
|
||||
}};
|
||||
}
|
||||
|
||||
let item = match spec {
|
||||
'A' => fix!(LongWeekdayName),
|
||||
'B' => fix!(LongMonthName),
|
||||
'C' => num0!(YearDiv100),
|
||||
'D' => {
|
||||
recons![num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)]
|
||||
}
|
||||
'F' => recons![num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)],
|
||||
'G' => num0!(IsoYear),
|
||||
'H' => num0!(Hour),
|
||||
'I' => num0!(Hour12),
|
||||
'M' => num0!(Minute),
|
||||
'P' => fix!(LowerAmPm),
|
||||
'R' => recons![num0!(Hour), lit!(":"), num0!(Minute)],
|
||||
'S' => num0!(Second),
|
||||
'T' => recons![num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)],
|
||||
'U' => num0!(WeekFromSun),
|
||||
'V' => num0!(IsoWeek),
|
||||
'W' => num0!(WeekFromMon),
|
||||
'X' => recons_from_slice!(self.t_fmt),
|
||||
'Y' => num0!(Year),
|
||||
'Z' => fix!(TimezoneName),
|
||||
'a' => fix!(ShortWeekdayName),
|
||||
'b' | 'h' => fix!(ShortMonthName),
|
||||
'c' => recons_from_slice!(self.d_t_fmt),
|
||||
'd' => num0!(Day),
|
||||
'e' => nums!(Day),
|
||||
'f' => num0!(Nanosecond),
|
||||
'g' => num0!(IsoYearMod100),
|
||||
'j' => num0!(Ordinal),
|
||||
'k' => nums!(Hour),
|
||||
'l' => nums!(Hour12),
|
||||
'm' => num0!(Month),
|
||||
'n' => sp!("\n"),
|
||||
'p' => fix!(UpperAmPm),
|
||||
'r' => recons![
|
||||
num0!(Hour12),
|
||||
lit!(":"),
|
||||
num0!(Minute),
|
||||
lit!(":"),
|
||||
num0!(Second),
|
||||
sp!(" "),
|
||||
fix!(UpperAmPm)
|
||||
],
|
||||
's' => num!(Timestamp),
|
||||
't' => sp!("\t"),
|
||||
'u' => num!(WeekdayFromMon),
|
||||
'v' => {
|
||||
recons![nums!(Day), lit!("-"), fix!(ShortMonthName), lit!("-"), num0!(Year)]
|
||||
}
|
||||
'w' => num!(NumDaysFromSun),
|
||||
'x' => recons_from_slice!(self.d_fmt),
|
||||
'y' => num0!(YearMod100),
|
||||
'z' => {
|
||||
if is_alternate {
|
||||
internal_fix!(TimezoneOffsetPermissive)
|
||||
} else {
|
||||
fix!(TimezoneOffset)
|
||||
}
|
||||
}
|
||||
'+' => fix!(RFC3339),
|
||||
':' => match next!() {
|
||||
'z' => fix!(TimezoneOffsetColon),
|
||||
_ => Item::Error,
|
||||
},
|
||||
'.' => match next!() {
|
||||
'3' => match next!() {
|
||||
'f' => fix!(Nanosecond3),
|
||||
_ => Item::Error,
|
||||
},
|
||||
'6' => match next!() {
|
||||
'f' => fix!(Nanosecond6),
|
||||
_ => Item::Error,
|
||||
},
|
||||
'9' => match next!() {
|
||||
'f' => fix!(Nanosecond9),
|
||||
_ => Item::Error,
|
||||
},
|
||||
'f' => fix!(Nanosecond),
|
||||
_ => Item::Error,
|
||||
},
|
||||
'3' => match next!() {
|
||||
'f' => internal_fix!(Nanosecond3NoDot),
|
||||
_ => Item::Error,
|
||||
},
|
||||
'6' => match next!() {
|
||||
'f' => internal_fix!(Nanosecond6NoDot),
|
||||
_ => Item::Error,
|
||||
},
|
||||
'9' => match next!() {
|
||||
'f' => internal_fix!(Nanosecond9NoDot),
|
||||
_ => Item::Error,
|
||||
},
|
||||
'%' => lit!("%"),
|
||||
_ => Item::Error, // no such specifier
|
||||
};
|
||||
|
||||
// adjust `item` if we have any padding modifier
|
||||
if let Some(new_pad) = pad_override {
|
||||
match item {
|
||||
Item::Numeric(ref kind, _pad) if self.recons.is_empty() => {
|
||||
Some(Item::Numeric(kind.clone(), new_pad))
|
||||
}
|
||||
_ => Some(Item::Error), // no reconstructed or non-numeric item allowed
|
||||
}
|
||||
} else {
|
||||
Some(item)
|
||||
}
|
||||
}
|
||||
|
||||
// the next item is space
|
||||
Some(c) if c.is_whitespace() => {
|
||||
// `%` is not a whitespace, so `c != '%'` is redundant
|
||||
let nextspec = self
|
||||
.remainder
|
||||
.find(|c: char| !c.is_whitespace())
|
||||
.unwrap_or_else(|| self.remainder.len());
|
||||
assert!(nextspec > 0);
|
||||
let item = sp!(&self.remainder[..nextspec]);
|
||||
self.remainder = &self.remainder[nextspec..];
|
||||
Some(item)
|
||||
}
|
||||
|
||||
// the next item is literal
|
||||
_ => {
|
||||
let nextspec = self
|
||||
.remainder
|
||||
.find(|c: char| c.is_whitespace() || c == '%')
|
||||
.unwrap_or_else(|| self.remainder.len());
|
||||
assert!(nextspec > 0);
|
||||
let item = lit!(&self.remainder[..nextspec]);
|
||||
self.remainder = &self.remainder[nextspec..];
|
||||
Some(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_strftime_items() {
|
||||
fn parse_and_collect<'a>(s: &'a str) -> Vec<Item<'a>> {
|
||||
// map any error into `[Item::Error]`. useful for easy testing.
|
||||
let items = StrftimeItems::new(s);
|
||||
let items = items.map(|spec| if spec == Item::Error { None } else { Some(spec) });
|
||||
items.collect::<Option<Vec<_>>>().unwrap_or(vec![Item::Error])
|
||||
}
|
||||
|
||||
assert_eq!(parse_and_collect(""), []);
|
||||
assert_eq!(parse_and_collect(" \t\n\r "), [sp!(" \t\n\r ")]);
|
||||
assert_eq!(parse_and_collect("hello?"), [lit!("hello?")]);
|
||||
assert_eq!(
|
||||
parse_and_collect("a b\t\nc"),
|
||||
[lit!("a"), sp!(" "), lit!("b"), sp!("\t\n"), lit!("c")]
|
||||
);
|
||||
assert_eq!(parse_and_collect("100%%"), [lit!("100"), lit!("%")]);
|
||||
assert_eq!(parse_and_collect("100%% ok"), [lit!("100"), lit!("%"), sp!(" "), lit!("ok")]);
|
||||
assert_eq!(parse_and_collect("%%PDF-1.0"), [lit!("%"), lit!("PDF-1.0")]);
|
||||
assert_eq!(
|
||||
parse_and_collect("%Y-%m-%d"),
|
||||
[num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)]
|
||||
);
|
||||
assert_eq!(parse_and_collect("[%F]"), parse_and_collect("[%Y-%m-%d]"));
|
||||
assert_eq!(parse_and_collect("%m %d"), [num0!(Month), sp!(" "), num0!(Day)]);
|
||||
assert_eq!(parse_and_collect("%"), [Item::Error]);
|
||||
assert_eq!(parse_and_collect("%%"), [lit!("%")]);
|
||||
assert_eq!(parse_and_collect("%%%"), [Item::Error]);
|
||||
assert_eq!(parse_and_collect("%%%%"), [lit!("%"), lit!("%")]);
|
||||
assert_eq!(parse_and_collect("foo%?"), [Item::Error]);
|
||||
assert_eq!(parse_and_collect("bar%42"), [Item::Error]);
|
||||
assert_eq!(parse_and_collect("quux% +"), [Item::Error]);
|
||||
assert_eq!(parse_and_collect("%.Z"), [Item::Error]);
|
||||
assert_eq!(parse_and_collect("%:Z"), [Item::Error]);
|
||||
assert_eq!(parse_and_collect("%-Z"), [Item::Error]);
|
||||
assert_eq!(parse_and_collect("%0Z"), [Item::Error]);
|
||||
assert_eq!(parse_and_collect("%_Z"), [Item::Error]);
|
||||
assert_eq!(parse_and_collect("%.j"), [Item::Error]);
|
||||
assert_eq!(parse_and_collect("%:j"), [Item::Error]);
|
||||
assert_eq!(parse_and_collect("%-j"), [num!(Ordinal)]);
|
||||
assert_eq!(parse_and_collect("%0j"), [num0!(Ordinal)]);
|
||||
assert_eq!(parse_and_collect("%_j"), [nums!(Ordinal)]);
|
||||
assert_eq!(parse_and_collect("%.e"), [Item::Error]);
|
||||
assert_eq!(parse_and_collect("%:e"), [Item::Error]);
|
||||
assert_eq!(parse_and_collect("%-e"), [num!(Day)]);
|
||||
assert_eq!(parse_and_collect("%0e"), [num0!(Day)]);
|
||||
assert_eq!(parse_and_collect("%_e"), [nums!(Day)]);
|
||||
assert_eq!(parse_and_collect("%z"), [fix!(TimezoneOffset)]);
|
||||
assert_eq!(parse_and_collect("%#z"), [internal_fix!(TimezoneOffsetPermissive)]);
|
||||
assert_eq!(parse_and_collect("%#m"), [Item::Error]);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_strftime_docs() {
|
||||
use {FixedOffset, TimeZone, Timelike};
|
||||
|
||||
let dt = FixedOffset::east(34200).ymd(2001, 7, 8).and_hms_nano(0, 34, 59, 1_026_490_708);
|
||||
|
||||
// date specifiers
|
||||
assert_eq!(dt.format("%Y").to_string(), "2001");
|
||||
assert_eq!(dt.format("%C").to_string(), "20");
|
||||
assert_eq!(dt.format("%y").to_string(), "01");
|
||||
assert_eq!(dt.format("%m").to_string(), "07");
|
||||
assert_eq!(dt.format("%b").to_string(), "Jul");
|
||||
assert_eq!(dt.format("%B").to_string(), "July");
|
||||
assert_eq!(dt.format("%h").to_string(), "Jul");
|
||||
assert_eq!(dt.format("%d").to_string(), "08");
|
||||
assert_eq!(dt.format("%e").to_string(), " 8");
|
||||
assert_eq!(dt.format("%e").to_string(), dt.format("%_d").to_string());
|
||||
assert_eq!(dt.format("%a").to_string(), "Sun");
|
||||
assert_eq!(dt.format("%A").to_string(), "Sunday");
|
||||
assert_eq!(dt.format("%w").to_string(), "0");
|
||||
assert_eq!(dt.format("%u").to_string(), "7");
|
||||
assert_eq!(dt.format("%U").to_string(), "28");
|
||||
assert_eq!(dt.format("%W").to_string(), "27");
|
||||
assert_eq!(dt.format("%G").to_string(), "2001");
|
||||
assert_eq!(dt.format("%g").to_string(), "01");
|
||||
assert_eq!(dt.format("%V").to_string(), "27");
|
||||
assert_eq!(dt.format("%j").to_string(), "189");
|
||||
assert_eq!(dt.format("%D").to_string(), "07/08/01");
|
||||
assert_eq!(dt.format("%x").to_string(), "07/08/01");
|
||||
assert_eq!(dt.format("%F").to_string(), "2001-07-08");
|
||||
assert_eq!(dt.format("%v").to_string(), " 8-Jul-2001");
|
||||
|
||||
// time specifiers
|
||||
assert_eq!(dt.format("%H").to_string(), "00");
|
||||
assert_eq!(dt.format("%k").to_string(), " 0");
|
||||
assert_eq!(dt.format("%k").to_string(), dt.format("%_H").to_string());
|
||||
assert_eq!(dt.format("%I").to_string(), "12");
|
||||
assert_eq!(dt.format("%l").to_string(), "12");
|
||||
assert_eq!(dt.format("%l").to_string(), dt.format("%_I").to_string());
|
||||
assert_eq!(dt.format("%P").to_string(), "am");
|
||||
assert_eq!(dt.format("%p").to_string(), "AM");
|
||||
assert_eq!(dt.format("%M").to_string(), "34");
|
||||
assert_eq!(dt.format("%S").to_string(), "60");
|
||||
assert_eq!(dt.format("%f").to_string(), "026490708");
|
||||
assert_eq!(dt.format("%.f").to_string(), ".026490708");
|
||||
assert_eq!(dt.with_nanosecond(1_026_490_000).unwrap().format("%.f").to_string(), ".026490");
|
||||
assert_eq!(dt.format("%.3f").to_string(), ".026");
|
||||
assert_eq!(dt.format("%.6f").to_string(), ".026490");
|
||||
assert_eq!(dt.format("%.9f").to_string(), ".026490708");
|
||||
assert_eq!(dt.format("%3f").to_string(), "026");
|
||||
assert_eq!(dt.format("%6f").to_string(), "026490");
|
||||
assert_eq!(dt.format("%9f").to_string(), "026490708");
|
||||
assert_eq!(dt.format("%R").to_string(), "00:34");
|
||||
assert_eq!(dt.format("%T").to_string(), "00:34:60");
|
||||
assert_eq!(dt.format("%X").to_string(), "00:34:60");
|
||||
assert_eq!(dt.format("%r").to_string(), "12:34:60 AM");
|
||||
|
||||
// time zone specifiers
|
||||
//assert_eq!(dt.format("%Z").to_string(), "ACST");
|
||||
assert_eq!(dt.format("%z").to_string(), "+0930");
|
||||
assert_eq!(dt.format("%:z").to_string(), "+09:30");
|
||||
|
||||
// date & time specifiers
|
||||
assert_eq!(dt.format("%c").to_string(), "Sun Jul 8 00:34:60 2001");
|
||||
assert_eq!(dt.format("%+").to_string(), "2001-07-08T00:34:60.026490708+09:30");
|
||||
assert_eq!(
|
||||
dt.with_nanosecond(1_026_490_000).unwrap().format("%+").to_string(),
|
||||
"2001-07-08T00:34:60.026490+09:30"
|
||||
);
|
||||
assert_eq!(dt.format("%s").to_string(), "994518299");
|
||||
|
||||
// special specifiers
|
||||
assert_eq!(dt.format("%t").to_string(), "\t");
|
||||
assert_eq!(dt.format("%n").to_string(), "\n");
|
||||
assert_eq!(dt.format("%%").to_string(), "%");
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable-locales")]
|
||||
#[test]
|
||||
fn test_strftime_docs_localized() {
|
||||
use {FixedOffset, TimeZone};
|
||||
|
||||
let dt = FixedOffset::east(34200).ymd(2001, 7, 8).and_hms_nano(0, 34, 59, 1_026_490_708);
|
||||
|
||||
// date specifiers
|
||||
assert_eq!(dt.format_localized("%b", Locale::fr_BE).to_string(), "jui");
|
||||
assert_eq!(dt.format_localized("%B", Locale::fr_BE).to_string(), "juillet");
|
||||
assert_eq!(dt.format_localized("%h", Locale::fr_BE).to_string(), "jui");
|
||||
assert_eq!(dt.format_localized("%a", Locale::fr_BE).to_string(), "dim");
|
||||
assert_eq!(dt.format_localized("%A", Locale::fr_BE).to_string(), "dimanche");
|
||||
assert_eq!(dt.format_localized("%D", Locale::fr_BE).to_string(), "07/08/01");
|
||||
assert_eq!(dt.format_localized("%x", Locale::fr_BE).to_string(), "08/07/01");
|
||||
assert_eq!(dt.format_localized("%F", Locale::fr_BE).to_string(), "2001-07-08");
|
||||
assert_eq!(dt.format_localized("%v", Locale::fr_BE).to_string(), " 8-jui-2001");
|
||||
|
||||
// time specifiers
|
||||
assert_eq!(dt.format_localized("%P", Locale::fr_BE).to_string(), "");
|
||||
assert_eq!(dt.format_localized("%p", Locale::fr_BE).to_string(), "");
|
||||
assert_eq!(dt.format_localized("%R", Locale::fr_BE).to_string(), "00:34");
|
||||
assert_eq!(dt.format_localized("%T", Locale::fr_BE).to_string(), "00:34:60");
|
||||
assert_eq!(dt.format_localized("%X", Locale::fr_BE).to_string(), "00:34:60");
|
||||
assert_eq!(dt.format_localized("%r", Locale::fr_BE).to_string(), "12:34:60 ");
|
||||
|
||||
// date & time specifiers
|
||||
assert_eq!(
|
||||
dt.format_localized("%c", Locale::fr_BE).to_string(),
|
||||
"dim 08 jui 2001 00:34:60 +09:30"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user