use crate::{
civil::{Date, DateTime, ISOWeekDate, Time, Weekday},
error::{fmt::strtime::Error as E, ErrorContext},
fmt::{
buffer::{ArrayBuffer, BorrowedWriter},
strtime::{parse::Parser, printer::Formatter},
Write,
},
tz::{Offset, OffsetConflict, TimeZone, TimeZoneDatabase},
util::{self, b, escape},
Error, Timestamp, Zoned,
};
mod parse;
mod printer;
#[inline]
pub fn parse(
format: impl AsRef<[u8]>,
input: impl AsRef<[u8]>,
) -> Result<BrokenDownTime, Error> {
BrokenDownTime::parse(format, input)
}
#[cfg(any(test, feature = "alloc"))]
#[inline]
pub fn format(
format: impl AsRef<[u8]>,
broken_down_time: impl Into<BrokenDownTime>,
) -> Result<alloc::string::String, Error> {
let broken_down_time: BrokenDownTime = broken_down_time.into();
let format = format.as_ref();
let mut buf = alloc::string::String::with_capacity(format.len());
broken_down_time.format(format, &mut buf)?;
Ok(buf)
}
#[derive(Clone, Debug)]
pub struct Config<C> {
custom: C,
lenient: bool,
}
impl Config<DefaultCustom> {
#[inline]
pub const fn new() -> Config<DefaultCustom> {
Config { custom: DefaultCustom::new(), lenient: false }
}
}
impl<C> Config<C> {
#[inline]
pub fn custom<U: Custom>(self, custom: U) -> Config<U> {
Config { custom, lenient: self.lenient }
}
#[inline]
pub fn lenient(self, yes: bool) -> Config<C> {
Config { lenient: yes, ..self }
}
}
pub trait Custom: Sized {
fn format_datetime<W: Write>(
&self,
config: &Config<Self>,
ext: &Extension,
tm: &BrokenDownTime,
wtr: &mut W,
) -> Result<(), Error> {
if matches!(ext.flag, Some(Flag::Uppercase)) {
tm.format_with_config(config, "%Y M%m %-d, %^a %H:%M:%S", wtr)
} else {
tm.format_with_config(config, "%Y M%m %-d, %a %H:%M:%S", wtr)
}
}
fn format_date<W: Write>(
&self,
config: &Config<Self>,
_ext: &Extension,
tm: &BrokenDownTime,
wtr: &mut W,
) -> Result<(), Error> {
tm.format_with_config(config, "%Y M%m %-d", wtr)
}
fn format_time<W: Write>(
&self,
config: &Config<Self>,
_ext: &Extension,
tm: &BrokenDownTime,
wtr: &mut W,
) -> Result<(), Error> {
tm.format_with_config(config, "%H:%M:%S", wtr)
}
fn format_12hour_time<W: Write>(
&self,
config: &Config<Self>,
ext: &Extension,
tm: &BrokenDownTime,
wtr: &mut W,
) -> Result<(), Error> {
if matches!(ext.flag, Some(Flag::Uppercase)) {
tm.format_with_config(config, "%-I:%M:%S %^p", wtr)
} else {
tm.format_with_config(config, "%-I:%M:%S %p", wtr)
}
}
}
#[derive(Clone, Debug, Default)]
pub struct DefaultCustom(());
impl DefaultCustom {
pub const fn new() -> DefaultCustom {
DefaultCustom(())
}
}
impl Custom for DefaultCustom {}
#[derive(Clone, Debug, Default)]
pub struct PosixCustom(());
impl PosixCustom {
pub const fn new() -> PosixCustom {
PosixCustom(())
}
}
impl Custom for PosixCustom {
fn format_datetime<W: Write>(
&self,
config: &Config<Self>,
ext: &Extension,
tm: &BrokenDownTime,
wtr: &mut W,
) -> Result<(), Error> {
if matches!(ext.flag, Some(Flag::Uppercase)) {
tm.format_with_config(config, "%^a %^b %e %H:%M:%S %Y", wtr)
} else {
tm.format_with_config(config, "%a %b %e %H:%M:%S %Y", wtr)
}
}
fn format_date<W: Write>(
&self,
config: &Config<Self>,
_ext: &Extension,
tm: &BrokenDownTime,
wtr: &mut W,
) -> Result<(), Error> {
tm.format_with_config(config, "%m/%d/%y", wtr)
}
fn format_time<W: Write>(
&self,
config: &Config<Self>,
_ext: &Extension,
tm: &BrokenDownTime,
wtr: &mut W,
) -> Result<(), Error> {
tm.format_with_config(config, "%H:%M:%S", wtr)
}
fn format_12hour_time<W: Write>(
&self,
config: &Config<Self>,
ext: &Extension,
tm: &BrokenDownTime,
wtr: &mut W,
) -> Result<(), Error> {
if matches!(ext.flag, Some(Flag::Uppercase)) {
tm.format_with_config(config, "%I:%M:%S %^p", wtr)
} else {
tm.format_with_config(config, "%I:%M:%S %p", wtr)
}
}
}
#[derive(Debug, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct BrokenDownTime {
year: Option<i16>,
month: Option<i8>,
day: Option<i8>,
day_of_year: Option<i16>,
iso_week_year: Option<i16>,
iso_week: Option<i8>,
week_sun: Option<i8>,
week_mon: Option<i8>,
hour: Option<i8>,
minute: Option<i8>,
second: Option<i8>,
subsec: Option<i32>,
offset: Option<Offset>,
weekday: Option<Weekday>,
meridiem: Option<Meridiem>,
timestamp: Option<Timestamp>,
tz: Option<TimeZone>,
#[cfg(feature = "alloc")]
iana: Option<alloc::string::String>,
#[cfg(feature = "alloc")]
iana_from_tz: bool,
}
impl BrokenDownTime {
#[inline]
pub fn parse(
format: impl AsRef<[u8]>,
input: impl AsRef<[u8]>,
) -> Result<BrokenDownTime, Error> {
BrokenDownTime::parse_mono(format.as_ref(), input.as_ref())
}
#[inline]
fn parse_mono(fmt: &[u8], inp: &[u8]) -> Result<BrokenDownTime, Error> {
let mut pieces = BrokenDownTime::default();
let mut p = Parser { fmt, inp, tm: &mut pieces };
p.parse().context(E::FailedStrptime)?;
if !p.inp.is_empty() {
return Err(Error::from(E::unconsumed(p.inp)));
}
Ok(pieces)
}
#[inline]
pub fn parse_prefix(
format: impl AsRef<[u8]>,
input: impl AsRef<[u8]>,
) -> Result<(BrokenDownTime, usize), Error> {
BrokenDownTime::parse_prefix_mono(format.as_ref(), input.as_ref())
}
#[inline]
fn parse_prefix_mono(
fmt: &[u8],
inp: &[u8],
) -> Result<(BrokenDownTime, usize), Error> {
let mkoffset = util::parse::offseter(inp);
let mut pieces = BrokenDownTime::default();
let mut p = Parser { fmt, inp, tm: &mut pieces };
p.parse().context(E::FailedStrptime)?;
let remainder = mkoffset(p.inp);
Ok((pieces, remainder))
}
#[inline]
pub fn format<W: Write>(
&self,
format: impl AsRef<[u8]>,
mut wtr: W,
) -> Result<(), Error> {
self.format_with_config(&Config::new(), format, &mut wtr)
}
#[inline]
pub fn format_with_config<W: Write, L: Custom>(
&self,
config: &Config<L>,
format: impl AsRef<[u8]>,
wtr: &mut W,
) -> Result<(), Error> {
let fmt = format.as_ref();
let mut buf = ArrayBuffer::<100>::default();
let mut bbuf = buf.as_borrowed();
let mut wtr = BorrowedWriter::new(&mut bbuf, wtr);
let mut formatter = Formatter { config, fmt, tm: self, wtr: &mut wtr };
formatter.format().context(E::FailedStrftime)?;
wtr.finish()
}
#[cfg(feature = "alloc")]
#[inline]
pub fn to_string(
&self,
format: impl AsRef<[u8]>,
) -> Result<alloc::string::String, Error> {
let format = format.as_ref();
let mut buf = alloc::string::String::with_capacity(format.len());
self.format(format, &mut buf)?;
Ok(buf)
}
#[cfg(feature = "alloc")]
#[inline]
pub fn to_string_with_config<L: Custom>(
&self,
config: &Config<L>,
format: impl AsRef<[u8]>,
) -> Result<alloc::string::String, Error> {
let format = format.as_ref();
let mut buf = alloc::string::String::with_capacity(format.len());
self.format_with_config(config, format, &mut buf)?;
Ok(buf)
}
#[inline]
pub fn to_zoned(&self) -> Result<Zoned, Error> {
self.to_zoned_with(crate::tz::db())
}
#[inline]
pub fn to_zoned_with(
&self,
db: &TimeZoneDatabase,
) -> Result<Zoned, Error> {
match (self.offset, self.iana_time_zone()) {
(None, None) => {
if let Some(ts) = self.timestamp {
return Ok(ts.to_zoned(TimeZone::unknown()));
}
Err(Error::from(E::ZonedOffsetOrTz))
}
(Some(offset), None) => {
let ts = match self.timestamp {
Some(ts) => ts,
None => {
let dt = self
.to_datetime()
.context(E::RequiredDateTimeForZoned)?;
let ts = offset
.to_timestamp(dt)
.context(E::RangeTimestamp)?;
ts
}
};
Ok(ts.to_zoned(TimeZone::fixed(offset)))
}
(None, Some(iana)) => {
let tz = db.get(iana)?;
match self.timestamp {
Some(ts) => Ok(ts.to_zoned(tz)),
None => {
let dt = self
.to_datetime()
.context(E::RequiredDateTimeForZoned)?;
Ok(tz.to_zoned(dt)?)
}
}
}
(Some(offset), Some(iana)) => {
let tz = db.get(iana)?;
match self.timestamp {
Some(ts) => {
let zdt = ts.to_zoned(tz);
if zdt.offset() != offset {
return Err(Error::from(E::MismatchOffset {
parsed: offset,
got: zdt.offset(),
}));
}
Ok(zdt)
}
None => {
let dt = self
.to_datetime()
.context(E::RequiredDateTimeForZoned)?;
let azdt =
OffsetConflict::Reject.resolve(dt, offset, tz)?;
let zdt = azdt.unambiguous().unwrap();
Ok(zdt)
}
}
}
}
}
#[inline]
pub fn to_timestamp(&self) -> Result<Timestamp, Error> {
if let Some(timestamp) = self.timestamp() {
return Ok(timestamp);
}
let dt =
self.to_datetime().context(E::RequiredDateTimeForTimestamp)?;
let offset = self.offset.ok_or(E::RequiredOffsetForTimestamp)?;
offset.to_timestamp(dt).context(E::RangeTimestamp)
}
#[inline]
pub fn to_datetime(&self) -> Result<DateTime, Error> {
let date = self.to_date().context(E::RequiredDateForDateTime)?;
let time = self.to_time().context(E::RequiredTimeForDateTime)?;
Ok(DateTime::from_parts(date, time))
}
#[inline]
pub fn to_date(&self) -> Result<Date, Error> {
#[cold]
#[inline(never)]
fn to_date(tm: &BrokenDownTime) -> Result<Date, Error> {
let Some(year) = tm.year else {
if let Some(date) = tm.to_date_from_iso()? {
return Ok(date);
}
return Err(Error::from(E::RequiredYearForDate));
};
let mut date = tm.to_date_from_gregorian(year)?;
if date.is_none() {
date = tm.to_date_from_iso()?;
}
if date.is_none() {
date = tm.to_date_from_day_of_year(year)?;
}
if date.is_none() {
date = tm.to_date_from_week_sun(year)?;
}
if date.is_none() {
date = tm.to_date_from_week_mon(year)?;
}
let Some(date) = date else {
return Err(Error::from(E::RequiredSomeDayForDate));
};
if let Some(weekday) = tm.weekday {
if weekday != date.weekday() {
return Err(Error::from(E::MismatchWeekday {
parsed: weekday,
got: date.weekday(),
}));
}
}
Ok(date)
}
let (Some(year), Some(month), Some(day)) =
(self.year, self.month, self.day)
else {
return to_date(self);
};
let date = Date::new(year, month, day).context(E::InvalidDate)?;
if let Some(weekday) = self.weekday {
if weekday != date.weekday() {
return Err(Error::from(E::MismatchWeekday {
parsed: weekday,
got: date.weekday(),
}));
}
}
Ok(date)
}
#[inline]
fn to_date_from_gregorian(
&self,
year: i16,
) -> Result<Option<Date>, Error> {
let (Some(month), Some(day)) = (self.month, self.day) else {
return Ok(None);
};
Ok(Some(Date::new(year, month, day).context(E::InvalidDate)?))
}
#[inline]
fn to_date_from_day_of_year(
&self,
year: i16,
) -> Result<Option<Date>, Error> {
let Some(doy) = self.day_of_year else { return Ok(None) };
Ok(Some({
let first = Date::new(year, 1, 1).unwrap();
first.with().day_of_year(doy).build().context(E::InvalidDate)?
}))
}
#[inline]
fn to_date_from_iso(&self) -> Result<Option<Date>, Error> {
let (Some(y), Some(w), Some(d)) =
(self.iso_week_year, self.iso_week, self.weekday)
else {
return Ok(None);
};
let wd = ISOWeekDate::new(y, w, d).context(E::InvalidISOWeekDate)?;
Ok(Some(wd.date()))
}
#[inline]
fn to_date_from_week_sun(&self, year: i16) -> Result<Option<Date>, Error> {
let (Some(week), Some(weekday)) = (self.week_sun, self.weekday) else {
return Ok(None);
};
let week = i16::from(week);
let wday = i16::from(weekday.to_sunday_zero_offset());
let first_of_year = Date::new(year, 1, 1).context(E::InvalidDate)?;
let first_sunday = first_of_year
.nth_weekday_of_month(1, Weekday::Sunday)
.map(|d| d.day_of_year())
.context(E::InvalidDate)?;
let doy = if week == 0 {
let days_before_first_sunday = 7 - wday;
let doy = first_sunday
.checked_sub(days_before_first_sunday)
.ok_or(E::InvalidWeekdaySunday { got: weekday })?;
if doy == 0 {
return Err(Error::from(E::InvalidWeekdaySunday {
got: weekday,
}));
}
doy
} else {
let days_since_first_sunday = (week - 1) * 7 + wday;
let doy = first_sunday + days_since_first_sunday;
doy
};
let date = first_of_year
.with()
.day_of_year(doy)
.build()
.context(E::InvalidDate)?;
Ok(Some(date))
}
#[inline]
fn to_date_from_week_mon(&self, year: i16) -> Result<Option<Date>, Error> {
let (Some(week), Some(weekday)) = (self.week_mon, self.weekday) else {
return Ok(None);
};
let week = i16::from(week);
let wday = i16::from(weekday.to_monday_zero_offset());
let first_of_year = Date::new(year, 1, 1).context(E::InvalidDate)?;
let first_monday = first_of_year
.nth_weekday_of_month(1, Weekday::Monday)
.map(|d| d.day_of_year())
.context(E::InvalidDate)?;
let doy = if week == 0 {
let days_before_first_monday = 7 - wday;
let doy = first_monday
.checked_sub(days_before_first_monday)
.ok_or(E::InvalidWeekdayMonday { got: weekday })?;
if doy == 0 {
return Err(Error::from(E::InvalidWeekdayMonday {
got: weekday,
}));
}
doy
} else {
let days_since_first_monday = (week - 1) * 7 + wday;
let doy = first_monday + days_since_first_monday;
doy
};
let date = first_of_year
.with()
.day_of_year(doy)
.build()
.context(E::InvalidDate)?;
Ok(Some(date))
}
#[inline]
pub fn to_time(&self) -> Result<Time, Error> {
let Some(hour) = self.hour() else {
if self.minute.is_some() {
return Err(Error::from(E::MissingTimeHourForMinute));
}
if self.second.is_some() {
return Err(Error::from(E::MissingTimeHourForSecond));
}
if self.subsec.is_some() {
return Err(Error::from(E::MissingTimeHourForFractional));
}
return Ok(Time::midnight());
};
let Some(minute) = self.minute() else {
if self.second.is_some() {
return Err(Error::from(E::MissingTimeMinuteForSecond));
}
if self.subsec.is_some() {
return Err(Error::from(E::MissingTimeMinuteForFractional));
}
return Time::new(hour, 0, 0, 0);
};
let Some(second) = self.second() else {
if self.subsec.is_some() {
return Err(Error::from(E::MissingTimeSecondForFractional));
}
return Time::new(hour, minute, 0, 0);
};
let Some(subsec) = self.subsec else {
return Time::new(hour, minute, second, 0);
};
Time::new(hour, minute, second, subsec)
}
#[inline]
pub fn year(&self) -> Option<i16> {
self.year
}
#[inline]
pub fn month(&self) -> Option<i8> {
self.month
}
#[inline]
pub fn day(&self) -> Option<i8> {
self.day
}
#[inline]
pub fn day_of_year(&self) -> Option<i16> {
self.day_of_year
}
#[inline]
pub fn iso_week_year(&self) -> Option<i16> {
self.iso_week_year
}
#[inline]
pub fn iso_week(&self) -> Option<i8> {
self.iso_week
}
#[inline]
pub fn sunday_based_week(&self) -> Option<i8> {
self.week_sun
}
#[inline]
pub fn monday_based_week(&self) -> Option<i8> {
self.week_mon
}
#[inline]
pub fn hour(&self) -> Option<i8> {
self.hour
}
#[inline]
pub fn minute(&self) -> Option<i8> {
self.minute
}
#[inline]
pub fn second(&self) -> Option<i8> {
self.second
}
#[inline]
pub fn subsec_nanosecond(&self) -> Option<i32> {
self.subsec
}
#[inline]
pub fn offset(&self) -> Option<Offset> {
self.offset
}
#[inline]
pub fn iana_time_zone(&self) -> Option<&str> {
#[cfg(feature = "alloc")]
{
if self.iana.is_none() && self.iana_from_tz {
return self.tz.as_ref().and_then(|tz| tz.iana_name());
}
self.iana.as_deref()
}
#[cfg(not(feature = "alloc"))]
{
None
}
}
#[inline]
pub fn weekday(&self) -> Option<Weekday> {
self.weekday
}
#[inline]
pub fn meridiem(&self) -> Option<Meridiem> {
self.meridiem
}
#[inline]
pub fn timestamp(&self) -> Option<Timestamp> {
self.timestamp
}
#[inline]
pub fn set_year(&mut self, year: Option<i16>) -> Result<(), Error> {
self.year = year.map(b::Year::check).transpose()?;
Ok(())
}
#[inline]
pub fn set_month(&mut self, month: Option<i8>) -> Result<(), Error> {
self.month = month.map(b::Month::check).transpose()?;
Ok(())
}
#[inline]
pub fn set_day(&mut self, day: Option<i8>) -> Result<(), Error> {
self.day = day.map(b::Day::check).transpose()?;
Ok(())
}
#[inline]
pub fn set_day_of_year(&mut self, day: Option<i16>) -> Result<(), Error> {
self.day_of_year = day.map(b::DayOfYear::check).transpose()?;
Ok(())
}
#[inline]
pub fn set_iso_week_year(
&mut self,
year: Option<i16>,
) -> Result<(), Error> {
self.iso_week_year = year.map(b::ISOYear::check).transpose()?;
Ok(())
}
#[inline]
pub fn set_iso_week(
&mut self,
week_number: Option<i8>,
) -> Result<(), Error> {
self.iso_week = week_number.map(b::ISOWeek::check).transpose()?;
Ok(())
}
#[inline]
pub fn set_sunday_based_week(
&mut self,
week_number: Option<i8>,
) -> Result<(), Error> {
self.week_sun = week_number.map(b::WeekNum::check).transpose()?;
Ok(())
}
#[inline]
pub fn set_monday_based_week(
&mut self,
week_number: Option<i8>,
) -> Result<(), Error> {
self.week_mon = week_number.map(b::WeekNum::check).transpose()?;
Ok(())
}
#[inline]
pub fn set_hour(&mut self, hour: Option<i8>) -> Result<(), Error> {
self.hour = hour.map(b::Hour::check).transpose()?;
if let Some(meridiem) = self.meridiem {
self.hour = self.hour.map(|hour| meridiem.adjust_hour(hour));
}
Ok(())
}
#[inline]
pub fn set_minute(&mut self, minute: Option<i8>) -> Result<(), Error> {
self.minute = minute.map(b::Minute::check).transpose()?;
Ok(())
}
#[inline]
pub fn set_second(&mut self, second: Option<i8>) -> Result<(), Error> {
self.second = second.map(b::Second::check).transpose()?;
Ok(())
}
#[inline]
pub fn set_subsec_nanosecond(
&mut self,
subsec_nanosecond: Option<i32>,
) -> Result<(), Error> {
self.subsec =
subsec_nanosecond.map(b::SubsecNanosecond::check).transpose()?;
Ok(())
}
#[inline]
pub fn set_offset(&mut self, offset: Option<Offset>) {
self.offset = offset;
}
#[cfg(feature = "alloc")]
#[inline]
pub fn set_iana_time_zone(&mut self, id: Option<alloc::string::String>) {
self.iana = id;
self.iana_from_tz = false;
}
#[inline]
pub fn set_weekday(&mut self, weekday: Option<Weekday>) {
self.weekday = weekday;
}
#[inline]
pub fn set_meridiem(&mut self, meridiem: Option<Meridiem>) {
if let Some(meridiem) = meridiem {
self.hour = self.hour.map(|hour| meridiem.adjust_hour(hour));
}
self.meridiem = meridiem;
}
#[inline]
pub fn set_timestamp(&mut self, timestamp: Option<Timestamp>) {
self.timestamp = timestamp;
}
}
impl<'a> From<&'a Zoned> for BrokenDownTime {
fn from(zdt: &'a Zoned) -> BrokenDownTime {
BrokenDownTime {
offset: Some(zdt.offset()),
timestamp: Some(zdt.timestamp()),
tz: Some(zdt.time_zone().clone()),
#[cfg(feature = "alloc")]
iana_from_tz: true,
..BrokenDownTime::from(zdt.datetime())
}
}
}
impl From<Timestamp> for BrokenDownTime {
fn from(ts: Timestamp) -> BrokenDownTime {
let dt = Offset::UTC.to_datetime(ts);
BrokenDownTime {
offset: Some(Offset::UTC),
timestamp: Some(ts),
..BrokenDownTime::from(dt)
}
}
}
impl From<DateTime> for BrokenDownTime {
fn from(dt: DateTime) -> BrokenDownTime {
let (d, t) = (dt.date(), dt.time());
BrokenDownTime {
year: Some(d.year()),
month: Some(d.month()),
day: Some(d.day()),
hour: Some(t.hour()),
minute: Some(t.minute()),
second: Some(t.second()),
subsec: Some(t.subsec_nanosecond()),
meridiem: Some(Meridiem::from(t)),
..BrokenDownTime::default()
}
}
}
impl From<Date> for BrokenDownTime {
fn from(d: Date) -> BrokenDownTime {
BrokenDownTime {
year: Some(d.year()),
month: Some(d.month()),
day: Some(d.day()),
..BrokenDownTime::default()
}
}
}
impl From<ISOWeekDate> for BrokenDownTime {
fn from(wd: ISOWeekDate) -> BrokenDownTime {
BrokenDownTime {
iso_week_year: Some(wd.year()),
iso_week: Some(wd.week()),
weekday: Some(wd.weekday()),
..BrokenDownTime::default()
}
}
}
impl From<Time> for BrokenDownTime {
fn from(t: Time) -> BrokenDownTime {
BrokenDownTime {
hour: Some(t.hour()),
minute: Some(t.minute()),
second: Some(t.second()),
subsec: Some(t.subsec_nanosecond()),
meridiem: Some(Meridiem::from(t)),
..BrokenDownTime::default()
}
}
}
pub struct Display<'f> {
pub(crate) fmt: &'f [u8],
pub(crate) tm: BrokenDownTime,
}
impl<'f> core::fmt::Display for Display<'f> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use crate::fmt::StdFmtWrite;
self.tm
.format_with_config(
&Config::new().lenient(true),
self.fmt,
&mut StdFmtWrite(f),
)
.map_err(|_| core::fmt::Error)
}
}
impl<'f> core::fmt::Debug for Display<'f> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
f.debug_struct("Display")
.field("fmt", &escape::Bytes(self.fmt))
.field("tm", &self.tm)
.finish()
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Meridiem {
AM,
PM,
}
impl Meridiem {
fn adjust_hour(self, hour: i8) -> i8 {
match self {
Meridiem::AM => hour % 12,
Meridiem::PM => (hour % 12) + 12,
}
}
}
impl From<Time> for Meridiem {
fn from(t: Time) -> Meridiem {
if t.hour() < 12 {
Meridiem::AM
} else {
Meridiem::PM
}
}
}
#[derive(Clone, Debug)]
pub struct Extension {
flag: Option<Flag>,
width: Option<u8>,
colons: u8,
}
impl Extension {
#[cfg_attr(feature = "perf-inline", inline(always))]
fn parse_flag<'i>(
fmt: &'i [u8],
) -> Result<(Option<Flag>, &'i [u8]), Error> {
let (&byte, tail) = fmt.split_first().unwrap();
let flag = match byte {
b'_' => Flag::PadSpace,
b'0' => Flag::PadZero,
b'-' => Flag::NoPad,
b'^' => Flag::Uppercase,
b'#' => Flag::Swapcase,
_ => return Ok((None, fmt)),
};
if tail.is_empty() {
return Err(Error::from(E::ExpectedDirectiveAfterFlag {
flag: byte,
}));
}
Ok((Some(flag), tail))
}
#[cfg_attr(feature = "perf-inline", inline(always))]
fn parse_width<'i>(
fmt: &'i [u8],
) -> Result<(Option<u8>, &'i [u8]), Error> {
let mut digits = 0;
while digits < fmt.len() && fmt[digits].is_ascii_digit() {
digits += 1;
}
if digits == 0 {
return Ok((None, fmt));
}
let (digits, fmt) = fmt.split_at(digits);
let width = util::parse::i64(digits).context(E::FailedWidth)?;
let width = u8::try_from(width).map_err(|_| E::RangeWidth)?;
if fmt.is_empty() {
return Err(Error::from(E::ExpectedDirectiveAfterWidth));
}
Ok((Some(width), fmt))
}
#[cfg_attr(feature = "perf-inline", inline(always))]
fn parse_colons<'i>(fmt: &'i [u8]) -> Result<(u8, &'i [u8]), Error> {
let mut colons = 0;
while colons < 3 && colons < fmt.len() && fmt[colons] == b':' {
colons += 1;
}
let fmt = &fmt[usize::from(colons)..];
if colons > 0 && fmt.is_empty() {
return Err(Error::from(E::ExpectedDirectiveAfterColons));
}
Ok((u8::try_from(colons).unwrap(), fmt))
}
}
#[derive(Clone, Copy, Debug)]
enum Flag {
PadSpace,
PadZero,
NoPad,
Uppercase,
Swapcase,
}
#[cfg_attr(feature = "perf-inline", inline(always))]
fn weekday_name_full(wd: Weekday) -> &'static str {
match wd {
Weekday::Sunday => "Sunday",
Weekday::Monday => "Monday",
Weekday::Tuesday => "Tuesday",
Weekday::Wednesday => "Wednesday",
Weekday::Thursday => "Thursday",
Weekday::Friday => "Friday",
Weekday::Saturday => "Saturday",
}
}
#[cfg_attr(feature = "perf-inline", inline(always))]
fn weekday_name_abbrev(wd: Weekday) -> &'static str {
match wd {
Weekday::Sunday => "Sun",
Weekday::Monday => "Mon",
Weekday::Tuesday => "Tue",
Weekday::Wednesday => "Wed",
Weekday::Thursday => "Thu",
Weekday::Friday => "Fri",
Weekday::Saturday => "Sat",
}
}
#[cfg_attr(feature = "perf-inline", inline(always))]
fn month_name_full(month: i8) -> &'static str {
match month {
1 => "January",
2 => "February",
3 => "March",
4 => "April",
5 => "May",
6 => "June",
7 => "July",
8 => "August",
9 => "September",
10 => "October",
11 => "November",
12 => "December",
unk => unreachable!("invalid month {unk}"),
}
}
#[cfg_attr(feature = "perf-inline", inline(always))]
fn month_name_abbrev(month: i8) -> &'static str {
match month {
1 => "Jan",
2 => "Feb",
3 => "Mar",
4 => "Apr",
5 => "May",
6 => "Jun",
7 => "Jul",
8 => "Aug",
9 => "Sep",
10 => "Oct",
11 => "Nov",
12 => "Dec",
unk => unreachable!("invalid month {unk}"),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_non_delimited() {
insta::assert_snapshot!(
Timestamp::strptime("%Y%m%d-%H%M%S%z", "20240730-005625+0400").unwrap(),
@"2024-07-29T20:56:25Z",
);
insta::assert_snapshot!(
Zoned::strptime("%Y%m%d-%H%M%S%z", "20240730-005625+0400").unwrap(),
@"2024-07-30T00:56:25+04:00[+04:00]",
);
}
#[test]
fn ok_non_ascii() {
let fmt = "%Y年%m月%d日,%H时%M分%S秒";
let dt = crate::civil::date(2022, 2, 4).at(3, 58, 59, 0);
insta::assert_snapshot!(
dt.strftime(fmt),
@"2022年02月04日,03时58分59秒",
);
insta::assert_debug_snapshot!(
DateTime::strptime(fmt, "2022年02月04日,03时58分59秒").unwrap(),
@"2022-02-04T03:58:59",
);
}
#[cfg(feature = "alloc")]
#[test]
fn iana_time_zone_from_zoned() {
if crate::tz::db().is_definitively_empty() {
return;
}
let zdt = crate::civil::date(2024, 7, 15)
.at(16, 24, 59, 0)
.in_tz("America/New_York")
.unwrap();
let mut tm = BrokenDownTime::from(&zdt);
assert_eq!(tm.iana_time_zone(), Some("America/New_York"));
insta::assert_snapshot!(
tm.to_string("%Q %Z").unwrap(),
@"America/New_York EDT",
);
tm.set_iana_time_zone(None);
assert_eq!(tm.iana_time_zone(), None);
insta::assert_snapshot!(tm.to_string("%Q %Z").unwrap(), @"-0400 EDT");
tm.set_iana_time_zone(Some("Australia/Tasmania".into()));
assert_eq!(tm.iana_time_zone(), Some("Australia/Tasmania"));
}
}