Skip to content

Commit f047da7

Browse files
authored
Fix parsing bugs related to UTC Designator usage (#197)
`DateTime`, `Date`, and `Time` should reject when the UTC designator `Z` is present.
1 parent 7449704 commit f047da7

File tree

3 files changed

+50
-9
lines changed

3 files changed

+50
-9
lines changed

src/builtins/core/time.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,8 @@ impl FromStr for PlainTime {
467467

468468
#[cfg(test)]
469469
mod tests {
470+
use core::str::FromStr;
471+
470472
use crate::{
471473
builtins::core::Duration,
472474
iso::IsoTime,
@@ -797,4 +799,19 @@ mod tests {
797799
.unwrap()
798800
);
799801
}
802+
803+
#[test]
804+
fn invalid_time_from_strs() {
805+
// UTC designator case
806+
let invalid_cases = [
807+
"2019-10-01T09:00:00Z",
808+
"2019-10-01T09:00:00Z[UTC]",
809+
"09:00:00Z[UTC]",
810+
"09:00:00Z",
811+
];
812+
for invalid_str in invalid_cases {
813+
let err = PlainTime::from_str(invalid_str);
814+
assert!(err.is_err());
815+
}
816+
}
800817
}

src/builtins/core/zoneddatetime.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use crate::{
2727
rounding::{IncrementRounder, Round},
2828
temporal_assert,
2929
time::EpochNanoseconds,
30-
Sign, TemporalError, TemporalResult,
30+
Sign, TemporalError, TemporalResult, TemporalUnwrap,
3131
};
3232

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

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

874872
let timezone = match annotation.tz {
875873
TimeZoneRecord::Name(s) => {

src/parsers.rs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -700,7 +700,27 @@ fn parse_ixdtf(source: &str, variant: ParseVariant) -> TemporalResult<IxdtfParse
700700
/// A utility function for parsing a `DateTime` string
701701
#[inline]
702702
pub(crate) fn parse_date_time(source: &str) -> TemporalResult<IxdtfParseRecord> {
703-
parse_ixdtf(source, ParseVariant::DateTime)
703+
let record = parse_ixdtf(source, ParseVariant::DateTime)?;
704+
705+
if record.offset == Some(UtcOffsetRecordOrZ::Z) {
706+
return Err(TemporalError::range()
707+
.with_message("UTC designator is not valid for DateTime parsing."));
708+
}
709+
710+
Ok(record)
711+
}
712+
713+
#[inline]
714+
pub(crate) fn parse_zoned_date_time(source: &str) -> TemporalResult<IxdtfParseRecord> {
715+
let record = parse_ixdtf(source, ParseVariant::DateTime)?;
716+
717+
// TODO: Support rejecting subminute precision in time zone annootations
718+
if record.tz.is_none() {
719+
return Err(TemporalError::range()
720+
.with_message("Time zone annotation is required for parsing a zoned date time."));
721+
}
722+
723+
Ok(record)
704724
}
705725

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

762782
let time_err = match time_record {
763-
Ok(time) => return time.time.temporal_unwrap(),
783+
Ok(time) => {
784+
if time.offset == Some(UtcOffsetRecordOrZ::Z) {
785+
return Err(TemporalError::range()
786+
.with_message("UTC designator is not valid for DateTime parsing."));
787+
}
788+
return time.time.temporal_unwrap();
789+
}
764790
Err(e) => TemporalError::range().with_message(format!("{e}")),
765791
};
766792

767-
let dt_parse = parse_ixdtf(source, ParseVariant::DateTime);
793+
let dt_parse = parse_date_time(source);
768794

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

0 commit comments

Comments
 (0)