Browse Source

Refactor Temporal Calendar API for `AnyCalendar` and fields (#3522)

* Refactor calendars for any

* Switch from EmptyCustomCalendar to ()

* TemporalCalendar trace & general cleanup

* Add clippy allow

* Apply review
pull/3540/head
Kevin 11 months ago committed by GitHub
parent
commit
1ef04a757f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      Cargo.lock
  2. 376
      core/engine/src/builtins/temporal/calendar/mod.rs
  3. 164
      core/engine/src/builtins/temporal/calendar/object.rs
  4. 80
      core/engine/src/builtins/temporal/fields.rs
  5. 12
      core/engine/src/builtins/temporal/plain_date/mod.rs
  6. 8
      core/engine/src/builtins/temporal/plain_date_time/mod.rs
  7. 10
      core/engine/src/builtins/temporal/plain_month_day/mod.rs
  8. 8
      core/engine/src/builtins/temporal/plain_year_month/mod.rs
  9. 4
      core/engine/src/builtins/temporal/zoned_date_time/mod.rs
  10. 2
      core/temporal/Cargo.toml
  11. 818
      core/temporal/src/components/calendar.rs
  12. 281
      core/temporal/src/components/calendar/iso.rs
  13. 27
      core/temporal/src/components/date.rs
  14. 25
      core/temporal/src/components/datetime.rs
  15. 19
      core/temporal/src/components/duration.rs
  16. 20
      core/temporal/src/components/month_day.rs
  17. 8
      core/temporal/src/components/tz.rs
  18. 20
      core/temporal/src/components/year_month.rs
  19. 31
      core/temporal/src/components/zoneddatetime.rs
  20. 8
      core/temporal/src/error.rs
  21. 289
      core/temporal/src/fields.rs
  22. 18
      core/temporal/src/parser/tests.rs

8
Cargo.lock generated

@ -1667,7 +1667,9 @@ dependencies = [
"calendrical_calculations",
"databake",
"displaydoc",
"icu_calendar_data",
"icu_locid",
"icu_locid_transform",
"icu_provider",
"serde",
"tinystr",
@ -1675,6 +1677,12 @@ dependencies = [
"zerovec",
]
[[package]]
name = "icu_calendar_data"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22aec7d032735d9acb256eeef72adcac43c3b7572f19b51576a63d664b524ca2"
[[package]]
name = "icu_casemap"
version = "1.4.0"

376
core/engine/src/builtins/temporal/calendar/mod.rs

@ -21,11 +21,11 @@ use crate::{
string::{common::StaticJsStrings, utf16},
Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_gc::{custom_trace, Finalize, Trace};
use boa_profiler::Profiler;
use boa_temporal::{
components::calendar::{
AvailableCalendars, CalendarDateLike, CalendarFieldsType, CalendarSlot,
CalendarDateLike, CalendarFieldsType, CalendarProtocol, CalendarSlot,
CALENDAR_PROTOCOL_METHODS,
},
options::{ArithmeticOverflow, TemporalUnit},
@ -33,22 +33,29 @@ use boa_temporal::{
mod object;
use object::CustomRuntimeCalendar;
#[doc(inline)]
pub(crate) use object::JsCustomCalendar;
#[cfg(feature = "experimental")]
#[cfg(test)]
mod tests;
/// The `Temporal.Calendar` object.
#[derive(Debug, Trace, Finalize, JsData)]
// SAFETY: `Calendar` doesn't contain traceable types.
#[boa_gc(unsafe_empty_trace)]
#[derive(Debug, Finalize, JsData)]
pub struct Calendar {
slot: CalendarSlot,
slot: CalendarSlot<JsCustomCalendar>,
}
unsafe impl Trace for Calendar {
custom_trace!(this, mark, {
match &this.slot {
CalendarSlot::Protocol(custom) => mark(custom),
// SAFETY: CalendarSlot::Builtin does not contain any JsValues for the gc to trace.
CalendarSlot::Builtin(_) => {}
}
});
}
impl Calendar {
pub(crate) fn new(slot: CalendarSlot) -> Self {
pub(crate) fn new(slot: CalendarSlot<JsCustomCalendar>) -> Self {
Self { slot }
}
}
@ -145,11 +152,10 @@ impl BuiltInConstructor for Calendar {
// 3. If IsBuiltinCalendar(id) is false, then
// a. Throw a RangeError exception.
let _ = AvailableCalendars::from_str(&id.to_std_string_escaped())?;
// 4. Return ? CreateTemporalCalendar(id, NewTarget).
create_temporal_calendar(
CalendarSlot::Identifier(id.to_std_string_escaped()),
CalendarSlot::<JsCustomCalendar>::from_str(&id.to_std_string_escaped())?,
Some(new_target.clone()),
context,
)
@ -172,12 +178,7 @@ impl Calendar {
.with_message("the this value of Calendar must be a Calendar object.")
})?;
let protocol = match &calendar.slot {
CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(),
CalendarSlot::Protocol(proto) => proto.clone(),
};
Ok(JsString::from(protocol.identifier(context)?.as_str()).into())
Ok(JsString::from(calendar.slot.identifier(context)?.as_str()).into())
}
/// 15.8.2.1 `Temporal.Calendar.prototype.dateFromFields ( fields [ , options ] )` - Supercedes 12.5.4
@ -196,12 +197,6 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.")
})?;
// Retrieve the current CalendarProtocol.
let protocol = match &calendar.slot {
CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(),
CalendarSlot::Protocol(proto) => proto.clone(),
};
// 3. If Type(fields) is not Object, throw a TypeError exception.
let fields = args.get_or_undefined(0);
let fields_obj = fields.as_object().ok_or_else(|| {
@ -220,7 +215,7 @@ impl Calendar {
]);
// 6. If calendar.[[Identifier]] is "iso8601", then
let mut fields = if protocol.identifier(context)?.as_str() == "iso8601" {
let mut fields = if calendar.slot.is_iso() {
// a. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « "year", "day" »).
let mut required_fields = Vec::from([js_string!("year"), js_string!("day")]);
fields::prepare_temporal_fields(
@ -235,7 +230,8 @@ impl Calendar {
// 7. Else,
} else {
// a. Let calendarRelevantFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], date).
let calendar_relevant_fields = protocol.field_descriptors(CalendarFieldsType::Date);
let calendar_relevant_fields =
calendar.slot.field_descriptors(CalendarFieldsType::Date)?;
// b. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « », calendarRelevantFieldDescriptors).
fields::prepare_temporal_fields(
fields_obj,
@ -260,7 +256,9 @@ impl Calendar {
// a. Perform ? CalendarResolveFields(calendar.[[Identifier]], fields, date).
// b. Let result be ? CalendarDateToISO(calendar.[[Identifier]], fields, overflow).
let result = protocol.date_from_fields(&mut fields, overflow, context)?;
let result = calendar
.slot
.date_from_fields(&mut fields, overflow, context)?;
create_temporal_date(result, None, context).map(Into::into)
}
@ -279,11 +277,6 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.")
})?;
let protocol = match &calendar.slot {
CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(),
CalendarSlot::Protocol(proto) => proto.clone(),
};
let fields = args.get_or_undefined(0);
let fields_obj = fields.as_object().ok_or_else(|| {
JsNativeError::typ().with_message("fields parameter must be an object.")
@ -299,7 +292,7 @@ impl Calendar {
]);
// 6. Set fields to ? PrepareTemporalFields(fields, « "month", "monthCode", "year" », « "year" »).
let mut fields = if protocol.identifier(context)?.as_str() == "iso8601" {
let mut fields = if calendar.slot.identifier(context)?.as_str() == "iso8601" {
// a. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « "year" »).
let mut required_fields = Vec::from([js_string!("year")]);
fields::prepare_temporal_fields(
@ -315,8 +308,9 @@ impl Calendar {
// a. Let calendarRelevantFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], year-month).
// b. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « », calendarRelevantFieldDescriptors).
let calendar_relevant_fields =
protocol.field_descriptors(CalendarFieldsType::YearMonth);
let calendar_relevant_fields = calendar
.slot
.field_descriptors(CalendarFieldsType::YearMonth)?;
fields::prepare_temporal_fields(
fields_obj,
&mut relevant_field_names,
@ -336,7 +330,9 @@ impl Calendar {
let overflow = get_option::<ArithmeticOverflow>(&options, utf16!("overflow"), context)?
.unwrap_or(ArithmeticOverflow::Constrain);
let result = protocol.year_month_from_fields(&mut fields, overflow, context)?;
let result = calendar
.slot
.year_month_from_fields(&mut fields, overflow, context)?;
create_temporal_year_month(result, None, context)
}
@ -357,11 +353,6 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.")
})?;
let protocol = match &calendar.slot {
CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(),
CalendarSlot::Protocol(proto) => proto.clone(),
};
// 3. If Type(fields) is not Object, throw a TypeError exception.
let fields = args.get_or_undefined(0);
let fields_obj = fields.as_object().ok_or_else(|| {
@ -380,7 +371,7 @@ impl Calendar {
]);
// 6. If calendar.[[Identifier]] is "iso8601", then
let mut fields = if protocol.identifier(context)?.as_str() == "iso8601" {
let mut fields = if calendar.slot.identifier(context)?.as_str() == "iso8601" {
// a. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « "day" »).
let mut required_fields = Vec::from([js_string!("day")]);
fields::prepare_temporal_fields(
@ -395,7 +386,9 @@ impl Calendar {
// 7. Else,
} else {
// a. Let calendarRelevantFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], month-day).
let calendar_relevant_fields = protocol.field_descriptors(CalendarFieldsType::MonthDay);
let calendar_relevant_fields = calendar
.slot
.field_descriptors(CalendarFieldsType::MonthDay)?;
// b. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « », calendarRelevantFieldDescriptors).
fields::prepare_temporal_fields(
fields_obj,
@ -412,7 +405,9 @@ impl Calendar {
let overflow = get_option(&options, utf16!("overflow"), context)?
.unwrap_or(ArithmeticOverflow::Constrain);
let result = protocol.month_day_from_fields(&mut fields, overflow, context)?;
let result = calendar
.slot
.month_day_from_fields(&mut fields, overflow, context)?;
create_temporal_month_day(result, None, context)
}
@ -430,11 +425,6 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.")
})?;
let protocol = match &calendar.slot {
CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(),
CalendarSlot::Protocol(proto) => proto.clone(),
};
// 4. Set date to ? ToTemporalDate(date).
let date_like = args.get_or_undefined(0);
let date = temporal::plain_date::to_temporal_date(date_like, None, context)?;
@ -454,7 +444,9 @@ impl Calendar {
// 8. Let balanceResult be ? BalanceTimeDuration(duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], "day").
duration.balance_time_duration(TemporalUnit::Day)?;
let result = protocol.date_add(&date.inner, &duration, overflow, context)?;
let result = calendar
.slot
.date_add(&date.inner, &duration, overflow, context)?;
create_temporal_date(result, None, context).map(Into::into)
}
@ -472,11 +464,6 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.")
})?;
let protocol = match &calendar.slot {
CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(),
CalendarSlot::Protocol(proto) => proto.clone(),
};
// 4. Set one to ? ToTemporalDate(one).
let one = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?;
// 5. Set two to ? ToTemporalDate(two).
@ -496,7 +483,9 @@ impl Calendar {
)?
.unwrap_or(TemporalUnit::Day);
let result = protocol.date_until(&one.inner, &two.inner, largest_unit, context)?;
let result = calendar
.slot
.date_until(&one.inner, &two.inner, largest_unit, context)?;
create_temporal_duration(result, None, context).map(Into::into)
}
@ -511,14 +500,10 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.")
})?;
let protocol = match &calendar.slot {
CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(),
CalendarSlot::Protocol(proto) => proto.clone(),
};
let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?;
let result = protocol
let result = calendar
.slot
.era(&date_like, context)?
.map_or(JsValue::undefined(), |r| JsString::from(r.as_str()).into());
@ -535,14 +520,10 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.")
})?;
let protocol = match &calendar.slot {
CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(),
CalendarSlot::Protocol(proto) => proto.clone(),
};
let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?;
let result = protocol
let result = calendar
.slot
.era_year(&date_like, context)?
.map_or(JsValue::undefined(), JsValue::from);
@ -559,14 +540,9 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.")
})?;
let protocol = match &calendar.slot {
CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(),
CalendarSlot::Protocol(proto) => proto.clone(),
};
let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?;
let result = protocol.year(&date_like, context)?;
let result = calendar.slot.year(&date_like, context)?;
Ok(result.into())
}
@ -581,11 +557,6 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.")
})?;
let protocol = match &calendar.slot {
CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(),
CalendarSlot::Protocol(proto) => proto.clone(),
};
let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?;
// 3. If Type(temporalDateLike) is Object and temporalDateLike has an [[InitializedTemporalMonthDay]] internal slot, then
@ -593,7 +564,7 @@ impl Calendar {
// 4. If Type(temporalDateLike) is not Object or temporalDateLike does not have an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], or [[InitializedTemporalYearMonth]] internal slot, then
// 4.a. Set temporalDateLike to ? ToTemporalDate(temporalDateLike).
let result = protocol.month(&date_like, context)?;
let result = calendar.slot.month(&date_like, context)?;
Ok(result.into())
}
@ -608,14 +579,9 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.")
})?;
let protocol = match &calendar.slot {
CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(),
CalendarSlot::Protocol(proto) => proto.clone(),
};
let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?;
let result = protocol.month_code(&date_like, context)?;
let result = calendar.slot.month_code(&date_like, context)?;
Ok(JsString::from(result.as_str()).into())
}
@ -630,14 +596,9 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.")
})?;
let protocol = match &calendar.slot {
CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(),
CalendarSlot::Protocol(proto) => proto.clone(),
};
let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?;
let result = protocol.day(&date_like, context)?;
let result = calendar.slot.day(&date_like, context)?;
Ok(result.into())
}
@ -654,15 +615,12 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.")
})?;
let protocol = match &calendar.slot {
CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(),
CalendarSlot::Protocol(proto) => proto.clone(),
};
// 3. Let temporalDate be ? ToTemporalDate(temporalDateLike).
let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?;
let result = protocol.day_of_week(&CalendarDateLike::Date(date.inner.clone()), context)?;
let result = calendar
.slot
.day_of_week(&CalendarDateLike::Date(date.inner.clone()), context)?;
Ok(result.into())
}
@ -677,15 +635,12 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.")
})?;
let protocol = match &calendar.slot {
CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(),
CalendarSlot::Protocol(proto) => proto.clone(),
};
// 3. Let temporalDate be ? ToTemporalDate(temporalDateLike).
let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?;
let result = protocol.day_of_year(&CalendarDateLike::Date(date.inner.clone()), context)?;
let result = calendar
.slot
.day_of_year(&CalendarDateLike::Date(date.inner.clone()), context)?;
Ok(result.into())
}
@ -700,15 +655,12 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.")
})?;
let protocol = match &calendar.slot {
CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(),
CalendarSlot::Protocol(proto) => proto.clone(),
};
// 3. Let temporalDate be ? ToTemporalDate(temporalDateLike).
let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?;
let result = protocol.week_of_year(&CalendarDateLike::Date(date.inner.clone()), context)?;
let result = calendar
.slot
.week_of_year(&CalendarDateLike::Date(date.inner.clone()), context)?;
Ok(result.into())
}
@ -723,15 +675,12 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.")
})?;
let protocol = match &calendar.slot {
CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(),
CalendarSlot::Protocol(proto) => proto.clone(),
};
// 3. Let temporalDate be ? ToTemporalDate(temporalDateLike).
let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?;
let result = protocol.year_of_week(&CalendarDateLike::Date(date.inner.clone()), context)?;
let result = calendar
.slot
.year_of_week(&CalendarDateLike::Date(date.inner.clone()), context)?;
Ok(result.into())
}
@ -746,15 +695,12 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.")
})?;
let protocol = match &calendar.slot {
CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(),
CalendarSlot::Protocol(proto) => proto.clone(),
};
// 3. Let temporalDate be ? ToTemporalDate(temporalDateLike).
let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?;
let result = protocol.days_in_week(&CalendarDateLike::Date(date.inner.clone()), context)?;
let result = calendar
.slot
.days_in_week(&CalendarDateLike::Date(date.inner.clone()), context)?;
Ok(result.into())
}
@ -769,14 +715,9 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.")
})?;
let protocol = match &calendar.slot {
CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(),
CalendarSlot::Protocol(proto) => proto.clone(),
};
let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?;
let result = protocol.days_in_month(&date_like, context)?;
let result = calendar.slot.days_in_month(&date_like, context)?;
Ok(result.into())
}
@ -791,13 +732,8 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.")
})?;
let protocol = match &calendar.slot {
CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(),
CalendarSlot::Protocol(proto) => proto.clone(),
};
let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?;
let result = protocol.days_in_year(&date_like, context)?;
let result = calendar.slot.days_in_year(&date_like, context)?;
Ok(result.into())
}
@ -816,14 +752,9 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.")
})?;
let protocol = match &calendar.slot {
CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(),
CalendarSlot::Protocol(proto) => proto.clone(),
};
let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?;
let result = protocol.months_in_year(&date_like, context)?;
let result = calendar.slot.months_in_year(&date_like, context)?;
Ok(result.into())
}
@ -838,14 +769,9 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.")
})?;
let protocol = match &calendar.slot {
CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(),
CalendarSlot::Protocol(proto) => proto.clone(),
};
let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?;
let result = protocol.in_leap_year(&date_like, context)?;
let result = calendar.slot.in_leap_year(&date_like, context)?;
Ok(result.into())
}
@ -862,10 +788,36 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.")
})?;
let protocol = match &calendar.slot {
CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(),
CalendarSlot::Protocol(proto) => proto.clone(),
};
// Custom Calendars override the `fields` method.
if let CalendarSlot::Protocol(proto) = &calendar.slot {
// TODO: Is there a more efficient way to convert from iterable <-> Vec;
let mut iterator_record =
args.get_or_undefined(0)
.get_iterator(context, Some(IteratorHint::Sync), None)?;
let mut fields_list = Vec::default();
while iterator_record.step(context)? {
let next_val = iterator_record.value(context)?;
if let JsValue::String(item) = next_val {
fields_list.push(item.to_std_string_escaped());
} else {
// 1. Let completion be ThrowCompletion(a newly created TypeError object).
let completion = Err(JsNativeError::typ()
.with_message("field must be of type string")
.into());
// 2. Return ? IteratorClose(iteratorRecord, completion).
return iterator_record.close(completion, context);
}
}
let result = proto.fields(fields_list, context)?;
return Ok(Array::create_array_from_list(
result.iter().map(|s| JsString::from(s.clone()).into()),
context,
)
.into());
}
// 3. Let iteratorRecord be ? GetIterator(fields, sync).
let mut iterator_record =
@ -918,11 +870,12 @@ impl Calendar {
// 7. Let result be fieldNames.
// 8. If calendar.[[Identifier]] is not "iso8601", then
if protocol.identifier(context)?.as_str() != "iso8601" {
if !calendar.slot.is_iso() {
// a. NOTE: Every built-in calendar preserves all input field names in output.
// b. Let extraFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], fieldNames).
let extended_fields =
protocol.field_descriptors(CalendarFieldsType::from(&fields_names[..]));
let extended_fields = calendar
.slot
.field_descriptors(CalendarFieldsType::from(&fields_names[..]))?;
// c. For each Calendar Field Descriptor Record desc of extraFieldDescriptors, do
for descriptor in extended_fields {
// i. Append desc.[[Property]] to result.
@ -952,87 +905,46 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.")
})?;
let protocol = match &calendar.slot {
CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(),
CalendarSlot::Protocol(proto) => proto.clone(),
};
let fields = args.get_or_undefined(0).to_object(context)?;
let additional_fields = args.get_or_undefined(1).to_object(context)?;
// 3. Let fieldsCopy be ? SnapshotOwnProperties(? ToObject(fields), null, « », « undefined »).
let fields_copy = temporal::snapshot_own_properties(
&fields,
Some(Vec::new()),
Some(Vec::from([JsValue::undefined()])),
context,
)?;
let fields_copy = temporal::fields::object_to_temporal_fields(&fields, context)?;
// 4. Let additionalFieldsCopy be ? SnapshotOwnProperties(? ToObject(additionalFields), null, « », « undefined »).
let additional_fields_copy = temporal::snapshot_own_properties(
&additional_fields,
Some(Vec::new()),
Some(Vec::from([JsValue::undefined()])),
context,
)?;
let additional_copy =
temporal::fields::object_to_temporal_fields(&additional_fields, context)?;
// 5. NOTE: Every property of fieldsCopy and additionalFieldsCopy is an enumerable data property with non-undefined value, but some property keys may be Symbols.
// 6. Let additionalKeys be ! additionalFieldsCopy.[[OwnPropertyKeys]]().
let add_keys = additional_fields_copy
.__own_property_keys__(context)?
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>();
// Custom Calendars override the `fields` method.
if let CalendarSlot::Protocol(proto) = &calendar.slot {
let result = proto.merge_fields(&fields_copy, &additional_copy, context)?; // TBD
return JsObject::from_temporal_fields(&result, context).map(Into::into);
}
// 5. NOTE: Every property of fieldsCopy and additionalFieldsCopy is an enumerable data property with non-undefined value,
// but some property keys may be Symbols.
// 6. Let additionalKeys be ! additionalFieldsCopy.[[OwnPropertyKeys]]().
// 7. If calendar.[[Identifier]] is "iso8601", then
// a. Let overriddenKeys be ISOFieldKeysToIgnore(additionalKeys).
// 8. Else,
// a. Let overriddenKeys be CalendarFieldKeysToIgnore(calendar, additionalKeys).
let overridden_keys = protocol.field_keys_to_ignore(add_keys);
// 9. Let merged be OrdinaryObjectCreate(null).
let merged = JsObject::with_null_proto();
// 10. NOTE: The following steps ensure that property iteration order of merged
// matches that of fields as modified by omitting overridden properties and
// appending non-overlapping properties from additionalFields in iteration order.
// 11. Let fieldsKeys be ! fieldsCopy.[[OwnPropertyKeys]]().
let field_keys = fields_copy
.__own_property_keys__(context)?
.iter()
.map(|k| JsString::from(k.to_string()))
.collect::<Vec<_>>();
// 12. For each element key of fieldsKeys, do
for key in field_keys {
// a. Let propValue be undefined.
// b. If overriddenKeys contains key, then
let prop_value = if overridden_keys.contains(&key.to_std_string_escaped()) {
// i. Set propValue to ! Get(additionalFieldsCopy, key).
additional_fields_copy.get(key.as_slice(), context)?
// c. Else,
} else {
// i. Set propValue to ! Get(fieldsCopy, key).
fields_copy.get(key.as_slice(), context)?
};
// d. If propValue is not undefined, perform ! CreateDataPropertyOrThrow(merged, key, propValue).
if !prop_value.is_undefined() {
merged.create_data_property_or_throw(key.as_slice(), prop_value, context)?;
}
}
let merged = fields_copy.merge_fields(&additional_copy, &calendar.slot)?;
// 13. Perform ! CopyDataProperties(merged, additionalFieldsCopy, « »).
temporal::copy_data_properties(
&merged,
&additional_fields_copy.into(),
&Vec::new(),
None,
context,
)?;
// 14. Return merged.
Ok(merged.into())
JsObject::from_temporal_fields(&merged, context).map(Into::into)
}
}
@ -1040,7 +952,7 @@ impl Calendar {
/// 12.2.1 `CreateTemporalCalendar ( identifier [ , newTarget ] )`
pub(crate) fn create_temporal_calendar(
identifier: CalendarSlot,
identifier: CalendarSlot<JsCustomCalendar>,
new_target: Option<JsValue>,
context: &mut Context,
) -> JsResult<JsValue> {
@ -1104,7 +1016,7 @@ where
pub(crate) fn get_temporal_calendar_slot_value_with_default(
item: &JsObject,
context: &mut Context,
) -> JsResult<CalendarSlot> {
) -> JsResult<CalendarSlot<JsCustomCalendar>> {
// 1. If item has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal slot, then
// a. Return item.[[Calendar]].
if let Some(calendar) = extract_from_temporal_type(
@ -1133,12 +1045,12 @@ pub(crate) fn get_temporal_calendar_slot_value_with_default(
pub(crate) fn to_temporal_calendar_slot_value(
calendar_like: &JsValue,
context: &mut Context,
) -> JsResult<CalendarSlot> {
) -> JsResult<CalendarSlot<JsCustomCalendar>> {
// 1. If temporalCalendarLike is undefined and default is present, then
// a. Assert: IsBuiltinCalendar(default) is true.
// b. Return default.
if calendar_like.is_undefined() {
return Ok(CalendarSlot::Identifier("iso8601".to_owned()));
return Ok(CalendarSlot::default());
// 2. If Type(temporalCalendarLike) is Object, then
} else if let Some(calendar_like) = calendar_like.as_object() {
// a. If temporalCalendarLike has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal slot, then
@ -1146,26 +1058,10 @@ pub(crate) fn to_temporal_calendar_slot_value(
if let Some(calendar) = extract_from_temporal_type(
calendar_like,
|d| Ok(Some(d.inner.calendar().clone())),
|_dt| {
Err(JsNativeError::range()
.with_message("Not yet implemented.")
.into())
},
|_ym| {
Err(JsNativeError::range()
.with_message("Not yet implemented.")
.into())
},
|_md| {
Err(JsNativeError::range()
.with_message("Not yet implemented.")
.into())
},
|_zdt| {
Err(JsNativeError::range()
.with_message("Not yet implemented.")
.into())
},
|dt| Ok(Some(dt.inner.calendar().clone())),
|ym| Ok(Some(ym.inner.calendar().clone())),
|md| Ok(Some(md.inner.calendar().clone())),
|zdt| Ok(Some(zdt.inner.calendar().clone())),
)? {
return Ok(calendar);
}
@ -1179,23 +1075,24 @@ pub(crate) fn to_temporal_calendar_slot_value(
}
// Types: Box<dyn CalendarProtocol> <- UserCalendar
let protocol = Box::new(CustomRuntimeCalendar::new(calendar_like));
let custom = JsCustomCalendar::new(calendar_like);
// c. Return temporalCalendarLike.
return Ok(CalendarSlot::Protocol(protocol));
return Ok(CalendarSlot::Protocol(custom));
}
// 3. If temporalCalendarLike is not a String, throw a TypeError exception.
if !calendar_like.is_string() {
let JsValue::String(calendar_id) = calendar_like else {
return Err(JsNativeError::typ()
.with_message("temporalCalendarLike is not a string.")
.into());
}
};
// TODO: 4-6
// 4. Let identifier be ? ParseTemporalCalendarString(temporalCalendarLike).
// 5. If IsBuiltinCalendar(identifier) is false, throw a RangeError exception.
// 6. Return the ASCII-lowercase of identifier.
Ok(CalendarSlot::Identifier("iso8601".to_owned()))
Ok(CalendarSlot::<JsCustomCalendar>::from_str(
&calendar_id.to_std_string_escaped(),
)?)
}
fn object_implements_calendar_protocol(calendar_like: &JsObject, context: &mut Context) -> bool {
@ -1207,7 +1104,10 @@ fn object_implements_calendar_protocol(calendar_like: &JsObject, context: &mut C
}
/// Utility function for taking a `JsValue` and converting it to a temporal library `CalendarDateLike` enum.
fn to_calendar_date_like(date_like: &JsValue, context: &mut Context) -> JsResult<CalendarDateLike> {
fn to_calendar_date_like(
date_like: &JsValue,
context: &mut Context,
) -> JsResult<CalendarDateLike<JsCustomCalendar>> {
let Some(obj) = date_like.as_object() else {
let date = temporal::plain_date::to_temporal_date(date_like, None, context)?;

164
core/engine/src/builtins/temporal/calendar/object.rs

@ -1,16 +1,23 @@
//! Boa's implementation of a user-defined Anonymous Calendar.
use crate::{
builtins::temporal::{plain_date, plain_month_day, plain_year_month},
builtins::{
iterable::IteratorHint,
temporal::{
fields::object_to_temporal_fields, plain_date, plain_month_day, plain_year_month,
},
Array,
},
property::PropertyKey,
Context, JsObject, JsString, JsValue,
};
use std::any::Any;
use boa_gc::{Finalize, Trace};
use boa_macros::utf16;
use boa_temporal::{
components::{
calendar::{CalendarDateLike, CalendarFieldsType, CalendarProtocol},
calendar::{CalendarDateLike, CalendarProtocol},
Date, Duration, MonthDay, YearMonth,
},
options::ArithmeticOverflow,
@ -26,12 +33,12 @@ use plain_year_month::PlainYearMonth;
///
/// A user-defined calendar implements all of the `CalendarProtocolMethods`
/// and therefore satisfies the requirements to be used as a calendar.
#[derive(Debug, Clone)]
pub(crate) struct CustomRuntimeCalendar {
#[derive(Debug, Clone, Trace, Finalize)]
pub(crate) struct JsCustomCalendar {
calendar: JsObject,
}
impl CustomRuntimeCalendar {
impl JsCustomCalendar {
pub(crate) fn new(calendar: &JsObject) -> Self {
Self {
calendar: calendar.clone(),
@ -39,13 +46,13 @@ impl CustomRuntimeCalendar {
}
}
impl CalendarProtocol for CustomRuntimeCalendar {
impl CalendarProtocol for JsCustomCalendar {
fn date_from_fields(
&self,
fields: &mut TemporalFields,
overflow: ArithmeticOverflow,
context: &mut dyn Any,
) -> TemporalResult<Date> {
) -> TemporalResult<Date<Self>> {
let context = context
.downcast_mut::<Context>()
.expect("Context was not provided for a CustomCalendar.");
@ -97,7 +104,7 @@ impl CalendarProtocol for CustomRuntimeCalendar {
fields: &mut TemporalFields,
overflow: ArithmeticOverflow,
context: &mut dyn Any,
) -> TemporalResult<YearMonth> {
) -> TemporalResult<YearMonth<JsCustomCalendar>> {
let context = context
.downcast_mut::<Context>()
.expect("Context was not provided for a CustomCalendar.");
@ -151,7 +158,7 @@ impl CalendarProtocol for CustomRuntimeCalendar {
fields: &mut TemporalFields,
overflow: ArithmeticOverflow,
context: &mut dyn Any,
) -> TemporalResult<MonthDay> {
) -> TemporalResult<MonthDay<JsCustomCalendar>> {
let context = context
.downcast_mut::<Context>()
.expect("Context was not provided for a CustomCalendar.");
@ -202,19 +209,19 @@ impl CalendarProtocol for CustomRuntimeCalendar {
fn date_add(
&self,
_date: &Date,
_date: &Date<JsCustomCalendar>,
_duration: &Duration,
_overflow: ArithmeticOverflow,
_context: &mut dyn Any,
) -> TemporalResult<Date> {
) -> TemporalResult<Date<JsCustomCalendar>> {
// TODO
Err(TemporalError::general("Not yet implemented."))
}
fn date_until(
&self,
_one: &Date,
_two: &Date,
_one: &Date<JsCustomCalendar>,
_two: &Date<JsCustomCalendar>,
_largest_unit: boa_temporal::options::TemporalUnit,
_context: &mut dyn Any,
) -> TemporalResult<Duration> {
@ -224,19 +231,27 @@ impl CalendarProtocol for CustomRuntimeCalendar {
fn era(
&self,
_: &CalendarDateLike,
_: &CalendarDateLike<JsCustomCalendar>,
_: &mut dyn Any,
) -> TemporalResult<Option<TinyAsciiStr<8>>> {
) -> TemporalResult<Option<TinyAsciiStr<16>>> {
// Return undefined as custom calendars do not implement -> Currently.
Ok(None)
}
fn era_year(&self, _: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult<Option<i32>> {
fn era_year(
&self,
_: &CalendarDateLike<JsCustomCalendar>,
_: &mut dyn Any,
) -> TemporalResult<Option<i32>> {
// Return undefined as custom calendars do not implement -> Currently.
Ok(None)
}
fn year(&self, date_like: &CalendarDateLike, context: &mut dyn Any) -> TemporalResult<i32> {
fn year(
&self,
date_like: &CalendarDateLike<JsCustomCalendar>,
context: &mut dyn Any,
) -> TemporalResult<i32> {
let context = context
.downcast_mut::<Context>()
.expect("Context was not provided for a CustomCalendar.");
@ -279,7 +294,11 @@ impl CalendarProtocol for CustomRuntimeCalendar {
Ok(result)
}
fn month(&self, date_like: &CalendarDateLike, context: &mut dyn Any) -> TemporalResult<u8> {
fn month(
&self,
date_like: &CalendarDateLike<JsCustomCalendar>,
context: &mut dyn Any,
) -> TemporalResult<u8> {
let context = context
.downcast_mut::<Context>()
.expect("Context was not provided for a CustomCalendar.");
@ -324,7 +343,7 @@ impl CalendarProtocol for CustomRuntimeCalendar {
fn month_code(
&self,
date_like: &CalendarDateLike,
date_like: &CalendarDateLike<JsCustomCalendar>,
context: &mut dyn Any,
) -> TemporalResult<TinyAsciiStr<4>> {
let context = context
@ -354,7 +373,11 @@ impl CalendarProtocol for CustomRuntimeCalendar {
Ok(result)
}
fn day(&self, date_like: &CalendarDateLike, context: &mut dyn Any) -> TemporalResult<u8> {
fn day(
&self,
date_like: &CalendarDateLike<JsCustomCalendar>,
context: &mut dyn Any,
) -> TemporalResult<u8> {
let context = context
.downcast_mut::<Context>()
.expect("Context was not provided for a CustomCalendar.");
@ -399,7 +422,7 @@ impl CalendarProtocol for CustomRuntimeCalendar {
fn day_of_week(
&self,
date_like: &CalendarDateLike,
date_like: &CalendarDateLike<JsCustomCalendar>,
context: &mut dyn Any,
) -> TemporalResult<u16> {
let context = context
@ -448,7 +471,7 @@ impl CalendarProtocol for CustomRuntimeCalendar {
fn day_of_year(
&self,
date_like: &CalendarDateLike,
date_like: &CalendarDateLike<JsCustomCalendar>,
context: &mut dyn Any,
) -> TemporalResult<u16> {
let context = context
@ -497,7 +520,7 @@ impl CalendarProtocol for CustomRuntimeCalendar {
fn week_of_year(
&self,
date_like: &CalendarDateLike,
date_like: &CalendarDateLike<JsCustomCalendar>,
context: &mut dyn Any,
) -> TemporalResult<u16> {
let context = context
@ -546,7 +569,7 @@ impl CalendarProtocol for CustomRuntimeCalendar {
fn year_of_week(
&self,
date_like: &CalendarDateLike,
date_like: &CalendarDateLike<JsCustomCalendar>,
context: &mut dyn Any,
) -> TemporalResult<i32> {
let context = context
@ -588,7 +611,7 @@ impl CalendarProtocol for CustomRuntimeCalendar {
fn days_in_week(
&self,
date_like: &CalendarDateLike,
date_like: &CalendarDateLike<JsCustomCalendar>,
context: &mut dyn Any,
) -> TemporalResult<u16> {
let context = context
@ -637,7 +660,7 @@ impl CalendarProtocol for CustomRuntimeCalendar {
fn days_in_month(
&self,
date_like: &CalendarDateLike,
date_like: &CalendarDateLike<JsCustomCalendar>,
context: &mut dyn Any,
) -> TemporalResult<u16> {
let context = context
@ -687,7 +710,7 @@ impl CalendarProtocol for CustomRuntimeCalendar {
fn days_in_year(
&self,
date_like: &CalendarDateLike,
date_like: &CalendarDateLike<JsCustomCalendar>,
context: &mut dyn Any,
) -> TemporalResult<u16> {
let context = context
@ -736,7 +759,7 @@ impl CalendarProtocol for CustomRuntimeCalendar {
fn months_in_year(
&self,
date_like: &CalendarDateLike,
date_like: &CalendarDateLike<JsCustomCalendar>,
context: &mut dyn Any,
) -> TemporalResult<u16> {
let context = context
@ -787,7 +810,7 @@ impl CalendarProtocol for CustomRuntimeCalendar {
fn in_leap_year(
&self,
date_like: &CalendarDateLike,
date_like: &CalendarDateLike<JsCustomCalendar>,
context: &mut dyn Any,
) -> TemporalResult<bool> {
let context = context
@ -816,18 +839,85 @@ impl CalendarProtocol for CustomRuntimeCalendar {
Ok(result)
}
// TODO: Determine fate of fn fields()
fn fields(&self, fields: Vec<String>, context: &mut dyn Any) -> TemporalResult<Vec<String>> {
let context = context
.downcast_mut::<Context>()
.expect("Context was not provided for a CustomCalendar.");
fn field_descriptors(&self, _: CalendarFieldsType) -> Vec<(String, bool)> {
Vec::default()
let fields_js = Array::create_array_from_list(
fields.iter().map(|s| JsString::from(s.clone()).into()),
context,
);
let method = self
.calendar
.get(PropertyKey::from(utf16!("fields")), context)
.expect("method must exist on an object that implements the CalendarProtocol.");
let result = method
.as_callable()
.expect("is method")
.call(&method, &[fields_js.into()], context)
.map_err(|e| TemporalError::general(e.to_string()))?;
// validate result and map to a `Vec<String>`
let mut iterator = result
.get_iterator(context, Some(IteratorHint::Sync), None)
.map_err(|e| TemporalError::general(e.to_string()))?;
let mut result = Vec::default();
while iterator
.step(context)
.map_err(|e| TemporalError::general(e.to_string()))?
{
let next_value = iterator
.value(context)
.map_err(|e| TemporalError::general(e.to_string()))?;
let JsValue::String(s) = next_value else {
return Err(TemporalError::r#type()
.with_message("Invalid return type in fields method implementation."));
};
result.push(s.to_std_string_escaped());
}
fn field_keys_to_ignore(&self, _: Vec<String>) -> Vec<String> {
Vec::default()
Ok(result)
}
fn resolve_fields(&self, _: &mut TemporalFields, _: CalendarFieldsType) -> TemporalResult<()> {
Ok(())
fn merge_fields(
&self,
fields: &TemporalFields,
additional_fields: &TemporalFields,
context: &mut dyn Any,
) -> TemporalResult<TemporalFields> {
let context = context
.downcast_mut::<Context>()
.expect("Context was not provided for a CustomCalendar.");
let fields = JsObject::from_temporal_fields(fields, context)
.map_err(|e| TemporalError::general(e.to_string()))?;
let add_fields = JsObject::from_temporal_fields(additional_fields, context)
.map_err(|e| TemporalError::general(e.to_string()))?;
let method = self
.calendar
.get(PropertyKey::from(utf16!("mergeFields")), context)
.expect("method must exist on an object that implements the CalendarProtocol.");
let value = method
.as_callable()
.expect("is method")
.call(&method, &[fields.into(), add_fields.into()], context)
.map_err(|e| TemporalError::general(e.to_string()))?;
let JsValue::Object(o) = value else {
return Err(
TemporalError::r#type().with_message("mergeFields did not return an object.")
);
};
object_to_temporal_fields(&o, context).map_err(|e| TemporalError::general(e.to_string()))
}
fn identifier(&self, context: &mut dyn Any) -> TemporalResult<String> {
@ -854,7 +944,7 @@ impl CalendarProtocol for CustomRuntimeCalendar {
/// Utility function for converting `Temporal`'s `CalendarDateLike` to it's `Boa` specific `JsObject`.
pub(crate) fn date_like_to_object(
date_like: &CalendarDateLike,
date_like: &CalendarDateLike<JsCustomCalendar>,
context: &mut Context,
) -> TemporalResult<JsValue> {
match date_like {

80
core/engine/src/builtins/temporal/fields.rs

@ -3,8 +3,8 @@
use std::str::FromStr;
use crate::{
js_string, property::PropertyKey, value::PreferredType, Context, JsNativeError, JsObject,
JsResult, JsString, JsValue,
js_string, object::internal_methods::InternalMethodContext, property::PropertyKey,
value::PreferredType, Context, JsNativeError, JsObject, JsResult, JsString, JsValue,
};
use rustc_hash::FxHashSet;
@ -152,6 +152,82 @@ pub(crate) fn prepare_temporal_fields(
Ok(result)
}
// NOTE(nekevss): The below serves as a replacement for `Snapshot` on `Calendar.prototype.mergeFields`.
//
// Some potential issues here: `Calendar.prototype.mergeFields` appears to allow extra fields that
// are not part of a `TemporalFields` record; however, the specification only calls `mergeFields` on an
// object returned by `PrepareTemporalFields`, so the translation should be fine sound.
//
// The restriction/trade-off would occur if someone wanted to include non-normative calendar fields (i.e. something
// not accounted for in the specification) in a Custom Calendar or use `Calendar.prototype.mergeFields` in
// general as a way to merge two objects.
pub(crate) fn object_to_temporal_fields(
source: &JsObject,
context: &mut Context,
) -> JsResult<TemporalFields> {
// Adapted from `CopyDataProperties` with ExcludedKeys -> << >> && ExcludedValues -> << Undefined >>
const VALID_FIELDS: [&str; 14] = [
"year",
"month",
"monthCode",
"day",
"hour",
"minute",
"second",
"millisecond",
"microsecond",
"nanosecond",
"offset",
"timeZone",
"era",
"eraYear",
];
let mut copy = TemporalFields::default();
let keys = source.__own_property_keys__(context)?;
for key in &keys {
let desc = source.__get_own_property__(key, &mut InternalMethodContext::new(context))?;
match desc {
// Enforce that the `PropertyKey` is a valid field here.
Some(desc)
if desc.expect_enumerable() && VALID_FIELDS.contains(&key.to_string().as_str()) =>
{
let value = source.get(key.clone(), context)?;
// Below is repurposed from `PrepareTemporalFields`.
if !value.is_undefined() {
let conversion = FieldConversion::from_str(&key.to_string())?;
let converted_value = match conversion {
FieldConversion::ToIntegerWithTruncation => {
let v = to_integer_with_truncation(&value, context)?;
FieldValue::Integer(v)
}
FieldConversion::ToPositiveIntegerWithTruncation => {
let v = to_positive_integer_with_trunc(&value, context)?;
FieldValue::Integer(v)
}
FieldConversion::ToPrimativeAndRequireString => {
let primitive = value.to_primitive(context, PreferredType::String)?;
FieldValue::String(
primitive.to_string(context)?.to_std_string_escaped(),
)
}
FieldConversion::None => {
unreachable!("todo need to implement conversion handling for tz.")
}
};
// TODO: Test the below further and potentially expand handling.
copy.set_field_value(&key.to_string(), &converted_value)
.expect("FieldConversion enforces the appropriate type");
}
}
_ => {}
};
}
Ok(copy)
}
impl JsObject {
pub(crate) fn from_temporal_fields(
fields: &TemporalFields,

12
core/engine/src/builtins/temporal/plain_date/mod.rs

@ -21,17 +21,17 @@ use boa_temporal::{
options::ArithmeticOverflow,
};
use super::{calendar, PlainDateTime, ZonedDateTime};
use super::{calendar, JsCustomCalendar, PlainDateTime, ZonedDateTime};
/// The `Temporal.PlainDate` object.
#[derive(Debug, Clone, Trace, Finalize, JsData)]
#[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerDate` could contain `Trace` types.
pub struct PlainDate {
pub(crate) inner: InnerDate,
pub(crate) inner: InnerDate<JsCustomCalendar>,
}
impl PlainDate {
pub(crate) fn new(inner: InnerDate) -> Self {
pub(crate) fn new(inner: InnerDate<JsCustomCalendar>) -> Self {
Self { inner }
}
}
@ -400,7 +400,7 @@ impl PlainDate {
/// 3.5.3 `CreateTemporalDate ( isoYear, isoMonth, isoDay, calendar [ , newTarget ] )`
pub(crate) fn create_temporal_date(
inner: InnerDate,
inner: InnerDate<JsCustomCalendar>,
new_target: Option<&JsValue>,
context: &mut Context,
) -> JsResult<JsObject> {
@ -412,7 +412,7 @@ pub(crate) fn create_temporal_date(
};
// 2. If ISODateTimeWithinLimits(isoYear, isoMonth, isoDay, 12, 0, 0, 0, 0, 0) is false, throw a RangeError exception.
if !DateTime::validate(&inner) {
if !DateTime::<JsCustomCalendar>::validate(&inner) {
return Err(JsNativeError::range()
.with_message("Date is not within ISO date time limits.")
.into());
@ -513,7 +513,7 @@ pub(crate) fn to_temporal_date(
// 13. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], calendar).
let result = date_like_string
.to_std_string_escaped()
.parse::<InnerDate>()
.parse::<InnerDate<JsCustomCalendar>>()
.map_err(|err| JsNativeError::range().with_message(err.to_string()))?;
Ok(PlainDate::new(result))

8
core/engine/src/builtins/temporal/plain_date_time/mod.rs

@ -14,19 +14,21 @@ use boa_profiler::Profiler;
use boa_temporal::components::DateTime as InnerDateTime;
use super::JsCustomCalendar;
/// The `Temporal.PlainDateTime` object.
#[derive(Debug, Clone, Trace, Finalize, JsData)]
#[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerDateTime` could contain `Trace` types.
pub struct PlainDateTime {
pub(crate) inner: InnerDateTime,
pub(crate) inner: InnerDateTime<JsCustomCalendar>,
}
impl PlainDateTime {
fn new(inner: InnerDateTime) -> Self {
fn new(inner: InnerDateTime<JsCustomCalendar>) -> Self {
Self { inner }
}
pub(crate) fn inner(&self) -> &InnerDateTime {
pub(crate) fn inner(&self) -> &InnerDateTime<JsCustomCalendar> {
&self.inner
}
}

10
core/engine/src/builtins/temporal/plain_month_day/mod.rs

@ -14,15 +14,17 @@ use boa_profiler::Profiler;
use boa_temporal::components::{DateTime, MonthDay as InnerMonthDay};
use super::JsCustomCalendar;
/// The `Temporal.PlainMonthDay` object.
#[derive(Debug, Clone, Trace, Finalize, JsData)]
#[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerMonthDay` could contain `Trace` types.
pub struct PlainMonthDay {
pub(crate) inner: InnerMonthDay,
pub(crate) inner: InnerMonthDay<JsCustomCalendar>,
}
impl PlainMonthDay {
fn new(inner: InnerMonthDay) -> Self {
fn new(inner: InnerMonthDay<JsCustomCalendar>) -> Self {
Self { inner }
}
}
@ -69,13 +71,13 @@ impl BuiltInConstructor for PlainMonthDay {
// ==== `PlainMonthDay` Abstract Operations ====
pub(crate) fn create_temporal_month_day(
inner: InnerMonthDay,
inner: InnerMonthDay<JsCustomCalendar>,
new_target: Option<&JsValue>,
context: &mut Context,
) -> JsResult<JsValue> {
// 1. If IsValidISODate(referenceISOYear, isoMonth, isoDay) is false, throw a RangeError exception.
// 2. If ISODateTimeWithinLimits(referenceISOYear, isoMonth, isoDay, 12, 0, 0, 0, 0, 0) is false, throw a RangeError exception.
if DateTime::validate(&inner) {
if DateTime::<JsCustomCalendar>::validate(&inner) {
return Err(JsNativeError::range()
.with_message("PlainMonthDay is not a valid ISO date time.")
.into());

8
core/engine/src/builtins/temporal/plain_year_month/mod.rs

@ -13,18 +13,18 @@ use crate::{
use boa_gc::{Finalize, Trace};
use boa_profiler::Profiler;
use super::calendar::to_temporal_calendar_slot_value;
use super::{calendar::to_temporal_calendar_slot_value, JsCustomCalendar};
use boa_temporal::{components::YearMonth as InnerYearMonth, options::ArithmeticOverflow};
/// The `Temporal.PlainYearMonth` object.
#[derive(Debug, Clone, Trace, Finalize, JsData)]
#[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerYearMonth` could contain `Trace` types.
pub struct PlainYearMonth {
pub(crate) inner: InnerYearMonth,
pub(crate) inner: InnerYearMonth<JsCustomCalendar>,
}
impl PlainYearMonth {
pub(crate) fn new(inner: InnerYearMonth) -> Self {
pub(crate) fn new(inner: InnerYearMonth<JsCustomCalendar>) -> Self {
Self { inner }
}
}
@ -269,7 +269,7 @@ impl PlainYearMonth {
// 9.5.5 `CreateTemporalYearMonth ( isoYear, isoMonth, calendar, referenceISODay [ , newTarget ] )`
pub(crate) fn create_temporal_year_month(
ym: InnerYearMonth,
ym: InnerYearMonth<JsCustomCalendar>,
new_target: Option<&JsValue>,
context: &mut Context,
) -> JsResult<JsValue> {

4
core/engine/src/builtins/temporal/zoned_date_time/mod.rs

@ -11,12 +11,14 @@ use boa_gc::{Finalize, Trace};
use boa_profiler::Profiler;
use boa_temporal::components::{Duration as TemporalDuration, ZonedDateTime as InnerZdt};
use super::JsCustomCalendar;
/// The `Temporal.ZonedDateTime` object.
#[derive(Debug, Clone, Finalize, Trace, JsData)]
// SAFETY: ZonedDateTime does not contain any traceable types.
#[boa_gc(unsafe_empty_trace)]
pub struct ZonedDateTime {
inner: InnerZdt,
pub(crate) inner: InnerZdt<JsCustomCalendar>,
}
impl BuiltInObject for ZonedDateTime {

2
core/temporal/Cargo.toml

@ -13,7 +13,7 @@ rust-version.workspace = true
[dependencies]
tinystr = "0.7.4"
icu_calendar = { workspace = true, default-features = false }
icu_calendar = { workspace = true, default-features = true }
rustc-hash = { workspace = true, features = ["std"] }
num-bigint = { workspace = true, features = ["serde"] }
bitflags.workspace = true

818
core/temporal/src/components/calendar.rs

File diff suppressed because it is too large Load Diff

281
core/temporal/src/components/calendar/iso.rs

@ -1,281 +0,0 @@
//! Implementation of the "iso8601" calendar.
use crate::{
components::{Date, Duration, MonthDay, YearMonth},
error::TemporalError,
fields::TemporalFields,
options::{ArithmeticOverflow, TemporalUnit},
utils, TemporalResult,
};
use std::any::Any;
use tinystr::TinyAsciiStr;
use super::{CalendarDateLike, CalendarFieldsType, CalendarProtocol, CalendarSlot};
use icu_calendar::week::{RelativeUnit, WeekCalculator};
/// This represents the implementation of the `ISO8601`
/// calendar for Temporal.
#[derive(Debug, Clone, Copy)]
pub struct IsoCalendar;
impl CalendarProtocol for IsoCalendar {
/// Temporal 15.8.2.1 `Temporal.prototype.dateFromFields( fields [, options])` - Supercedes 12.5.4
///
/// This is a basic implementation for an iso8601 calendar's `dateFromFields` method.
fn date_from_fields(
&self,
fields: &mut TemporalFields,
overflow: ArithmeticOverflow,
_: &mut dyn Any,
) -> TemporalResult<Date> {
// NOTE: we are in ISO by default here.
// a. Perform ? ISOResolveMonth(fields).
// b. Let result be ? ISODateFromFields(fields, overflow).
fields.iso_resolve_month()?;
// 9. Return ? CreateDate(result.[[Year]], result.[[Month]], result.[[Day]], "iso8601").
Date::new(
fields.year().unwrap_or(0),
fields.month().unwrap_or(0),
fields.day().unwrap_or(0),
CalendarSlot::Identifier("iso8601".to_string()),
overflow,
)
}
/// 12.5.5 `Temporal.Calendar.prototype.yearMonthFromFields ( fields [ , options ] )`
///
/// This is a basic implementation for an iso8601 calendar's `yearMonthFromFields` method.
fn year_month_from_fields(
&self,
fields: &mut TemporalFields,
overflow: ArithmeticOverflow,
_: &mut dyn Any,
) -> TemporalResult<YearMonth> {
// 9. If calendar.[[Identifier]] is "iso8601", then
// a. Perform ? ISOResolveMonth(fields).
fields.iso_resolve_month()?;
// TODO: Do we even need ISOYearMonthFromFields? YearMonth would should pass as a valid date
// b. Let result be ? ISOYearMonthFromFields(fields, overflow).
// 10. Return ? CreateYearMonth(result.[[Year]], result.[[Month]], "iso8601", result.[[ReferenceISODay]]).
YearMonth::new(
fields.year().unwrap_or(0),
fields.month().unwrap_or(0),
fields.day(),
CalendarSlot::Identifier("iso8601".to_string()),
overflow,
)
}
/// 12.5.6 `Temporal.Calendar.prototype.monthDayFromFields ( fields [ , options ] )`
///
/// This is a basic implementation for an iso8601 calendar's `monthDayFromFields` method.
fn month_day_from_fields(
&self,
fields: &mut TemporalFields,
overflow: ArithmeticOverflow,
_: &mut dyn Any,
) -> TemporalResult<MonthDay> {
// 8. Perform ? ISOResolveMonth(fields).
fields.iso_resolve_month()?;
// TODO: double check error mapping is correct for specifcation/test262.
// 9. Let result be ? ISOMonthDayFromFields(fields, overflow).
// 10. Return ? CreateMonthDay(result.[[Month]], result.[[Day]], "iso8601", result.[[ReferenceISOYear]]).
MonthDay::new(
fields.month().unwrap_or(0),
fields.month().unwrap_or(0),
CalendarSlot::Identifier("iso8601".to_string()),
overflow,
)
}
/// 12.5.7 `Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] )`
///
/// Below implements the basic implementation for an iso8601 calendar's `dateAdd` method.
fn date_add(
&self,
_date: &Date,
_duration: &Duration,
_overflow: ArithmeticOverflow,
_: &mut dyn Any,
) -> TemporalResult<Date> {
// TODO: Not stable on `ICU4X`. Implement once completed.
Err(TemporalError::range().with_message("feature not implemented."))
// 9. Let result be ? AddISODate(date.[[ISOYear]], date.[[ISOMonth]], date.[[ISODay]], duration.[[Years]], duration.[[Months]], duration.[[Weeks]], balanceResult.[[Days]], overflow).
// 10. Return ? CreateDate(result.[[Year]], result.[[Month]], result.[[Day]], "iso8601").
}
/// 12.5.8 `Temporal.Calendar.prototype.dateUntil ( one, two [ , options ] )`
///
/// Below implements the basic implementation for an iso8601 calendar's `dateUntil` method.
fn date_until(
&self,
_one: &Date,
_two: &Date,
_largest_unit: TemporalUnit,
_: &mut dyn Any,
) -> TemporalResult<Duration> {
// TODO: Not stable on `ICU4X`. Implement once completed.
Err(TemporalError::range().with_message("Feature not yet implemented."))
// 9. Let result be DifferenceISODate(one.[[ISOYear]], one.[[ISOMonth]], one.[[ISODay]], two.[[ISOYear]], two.[[ISOMonth]], two.[[ISODay]], largestUnit).
// 10. Return ! CreateTemporalDuration(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], 0, 0, 0, 0, 0, 0).
}
/// `Temporal.Calendar.prototype.era( dateLike )` for iso8601 calendar.
fn era(
&self,
_: &CalendarDateLike,
_: &mut dyn Any,
) -> TemporalResult<Option<TinyAsciiStr<8>>> {
// Returns undefined on iso8601.
Ok(None)
}
/// `Temporal.Calendar.prototype.eraYear( dateLike )` for iso8601 calendar.
fn era_year(&self, _: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult<Option<i32>> {
// Returns undefined on iso8601.
Ok(None)
}
/// Returns the `year` for the `Iso` calendar.
fn year(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult<i32> {
Ok(date_like.as_iso_date().year())
}
/// Returns the `month` for the `Iso` calendar.
fn month(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult<u8> {
Ok(date_like.as_iso_date().month())
}
/// Returns the `monthCode` for the `Iso` calendar.
fn month_code(
&self,
date_like: &CalendarDateLike,
_: &mut dyn Any,
) -> TemporalResult<TinyAsciiStr<4>> {
let date = date_like.as_iso_date().as_icu4x()?;
Ok(date.month().code.0)
}
/// Returns the `day` for the `Iso` calendar.
fn day(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult<u8> {
Ok(date_like.as_iso_date().day())
}
/// Returns the `dayOfWeek` for the `Iso` calendar.
fn day_of_week(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult<u16> {
let date = date_like.as_iso_date().as_icu4x()?;
Ok(date.day_of_week() as u16)
}
/// Returns the `dayOfYear` for the `Iso` calendar.
fn day_of_year(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult<u16> {
let date = date_like.as_iso_date().as_icu4x()?;
Ok(date.day_of_year_info().day_of_year)
}
/// Returns the `weekOfYear` for the `Iso` calendar.
fn week_of_year(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult<u16> {
let date = date_like.as_iso_date().as_icu4x()?;
let week_calculator = WeekCalculator::default();
let week_of = date
.week_of_year(&week_calculator)
.map_err(|err| TemporalError::range().with_message(err.to_string()))?;
Ok(week_of.week)
}
/// Returns the `yearOfWeek` for the `Iso` calendar.
fn year_of_week(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult<i32> {
let date = date_like.as_iso_date().as_icu4x()?;
let week_calculator = WeekCalculator::default();
let week_of = date
.week_of_year(&week_calculator)
.map_err(|err| TemporalError::range().with_message(err.to_string()))?;
// TODO: Reach out and see about RelativeUnit starting at -1
// Ok(date.year().number - week_of.unit)
match week_of.unit {
RelativeUnit::Previous => Ok(date.year().number - 1),
RelativeUnit::Current => Ok(date.year().number),
RelativeUnit::Next => Ok(date.year().number + 1),
}
}
/// Returns the `daysInWeek` value for the `Iso` calendar.
fn days_in_week(&self, _: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult<u16> {
Ok(7)
}
/// Returns the `daysInMonth` value for the `Iso` calendar.
fn days_in_month(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult<u16> {
let date = date_like.as_iso_date().as_icu4x()?;
Ok(u16::from(date.days_in_month()))
}
/// Returns the `daysInYear` value for the `Iso` calendar.
fn days_in_year(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult<u16> {
let date = date_like.as_iso_date().as_icu4x()?;
Ok(date.days_in_year())
}
/// Return the amount of months in an ISO Calendar.
fn months_in_year(&self, _: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult<u16> {
Ok(12)
}
/// Returns whether provided date is in a leap year according to this calendar.
fn in_leap_year(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult<bool> {
// `ICU4X`'s `CalendarArithmetic` is currently private.
Ok(utils::mathematical_days_in_year(date_like.as_iso_date().year()) == 366)
}
// Resolve the fields for the iso calendar.
fn resolve_fields(
&self,
fields: &mut TemporalFields,
_: CalendarFieldsType,
) -> TemporalResult<()> {
fields.iso_resolve_month()?;
Ok(())
}
/// Returns the ISO field descriptors, which is not called for the iso8601 calendar.
fn field_descriptors(&self, _: CalendarFieldsType) -> Vec<(String, bool)> {
// NOTE(potential improvement): look into implementing field descriptors and call
// ISO like any other calendar?
// Field descriptors is unused on ISO8601.
unreachable!()
}
/// Returns the `CalendarFieldKeysToIgnore` implementation for ISO.
fn field_keys_to_ignore(&self, additional_keys: Vec<String>) -> Vec<String> {
let mut result = Vec::new();
for key in &additional_keys {
result.push(key.clone());
if key.as_str() == "month" {
result.push("monthCode".to_string());
} else if key.as_str() == "monthCode" {
result.push("month".to_string());
}
}
result
}
// NOTE: This is currently not a name that is compliant with
// the Temporal proposal. For debugging purposes only.
/// Returns the debug name.
fn identifier(&self, _: &mut dyn Any) -> TemporalResult<String> {
Ok("iso8601".to_string())
}
}

27
core/temporal/src/components/date.rs

@ -2,7 +2,7 @@
use crate::{
components::{
calendar::{AvailableCalendars, CalendarSlot},
calendar::{CalendarProtocol, CalendarSlot},
duration::DateDuration,
DateTime, Duration,
},
@ -15,18 +15,18 @@ use std::{any::Any, str::FromStr};
/// The native Rust implementation of `Temporal.PlainDate`.
#[derive(Debug, Default, Clone)]
pub struct Date {
pub struct Date<C: CalendarProtocol> {
iso: IsoDate,
calendar: CalendarSlot,
calendar: CalendarSlot<C>,
}
// ==== Private API ====
impl Date {
impl<C: CalendarProtocol> Date<C> {
/// Create a new `Date` with the date values and calendar slot.
#[inline]
#[must_use]
pub(crate) fn new_unchecked(iso: IsoDate, calendar: CalendarSlot) -> Self {
pub(crate) fn new_unchecked(iso: IsoDate, calendar: CalendarSlot<C>) -> Self {
Self { iso, calendar }
}
@ -46,13 +46,13 @@ impl Date {
// ==== Public API ====
impl Date {
impl<C: CalendarProtocol> Date<C> {
/// Creates a new `Date` while checking for validity.
pub fn new(
year: i32,
month: i32,
day: i32,
calendar: CalendarSlot,
calendar: CalendarSlot<C>,
overflow: ArithmeticOverflow,
) -> TemporalResult<Self> {
let iso = IsoDate::new(year, month, day, overflow)?;
@ -61,7 +61,7 @@ impl Date {
#[must_use]
/// Creates a `Date` from a `DateTime`.
pub fn from_datetime(dt: &DateTime) -> Self {
pub fn from_datetime(dt: &DateTime<C>) -> Self {
Self {
iso: dt.iso_date(),
calendar: dt.calendar().clone(),
@ -99,7 +99,7 @@ impl Date {
#[inline]
#[must_use]
/// Returns a reference to this `Date`'s calendar slot.
pub fn calendar(&self) -> &CalendarSlot {
pub fn calendar(&self) -> &CalendarSlot<C> {
&self.calendar
}
@ -121,7 +121,7 @@ impl Date {
}
}
impl IsoDateSlots for Date {
impl<C: CalendarProtocol> IsoDateSlots for Date<C> {
/// Returns the structs `IsoDate`
fn iso_date(&self) -> IsoDate {
self.iso
@ -130,7 +130,7 @@ impl IsoDateSlots for Date {
// ==== Context based API ====
impl Date {
impl<C: CalendarProtocol> Date<C> {
/// Returns the date after adding the given duration to date with a provided context.
///
/// Temporal Equivalent: 3.5.13 `AddDate ( calendar, plainDate, duration [ , options [ , dateAdd ] ] )`
@ -224,14 +224,13 @@ impl Date {
// ==== Trait impls ====
impl FromStr for Date {
impl<C: CalendarProtocol> FromStr for Date<C> {
type Err = TemporalError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parse_record = parse_date_time(s)?;
let calendar = parse_record.calendar.unwrap_or("iso8601".to_owned());
let _ = AvailableCalendars::from_str(calendar.to_ascii_lowercase().as_str())?;
let date = IsoDate::new(
parse_record.date.year,
@ -242,7 +241,7 @@ impl FromStr for Date {
Ok(Self::new_unchecked(
date,
CalendarSlot::Identifier(calendar),
CalendarSlot::from_str(&calendar)?,
))
}
}

25
core/temporal/src/components/datetime.rs

@ -3,7 +3,10 @@
use std::str::FromStr;
use crate::{
components::{calendar::CalendarSlot, Instant},
components::{
calendar::{CalendarProtocol, CalendarSlot},
Instant,
},
iso::{IsoDate, IsoDateSlots, IsoDateTime, IsoTime},
options::ArithmeticOverflow,
parser::parse_date_time,
@ -12,18 +15,18 @@ use crate::{
/// The native Rust implementation of `Temporal.PlainDateTime`
#[derive(Debug, Default, Clone)]
pub struct DateTime {
pub struct DateTime<C: CalendarProtocol> {
iso: IsoDateTime,
calendar: CalendarSlot,
calendar: CalendarSlot<C>,
}
// ==== Private DateTime API ====
impl DateTime {
impl<C: CalendarProtocol> DateTime<C> {
/// Creates a new unchecked `DateTime`.
#[inline]
#[must_use]
pub(crate) fn new_unchecked(date: IsoDate, time: IsoTime, calendar: CalendarSlot) -> Self {
pub(crate) fn new_unchecked(date: IsoDate, time: IsoTime, calendar: CalendarSlot<C>) -> Self {
Self {
iso: IsoDateTime::new_unchecked(date, time),
calendar,
@ -42,7 +45,7 @@ impl DateTime {
pub(crate) fn from_instant(
instant: &Instant,
offset: f64,
calendar: CalendarSlot,
calendar: CalendarSlot<C>,
) -> TemporalResult<Self> {
let iso = IsoDateTime::from_epoch_nanos(&instant.nanos, offset)?;
Ok(Self { iso, calendar })
@ -51,7 +54,7 @@ impl DateTime {
// ==== Public DateTime API ====
impl DateTime {
impl<C: CalendarProtocol> DateTime<C> {
/// Creates a new validated `DateTime`.
#[inline]
#[allow(clippy::too_many_arguments)]
@ -65,7 +68,7 @@ impl DateTime {
millisecond: i32,
microsecond: i32,
nanosecond: i32,
calendar: CalendarSlot,
calendar: CalendarSlot<C>,
) -> TemporalResult<Self> {
let iso_date = IsoDate::new(year, month, day, ArithmeticOverflow::Reject)?;
let iso_time = IsoTime::new(
@ -145,14 +148,14 @@ impl DateTime {
/// Returns the Calendar value.
#[inline]
#[must_use]
pub fn calendar(&self) -> &CalendarSlot {
pub fn calendar(&self) -> &CalendarSlot<C> {
&self.calendar
}
}
// ==== Trait impls ====
impl FromStr for DateTime {
impl<C: CalendarProtocol> FromStr for DateTime<C> {
type Err = TemporalError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
@ -181,7 +184,7 @@ impl FromStr for DateTime {
Ok(Self::new_unchecked(
date,
time,
CalendarSlot::Identifier(calendar),
CalendarSlot::from_str(&calendar)?,
))
}
}

19
core/temporal/src/components/duration.rs

@ -8,6 +8,8 @@ use crate::{
};
use std::{any::Any, str::FromStr};
use super::calendar::CalendarProtocol;
// ==== `DateDuration` ====
/// `DateDuration` represents the [date duration record][spec] of the `Duration.`
@ -697,10 +699,10 @@ impl Duration {
/// 7.5.21 `UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit, plainRelativeTo )`
#[allow(dead_code)]
pub(crate) fn unbalance_duration_relative(
pub(crate) fn unbalance_duration_relative<C: CalendarProtocol>(
&self,
largest_unit: TemporalUnit,
plain_relative_to: Option<&Date>,
plain_relative_to: Option<&Date<C>>,
context: &mut dyn Any,
) -> TemporalResult<DateDuration> {
// 1. Let allZero be false.
@ -918,10 +920,10 @@ impl Duration {
// TODO: Move to DateDuration
/// `BalanceDateDurationRelative`
#[allow(unused)]
pub fn balance_date_duration_relative(
pub fn balance_date_duration_relative<C: CalendarProtocol>(
&self,
largest_unit: TemporalUnit,
plain_relative_to: Option<&Date>,
plain_relative_to: Option<&Date<C>>,
context: &mut dyn Any,
) -> TemporalResult<DateDuration> {
let mut result = self.date;
@ -1151,13 +1153,18 @@ impl Duration {
/// Abstract Operation 7.5.26 `RoundDuration ( years, months, weeks, days, hours, minutes,
/// seconds, milliseconds, microseconds, nanoseconds, increment, unit,
/// roundingMode [ , plainRelativeTo [, zonedRelativeTo [, precalculatedDateTime]]] )`
pub fn round_duration(
#[allow(clippy::type_complexity)]
pub fn round_duration<C: CalendarProtocol>(
&self,
unbalance_date_duration: DateDuration,
increment: f64,
unit: TemporalUnit,
rounding_mode: TemporalRoundingMode,
relative_targets: (Option<&Date>, Option<&ZonedDateTime>, Option<&DateTime>),
relative_targets: (
Option<&Date<C>>,
Option<&ZonedDateTime<C>>,
Option<&DateTime<C>>,
),
context: &mut dyn Any,
) -> TemporalResult<(Self, f64)> {
let mut result = Duration::new_unchecked(unbalance_date_duration, self.time);

20
core/temporal/src/components/month_day.rs

@ -9,18 +9,20 @@ use crate::{
TemporalError, TemporalResult,
};
use super::calendar::CalendarProtocol;
/// The native Rust implementation of `Temporal.PlainMonthDay`
#[derive(Debug, Default, Clone)]
pub struct MonthDay {
pub struct MonthDay<C: CalendarProtocol> {
iso: IsoDate,
calendar: CalendarSlot,
calendar: CalendarSlot<C>,
}
impl MonthDay {
impl<C: CalendarProtocol> MonthDay<C> {
/// Creates a new unchecked `MonthDay`
#[inline]
#[must_use]
pub(crate) fn new_unchecked(iso: IsoDate, calendar: CalendarSlot) -> Self {
pub(crate) fn new_unchecked(iso: IsoDate, calendar: CalendarSlot<C>) -> Self {
Self { iso, calendar }
}
@ -29,7 +31,7 @@ impl MonthDay {
pub fn new(
month: i32,
day: i32,
calendar: CalendarSlot,
calendar: CalendarSlot<C>,
overflow: ArithmeticOverflow,
) -> TemporalResult<Self> {
let iso = IsoDate::new(1972, month, day, overflow)?;
@ -53,12 +55,12 @@ impl MonthDay {
/// Returns a reference to `MonthDay`'s `CalendarSlot`
#[inline]
#[must_use]
pub fn calendar(&self) -> &CalendarSlot {
pub fn calendar(&self) -> &CalendarSlot<C> {
&self.calendar
}
}
impl IsoDateSlots for MonthDay {
impl<C: CalendarProtocol> IsoDateSlots for MonthDay<C> {
#[inline]
/// Returns this structs `IsoDate`.
fn iso_date(&self) -> IsoDate {
@ -66,7 +68,7 @@ impl IsoDateSlots for MonthDay {
}
}
impl FromStr for MonthDay {
impl<C: CalendarProtocol> FromStr for MonthDay<C> {
type Err = TemporalError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
@ -77,7 +79,7 @@ impl FromStr for MonthDay {
Self::new(
record.date.month,
record.date.day,
CalendarSlot::Identifier(calendar),
CalendarSlot::from_str(&calendar)?,
ArithmeticOverflow::Reject,
)
}

8
core/temporal/src/components/tz.rs

@ -10,6 +10,8 @@ use crate::{
TemporalError, TemporalResult,
};
use super::calendar::CalendarProtocol;
/// Any object that implements the `TzProtocol` must implement the below methods/properties.
pub const TIME_ZONE_PROPERTIES: [&str; 3] =
["getOffsetNanosecondsFor", "getPossibleInstantsFor", "id"];
@ -74,12 +76,12 @@ impl Clone for TimeZoneSlot {
}
impl TimeZoneSlot {
pub(crate) fn get_datetime_for(
pub(crate) fn get_datetime_for<C: CalendarProtocol>(
&self,
instant: &Instant,
calendar: &CalendarSlot,
calendar: &CalendarSlot<C>,
context: &mut dyn Any,
) -> TemporalResult<DateTime> {
) -> TemporalResult<DateTime<C>> {
let nanos = self.get_offset_nanos_for(context)?;
DateTime::from_instant(instant, nanos.to_f64().unwrap_or(0.0), calendar.clone())
}

20
core/temporal/src/components/year_month.rs

@ -9,18 +9,20 @@ use crate::{
TemporalError, TemporalResult,
};
use super::calendar::CalendarProtocol;
/// The native Rust implementation of `Temporal.YearMonth`.
#[derive(Debug, Default, Clone)]
pub struct YearMonth {
pub struct YearMonth<C: CalendarProtocol> {
iso: IsoDate,
calendar: CalendarSlot,
calendar: CalendarSlot<C>,
}
impl YearMonth {
impl<C: CalendarProtocol> YearMonth<C> {
/// Creates an unvalidated `YearMonth`.
#[inline]
#[must_use]
pub(crate) fn new_unchecked(iso: IsoDate, calendar: CalendarSlot) -> Self {
pub(crate) fn new_unchecked(iso: IsoDate, calendar: CalendarSlot<C>) -> Self {
Self { iso, calendar }
}
@ -30,7 +32,7 @@ impl YearMonth {
year: i32,
month: i32,
reference_day: Option<i32>,
calendar: CalendarSlot,
calendar: CalendarSlot<C>,
overflow: ArithmeticOverflow,
) -> TemporalResult<Self> {
let day = reference_day.unwrap_or(1);
@ -55,12 +57,12 @@ impl YearMonth {
#[inline]
#[must_use]
/// Returns a reference to `YearMonth`'s `CalendarSlot`
pub fn calendar(&self) -> &CalendarSlot {
pub fn calendar(&self) -> &CalendarSlot<C> {
&self.calendar
}
}
impl IsoDateSlots for YearMonth {
impl<C: CalendarProtocol> IsoDateSlots for YearMonth<C> {
#[inline]
/// Returns this `YearMonth`'s `IsoDate`
fn iso_date(&self) -> IsoDate {
@ -68,7 +70,7 @@ impl IsoDateSlots for YearMonth {
}
}
impl FromStr for YearMonth {
impl<C: CalendarProtocol> FromStr for YearMonth<C> {
type Err = TemporalError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
@ -80,7 +82,7 @@ impl FromStr for YearMonth {
record.date.year,
record.date.month,
None,
CalendarSlot::Identifier(calendar),
CalendarSlot::from_str(&calendar)?,
ArithmeticOverflow::Reject,
)
}

31
core/temporal/src/components/zoneddatetime.rs

@ -5,7 +5,7 @@ use tinystr::TinyStr4;
use crate::{
components::{
calendar::{CalendarDateLike, CalendarSlot},
calendar::{CalendarDateLike, CalendarProtocol, CalendarSlot},
tz::TimeZoneSlot,
Instant,
},
@ -16,21 +16,21 @@ use core::any::Any;
/// The native Rust implementation of `Temporal.ZonedDateTime`.
#[derive(Debug, Clone)]
pub struct ZonedDateTime {
pub struct ZonedDateTime<C: CalendarProtocol> {
instant: Instant,
calendar: CalendarSlot,
calendar: CalendarSlot<C>,
tz: TimeZoneSlot,
}
// ==== Private API ====
impl ZonedDateTime {
impl<C: CalendarProtocol> ZonedDateTime<C> {
/// Creates a `ZonedDateTime` without validating the input.
#[inline]
#[must_use]
pub(crate) fn new_unchecked(
instant: Instant,
calendar: CalendarSlot,
calendar: CalendarSlot<C>,
tz: TimeZoneSlot,
) -> Self {
Self {
@ -43,10 +43,10 @@ impl ZonedDateTime {
// ==== Public API ====
impl ZonedDateTime {
impl<C: CalendarProtocol> ZonedDateTime<C> {
/// Creates a new valid `ZonedDateTime`.
#[inline]
pub fn new(nanos: BigInt, calendar: CalendarSlot, tz: TimeZoneSlot) -> TemporalResult<Self> {
pub fn new(nanos: BigInt, calendar: CalendarSlot<C>, tz: TimeZoneSlot) -> TemporalResult<Self> {
let instant = Instant::new(nanos)?;
Ok(Self::new_unchecked(instant, calendar, tz))
}
@ -54,9 +54,8 @@ impl ZonedDateTime {
/// Returns the `ZonedDateTime`'s Calendar identifier.
#[inline]
#[must_use]
pub fn calendar_id(&self) -> String {
// TODO: Implement Identifier method on `CalendarSlot`
String::from("Not yet implemented.")
pub fn calendar(&self) -> &CalendarSlot<C> {
&self.calendar
}
/// Returns the `epochSeconds` value of this `ZonedDateTime`.
@ -86,7 +85,7 @@ impl ZonedDateTime {
// ==== Context based API ====
impl ZonedDateTime {
impl<C: CalendarProtocol> ZonedDateTime<C> {
/// Returns the `year` value for this `ZonedDateTime`.
#[inline]
pub fn contextual_year(&self, context: &mut dyn Any) -> TemporalResult<i32> {
@ -226,6 +225,8 @@ impl ZonedDateTime {
#[cfg(test)]
mod tests {
use std::str::FromStr;
use crate::components::tz::TimeZone;
use num_bigint::BigInt;
@ -235,9 +236,9 @@ mod tests {
fn basic_zdt_test() {
let nov_30_2023_utc = BigInt::from(1_701_308_952_000_000_000i64);
let zdt = ZonedDateTime::new(
let zdt = ZonedDateTime::<()>::new(
nov_30_2023_utc.clone(),
CalendarSlot::Identifier("iso8601".to_owned()),
CalendarSlot::from_str("iso8601").unwrap(),
TimeZoneSlot::Tz(TimeZone {
iana: None,
offset: Some(0),
@ -252,9 +253,9 @@ mod tests {
assert_eq!(zdt.minute().unwrap(), 49);
assert_eq!(zdt.second().unwrap(), 12);
let zdt_minus_five = ZonedDateTime::new(
let zdt_minus_five = ZonedDateTime::<()>::new(
nov_30_2023_utc,
CalendarSlot::Identifier("iso8601".to_owned()),
CalendarSlot::from_str("iso8601").unwrap(),
TimeZoneSlot::Tz(TimeZone {
iana: None,
offset: Some(-300),

8
core/temporal/src/error.rs

@ -2,6 +2,8 @@
use core::fmt;
use icu_calendar::CalendarError;
/// `TemporalError`'s error type.
#[derive(Debug, Default, Clone, Copy)]
pub enum ErrorKind {
@ -111,3 +113,9 @@ impl fmt::Display for TemporalError {
Ok(())
}
}
impl From<CalendarError> for TemporalError {
fn from(value: CalendarError) -> Self {
TemporalError::general(value.to_string())
}
}

289
core/temporal/src/fields.rs

@ -1,8 +1,12 @@
//! This module implements a native Rust `TemporalField` and components.
use std::str::FromStr;
use std::{fmt, str::FromStr};
use crate::{error::TemporalError, TemporalResult};
use crate::{
components::calendar::{CalendarProtocol, CalendarSlot},
error::TemporalError,
TemporalResult,
};
use bitflags::bitflags;
// use rustc_hash::FxHashSet;
@ -56,6 +60,12 @@ pub enum FieldValue {
String(String),
}
impl From<i32> for FieldValue {
fn from(value: i32) -> Self {
FieldValue::Integer(value)
}
}
/// The Conversion type of a field.
#[derive(Debug, Clone, Copy)]
pub enum FieldConversion {
@ -149,6 +159,10 @@ impl Default for TemporalFields {
}
impl TemporalFields {
pub(crate) fn era(&self) -> TinyAsciiStr<16> {
self.era.unwrap_or("default".parse().expect("less than 8"))
}
pub(crate) const fn year(&self) -> Option<i32> {
self.year
}
@ -157,6 +171,12 @@ impl TemporalFields {
self.month
}
pub(crate) fn month_code(&self) -> TinyAsciiStr<4> {
// Passing along an invalid MonthCode to ICU...might be better to figure out a different approach...TBD.
self.month_code
.unwrap_or("M00".parse().expect("less than 4"))
}
pub(crate) const fn day(&self) -> Option<i32> {
self.day
}
@ -213,6 +233,53 @@ impl TemporalFields {
Ok(())
}
/// Retrieves a field value if set, else None.
pub fn get(&self, field: &str) -> Option<FieldValue> {
if !self.is_set_field(field) {
return None;
}
match field {
"year" => self.year.map(FieldValue::Integer),
"month" => self.month.map(FieldValue::Integer),
"monthCode" => self.month_code.map(|s| FieldValue::String(s.to_string())),
"day" => self.day.map(FieldValue::from),
"hour" => Some(FieldValue::Integer(self.hour)),
"minute" => Some(FieldValue::Integer(self.minute)),
"second" => Some(FieldValue::Integer(self.second)),
"millisecond" => Some(FieldValue::Integer(self.millisecond)),
"microsecond" => Some(FieldValue::Integer(self.microsecond)),
"nanosecond" => Some(FieldValue::Integer(self.nanosecond)),
"offset" => self.offset.as_ref().map(|s| FieldValue::String(s.clone())),
"era" => self.era.map(|s| FieldValue::String(s.to_string())),
"eraYear" => self.era_year.map(FieldValue::Integer),
"timeZone" => self
.time_zone
.as_ref()
.map(|s| FieldValue::String(s.clone())),
_ => unreachable!(),
}
}
fn is_set_field(&self, field: &str) -> bool {
match field {
"year" => self.bit_map.contains(FieldMap::YEAR),
"month" => self.bit_map.contains(FieldMap::MONTH),
"monthCode" => self.bit_map.contains(FieldMap::MONTH_CODE),
"day" => self.bit_map.contains(FieldMap::DAY),
"hour" => self.bit_map.contains(FieldMap::HOUR),
"minute" => self.bit_map.contains(FieldMap::MINUTE),
"second" => self.bit_map.contains(FieldMap::SECOND),
"millisecond" => self.bit_map.contains(FieldMap::MILLISECOND),
"microsecond" => self.bit_map.contains(FieldMap::MICROSECOND),
"nanosecond" => self.bit_map.contains(FieldMap::NANOSECOND),
"offset" => self.bit_map.contains(FieldMap::OFFSET),
"era" => self.bit_map.contains(FieldMap::ERA),
"eraYear" => self.bit_map.contains(FieldMap::ERA_YEAR),
"timeZone" => self.bit_map.contains(FieldMap::TIME_ZONE),
_ => unreachable!(),
}
}
#[inline]
fn set_year(&mut self, value: &FieldValue) -> TemporalResult<()> {
let FieldValue::Integer(y) = value else {
@ -358,78 +425,28 @@ impl TemporalFields {
}
}
// TODO: optimize into iter.
impl TemporalFields {
/// Returns a vector filled with the key-value pairs marked as active.
#[must_use]
pub fn active_kvs(&self) -> Vec<(String, FieldValue)> {
let mut result = Vec::default();
self.keys().zip(self.values()).collect()
}
for field in self.bit_map.iter() {
match field {
FieldMap::YEAR => result.push((
"year".to_owned(),
self.year.map_or(FieldValue::Undefined, FieldValue::Integer),
)),
FieldMap::MONTH => result.push((
"month".to_owned(),
self.month
.map_or(FieldValue::Undefined, FieldValue::Integer),
)),
FieldMap::MONTH_CODE => result.push((
"monthCode".to_owned(),
self.month_code
.map_or(FieldValue::Undefined, |s| FieldValue::String(s.to_string())),
)),
FieldMap::DAY => result.push((
"day".to_owned(),
self.day.map_or(FieldValue::Undefined, FieldValue::Integer),
)),
FieldMap::HOUR => result.push(("hour".to_owned(), FieldValue::Integer(self.hour))),
FieldMap::MINUTE => {
result.push(("minute".to_owned(), FieldValue::Integer(self.minute)));
}
FieldMap::SECOND => {
result.push(("second".to_owned(), FieldValue::Integer(self.second)));
}
FieldMap::MILLISECOND => result.push((
"millisecond".to_owned(),
FieldValue::Integer(self.millisecond),
)),
FieldMap::MICROSECOND => result.push((
"microsecond".to_owned(),
FieldValue::Integer(self.microsecond),
)),
FieldMap::NANOSECOND => result.push((
"nanosecond".to_owned(),
FieldValue::Integer(self.nanosecond),
)),
FieldMap::OFFSET => result.push((
"offset".to_owned(),
self.offset
.clone()
.map_or(FieldValue::Undefined, FieldValue::String),
)),
FieldMap::ERA => result.push((
"era".to_owned(),
self.era
.map_or(FieldValue::Undefined, |s| FieldValue::String(s.to_string())),
)),
FieldMap::ERA_YEAR => result.push((
"eraYear".to_owned(),
self.era_year
.map_or(FieldValue::Undefined, FieldValue::Integer),
)),
FieldMap::TIME_ZONE => result.push((
"timeZone".to_owned(),
self.time_zone
.clone()
.map_or(FieldValue::Undefined, FieldValue::String),
)),
_ => {}
/// Returns an iterator over the current keys.
#[must_use]
pub fn keys(&self) -> Keys {
Keys {
iter: self.bit_map.iter(),
}
}
result
/// Returns an iterator over the current values.
#[must_use]
pub fn values(&self) -> Values<'_> {
Values {
fields: self,
iter: self.bit_map.iter(),
}
}
/// Resolve `TemporalFields` month and monthCode fields.
@ -463,6 +480,144 @@ impl TemporalFields {
Ok(())
}
/// Merges two `TemporalFields` values given a specific `CalendarSlot`.
pub fn merge_fields<C: CalendarProtocol>(
&self,
other: &Self,
calendar: &CalendarSlot<C>,
) -> TemporalResult<Self> {
let add_keys = other.keys().collect::<Vec<_>>();
let overridden_keys = calendar.field_keys_to_ignore(&add_keys)?;
let mut result = Self::default();
for key in self.keys() {
let value = if overridden_keys.contains(&key) {
other.get(&key)
} else {
self.get(&key)
};
if let Some(value) = value {
result.set_field_value(&key, &value)?;
}
}
Ok(result)
}
}
/// Iterator over `TemporalFields` keys.
pub struct Keys {
iter: bitflags::iter::Iter<FieldMap>,
}
impl fmt::Debug for Keys {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "TemporalFields KeyIterator")
}
}
impl Iterator for Keys {
type Item = String;
fn next(&mut self) -> Option<Self::Item> {
let Some(field) = self.iter.next() else {
return None;
};
match field {
FieldMap::YEAR => Some("year".to_owned()),
FieldMap::MONTH => Some("month".to_owned()),
FieldMap::MONTH_CODE => Some("monthCode".to_owned()),
FieldMap::DAY => Some("day".to_owned()),
FieldMap::HOUR => Some("hour".to_owned()),
FieldMap::MINUTE => Some("minute".to_owned()),
FieldMap::SECOND => Some("second".to_owned()),
FieldMap::MILLISECOND => Some("millisecond".to_owned()),
FieldMap::MICROSECOND => Some("microsecond".to_owned()),
FieldMap::NANOSECOND => Some("nanosecond".to_owned()),
FieldMap::OFFSET => Some("offset".to_owned()),
FieldMap::ERA => Some("era".to_owned()),
FieldMap::ERA_YEAR => Some("eraYear".to_owned()),
FieldMap::TIME_ZONE => Some("timeZone".to_owned()),
_ => None,
}
}
}
/// An iterator over `TemporalFields`'s values.
pub struct Values<'a> {
fields: &'a TemporalFields,
iter: bitflags::iter::Iter<FieldMap>,
}
impl fmt::Debug for Values<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "TemporalFields Values Iterator")
}
}
impl Iterator for Values<'_> {
type Item = FieldValue;
fn next(&mut self) -> Option<Self::Item> {
let Some(field) = self.iter.next() else {
return None;
};
match field {
FieldMap::YEAR => Some(
self.fields
.year
.map_or(FieldValue::Undefined, FieldValue::Integer),
),
FieldMap::MONTH => Some(
self.fields
.month
.map_or(FieldValue::Undefined, FieldValue::Integer),
),
FieldMap::MONTH_CODE => Some(
self.fields
.month_code
.map_or(FieldValue::Undefined, |s| FieldValue::String(s.to_string())),
),
FieldMap::DAY => Some(
self.fields
.day
.map_or(FieldValue::Undefined, FieldValue::Integer),
),
FieldMap::HOUR => Some(FieldValue::Integer(self.fields.hour)),
FieldMap::MINUTE => Some(FieldValue::Integer(self.fields.minute)),
FieldMap::SECOND => Some(FieldValue::Integer(self.fields.second)),
FieldMap::MILLISECOND => Some(FieldValue::Integer(self.fields.millisecond)),
FieldMap::MICROSECOND => Some(FieldValue::Integer(self.fields.microsecond)),
FieldMap::NANOSECOND => Some(FieldValue::Integer(self.fields.nanosecond)),
FieldMap::OFFSET => Some(
self.fields
.offset
.clone()
.map_or(FieldValue::Undefined, FieldValue::String),
),
FieldMap::ERA => Some(
self.fields
.era
.map_or(FieldValue::Undefined, |s| FieldValue::String(s.to_string())),
),
FieldMap::ERA_YEAR => Some(
self.fields
.era_year
.map_or(FieldValue::Undefined, FieldValue::Integer),
),
FieldMap::TIME_ZONE => Some(
self.fields
.time_zone
.clone()
.map_or(FieldValue::Undefined, FieldValue::String),
),
_ => None,
}
}
}
fn month_code_to_integer(mc: TinyAsciiStr<4>) -> TemporalResult<i32> {

18
core/temporal/src/parser/tests.rs

@ -10,9 +10,9 @@ fn temporal_parser_basic() {
let basic = "20201108";
let basic_separated = "2020-11-08";
let basic_result = basic.parse::<DateTime>().unwrap();
let basic_result = basic.parse::<DateTime<()>>().unwrap();
let sep_result = basic_separated.parse::<DateTime>().unwrap();
let sep_result = basic_separated.parse::<DateTime<()>>().unwrap();
assert_eq!(basic_result.iso_date().year(), 2020);
assert_eq!(basic_result.iso_date().month(), 11);
@ -32,7 +32,7 @@ fn temporal_date_time_max() {
let date_time =
"+002020-11-08T12:28:32.329402834[!America/Argentina/ComodRivadavia][!u-ca=iso8601]";
let result = date_time.parse::<DateTime>().unwrap();
let result = date_time.parse::<DateTime<()>>().unwrap();
let time_results = result.iso_time();
@ -49,10 +49,10 @@ fn temporal_year_parsing() {
let long = "+002020-11-08";
let bad_year = "-000000-11-08";
let result_good = long.parse::<DateTime>().unwrap();
let result_good = long.parse::<DateTime<()>>().unwrap();
assert_eq!(result_good.iso_date().year(), 2020);
let err_result = bad_year.parse::<DateTime>();
let err_result = bad_year.parse::<DateTime<()>>();
assert!(
err_result.is_err(),
"Invalid extended year parsing: \"{bad_year}\" should fail to parse."
@ -90,7 +90,7 @@ fn temporal_year_month() {
];
for ym in possible_year_months {
let result = ym.parse::<YearMonth>().unwrap();
let result = ym.parse::<YearMonth<()>>().unwrap();
assert_eq!(result.year(), 2020);
assert_eq!(result.month(), 11);
@ -108,7 +108,7 @@ fn temporal_month_day() {
];
for md in possible_month_day {
let result = md.parse::<MonthDay>().unwrap();
let result = md.parse::<MonthDay<()>>().unwrap();
assert_eq!(result.month(), 11);
assert_eq!(result.day(), 7);
@ -124,7 +124,7 @@ fn temporal_invalid_annotations() {
];
for invalid in invalid_annotations {
let err_result = invalid.parse::<MonthDay>();
let err_result = invalid.parse::<MonthDay<()>>();
assert!(
err_result.is_err(),
"Invalid ISO annotation parsing: \"{invalid}\" should fail parsing."
@ -240,7 +240,7 @@ fn temporal_invalid_iso_datetime_strings() {
];
for invalid_target in INVALID_DATETIME_STRINGS {
let error_result = invalid_target.parse::<DateTime>();
let error_result = invalid_target.parse::<DateTime<()>>();
assert!(
error_result.is_err(),
"Invalid ISO8601 `DateTime` target: \"{invalid_target}\" should fail parsing."

Loading…
Cancel
Save