Skip to content

Commit

Permalink
Fix parsing bugs related to UTC Designator usage (#197)
Browse files Browse the repository at this point in the history
`DateTime`, `Date`, and `Time` should reject when the UTC designator `Z`
is present.
  • Loading branch information
nekevss authored Feb 19, 2025
1 parent 7449704 commit f047da7
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 9 deletions.
17 changes: 17 additions & 0 deletions src/builtins/core/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,8 @@ impl FromStr for PlainTime {

#[cfg(test)]
mod tests {
use core::str::FromStr;

use crate::{
builtins::core::Duration,
iso::IsoTime,
Expand Down Expand Up @@ -797,4 +799,19 @@ mod tests {
.unwrap()
);
}

#[test]
fn invalid_time_from_strs() {
// UTC designator case
let invalid_cases = [
"2019-10-01T09:00:00Z",
"2019-10-01T09:00:00Z[UTC]",
"09:00:00Z[UTC]",
"09:00:00Z",
];
for invalid_str in invalid_cases {
let err = PlainTime::from_str(invalid_str);
assert!(err.is_err());
}
}
}
10 changes: 4 additions & 6 deletions src/builtins/core/zoneddatetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use crate::{
rounding::{IncrementRounder, Round},
temporal_assert,
time::EpochNanoseconds,
Sign, TemporalError, TemporalResult,
Sign, TemporalError, TemporalResult, TemporalUnwrap,
};

/// A struct representing a partial `ZonedDateTime`.
Expand Down Expand Up @@ -864,12 +864,10 @@ impl ZonedDateTime {
offset_option: OffsetDisambiguation,
provider: &impl TimeZoneProvider,
) -> TemporalResult<Self> {
let parse_result = parsers::parse_date_time(source)?;
let parse_result = parsers::parse_zoned_date_time(source)?;

let Some(annotation) = parse_result.tz else {
return Err(TemporalError::r#type()
.with_message("Time zone annotation is required for ZonedDateTime string."));
};
// NOTE (nekevss): `parse_zoned_date_time` guarantees that this value exists.
let annotation = parse_result.tz.temporal_unwrap()?;

let timezone = match annotation.tz {
TimeZoneRecord::Name(s) => {
Expand Down
32 changes: 29 additions & 3 deletions src/parsers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -700,7 +700,27 @@ fn parse_ixdtf(source: &str, variant: ParseVariant) -> TemporalResult<IxdtfParse
/// A utility function for parsing a `DateTime` string
#[inline]
pub(crate) fn parse_date_time(source: &str) -> TemporalResult<IxdtfParseRecord> {
parse_ixdtf(source, ParseVariant::DateTime)
let record = parse_ixdtf(source, ParseVariant::DateTime)?;

if record.offset == Some(UtcOffsetRecordOrZ::Z) {
return Err(TemporalError::range()
.with_message("UTC designator is not valid for DateTime parsing."));
}

Ok(record)
}

#[inline]
pub(crate) fn parse_zoned_date_time(source: &str) -> TemporalResult<IxdtfParseRecord> {
let record = parse_ixdtf(source, ParseVariant::DateTime)?;

// TODO: Support rejecting subminute precision in time zone annootations
if record.tz.is_none() {
return Err(TemporalError::range()
.with_message("Time zone annotation is required for parsing a zoned date time."));
}

Ok(record)
}

pub(crate) struct IxdtfParseInstantRecord {
Expand Down Expand Up @@ -760,11 +780,17 @@ pub(crate) fn parse_time(source: &str) -> TemporalResult<TimeRecord> {
let time_record = parse_ixdtf(source, ParseVariant::Time);

let time_err = match time_record {
Ok(time) => return time.time.temporal_unwrap(),
Ok(time) => {
if time.offset == Some(UtcOffsetRecordOrZ::Z) {
return Err(TemporalError::range()
.with_message("UTC designator is not valid for DateTime parsing."));
}
return time.time.temporal_unwrap();
}
Err(e) => TemporalError::range().with_message(format!("{e}")),
};

let dt_parse = parse_ixdtf(source, ParseVariant::DateTime);
let dt_parse = parse_date_time(source);

match dt_parse {
Ok(dt) if dt.time.is_some() => Ok(dt.time.temporal_unwrap()?),
Expand Down

0 comments on commit f047da7

Please sign in to comment.