Skip to content

Commit

Permalink
Patch for partial records (#94)
Browse files Browse the repository at this point in the history
This PR is a patch for some things I found while doing some
implementation of `temporal_rs` in Boa.

The things the PR addresses / patches

- Adds a `DateTime::from_date_and_time`
- I noticed that the `with` methods weren't checking whether the
partials were empty, which would not be valid. This update throws an
early error if the partial provided is empty.
- Changed the internal way for constructing a `TemporalFields` from a
`PartialDate`. Primarily, after doing the implementation, I don't think
having a `From<PartialDate> for TemporalFields` is a good idea. We will
still need it internally, but I'm now leaning more towards using the
calendar to generate the fields from a partial. Something akin to
`calendar::create_fields_from_partial_date(partial_date)`.
  • Loading branch information
nekevss authored Aug 19, 2024
1 parent 51bc30a commit 1e7901d
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 36 deletions.
29 changes: 27 additions & 2 deletions src/components/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use super::{

// TODO: PrepareTemporalFields expects a type error to be thrown when all partial fields are None/undefined.
/// A partial Date that may or may not be complete.
#[derive(Debug, Default, Clone, Copy)]
#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct PartialDate {
// A potentially set `year` field.
pub year: Option<i32>,
Expand All @@ -42,6 +42,13 @@ pub struct PartialDate {
pub era_year: Option<i32>,
}

impl PartialDate {
/// Returns a boolean for if the current `PartialDate` is empty.
pub(crate) fn is_empty(&self) -> bool {
*self == Self::default()
}
}

/// The native Rust implementation of `Temporal.PlainDate`.
#[non_exhaustive]
#[derive(Debug, Default, Clone, PartialEq, Eq)]
Expand Down Expand Up @@ -234,10 +241,13 @@ impl Date {
partial: PartialDate,
overflow: Option<ArithmeticOverflow>,
) -> TemporalResult<Self> {
if partial.is_empty() {
return Err(TemporalError::r#type().with_message("A PartialDate must have a field."));
}
// 6. Let fieldsResult be ? PrepareCalendarFieldsAndFieldNames(calendarRec, temporalDate, « "day", "month", "monthCode", "year" »).
let fields = TemporalFields::from(self);
// 7. Let partialDate be ? PrepareTemporalFields(temporalDateLike, fieldsResult.[[FieldNames]], partial).
let partial_fields = TemporalFields::from(partial);
let partial_fields = TemporalFields::from_partial_date(&partial);

// 8. Let fields be ? CalendarMergeFields(calendarRec, fieldsResult.[[Fields]], partialDate).
let mut merge_result = fields.merge_fields(&partial_fields, self.calendar())?;
Expand Down Expand Up @@ -617,6 +627,21 @@ mod tests {
assert_eq!(result.days(), 9719.0,);
}

#[test]
fn date_with_empty_error() {
let base = Date::new(
1976,
11,
18,
Calendar::default(),
ArithmeticOverflow::Constrain,
)
.unwrap();

let err = base.with(PartialDate::default(), None);
assert!(err.is_err());
}

#[test]
fn basic_date_with() {
let base = Date::new(
Expand Down
30 changes: 27 additions & 3 deletions src/components/datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ use super::{
/// A partial DateTime record
#[derive(Debug, Default, Copy, Clone)]
pub struct PartialDateTime {
date: PartialDate,
time: PartialTime,
/// The `PartialDate` portion of a `PartialDateTime`
pub date: PartialDate,
/// The `PartialTime` portion of a `PartialDateTime`
pub time: PartialTime,
}

/// The native Rust implementation of `Temporal.PlainDateTime`
Expand Down Expand Up @@ -250,16 +252,29 @@ impl DateTime {
))
}

/// Create a `DateTime` from a `Date` and a `Time`.
pub fn from_date_and_time(date: Date, time: Time) -> TemporalResult<Self> {
Ok(Self::new_unchecked(
IsoDateTime::new(date.iso, time.iso)?,
date.calendar().clone(),
))
}

/// Creates a new `DateTime` with the fields of a `PartialDateTime`.
#[inline]
pub fn with(
&self,
partial_datetime: PartialDateTime,
overflow: Option<ArithmeticOverflow>,
) -> TemporalResult<Self> {
if partial_datetime.date.is_empty() && partial_datetime.time.is_empty() {
return Err(
TemporalError::r#type().with_message("A PartialDateTime must have a valid field.")
);
}
// Determine the Date from the provided fields.
let fields = TemporalFields::from(self);
let partial_fields = TemporalFields::from(partial_datetime.date);
let partial_fields = TemporalFields::from_partial_date(&partial_datetime.date);

let mut merge_result = fields.merge_fields(&partial_fields, self.calendar())?;

Expand Down Expand Up @@ -775,6 +790,15 @@ mod tests {
);
}

#[test]
fn datetime_with_empty_partial() {
let pdt =
DateTime::new(2020, 1, 31, 12, 34, 56, 987, 654, 321, Calendar::default()).unwrap();

let err = pdt.with(PartialDateTime::default(), None);
assert!(err.is_err());
}

// options-undefined.js
#[test]
fn datetime_add_test() {
Expand Down
12 changes: 11 additions & 1 deletion src/components/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use super::{duration::normalized::NormalizedTimeDuration, DateTime};
use std::str::FromStr;

/// A `PartialTime` represents partially filled `Time` fields.
#[derive(Debug, Default, Clone, Copy)]
#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct PartialTime {
// A potentially set `hour` field.
pub hour: Option<i32>,
Expand All @@ -33,6 +33,12 @@ pub struct PartialTime {
pub nanosecond: Option<i32>,
}

impl PartialTime {
pub(crate) fn is_empty(&self) -> bool {
*self == Self::default()
}
}

/// The native Rust implementation of `Temporal.PlainTime`.
#[non_exhaustive]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
Expand Down Expand Up @@ -180,6 +186,10 @@ impl Time {
partial: PartialTime,
overflow: Option<ArithmeticOverflow>,
) -> TemporalResult<Self> {
if partial.is_empty() {
return Err(TemporalError::r#type().with_message("PartialTime cannot be empty."));
}

let iso = self
.iso
.with(partial, overflow.unwrap_or(ArithmeticOverflow::Constrain))?;
Expand Down
66 changes: 36 additions & 30 deletions src/fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,40 @@ impl TemporalFields {

Ok(result)
}

/// Creates a `TemporalField` from a `PartialDate`.
pub(crate) fn from_partial_date(partial: &PartialDate) -> Self {
let mut bit_map = FieldMap::empty();
if partial.year.is_some() {
bit_map.set(FieldMap::YEAR, true)
};
if partial.month.is_some() {
bit_map.set(FieldMap::MONTH, true)
};
if partial.month_code.is_some() {
bit_map.set(FieldMap::MONTH_CODE, true)
};
if partial.day.is_some() {
bit_map.set(FieldMap::DAY, true)
};
if partial.era.is_some() {
bit_map.set(FieldMap::ERA, true)
}
if partial.era_year.is_some() {
bit_map.set(FieldMap::ERA_YEAR, true)
}

Self {
bit_map,
year: partial.year,
month: partial.month,
month_code: partial.month_code,
day: partial.day,
era: partial.era,
era_year: partial.era_year,
..Default::default()
}
}
}

impl From<&DateTime> for TemporalFields {
Expand Down Expand Up @@ -504,38 +538,10 @@ impl From<&Date> for TemporalFields {
}
}

// TODO: Remove in favor of a more formal `TemporalFields::create_from_partial method`
impl From<PartialDate> for TemporalFields {
fn from(value: PartialDate) -> Self {
let mut bit_map = FieldMap::empty();
if value.year.is_some() {
bit_map.set(FieldMap::YEAR, true)
};
if value.month.is_some() {
bit_map.set(FieldMap::MONTH, true)
};
if value.month_code.is_some() {
bit_map.set(FieldMap::MONTH_CODE, true)
};
if value.day.is_some() {
bit_map.set(FieldMap::DAY, true)
};
if value.era.is_some() {
bit_map.set(FieldMap::ERA, true)
}
if value.era_year.is_some() {
bit_map.set(FieldMap::ERA_YEAR, true)
}

Self {
bit_map,
year: value.year,
month: value.month,
month_code: value.month_code,
day: value.day,
era: value.era,
era_year: value.era_year,
..Default::default()
}
Self::from_partial_date(&value)
}
}

Expand Down

0 comments on commit 1e7901d

Please sign in to comment.