use core::fmt::Debug;
use super::{
util::{
array_str::Abbreviation,
itime::{
IAmbiguousOffset, IDate, IDateTime, IOffset, ITime, ITimeSecond,
ITimestamp, IWeekday,
},
},
PosixDay, PosixDayTime, PosixDst, PosixOffset, PosixRule, PosixTime,
PosixTimeZone,
};
impl PosixTimeZone<Abbreviation> {
pub fn parse(
bytes: &[u8],
) -> Result<PosixTimeZone<Abbreviation>, PosixTimeZoneError> {
let parser = Parser { ianav3plus: true, ..Parser::new(bytes) };
parser.parse()
}
pub fn parse_prefix<'b>(
bytes: &'b [u8],
) -> Result<(PosixTimeZone<Abbreviation>, &'b [u8]), PosixTimeZoneError>
{
let parser = Parser { ianav3plus: true, ..Parser::new(bytes) };
parser.parse_prefix()
}
}
impl<ABBREV: AsRef<str> + Debug> PosixTimeZone<ABBREV> {
pub(crate) fn to_offset(&self, timestamp: ITimestamp) -> IOffset {
let std_offset = self.std_offset.to_ioffset();
if self.dst.is_none() {
return std_offset;
}
let dt = timestamp.to_datetime(IOffset::UTC);
self.dst_info_utc(dt.date.year)
.filter(|dst_info| dst_info.in_dst(dt))
.map(|dst_info| dst_info.offset().to_ioffset())
.unwrap_or_else(|| std_offset)
}
pub(crate) fn to_offset_info(
&self,
timestamp: ITimestamp,
) -> (IOffset, &'_ str, bool) {
let std_offset = self.std_offset.to_ioffset();
if self.dst.is_none() {
return (std_offset, self.std_abbrev.as_ref(), false);
}
let dt = timestamp.to_datetime(IOffset::UTC);
self.dst_info_utc(dt.date.year)
.filter(|dst_info| dst_info.in_dst(dt))
.map(|dst_info| {
(
dst_info.offset().to_ioffset(),
dst_info.dst.abbrev.as_ref(),
true,
)
})
.unwrap_or_else(|| (std_offset, self.std_abbrev.as_ref(), false))
}
pub(crate) fn to_ambiguous_kind(&self, dt: IDateTime) -> IAmbiguousOffset {
let year = dt.date.year;
let std_offset = self.std_offset.to_ioffset();
let Some(dst_info) = self.dst_info_wall(year) else {
return IAmbiguousOffset::Unambiguous { offset: std_offset };
};
let dst_offset = dst_info.offset().to_ioffset();
let diff = dst_offset.second - std_offset.second;
if diff == 0 {
debug_assert_eq!(std_offset, dst_offset);
IAmbiguousOffset::Unambiguous { offset: std_offset }
} else if diff.is_negative() {
if dst_info.in_dst(dt) {
IAmbiguousOffset::Unambiguous { offset: dst_offset }
} else {
let fold_start = dst_info.start.saturating_add_seconds(diff);
let gap_end =
dst_info.end.saturating_add_seconds(diff.saturating_neg());
if fold_start <= dt && dt < dst_info.start {
IAmbiguousOffset::Fold {
before: std_offset,
after: dst_offset,
}
} else if dst_info.end <= dt && dt < gap_end {
IAmbiguousOffset::Gap {
before: dst_offset,
after: std_offset,
}
} else {
IAmbiguousOffset::Unambiguous { offset: std_offset }
}
}
} else {
if !dst_info.in_dst(dt) {
IAmbiguousOffset::Unambiguous { offset: std_offset }
} else {
let gap_end = dst_info.start.saturating_add_seconds(diff);
let fold_start =
dst_info.end.saturating_add_seconds(diff.saturating_neg());
if dst_info.start <= dt && dt < gap_end {
IAmbiguousOffset::Gap {
before: std_offset,
after: dst_offset,
}
} else if fold_start <= dt && dt < dst_info.end {
IAmbiguousOffset::Fold {
before: dst_offset,
after: std_offset,
}
} else {
IAmbiguousOffset::Unambiguous { offset: dst_offset }
}
}
}
}
pub(crate) fn previous_transition(
&self,
timestamp: ITimestamp,
) -> Option<(ITimestamp, IOffset, &'_ str, bool)> {
let dt = timestamp.to_datetime(IOffset::UTC);
let dst_info = self.dst_info_utc(dt.date.year)?;
let (earlier, later) = dst_info.ordered();
let (prev, dst_info) = if dt > later {
(later, dst_info)
} else if dt > earlier {
(earlier, dst_info)
} else {
let prev_year = dt.date.prev_year().ok()?;
let dst_info = self.dst_info_utc(prev_year)?;
let (_, later) = dst_info.ordered();
(later, dst_info)
};
let timestamp = prev.to_timestamp_checked(IOffset::UTC)?;
let dt = timestamp.to_datetime(IOffset::UTC);
let (offset, abbrev, dst) = if dst_info.in_dst(dt) {
(dst_info.offset(), dst_info.dst.abbrev.as_ref(), true)
} else {
(&self.std_offset, self.std_abbrev.as_ref(), false)
};
Some((timestamp, offset.to_ioffset(), abbrev, dst))
}
pub(crate) fn next_transition(
&self,
timestamp: ITimestamp,
) -> Option<(ITimestamp, IOffset, &'_ str, bool)> {
let dt = timestamp.to_datetime(IOffset::UTC);
let dst_info = self.dst_info_utc(dt.date.year)?;
let (earlier, later) = dst_info.ordered();
let (next, dst_info) = if dt < earlier {
(earlier, dst_info)
} else if dt < later {
(later, dst_info)
} else {
let next_year = dt.date.next_year().ok()?;
let dst_info = self.dst_info_utc(next_year)?;
let (earlier, _) = dst_info.ordered();
(earlier, dst_info)
};
let timestamp = next.to_timestamp_checked(IOffset::UTC)?;
let dt = timestamp.to_datetime(IOffset::UTC);
let (offset, abbrev, dst) = if dst_info.in_dst(dt) {
(dst_info.offset(), dst_info.dst.abbrev.as_ref(), true)
} else {
(&self.std_offset, self.std_abbrev.as_ref(), false)
};
Some((timestamp, offset.to_ioffset(), abbrev, dst))
}
fn dst_info_utc(&self, year: i16) -> Option<DstInfo<'_, ABBREV>> {
let dst = self.dst.as_ref()?;
let start =
dst.rule.start.to_datetime(year, self.std_offset.to_ioffset());
let mut end = dst.rule.end.to_datetime(year, dst.offset.to_ioffset());
if start.date.month == 1
&& start.date.day == 1
&& start.time == ITime::MIN
&& year
!= end.saturating_add_seconds(self.std_offset.second).date.year
{
end = IDateTime {
date: IDate { year, month: 12, day: 31 },
time: ITime::MAX,
};
}
Some(DstInfo { dst, start, end })
}
fn dst_info_wall(&self, year: i16) -> Option<DstInfo<'_, ABBREV>> {
let dst = self.dst.as_ref()?;
let start = dst.rule.start.to_datetime(year, IOffset::UTC);
let end = dst.rule.end.to_datetime(year, IOffset::UTC);
Some(DstInfo { dst, start, end })
}
#[cfg(test)]
fn rule(&self) -> &PosixRule {
&self.dst.as_ref().unwrap().rule
}
}
impl PosixDayTime {
pub(crate) fn to_datetime(&self, year: i16, offset: IOffset) -> IDateTime {
let mkmin = || IDateTime {
date: IDate { year, month: 1, day: 1 },
time: ITime::MIN,
};
let mkmax = || IDateTime {
date: IDate { year, month: 12, day: 31 },
time: ITime::MAX,
};
let Some(date) = self.date.to_date(year) else { return mkmax() };
let offset = self.time.second - offset.second;
let days = offset.div_euclid(86400);
let second = offset.rem_euclid(86400);
let Ok(date) = date.checked_add_days(days) else {
return if offset < 0 { mkmin() } else { mkmax() };
};
if date.year < year {
mkmin()
} else if date.year > year {
mkmax()
} else {
let time = ITimeSecond { second }.to_time();
IDateTime { date, time }
}
}
}
impl PosixDay {
fn to_date(&self, year: i16) -> Option<IDate> {
match *self {
PosixDay::JulianOne(day) => {
Some(
IDate::from_day_of_year_no_leap(year, day)
.expect("Julian `J day` should be in bounds"),
)
}
PosixDay::JulianZero(day) => {
IDate::from_day_of_year(year, day + 1).ok()
}
PosixDay::WeekdayOfMonth { month, week, weekday } => {
let weekday = IWeekday::from_sunday_zero_offset(weekday);
let first = IDate { year, month, day: 1 };
let week = if week == 5 { -1 } else { week };
debug_assert!(week == -1 || (1..=4).contains(&week));
Some(
first
.nth_weekday_of_month(week, weekday)
.expect("nth weekday always exists"),
)
}
}
}
}
impl PosixTime {
pub(crate) const DEFAULT: PosixTime = PosixTime { second: 2 * 60 * 60 };
}
impl PosixOffset {
fn to_ioffset(&self) -> IOffset {
IOffset { second: self.second }
}
}
#[derive(Debug, Eq, PartialEq)]
struct DstInfo<'a, ABBREV> {
dst: &'a PosixDst<ABBREV>,
start: IDateTime,
end: IDateTime,
}
impl<'a, ABBREV> DstInfo<'a, ABBREV> {
fn in_dst(&self, utc_dt: IDateTime) -> bool {
if self.start <= self.end {
self.start <= utc_dt && utc_dt < self.end
} else {
!(self.end <= utc_dt && utc_dt < self.start)
}
}
fn ordered(&self) -> (IDateTime, IDateTime) {
if self.start <= self.end {
(self.start, self.end)
} else {
(self.end, self.start)
}
}
fn offset(&self) -> &PosixOffset {
&self.dst.offset
}
}
#[derive(Debug)]
struct Parser<'s> {
tz: &'s [u8],
pos: core::cell::Cell<usize>,
ianav3plus: bool,
}
impl<'s> Parser<'s> {
fn new<B: ?Sized + AsRef<[u8]>>(tz: &'s B) -> Parser<'s> {
Parser {
tz: tz.as_ref(),
pos: core::cell::Cell::new(0),
ianav3plus: false,
}
}
fn parse(
&self,
) -> Result<PosixTimeZone<Abbreviation>, PosixTimeZoneError> {
let (time_zone, remaining) = self.parse_prefix()?;
if !remaining.is_empty() {
return Err(ErrorKind::FoundRemaining.into());
}
Ok(time_zone)
}
fn parse_prefix(
&self,
) -> Result<(PosixTimeZone<Abbreviation>, &'s [u8]), PosixTimeZoneError>
{
let time_zone = self.parse_posix_time_zone()?;
Ok((time_zone, self.remaining()))
}
fn parse_posix_time_zone(
&self,
) -> Result<PosixTimeZone<Abbreviation>, PosixTimeZoneError> {
if self.is_done() {
return Err(ErrorKind::Empty.into());
}
let std_abbrev =
self.parse_abbreviation().map_err(ErrorKind::AbbreviationStd)?;
let std_offset =
self.parse_posix_offset().map_err(ErrorKind::OffsetStd)?;
let mut dst = None;
if !self.is_done()
&& (self.byte().is_ascii_alphabetic() || self.byte() == b'<')
{
dst = Some(self.parse_posix_dst(&std_offset)?);
}
Ok(PosixTimeZone { std_abbrev, std_offset, dst })
}
fn parse_posix_dst(
&self,
std_offset: &PosixOffset,
) -> Result<PosixDst<Abbreviation>, PosixTimeZoneError> {
let abbrev =
self.parse_abbreviation().map_err(ErrorKind::AbbreviationDst)?;
if self.is_done() {
return Err(ErrorKind::FoundDstNoRule.into());
}
let mut offset = PosixOffset { second: std_offset.second + 3600 };
if self.byte() != b',' {
offset =
self.parse_posix_offset().map_err(ErrorKind::OffsetDst)?;
if self.is_done() {
return Err(ErrorKind::FoundDstNoRuleWithOffset.into());
}
}
if self.byte() != b',' {
return Err(ErrorKind::ExpectedCommaAfterDst.into());
}
if !self.bump() {
return Err(ErrorKind::FoundEndAfterComma.into());
}
let rule = self.parse_rule().map_err(ErrorKind::Rule)?;
Ok(PosixDst { abbrev, offset, rule })
}
fn parse_abbreviation(&self) -> Result<Abbreviation, AbbreviationError> {
if self.byte() == b'<' {
if !self.bump() {
return Err(AbbreviationError::Quoted(
QuotedAbbreviationError::UnexpectedEndAfterOpening,
));
}
self.parse_quoted_abbreviation().map_err(AbbreviationError::Quoted)
} else {
self.parse_unquoted_abbreviation()
.map_err(AbbreviationError::Unquoted)
}
}
fn parse_unquoted_abbreviation(
&self,
) -> Result<Abbreviation, UnquotedAbbreviationError> {
let start = self.pos();
for i in 0.. {
if !self.byte().is_ascii_alphabetic() {
break;
}
if i >= Abbreviation::capacity() {
return Err(UnquotedAbbreviationError::TooLong);
}
if !self.bump() {
break;
}
}
let end = self.pos();
let abbrev =
core::str::from_utf8(&self.tz[start..end]).map_err(|_| {
UnquotedAbbreviationError::InvalidUtf8
})?;
if abbrev.len() < 3 {
return Err(UnquotedAbbreviationError::TooShort);
}
Ok(Abbreviation::new(abbrev).unwrap())
}
fn parse_quoted_abbreviation(
&self,
) -> Result<Abbreviation, QuotedAbbreviationError> {
let start = self.pos();
for i in 0.. {
if !self.byte().is_ascii_alphanumeric()
&& self.byte() != b'+'
&& self.byte() != b'-'
{
break;
}
if i >= Abbreviation::capacity() {
return Err(QuotedAbbreviationError::TooLong);
}
if !self.bump() {
break;
}
}
let end = self.pos();
let abbrev =
core::str::from_utf8(&self.tz[start..end]).map_err(|_| {
QuotedAbbreviationError::InvalidUtf8
})?;
if self.is_done() {
return Err(QuotedAbbreviationError::UnexpectedEnd);
}
if self.byte() != b'>' {
return Err(QuotedAbbreviationError::UnexpectedLastByte);
}
self.bump();
if abbrev.len() < 3 {
return Err(QuotedAbbreviationError::TooShort);
}
Ok(Abbreviation::new(abbrev).unwrap())
}
fn parse_posix_offset(&self) -> Result<PosixOffset, PosixOffsetError> {
let sign = self.parse_optional_sign()?.unwrap_or(1);
let hour = self.parse_hour_posix()?;
let (mut minute, mut second) = (0, 0);
if self.maybe_byte() == Some(b':') {
if !self.bump() {
return Err(PosixOffsetError::IncompleteMinutes);
}
minute = self.parse_minute()?;
if self.maybe_byte() == Some(b':') {
if !self.bump() {
return Err(PosixOffsetError::IncompleteSeconds);
}
second = self.parse_second()?;
}
}
let mut offset = PosixOffset { second: i32::from(hour) * 3600 };
offset.second += i32::from(minute) * 60;
offset.second += i32::from(second);
offset.second *= i32::from(-sign);
assert!(
-89999 <= offset.second && offset.second <= 89999,
"POSIX offset seconds {} is out of range",
offset.second
);
Ok(offset)
}
fn parse_rule(&self) -> Result<PosixRule, PosixRuleError> {
let start = self
.parse_posix_datetime()
.map_err(PosixRuleError::DateTimeStart)?;
if self.maybe_byte() != Some(b',') || !self.bump() {
return Err(PosixRuleError::ExpectedEnd);
}
let end = self
.parse_posix_datetime()
.map_err(PosixRuleError::DateTimeEnd)?;
Ok(PosixRule { start, end })
}
fn parse_posix_datetime(
&self,
) -> Result<PosixDayTime, PosixDateTimeError> {
let mut daytime = PosixDayTime {
date: self.parse_posix_date()?,
time: PosixTime::DEFAULT,
};
if self.maybe_byte() != Some(b'/') {
return Ok(daytime);
}
if !self.bump() {
return Err(PosixDateTimeError::ExpectedTime);
}
daytime.time = self.parse_posix_time()?;
Ok(daytime)
}
fn parse_posix_date(&self) -> Result<PosixDay, PosixDateError> {
match self.byte() {
b'J' => {
if !self.bump() {
return Err(PosixDateError::ExpectedJulianNoLeap);
}
Ok(PosixDay::JulianOne(self.parse_posix_julian_day_no_leap()?))
}
b'0'..=b'9' => Ok(PosixDay::JulianZero(
self.parse_posix_julian_day_with_leap()?,
)),
b'M' => {
if !self.bump() {
return Err(PosixDateError::ExpectedMonthWeekWeekday);
}
let (month, week, weekday) = self.parse_weekday_of_month()?;
Ok(PosixDay::WeekdayOfMonth { month, week, weekday })
}
_ => Err(PosixDateError::UnexpectedByte),
}
}
fn parse_posix_julian_day_no_leap(
&self,
) -> Result<i16, PosixJulianNoLeapError> {
let number = self
.parse_number_with_upto_n_digits(3)
.map_err(PosixJulianNoLeapError::Parse)?;
let number = i16::try_from(number)
.map_err(|_| PosixJulianNoLeapError::Range)?;
if !(1 <= number && number <= 365) {
return Err(PosixJulianNoLeapError::Range);
}
Ok(number)
}
fn parse_posix_julian_day_with_leap(
&self,
) -> Result<i16, PosixJulianLeapError> {
let number = self
.parse_number_with_upto_n_digits(3)
.map_err(PosixJulianLeapError::Parse)?;
let number =
i16::try_from(number).map_err(|_| PosixJulianLeapError::Range)?;
if !(0 <= number && number <= 365) {
return Err(PosixJulianLeapError::Range);
}
Ok(number)
}
fn parse_weekday_of_month(
&self,
) -> Result<(i8, i8, i8), WeekdayOfMonthError> {
let month = self.parse_month()?;
if self.maybe_byte() != Some(b'.') {
return Err(WeekdayOfMonthError::ExpectedDotAfterMonth);
}
if !self.bump() {
return Err(WeekdayOfMonthError::ExpectedWeekAfterMonth);
}
let week = self.parse_week()?;
if self.maybe_byte() != Some(b'.') {
return Err(WeekdayOfMonthError::ExpectedDotAfterWeek);
}
if !self.bump() {
return Err(WeekdayOfMonthError::ExpectedDayOfWeekAfterWeek);
}
let weekday = self.parse_weekday()?;
Ok((month, week, weekday))
}
fn parse_posix_time(&self) -> Result<PosixTime, PosixTimeError> {
let (sign, hour) = if self.ianav3plus {
let sign = self.parse_optional_sign()?.unwrap_or(1);
let hour = self.parse_hour_ianav3plus()?;
(sign, hour)
} else {
(1, i16::from(self.parse_hour_posix()?))
};
let (mut minute, mut second) = (0, 0);
if self.maybe_byte() == Some(b':') {
if !self.bump() {
return Err(PosixTimeError::IncompleteMinutes);
}
minute = self.parse_minute()?;
if self.maybe_byte() == Some(b':') {
if !self.bump() {
return Err(PosixTimeError::IncompleteSeconds);
}
second = self.parse_second()?;
}
}
let mut time = PosixTime { second: i32::from(hour) * 3600 };
time.second += i32::from(minute) * 60;
time.second += i32::from(second);
time.second *= i32::from(sign);
assert!(
-604799 <= time.second && time.second <= 604799,
"POSIX time seconds {} is out of range",
time.second
);
Ok(time)
}
fn parse_month(&self) -> Result<i8, MonthError> {
let number = self
.parse_number_with_upto_n_digits(2)
.map_err(MonthError::Parse)?;
let number = i8::try_from(number).map_err(|_| MonthError::Range)?;
if !(1 <= number && number <= 12) {
return Err(MonthError::Range);
}
Ok(number)
}
fn parse_week(&self) -> Result<i8, WeekOfMonthError> {
let number = self
.parse_number_with_exactly_n_digits(1)
.map_err(WeekOfMonthError::Parse)?;
let number =
i8::try_from(number).map_err(|_| WeekOfMonthError::Range)?;
if !(1 <= number && number <= 5) {
return Err(WeekOfMonthError::Range);
}
Ok(number)
}
fn parse_weekday(&self) -> Result<i8, WeekdayError> {
let number = self
.parse_number_with_exactly_n_digits(1)
.map_err(WeekdayError::Parse)?;
let number = i8::try_from(number).map_err(|_| WeekdayError::Range)?;
if !(0 <= number && number <= 6) {
return Err(WeekdayError::Range);
}
Ok(number)
}
fn parse_hour_ianav3plus(&self) -> Result<i16, HourIanaError> {
assert!(self.ianav3plus);
let number = self
.parse_number_with_upto_n_digits(3)
.map_err(HourIanaError::Parse)?;
let number =
i16::try_from(number).map_err(|_| HourIanaError::Range)?;
if !(0 <= number && number <= 167) {
return Err(HourIanaError::Range);
}
Ok(number)
}
fn parse_hour_posix(&self) -> Result<i8, HourPosixError> {
let number = self
.parse_number_with_upto_n_digits(2)
.map_err(HourPosixError::Parse)?;
let number =
i8::try_from(number).map_err(|_| HourPosixError::Range)?;
if !(0 <= number && number <= 24) {
return Err(HourPosixError::Range);
}
Ok(number)
}
fn parse_minute(&self) -> Result<i8, MinuteError> {
let number = self
.parse_number_with_exactly_n_digits(2)
.map_err(MinuteError::Parse)?;
let number = i8::try_from(number).map_err(|_| MinuteError::Range)?;
if !(0 <= number && number <= 59) {
return Err(MinuteError::Range);
}
Ok(number)
}
fn parse_second(&self) -> Result<i8, SecondError> {
let number = self
.parse_number_with_exactly_n_digits(2)
.map_err(SecondError::Parse)?;
let number = i8::try_from(number).map_err(|_| SecondError::Range)?;
if !(0 <= number && number <= 59) {
return Err(SecondError::Range);
}
Ok(number)
}
fn parse_number_with_exactly_n_digits(
&self,
n: usize,
) -> Result<i32, NumberError> {
assert!(n >= 1, "numbers must have at least 1 digit");
let mut number: i32 = 0;
for _ in 0..n {
if self.is_done() {
return Err(NumberError::ExpectedLength);
}
let byte = self.byte();
let digit = match byte.checked_sub(b'0') {
None => {
return Err(NumberError::InvalidDigit);
}
Some(digit) if digit > 9 => {
return Err(NumberError::InvalidDigit);
}
Some(digit) => {
debug_assert!((0..=9).contains(&digit));
i32::from(digit)
}
};
number = number
.checked_mul(10)
.and_then(|n| n.checked_add(digit))
.ok_or(NumberError::TooBig)?;
self.bump();
}
Ok(number)
}
fn parse_number_with_upto_n_digits(
&self,
n: usize,
) -> Result<i32, NumberError> {
assert!(n >= 1, "numbers must have at least 1 digit");
let mut number: i32 = 0;
for i in 0..n {
if self.is_done() || !self.byte().is_ascii_digit() {
if i == 0 {
return Err(NumberError::Empty);
}
break;
}
let digit = i32::from(self.byte() - b'0');
number = number
.checked_mul(10)
.and_then(|n| n.checked_add(digit))
.ok_or(NumberError::TooBig)?;
self.bump();
}
Ok(number)
}
fn parse_optional_sign(&self) -> Result<Option<i8>, OptionalSignError> {
if self.is_done() {
return Ok(None);
}
Ok(match self.byte() {
b'-' => {
if !self.bump() {
return Err(OptionalSignError::ExpectedDigitAfterMinus);
}
Some(-1)
}
b'+' => {
if !self.bump() {
return Err(OptionalSignError::ExpectedDigitAfterPlus);
}
Some(1)
}
_ => None,
})
}
}
impl<'s> Parser<'s> {
fn bump(&self) -> bool {
if self.is_done() {
return false;
}
self.pos.set(
self.pos().checked_add(1).expect("pos cannot overflow usize"),
);
!self.is_done()
}
fn is_done(&self) -> bool {
self.pos() == self.tz.len()
}
fn byte(&self) -> u8 {
self.tz[self.pos()]
}
fn maybe_byte(&self) -> Option<u8> {
self.tz.get(self.pos()).copied()
}
fn pos(&self) -> usize {
self.pos.get()
}
fn remaining(&self) -> &'s [u8] {
&self.tz[self.pos()..]
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PosixTimeZoneError {
kind: ErrorKind,
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum ErrorKind {
AbbreviationDst(AbbreviationError),
AbbreviationStd(AbbreviationError),
Empty,
ExpectedCommaAfterDst,
FoundDstNoRule,
FoundDstNoRuleWithOffset,
FoundEndAfterComma,
FoundRemaining,
OffsetDst(PosixOffsetError),
OffsetStd(PosixOffsetError),
Rule(PosixRuleError),
}
impl core::fmt::Display for PosixTimeZoneError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::ErrorKind::*;
match self.kind {
AbbreviationDst(ref err) => {
f.write_str("failed to parse DST time zone abbreviation: ")?;
core::fmt::Display::fmt(err, f)
}
AbbreviationStd(ref err) => {
f.write_str(
"failed to parse standard time zone abbreviation: ",
)?;
core::fmt::Display::fmt(err, f)
}
Empty => f.write_str(
"an empty string is not a valid POSIX time zone \
transition rule",
),
ExpectedCommaAfterDst => f.write_str(
"expected `,` after parsing DST offset \
in POSIX time zone string",
),
FoundDstNoRule => f.write_str(
"found DST abbreviation in POSIX time zone string, \
but no transition rule \
(this is technically allowed by POSIX, but has \
unspecified behavior)",
),
FoundDstNoRuleWithOffset => f.write_str(
"found DST abbreviation and offset in POSIX time zone string, \
but no transition rule \
(this is technically allowed by POSIX, but has \
unspecified behavior)",
),
FoundEndAfterComma => f.write_str(
"after parsing DST offset in POSIX time zone string, \
found end of string after a trailing `,`",
),
FoundRemaining => f.write_str(
"expected entire POSIX TZ string to be a valid \
time zone transition rule, but found data after \
parsing a valid time zone transition rule",
),
OffsetDst(ref err) => {
f.write_str("failed to parse DST offset: ")?;
core::fmt::Display::fmt(err, f)
}
OffsetStd(ref err) => {
f.write_str("failed to parse standard offset: ")?;
core::fmt::Display::fmt(err, f)
}
Rule(ref err) => core::fmt::Display::fmt(err, f),
}
}
}
impl From<ErrorKind> for PosixTimeZoneError {
fn from(kind: ErrorKind) -> PosixTimeZoneError {
PosixTimeZoneError { kind }
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum PosixOffsetError {
HourPosix(HourPosixError),
IncompleteMinutes,
IncompleteSeconds,
Minute(MinuteError),
OptionalSign(OptionalSignError),
Second(SecondError),
}
impl core::fmt::Display for PosixOffsetError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::PosixOffsetError::*;
match *self {
HourPosix(ref err) => core::fmt::Display::fmt(err, f),
IncompleteMinutes => f.write_str(
"incomplete time in \
POSIX time zone string (missing minutes)",
),
IncompleteSeconds => f.write_str(
"incomplete time in \
POSIX time zone string (missing seconds)",
),
Minute(ref err) => core::fmt::Display::fmt(err, f),
Second(ref err) => core::fmt::Display::fmt(err, f),
OptionalSign(ref err) => {
f.write_str(
"failed to parse sign for time offset \
POSIX time zone string",
)?;
core::fmt::Display::fmt(err, f)
}
}
}
}
impl From<HourPosixError> for PosixOffsetError {
fn from(err: HourPosixError) -> PosixOffsetError {
PosixOffsetError::HourPosix(err)
}
}
impl From<MinuteError> for PosixOffsetError {
fn from(err: MinuteError) -> PosixOffsetError {
PosixOffsetError::Minute(err)
}
}
impl From<OptionalSignError> for PosixOffsetError {
fn from(err: OptionalSignError) -> PosixOffsetError {
PosixOffsetError::OptionalSign(err)
}
}
impl From<SecondError> for PosixOffsetError {
fn from(err: SecondError) -> PosixOffsetError {
PosixOffsetError::Second(err)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum PosixRuleError {
DateTimeEnd(PosixDateTimeError),
DateTimeStart(PosixDateTimeError),
ExpectedEnd,
}
impl core::fmt::Display for PosixRuleError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::PosixRuleError::*;
match *self {
DateTimeEnd(ref err) => {
f.write_str("failed to parse end of DST transition rule: ")?;
core::fmt::Display::fmt(err, f)
}
DateTimeStart(ref err) => {
f.write_str("failed to parse start of DST transition rule: ")?;
core::fmt::Display::fmt(err, f)
}
ExpectedEnd => f.write_str(
"expected end of DST rule after parsing the start \
of the DST rule",
),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum PosixDateTimeError {
Date(PosixDateError),
ExpectedTime,
Time(PosixTimeError),
}
impl core::fmt::Display for PosixDateTimeError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::PosixDateTimeError::*;
match *self {
Date(ref err) => core::fmt::Display::fmt(err, f),
ExpectedTime => f.write_str(
"expected time specification after `/` following a date
specification in a POSIX time zone DST transition rule",
),
Time(ref err) => core::fmt::Display::fmt(err, f),
}
}
}
impl From<PosixDateError> for PosixDateTimeError {
fn from(err: PosixDateError) -> PosixDateTimeError {
PosixDateTimeError::Date(err)
}
}
impl From<PosixTimeError> for PosixDateTimeError {
fn from(err: PosixTimeError) -> PosixDateTimeError {
PosixDateTimeError::Time(err)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum PosixDateError {
ExpectedJulianNoLeap,
ExpectedMonthWeekWeekday,
JulianLeap(PosixJulianLeapError),
JulianNoLeap(PosixJulianNoLeapError),
UnexpectedByte,
WeekdayOfMonth(WeekdayOfMonthError),
}
impl core::fmt::Display for PosixDateError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::PosixDateError::*;
match *self {
ExpectedJulianNoLeap => f.write_str(
"expected one-based Julian day after `J` in date \
specification of a POSIX time zone DST \
transition rule, but found the end of input",
),
ExpectedMonthWeekWeekday => f.write_str(
"expected month-week-weekday after `M` in date \
specification of a POSIX time zone DST \
transition rule, but found the end of input",
),
JulianLeap(ref err) => core::fmt::Display::fmt(err, f),
JulianNoLeap(ref err) => core::fmt::Display::fmt(err, f),
UnexpectedByte => f.write_str(
"expected `J`, a digit or `M` at the beginning of a date \
specification of a POSIX time zone DST transition rule",
),
WeekdayOfMonth(ref err) => core::fmt::Display::fmt(err, f),
}
}
}
impl From<PosixJulianLeapError> for PosixDateError {
fn from(err: PosixJulianLeapError) -> PosixDateError {
PosixDateError::JulianLeap(err)
}
}
impl From<PosixJulianNoLeapError> for PosixDateError {
fn from(err: PosixJulianNoLeapError) -> PosixDateError {
PosixDateError::JulianNoLeap(err)
}
}
impl From<WeekdayOfMonthError> for PosixDateError {
fn from(err: WeekdayOfMonthError) -> PosixDateError {
PosixDateError::WeekdayOfMonth(err)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum PosixJulianNoLeapError {
Parse(NumberError),
Range,
}
impl core::fmt::Display for PosixJulianNoLeapError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::PosixJulianNoLeapError::*;
match *self {
Parse(ref err) => {
f.write_str("invalid one-based Julian day digits: ")?;
core::fmt::Display::fmt(err, f)
}
Range => f.write_str(
"parsed one-based Julian day, but it's not in supported \
range of `1..=365`",
),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum PosixJulianLeapError {
Parse(NumberError),
Range,
}
impl core::fmt::Display for PosixJulianLeapError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::PosixJulianLeapError::*;
match *self {
Parse(ref err) => {
f.write_str("invalid zero-based Julian day digits: ")?;
core::fmt::Display::fmt(err, f)
}
Range => f.write_str(
"parsed zero-based Julian day, but it's not in supported \
range of `0..=365`",
),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum AbbreviationError {
Quoted(QuotedAbbreviationError),
Unquoted(UnquotedAbbreviationError),
}
impl core::fmt::Display for AbbreviationError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::AbbreviationError::*;
match *self {
Quoted(ref err) => core::fmt::Display::fmt(err, f),
Unquoted(ref err) => core::fmt::Display::fmt(err, f),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum UnquotedAbbreviationError {
InvalidUtf8,
TooLong,
TooShort,
}
impl core::fmt::Display for UnquotedAbbreviationError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::UnquotedAbbreviationError::*;
match *self {
InvalidUtf8 => f.write_str(
"unquoted time zone abbreviation must be valid UTF-8",
),
TooLong => write!(
f,
"expected unquoted time zone abbreviation with at most \
{} bytes, but found an abbreviation that is longer",
Abbreviation::capacity(),
),
TooShort => f.write_str(
"expected unquoted time zone abbreviation to have length of \
3 or more bytes, but an abbreviation that is shorter",
),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum QuotedAbbreviationError {
InvalidUtf8,
TooLong,
TooShort,
UnexpectedEnd,
UnexpectedEndAfterOpening,
UnexpectedLastByte,
}
impl core::fmt::Display for QuotedAbbreviationError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::QuotedAbbreviationError::*;
match *self {
InvalidUtf8 => f.write_str(
"quoted time zone abbreviation must be valid UTF-8",
),
TooLong => write!(
f,
"expected quoted time zone abbreviation with at most \
{} bytes, but found an abbreviation that is longer",
Abbreviation::capacity(),
),
TooShort => f.write_str(
"expected quoted time zone abbreviation to have length of \
3 or more bytes, but an abbreviation that is shorter",
),
UnexpectedEnd => f.write_str(
"found non-empty quoted time zone abbreviation, but \
found end of input before an end-of-quoted abbreviation \
`>` character",
),
UnexpectedEndAfterOpening => f.write_str(
"found opening `<` quote for time zone abbreviation in \
POSIX time zone transition rule, and expected a name \
following it, but found the end of input instead",
),
UnexpectedLastByte => f.write_str(
"found non-empty quoted time zone abbreviation, but \
found did not find end-of-quoted abbreviation `>` \
character",
),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum WeekdayOfMonthError {
ExpectedDayOfWeekAfterWeek,
ExpectedDotAfterMonth,
ExpectedDotAfterWeek,
ExpectedWeekAfterMonth,
Month(MonthError),
WeekOfMonth(WeekOfMonthError),
Weekday(WeekdayError),
}
impl core::fmt::Display for WeekdayOfMonthError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::WeekdayOfMonthError::*;
match *self {
ExpectedDayOfWeekAfterWeek => f.write_str(
"expected day-of-week after week in POSIX time zone rule",
),
ExpectedDotAfterMonth => {
f.write_str("expected `.` after month in POSIX time zone rule")
}
ExpectedWeekAfterMonth => f.write_str(
"expected week after month in POSIX time zone rule",
),
ExpectedDotAfterWeek => {
f.write_str("expected `.` after week in POSIX time zone rule")
}
Month(ref err) => core::fmt::Display::fmt(err, f),
WeekOfMonth(ref err) => core::fmt::Display::fmt(err, f),
Weekday(ref err) => core::fmt::Display::fmt(err, f),
}
}
}
impl From<MonthError> for WeekdayOfMonthError {
fn from(err: MonthError) -> WeekdayOfMonthError {
WeekdayOfMonthError::Month(err)
}
}
impl From<WeekOfMonthError> for WeekdayOfMonthError {
fn from(err: WeekOfMonthError) -> WeekdayOfMonthError {
WeekdayOfMonthError::WeekOfMonth(err)
}
}
impl From<WeekdayError> for WeekdayOfMonthError {
fn from(err: WeekdayError) -> WeekdayOfMonthError {
WeekdayOfMonthError::Weekday(err)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum PosixTimeError {
HourIana(HourIanaError),
HourPosix(HourPosixError),
IncompleteMinutes,
IncompleteSeconds,
Minute(MinuteError),
OptionalSign(OptionalSignError),
Second(SecondError),
}
impl core::fmt::Display for PosixTimeError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::PosixTimeError::*;
match *self {
HourIana(ref err) => core::fmt::Display::fmt(err, f),
HourPosix(ref err) => core::fmt::Display::fmt(err, f),
IncompleteMinutes => f.write_str(
"incomplete time zone transition time in \
POSIX time zone string (missing minutes)",
),
IncompleteSeconds => f.write_str(
"incomplete time zone transition time in \
POSIX time zone string (missing seconds)",
),
Minute(ref err) => core::fmt::Display::fmt(err, f),
Second(ref err) => core::fmt::Display::fmt(err, f),
OptionalSign(ref err) => {
f.write_str(
"failed to parse sign for time zone transition time",
)?;
core::fmt::Display::fmt(err, f)
}
}
}
}
impl From<HourIanaError> for PosixTimeError {
fn from(err: HourIanaError) -> PosixTimeError {
PosixTimeError::HourIana(err)
}
}
impl From<HourPosixError> for PosixTimeError {
fn from(err: HourPosixError) -> PosixTimeError {
PosixTimeError::HourPosix(err)
}
}
impl From<MinuteError> for PosixTimeError {
fn from(err: MinuteError) -> PosixTimeError {
PosixTimeError::Minute(err)
}
}
impl From<OptionalSignError> for PosixTimeError {
fn from(err: OptionalSignError) -> PosixTimeError {
PosixTimeError::OptionalSign(err)
}
}
impl From<SecondError> for PosixTimeError {
fn from(err: SecondError) -> PosixTimeError {
PosixTimeError::Second(err)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum MonthError {
Parse(NumberError),
Range,
}
impl core::fmt::Display for MonthError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::MonthError::*;
match *self {
Parse(ref err) => {
f.write_str("invalid month digits: ")?;
core::fmt::Display::fmt(err, f)
}
Range => f.write_str(
"parsed month, but it's not in supported \
range of `1..=12`",
),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum WeekOfMonthError {
Parse(NumberError),
Range,
}
impl core::fmt::Display for WeekOfMonthError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::WeekOfMonthError::*;
match *self {
Parse(ref err) => {
f.write_str("invalid week-of-month digits: ")?;
core::fmt::Display::fmt(err, f)
}
Range => f.write_str(
"parsed week-of-month, but it's not in supported \
range of `1..=5`",
),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum WeekdayError {
Parse(NumberError),
Range,
}
impl core::fmt::Display for WeekdayError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::WeekdayError::*;
match *self {
Parse(ref err) => {
f.write_str("invalid weekday digits: ")?;
core::fmt::Display::fmt(err, f)
}
Range => f.write_str(
"parsed weekday, but it's not in supported \
range of `0..=6` (with `0` corresponding to Sunday)",
),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum HourIanaError {
Parse(NumberError),
Range,
}
impl core::fmt::Display for HourIanaError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::HourIanaError::*;
match *self {
Parse(ref err) => {
f.write_str("invalid hour digits: ")?;
core::fmt::Display::fmt(err, f)
}
Range => f.write_str(
"parsed hours, but it's not in supported \
range of `-167..=167`",
),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum HourPosixError {
Parse(NumberError),
Range,
}
impl core::fmt::Display for HourPosixError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::HourPosixError::*;
match *self {
Parse(ref err) => {
f.write_str("invalid hour digits: ")?;
core::fmt::Display::fmt(err, f)
}
Range => f.write_str(
"parsed hours, but it's not in supported \
range of `0..=24`",
),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum MinuteError {
Parse(NumberError),
Range,
}
impl core::fmt::Display for MinuteError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::MinuteError::*;
match *self {
Parse(ref err) => {
f.write_str("invalid minute digits: ")?;
core::fmt::Display::fmt(err, f)
}
Range => f.write_str(
"parsed minutes, but it's not in supported \
range of `0..=59`",
),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum SecondError {
Parse(NumberError),
Range,
}
impl core::fmt::Display for SecondError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::SecondError::*;
match *self {
Parse(ref err) => {
f.write_str("invalid second digits: ")?;
core::fmt::Display::fmt(err, f)
}
Range => f.write_str(
"parsed seconds, but it's not in supported \
range of `0..=59`",
),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum NumberError {
Empty,
ExpectedLength,
InvalidDigit,
TooBig,
}
impl core::fmt::Display for NumberError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::NumberError::*;
match *self {
Empty => f.write_str("invalid number, no digits found"),
ExpectedLength => f.write_str(
"expected a fixed number of digits, \
but found incorrect number",
),
InvalidDigit => f.write_str("expected digit in range `0..=9`"),
TooBig => f.write_str(
"parsed number too big to fit into a 32-bit signed integer",
),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum OptionalSignError {
ExpectedDigitAfterMinus,
ExpectedDigitAfterPlus,
}
impl core::fmt::Display for OptionalSignError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use self::OptionalSignError::*;
match *self {
ExpectedDigitAfterMinus => f.write_str(
"expected digit after `-` sign, \
but got end of input",
),
ExpectedDigitAfterPlus => f.write_str(
"expected digit after `+` sign, \
but got end of input",
),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn posix_time_zone(
input: impl AsRef<[u8]>,
) -> PosixTimeZone<Abbreviation> {
let input = input.as_ref();
let tz = PosixTimeZone::parse(input).unwrap();
#[cfg(feature = "alloc")]
{
use alloc::string::String;
fn to_string(tz: &PosixTimeZone<Abbreviation>) -> String {
let mut buf = String::new();
crate::fmt::temporal::DateTimePrinter::new()
.print_posix_time_zone(&tz, &mut buf)
.unwrap();
buf
}
let reparsed =
PosixTimeZone::parse(to_string(&tz).as_bytes()).unwrap();
assert_eq!(tz, reparsed);
assert_eq!(to_string(&tz), to_string(&reparsed));
}
tz
}
fn parser(s: &str) -> Parser<'_> {
Parser::new(s.as_bytes())
}
fn date(year: i16, month: i8, day: i8) -> IDate {
IDate { year, month, day }
}
#[test]
fn parse() {
let p = parser("NZST-12NZDT,J60,J300");
assert_eq!(
p.parse().unwrap(),
PosixTimeZone {
std_abbrev: "NZST".into(),
std_offset: PosixOffset { second: 12 * 60 * 60 },
dst: Some(PosixDst {
abbrev: "NZDT".into(),
offset: PosixOffset { second: 13 * 60 * 60 },
rule: PosixRule {
start: PosixDayTime {
date: PosixDay::JulianOne(60),
time: PosixTime { second: 2 * 60 * 60 },
},
end: PosixDayTime {
date: PosixDay::JulianOne(300),
time: PosixTime { second: 2 * 60 * 60 },
},
},
}),
},
);
let p = Parser::new("NZST-12NZDT,J60,J300WAT");
assert!(p.parse().is_err());
}
#[test]
fn parse_posix_time_zone() {
let p = Parser::new("NZST-12NZDT,M9.5.0,M4.1.0/3");
assert_eq!(
p.parse_posix_time_zone().unwrap(),
PosixTimeZone {
std_abbrev: "NZST".into(),
std_offset: PosixOffset { second: 12 * 60 * 60 },
dst: Some(PosixDst {
abbrev: "NZDT".into(),
offset: PosixOffset { second: 13 * 60 * 60 },
rule: PosixRule {
start: PosixDayTime {
date: PosixDay::WeekdayOfMonth {
month: 9,
week: 5,
weekday: 0,
},
time: PosixTime { second: 2 * 60 * 60 },
},
end: PosixDayTime {
date: PosixDay::WeekdayOfMonth {
month: 4,
week: 1,
weekday: 0,
},
time: PosixTime { second: 3 * 60 * 60 },
},
},
}),
},
);
let p = Parser::new("NZST-12NZDT,M9.5.0,M4.1.0/3WAT");
assert_eq!(
p.parse_posix_time_zone().unwrap(),
PosixTimeZone {
std_abbrev: "NZST".into(),
std_offset: PosixOffset { second: 12 * 60 * 60 },
dst: Some(PosixDst {
abbrev: "NZDT".into(),
offset: PosixOffset { second: 13 * 60 * 60 },
rule: PosixRule {
start: PosixDayTime {
date: PosixDay::WeekdayOfMonth {
month: 9,
week: 5,
weekday: 0,
},
time: PosixTime { second: 2 * 60 * 60 },
},
end: PosixDayTime {
date: PosixDay::WeekdayOfMonth {
month: 4,
week: 1,
weekday: 0,
},
time: PosixTime { second: 3 * 60 * 60 },
},
},
}),
},
);
let p = Parser::new("NZST-12NZDT,J60,J300");
assert_eq!(
p.parse_posix_time_zone().unwrap(),
PosixTimeZone {
std_abbrev: "NZST".into(),
std_offset: PosixOffset { second: 12 * 60 * 60 },
dst: Some(PosixDst {
abbrev: "NZDT".into(),
offset: PosixOffset { second: 13 * 60 * 60 },
rule: PosixRule {
start: PosixDayTime {
date: PosixDay::JulianOne(60),
time: PosixTime { second: 2 * 60 * 60 },
},
end: PosixDayTime {
date: PosixDay::JulianOne(300),
time: PosixTime { second: 2 * 60 * 60 },
},
},
}),
},
);
let p = Parser::new("NZST-12NZDT,J60,J300WAT");
assert_eq!(
p.parse_posix_time_zone().unwrap(),
PosixTimeZone {
std_abbrev: "NZST".into(),
std_offset: PosixOffset { second: 12 * 60 * 60 },
dst: Some(PosixDst {
abbrev: "NZDT".into(),
offset: PosixOffset { second: 13 * 60 * 60 },
rule: PosixRule {
start: PosixDayTime {
date: PosixDay::JulianOne(60),
time: PosixTime { second: 2 * 60 * 60 },
},
end: PosixDayTime {
date: PosixDay::JulianOne(300),
time: PosixTime { second: 2 * 60 * 60 },
},
},
}),
},
);
}
#[test]
fn parse_posix_dst() {
let p = Parser::new("NZDT,M9.5.0,M4.1.0/3");
assert_eq!(
p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(),
PosixDst {
abbrev: "NZDT".into(),
offset: PosixOffset { second: 13 * 60 * 60 },
rule: PosixRule {
start: PosixDayTime {
date: PosixDay::WeekdayOfMonth {
month: 9,
week: 5,
weekday: 0,
},
time: PosixTime { second: 2 * 60 * 60 },
},
end: PosixDayTime {
date: PosixDay::WeekdayOfMonth {
month: 4,
week: 1,
weekday: 0,
},
time: PosixTime { second: 3 * 60 * 60 },
},
},
},
);
let p = Parser::new("NZDT,J60,J300");
assert_eq!(
p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(),
PosixDst {
abbrev: "NZDT".into(),
offset: PosixOffset { second: 13 * 60 * 60 },
rule: PosixRule {
start: PosixDayTime {
date: PosixDay::JulianOne(60),
time: PosixTime { second: 2 * 60 * 60 },
},
end: PosixDayTime {
date: PosixDay::JulianOne(300),
time: PosixTime { second: 2 * 60 * 60 },
},
},
},
);
let p = Parser::new("NZDT-7,J60,J300");
assert_eq!(
p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(),
PosixDst {
abbrev: "NZDT".into(),
offset: PosixOffset { second: 7 * 60 * 60 },
rule: PosixRule {
start: PosixDayTime {
date: PosixDay::JulianOne(60),
time: PosixTime { second: 2 * 60 * 60 },
},
end: PosixDayTime {
date: PosixDay::JulianOne(300),
time: PosixTime { second: 2 * 60 * 60 },
},
},
},
);
let p = Parser::new("NZDT+7,J60,J300");
assert_eq!(
p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(),
PosixDst {
abbrev: "NZDT".into(),
offset: PosixOffset { second: -7 * 60 * 60 },
rule: PosixRule {
start: PosixDayTime {
date: PosixDay::JulianOne(60),
time: PosixTime { second: 2 * 60 * 60 },
},
end: PosixDayTime {
date: PosixDay::JulianOne(300),
time: PosixTime { second: 2 * 60 * 60 },
},
},
},
);
let p = Parser::new("NZDT7,J60,J300");
assert_eq!(
p.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 }).unwrap(),
PosixDst {
abbrev: "NZDT".into(),
offset: PosixOffset { second: -7 * 60 * 60 },
rule: PosixRule {
start: PosixDayTime {
date: PosixDay::JulianOne(60),
time: PosixTime { second: 2 * 60 * 60 },
},
end: PosixDayTime {
date: PosixDay::JulianOne(300),
time: PosixTime { second: 2 * 60 * 60 },
},
},
},
);
let p = Parser::new("NZDT7,");
assert!(p
.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 })
.is_err());
let p = Parser::new("NZDT7!");
assert!(p
.parse_posix_dst(&PosixOffset { second: 12 * 60 * 60 })
.is_err());
}
#[test]
fn parse_abbreviation() {
let p = Parser::new("ABC");
assert_eq!(p.parse_abbreviation().unwrap(), "ABC");
let p = Parser::new("<ABC>");
assert_eq!(p.parse_abbreviation().unwrap(), "ABC");
let p = Parser::new("<+09>");
assert_eq!(p.parse_abbreviation().unwrap(), "+09");
let p = Parser::new("+09");
assert!(p.parse_abbreviation().is_err());
}
#[test]
fn parse_unquoted_abbreviation() {
let p = Parser::new("ABC");
assert_eq!(p.parse_unquoted_abbreviation().unwrap(), "ABC");
let p = Parser::new("ABCXYZ");
assert_eq!(p.parse_unquoted_abbreviation().unwrap(), "ABCXYZ");
let p = Parser::new("ABC123");
assert_eq!(p.parse_unquoted_abbreviation().unwrap(), "ABC");
let tz = "a".repeat(30);
let p = Parser::new(&tz);
assert_eq!(p.parse_unquoted_abbreviation().unwrap(), &*tz);
let p = Parser::new("a");
assert!(p.parse_unquoted_abbreviation().is_err());
let p = Parser::new("ab");
assert!(p.parse_unquoted_abbreviation().is_err());
let p = Parser::new("ab1");
assert!(p.parse_unquoted_abbreviation().is_err());
let tz = "a".repeat(31);
let p = Parser::new(&tz);
assert!(p.parse_unquoted_abbreviation().is_err());
let p = Parser::new(b"ab\xFFcd");
assert!(p.parse_unquoted_abbreviation().is_err());
}
#[test]
fn parse_quoted_abbreviation() {
let p = Parser::new("ABC>");
assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABC");
let p = Parser::new("ABCXYZ>");
assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABCXYZ");
let p = Parser::new("ABC>123");
assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABC");
let p = Parser::new("ABC123>");
assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ABC123");
let p = Parser::new("ab1>");
assert_eq!(p.parse_quoted_abbreviation().unwrap(), "ab1");
let p = Parser::new("+09>");
assert_eq!(p.parse_quoted_abbreviation().unwrap(), "+09");
let p = Parser::new("-09>");
assert_eq!(p.parse_quoted_abbreviation().unwrap(), "-09");
let tz = alloc::format!("{}>", "a".repeat(30));
let p = Parser::new(&tz);
assert_eq!(
p.parse_quoted_abbreviation().unwrap(),
tz.trim_end_matches(">")
);
let p = Parser::new("a>");
assert!(p.parse_quoted_abbreviation().is_err());
let p = Parser::new("ab>");
assert!(p.parse_quoted_abbreviation().is_err());
let tz = alloc::format!("{}>", "a".repeat(31));
let p = Parser::new(&tz);
assert!(p.parse_quoted_abbreviation().is_err());
let p = Parser::new(b"ab\xFFcd>");
assert!(p.parse_quoted_abbreviation().is_err());
let p = Parser::new("ABC");
assert!(p.parse_quoted_abbreviation().is_err());
let p = Parser::new("ABC!>");
assert!(p.parse_quoted_abbreviation().is_err());
}
#[test]
fn parse_posix_offset() {
let p = Parser::new("5");
assert_eq!(p.parse_posix_offset().unwrap().second, -5 * 60 * 60);
let p = Parser::new("+5");
assert_eq!(p.parse_posix_offset().unwrap().second, -5 * 60 * 60);
let p = Parser::new("-5");
assert_eq!(p.parse_posix_offset().unwrap().second, 5 * 60 * 60);
let p = Parser::new("-12:34:56");
assert_eq!(
p.parse_posix_offset().unwrap().second,
12 * 60 * 60 + 34 * 60 + 56,
);
let p = Parser::new("a");
assert!(p.parse_posix_offset().is_err());
let p = Parser::new("-");
assert!(p.parse_posix_offset().is_err());
let p = Parser::new("+");
assert!(p.parse_posix_offset().is_err());
let p = Parser::new("-a");
assert!(p.parse_posix_offset().is_err());
let p = Parser::new("+a");
assert!(p.parse_posix_offset().is_err());
let p = Parser::new("-25");
assert!(p.parse_posix_offset().is_err());
let p = Parser::new("+25");
assert!(p.parse_posix_offset().is_err());
let p = Parser { ianav3plus: true, ..Parser::new("25") };
assert!(p.parse_posix_offset().is_err());
let p = Parser { ianav3plus: true, ..Parser::new("+25") };
assert!(p.parse_posix_offset().is_err());
let p = Parser { ianav3plus: true, ..Parser::new("-25") };
assert!(p.parse_posix_offset().is_err());
}
#[test]
fn parse_rule() {
let p = Parser::new("M9.5.0,M4.1.0/3");
assert_eq!(
p.parse_rule().unwrap(),
PosixRule {
start: PosixDayTime {
date: PosixDay::WeekdayOfMonth {
month: 9,
week: 5,
weekday: 0,
},
time: PosixTime { second: 2 * 60 * 60 },
},
end: PosixDayTime {
date: PosixDay::WeekdayOfMonth {
month: 4,
week: 1,
weekday: 0,
},
time: PosixTime { second: 3 * 60 * 60 },
},
},
);
let p = Parser::new("M9.5.0");
assert!(p.parse_rule().is_err());
let p = Parser::new(",M9.5.0,M4.1.0/3");
assert!(p.parse_rule().is_err());
let p = Parser::new("M9.5.0/");
assert!(p.parse_rule().is_err());
let p = Parser::new("M9.5.0,M4.1.0/");
assert!(p.parse_rule().is_err());
}
#[test]
fn parse_posix_datetime() {
let p = Parser::new("J1");
assert_eq!(
p.parse_posix_datetime().unwrap(),
PosixDayTime {
date: PosixDay::JulianOne(1),
time: PosixTime { second: 2 * 60 * 60 }
},
);
let p = Parser::new("J1/3");
assert_eq!(
p.parse_posix_datetime().unwrap(),
PosixDayTime {
date: PosixDay::JulianOne(1),
time: PosixTime { second: 3 * 60 * 60 }
},
);
let p = Parser::new("M4.1.0/3");
assert_eq!(
p.parse_posix_datetime().unwrap(),
PosixDayTime {
date: PosixDay::WeekdayOfMonth {
month: 4,
week: 1,
weekday: 0,
},
time: PosixTime { second: 3 * 60 * 60 },
},
);
let p = Parser::new("1/3:45:05");
assert_eq!(
p.parse_posix_datetime().unwrap(),
PosixDayTime {
date: PosixDay::JulianZero(1),
time: PosixTime { second: 3 * 60 * 60 + 45 * 60 + 5 },
},
);
let p = Parser::new("a");
assert!(p.parse_posix_datetime().is_err());
let p = Parser::new("J1/");
assert!(p.parse_posix_datetime().is_err());
let p = Parser::new("1/");
assert!(p.parse_posix_datetime().is_err());
let p = Parser::new("M4.1.0/");
assert!(p.parse_posix_datetime().is_err());
}
#[test]
fn parse_posix_date() {
let p = Parser::new("J1");
assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianOne(1));
let p = Parser::new("J365");
assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianOne(365));
let p = Parser::new("0");
assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianZero(0));
let p = Parser::new("1");
assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianZero(1));
let p = Parser::new("365");
assert_eq!(p.parse_posix_date().unwrap(), PosixDay::JulianZero(365));
let p = Parser::new("M9.5.0");
assert_eq!(
p.parse_posix_date().unwrap(),
PosixDay::WeekdayOfMonth { month: 9, week: 5, weekday: 0 },
);
let p = Parser::new("M9.5.6");
assert_eq!(
p.parse_posix_date().unwrap(),
PosixDay::WeekdayOfMonth { month: 9, week: 5, weekday: 6 },
);
let p = Parser::new("M09.5.6");
assert_eq!(
p.parse_posix_date().unwrap(),
PosixDay::WeekdayOfMonth { month: 9, week: 5, weekday: 6 },
);
let p = Parser::new("M12.1.1");
assert_eq!(
p.parse_posix_date().unwrap(),
PosixDay::WeekdayOfMonth { month: 12, week: 1, weekday: 1 },
);
let p = Parser::new("a");
assert!(p.parse_posix_date().is_err());
let p = Parser::new("j");
assert!(p.parse_posix_date().is_err());
let p = Parser::new("m");
assert!(p.parse_posix_date().is_err());
let p = Parser::new("n");
assert!(p.parse_posix_date().is_err());
let p = Parser::new("J366");
assert!(p.parse_posix_date().is_err());
let p = Parser::new("366");
assert!(p.parse_posix_date().is_err());
}
#[test]
fn parse_posix_julian_day_no_leap() {
let p = Parser::new("1");
assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 1);
let p = Parser::new("001");
assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 1);
let p = Parser::new("365");
assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 365);
let p = Parser::new("3655");
assert_eq!(p.parse_posix_julian_day_no_leap().unwrap(), 365);
let p = Parser::new("0");
assert!(p.parse_posix_julian_day_no_leap().is_err());
let p = Parser::new("366");
assert!(p.parse_posix_julian_day_no_leap().is_err());
}
#[test]
fn parse_posix_julian_day_with_leap() {
let p = Parser::new("0");
assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 0);
let p = Parser::new("1");
assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 1);
let p = Parser::new("001");
assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 1);
let p = Parser::new("365");
assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 365);
let p = Parser::new("3655");
assert_eq!(p.parse_posix_julian_day_with_leap().unwrap(), 365);
let p = Parser::new("366");
assert!(p.parse_posix_julian_day_with_leap().is_err());
}
#[test]
fn parse_weekday_of_month() {
let p = Parser::new("9.5.0");
assert_eq!(p.parse_weekday_of_month().unwrap(), (9, 5, 0));
let p = Parser::new("9.1.6");
assert_eq!(p.parse_weekday_of_month().unwrap(), (9, 1, 6));
let p = Parser::new("09.1.6");
assert_eq!(p.parse_weekday_of_month().unwrap(), (9, 1, 6));
let p = Parser::new("9");
assert!(p.parse_weekday_of_month().is_err());
let p = Parser::new("9.");
assert!(p.parse_weekday_of_month().is_err());
let p = Parser::new("9.5");
assert!(p.parse_weekday_of_month().is_err());
let p = Parser::new("9.5.");
assert!(p.parse_weekday_of_month().is_err());
let p = Parser::new("0.5.0");
assert!(p.parse_weekday_of_month().is_err());
let p = Parser::new("13.5.0");
assert!(p.parse_weekday_of_month().is_err());
let p = Parser::new("9.0.0");
assert!(p.parse_weekday_of_month().is_err());
let p = Parser::new("9.6.0");
assert!(p.parse_weekday_of_month().is_err());
let p = Parser::new("9.5.7");
assert!(p.parse_weekday_of_month().is_err());
}
#[test]
fn parse_posix_time() {
let p = Parser::new("5");
assert_eq!(p.parse_posix_time().unwrap().second, 5 * 60 * 60);
let p = Parser::new("22");
assert_eq!(p.parse_posix_time().unwrap().second, 22 * 60 * 60);
let p = Parser::new("02");
assert_eq!(p.parse_posix_time().unwrap().second, 2 * 60 * 60);
let p = Parser::new("5:45");
assert_eq!(
p.parse_posix_time().unwrap().second,
5 * 60 * 60 + 45 * 60
);
let p = Parser::new("5:45:12");
assert_eq!(
p.parse_posix_time().unwrap().second,
5 * 60 * 60 + 45 * 60 + 12
);
let p = Parser::new("5:45:129");
assert_eq!(
p.parse_posix_time().unwrap().second,
5 * 60 * 60 + 45 * 60 + 12
);
let p = Parser::new("5:45:12:");
assert_eq!(
p.parse_posix_time().unwrap().second,
5 * 60 * 60 + 45 * 60 + 12
);
let p = Parser { ianav3plus: true, ..Parser::new("+5:45:12") };
assert_eq!(
p.parse_posix_time().unwrap().second,
5 * 60 * 60 + 45 * 60 + 12
);
let p = Parser { ianav3plus: true, ..Parser::new("-5:45:12") };
assert_eq!(
p.parse_posix_time().unwrap().second,
-(5 * 60 * 60 + 45 * 60 + 12)
);
let p = Parser { ianav3plus: true, ..Parser::new("-167:45:12") };
assert_eq!(
p.parse_posix_time().unwrap().second,
-(167 * 60 * 60 + 45 * 60 + 12),
);
let p = Parser::new("25");
assert!(p.parse_posix_time().is_err());
let p = Parser::new("12:2");
assert!(p.parse_posix_time().is_err());
let p = Parser::new("12:");
assert!(p.parse_posix_time().is_err());
let p = Parser::new("12:23:5");
assert!(p.parse_posix_time().is_err());
let p = Parser::new("12:23:");
assert!(p.parse_posix_time().is_err());
let p = Parser { ianav3plus: true, ..Parser::new("168") };
assert!(p.parse_posix_time().is_err());
let p = Parser { ianav3plus: true, ..Parser::new("-168") };
assert!(p.parse_posix_time().is_err());
let p = Parser { ianav3plus: true, ..Parser::new("+168") };
assert!(p.parse_posix_time().is_err());
}
#[test]
fn parse_month() {
let p = Parser::new("1");
assert_eq!(p.parse_month().unwrap(), 1);
let p = Parser::new("01");
assert_eq!(p.parse_month().unwrap(), 1);
let p = Parser::new("12");
assert_eq!(p.parse_month().unwrap(), 12);
let p = Parser::new("0");
assert!(p.parse_month().is_err());
let p = Parser::new("00");
assert!(p.parse_month().is_err());
let p = Parser::new("001");
assert!(p.parse_month().is_err());
let p = Parser::new("13");
assert!(p.parse_month().is_err());
}
#[test]
fn parse_week() {
let p = Parser::new("1");
assert_eq!(p.parse_week().unwrap(), 1);
let p = Parser::new("5");
assert_eq!(p.parse_week().unwrap(), 5);
let p = Parser::new("55");
assert_eq!(p.parse_week().unwrap(), 5);
let p = Parser::new("0");
assert!(p.parse_week().is_err());
let p = Parser::new("6");
assert!(p.parse_week().is_err());
let p = Parser::new("00");
assert!(p.parse_week().is_err());
let p = Parser::new("01");
assert!(p.parse_week().is_err());
let p = Parser::new("05");
assert!(p.parse_week().is_err());
}
#[test]
fn parse_weekday() {
let p = Parser::new("0");
assert_eq!(p.parse_weekday().unwrap(), 0);
let p = Parser::new("1");
assert_eq!(p.parse_weekday().unwrap(), 1);
let p = Parser::new("6");
assert_eq!(p.parse_weekday().unwrap(), 6);
let p = Parser::new("00");
assert_eq!(p.parse_weekday().unwrap(), 0);
let p = Parser::new("06");
assert_eq!(p.parse_weekday().unwrap(), 0);
let p = Parser::new("60");
assert_eq!(p.parse_weekday().unwrap(), 6);
let p = Parser::new("7");
assert!(p.parse_weekday().is_err());
}
#[test]
fn parse_hour_posix() {
let p = Parser::new("5");
assert_eq!(p.parse_hour_posix().unwrap(), 5);
let p = Parser::new("0");
assert_eq!(p.parse_hour_posix().unwrap(), 0);
let p = Parser::new("00");
assert_eq!(p.parse_hour_posix().unwrap(), 0);
let p = Parser::new("24");
assert_eq!(p.parse_hour_posix().unwrap(), 24);
let p = Parser::new("100");
assert_eq!(p.parse_hour_posix().unwrap(), 10);
let p = Parser::new("25");
assert!(p.parse_hour_posix().is_err());
let p = Parser::new("99");
assert!(p.parse_hour_posix().is_err());
}
#[test]
fn parse_hour_ianav3plus() {
let new = |input| Parser { ianav3plus: true, ..Parser::new(input) };
let p = new("5");
assert_eq!(p.parse_hour_ianav3plus().unwrap(), 5);
let p = new("0");
assert_eq!(p.parse_hour_ianav3plus().unwrap(), 0);
let p = new("00");
assert_eq!(p.parse_hour_ianav3plus().unwrap(), 0);
let p = new("000");
assert_eq!(p.parse_hour_ianav3plus().unwrap(), 0);
let p = new("24");
assert_eq!(p.parse_hour_ianav3plus().unwrap(), 24);
let p = new("100");
assert_eq!(p.parse_hour_ianav3plus().unwrap(), 100);
let p = new("1000");
assert_eq!(p.parse_hour_ianav3plus().unwrap(), 100);
let p = new("167");
assert_eq!(p.parse_hour_ianav3plus().unwrap(), 167);
let p = new("168");
assert!(p.parse_hour_ianav3plus().is_err());
let p = new("999");
assert!(p.parse_hour_ianav3plus().is_err());
}
#[test]
fn parse_minute() {
let p = Parser::new("00");
assert_eq!(p.parse_minute().unwrap(), 0);
let p = Parser::new("24");
assert_eq!(p.parse_minute().unwrap(), 24);
let p = Parser::new("59");
assert_eq!(p.parse_minute().unwrap(), 59);
let p = Parser::new("599");
assert_eq!(p.parse_minute().unwrap(), 59);
let p = Parser::new("0");
assert!(p.parse_minute().is_err());
let p = Parser::new("1");
assert!(p.parse_minute().is_err());
let p = Parser::new("9");
assert!(p.parse_minute().is_err());
let p = Parser::new("60");
assert!(p.parse_minute().is_err());
}
#[test]
fn parse_second() {
let p = Parser::new("00");
assert_eq!(p.parse_second().unwrap(), 0);
let p = Parser::new("24");
assert_eq!(p.parse_second().unwrap(), 24);
let p = Parser::new("59");
assert_eq!(p.parse_second().unwrap(), 59);
let p = Parser::new("599");
assert_eq!(p.parse_second().unwrap(), 59);
let p = Parser::new("0");
assert!(p.parse_second().is_err());
let p = Parser::new("1");
assert!(p.parse_second().is_err());
let p = Parser::new("9");
assert!(p.parse_second().is_err());
let p = Parser::new("60");
assert!(p.parse_second().is_err());
}
#[test]
fn parse_number_with_exactly_n_digits() {
let p = Parser::new("1");
assert_eq!(p.parse_number_with_exactly_n_digits(1).unwrap(), 1);
let p = Parser::new("12");
assert_eq!(p.parse_number_with_exactly_n_digits(2).unwrap(), 12);
let p = Parser::new("123");
assert_eq!(p.parse_number_with_exactly_n_digits(2).unwrap(), 12);
let p = Parser::new("");
assert!(p.parse_number_with_exactly_n_digits(1).is_err());
let p = Parser::new("1");
assert!(p.parse_number_with_exactly_n_digits(2).is_err());
let p = Parser::new("12");
assert!(p.parse_number_with_exactly_n_digits(3).is_err());
}
#[test]
fn parse_number_with_upto_n_digits() {
let p = Parser::new("1");
assert_eq!(p.parse_number_with_upto_n_digits(1).unwrap(), 1);
let p = Parser::new("1");
assert_eq!(p.parse_number_with_upto_n_digits(2).unwrap(), 1);
let p = Parser::new("12");
assert_eq!(p.parse_number_with_upto_n_digits(2).unwrap(), 12);
let p = Parser::new("12");
assert_eq!(p.parse_number_with_upto_n_digits(3).unwrap(), 12);
let p = Parser::new("123");
assert_eq!(p.parse_number_with_upto_n_digits(2).unwrap(), 12);
let p = Parser::new("");
assert!(p.parse_number_with_upto_n_digits(1).is_err());
let p = Parser::new("a");
assert!(p.parse_number_with_upto_n_digits(1).is_err());
}
#[test]
fn to_dst_civil_datetime_utc_range() {
let tz = posix_time_zone("WART4WARST,J1/-3,J365/20");
let dst_info = DstInfo {
dst: tz.dst.as_ref().unwrap(),
start: date(2024, 1, 1).at(1, 0, 0, 0),
end: date(2024, 12, 31).at(23, 0, 0, 0),
};
assert_eq!(tz.dst_info_utc(2024), Some(dst_info));
let tz = posix_time_zone("WART4WARST,J1/-4,J365/21");
let dst_info = DstInfo {
dst: tz.dst.as_ref().unwrap(),
start: date(2024, 1, 1).at(0, 0, 0, 0),
end: date(2024, 12, 31).at(23, 59, 59, 999_999_999),
};
assert_eq!(tz.dst_info_utc(2024), Some(dst_info));
let tz = posix_time_zone("EST5EDT,M3.2.0,M11.1.0");
let dst_info = DstInfo {
dst: tz.dst.as_ref().unwrap(),
start: date(2024, 3, 10).at(7, 0, 0, 0),
end: date(2024, 11, 3).at(6, 0, 0, 0),
};
assert_eq!(tz.dst_info_utc(2024), Some(dst_info));
}
#[test]
fn regression_permanent_dst() {
let tz = posix_time_zone("XXX-2<+01>-1,0/0,J365/23");
let dst_info = DstInfo {
dst: tz.dst.as_ref().unwrap(),
start: date(2087, 1, 1).at(0, 0, 0, 0),
end: date(2087, 12, 31).at(23, 59, 59, 999_999_999),
};
assert_eq!(tz.dst_info_utc(2087), Some(dst_info));
}
#[test]
fn reasonable() {
assert!(PosixTimeZone::parse(b"EST5").is_ok());
assert!(PosixTimeZone::parse(b"EST5EDT").is_err());
assert!(PosixTimeZone::parse(b"EST5EDT,J1,J365").is_ok());
let tz = posix_time_zone("EST24EDT,J1,J365");
assert_eq!(
tz,
PosixTimeZone {
std_abbrev: "EST".into(),
std_offset: PosixOffset { second: -24 * 60 * 60 },
dst: Some(PosixDst {
abbrev: "EDT".into(),
offset: PosixOffset { second: -23 * 60 * 60 },
rule: PosixRule {
start: PosixDayTime {
date: PosixDay::JulianOne(1),
time: PosixTime::DEFAULT,
},
end: PosixDayTime {
date: PosixDay::JulianOne(365),
time: PosixTime::DEFAULT,
},
},
}),
},
);
let tz = posix_time_zone("EST-24EDT,J1,J365");
assert_eq!(
tz,
PosixTimeZone {
std_abbrev: "EST".into(),
std_offset: PosixOffset { second: 24 * 60 * 60 },
dst: Some(PosixDst {
abbrev: "EDT".into(),
offset: PosixOffset { second: 25 * 60 * 60 },
rule: PosixRule {
start: PosixDayTime {
date: PosixDay::JulianOne(1),
time: PosixTime::DEFAULT,
},
end: PosixDayTime {
date: PosixDay::JulianOne(365),
time: PosixTime::DEFAULT,
},
},
}),
},
);
}
#[test]
fn posix_date_time_spec_to_datetime() {
let to_datetime = |daytime: &PosixDayTime, year: i16| {
daytime.to_datetime(year, IOffset::UTC)
};
let tz = posix_time_zone("EST5EDT,J1,J365/5:12:34");
assert_eq!(
to_datetime(&tz.rule().start, 2023),
date(2023, 1, 1).at(2, 0, 0, 0),
);
assert_eq!(
to_datetime(&tz.rule().end, 2023),
date(2023, 12, 31).at(5, 12, 34, 0),
);
let tz = posix_time_zone("EST+5EDT,M3.2.0/2,M11.1.0/2");
assert_eq!(
to_datetime(&tz.rule().start, 2024),
date(2024, 3, 10).at(2, 0, 0, 0),
);
assert_eq!(
to_datetime(&tz.rule().end, 2024),
date(2024, 11, 3).at(2, 0, 0, 0),
);
let tz = posix_time_zone("EST+5EDT,M1.1.1,M12.5.2");
assert_eq!(
to_datetime(&tz.rule().start, 2024),
date(2024, 1, 1).at(2, 0, 0, 0),
);
assert_eq!(
to_datetime(&tz.rule().end, 2024),
date(2024, 12, 31).at(2, 0, 0, 0),
);
let tz = posix_time_zone("EST5EDT,0/0,J365/25");
assert_eq!(
to_datetime(&tz.rule().start, 2024),
date(2024, 1, 1).at(0, 0, 0, 0),
);
assert_eq!(
to_datetime(&tz.rule().end, 2024),
date(2024, 12, 31).at(23, 59, 59, 999_999_999),
);
let tz = posix_time_zone("XXX3EDT4,0/0,J365/23");
assert_eq!(
to_datetime(&tz.rule().start, 2024),
date(2024, 1, 1).at(0, 0, 0, 0),
);
assert_eq!(
to_datetime(&tz.rule().end, 2024),
date(2024, 12, 31).at(23, 0, 0, 0),
);
let tz = posix_time_zone("XXX3EDT4,0/0,365");
assert_eq!(
to_datetime(&tz.rule().end, 2023),
date(2023, 12, 31).at(23, 59, 59, 999_999_999),
);
assert_eq!(
to_datetime(&tz.rule().end, 2024),
date(2024, 12, 31).at(2, 0, 0, 0),
);
let tz = posix_time_zone("XXX3EDT4,J1/-167:59:59,J365/167:59:59");
assert_eq!(
to_datetime(&tz.rule().start, 2024),
date(2024, 1, 1).at(0, 0, 0, 0),
);
assert_eq!(
to_datetime(&tz.rule().end, 2024),
date(2024, 12, 31).at(23, 59, 59, 999_999_999),
);
}
#[test]
fn posix_date_time_spec_time() {
let tz = posix_time_zone("EST5EDT,J1,J365/5:12:34");
assert_eq!(tz.rule().start.time, PosixTime::DEFAULT);
assert_eq!(
tz.rule().end.time,
PosixTime { second: 5 * 60 * 60 + 12 * 60 + 34 },
);
}
#[test]
fn posix_date_spec_to_date() {
let tz = posix_time_zone("EST+5EDT,M3.2.0/2,M11.1.0/2");
let start = tz.rule().start.date.to_date(2023);
assert_eq!(start, Some(date(2023, 3, 12)));
let end = tz.rule().end.date.to_date(2023);
assert_eq!(end, Some(date(2023, 11, 5)));
let start = tz.rule().start.date.to_date(2024);
assert_eq!(start, Some(date(2024, 3, 10)));
let end = tz.rule().end.date.to_date(2024);
assert_eq!(end, Some(date(2024, 11, 3)));
let tz = posix_time_zone("EST+5EDT,J60,J365");
let start = tz.rule().start.date.to_date(2023);
assert_eq!(start, Some(date(2023, 3, 1)));
let end = tz.rule().end.date.to_date(2023);
assert_eq!(end, Some(date(2023, 12, 31)));
let start = tz.rule().start.date.to_date(2024);
assert_eq!(start, Some(date(2024, 3, 1)));
let end = tz.rule().end.date.to_date(2024);
assert_eq!(end, Some(date(2024, 12, 31)));
let tz = posix_time_zone("EST+5EDT,59,365");
let start = tz.rule().start.date.to_date(2023);
assert_eq!(start, Some(date(2023, 3, 1)));
let end = tz.rule().end.date.to_date(2023);
assert_eq!(end, None);
let start = tz.rule().start.date.to_date(2024);
assert_eq!(start, Some(date(2024, 2, 29)));
let end = tz.rule().end.date.to_date(2024);
assert_eq!(end, Some(date(2024, 12, 31)));
let tz = posix_time_zone("EST+5EDT,M1.1.1,M12.5.2");
let start = tz.rule().start.date.to_date(2024);
assert_eq!(start, Some(date(2024, 1, 1)));
let end = tz.rule().end.date.to_date(2024);
assert_eq!(end, Some(date(2024, 12, 31)));
}
#[test]
fn posix_time_spec_to_civil_time() {
let tz = posix_time_zone("EST5EDT,J1,J365/5:12:34");
assert_eq!(
tz.dst.as_ref().unwrap().rule.start.time.second,
2 * 60 * 60,
);
assert_eq!(
tz.dst.as_ref().unwrap().rule.end.time.second,
5 * 60 * 60 + 12 * 60 + 34,
);
let tz = posix_time_zone("EST5EDT,J1/23:59:59,J365/24:00:00");
assert_eq!(
tz.dst.as_ref().unwrap().rule.start.time.second,
23 * 60 * 60 + 59 * 60 + 59,
);
assert_eq!(
tz.dst.as_ref().unwrap().rule.end.time.second,
24 * 60 * 60,
);
let tz = posix_time_zone("EST5EDT,J1/-1,J365/167:00:00");
assert_eq!(
tz.dst.as_ref().unwrap().rule.start.time.second,
-1 * 60 * 60,
);
assert_eq!(
tz.dst.as_ref().unwrap().rule.end.time.second,
167 * 60 * 60,
);
}
#[test]
fn parse_iana() {
let p = PosixTimeZone::parse(b"CRAZY5SHORT,M12.5.0/50,0/2").unwrap();
assert_eq!(
p,
PosixTimeZone {
std_abbrev: "CRAZY".into(),
std_offset: PosixOffset { second: -5 * 60 * 60 },
dst: Some(PosixDst {
abbrev: "SHORT".into(),
offset: PosixOffset { second: -4 * 60 * 60 },
rule: PosixRule {
start: PosixDayTime {
date: PosixDay::WeekdayOfMonth {
month: 12,
week: 5,
weekday: 0,
},
time: PosixTime { second: 50 * 60 * 60 },
},
end: PosixDayTime {
date: PosixDay::JulianZero(0),
time: PosixTime { second: 2 * 60 * 60 },
},
},
}),
},
);
assert!(PosixTimeZone::parse(b"America/New_York").is_err());
assert!(PosixTimeZone::parse(b":America/New_York").is_err());
}
#[test]
fn parse_empty_is_err() {
assert!(PosixTimeZone::parse(b"").is_err());
}
#[test]
fn parse_weird_is_err() {
let s =
b"AAAAAAAAAAAAAAACAAAAAAAAAAAAQA8AACAAAAAAAAAAAAAAAAACAAAAAAAAAAA";
assert!(PosixTimeZone::parse(s).is_err());
let s =
b"<AAAAAAAAAAAAAAACAAAAAAAAAAAAQA>8<AACAAAAAAAAAAAAAAAAACAAAAAAAAAAA>";
assert!(PosixTimeZone::parse(s).is_err());
let s = b"PPPPPPPPPPPPPPPPPPPPnoofPPPAAA6DaPPPPPPPPPPPPPPPPPPPPPnoofPPPPP,n";
assert!(PosixTimeZone::parse(s).is_err());
let s = b"oooooooooovooooooooooooooooool9<ooooo2o-o-oooooookoorooooooooroo8";
assert!(PosixTimeZone::parse(s).is_err());
}
}