use core::fmt;
use std::boxed::Box;
mod formatting;
pub(crate) mod locales;
mod parse;
mod parsed;
pub(crate) mod scan;
pub mod strftime;
pub use formatting::{DelayedFormat, SecondsFormat};
pub(crate) use formatting::{write_rfc2822, write_rfc3339};
#[cfg(feature = "unstable-locales")]
pub use locales::Locale;
pub(crate) use parse::parse_rfc3339;
pub use parse::{parse, parse_and_remainder};
pub use parsed::Parsed;
pub use strftime::StrftimeItems;
#[derive(Clone, PartialEq, Eq, Hash)]
enum Void {}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Pad {
None,
Zero,
Space,
}
#[non_exhaustive]
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Numeric {
Year,
YearDiv100,
YearMod100,
IsoYear,
IsoYearDiv100,
IsoYearMod100,
Quarter,
Month,
Day,
WeekFromSun,
WeekFromMon,
IsoWeek,
NumDaysFromSun,
WeekdayFromMon,
Ordinal,
Hour,
Hour12,
Minute,
Second,
Nanosecond,
Timestamp,
Internal(InternalNumeric),
}
#[derive(Clone, Eq, Hash, PartialEq)]
pub struct InternalNumeric {
_dummy: Void,
}
impl fmt::Debug for InternalNumeric {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "<InternalNumeric>")
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for InternalNumeric {
fn format(&self, f: defmt::Formatter) {
defmt::write!(f, "<InternalNumeric>")
}
}
#[non_exhaustive]
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Fixed {
ShortMonthName,
LongMonthName,
ShortWeekdayName,
LongWeekdayName,
LowerAmPm,
UpperAmPm,
Nanosecond,
Nanosecond3,
Nanosecond6,
Nanosecond9,
TimezoneName,
TimezoneOffsetColon,
TimezoneOffsetDoubleColon,
TimezoneOffsetTripleColon,
TimezoneOffsetColonZ,
TimezoneOffset,
TimezoneOffsetZ,
RFC2822,
RFC3339,
Internal(InternalFixed),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct InternalFixed {
val: InternalInternal,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum InternalInternal {
TimezoneOffsetPermissive,
Nanosecond3NoDot,
Nanosecond6NoDot,
Nanosecond9NoDot,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct OffsetFormat {
pub precision: OffsetPrecision,
pub colons: Colons,
pub allow_zulu: bool,
pub padding: Pad,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum OffsetPrecision {
Hours,
Minutes,
Seconds,
OptionalMinutes,
OptionalSeconds,
OptionalMinutesAndSeconds,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Colons {
None,
Colon,
Maybe,
}
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
pub enum Item<'a> {
Literal(&'a str),
OwnedLiteral(Box<str>),
Space(&'a str),
OwnedSpace(Box<str>),
Numeric(Numeric, Pad),
Fixed(Fixed),
Error,
}
#[cfg(feature = "defmt")]
impl<'a> defmt::Format for Item<'a> {
fn format(&self, f: defmt::Formatter) {
match self {
Item::Literal(v) => defmt::write!(f, "Literal {{ {} }}", v),
Item::OwnedLiteral(_) => {}
Item::Space(v) => defmt::write!(f, "Space {{ {} }}", v),
Item::OwnedSpace(_) => {}
Item::Numeric(u, v) => defmt::write!(f, "Numeric {{ {}, {} }}", u, v),
Item::Fixed(v) => defmt::write!(f, "Fixed {{ {} }}", v),
Item::Error => defmt::write!(f, "Error"),
}
}
}
const fn num(numeric: Numeric) -> Item<'static> {
Item::Numeric(numeric, Pad::None)
}
const fn num0(numeric: Numeric) -> Item<'static> {
Item::Numeric(numeric, Pad::Zero)
}
const fn nums(numeric: Numeric) -> Item<'static> {
Item::Numeric(numeric, Pad::Space)
}
const fn fixed(fixed: Fixed) -> Item<'static> {
Item::Fixed(fixed)
}
const fn internal_fixed(val: InternalInternal) -> Item<'static> {
Item::Fixed(Fixed::Internal(InternalFixed { val }))
}
impl Item<'_> {
pub fn to_owned(self) -> Item<'static> {
match self {
Item::Literal(s) => Item::OwnedLiteral(Box::from(s)),
Item::Space(s) => Item::OwnedSpace(Box::from(s)),
Item::Numeric(n, p) => Item::Numeric(n, p),
Item::Fixed(f) => Item::Fixed(f),
Item::OwnedLiteral(l) => Item::OwnedLiteral(l),
Item::OwnedSpace(s) => Item::OwnedSpace(s),
Item::Error => Item::Error,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ParseError(ParseErrorKind);
impl ParseError {
pub const fn kind(&self) -> ParseErrorKind {
self.0
}
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ParseErrorKind {
OutOfRange,
Impossible,
NotEnough,
Invalid,
TooShort,
TooLong,
BadFormat,
}
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"),
}
}
}
impl std::error::Error for ParseError {}
pub(crate) 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);
pub(crate) const TOO_LONG: ParseError = ParseError(ParseErrorKind::TooLong);
const BAD_FORMAT: ParseError = ParseError(ParseErrorKind::BadFormat);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn item_to_owned_converts_literal_and_space_variants() {
assert_eq!(Item::Literal("abc").to_owned(), Item::OwnedLiteral(Box::from("abc")));
assert_eq!(Item::Space(" ").to_owned(), Item::OwnedSpace(Box::from(" ")));
assert_eq!(
Item::OwnedLiteral(Box::from("xyz")).to_owned(),
Item::OwnedLiteral(Box::from("xyz"))
);
assert_eq!(Item::Numeric(Numeric::Year, Pad::Zero).to_owned(), num0(Numeric::Year));
assert_eq!(Item::Fixed(Fixed::RFC3339).to_owned(), fixed(Fixed::RFC3339));
assert_eq!(Item::Error.to_owned(), Item::Error);
}
#[test]
fn parse_error_kind_round_trips_through_the_accessor() {
assert_eq!(OUT_OF_RANGE.kind(), ParseErrorKind::OutOfRange);
assert_eq!(TOO_SHORT.kind(), ParseErrorKind::TooShort);
assert_eq!(TOO_LONG.kind(), ParseErrorKind::TooLong);
assert_eq!(BAD_FORMAT.kind(), ParseErrorKind::BadFormat);
assert_eq!(IMPOSSIBLE.kind(), ParseErrorKind::Impossible);
assert_eq!(NOT_ENOUGH.kind(), ParseErrorKind::NotEnough);
assert_eq!(INVALID.kind(), ParseErrorKind::Invalid);
}
#[test]
fn parse_error_display_messages_are_distinct_and_non_empty() {
let all = [OUT_OF_RANGE, IMPOSSIBLE, NOT_ENOUGH, INVALID, TOO_SHORT, TOO_LONG, BAD_FORMAT];
let mut messages: Vec<String> = all.iter().map(|e| e.to_string()).collect();
for msg in &messages {
assert!(!msg.is_empty());
}
let original_len = messages.len();
messages.sort();
messages.dedup();
assert_eq!(messages.len(), original_len, "each ParseErrorKind should have a distinct message");
}
#[test]
fn numeric_and_fixed_helper_constructors_use_the_expected_padding() {
assert_eq!(num(Numeric::Day), Item::Numeric(Numeric::Day, Pad::None));
assert_eq!(num0(Numeric::Day), Item::Numeric(Numeric::Day, Pad::Zero));
assert_eq!(nums(Numeric::Day), Item::Numeric(Numeric::Day, Pad::Space));
assert_eq!(fixed(Fixed::ShortMonthName), Item::Fixed(Fixed::ShortMonthName));
}
#[test]
fn pad_variants_are_distinguishable() {
assert_ne!(Pad::None, Pad::Zero);
assert_ne!(Pad::Zero, Pad::Space);
assert_ne!(Pad::None, Pad::Space);
}
}