#![allow(warnings)]
use core::{ops::RangeInclusive, str::FromStr};
use alloc::{
collections::BTreeMap,
string::{String, ToString},
sync::Arc,
vec,
vec::Vec,
};
use crate::{
civil::{Date, DateTime, Time, Weekday},
error::{err, Error, ErrorContext},
span::{Span, ToSpan},
timestamp::Timestamp,
tz::{Dst, Offset},
util::{
parse,
rangeint::RInto,
t::{self, C},
},
Unit,
};
#[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().map_err(|e| {
err!("SAVE value in rule {:?} is too big: {e}", inner.name)
})?;
let years = r
.years()
.map_err(|e| e.context(err!("rule {:?}", inner.name)))?;
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<t::Year>,
month: t::Month,
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)
.map_err(|e| e.context(err!("line {}", parser.line_number)))?;
}
if let Some(ref name) = parser.continuation_zone_for {
return Err(err!(
"expected continuation zone line for {name:?}, \
but found end of data instead",
));
}
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).map_err(|e| {
e.context("failed to parse continuation 'Zone' line")
})?;
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)
.map_err(|e| e.context("failed to parse 'Rule' line"))?;
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)
.map_err(|e| e.context("failed to parse first 'Zone' line"))?;
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(err!(
"found zone with name {name:?} that conflicts \
with a link of the same name",
));
}
if let Some(previous_zone) = self.zones.insert(name, zone) {
return Err(err!(
"found duplicate zone for {:?}",
previous_zone.first.name.name,
));
}
} else if first.starts_with("L") && "Link".starts_with(first) {
let link = LinkP::parse(rest)
.map_err(|e| e.context("failed to parse 'Link' line"))?;
let name = link.name.name.clone();
if self.zones.contains_key(&name) {
return Err(err!(
"found link with name {name:?} that conflicts \
with a zone of the same name",
));
}
if let Some(previous_link) = self.links.insert(name, link) {
return Err(err!(
"found duplicate link for {:?}",
previous_link.name.name,
));
}
} else {
return Err(err!("unrecognized zic line: {first:?}"));
}
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(err!(
"expected exactly 9 fields for rule, but found {} fields",
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) = (fields[0], &fields[1..]);
let name = name_field
.parse::<RuleNameP>()
.map_err(|e| e.context("failed to parse NAME field"))?;
let from = from_field
.parse::<RuleFromP>()
.map_err(|e| e.context("failed to parse FROM field"))?;
let to = to_field
.parse::<RuleToP>()
.map_err(|e| e.context("failed to parse TO field"))?;
let inn = in_field
.parse::<RuleInP>()
.map_err(|e| e.context("failed to parse IN field"))?;
let on = on_field
.parse::<RuleOnP>()
.map_err(|e| e.context("failed to parse ON field"))?;
let at = at_field
.parse::<RuleAtP>()
.map_err(|e| e.context("failed to parse AT field"))?;
let save = save_field
.parse::<RuleSaveP>()
.map_err(|e| e.context("failed to parse SAVE field"))?;
let letters = letters_field
.parse::<RuleLettersP>()
.map_err(|e| e.context("failed to parse LETTERS field"))?;
Ok(RuleP { name, from, to, inn, on, at, save, letters })
}
fn years(&self) -> Result<RangeInclusive<t::Year>, Error> {
let start = self.from.year;
let end = match self.to {
RuleToP::Max => t::Year::MAX_SELF,
RuleToP::Only => start,
RuleToP::Year { year } => year,
};
if start > end {
return Err(err!(
"found start year {start} to be greater than end year {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(err!("first ZONE line must have at least 4 fields"));
}
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>()
.map_err(|e| e.context("failed to parse NAME field"))?;
let stdoff = stdoff_field
.parse::<ZoneStdoffP>()
.map_err(|e| e.context("failed to parse STDOFF field"))?;
let rules = rules_field
.parse::<ZoneRulesP>()
.map_err(|e| e.context("failed to parse RULES field"))?;
let format = format_field
.parse::<ZoneFormatP>()
.map_err(|e| e.context("failed to parse FORMAT field"))?;
let until = if fields.is_empty() {
None
} else {
Some(
ZoneUntilP::parse(fields)
.map_err(|e| e.context("failed to parse UNTIL field"))?,
)
};
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(err!(
"continuation ZONE line must have at least 3 fields"
));
}
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>()
.map_err(|e| e.context("failed to parse STDOFF field"))?;
let rules = rules_field
.parse::<ZoneRulesP>()
.map_err(|e| e.context("failed to parse RULES field"))?;
let format = format_field
.parse::<ZoneFormatP>()
.map_err(|e| e.context("failed to parse FORMAT field"))?;
let until = if fields.is_empty() {
None
} else {
Some(
ZoneUntilP::parse(fields)
.map_err(|e| e.context("failed to parse UNTIL field"))?,
)
};
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(err!(
"expected exactly 2 fields after LINK, but found {}",
fields.len()
));
}
let target = fields[0]
.parse::<ZoneNameP>()
.map_err(|e| e.context("failed to parse LINK target"))?;
let name = fields[1]
.parse::<ZoneNameP>()
.map_err(|e| e.context("failed to parse LINK name"))?;
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(err!("NAME field for rule cannot be empty"))
} else if name.starts_with(|ch| matches!(ch, '0'..='9' | '+' | '-')) {
Err(err!(
"NAME field cannot begin with a digit, + or -, \
but {name:?} begins with one of those",
))
} else {
Ok(RuleNameP { name: name.to_string() })
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct RuleFromP {
year: t::Year,
}
impl FromStr for RuleFromP {
type Err = Error;
fn from_str(from: &str) -> Result<RuleFromP, Error> {
let year = parse_year(from)
.map_err(|e| e.context("failed to parse FROM field"))?;
Ok(RuleFromP { year })
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum RuleToP {
Max,
Only,
Year { year: t::Year },
}
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)
.map_err(|e| e.context("failed to parse TO field"))?;
Ok(RuleToP::Year { year })
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct RuleInP {
month: t::Month,
}
impl FromStr for RuleInP {
type Err = Error;
fn from_str(field: &str) -> Result<RuleInP, Error> {
static MONTH_PREFIXES: &[(u8, &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 &(number, name, prefix) in MONTH_PREFIXES {
if field.starts_with(prefix) && name.starts_with(field) {
let month = t::Month::new(number).unwrap();
return Ok(RuleInP { month });
}
}
Err(err!("unrecognized month name: {field:?}"))
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum RuleOnP {
Day { day: t::Day },
Last { weekday: Weekday },
OnOrBefore { weekday: Weekday, day: t::Day },
OnOrAfter { weekday: Weekday, day: t::Day },
}
impl RuleOnP {
fn date(&self, year: t::Year, month: t::Month) -> Result<Date, Error> {
match *self {
RuleOnP::Day { day } => Date::new_ranged(year, month, day),
RuleOnP::Last { weekday } => {
let date = Date::new_ranged(year, month, C(1)).unwrap();
date.nth_weekday_of_month(-1, weekday)
}
RuleOnP::OnOrBefore { weekday, day } => {
let start = Date::new_ranged(year, month, day)?
.checked_add(1.day())?;
start.nth_weekday(-1, weekday)
}
RuleOnP::OnOrAfter { weekday, day } => {
let start = Date::new_ranged(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(err!("unrecognized format for day-of-month: {field:?}"))
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct RuleAtP {
span: Span,
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(err!("empty field is not a valid AT value"));
}
let (span_string, suffix_string) = at.split_at(at.len() - 1);
if suffix_string.chars().all(|ch| ch.is_ascii_alphabetic()) {
let span = parse_span(span_string)?;
let suffix = suffix_string.parse()?;
Ok(RuleAtP { span, suffix: Some(suffix) })
} else {
let span = parse_span(at)?;
Ok(RuleAtP { 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(err!("unrecognized AT time suffix {suffix:?}")),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct RuleSaveP {
span: Span,
suffix: Option<RuleSaveSuffixP>,
}
impl RuleSaveP {
fn to_offset(&self) -> Result<Offset, Error> {
let seconds = Span::from_invariant_nanoseconds(
Unit::Second,
self.span.to_invariant_nanoseconds(),
)?
.get_seconds();
let seconds = i32::try_from(seconds).map_err(|_| {
Error::signed("SAVE seconds", seconds, i32::MIN, i32::MAX)
})?;
Offset::from_seconds(seconds)
}
fn suffix(&self) -> RuleSaveSuffixP {
self.suffix.unwrap_or_else(|| {
if self.span.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(err!("empty field is not a valid SAVE value"));
}
let (span_string, suffix_string) = at.split_at(at.len() - 1);
if suffix_string.chars().all(|ch| ch.is_ascii_alphabetic()) {
let span = parse_span(span_string)?;
let suffix = suffix_string.parse()?;
Ok(RuleSaveP { span, suffix: Some(suffix) })
} else {
let span = parse_span(at)?;
Ok(RuleSaveP { 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(err!("unrecognized SAVE time suffix {suffix:?}")),
}
}
}
#[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(err!("zone names cannot be empty"));
}
for component in name.split('/') {
if component == "." || component == ".." {
return Err(err!(
"component {component:?} in zone name {name:?} cannot \
be \".\" or \"..\"",
));
}
}
Ok(ZoneNameP { name: name.to_string() })
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct ZoneStdoffP {
span: Span,
}
impl FromStr for ZoneStdoffP {
type Err = Error;
fn from_str(stdoff: &str) -> Result<ZoneStdoffP, Error> {
let span = parse_span(stdoff)?;
Ok(ZoneStdoffP { span })
}
}
#[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(err!("empty abbreviations are not allowed"));
}
let is_ok =
|ch| matches!(ch, '+'|'-'|'0'..='9'|'A'..='Z'|'a'..='z');
if !abbrev.chars().all(is_ok) {
return Err(err!(
"abbreviation {abbrev:?} \
contains invalid character; only \"+\", \"-\" and \
ASCII alpha-numeric characters are allowed"
));
}
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: t::Year,
},
YearMonth {
year: t::Year,
month: RuleInP,
},
YearMonthDay {
year: t::Year,
month: RuleInP,
day: RuleOnP,
},
YearMonthDayTime {
year: t::Year,
month: RuleInP,
day: RuleOnP,
duration: RuleAtP,
},
}
impl ZoneUntilP {
fn parse(fields: &[&str]) -> Result<ZoneUntilP, Error> {
if fields.is_empty() {
return Err(err!("expected at least a year"));
}
let (year_field, fields) = (fields[0], &fields[1..]);
let year = parse_year(year_field)
.map_err(|e| e.context("failed to parse year"))?;
if fields.is_empty() {
return Ok(ZoneUntilP::Year { year });
}
let (month_field, fields) = (fields[0], &fields[1..]);
let month = month_field
.parse::<RuleInP>()
.map_err(|e| e.context("failed to parse month"))?;
if fields.is_empty() {
return Ok(ZoneUntilP::YearMonth { year, month });
}
let (day_field, fields) = (fields[0], &fields[1..]);
let day = day_field
.parse::<RuleOnP>()
.map_err(|e| e.context("failed to parse day"))?;
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>()
.map_err(|e| e.context("failed to parse time duration"))?;
if !fields.is_empty() {
return Err(err!(
"expected no more fields after time of day, \
but found: {fields:?}",
fields = fields.join(" "),
));
}
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().span)?;
Ok(dt)
}
fn year(&self) -> t::Year {
use self::ZoneUntilP::*;
match *self {
Year { year }
| YearMonth { year, .. }
| YearMonthDay { year, .. }
| YearMonthDayTime { year, .. } => year,
}
}
fn month(&self) -> t::Month {
use self::ZoneUntilP::*;
match *self {
Year { .. } => t::Month::N::<1>(),
YearMonth { month, .. }
| YearMonthDay { month, .. }
| YearMonthDayTime { month, .. } => month.month,
}
}
fn on(&self) -> RuleOnP {
use self::ZoneUntilP::*;
match *self {
Year { .. } | YearMonth { .. } => {
RuleOnP::Day { day: t::Day::N::<1>() }
}
YearMonthDay { day, .. } | YearMonthDayTime { day, .. } => day,
}
}
fn at(&self) -> RuleAtP {
use self::ZoneUntilP::*;
match *self {
Year { .. } | YearMonth { .. } | YearMonthDay { .. } => {
RuleAtP { span: Span::new(), suffix: None }
}
YearMonthDayTime { duration, .. } => duration,
}
}
}
fn parse_year(year: &str) -> Result<t::Year, Error> {
let (sign, rest) = if year.starts_with("-") {
(t::Sign::N::<-1>(), &year[1..])
} else {
(t::Sign::N::<1>(), year)
};
let number = parse::i64(rest.as_bytes())
.map_err(|e| e.context("failed to parse year"))?;
let year = t::Year::new(number)
.ok_or_else(|| err!("year is out of range: {number}"))?;
Ok(year * sign)
}
fn parse_span(span: &str) -> Result<Span, Error> {
let rest = span;
let (mut span, sign, rest) = if rest.starts_with("-") {
if span.len() == 1 {
return Ok(Span::new());
}
(Span::new(), t::Sign::N::<-1>(), &rest[1..])
} else {
(Span::new(), t::Sign::N::<1>(), rest)
};
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(err!(
"expected time duration to contain at least one hour digit"
));
}
let hours = parse::i64(hour_digits.as_bytes())
.map_err(|e| e.context("failed to parse hours in time duration"))?;
span = span
.try_hours(hours.saturating_mul(i64::from(sign.get())))
.map_err(|_| err!("duration hours '{hours:?}' is out of range"))?;
if rest.is_empty() {
return Ok(span);
}
if !rest.starts_with(":") {
return Err(err!("expected ':' after hours, but found {rest:?}"));
}
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(err!(
"expected minute digits after 'HH:', but found {rest:?} instead"
));
}
let minutes = parse::i64(minute_digits.as_bytes())
.map_err(|e| e.context("failed to parse minutes in time duration"))?;
let minutes_ranged = t::Minute::new(minutes).ok_or_else(|| {
err!("duration minutes '{minutes:?}' is out of range")
})?;
span = span.minutes_ranged(minutes_ranged * sign);
if rest.is_empty() {
return Ok(span);
}
if !rest.starts_with(":") {
return Err(err!("expected ':' after minutes, but found {rest:?}"));
}
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(err!(
"expected second digits after 'MM:', but found {rest:?} instead"
));
}
let seconds = parse::i64(second_digits.as_bytes())
.map_err(|e| e.context("failed to parse seconds in time duration"))?;
let seconds_ranged = t::Second::new(seconds).ok_or_else(|| {
err!("duration seconds '{seconds:?}' is out of range")
})?;
span = span.seconds_ranged(seconds_ranged * sign);
if rest.is_empty() {
return Ok(span);
}
if !rest.starts_with(".") {
return Err(err!("expected '.' after seconds, but found {rest:?}"));
}
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(err!(
"expected nanosecond digits after 'SS.', \
but found {rest:?} instead"
));
}
let nanoseconds = parse::fraction(nanosecond_digits.as_bytes(), 9)
.map_err(|e| {
e.context("failed to parse nanoseconds in time duration")
})?;
let nanoseconds_ranged = t::FractionalNanosecond::new(nanoseconds)
.ok_or_else(|| {
err!("duration nanoseconds '{nanoseconds:?}' is out of range")
})?;
span = span.nanoseconds_ranged(nanoseconds_ranged * sign);
if !rest.is_empty() {
return Err(err!(
"found unrecognized trailing {rest:?} in time duration"
));
}
span.rebalance(Unit::Hour)
}
fn parse_day(string: &str) -> Result<t::Day, Error> {
let number = parse::i64(string.as_bytes())
.map_err(|e| e.context("failed to parse number for day"))?;
let day = t::Day::new(number)
.ok_or_else(|| err!("{number} is not a valid day"))?;
Ok(day)
}
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(err!("unrecognized day of the week: {string:?}"))
}
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 {
FieldParser {
lines: src.lines(),
line_number: 0,
fields: vec![],
continuation_zone_for: None,
}
}
fn from_bytes(src: &'a [u8]) -> Result<FieldParser, Error> {
let src = core::str::from_utf8(src)
.map_err(|e| err!("invalid UTF-8: {e}"))?;
Ok(FieldParser::new(src))
}
fn read_next_fields(&mut self) -> Result<bool, Error> {
self.fields.clear();
loop {
let Some(mut line) = self.lines.next() else { return Ok(false) };
self.line_number = self
.line_number
.checked_add(1)
.ok_or_else(|| err!("line count overflowed"))?;
parse_fields(&line, &mut self.fields)
.with_context(|| err!("line {}", self.line_number))?;
if self.fields.is_empty() {
continue;
}
return Ok(true);
}
}
}
fn parse_fields<'a>(
mut line: &'a str,
fields: &mut Vec<&'a str>,
) -> Result<(), Error> {
fn is_space(ch: char) -> bool {
matches!(ch, ' ' | '\x0C' | '\n' | '\r' | '\t' | '\x0B')
}
const MAX_LINE_LEN: usize = 2047;
enum State {
Whitespace,
InUnquote,
InQuote,
AfterQuote,
}
fields.clear();
if line.len() > MAX_LINE_LEN {
return Err(err!(
"line with length {} exceeds \
max length of {MAX_LINE_LEN}",
line.len()
));
}
if line.contains('\x00') {
return Err(err!("found line with NUL byte, which isn't allowed"));
}
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(err!(
"expected whitespace after quoted field, \
but found {ch:?} instead",
));
}
State::Whitespace
}
};
}
match state {
State::Whitespace | State::AfterQuote => {}
State::InUnquote => {
fields.push(&line[start..]);
}
State::InQuote => {
return Err(err!("found unclosed quote"));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use crate::civil::date;
use super::*;
fn td(seconds: i64, nanoseconds: i32) -> Span {
Span::new()
.seconds(seconds)
.nanoseconds(nanoseconds)
.rebalance(Unit::Hour)
.unwrap()
}
#[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));
}
#[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);
}
#[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());
}
#[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 {
span: PT2h,
suffix: Some(
Wall,
),
},
save: RuleSaveP {
span: PT1h,
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());
}
#[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 {
span: -PT5h,
},
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 {
span: -PT5h,
},
rules: None,
format: Static {
format: "EST",
},
until: Some(
YearMonthDayTime {
year: 1973,
month: RuleInP {
month: 4,
},
day: Day {
day: 29,
},
duration: RuleAtP {
span: PT2h,
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());
}
#[test]
fn parse_zone_continuation_ok() {
let zone: ZoneContinuationP =
ZoneContinuationP::parse(&["-5:00", "-", "EST"]).unwrap();
insta::assert_debug_snapshot!(zone, @r###"
ZoneContinuationP {
stdoff: ZoneStdoffP {
span: -PT5h,
},
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 {
span: -PT5h,
},
rules: None,
format: Static {
format: "EST",
},
until: Some(
YearMonthDayTime {
year: 1973,
month: RuleInP {
month: 4,
},
day: Day {
day: 29,
},
duration: RuleAtP {
span: PT2h,
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: t::Year::new(2025).unwrap() });
let to: RuleFromP = "9999".parse().unwrap();
assert_eq!(to, RuleFromP { year: t::Year::new(9999).unwrap() });
let to: RuleFromP = "-9999".parse().unwrap();
assert_eq!(to, RuleFromP { year: t::Year::new(-9999).unwrap() });
}
#[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: t::Year::new(2025).unwrap() });
let to: RuleToP = "9999".parse().unwrap();
assert_eq!(to, RuleToP::Year { year: t::Year::new(9999).unwrap() });
let to: RuleToP = "-9999".parse().unwrap();
assert_eq!(to, RuleToP::Year { year: t::Year::new(-9999).unwrap() });
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: t::Day::new(5).unwrap() });
let on: RuleOnP = "05".parse().unwrap();
assert_eq!(on, RuleOnP::Day { day: t::Day::new(5).unwrap() });
let on: RuleOnP = "31".parse().unwrap();
assert_eq!(on, RuleOnP::Day { day: t::Day::new(31).unwrap() });
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: t::Day::new(25).unwrap()
}
);
let on: RuleOnP = "Sunday<=25".parse().unwrap();
assert_eq!(
on,
RuleOnP::OnOrBefore {
weekday: Weekday::Sunday,
day: t::Day::new(25).unwrap()
}
);
let on: RuleOnP = "Sun>=8".parse().unwrap();
assert_eq!(
on,
RuleOnP::OnOrAfter {
weekday: Weekday::Sunday,
day: t::Day::new(8).unwrap()
}
);
let on: RuleOnP = "Sunday>=8".parse().unwrap();
assert_eq!(
on,
RuleOnP::OnOrAfter {
weekday: Weekday::Sunday,
day: t::Day::new(8).unwrap()
}
);
}
#[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 { span: td(5 * 60 * 60, 0), suffix: None });
let at: RuleAtP = "5w".parse().unwrap();
assert_eq!(
at,
RuleAtP {
span: td(5 * 60 * 60, 0),
suffix: Some(RuleAtSuffixP::Wall)
}
);
let at: RuleAtP = "-5w".parse().unwrap();
assert_eq!(
at,
RuleAtP {
span: td(-5 * 60 * 60, 0),
suffix: Some(RuleAtSuffixP::Wall)
}
);
let at: RuleAtP = "-".parse().unwrap();
assert_eq!(at, RuleAtP { span: td(0, 0), suffix: None });
let at: RuleAtP = "-s".parse().unwrap();
assert_eq!(
at,
RuleAtP { span: 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 { span: td(5 * 60 * 60, 0), suffix: None });
let at: RuleSaveP = "5s".parse().unwrap();
assert_eq!(
at,
RuleSaveP {
span: td(5 * 60 * 60, 0),
suffix: Some(RuleSaveSuffixP::Standard)
}
);
let at: RuleSaveP = "-5s".parse().unwrap();
assert_eq!(
at,
RuleSaveP {
span: td(-5 * 60 * 60, 0),
suffix: Some(RuleSaveSuffixP::Standard)
}
);
let at: RuleSaveP = "-".parse().unwrap();
assert_eq!(at, RuleSaveP { span: td(0, 0), suffix: None });
let at: RuleSaveP = "-s".parse().unwrap();
assert_eq!(
at,
RuleSaveP {
span: 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 { span: td(5 * 60 * 60, 0) });
let stdoff: ZoneStdoffP = "-5".parse().unwrap();
assert_eq!(stdoff, ZoneStdoffP { span: 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 {
span: td(5 * 60 * 60, 0),
suffix: None,
})
);
let rules: ZoneRulesP = "-5".parse().unwrap();
assert_eq!(
rules,
ZoneRulesP::Save(RuleSaveP {
span: td(-5 * 60 * 60, 0),
suffix: None,
})
);
let rules: ZoneRulesP = "-1d".parse().unwrap();
assert_eq!(
rules,
ZoneRulesP::Save(RuleSaveP {
span: 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: t::Year::new(2025).unwrap() },
);
let until = ZoneUntilP::parse(&["9999"]).unwrap();
assert_eq!(
until,
ZoneUntilP::Year { year: t::Year::new(9999).unwrap() },
);
let until = ZoneUntilP::parse(&["-9999"]).unwrap();
assert_eq!(
until,
ZoneUntilP::Year { year: t::Year::new(-9999).unwrap() },
);
let until = ZoneUntilP::parse(&["2025", "Jan"]).unwrap();
assert_eq!(
until,
ZoneUntilP::YearMonth {
year: t::Year::new(2025).unwrap(),
month: RuleInP { month: t::Month::new(1).unwrap() },
},
);
let until = ZoneUntilP::parse(&["2025", "Jan", "5"]).unwrap();
assert_eq!(
until,
ZoneUntilP::YearMonthDay {
year: t::Year::new(2025).unwrap(),
month: RuleInP { month: t::Month::new(1).unwrap() },
day: RuleOnP::Day { day: t::Day::new(5).unwrap() },
},
);
let until = ZoneUntilP::parse(&["2025", "Jan", "lastSun"]).unwrap();
assert_eq!(
until,
ZoneUntilP::YearMonthDay {
year: t::Year::new(2025).unwrap(),
month: RuleInP { month: t::Month::new(1).unwrap() },
day: RuleOnP::Last { weekday: Weekday::Sunday },
},
);
let until =
ZoneUntilP::parse(&["2025", "Jan", "lastSun", "-"]).unwrap();
assert_eq!(
until,
ZoneUntilP::YearMonthDayTime {
year: t::Year::new(2025).unwrap(),
month: RuleInP { month: t::Month::new(1).unwrap() },
day: RuleOnP::Last { weekday: Weekday::Sunday },
duration: RuleAtP { span: td(0, 0), suffix: None },
},
);
let until =
ZoneUntilP::parse(&["2025", "Jan", "lastSun", "5"]).unwrap();
assert_eq!(
until,
ZoneUntilP::YearMonthDayTime {
year: t::Year::new(2025).unwrap(),
month: RuleInP { month: t::Month::new(1).unwrap() },
day: RuleOnP::Last { weekday: Weekday::Sunday },
duration: RuleAtP { span: td(5 * 60 * 60, 0), suffix: None },
},
);
let until =
ZoneUntilP::parse(&["2025", "Jan", "lastSun", "-5"]).unwrap();
assert_eq!(
until,
ZoneUntilP::YearMonthDayTime {
year: t::Year::new(2025).unwrap(),
month: RuleInP { month: t::Month::new(1).unwrap() },
day: RuleOnP::Last { weekday: Weekday::Sunday },
duration: RuleAtP { span: 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: t::Year::new(2025).unwrap(),
month: RuleInP { month: t::Month::new(1).unwrap() },
day: RuleOnP::Last { weekday: Weekday::Sunday },
duration: RuleAtP { span: td(3661, 1), suffix: None },
},
);
let until =
ZoneUntilP::parse(&["2025", "Jan", "lastSun", "5u"]).unwrap();
assert_eq!(
until,
ZoneUntilP::YearMonthDayTime {
year: t::Year::new(2025).unwrap(),
month: RuleInP { month: t::Month::new(1).unwrap() },
day: RuleOnP::Last { weekday: Weekday::Sunday },
duration: RuleAtP {
span: 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_span("-").unwrap(), td(0, 0));
assert_eq!(parse_span("0").unwrap(), td(0, 0));
assert_eq!(parse_span("-0").unwrap(), td(0, 0));
assert_eq!(parse_span("1").unwrap(), td(3600, 0));
assert_eq!(parse_span("1:1").unwrap(), td(3660, 0));
assert_eq!(parse_span("1:1:1").unwrap(), td(3661, 0));
assert_eq!(parse_span("1:1:1.1").unwrap(), td(3661, 100_000_000));
assert_eq!(
parse_span("1:1:1.123456789").unwrap(),
td(3661, 123_456_789)
);
assert_eq!(parse_span("0:1:0").unwrap(), td(60, 0));
assert_eq!(parse_span("0:0:1").unwrap(), td(1, 0));
assert_eq!(parse_span("0:0:0.000000001").unwrap(), td(0, 1));
assert_eq!(parse_span("0:0:0.000000000").unwrap(), td(0, 0));
assert_eq!(parse_span("-1").unwrap(), td(-3600, 0));
assert_eq!(parse_span("-1:1").unwrap(), td(-3660, 0));
assert_eq!(parse_span("-1:1:1").unwrap(), td(-3661, 0));
assert_eq!(parse_span("-1:1:1.1").unwrap(), td(-3661, -100_000_000));
assert_eq!(
parse_span("-1:1:1.123456789").unwrap(),
td(-3661, -123_456_789)
);
assert_eq!(parse_span("-0:1:0").unwrap(), td(-60, 0));
assert_eq!(parse_span("-0:0:1").unwrap(), td(-1, 0));
assert_eq!(parse_span("-0:0:0.000000001").unwrap(), td(0, -1));
}
#[test]
fn parse_duration_err() {
assert!(parse_span("").is_err());
assert!(parse_span(" ").is_err());
assert!(parse_span("a").is_err());
assert!(parse_span("999999999999999").is_err());
assert!(parse_span("1:").is_err());
assert!(parse_span("1:a").is_err());
assert!(parse_span("1:60").is_err());
assert!(parse_span("1:01:").is_err());
assert!(parse_span("1:01:60").is_err());
assert!(parse_span("1:01:59.").is_err());
assert!(parse_span("1:01:59.0000000001").is_err());
assert!(parse_span("1:01:59.0000000000").is_err());
assert!(parse_span("1:01:59.000000001a").is_err());
assert!(parse_span("1:01:59.000000001 ").is_err());
assert!(parse_span("1::59").is_err());
assert!(parse_span("1::.1").is_err());
assert!(parse_span("::").is_err());
assert!(parse_span("+1").is_err());
assert!(parse_span("175307616").is_ok());
assert!(parse_span("175307617").is_err());
assert!(parse_span("175307616:01").is_ok());
assert!(parse_span("175307616:00:01").is_ok());
assert!(parse_span("175307616:00:00.999999999").is_ok());
assert!(parse_span("-175307616").is_ok());
assert!(parse_span("-175307617").is_err());
assert!(parse_span("-175307616:01").is_ok());
assert!(parse_span("-175307616:00:01").is_ok());
assert!(parse_span("-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());
}
}