Skip to content

Commit 7eceb3b

Browse files
authored
Allow parsers to accept unvalidated UTF8 (#295)
Partially resolves #275 We still validate for utf16, ideally the utf16 parsing would be available in the `ixdtf` crate.
1 parent 24ba8db commit 7eceb3b

File tree

17 files changed

+311
-187
lines changed

17 files changed

+311
-187
lines changed

src/builtins/core/date.rs

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,22 @@ impl PlainDate {
431431
partial.calendar.date_from_partial(&partial, overflow)
432432
}
433433

434+
// Converts a UTF-8 encoded string into a `PlainDate`.
435+
pub fn from_utf8(s: &[u8]) -> TemporalResult<Self> {
436+
let parse_record = parse_date_time(s)?;
437+
438+
let calendar = parse_record
439+
.calendar
440+
.map(Calendar::try_from_utf8)
441+
.transpose()?
442+
.unwrap_or_default();
443+
444+
// Assertion: PlainDate must exist on a DateTime parse.
445+
let date = parse_record.date.temporal_unwrap()?;
446+
447+
Self::try_new(date.year, date.month, date.day, calendar)
448+
}
449+
434450
/// Creates a date time with values from a `PartialDate`.
435451
pub fn with(
436452
&self,
@@ -726,18 +742,7 @@ impl FromStr for PlainDate {
726742
type Err = TemporalError;
727743

728744
fn from_str(s: &str) -> Result<Self, Self::Err> {
729-
let parse_record = parse_date_time(s)?;
730-
731-
let calendar = parse_record
732-
.calendar
733-
.map(Calendar::try_from_utf8)
734-
.transpose()?
735-
.unwrap_or_default();
736-
737-
// Assertion: PlainDate must exist on a DateTime parse.
738-
let date = parse_record.date.temporal_unwrap()?;
739-
740-
Self::try_new(date.year, date.month, date.day, calendar)
745+
Self::from_utf8(s.as_bytes())
741746
}
742747
}
743748

src/builtins/core/datetime.rs

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,34 @@ impl PlainDateTime {
449449
Self::from_date_and_time(date, PlainTime::new_unchecked(iso_time))
450450
}
451451

452+
// Converts a UTF-8 encoded string into a `PlainDateTime`.
453+
pub fn from_utf8(s: &[u8]) -> TemporalResult<Self> {
454+
let parse_record = parse_date_time(s)?;
455+
456+
let calendar = parse_record
457+
.calendar
458+
.map(Calendar::try_from_utf8)
459+
.transpose()?
460+
.unwrap_or_default();
461+
462+
let time = parse_record
463+
.time
464+
.map(IsoTime::from_time_record)
465+
.transpose()?
466+
.unwrap_or_default();
467+
468+
let parsed_date = parse_record.date.temporal_unwrap()?;
469+
470+
let date = IsoDate::new_with_overflow(
471+
parsed_date.year,
472+
parsed_date.month,
473+
parsed_date.day,
474+
ArithmeticOverflow::Reject,
475+
)?;
476+
477+
Ok(Self::new_unchecked(IsoDateTime::new(date, time)?, calendar))
478+
}
479+
452480
/// Creates a new `DateTime` with the fields of a `PartialDateTime`.
453481
///
454482
/// ```rust
@@ -816,30 +844,7 @@ impl FromStr for PlainDateTime {
816844
type Err = TemporalError;
817845

818846
fn from_str(s: &str) -> Result<Self, Self::Err> {
819-
let parse_record = parse_date_time(s)?;
820-
821-
let calendar = parse_record
822-
.calendar
823-
.map(Calendar::try_from_utf8)
824-
.transpose()?
825-
.unwrap_or_default();
826-
827-
let time = parse_record
828-
.time
829-
.map(IsoTime::from_time_record)
830-
.transpose()?
831-
.unwrap_or_default();
832-
833-
let parsed_date = parse_record.date.temporal_unwrap()?;
834-
835-
let date = IsoDate::new_with_overflow(
836-
parsed_date.year,
837-
parsed_date.month,
838-
parsed_date.day,
839-
ArithmeticOverflow::Reject,
840-
)?;
841-
842-
Ok(Self::new_unchecked(IsoDateTime::new(date, time)?, calendar))
847+
Self::from_utf8(s.as_bytes())
843848
}
844849
}
845850

src/builtins/core/duration.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,113 @@ impl Duration {
257257
)
258258
}
259259

260+
// Converts a UTF-8 encoded string into a `Duration`.
261+
pub fn from_utf8(s: &[u8]) -> TemporalResult<Self> {
262+
let parse_record = IsoDurationParser::from_utf8(s)
263+
.parse()
264+
.map_err(|e| TemporalError::range().with_message(format!("{e}")))?;
265+
266+
let (hours, minutes, seconds, millis, micros, nanos) = match parse_record.time {
267+
Some(TimeDurationRecord::Hours { hours, fraction }) => {
268+
let unadjusted_fraction =
269+
fraction.and_then(|x| x.to_nanoseconds()).unwrap_or(0) as u64;
270+
let fractional_hours_ns = unadjusted_fraction * 3600;
271+
let minutes = fractional_hours_ns.div_euclid(60 * 1_000_000_000);
272+
let fractional_minutes_ns = fractional_hours_ns.rem_euclid(60 * 1_000_000_000);
273+
274+
let seconds = fractional_minutes_ns.div_euclid(1_000_000_000);
275+
let fractional_seconds = fractional_minutes_ns.rem_euclid(1_000_000_000);
276+
277+
let milliseconds = fractional_seconds.div_euclid(1_000_000);
278+
let rem = fractional_seconds.rem_euclid(1_000_000);
279+
280+
let microseconds = rem.div_euclid(1_000);
281+
let nanoseconds = rem.rem_euclid(1_000);
282+
283+
(
284+
hours,
285+
minutes,
286+
seconds,
287+
milliseconds,
288+
microseconds,
289+
nanoseconds,
290+
)
291+
}
292+
// Minutes variant is defined as { hours: u32, minutes: u32, fraction: u64 }
293+
Some(TimeDurationRecord::Minutes {
294+
hours,
295+
minutes,
296+
fraction,
297+
}) => {
298+
let unadjusted_fraction =
299+
fraction.and_then(|x| x.to_nanoseconds()).unwrap_or(0) as u64;
300+
let fractional_minutes_ns = unadjusted_fraction * 60;
301+
let seconds = fractional_minutes_ns.div_euclid(1_000_000_000);
302+
let fractional_seconds = fractional_minutes_ns.rem_euclid(1_000_000_000);
303+
304+
let milliseconds = fractional_seconds.div_euclid(1_000_000);
305+
let rem = fractional_seconds.rem_euclid(1_000_000);
306+
307+
let microseconds = rem.div_euclid(1_000);
308+
let nanoseconds = rem.rem_euclid(1_000);
309+
310+
(
311+
hours,
312+
minutes,
313+
seconds,
314+
milliseconds,
315+
microseconds,
316+
nanoseconds,
317+
)
318+
}
319+
// Seconds variant is defined as { hours: u32, minutes: u32, seconds: u32, fraction: u32 }
320+
Some(TimeDurationRecord::Seconds {
321+
hours,
322+
minutes,
323+
seconds,
324+
fraction,
325+
}) => {
326+
let ns = fraction.and_then(|x| x.to_nanoseconds()).unwrap_or(0);
327+
let milliseconds = ns.div_euclid(1_000_000);
328+
let rem = ns.rem_euclid(1_000_000);
329+
330+
let microseconds = rem.div_euclid(1_000);
331+
let nanoseconds = rem.rem_euclid(1_000);
332+
333+
(
334+
hours,
335+
minutes,
336+
seconds,
337+
milliseconds as u64,
338+
microseconds as u64,
339+
nanoseconds as u64,
340+
)
341+
}
342+
None => (0, 0, 0, 0, 0, 0),
343+
};
344+
345+
let (years, months, weeks, days) = if let Some(date) = parse_record.date {
346+
(date.years, date.months, date.weeks, date.days)
347+
} else {
348+
(0, 0, 0, 0)
349+
};
350+
351+
let sign = parse_record.sign as i64;
352+
353+
Self::new(
354+
years as i64 * sign,
355+
months as i64 * sign,
356+
weeks as i64 * sign,
357+
days as i64 * sign,
358+
hours as i64 * sign,
359+
minutes as i64 * sign,
360+
seconds as i64 * sign,
361+
millis as i64 * sign,
362+
micros as i128 * sign as i128,
363+
nanos as i128 * sign as i128,
364+
)
365+
}
366+
260367
/// Return if the Durations values are within their valid ranges.
261368
#[inline]
262369
#[must_use]

src/builtins/core/instant.rs

Lines changed: 47 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,51 @@ impl Instant {
163163
Self::try_new(epoch_nanos)
164164
}
165165

166+
// Converts a UTF-8 encoded string into a `Instant`.
167+
pub fn from_utf8(s: &[u8]) -> TemporalResult<Self> {
168+
let ixdtf_record = parse_instant(s)?;
169+
170+
// Find the offset
171+
let ns_offset = match ixdtf_record.offset {
172+
UtcOffsetRecordOrZ::Offset(offset) => {
173+
let ns = offset
174+
.fraction
175+
.and_then(|x| x.to_nanoseconds())
176+
.unwrap_or(0);
177+
(offset.hour as i64 * NANOSECONDS_PER_HOUR
178+
+ i64::from(offset.minute) * NANOSECONDS_PER_MINUTE
179+
+ i64::from(offset.second) * NANOSECONDS_PER_SECOND
180+
+ i64::from(ns))
181+
* offset.sign as i64
182+
}
183+
UtcOffsetRecordOrZ::Z => 0,
184+
};
185+
186+
let time_nanoseconds = ixdtf_record
187+
.time
188+
.fraction
189+
.and_then(|x| x.to_nanoseconds())
190+
.unwrap_or(0);
191+
let (millisecond, rem) = time_nanoseconds.div_rem_euclid(&1_000_000);
192+
let (microsecond, nanosecond) = rem.div_rem_euclid(&1_000);
193+
194+
let balanced = IsoDateTime::balance(
195+
ixdtf_record.date.year,
196+
ixdtf_record.date.month.into(),
197+
ixdtf_record.date.day.into(),
198+
ixdtf_record.time.hour.into(),
199+
ixdtf_record.time.minute.into(),
200+
ixdtf_record.time.second.clamp(0, 59).into(),
201+
millisecond.into(),
202+
microsecond.into(),
203+
i128::from(nanosecond) - i128::from(ns_offset),
204+
);
205+
206+
let nanoseconds = balanced.as_nanoseconds()?;
207+
208+
Ok(Self(nanoseconds))
209+
}
210+
166211
/// Adds a `Duration` to the current `Instant`, returning an error if the `Duration`
167212
/// contains a `DateDuration`.
168213
#[inline]
@@ -274,48 +319,9 @@ impl Instant {
274319

275320
impl FromStr for Instant {
276321
type Err = TemporalError;
277-
fn from_str(s: &str) -> Result<Self, Self::Err> {
278-
let ixdtf_record = parse_instant(s)?;
279-
280-
// Find the offset
281-
let ns_offset = match ixdtf_record.offset {
282-
UtcOffsetRecordOrZ::Offset(offset) => {
283-
let ns = offset
284-
.fraction
285-
.and_then(|x| x.to_nanoseconds())
286-
.unwrap_or(0);
287-
(offset.hour as i64 * NANOSECONDS_PER_HOUR
288-
+ i64::from(offset.minute) * NANOSECONDS_PER_MINUTE
289-
+ i64::from(offset.second) * NANOSECONDS_PER_SECOND
290-
+ i64::from(ns))
291-
* offset.sign as i64
292-
}
293-
UtcOffsetRecordOrZ::Z => 0,
294-
};
295-
296-
let time_nanoseconds = ixdtf_record
297-
.time
298-
.fraction
299-
.and_then(|x| x.to_nanoseconds())
300-
.unwrap_or(0);
301-
let (millisecond, rem) = time_nanoseconds.div_rem_euclid(&1_000_000);
302-
let (microsecond, nanosecond) = rem.div_rem_euclid(&1_000);
303322

304-
let balanced = IsoDateTime::balance(
305-
ixdtf_record.date.year,
306-
ixdtf_record.date.month.into(),
307-
ixdtf_record.date.day.into(),
308-
ixdtf_record.time.hour.into(),
309-
ixdtf_record.time.minute.into(),
310-
ixdtf_record.time.second.clamp(0, 59).into(),
311-
millisecond.into(),
312-
microsecond.into(),
313-
i128::from(nanosecond) - i128::from(ns_offset),
314-
);
315-
316-
let nanoseconds = balanced.as_nanoseconds()?;
317-
318-
Ok(Self(nanoseconds))
323+
fn from_str(s: &str) -> Result<Self, Self::Err> {
324+
Self::from_utf8(s.as_bytes())
319325
}
320326
}
321327

0 commit comments

Comments
 (0)