#![allow(dead_code)]
use core::{ops::RangeInclusive, str::FromStr};
use alloc::{
collections::BTreeMap,
string::{String, ToString},
vec,
vec::Vec,
};
use crate::{
civil::{Date, DateTime, Time, Weekday},
error::{
tz::zic::{Error as E, MAX_LINE_LEN},
Error, ErrorContext,
},
span::ToSpan,
timestamp::Timestamp,
tz::{Dst, Offset},
util::{b, parse, sync::Arc},
SignedDuration,
};
#[derive(Debug, Default, Eq, PartialEq)]
struct Zic {
zones: BTreeMap<String, Zone>,
links: BTreeMap<String, Zone>,
}
impl Zic {
fn new(_zicp: ZicP) -> Result<Zic, Error> {
todo!()
}
}
#[derive(Debug, Eq, PartialEq)]
struct TimeZone {
inner: Arc<TimeZoneInner>,
}
#[derive(Debug, Eq, PartialEq)]
struct TimeZoneInner {
name: String,
aliases: Vec<String>,
zones: Vec<Zone>,
}
#[derive(Debug, Eq, PartialEq)]
struct Zone {
offset: Offset,
rules: Rules,
format: ZoneFormatP,
until_timestamp: Timestamp,
until_wall: DateTime,
}
#[derive(Debug, Eq, PartialEq)]
struct Rules {
inner: Arc<RulesInner>,
}
#[derive(Debug, Eq, PartialEq)]
struct RulesInner {
name: String,
rules: Vec<Rule>,
}
impl Rules {
fn new(rulesp: Vec<RuleP>) -> Result<Rules, Error> {
assert!(!rulesp.is_empty(), "rule group must be non-empty");
let mut inner =
RulesInner { name: rulesp[0].name.name.clone(), rules: vec![] };
for r in rulesp {
assert_eq!(
inner.name, r.name.name,
"every name in rule group must be identical"
);
let dst = Dst::from(r.save.suffix() == RuleSaveSuffixP::Dst);
let offset = r.save.to_offset().with_context(|| {
E::FailedRule { name: inner.name.as_str().into() }
})?;
let years = r.years().with_context(|| E::FailedRule {
name: inner.name.as_str().into(),
})?;
let month = r.inn.month;
let letters = r.letters.part;
let day = r.on;
let at = r.at;
let rule = Rule { dst, offset, letters, years, month, day, at };
inner.rules.push(rule);
}
Ok(Rules { inner: Arc::new(inner) })
}
}
#[derive(Debug, Eq, PartialEq)]
struct Rule {
dst: Dst,
offset: Offset,
letters: String,
years: RangeInclusive<i16>,
month: i8,
day: RuleOnP,
at: RuleAtP,
}
#[derive(Debug, Default, Eq, PartialEq)]
struct ZicP {
rules: BTreeMap<String, Vec<RuleP>>,
zones: BTreeMap<String, ZoneP>,
links: BTreeMap<String, LinkP>,
}
impl ZicP {
fn parse(&mut self, src: &str) -> Result<(), Error> {
self.parse_with_fields(FieldParser::new(src))
}
fn parse_bytes(&mut self, src: &[u8]) -> Result<(), Error> {
self.parse_with_fields(FieldParser::from_bytes(src)?)
}
fn parse_with_fields(
&mut self,
mut parser: FieldParser<'_>,
) -> Result<(), Error> {
while parser.read_next_fields()? {
self.parse_one(&mut parser)
.context(E::Line { number: parser.line_number })?;
}
if let Some(ref name) = parser.continuation_zone_for {
return Err(Error::from(E::ExpectedContinuationZoneLine {
name: name.as_str().into(),
}));
}
Ok(())
}
fn parse_one(&mut self, p: &mut FieldParser<'_>) -> Result<(), Error> {
assert!(!p.fields.is_empty());
if let Some(name) = p.continuation_zone_for.take() {
let zone = ZoneContinuationP::parse(&p.fields)
.context(E::FailedContinuationZone)?;
let more_continuations = zone.until.is_some();
self.zones.get_mut(&name).unwrap().continuations.push(zone);
if more_continuations {
p.continuation_zone_for = Some(name);
}
return Ok(());
}
let (first, rest) = (&p.fields[0], &p.fields[1..]);
if first.starts_with("R") && "Rule".starts_with(first) {
let rule = RuleP::parse(rest).context(E::FailedRuleLine)?;
let name = rule.name.name.clone();
self.rules.entry(name).or_default().push(rule);
} else if first.starts_with("Z") && "Zone".starts_with(first) {
let first = ZoneFirstP::parse(rest).context(E::FailedZoneFirst)?;
let name = first.name.name.clone();
if first.until.is_some() {
p.continuation_zone_for = Some(name.clone());
}
let zone = ZoneP { first, continuations: vec![] };
if self.links.contains_key(&name) {
return Err(Error::from(E::DuplicateZoneLink {
name: name.into(),
}));
}
if let Some(previous_zone) = self.zones.insert(name, zone) {
return Err(Error::from(E::DuplicateZone {
name: previous_zone.first.name.name.into(),
}));
}
} else if first.starts_with("L") && "Link".starts_with(first) {
let link = LinkP::parse(rest).context(E::FailedLinkLine)?;
let name = link.name.name.clone();
if self.zones.contains_key(&name) {
return Err(Error::from(E::DuplicateLinkZone {
name: name.into(),
}));
}
if let Some(previous_link) = self.links.insert(name, link) {
return Err(Error::from(E::DuplicateLink {
name: previous_link.name.name.into(),
}));
}
} else {
return Err(Error::from(E::UnrecognizedZicLine)
.context(E::Line { number: p.line_number }));
}
Ok(())
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct RuleP {
name: RuleNameP,
from: RuleFromP,
to: RuleToP,
inn: RuleInP,
on: RuleOnP,
at: RuleAtP,
save: RuleSaveP,
letters: RuleLettersP,
}
impl RuleP {
fn parse(fields: &[&str]) -> Result<RuleP, Error> {
if fields.len() != 9 {
return Err(Error::from(E::ExpectedRuleNineFields {
got: fields.len(),
}));
}
let (name_field, fields) = (fields[0], &fields[1..]);
let (from_field, fields) = (fields[0], &fields[1..]);
let (to_field, fields) = (fields[0], &fields[1..]);
let (_reserved_field, fields) = (fields[0], &fields[1..]);
let (in_field, fields) = (fields[0], &fields[1..]);
let (on_field, fields) = (fields[0], &fields[1..]);
let (at_field, fields) = (fields[0], &fields[1..]);
let (save_field, fields) = (fields[0], &fields[1..]);
let letters_field = fields[0];
let name = name_field
.parse::<RuleNameP>()
.context(E::FailedParseFieldName)?;
let from = from_field
.parse::<RuleFromP>()
.context(E::FailedParseFieldFrom)?;
let to = to_field.parse::<RuleToP>().context(E::FailedParseFieldTo)?;
let inn =
in_field.parse::<RuleInP>().context(E::FailedParseFieldIn)?;
let on = on_field.parse::<RuleOnP>().context(E::FailedParseFieldOn)?;
let at = at_field.parse::<RuleAtP>().context(E::FailedParseFieldAt)?;
let save = save_field
.parse::<RuleSaveP>()
.context(E::FailedParseFieldSave)?;
let letters = letters_field
.parse::<RuleLettersP>()
.context(E::FailedParseFieldLetters)?;
Ok(RuleP { name, from, to, inn, on, at, save, letters })
}
fn years(&self) -> Result<RangeInclusive<i16>, Error> {
let start = self.from.year;
let end = match self.to {
RuleToP::Max => b::Year::MAX,
RuleToP::Only => start,
RuleToP::Year { year } => year,
};
if start > end {
return Err(Error::from(E::InvalidRuleYear {
start: start,
end: end,
}));
}
Ok(start..=end)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct ZoneP {
first: ZoneFirstP,
continuations: Vec<ZoneContinuationP>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct ZoneFirstP {
name: ZoneNameP,
stdoff: ZoneStdoffP,
rules: ZoneRulesP,
format: ZoneFormatP,
until: Option<ZoneUntilP>,
}
impl ZoneFirstP {
fn parse(fields: &[&str]) -> Result<ZoneFirstP, Error> {
if fields.len() < 4 {
return Err(Error::from(E::ExpectedFirstZoneFourFields));
}
let (name_field, fields) = (fields[0], &fields[1..]);
let (stdoff_field, fields) = (fields[0], &fields[1..]);
let (rules_field, fields) = (fields[0], &fields[1..]);
let (format_field, fields) = (fields[0], &fields[1..]);
let name = name_field
.parse::<ZoneNameP>()
.context(E::FailedParseFieldName)?;
let stdoff = stdoff_field
.parse::<ZoneStdoffP>()
.context(E::FailedParseFieldStdOff)?;
let rules = rules_field
.parse::<ZoneRulesP>()
.context(E::FailedParseFieldRules)?;
let format = format_field
.parse::<ZoneFormatP>()
.context(E::FailedParseFieldFormat)?;
let until = if fields.is_empty() {
None
} else {
Some(ZoneUntilP::parse(fields).context(E::FailedParseFieldUntil)?)
};
Ok(ZoneFirstP { name, stdoff, rules, format, until })
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct ZoneContinuationP {
stdoff: ZoneStdoffP,
rules: ZoneRulesP,
format: ZoneFormatP,
until: Option<ZoneUntilP>,
}
impl ZoneContinuationP {
fn parse(fields: &[&str]) -> Result<ZoneContinuationP, Error> {
if fields.len() < 3 {
return Err(Error::from(E::ExpectedContinuationZoneThreeFields));
}
let (stdoff_field, fields) = (fields[0], &fields[1..]);
let (rules_field, fields) = (fields[0], &fields[1..]);
let (format_field, fields) = (fields[0], &fields[1..]);
let stdoff = stdoff_field
.parse::<ZoneStdoffP>()
.context(E::FailedParseFieldStdOff)?;
let rules = rules_field
.parse::<ZoneRulesP>()
.context(E::FailedParseFieldRules)?;
let format = format_field
.parse::<ZoneFormatP>()
.context(E::FailedParseFieldFormat)?;
let until = if fields.is_empty() {
None
} else {
Some(ZoneUntilP::parse(fields).context(E::FailedParseFieldUntil)?)
};
Ok(ZoneContinuationP { stdoff, rules, format, until })
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct LinkP {
target: ZoneNameP,
name: ZoneNameP,
}
impl LinkP {
fn parse(fields: &[&str]) -> Result<LinkP, Error> {
if fields.len() != 2 {
return Err(Error::from(E::ExpectedLinkTwoFields));
}
let target = fields[0]
.parse::<ZoneNameP>()
.context(E::FailedParseFieldLinkTarget)?;
let name = fields[1]
.parse::<ZoneNameP>()
.context(E::FailedParseFieldLinkName)?;
Ok(LinkP { target, name })
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct RuleNameP {
name: String,
}
impl FromStr for RuleNameP {
type Err = Error;
fn from_str(name: &str) -> Result<RuleNameP, Error> {
if name.is_empty() {
Err(Error::from(E::ExpectedNonEmptyName))
} else if name.starts_with(|ch| matches!(ch, '0'..='9' | '+' | '-')) {
Err(Error::from(E::ExpectedNameBegin))
} else {
Ok(RuleNameP { name: name.to_string() })
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct RuleFromP {
year: i16,
}
impl FromStr for RuleFromP {
type Err = Error;
fn from_str(from: &str) -> Result<RuleFromP, Error> {
let year = parse_year(from).context(E::FailedParseFieldFrom)?;
Ok(RuleFromP { year })
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum RuleToP {
Max,
Only,
Year { year: i16 },
}
impl FromStr for RuleToP {
type Err = Error;
fn from_str(to: &str) -> Result<RuleToP, Error> {
if to.starts_with("m") && "maximum".starts_with(to) {
Ok(RuleToP::Max)
} else if to.starts_with("o") && "only".starts_with(to) {
Ok(RuleToP::Only)
} else {
let year = parse_year(to).context(E::FailedParseFieldTo)?;
Ok(RuleToP::Year { year })
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct RuleInP {
month: i8,
}
impl FromStr for RuleInP {
type Err = Error;
fn from_str(field: &str) -> Result<RuleInP, Error> {
static MONTH_PREFIXES: &[(i8, &str, &str)] = &[
(1, "January", "Ja"),
(2, "February", "F"),
(3, "March", "Mar"),
(4, "April", "Ap"),
(5, "May", "May"),
(6, "June", "Jun"),
(7, "July", "Jul"),
(8, "August", "Au"),
(9, "September", "S"),
(10, "October", "O"),
(11, "November", "N"),
(12, "December", "D"),
];
for &(month, name, prefix) in MONTH_PREFIXES {
if field.starts_with(prefix) && name.starts_with(field) {
return Ok(RuleInP { month });
}
}
Err(Error::from(E::UnrecognizedMonthName))
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum RuleOnP {
Day { day: i8 },
Last { weekday: Weekday },
OnOrBefore { weekday: Weekday, day: i8 },
OnOrAfter { weekday: Weekday, day: i8 },
}
impl RuleOnP {
fn date(&self, year: i16, month: i8) -> Result<Date, Error> {
match *self {
RuleOnP::Day { day } => Date::new(year, month, day),
RuleOnP::Last { weekday } => {
let date = Date::new(year, month, 1).unwrap();
date.nth_weekday_of_month(-1, weekday)
}
RuleOnP::OnOrBefore { weekday, day } => {
let start =
Date::new(year, month, day)?.checked_add(1.day())?;
start.nth_weekday(-1, weekday)
}
RuleOnP::OnOrAfter { weekday, day } => {
let start =
Date::new(year, month, day)?.checked_sub(1.day())?;
start.nth_weekday(1, weekday)
}
}
}
}
impl FromStr for RuleOnP {
type Err = Error;
fn from_str(field: &str) -> Result<RuleOnP, Error> {
if field.starts_with("last") {
let weekday = parse_weekday(&field[4..])?;
Ok(RuleOnP::Last { weekday })
} else if let Some(i) = field.find("<=") {
let weekday = parse_weekday(&field[..i])?;
let day = parse_day(&field[i + 2..])?;
Ok(RuleOnP::OnOrBefore { weekday, day })
} else if let Some(i) = field.find(">=") {
let weekday = parse_weekday(&field[..i])?;
let day = parse_day(&field[i + 2..])?;
Ok(RuleOnP::OnOrAfter { weekday, day })
} else if field.chars().all(|ch| ch.is_ascii_digit()) {
let day = parse_day(field)?;
Ok(RuleOnP::Day { day })
} else {
Err(Error::from(E::UnrecognizedDayOfMonthFormat))
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct RuleAtP {
dur: SignedDuration,
suffix: Option<RuleAtSuffixP>,
}
impl RuleAtP {
fn suffix(&self) -> RuleAtSuffixP {
self.suffix.unwrap_or_default()
}
}
impl FromStr for RuleAtP {
type Err = Error;
fn from_str(at: &str) -> Result<RuleAtP, Error> {
if at.is_empty() {
return Err(Error::from(E::ExpectedNonEmptyAt));
}
let (span_string, suffix_string) = at.split_at(at.len() - 1);
if suffix_string.chars().all(|ch| ch.is_ascii_alphabetic()) {
let span = parse_duration(span_string)?;
let suffix = suffix_string.parse()?;
Ok(RuleAtP { dur: span, suffix: Some(suffix) })
} else {
let span = parse_duration(at)?;
Ok(RuleAtP { dur: span, suffix: None })
}
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
enum RuleAtSuffixP {
#[default]
Wall,
Standard,
Universal,
}
impl FromStr for RuleAtSuffixP {
type Err = Error;
fn from_str(suffix: &str) -> Result<RuleAtSuffixP, Error> {
match suffix {
"w" => Ok(RuleAtSuffixP::Wall),
"s" => Ok(RuleAtSuffixP::Standard),
"u" | "g" | "z" => Ok(RuleAtSuffixP::Universal),
_ => Err(Error::from(E::UnrecognizedAtTimeSuffix)),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct RuleSaveP {
dur: SignedDuration,
suffix: Option<RuleSaveSuffixP>,
}
impl RuleSaveP {
fn to_offset(&self) -> Result<Offset, Error> {
let seconds = b::OffsetTotalSeconds::check(self.dur.as_secs())?;
Offset::from_seconds(seconds)
}
fn suffix(&self) -> RuleSaveSuffixP {
self.suffix.unwrap_or_else(|| {
if self.dur.is_zero() {
RuleSaveSuffixP::Standard
} else {
RuleSaveSuffixP::Dst
}
})
}
}
impl FromStr for RuleSaveP {
type Err = Error;
fn from_str(at: &str) -> Result<RuleSaveP, Error> {
if at.is_empty() {
return Err(Error::from(E::ExpectedNonEmptySave));
}
let (span_string, suffix_string) = at.split_at(at.len() - 1);
if suffix_string.chars().all(|ch| ch.is_ascii_alphabetic()) {
let span = parse_duration(span_string)?;
let suffix = suffix_string.parse()?;
Ok(RuleSaveP { dur: span, suffix: Some(suffix) })
} else {
let span = parse_duration(at)?;
Ok(RuleSaveP { dur: span, suffix: None })
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum RuleSaveSuffixP {
Standard,
Dst,
}
impl FromStr for RuleSaveSuffixP {
type Err = Error;
fn from_str(suffix: &str) -> Result<RuleSaveSuffixP, Error> {
match suffix {
"s" => Ok(RuleSaveSuffixP::Standard),
"d" => Ok(RuleSaveSuffixP::Dst),
_ => Err(Error::from(E::UnrecognizedSaveTimeSuffix)),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct RuleLettersP {
part: String,
}
impl FromStr for RuleLettersP {
type Err = Error;
fn from_str(letters: &str) -> Result<RuleLettersP, Error> {
let part =
if letters == "-" { String::new() } else { letters.to_string() };
Ok(RuleLettersP { part })
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct ZoneNameP {
name: String,
}
impl FromStr for ZoneNameP {
type Err = Error;
fn from_str(name: &str) -> Result<ZoneNameP, Error> {
if name.is_empty() {
return Err(Error::from(E::ExpectedNonEmptyZoneName));
}
for component in name.split('/') {
if component == "." || component == ".." {
return Err(Error::from(E::ExpectedZoneNameComponentNoDots {
component: component.into(),
}));
}
}
Ok(ZoneNameP { name: name.to_string() })
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct ZoneStdoffP {
dur: SignedDuration,
}
impl FromStr for ZoneStdoffP {
type Err = Error;
fn from_str(stdoff: &str) -> Result<ZoneStdoffP, Error> {
let dur = parse_duration(stdoff)?;
Ok(ZoneStdoffP { dur })
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
enum ZoneRulesP {
None,
Named(RuleNameP),
Save(RuleSaveP),
}
impl FromStr for ZoneRulesP {
type Err = Error;
fn from_str(rules: &str) -> Result<ZoneRulesP, Error> {
if rules.starts_with(|ch: char| ch == '-' || ch.is_ascii_digit()) {
if rules == "-" {
Ok(ZoneRulesP::None)
} else {
Ok(ZoneRulesP::Save(rules.parse()?))
}
} else {
Ok(ZoneRulesP::Named(rules.parse()?))
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
enum ZoneFormatP {
Variable {
before: String,
after: String,
},
Offset,
Pair {
std: String,
dst: String,
},
Static {
format: String,
},
}
impl FromStr for ZoneFormatP {
type Err = Error;
fn from_str(format: &str) -> Result<ZoneFormatP, Error> {
fn check_abbrev(abbrev: &str) -> Result<String, Error> {
if abbrev.is_empty() {
return Err(Error::from(E::ExpectedNonEmptyAbbreviation));
}
let is_ok =
|ch| matches!(ch, '+'|'-'|'0'..='9'|'A'..='Z'|'a'..='z');
if !abbrev.chars().all(is_ok) {
return Err(Error::from(E::InvalidAbbreviation));
}
Ok(abbrev.to_string())
}
if format == "%z" {
Ok(ZoneFormatP::Offset)
} else if let Some((before, after)) = format.split_once("%s") {
Ok(ZoneFormatP::Variable {
before: check_abbrev(before)?,
after: check_abbrev(after)?,
})
} else if let Some((std, dst)) = format.split_once("/") {
Ok(ZoneFormatP::Pair {
std: check_abbrev(std)?,
dst: check_abbrev(dst)?,
})
} else {
Ok(ZoneFormatP::Static { format: check_abbrev(format)? })
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
enum ZoneUntilP {
Year {
year: i16,
},
YearMonth {
year: i16,
month: RuleInP,
},
YearMonthDay {
year: i16,
month: RuleInP,
day: RuleOnP,
},
YearMonthDayTime {
year: i16,
month: RuleInP,
day: RuleOnP,
duration: RuleAtP,
},
}
impl ZoneUntilP {
fn parse(fields: &[&str]) -> Result<ZoneUntilP, Error> {
if fields.is_empty() {
return Err(Error::from(E::ExpectedUntilYear));
}
let (year_field, fields) = (fields[0], &fields[1..]);
let year = parse_year(year_field).context(E::FailedParseYear)?;
if fields.is_empty() {
return Ok(ZoneUntilP::Year { year });
}
let (month_field, fields) = (fields[0], &fields[1..]);
let month =
month_field.parse::<RuleInP>().context(E::FailedParseMonth)?;
if fields.is_empty() {
return Ok(ZoneUntilP::YearMonth { year, month });
}
let (day_field, fields) = (fields[0], &fields[1..]);
let day = day_field.parse::<RuleOnP>().context(E::FailedParseDay)?;
if fields.is_empty() {
return Ok(ZoneUntilP::YearMonthDay { year, month, day });
}
let (duration_field, fields) = (fields[0], &fields[1..]);
let duration = duration_field
.parse::<RuleAtP>()
.context(E::FailedParseTimeDuration)?;
if !fields.is_empty() {
return Err(Error::from(E::ExpectedNothingAfterTime));
}
Ok(ZoneUntilP::YearMonthDayTime { year, month, day, duration })
}
fn to_datetime(&self) -> Result<DateTime, Error> {
let date = self.on().date(self.year(), self.month())?;
let dt =
date.to_datetime(Time::midnight()).checked_add(self.at().dur)?;
Ok(dt)
}
fn year(&self) -> i16 {
use self::ZoneUntilP::*;
match *self {
Year { year }
| YearMonth { year, .. }
| YearMonthDay { year, .. }
| YearMonthDayTime { year, .. } => year,
}
}
fn month(&self) -> i8 {
use self::ZoneUntilP::*;
match *self {
Year { .. } => 1,
YearMonth { month, .. }
| YearMonthDay { month, .. }
| YearMonthDayTime { month, .. } => month.month,
}
}
fn on(&self) -> RuleOnP {
use self::ZoneUntilP::*;
match *self {
Year { .. } | YearMonth { .. } => RuleOnP::Day { day: 1 },
YearMonthDay { day, .. } | YearMonthDayTime { day, .. } => day,
}
}
fn at(&self) -> RuleAtP {
use self::ZoneUntilP::*;
match *self {
Year { .. } | YearMonth { .. } | YearMonthDay { .. } => {
RuleAtP { dur: SignedDuration::ZERO, suffix: None }
}
YearMonthDayTime { duration, .. } => duration,
}
}
}
fn parse_year(year: &str) -> Result<i16, Error> {
let (sign, rest) = if year.starts_with("-") {
(b::Sign::Negative, &year[1..])
} else {
(b::Sign::Positive, year)
};
let year = b::Year::parse(rest.as_bytes()).context(E::FailedParseYear)?;
Ok(sign * year)
}
fn parse_duration(span: &str) -> Result<SignedDuration, Error> {
let rest = span;
let (sign, rest) = if rest.starts_with("-") {
if span.len() == 1 {
return Ok(SignedDuration::ZERO);
}
(b::Sign::Negative, &rest[1..])
} else {
(b::Sign::Positive, rest)
};
let mut dur = SignedDuration::ZERO;
let hour_len = rest.chars().take_while(|c| c.is_ascii_digit()).count();
let (hour_digits, rest) = rest.split_at(hour_len);
if hour_digits.is_empty() {
return Err(Error::from(E::ExpectedTimeOneHour));
}
let hours = b::SpanHours::parse(hour_digits.as_bytes())
.context(E::FailedParseHour)?;
dur += SignedDuration::from_hours(sign * i64::from(hours));
if rest.is_empty() {
return Ok(dur);
}
if !rest.starts_with(":") {
return Err(Error::from(E::ExpectedColonAfterHour));
}
let rest = &rest[1..];
let minute_len = rest.chars().take_while(|c| c.is_ascii_digit()).count();
let (minute_digits, rest) = rest.split_at(minute_len);
if minute_digits.is_empty() {
return Err(Error::from(E::ExpectedMinuteAfterHours));
}
let minutes = b::Minute::parse(minute_digits.as_bytes())
.context(E::FailedParseMinute)?;
dur += SignedDuration::from_mins(sign * i64::from(minutes));
if rest.is_empty() {
return Ok(dur);
}
if !rest.starts_with(":") {
return Err(Error::from(E::ExpectedColonAfterMinute));
}
let rest = &rest[1..];
let second_len = rest.chars().take_while(|c| c.is_ascii_digit()).count();
let (second_digits, rest) = rest.split_at(second_len);
if second_digits.is_empty() {
return Err(Error::from(E::ExpectedSecondAfterMinutes));
}
let seconds = b::Second::parse(second_digits.as_bytes())
.context(E::FailedParseSecond)?;
dur += SignedDuration::from_secs(sign * i64::from(seconds));
if rest.is_empty() {
return Ok(dur);
}
if !rest.starts_with(".") {
return Err(Error::from(E::ExpectedDotAfterSeconds));
}
let rest = &rest[1..];
let nanosecond_len =
rest.chars().take_while(|c| c.is_ascii_digit()).count();
let (nanosecond_digits, rest) = rest.split_at(nanosecond_len);
if nanosecond_digits.is_empty() {
return Err(Error::from(E::ExpectedNanosecondDigits));
}
let nanoseconds = parse::fraction(nanosecond_digits.as_bytes())
.context(E::FailedParseNanosecond)?;
dur += SignedDuration::from_nanos(sign * i64::from(nanoseconds));
if !rest.is_empty() {
return Err(Error::from(E::UnrecognizedTrailingTimeDuration));
}
Ok(dur)
}
fn parse_day(string: &str) -> Result<i8, Error> {
b::Day::parse(string.as_bytes()).context(E::FailedParseDay)
}
fn parse_weekday(string: &str) -> Result<Weekday, Error> {
static WEEKDAY_PREFIXES: &[(Weekday, &str, &str)] = &[
(Weekday::Monday, "Monday", "M"),
(Weekday::Tuesday, "Tuesday", "Tu"),
(Weekday::Wednesday, "Wednesday", "W"),
(Weekday::Thursday, "Thursday", "Th"),
(Weekday::Friday, "Friday", "F"),
(Weekday::Saturday, "Saturday", "Sa"),
(Weekday::Sunday, "Sunday", "Su"),
];
for &(weekday, name, prefix) in WEEKDAY_PREFIXES {
if string.starts_with(prefix) && name.starts_with(string) {
return Ok(weekday);
}
}
Err(Error::from(E::UnrecognizedDayOfWeek))
}
struct FieldParser<'a> {
lines: core::str::Lines<'a>,
line_number: usize,
fields: Vec<&'a str>,
continuation_zone_for: Option<String>,
}
impl<'a> FieldParser<'a> {
fn new(src: &'a str) -> FieldParser<'a> {
FieldParser {
lines: src.lines(),
line_number: 0,
fields: vec![],
continuation_zone_for: None,
}
}
fn from_bytes(src: &'a [u8]) -> Result<FieldParser<'a>, Error> {
let src = core::str::from_utf8(src)
.map_err(|_| Error::from(E::InvalidUtf8))?;
Ok(FieldParser::new(src))
}
fn read_next_fields(&mut self) -> Result<bool, Error> {
self.fields.clear();
loop {
let Some(line) = self.lines.next() else { return Ok(false) };
self.line_number =
self.line_number.checked_add(1).ok_or(E::LineOverflow)?;
parse_fields(&line, &mut self.fields)
.context(E::Line { number: self.line_number })?;
if self.fields.is_empty() {
continue;
}
return Ok(true);
}
}
}
fn parse_fields<'a>(
line: &'a str,
fields: &mut Vec<&'a str>,
) -> Result<(), Error> {
fn is_space(ch: char) -> bool {
matches!(ch, ' ' | '\x0C' | '\n' | '\r' | '\t' | '\x0B')
}
enum State {
Whitespace,
InUnquote,
InQuote,
AfterQuote,
}
fields.clear();
if line.len() > MAX_LINE_LEN {
return Err(Error::from(E::LineMaxLength));
}
if line.contains('\x00') {
return Err(Error::from(E::LineNul));
}
let mut state = State::Whitespace;
let mut start = 0;
for (i, ch) in line.char_indices() {
assert_ne!(ch, '\n', "no line terminators allowed within a line");
state = match state {
State::Whitespace => {
if is_space(ch) {
State::Whitespace
} else if ch == '#' {
return Ok(());
} else if ch == '"' {
start = i + ch.len_utf8();
State::InQuote
} else {
start = i;
State::InUnquote
}
}
State::InUnquote => {
if is_space(ch) {
fields.push(&line[start..i]);
State::Whitespace
} else if ch == '#' {
fields.push(&line[start..i]);
return Ok(());
} else {
State::InUnquote
}
}
State::InQuote => {
if ch == '"' {
fields.push(&line[start..i]);
State::AfterQuote
} else {
State::InQuote
}
}
State::AfterQuote => {
if !is_space(ch) {
return Err(Error::from(
E::ExpectedWhitespaceAfterQuotedField,
));
}
State::Whitespace
}
};
}
match state {
State::Whitespace | State::AfterQuote => {}
State::InUnquote => {
fields.push(&line[start..]);
}
State::InQuote => {
return Err(Error::from(E::ExpectedCloseQuote));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use crate::civil::date;
use super::*;
fn td(seconds: i64, nanoseconds: i32) -> SignedDuration {
SignedDuration::new(seconds, nanoseconds)
}
#[test]
fn zone_until_to_datetime() {
let until = ZoneUntilP::parse(&["2024"]).unwrap();
let wall = until.to_datetime().unwrap();
assert_eq!(wall, date(2024, 1, 1).at(0, 0, 0, 0));
let until = ZoneUntilP::parse(&["2024", "Mar"]).unwrap();
let wall = until.to_datetime().unwrap();
assert_eq!(wall, date(2024, 3, 1).at(0, 0, 0, 0));
let until = ZoneUntilP::parse(&["2024", "Mar", "Sun>=8"]).unwrap();
let wall = until.to_datetime().unwrap();
assert_eq!(wall, date(2024, 3, 10).at(0, 0, 0, 0));
let until =
ZoneUntilP::parse(&["2024", "Mar", "Sun>=8", "2:00"]).unwrap();
let wall = until.to_datetime().unwrap();
assert_eq!(wall, date(2024, 3, 10).at(2, 0, 0, 0));
let until =
ZoneUntilP::parse(&["2024", "Mar", "Sun>=10", "2:00"]).unwrap();
let wall = until.to_datetime().unwrap();
assert_eq!(wall, date(2024, 3, 10).at(2, 0, 0, 0));
let until =
ZoneUntilP::parse(&["2024", "Mar", "Sun<=10", "2:00"]).unwrap();
let wall = until.to_datetime().unwrap();
assert_eq!(wall, date(2024, 3, 10).at(2, 0, 0, 0));
let until =
ZoneUntilP::parse(&["2024", "Mar", "Sun<=10", "2:00u"]).unwrap();
let wall = until.to_datetime().unwrap();
assert_eq!(wall, date(2024, 3, 10).at(2, 0, 0, 0));
}
#[cfg(not(miri))]
#[test]
fn parse_zic_man1() {
let data = "
# Rule NAME FROM TO - IN ON AT SAVE LETTER/S
Rule US 1967 2006 - Oct lastSun 2:00 0 S
Rule US 1967 1973 - Apr lastSun 2:00 1:00 D
# Zone NAME STDOFF RULES FORMAT [UNTIL]
Zone America/Menominee -5:00 - EST 1973 Apr 29 2:00
-6:00 US C%sT
";
let mut zic = ZicP::default();
zic.parse(data).unwrap();
insta::assert_debug_snapshot!(zic);
}
#[cfg(not(miri))]
#[test]
fn parse_zic_man2() {
let data = "
# Rule NAME FROM TO - IN ON AT SAVE LETTER/S
Rule Swiss 1941 1942 - May Mon>=1 1:00 1:00 S
Rule Swiss 1941 1942 - Oct Mon>=1 2:00 0 -
Rule EU 1977 1980 - Apr Sun>=1 1:00u 1:00 S
Rule EU 1977 only - Sep lastSun 1:00u 0 -
Rule EU 1978 only - Oct 1 1:00u 0 -
Rule EU 1979 1995 - Sep lastSun 1:00u 0 -
Rule EU 1981 max - Mar lastSun 1:00u 1:00 S
Rule EU 1996 max - Oct lastSun 1:00u 0 -
# Zone NAME STDOFF RULES FORMAT [UNTIL]
Zone Europe/Zurich 0:34:08 - LMT 1853 Jul 16
0:29:45.50 - BMT 1894 Jun
1:00 Swiss CE%sT 1981
1:00 EU CE%sT
Link Europe/Zurich Europe/Vaduz
";
let mut zic = ZicP::default();
zic.parse(data).unwrap();
insta::assert_debug_snapshot!(zic);
}
#[test]
fn parse_zic_err() {
let data = "
Zone America/Menominee -5:00 - EST
-6:00 US C%sT
";
assert!(ZicP::default().parse(data).is_err());
let data = "
Zone America/Menominee -5:00 - EST 1973 Apr 29 2:00
Link America/Menominee Foo
";
assert!(ZicP::default().parse(data).is_err());
let data = "
Zone America/Menominee -5:00 - EST 1973 Apr 29 2:00
";
assert!(ZicP::default().parse(data).is_err());
let data = "
Zone America/New_York -5:00 - EST 1973 Apr 29 2:00
Zone America/Menominee -5:00 - EST 1973 Apr 29 2:00
Link America/New_York America/Menominee
";
assert!(ZicP::default().parse(data).is_err());
let data = "
Link America/New_York America/Menominee
Zone America/New_York -5:00 - EST 1973 Apr 29 2:00
Zone America/Menominee -5:00 - EST 1973 Apr 29 2:00
";
assert!(ZicP::default().parse(data).is_err());
let data = "
Zone America/New_York -5:00 - EST 1973 Apr 29 2:00
Zone America/New_York -5:00 - EST 1973 Apr 29 2:00
";
assert!(ZicP::default().parse(data).is_err());
let data = "
Zone America/New_York -5:00 - EST 1973 Apr 29 2:00
Zone America/Menominee -5:00 - EST 1973 Apr 29 2:00
Link America/New_York Foo
Link America/Menominee Foo
";
assert!(ZicP::default().parse(data).is_err());
}
#[cfg(not(miri))]
#[test]
fn parse_rule_ok() {
let rule: RuleP = RuleP::parse(&[
"US", "1967", "1973", "-", "Apr", "lastSun", "2:00w", "1:00d", "D",
])
.unwrap();
insta::assert_debug_snapshot!(rule, @r#"
RuleP {
name: RuleNameP {
name: "US",
},
from: RuleFromP {
year: 1967,
},
to: Year {
year: 1973,
},
inn: RuleInP {
month: 4,
},
on: Last {
weekday: Sunday,
},
at: RuleAtP {
dur: 7200s,
suffix: Some(
Wall,
),
},
save: RuleSaveP {
dur: 3600s,
suffix: Some(
Dst,
),
},
letters: RuleLettersP {
part: "D",
},
}
"#);
}
#[test]
fn parse_rule_err() {
assert!(RuleP::parse(&[
"US", "1967", "1973", "-", "Apr", "lastSun", "2:00w", "1:00d",
"D", "E",
])
.is_err());
assert!(RuleP::parse(&[
"US", "1967", "1973", "-", "Apr", "lastSun", "2:00w", "1:00d",
])
.is_err());
}
#[cfg(not(miri))]
#[test]
fn parse_zone_first_ok() {
let zone: ZoneFirstP =
ZoneFirstP::parse(&["America/Menominee", "-5:00", "-", "EST"])
.unwrap();
insta::assert_debug_snapshot!(zone, @r#"
ZoneFirstP {
name: ZoneNameP {
name: "America/Menominee",
},
stdoff: ZoneStdoffP {
dur: -18000s,
},
rules: None,
format: Static {
format: "EST",
},
until: None,
}
"#);
let zone: ZoneFirstP = ZoneFirstP::parse(&[
"America/Menominee",
"-5:00",
"-",
"EST",
"1973",
"Apr",
"29",
"2:00",
])
.unwrap();
insta::assert_debug_snapshot!(zone, @r#"
ZoneFirstP {
name: ZoneNameP {
name: "America/Menominee",
},
stdoff: ZoneStdoffP {
dur: -18000s,
},
rules: None,
format: Static {
format: "EST",
},
until: Some(
YearMonthDayTime {
year: 1973,
month: RuleInP {
month: 4,
},
day: Day {
day: 29,
},
duration: RuleAtP {
dur: 7200s,
suffix: None,
},
},
),
}
"#);
}
#[test]
fn parse_zone_first_err() {
assert!(ZoneFirstP::parse(&[]).is_err());
assert!(ZoneFirstP::parse(&["foo"]).is_err());
assert!(ZoneFirstP::parse(&["foo", "-5"]).is_err());
assert!(ZoneFirstP::parse(&["foo", "-5", "-"]).is_err());
assert!(ZoneFirstP::parse(&[
"foo", "-5", "-", "EST", "1973", "Apr", "29", "2:00", "w",
])
.is_err());
}
#[cfg(not(miri))]
#[test]
fn parse_zone_continuation_ok() {
let zone: ZoneContinuationP =
ZoneContinuationP::parse(&["-5:00", "-", "EST"]).unwrap();
insta::assert_debug_snapshot!(zone, @r#"
ZoneContinuationP {
stdoff: ZoneStdoffP {
dur: -18000s,
},
rules: None,
format: Static {
format: "EST",
},
until: None,
}
"#);
let zone: ZoneContinuationP = ZoneContinuationP::parse(&[
"-5:00", "-", "EST", "1973", "Apr", "29", "2:00",
])
.unwrap();
insta::assert_debug_snapshot!(zone, @r#"
ZoneContinuationP {
stdoff: ZoneStdoffP {
dur: -18000s,
},
rules: None,
format: Static {
format: "EST",
},
until: Some(
YearMonthDayTime {
year: 1973,
month: RuleInP {
month: 4,
},
day: Day {
day: 29,
},
duration: RuleAtP {
dur: 7200s,
suffix: None,
},
},
),
}
"#);
}
#[test]
fn parse_zone_continuation_err() {
assert!(ZoneContinuationP::parse(&[]).is_err());
assert!(ZoneContinuationP::parse(&["-5"]).is_err());
assert!(ZoneContinuationP::parse(&["-5", "-"]).is_err());
assert!(ZoneContinuationP::parse(&[
"-5", "-", "EST", "1973", "Apr", "29", "2:00", "w",
])
.is_err());
}
#[test]
fn parse_link_ok() {
let link: LinkP = LinkP::parse(&["Greenwich", "G_M_T"]).unwrap();
assert_eq!(
link,
LinkP {
target: ZoneNameP { name: "Greenwich".to_string() },
name: ZoneNameP { name: "G_M_T".to_string() },
}
);
}
#[test]
fn parse_link_err() {
assert!(LinkP::parse(&["Greenwich"]).is_err());
assert!(LinkP::parse(&["Greenwich", "G_M_T", "foo"]).is_err());
assert!(LinkP::parse(&["", "G_M_T"]).is_err());
assert!(LinkP::parse(&["Greenwich", ""]).is_err());
}
#[test]
fn parse_rule_name_ok() {
let name: RuleNameP = "US".parse().unwrap();
assert_eq!(name, RuleNameP { name: "US".to_string() });
let name: RuleNameP = "U571".parse().unwrap();
assert_eq!(name, RuleNameP { name: "U571".to_string() });
let name: RuleNameP = "U+-1".parse().unwrap();
assert_eq!(name, RuleNameP { name: "U+-1".to_string() });
let name: RuleNameP =
r#"U!$%&'()*,/:;<=>?@[\]^`{|}~S"#.parse().unwrap();
assert_eq!(
name,
RuleNameP { name: r#"U!$%&'()*,/:;<=>?@[\]^`{|}~S"#.to_string() }
);
}
#[test]
fn parse_rule_name_err() {
assert!("".parse::<RuleNameP>().is_err());
assert!("+U".parse::<RuleNameP>().is_err());
assert!("-U".parse::<RuleNameP>().is_err());
assert!("0U".parse::<RuleNameP>().is_err());
assert!("9U".parse::<RuleNameP>().is_err());
}
#[test]
fn parse_rule_from_ok() {
let to: RuleFromP = "2025".parse().unwrap();
assert_eq!(to, RuleFromP { year: 2025 });
let to: RuleFromP = "9999".parse().unwrap();
assert_eq!(to, RuleFromP { year: 9999 });
let to: RuleFromP = "-9999".parse().unwrap();
assert_eq!(to, RuleFromP { year: -9999 });
}
#[test]
fn parse_rule_from_err() {
assert!("10000".parse::<RuleFromP>().is_err());
assert!("-10000".parse::<RuleFromP>().is_err());
}
#[test]
fn parse_rule_to_ok() {
let to: RuleToP = "2025".parse().unwrap();
assert_eq!(to, RuleToP::Year { year: 2025 });
let to: RuleToP = "9999".parse().unwrap();
assert_eq!(to, RuleToP::Year { year: 9999 });
let to: RuleToP = "-9999".parse().unwrap();
assert_eq!(to, RuleToP::Year { year: -9999 });
let to: RuleToP = "o".parse().unwrap();
assert_eq!(to, RuleToP::Only);
let to: RuleToP = "only".parse().unwrap();
assert_eq!(to, RuleToP::Only);
let to: RuleToP = "m".parse().unwrap();
assert_eq!(to, RuleToP::Max);
let to: RuleToP = "max".parse().unwrap();
assert_eq!(to, RuleToP::Max);
let to: RuleToP = "maximum".parse().unwrap();
assert_eq!(to, RuleToP::Max);
}
#[test]
fn parse_rule_to_err() {
assert!("10000".parse::<RuleToP>().is_err());
assert!("-10000".parse::<RuleToP>().is_err());
assert!("oonly".parse::<RuleToP>().is_err());
assert!("ononly".parse::<RuleToP>().is_err());
assert!("onlyy".parse::<RuleToP>().is_err());
assert!("only ".parse::<RuleToP>().is_err());
assert!("mmaximum".parse::<RuleToP>().is_err());
assert!("mamaximum".parse::<RuleToP>().is_err());
assert!("maxmaximum".parse::<RuleToP>().is_err());
assert!("maximumm".parse::<RuleToP>().is_err());
assert!("maximum ".parse::<RuleToP>().is_err());
}
#[test]
fn parse_rule_in_ok() {
let inn: RuleInP = "Ja".parse().unwrap();
assert_eq!(inn.month, 1);
let inn: RuleInP = "January".parse().unwrap();
assert_eq!(inn.month, 1);
let inn: RuleInP = "F".parse().unwrap();
assert_eq!(inn.month, 2);
let inn: RuleInP = "February".parse().unwrap();
assert_eq!(inn.month, 2);
let inn: RuleInP = "Mar".parse().unwrap();
assert_eq!(inn.month, 3);
let inn: RuleInP = "March".parse().unwrap();
assert_eq!(inn.month, 3);
let inn: RuleInP = "Ap".parse().unwrap();
assert_eq!(inn.month, 4);
let inn: RuleInP = "April".parse().unwrap();
assert_eq!(inn.month, 4);
let inn: RuleInP = "May".parse().unwrap();
assert_eq!(inn.month, 5);
let inn: RuleInP = "Jun".parse().unwrap();
assert_eq!(inn.month, 6);
let inn: RuleInP = "June".parse().unwrap();
assert_eq!(inn.month, 6);
let inn: RuleInP = "Jul".parse().unwrap();
assert_eq!(inn.month, 7);
let inn: RuleInP = "July".parse().unwrap();
assert_eq!(inn.month, 7);
let inn: RuleInP = "Au".parse().unwrap();
assert_eq!(inn.month, 8);
let inn: RuleInP = "August".parse().unwrap();
assert_eq!(inn.month, 8);
let inn: RuleInP = "S".parse().unwrap();
assert_eq!(inn.month, 9);
let inn: RuleInP = "September".parse().unwrap();
assert_eq!(inn.month, 9);
let inn: RuleInP = "O".parse().unwrap();
assert_eq!(inn.month, 10);
let inn: RuleInP = "October".parse().unwrap();
assert_eq!(inn.month, 10);
let inn: RuleInP = "N".parse().unwrap();
assert_eq!(inn.month, 11);
let inn: RuleInP = "November".parse().unwrap();
assert_eq!(inn.month, 11);
let inn: RuleInP = "D".parse().unwrap();
assert_eq!(inn.month, 12);
let inn: RuleInP = "December".parse().unwrap();
assert_eq!(inn.month, 12);
}
#[test]
fn parse_rule_in_err() {
assert!("J".parse::<RuleInP>().is_err());
assert!("Januaryy".parse::<RuleInP>().is_err());
assert!("JJanuary".parse::<RuleInP>().is_err());
assert!("JaJanuary".parse::<RuleInP>().is_err());
}
#[test]
fn parse_rule_on_ok() {
let on: RuleOnP = "5".parse().unwrap();
assert_eq!(on, RuleOnP::Day { day: 5 });
let on: RuleOnP = "05".parse().unwrap();
assert_eq!(on, RuleOnP::Day { day: 5 });
let on: RuleOnP = "31".parse().unwrap();
assert_eq!(on, RuleOnP::Day { day: 31 });
let on: RuleOnP = "lastSu".parse().unwrap();
assert_eq!(on, RuleOnP::Last { weekday: Weekday::Sunday });
let on: RuleOnP = "lastSun".parse().unwrap();
assert_eq!(on, RuleOnP::Last { weekday: Weekday::Sunday });
let on: RuleOnP = "lastSund".parse().unwrap();
assert_eq!(on, RuleOnP::Last { weekday: Weekday::Sunday });
let on: RuleOnP = "lastSunda".parse().unwrap();
assert_eq!(on, RuleOnP::Last { weekday: Weekday::Sunday });
let on: RuleOnP = "lastSunday".parse().unwrap();
assert_eq!(on, RuleOnP::Last { weekday: Weekday::Sunday });
let on: RuleOnP = "Sun<=25".parse().unwrap();
assert_eq!(
on,
RuleOnP::OnOrBefore { weekday: Weekday::Sunday, day: 25 }
);
let on: RuleOnP = "Sunday<=25".parse().unwrap();
assert_eq!(
on,
RuleOnP::OnOrBefore { weekday: Weekday::Sunday, day: 25 }
);
let on: RuleOnP = "Sun>=8".parse().unwrap();
assert_eq!(
on,
RuleOnP::OnOrAfter { weekday: Weekday::Sunday, day: 8 }
);
let on: RuleOnP = "Sunday>=8".parse().unwrap();
assert_eq!(
on,
RuleOnP::OnOrAfter { weekday: Weekday::Sunday, day: 8 }
);
}
#[test]
fn parse_rule_on_err() {
assert!("0".parse::<RuleOnP>().is_err());
assert!("00000".parse::<RuleOnP>().is_err());
assert!("00000000000000000000000000".parse::<RuleOnP>().is_err());
assert!("32".parse::<RuleOnP>().is_err());
assert!("lastS".parse::<RuleOnP>().is_err());
assert!("lastSSunday".parse::<RuleOnP>().is_err());
assert!("lastSuSunday".parse::<RuleOnP>().is_err());
assert!("lastSundayy".parse::<RuleOnP>().is_err());
assert!("lastZ".parse::<RuleOnP>().is_err());
assert!("last".parse::<RuleOnP>().is_err());
assert!("S<=25".parse::<RuleOnP>().is_err());
assert!("Sun<=0".parse::<RuleOnP>().is_err());
assert!("Sun<=32".parse::<RuleOnP>().is_err());
assert!("S>=25".parse::<RuleOnP>().is_err());
assert!("Sun>=0".parse::<RuleOnP>().is_err());
assert!("Sun>=32".parse::<RuleOnP>().is_err());
assert!("abc".parse::<RuleOnP>().is_err());
assert!("Sun<25".parse::<RuleOnP>().is_err());
assert!("Sun>25".parse::<RuleOnP>().is_err());
assert!("Sun < 25".parse::<RuleOnP>().is_err());
assert!("Sun > 25".parse::<RuleOnP>().is_err());
assert!("Sun <= 25".parse::<RuleOnP>().is_err());
assert!("Sun >= 25".parse::<RuleOnP>().is_err());
}
#[test]
fn parse_rule_at_ok() {
let at: RuleAtP = "5".parse().unwrap();
assert_eq!(at, RuleAtP { dur: td(5 * 60 * 60, 0), suffix: None });
let at: RuleAtP = "5w".parse().unwrap();
assert_eq!(
at,
RuleAtP {
dur: td(5 * 60 * 60, 0),
suffix: Some(RuleAtSuffixP::Wall)
}
);
let at: RuleAtP = "-5w".parse().unwrap();
assert_eq!(
at,
RuleAtP {
dur: td(-5 * 60 * 60, 0),
suffix: Some(RuleAtSuffixP::Wall)
}
);
let at: RuleAtP = "-".parse().unwrap();
assert_eq!(at, RuleAtP { dur: td(0, 0), suffix: None });
let at: RuleAtP = "-s".parse().unwrap();
assert_eq!(
at,
RuleAtP { dur: td(0, 0), suffix: Some(RuleAtSuffixP::Standard) }
);
}
#[test]
fn parse_rule_at_err() {
assert!("".parse::<RuleAtP>().is_err());
assert!("w".parse::<RuleAtP>().is_err());
}
#[test]
fn parse_rule_at_suffix_ok() {
let suffix: RuleAtSuffixP = "w".parse().unwrap();
assert_eq!(suffix, RuleAtSuffixP::Wall);
let suffix: RuleAtSuffixP = "s".parse().unwrap();
assert_eq!(suffix, RuleAtSuffixP::Standard);
let suffix: RuleAtSuffixP = "u".parse().unwrap();
assert_eq!(suffix, RuleAtSuffixP::Universal);
let suffix: RuleAtSuffixP = "g".parse().unwrap();
assert_eq!(suffix, RuleAtSuffixP::Universal);
let suffix: RuleAtSuffixP = "z".parse().unwrap();
assert_eq!(suffix, RuleAtSuffixP::Universal);
}
#[test]
fn parse_rule_at_suffix_err() {
assert!("W".parse::<RuleAtSuffixP>().is_err());
assert!("w ".parse::<RuleAtSuffixP>().is_err());
assert!(" w".parse::<RuleAtSuffixP>().is_err());
assert!("ww".parse::<RuleAtSuffixP>().is_err());
assert!("".parse::<RuleAtSuffixP>().is_err());
}
#[test]
fn parse_rule_save_ok() {
let at: RuleSaveP = "5".parse().unwrap();
assert_eq!(at, RuleSaveP { dur: td(5 * 60 * 60, 0), suffix: None });
let at: RuleSaveP = "5s".parse().unwrap();
assert_eq!(
at,
RuleSaveP {
dur: td(5 * 60 * 60, 0),
suffix: Some(RuleSaveSuffixP::Standard)
}
);
let at: RuleSaveP = "-5s".parse().unwrap();
assert_eq!(
at,
RuleSaveP {
dur: td(-5 * 60 * 60, 0),
suffix: Some(RuleSaveSuffixP::Standard)
}
);
let at: RuleSaveP = "-".parse().unwrap();
assert_eq!(at, RuleSaveP { dur: td(0, 0), suffix: None });
let at: RuleSaveP = "-s".parse().unwrap();
assert_eq!(
at,
RuleSaveP {
dur: td(0, 0),
suffix: Some(RuleSaveSuffixP::Standard)
}
);
}
#[test]
fn parse_rule_save_err() {
assert!("".parse::<RuleSaveP>().is_err());
assert!("s".parse::<RuleSaveP>().is_err());
}
#[test]
fn parse_rule_save_suffix_ok() {
let suffix: RuleSaveSuffixP = "s".parse().unwrap();
assert_eq!(suffix, RuleSaveSuffixP::Standard);
let suffix: RuleSaveSuffixP = "d".parse().unwrap();
assert_eq!(suffix, RuleSaveSuffixP::Dst);
}
#[test]
fn parse_rule_save_suffix_err() {
assert!("S".parse::<RuleSaveSuffixP>().is_err());
assert!("s ".parse::<RuleSaveSuffixP>().is_err());
assert!(" s".parse::<RuleSaveSuffixP>().is_err());
assert!("ss".parse::<RuleSaveSuffixP>().is_err());
assert!("".parse::<RuleSaveSuffixP>().is_err());
}
#[test]
fn parse_rule_letters_ok() {
let letters: RuleLettersP = "-".parse().unwrap();
assert_eq!(letters, RuleLettersP { part: "".to_string() });
let letters: RuleLettersP = "S".parse().unwrap();
assert_eq!(letters, RuleLettersP { part: "S".to_string() });
let letters: RuleLettersP = "D".parse().unwrap();
assert_eq!(letters, RuleLettersP { part: "D".to_string() });
}
#[test]
fn parse_zone_name_ok() {
let name: ZoneNameP = "foo".parse().unwrap();
assert_eq!(name, ZoneNameP { name: "foo".to_string() });
let name: ZoneNameP = "foo/.../bar".parse().unwrap();
assert_eq!(name, ZoneNameP { name: "foo/.../bar".to_string() });
let name: ZoneNameP = "foo bar".parse().unwrap();
assert_eq!(name, ZoneNameP { name: "foo bar".to_string() });
}
#[test]
fn parse_zone_name_err() {
assert!("".parse::<ZoneNameP>().is_err());
assert!("foo/./bar".parse::<ZoneNameP>().is_err());
assert!("foo/../bar".parse::<ZoneNameP>().is_err());
}
#[test]
fn parse_zone_stdoff_ok() {
let stdoff: ZoneStdoffP = "5".parse().unwrap();
assert_eq!(stdoff, ZoneStdoffP { dur: td(5 * 60 * 60, 0) });
let stdoff: ZoneStdoffP = "-5".parse().unwrap();
assert_eq!(stdoff, ZoneStdoffP { dur: td(-5 * 60 * 60, 0) });
}
#[test]
fn parse_zone_stdoff_err() {
assert!("".parse::<ZoneStdoffP>().is_err());
assert!("a".parse::<ZoneStdoffP>().is_err());
assert!("999999999999999999".parse::<ZoneStdoffP>().is_err());
}
#[test]
fn parse_zone_rules_ok() {
let rules: ZoneRulesP = "-".parse().unwrap();
assert_eq!(rules, ZoneRulesP::None);
let rules: ZoneRulesP = "foo".parse().unwrap();
assert_eq!(
rules,
ZoneRulesP::Named(RuleNameP { name: "foo".to_string() })
);
let rules: ZoneRulesP = "5".parse().unwrap();
assert_eq!(
rules,
ZoneRulesP::Save(RuleSaveP {
dur: td(5 * 60 * 60, 0),
suffix: None,
})
);
let rules: ZoneRulesP = "-5".parse().unwrap();
assert_eq!(
rules,
ZoneRulesP::Save(RuleSaveP {
dur: td(-5 * 60 * 60, 0),
suffix: None,
})
);
let rules: ZoneRulesP = "-1d".parse().unwrap();
assert_eq!(
rules,
ZoneRulesP::Save(RuleSaveP {
dur: td(-1 * 60 * 60, 0),
suffix: Some(RuleSaveSuffixP::Dst),
})
);
}
#[test]
fn parse_zone_rules_err() {
assert!("".parse::<ZoneRulesP>().is_err());
assert!("-foo".parse::<ZoneRulesP>().is_err());
assert!("+foo".parse::<ZoneRulesP>().is_err());
assert!("+5".parse::<ZoneRulesP>().is_err());
}
#[test]
fn parse_zone_format_ok() {
let format: ZoneFormatP = "E%sT".parse().unwrap();
assert_eq!(
format,
ZoneFormatP::Variable {
before: "E".to_string(),
after: "T".to_string(),
}
);
let format: ZoneFormatP = "AB%sXYZ".parse().unwrap();
assert_eq!(
format,
ZoneFormatP::Variable {
before: "AB".to_string(),
after: "XYZ".to_string(),
}
);
let format: ZoneFormatP = "%z".parse().unwrap();
assert_eq!(format, ZoneFormatP::Offset,);
let format: ZoneFormatP = "EST/EDT".parse().unwrap();
assert_eq!(
format,
ZoneFormatP::Pair {
std: "EST".to_string(),
dst: "EDT".to_string(),
}
);
let format: ZoneFormatP = "+EST/-EDT".parse().unwrap();
assert_eq!(
format,
ZoneFormatP::Pair {
std: "+EST".to_string(),
dst: "-EDT".to_string(),
}
);
let format: ZoneFormatP = "GMT".parse().unwrap();
assert_eq!(format, ZoneFormatP::Static { format: "GMT".to_string() });
}
#[test]
fn parse_zone_format_err() {
assert!("".parse::<ZoneFormatP>().is_err());
assert!("%s".parse::<ZoneFormatP>().is_err());
assert!("A%s".parse::<ZoneFormatP>().is_err());
assert!("%sZ".parse::<ZoneFormatP>().is_err());
assert!("A/B/C".parse::<ZoneFormatP>().is_err());
assert!("A&Z".parse::<ZoneFormatP>().is_err());
assert!("A&B/YZ".parse::<ZoneFormatP>().is_err());
assert!("AB/Y&Z".parse::<ZoneFormatP>().is_err());
assert!("A&%sZ".parse::<ZoneFormatP>().is_err());
assert!("A%s&Z".parse::<ZoneFormatP>().is_err());
assert!("AB%zYZ".parse::<ZoneFormatP>().is_err());
}
#[test]
fn parse_zone_until_ok() {
let until = ZoneUntilP::parse(&["2025"]).unwrap();
assert_eq!(until, ZoneUntilP::Year { year: 2025 },);
let until = ZoneUntilP::parse(&["9999"]).unwrap();
assert_eq!(until, ZoneUntilP::Year { year: 9999 },);
let until = ZoneUntilP::parse(&["-9999"]).unwrap();
assert_eq!(until, ZoneUntilP::Year { year: -9999 },);
let until = ZoneUntilP::parse(&["2025", "Jan"]).unwrap();
assert_eq!(
until,
ZoneUntilP::YearMonth { year: 2025, month: RuleInP { month: 1 } },
);
let until = ZoneUntilP::parse(&["2025", "Jan", "5"]).unwrap();
assert_eq!(
until,
ZoneUntilP::YearMonthDay {
year: 2025,
month: RuleInP { month: 1 },
day: RuleOnP::Day { day: 5 },
},
);
let until = ZoneUntilP::parse(&["2025", "Jan", "lastSun"]).unwrap();
assert_eq!(
until,
ZoneUntilP::YearMonthDay {
year: 2025,
month: RuleInP { month: 1 },
day: RuleOnP::Last { weekday: Weekday::Sunday },
},
);
let until =
ZoneUntilP::parse(&["2025", "Jan", "lastSun", "-"]).unwrap();
assert_eq!(
until,
ZoneUntilP::YearMonthDayTime {
year: 2025,
month: RuleInP { month: 1 },
day: RuleOnP::Last { weekday: Weekday::Sunday },
duration: RuleAtP { dur: td(0, 0), suffix: None },
},
);
let until =
ZoneUntilP::parse(&["2025", "Jan", "lastSun", "5"]).unwrap();
assert_eq!(
until,
ZoneUntilP::YearMonthDayTime {
year: 2025,
month: RuleInP { month: 1 },
day: RuleOnP::Last { weekday: Weekday::Sunday },
duration: RuleAtP { dur: td(5 * 60 * 60, 0), suffix: None },
},
);
let until =
ZoneUntilP::parse(&["2025", "Jan", "lastSun", "-5"]).unwrap();
assert_eq!(
until,
ZoneUntilP::YearMonthDayTime {
year: 2025,
month: RuleInP { month: 1 },
day: RuleOnP::Last { weekday: Weekday::Sunday },
duration: RuleAtP { dur: td(-5 * 60 * 60, 0), suffix: None },
},
);
let until =
ZoneUntilP::parse(&["2025", "Jan", "lastSun", "1:1:1.000000001"])
.unwrap();
assert_eq!(
until,
ZoneUntilP::YearMonthDayTime {
year: 2025,
month: RuleInP { month: 1 },
day: RuleOnP::Last { weekday: Weekday::Sunday },
duration: RuleAtP { dur: td(3661, 1), suffix: None },
},
);
let until =
ZoneUntilP::parse(&["2025", "Jan", "lastSun", "5u"]).unwrap();
assert_eq!(
until,
ZoneUntilP::YearMonthDayTime {
year: 2025,
month: RuleInP { month: 1 },
day: RuleOnP::Last { weekday: Weekday::Sunday },
duration: RuleAtP {
dur: td(5 * 60 * 60, 0),
suffix: Some(RuleAtSuffixP::Universal),
},
},
);
}
#[test]
fn parse_zone_until_err() {
assert!(ZoneUntilP::parse(&[]).is_err());
assert!(ZoneUntilP::parse(&["10000"]).is_err());
assert!(ZoneUntilP::parse(&["-10000"]).is_err());
assert!(ZoneUntilP::parse(&["2025", "J"]).is_err());
assert!(ZoneUntilP::parse(&["2025", "Z"]).is_err());
assert!(ZoneUntilP::parse(&["2025", "Jan", "lastS"]).is_err());
assert!(ZoneUntilP::parse(&["2025", "Jan", "0"]).is_err());
assert!(ZoneUntilP::parse(&["2025", "Jan", "32"]).is_err());
assert!(ZoneUntilP::parse(&["2025", "Jan", "lastSun", "w"]).is_err());
assert!(ZoneUntilP::parse(&[
"2025",
"Jan",
"lastSun",
"1:1:1.0000000001"
])
.is_err());
}
#[test]
fn parse_year_ok() {
assert_eq!(parse_year("0").unwrap(), 0);
assert_eq!(parse_year("1").unwrap(), 1);
assert_eq!(parse_year("-1").unwrap(), -1);
assert_eq!(parse_year("2025").unwrap(), 2025);
assert_eq!(parse_year("9999").unwrap(), 9999);
assert_eq!(parse_year("-9999").unwrap(), -9999);
}
#[test]
fn parse_year_err() {
assert!(parse_year("-10000").is_err());
assert!(parse_year("10000").is_err());
assert!(parse_year("+2025").is_err());
assert!(parse_year(" 2025").is_err());
assert!(parse_year("2025 ").is_err());
assert!(parse_year("9999999999999999999999999999").is_err());
}
#[test]
fn parse_duration_ok() {
assert_eq!(parse_duration("-").unwrap(), td(0, 0));
assert_eq!(parse_duration("0").unwrap(), td(0, 0));
assert_eq!(parse_duration("-0").unwrap(), td(0, 0));
assert_eq!(parse_duration("1").unwrap(), td(3600, 0));
assert_eq!(parse_duration("1:1").unwrap(), td(3660, 0));
assert_eq!(parse_duration("1:1:1").unwrap(), td(3661, 0));
assert_eq!(parse_duration("1:1:1.1").unwrap(), td(3661, 100_000_000));
assert_eq!(
parse_duration("1:1:1.123456789").unwrap(),
td(3661, 123_456_789)
);
assert_eq!(parse_duration("0:1:0").unwrap(), td(60, 0));
assert_eq!(parse_duration("0:0:1").unwrap(), td(1, 0));
assert_eq!(parse_duration("0:0:0.000000001").unwrap(), td(0, 1));
assert_eq!(parse_duration("0:0:0.000000000").unwrap(), td(0, 0));
assert_eq!(parse_duration("-1").unwrap(), td(-3600, 0));
assert_eq!(parse_duration("-1:1").unwrap(), td(-3660, 0));
assert_eq!(parse_duration("-1:1:1").unwrap(), td(-3661, 0));
assert_eq!(
parse_duration("-1:1:1.1").unwrap(),
td(-3661, -100_000_000)
);
assert_eq!(
parse_duration("-1:1:1.123456789").unwrap(),
td(-3661, -123_456_789)
);
assert_eq!(parse_duration("-0:1:0").unwrap(), td(-60, 0));
assert_eq!(parse_duration("-0:0:1").unwrap(), td(-1, 0));
assert_eq!(parse_duration("-0:0:0.000000001").unwrap(), td(0, -1));
}
#[test]
fn parse_duration_err() {
assert!(parse_duration("").is_err());
assert!(parse_duration(" ").is_err());
assert!(parse_duration("a").is_err());
assert!(parse_duration("999999999999999").is_err());
assert!(parse_duration("1:").is_err());
assert!(parse_duration("1:a").is_err());
assert!(parse_duration("1:60").is_err());
assert!(parse_duration("1:01:").is_err());
assert!(parse_duration("1:01:60").is_err());
assert!(parse_duration("1:01:59.").is_err());
assert!(parse_duration("1:01:59.0000000001").is_err());
assert!(parse_duration("1:01:59.0000000000").is_err());
assert!(parse_duration("1:01:59.000000001a").is_err());
assert!(parse_duration("1:01:59.000000001 ").is_err());
assert!(parse_duration("1::59").is_err());
assert!(parse_duration("1::.1").is_err());
assert!(parse_duration("::").is_err());
assert!(parse_duration("+1").is_err());
assert!(parse_duration("175307616").is_ok());
assert!(parse_duration("175307617").is_err());
assert!(parse_duration("175307616:01").is_ok());
assert!(parse_duration("175307616:00:01").is_ok());
assert!(parse_duration("175307616:00:00.999999999").is_ok());
assert!(parse_duration("-175307616").is_ok());
assert!(parse_duration("-175307617").is_err());
assert!(parse_duration("-175307616:01").is_ok());
assert!(parse_duration("-175307616:00:01").is_ok());
assert!(parse_duration("-175307616:00:00.999999999").is_ok());
}
#[test]
fn parse_day_ok() {
assert_eq!(parse_day("1").unwrap(), 1);
assert_eq!(parse_day("2").unwrap(), 2);
assert_eq!(parse_day("20").unwrap(), 20);
assert_eq!(parse_day("30").unwrap(), 30);
assert_eq!(parse_day("31").unwrap(), 31);
assert_eq!(parse_day("01").unwrap(), 1);
assert_eq!(parse_day("00000001").unwrap(), 1);
assert_eq!(parse_day("0000000000000000000001").unwrap(), 1);
}
#[test]
fn parse_day_err() {
assert!(parse_day("").is_err());
assert!(parse_day("0").is_err());
assert!(parse_day("00").is_err());
assert!(parse_day("32").is_err());
assert!(parse_day("032").is_err());
}
#[test]
fn parse_weekday_ok() {
assert_eq!(parse_weekday("M").unwrap(), Weekday::Monday);
assert_eq!(parse_weekday("Monday").unwrap(), Weekday::Monday);
assert_eq!(parse_weekday("Tu").unwrap(), Weekday::Tuesday);
assert_eq!(parse_weekday("Tuesday").unwrap(), Weekday::Tuesday);
assert_eq!(parse_weekday("W").unwrap(), Weekday::Wednesday);
assert_eq!(parse_weekday("Wednesday").unwrap(), Weekday::Wednesday);
assert_eq!(parse_weekday("Th").unwrap(), Weekday::Thursday);
assert_eq!(parse_weekday("Thursday").unwrap(), Weekday::Thursday);
assert_eq!(parse_weekday("F").unwrap(), Weekday::Friday);
assert_eq!(parse_weekday("Friday").unwrap(), Weekday::Friday);
assert_eq!(parse_weekday("Sa").unwrap(), Weekday::Saturday);
assert_eq!(parse_weekday("Saturday").unwrap(), Weekday::Saturday);
assert_eq!(parse_weekday("Su").unwrap(), Weekday::Sunday);
assert_eq!(parse_weekday("Sunday").unwrap(), Weekday::Sunday);
}
#[test]
fn parse_weekday_err() {
assert!(parse_weekday("S").is_err());
assert!(parse_weekday("Sundayy").is_err());
assert!(parse_weekday("SSunday").is_err());
assert!(parse_weekday("SuSunday").is_err());
}
#[test]
fn parse_fields_ok() {
let mut fields: Vec<&str> = vec![];
parse_fields("", &mut fields).unwrap();
assert_eq!(fields, Vec::<&str>::new());
parse_fields("# foo bar baz", &mut fields).unwrap();
assert_eq!(fields, Vec::<&str>::new());
parse_fields("a", &mut fields).unwrap();
assert_eq!(fields, vec!["a"]);
parse_fields("foo", &mut fields).unwrap();
assert_eq!(fields, vec!["foo"]);
parse_fields("foo#foo", &mut fields).unwrap();
assert_eq!(fields, vec!["foo"]);
parse_fields(r#""foo""#, &mut fields).unwrap();
assert_eq!(fields, vec!["foo"]);
parse_fields(r#"fo"o"#, &mut fields).unwrap();
assert_eq!(fields, vec![r#"fo"o"#]);
parse_fields("foo bar", &mut fields).unwrap();
assert_eq!(fields, vec!["foo", "bar"]);
parse_fields(" \x0C\t\x0B foo \t bar ", &mut fields).unwrap();
assert_eq!(fields, vec!["foo", "bar"]);
parse_fields(r#"foo "bar" baz"#, &mut fields).unwrap();
assert_eq!(fields, vec!["foo", "bar", "baz"]);
parse_fields(r#"foo"bar baz"quux"#, &mut fields).unwrap();
assert_eq!(fields, vec![r#"foo"bar"#, r#"baz"quux"#]);
parse_fields(r#""""#, &mut fields).unwrap();
assert_eq!(fields, vec![""]);
}
#[test]
fn parse_fields_err() {
let mut fields: Vec<&str> = vec![];
assert!(parse_fields(r#""foo"bar"#, &mut fields).is_err());
assert!(parse_fields(r#""foo"#, &mut fields).is_err());
assert!(parse_fields("foo\0", &mut fields).is_err());
let fits = "a".repeat(2047);
assert!(parse_fields(&fits, &mut fields).is_ok());
let toobig = "a".repeat(2048);
assert!(parse_fields(&toobig, &mut fields).is_err());
}
}