Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/builtins/core/plain_date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -982,7 +982,7 @@ mod tests {
// test262/test/built-ins/Temporal/Calendar/prototype/month/argument-string-invalid.js
#[test]
fn invalid_strings() {
const INVALID_STRINGS: [&str; 35] = [
const INVALID_STRINGS: &[&str] = &[
// invalid ISO strings:
"",
"invalid iso8601",
Expand Down Expand Up @@ -1023,9 +1023,14 @@ mod tests {
// valid, but outside the supported range:
"-999999-01-01",
"+999999-01-01",
// built-ins/Temporal/PlainDate/from/argument-string-too-many-decimals
"1970-01-01T00:00:00.1234567891",
"1970-01-01T00:00:00.1234567890",
"1970-01-01T00+00:00:00.1234567891",
"1970-01-01T00+00:00:00.1234567890",
];
for s in INVALID_STRINGS {
assert!(PlainDate::from_str(s).is_err())
assert!(PlainDate::from_str(s).is_err(), "{} should not parse", s)
}
}

Expand Down
9 changes: 9 additions & 0 deletions src/builtins/core/plain_time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,15 @@ mod tests {
"2019-10-01T09:00:00Z[UTC]",
"09:00:00Z[UTC]",
"09:00:00Z",
// built-ins/Temporal/PlainTime/from/argument-string-too-many-decimals
"1970-01-01T00:00:00.1234567891",
"1970-01-01T00:00:00.1234567890",
"1970-01-01T00+00:00:00.1234567891",
"1970-01-01T00+00:00:00.1234567890",
"00:00:00.1234567891",
"00:00:00.1234567890",
"00+00:00:00.1234567891",
"00+00:00:00.1234567890",
];
for invalid_str in invalid_cases {
let err = PlainTime::from_str(invalid_str);
Expand Down
5 changes: 5 additions & 0 deletions src/builtins/core/plain_year_month.rs
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,11 @@ mod tests {
"+275760-10-01",
"+275760-10-01T00:00",
"1976-11[u-ca=hebrew]",
// built-ins/Temporal/PlainYearMonth/from/argument-string-too-many-decimals
"1970-01-01T00:00:00.1234567891",
"1970-01-01T00:00:00.1234567890",
"1970-01-01T00+00:00:00.1234567891",
"1970-01-01T00+00:00:00.1234567890",
];

for invalid_case in invalid_strings {
Expand Down
38 changes: 30 additions & 8 deletions src/parsers.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
//! This module implements Temporal Date/Time parsing functionality.

use crate::{
error::ErrorMessage,
iso::{IsoDate, IsoTime},
options::{DisplayCalendar, DisplayOffset, DisplayTimeZone},
Sign, TemporalError, TemporalResult,
};
use ixdtf::{
encoding::Utf8,
parsers::IxdtfParser,
records::{Annotation, DateRecord, IxdtfParseRecord, TimeRecord, UtcOffsetRecordOrZ},
records::{
Annotation, DateRecord, FullPrecisionOffset, IxdtfParseRecord, TimeRecord, UtcOffsetRecord,
UtcOffsetRecordOrZ,
},
};
use writeable::{impl_display_with_writeable, LengthHint, Writeable};

Expand Down Expand Up @@ -694,6 +698,21 @@ fn parse_ixdtf(source: &[u8], variant: ParseVariant) -> TemporalResult<IxdtfPars
ParseVariant::Time => parser.parse_time_with_annotation_handler(handler),
}?;

// The ixdtf crate allows arbitrary precision for offsets, but Temporal only allows 9
if let Some(UtcOffsetRecordOrZ::Offset(UtcOffsetRecord::FullPrecisionOffset(
FullPrecisionOffset {
fraction: Some(fraction),
..
},
))) = record.offset
{
if fraction.to_nanoseconds().is_none() {
return Err(
TemporalError::range().with_enum(ErrorMessage::FractionalTimeMoreThanNineDigits)
);
}
}

record.calendar = first_calendar.map(|v| v.value);

// Note: this method only handles the specific AnnotatedFoo nonterminals;
Expand Down Expand Up @@ -806,14 +825,17 @@ pub(crate) fn parse_instant(source: &[u8]) -> TemporalResult<IxdtfParseInstantRe
Ok(IxdtfParseInstantRecord { date, time, offset })
}

// Ensure that the record does not have an offset element.
// Ensure that the record does not have a Z offset element.
//
// This handles the [~Zoned] in TemporalFooString productions
fn check_offset(record: IxdtfParseRecord<'_, Utf8>) -> TemporalResult<IxdtfParseRecord<'_, Utf8>> {
fn check_offset_no_z(
record: IxdtfParseRecord<'_, Utf8>,
) -> TemporalResult<IxdtfParseRecord<'_, Utf8>> {
if record.offset == Some(UtcOffsetRecordOrZ::Z) {
return Err(TemporalError::range()
.with_message("UTC designator is not valid for plain date/time parsing."));
}

Ok(record)
}

Expand All @@ -823,13 +845,13 @@ pub(crate) fn parse_year_month(source: &[u8]) -> TemporalResult<IxdtfParseRecord
let ym_record = parse_ixdtf(source, ParseVariant::YearMonth);

let Err(e) = ym_record else {
return ym_record.and_then(check_offset);
return ym_record.and_then(check_offset_no_z);
};

let dt_parse = parse_date_time(source);

match dt_parse {
Ok(dt) => check_offset(dt),
Ok(dt) => check_offset_no_z(dt),
// Format and return the error from parsing YearMonth.
_ => Err(e),
}
Expand All @@ -839,13 +861,13 @@ pub(crate) fn parse_year_month(source: &[u8]) -> TemporalResult<IxdtfParseRecord
pub(crate) fn parse_month_day(source: &[u8]) -> TemporalResult<IxdtfParseRecord<'_, Utf8>> {
let md_record = parse_ixdtf(source, ParseVariant::MonthDay);
let Err(e) = md_record else {
return md_record.and_then(check_offset);
return md_record.and_then(check_offset_no_z);
};

let dt_parse = parse_date_time(source);

match dt_parse {
Ok(dt) => check_offset(dt),
Ok(dt) => check_offset_no_z(dt),
// Format and return the error from parsing MonthDay.
_ => Err(e),
}
Expand All @@ -854,7 +876,7 @@ pub(crate) fn parse_month_day(source: &[u8]) -> TemporalResult<IxdtfParseRecord<
// Ensures that an IxdtfParseRecord was parsed with [~Zoned][+TimeRequired]
fn check_time_record(record: IxdtfParseRecord<Utf8>) -> TemporalResult<TimeRecord> {
// Handle [~Zoned]
let record = check_offset(record)?;
let record = check_offset_no_z(record)?;
// Handle [+TimeRequired]
let Some(time) = record.time else {
return Err(TemporalError::range()
Expand Down