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. 388
      core/engine/src/builtins/temporal/calendar/mod.rs
  3. 166
      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. 293
      core/temporal/src/fields.rs
  22. 18
      core/temporal/src/parser/tests.rs

8
Cargo.lock generated

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

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

@ -21,11 +21,11 @@ use crate::{
string::{common::StaticJsStrings, utf16}, string::{common::StaticJsStrings, utf16},
Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, 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_profiler::Profiler;
use boa_temporal::{ use boa_temporal::{
components::calendar::{ components::calendar::{
AvailableCalendars, CalendarDateLike, CalendarFieldsType, CalendarSlot, CalendarDateLike, CalendarFieldsType, CalendarProtocol, CalendarSlot,
CALENDAR_PROTOCOL_METHODS, CALENDAR_PROTOCOL_METHODS,
}, },
options::{ArithmeticOverflow, TemporalUnit}, options::{ArithmeticOverflow, TemporalUnit},
@ -33,22 +33,29 @@ use boa_temporal::{
mod object; mod object;
use object::CustomRuntimeCalendar; #[doc(inline)]
pub(crate) use object::JsCustomCalendar;
#[cfg(feature = "experimental")]
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
/// The `Temporal.Calendar` object. /// The `Temporal.Calendar` object.
#[derive(Debug, Trace, Finalize, JsData)] #[derive(Debug, Finalize, JsData)]
// SAFETY: `Calendar` doesn't contain traceable types.
#[boa_gc(unsafe_empty_trace)]
pub struct Calendar { 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 { impl Calendar {
pub(crate) fn new(slot: CalendarSlot) -> Self { pub(crate) fn new(slot: CalendarSlot<JsCustomCalendar>) -> Self {
Self { slot } Self { slot }
} }
} }
@ -145,11 +152,10 @@ impl BuiltInConstructor for Calendar {
// 3. If IsBuiltinCalendar(id) is false, then // 3. If IsBuiltinCalendar(id) is false, then
// a. Throw a RangeError exception. // a. Throw a RangeError exception.
let _ = AvailableCalendars::from_str(&id.to_std_string_escaped())?;
// 4. Return ? CreateTemporalCalendar(id, NewTarget). // 4. Return ? CreateTemporalCalendar(id, NewTarget).
create_temporal_calendar( create_temporal_calendar(
CalendarSlot::Identifier(id.to_std_string_escaped()), CalendarSlot::<JsCustomCalendar>::from_str(&id.to_std_string_escaped())?,
Some(new_target.clone()), Some(new_target.clone()),
context, context,
) )
@ -172,12 +178,7 @@ impl Calendar {
.with_message("the this value of Calendar must be a Calendar object.") .with_message("the this value of Calendar must be a Calendar object.")
})?; })?;
let protocol = match &calendar.slot { Ok(JsString::from(calendar.slot.identifier(context)?.as_str()).into())
CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(),
CalendarSlot::Protocol(proto) => proto.clone(),
};
Ok(JsString::from(protocol.identifier(context)?.as_str()).into())
} }
/// 15.8.2.1 `Temporal.Calendar.prototype.dateFromFields ( fields [ , options ] )` - Supercedes 12.5.4 /// 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.") .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. // 3. If Type(fields) is not Object, throw a TypeError exception.
let fields = args.get_or_undefined(0); let fields = args.get_or_undefined(0);
let fields_obj = fields.as_object().ok_or_else(|| { let fields_obj = fields.as_object().ok_or_else(|| {
@ -220,7 +215,7 @@ impl Calendar {
]); ]);
// 6. If calendar.[[Identifier]] is "iso8601", then // 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" »). // a. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « "year", "day" »).
let mut required_fields = Vec::from([js_string!("year"), js_string!("day")]); let mut required_fields = Vec::from([js_string!("year"), js_string!("day")]);
fields::prepare_temporal_fields( fields::prepare_temporal_fields(
@ -235,7 +230,8 @@ impl Calendar {
// 7. Else, // 7. Else,
} else { } else {
// a. Let calendarRelevantFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], date). // 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). // b. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « », calendarRelevantFieldDescriptors).
fields::prepare_temporal_fields( fields::prepare_temporal_fields(
fields_obj, fields_obj,
@ -260,7 +256,9 @@ impl Calendar {
// a. Perform ? CalendarResolveFields(calendar.[[Identifier]], fields, date). // a. Perform ? CalendarResolveFields(calendar.[[Identifier]], fields, date).
// b. Let result be ? CalendarDateToISO(calendar.[[Identifier]], fields, overflow). // 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) 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.") .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 = args.get_or_undefined(0);
let fields_obj = fields.as_object().ok_or_else(|| { let fields_obj = fields.as_object().ok_or_else(|| {
JsNativeError::typ().with_message("fields parameter must be an object.") 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" »). // 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" »). // a. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « "year" »).
let mut required_fields = Vec::from([js_string!("year")]); let mut required_fields = Vec::from([js_string!("year")]);
fields::prepare_temporal_fields( fields::prepare_temporal_fields(
@ -315,8 +308,9 @@ impl Calendar {
// a. Let calendarRelevantFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], year-month). // a. Let calendarRelevantFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], year-month).
// b. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « », calendarRelevantFieldDescriptors). // b. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « », calendarRelevantFieldDescriptors).
let calendar_relevant_fields = let calendar_relevant_fields = calendar
protocol.field_descriptors(CalendarFieldsType::YearMonth); .slot
.field_descriptors(CalendarFieldsType::YearMonth)?;
fields::prepare_temporal_fields( fields::prepare_temporal_fields(
fields_obj, fields_obj,
&mut relevant_field_names, &mut relevant_field_names,
@ -336,7 +330,9 @@ impl Calendar {
let overflow = get_option::<ArithmeticOverflow>(&options, utf16!("overflow"), context)? let overflow = get_option::<ArithmeticOverflow>(&options, utf16!("overflow"), context)?
.unwrap_or(ArithmeticOverflow::Constrain); .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) create_temporal_year_month(result, None, context)
} }
@ -357,11 +353,6 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.") .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. // 3. If Type(fields) is not Object, throw a TypeError exception.
let fields = args.get_or_undefined(0); let fields = args.get_or_undefined(0);
let fields_obj = fields.as_object().ok_or_else(|| { let fields_obj = fields.as_object().ok_or_else(|| {
@ -380,7 +371,7 @@ impl Calendar {
]); ]);
// 6. If calendar.[[Identifier]] is "iso8601", then // 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" »). // a. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « "day" »).
let mut required_fields = Vec::from([js_string!("day")]); let mut required_fields = Vec::from([js_string!("day")]);
fields::prepare_temporal_fields( fields::prepare_temporal_fields(
@ -395,7 +386,9 @@ impl Calendar {
// 7. Else, // 7. Else,
} else { } else {
// a. Let calendarRelevantFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], month-day). // 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). // b. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « », calendarRelevantFieldDescriptors).
fields::prepare_temporal_fields( fields::prepare_temporal_fields(
fields_obj, fields_obj,
@ -412,7 +405,9 @@ impl Calendar {
let overflow = get_option(&options, utf16!("overflow"), context)? let overflow = get_option(&options, utf16!("overflow"), context)?
.unwrap_or(ArithmeticOverflow::Constrain); .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) create_temporal_month_day(result, None, context)
} }
@ -430,11 +425,6 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.") .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). // 4. Set date to ? ToTemporalDate(date).
let date_like = args.get_or_undefined(0); let date_like = args.get_or_undefined(0);
let date = temporal::plain_date::to_temporal_date(date_like, None, context)?; 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"). // 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)?; 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) 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.") .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). // 4. Set one to ? ToTemporalDate(one).
let one = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; let one = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?;
// 5. Set two to ? ToTemporalDate(two). // 5. Set two to ? ToTemporalDate(two).
@ -496,7 +483,9 @@ impl Calendar {
)? )?
.unwrap_or(TemporalUnit::Day); .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) 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.") .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 date_like = to_calendar_date_like(args.get_or_undefined(0), context)?;
let result = protocol let result = calendar
.slot
.era(&date_like, context)? .era(&date_like, context)?
.map_or(JsValue::undefined(), |r| JsString::from(r.as_str()).into()); .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.") .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 date_like = to_calendar_date_like(args.get_or_undefined(0), context)?;
let result = protocol let result = calendar
.slot
.era_year(&date_like, context)? .era_year(&date_like, context)?
.map_or(JsValue::undefined(), JsValue::from); .map_or(JsValue::undefined(), JsValue::from);
@ -559,14 +540,9 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.") .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 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()) Ok(result.into())
} }
@ -581,11 +557,6 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.") .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 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 // 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. 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). // 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()) Ok(result.into())
} }
@ -608,14 +579,9 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.") .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 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()) Ok(JsString::from(result.as_str()).into())
} }
@ -630,14 +596,9 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.") .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 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()) Ok(result.into())
} }
@ -654,15 +615,12 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.") .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). // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike).
let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; 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()) Ok(result.into())
} }
@ -677,15 +635,12 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.") .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). // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike).
let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; 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()) Ok(result.into())
} }
@ -700,15 +655,12 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.") .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). // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike).
let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; 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()) Ok(result.into())
} }
@ -723,15 +675,12 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.") .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). // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike).
let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; 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()) Ok(result.into())
} }
@ -746,15 +695,12 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.") .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). // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike).
let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; 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()) Ok(result.into())
} }
@ -769,14 +715,9 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.") .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 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()) Ok(result.into())
} }
@ -791,13 +732,8 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.") .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 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()) Ok(result.into())
} }
@ -816,14 +752,9 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.") .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 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()) Ok(result.into())
} }
@ -838,14 +769,9 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.") .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 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()) Ok(result.into())
} }
@ -862,10 +788,36 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.") .with_message("this value of Calendar must be a Calendar object.")
})?; })?;
let protocol = match &calendar.slot { // Custom Calendars override the `fields` method.
CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), if let CalendarSlot::Protocol(proto) = &calendar.slot {
CalendarSlot::Protocol(proto) => proto.clone(), // 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). // 3. Let iteratorRecord be ? GetIterator(fields, sync).
let mut iterator_record = let mut iterator_record =
@ -918,11 +870,12 @@ impl Calendar {
// 7. Let result be fieldNames. // 7. Let result be fieldNames.
// 8. If calendar.[[Identifier]] is not "iso8601", then // 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. // a. NOTE: Every built-in calendar preserves all input field names in output.
// b. Let extraFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], fieldNames). // b. Let extraFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], fieldNames).
let extended_fields = let extended_fields = calendar
protocol.field_descriptors(CalendarFieldsType::from(&fields_names[..])); .slot
.field_descriptors(CalendarFieldsType::from(&fields_names[..]))?;
// c. For each Calendar Field Descriptor Record desc of extraFieldDescriptors, do // c. For each Calendar Field Descriptor Record desc of extraFieldDescriptors, do
for descriptor in extended_fields { for descriptor in extended_fields {
// i. Append desc.[[Property]] to result. // i. Append desc.[[Property]] to result.
@ -952,87 +905,46 @@ impl Calendar {
.with_message("this value of Calendar must be a Calendar object.") .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 fields = args.get_or_undefined(0).to_object(context)?;
let additional_fields = args.get_or_undefined(1).to_object(context)?; let additional_fields = args.get_or_undefined(1).to_object(context)?;
// 3. Let fieldsCopy be ? SnapshotOwnProperties(? ToObject(fields), null, « », « undefined »). // 3. Let fieldsCopy be ? SnapshotOwnProperties(? ToObject(fields), null, « », « undefined »).
let fields_copy = temporal::snapshot_own_properties( let fields_copy = temporal::fields::object_to_temporal_fields(&fields, context)?;
&fields,
Some(Vec::new()),
Some(Vec::from([JsValue::undefined()])),
context,
)?;
// 4. Let additionalFieldsCopy be ? SnapshotOwnProperties(? ToObject(additionalFields), null, « », « undefined »). // 4. Let additionalFieldsCopy be ? SnapshotOwnProperties(? ToObject(additionalFields), null, « », « undefined »).
let additional_fields_copy = temporal::snapshot_own_properties( let additional_copy =
&additional_fields, temporal::fields::object_to_temporal_fields(&additional_fields, context)?;
Some(Vec::new()),
Some(Vec::from([JsValue::undefined()])),
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. // Custom Calendars override the `fields` method.
// 6. Let additionalKeys be ! additionalFieldsCopy.[[OwnPropertyKeys]](). if let CalendarSlot::Protocol(proto) = &calendar.slot {
let add_keys = additional_fields_copy let result = proto.merge_fields(&fields_copy, &additional_copy, context)?; // TBD
.__own_property_keys__(context)? return JsObject::from_temporal_fields(&result, context).map(Into::into);
.iter() }
.map(ToString::to_string)
.collect::<Vec<_>>();
// 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 // 7. If calendar.[[Identifier]] is "iso8601", then
// a. Let overriddenKeys be ISOFieldKeysToIgnore(additionalKeys). // a. Let overriddenKeys be ISOFieldKeysToIgnore(additionalKeys).
// 8. Else, // 8. Else,
// a. Let overriddenKeys be CalendarFieldKeysToIgnore(calendar, additionalKeys). // a. Let overriddenKeys be CalendarFieldKeysToIgnore(calendar, additionalKeys).
let overridden_keys = protocol.field_keys_to_ignore(add_keys);
// 9. Let merged be OrdinaryObjectCreate(null). // 9. Let merged be OrdinaryObjectCreate(null).
let merged = JsObject::with_null_proto();
// 10. NOTE: The following steps ensure that property iteration order of merged // 10. NOTE: The following steps ensure that property iteration order of merged
// matches that of fields as modified by omitting overridden properties and // matches that of fields as modified by omitting overridden properties and
// appending non-overlapping properties from additionalFields in iteration order. // appending non-overlapping properties from additionalFields in iteration order.
// 11. Let fieldsKeys be ! fieldsCopy.[[OwnPropertyKeys]](). // 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 // 12. For each element key of fieldsKeys, do
for key in field_keys { // a. Let propValue be undefined.
// a. Let propValue be undefined. // b. If overriddenKeys contains key, then
// b. If overriddenKeys contains key, then // i. Set propValue to ! Get(additionalFieldsCopy, key).
let prop_value = if overridden_keys.contains(&key.to_std_string_escaped()) { // c. Else,
// i. Set propValue to ! Get(additionalFieldsCopy, key). // i. Set propValue to ! Get(fieldsCopy, key).
additional_fields_copy.get(key.as_slice(), context)? // d. If propValue is not undefined, perform ! CreateDataPropertyOrThrow(merged, key, propValue).
// c. Else, let merged = fields_copy.merge_fields(&additional_copy, &calendar.slot)?;
} 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)?;
}
}
// 13. Perform ! CopyDataProperties(merged, additionalFieldsCopy, « »). // 13. Perform ! CopyDataProperties(merged, additionalFieldsCopy, « »).
temporal::copy_data_properties(
&merged,
&additional_fields_copy.into(),
&Vec::new(),
None,
context,
)?;
// 14. Return merged. // 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 ] )` /// 12.2.1 `CreateTemporalCalendar ( identifier [ , newTarget ] )`
pub(crate) fn create_temporal_calendar( pub(crate) fn create_temporal_calendar(
identifier: CalendarSlot, identifier: CalendarSlot<JsCustomCalendar>,
new_target: Option<JsValue>, new_target: Option<JsValue>,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
@ -1104,7 +1016,7 @@ where
pub(crate) fn get_temporal_calendar_slot_value_with_default( pub(crate) fn get_temporal_calendar_slot_value_with_default(
item: &JsObject, item: &JsObject,
context: &mut Context, context: &mut Context,
) -> JsResult<CalendarSlot> { ) -> JsResult<CalendarSlot<JsCustomCalendar>> {
// 1. If item has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal slot, then // 1. If item has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal slot, then
// a. Return item.[[Calendar]]. // a. Return item.[[Calendar]].
if let Some(calendar) = extract_from_temporal_type( 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( pub(crate) fn to_temporal_calendar_slot_value(
calendar_like: &JsValue, calendar_like: &JsValue,
context: &mut Context, context: &mut Context,
) -> JsResult<CalendarSlot> { ) -> JsResult<CalendarSlot<JsCustomCalendar>> {
// 1. If temporalCalendarLike is undefined and default is present, then // 1. If temporalCalendarLike is undefined and default is present, then
// a. Assert: IsBuiltinCalendar(default) is true. // a. Assert: IsBuiltinCalendar(default) is true.
// b. Return default. // b. Return default.
if calendar_like.is_undefined() { if calendar_like.is_undefined() {
return Ok(CalendarSlot::Identifier("iso8601".to_owned())); return Ok(CalendarSlot::default());
// 2. If Type(temporalCalendarLike) is Object, then // 2. If Type(temporalCalendarLike) is Object, then
} else if let Some(calendar_like) = calendar_like.as_object() { } else if let Some(calendar_like) = calendar_like.as_object() {
// a. If temporalCalendarLike has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal slot, then // 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( if let Some(calendar) = extract_from_temporal_type(
calendar_like, calendar_like,
|d| Ok(Some(d.inner.calendar().clone())), |d| Ok(Some(d.inner.calendar().clone())),
|_dt| { |dt| Ok(Some(dt.inner.calendar().clone())),
Err(JsNativeError::range() |ym| Ok(Some(ym.inner.calendar().clone())),
.with_message("Not yet implemented.") |md| Ok(Some(md.inner.calendar().clone())),
.into()) |zdt| Ok(Some(zdt.inner.calendar().clone())),
},
|_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())
},
)? { )? {
return Ok(calendar); return Ok(calendar);
} }
@ -1179,23 +1075,24 @@ pub(crate) fn to_temporal_calendar_slot_value(
} }
// Types: Box<dyn CalendarProtocol> <- UserCalendar // Types: Box<dyn CalendarProtocol> <- UserCalendar
let protocol = Box::new(CustomRuntimeCalendar::new(calendar_like)); let custom = JsCustomCalendar::new(calendar_like);
// c. Return temporalCalendarLike. // c. Return temporalCalendarLike.
return Ok(CalendarSlot::Protocol(protocol)); return Ok(CalendarSlot::Protocol(custom));
} }
// 3. If temporalCalendarLike is not a String, throw a TypeError exception. // 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() return Err(JsNativeError::typ()
.with_message("temporalCalendarLike is not a string.") .with_message("temporalCalendarLike is not a string.")
.into()); .into());
} };
// TODO: 4-6
// 4. Let identifier be ? ParseTemporalCalendarString(temporalCalendarLike). // 4. Let identifier be ? ParseTemporalCalendarString(temporalCalendarLike).
// 5. If IsBuiltinCalendar(identifier) is false, throw a RangeError exception. // 5. If IsBuiltinCalendar(identifier) is false, throw a RangeError exception.
// 6. Return the ASCII-lowercase of identifier. // 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 { 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. /// 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 Some(obj) = date_like.as_object() else {
let date = temporal::plain_date::to_temporal_date(date_like, None, context)?; let date = temporal::plain_date::to_temporal_date(date_like, None, context)?;

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

@ -1,16 +1,23 @@
//! Boa's implementation of a user-defined Anonymous Calendar. //! Boa's implementation of a user-defined Anonymous Calendar.
use crate::{ 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, property::PropertyKey,
Context, JsObject, JsString, JsValue, Context, JsObject, JsString, JsValue,
}; };
use std::any::Any; use std::any::Any;
use boa_gc::{Finalize, Trace};
use boa_macros::utf16; use boa_macros::utf16;
use boa_temporal::{ use boa_temporal::{
components::{ components::{
calendar::{CalendarDateLike, CalendarFieldsType, CalendarProtocol}, calendar::{CalendarDateLike, CalendarProtocol},
Date, Duration, MonthDay, YearMonth, Date, Duration, MonthDay, YearMonth,
}, },
options::ArithmeticOverflow, options::ArithmeticOverflow,
@ -26,12 +33,12 @@ use plain_year_month::PlainYearMonth;
/// ///
/// A user-defined calendar implements all of the `CalendarProtocolMethods` /// A user-defined calendar implements all of the `CalendarProtocolMethods`
/// and therefore satisfies the requirements to be used as a calendar. /// and therefore satisfies the requirements to be used as a calendar.
#[derive(Debug, Clone)] #[derive(Debug, Clone, Trace, Finalize)]
pub(crate) struct CustomRuntimeCalendar { pub(crate) struct JsCustomCalendar {
calendar: JsObject, calendar: JsObject,
} }
impl CustomRuntimeCalendar { impl JsCustomCalendar {
pub(crate) fn new(calendar: &JsObject) -> Self { pub(crate) fn new(calendar: &JsObject) -> Self {
Self { Self {
calendar: calendar.clone(), calendar: calendar.clone(),
@ -39,13 +46,13 @@ impl CustomRuntimeCalendar {
} }
} }
impl CalendarProtocol for CustomRuntimeCalendar { impl CalendarProtocol for JsCustomCalendar {
fn date_from_fields( fn date_from_fields(
&self, &self,
fields: &mut TemporalFields, fields: &mut TemporalFields,
overflow: ArithmeticOverflow, overflow: ArithmeticOverflow,
context: &mut dyn Any, context: &mut dyn Any,
) -> TemporalResult<Date> { ) -> TemporalResult<Date<Self>> {
let context = context let context = context
.downcast_mut::<Context>() .downcast_mut::<Context>()
.expect("Context was not provided for a CustomCalendar."); .expect("Context was not provided for a CustomCalendar.");
@ -97,7 +104,7 @@ impl CalendarProtocol for CustomRuntimeCalendar {
fields: &mut TemporalFields, fields: &mut TemporalFields,
overflow: ArithmeticOverflow, overflow: ArithmeticOverflow,
context: &mut dyn Any, context: &mut dyn Any,
) -> TemporalResult<YearMonth> { ) -> TemporalResult<YearMonth<JsCustomCalendar>> {
let context = context let context = context
.downcast_mut::<Context>() .downcast_mut::<Context>()
.expect("Context was not provided for a CustomCalendar."); .expect("Context was not provided for a CustomCalendar.");
@ -151,7 +158,7 @@ impl CalendarProtocol for CustomRuntimeCalendar {
fields: &mut TemporalFields, fields: &mut TemporalFields,
overflow: ArithmeticOverflow, overflow: ArithmeticOverflow,
context: &mut dyn Any, context: &mut dyn Any,
) -> TemporalResult<MonthDay> { ) -> TemporalResult<MonthDay<JsCustomCalendar>> {
let context = context let context = context
.downcast_mut::<Context>() .downcast_mut::<Context>()
.expect("Context was not provided for a CustomCalendar."); .expect("Context was not provided for a CustomCalendar.");
@ -202,19 +209,19 @@ impl CalendarProtocol for CustomRuntimeCalendar {
fn date_add( fn date_add(
&self, &self,
_date: &Date, _date: &Date<JsCustomCalendar>,
_duration: &Duration, _duration: &Duration,
_overflow: ArithmeticOverflow, _overflow: ArithmeticOverflow,
_context: &mut dyn Any, _context: &mut dyn Any,
) -> TemporalResult<Date> { ) -> TemporalResult<Date<JsCustomCalendar>> {
// TODO // TODO
Err(TemporalError::general("Not yet implemented.")) Err(TemporalError::general("Not yet implemented."))
} }
fn date_until( fn date_until(
&self, &self,
_one: &Date, _one: &Date<JsCustomCalendar>,
_two: &Date, _two: &Date<JsCustomCalendar>,
_largest_unit: boa_temporal::options::TemporalUnit, _largest_unit: boa_temporal::options::TemporalUnit,
_context: &mut dyn Any, _context: &mut dyn Any,
) -> TemporalResult<Duration> { ) -> TemporalResult<Duration> {
@ -224,19 +231,27 @@ impl CalendarProtocol for CustomRuntimeCalendar {
fn era( fn era(
&self, &self,
_: &CalendarDateLike, _: &CalendarDateLike<JsCustomCalendar>,
_: &mut dyn Any, _: &mut dyn Any,
) -> TemporalResult<Option<TinyAsciiStr<8>>> { ) -> TemporalResult<Option<TinyAsciiStr<16>>> {
// Return undefined as custom calendars do not implement -> Currently. // Return undefined as custom calendars do not implement -> Currently.
Ok(None) 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. // Return undefined as custom calendars do not implement -> Currently.
Ok(None) 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 let context = context
.downcast_mut::<Context>() .downcast_mut::<Context>()
.expect("Context was not provided for a CustomCalendar."); .expect("Context was not provided for a CustomCalendar.");
@ -279,7 +294,11 @@ impl CalendarProtocol for CustomRuntimeCalendar {
Ok(result) 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 let context = context
.downcast_mut::<Context>() .downcast_mut::<Context>()
.expect("Context was not provided for a CustomCalendar."); .expect("Context was not provided for a CustomCalendar.");
@ -324,7 +343,7 @@ impl CalendarProtocol for CustomRuntimeCalendar {
fn month_code( fn month_code(
&self, &self,
date_like: &CalendarDateLike, date_like: &CalendarDateLike<JsCustomCalendar>,
context: &mut dyn Any, context: &mut dyn Any,
) -> TemporalResult<TinyAsciiStr<4>> { ) -> TemporalResult<TinyAsciiStr<4>> {
let context = context let context = context
@ -354,7 +373,11 @@ impl CalendarProtocol for CustomRuntimeCalendar {
Ok(result) 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 let context = context
.downcast_mut::<Context>() .downcast_mut::<Context>()
.expect("Context was not provided for a CustomCalendar."); .expect("Context was not provided for a CustomCalendar.");
@ -399,7 +422,7 @@ impl CalendarProtocol for CustomRuntimeCalendar {
fn day_of_week( fn day_of_week(
&self, &self,
date_like: &CalendarDateLike, date_like: &CalendarDateLike<JsCustomCalendar>,
context: &mut dyn Any, context: &mut dyn Any,
) -> TemporalResult<u16> { ) -> TemporalResult<u16> {
let context = context let context = context
@ -448,7 +471,7 @@ impl CalendarProtocol for CustomRuntimeCalendar {
fn day_of_year( fn day_of_year(
&self, &self,
date_like: &CalendarDateLike, date_like: &CalendarDateLike<JsCustomCalendar>,
context: &mut dyn Any, context: &mut dyn Any,
) -> TemporalResult<u16> { ) -> TemporalResult<u16> {
let context = context let context = context
@ -497,7 +520,7 @@ impl CalendarProtocol for CustomRuntimeCalendar {
fn week_of_year( fn week_of_year(
&self, &self,
date_like: &CalendarDateLike, date_like: &CalendarDateLike<JsCustomCalendar>,
context: &mut dyn Any, context: &mut dyn Any,
) -> TemporalResult<u16> { ) -> TemporalResult<u16> {
let context = context let context = context
@ -546,7 +569,7 @@ impl CalendarProtocol for CustomRuntimeCalendar {
fn year_of_week( fn year_of_week(
&self, &self,
date_like: &CalendarDateLike, date_like: &CalendarDateLike<JsCustomCalendar>,
context: &mut dyn Any, context: &mut dyn Any,
) -> TemporalResult<i32> { ) -> TemporalResult<i32> {
let context = context let context = context
@ -588,7 +611,7 @@ impl CalendarProtocol for CustomRuntimeCalendar {
fn days_in_week( fn days_in_week(
&self, &self,
date_like: &CalendarDateLike, date_like: &CalendarDateLike<JsCustomCalendar>,
context: &mut dyn Any, context: &mut dyn Any,
) -> TemporalResult<u16> { ) -> TemporalResult<u16> {
let context = context let context = context
@ -637,7 +660,7 @@ impl CalendarProtocol for CustomRuntimeCalendar {
fn days_in_month( fn days_in_month(
&self, &self,
date_like: &CalendarDateLike, date_like: &CalendarDateLike<JsCustomCalendar>,
context: &mut dyn Any, context: &mut dyn Any,
) -> TemporalResult<u16> { ) -> TemporalResult<u16> {
let context = context let context = context
@ -687,7 +710,7 @@ impl CalendarProtocol for CustomRuntimeCalendar {
fn days_in_year( fn days_in_year(
&self, &self,
date_like: &CalendarDateLike, date_like: &CalendarDateLike<JsCustomCalendar>,
context: &mut dyn Any, context: &mut dyn Any,
) -> TemporalResult<u16> { ) -> TemporalResult<u16> {
let context = context let context = context
@ -736,7 +759,7 @@ impl CalendarProtocol for CustomRuntimeCalendar {
fn months_in_year( fn months_in_year(
&self, &self,
date_like: &CalendarDateLike, date_like: &CalendarDateLike<JsCustomCalendar>,
context: &mut dyn Any, context: &mut dyn Any,
) -> TemporalResult<u16> { ) -> TemporalResult<u16> {
let context = context let context = context
@ -787,7 +810,7 @@ impl CalendarProtocol for CustomRuntimeCalendar {
fn in_leap_year( fn in_leap_year(
&self, &self,
date_like: &CalendarDateLike, date_like: &CalendarDateLike<JsCustomCalendar>,
context: &mut dyn Any, context: &mut dyn Any,
) -> TemporalResult<bool> { ) -> TemporalResult<bool> {
let context = context let context = context
@ -816,18 +839,85 @@ impl CalendarProtocol for CustomRuntimeCalendar {
Ok(result) 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.");
let fields_js = Array::create_array_from_list(
fields.iter().map(|s| JsString::from(s.clone()).into()),
context,
);
fn field_descriptors(&self, _: CalendarFieldsType) -> Vec<(String, bool)> { let method = self
Vec::default() .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> { Ok(result)
Vec::default()
} }
fn resolve_fields(&self, _: &mut TemporalFields, _: CalendarFieldsType) -> TemporalResult<()> { fn merge_fields(
Ok(()) &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> { 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`. /// Utility function for converting `Temporal`'s `CalendarDateLike` to it's `Boa` specific `JsObject`.
pub(crate) fn date_like_to_object( pub(crate) fn date_like_to_object(
date_like: &CalendarDateLike, date_like: &CalendarDateLike<JsCustomCalendar>,
context: &mut Context, context: &mut Context,
) -> TemporalResult<JsValue> { ) -> TemporalResult<JsValue> {
match date_like { match date_like {

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

@ -3,8 +3,8 @@
use std::str::FromStr; use std::str::FromStr;
use crate::{ use crate::{
js_string, property::PropertyKey, value::PreferredType, Context, JsNativeError, JsObject, js_string, object::internal_methods::InternalMethodContext, property::PropertyKey,
JsResult, JsString, JsValue, value::PreferredType, Context, JsNativeError, JsObject, JsResult, JsString, JsValue,
}; };
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
@ -152,6 +152,82 @@ pub(crate) fn prepare_temporal_fields(
Ok(result) 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 { impl JsObject {
pub(crate) fn from_temporal_fields( pub(crate) fn from_temporal_fields(
fields: &TemporalFields, fields: &TemporalFields,

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

@ -21,17 +21,17 @@ use boa_temporal::{
options::ArithmeticOverflow, options::ArithmeticOverflow,
}; };
use super::{calendar, PlainDateTime, ZonedDateTime}; use super::{calendar, JsCustomCalendar, PlainDateTime, ZonedDateTime};
/// The `Temporal.PlainDate` object. /// The `Temporal.PlainDate` object.
#[derive(Debug, Clone, Trace, Finalize, JsData)] #[derive(Debug, Clone, Trace, Finalize, JsData)]
#[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerDate` could contain `Trace` types. #[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerDate` could contain `Trace` types.
pub struct PlainDate { pub struct PlainDate {
pub(crate) inner: InnerDate, pub(crate) inner: InnerDate<JsCustomCalendar>,
} }
impl PlainDate { impl PlainDate {
pub(crate) fn new(inner: InnerDate) -> Self { pub(crate) fn new(inner: InnerDate<JsCustomCalendar>) -> Self {
Self { inner } Self { inner }
} }
} }
@ -400,7 +400,7 @@ impl PlainDate {
/// 3.5.3 `CreateTemporalDate ( isoYear, isoMonth, isoDay, calendar [ , newTarget ] )` /// 3.5.3 `CreateTemporalDate ( isoYear, isoMonth, isoDay, calendar [ , newTarget ] )`
pub(crate) fn create_temporal_date( pub(crate) fn create_temporal_date(
inner: InnerDate, inner: InnerDate<JsCustomCalendar>,
new_target: Option<&JsValue>, new_target: Option<&JsValue>,
context: &mut Context, context: &mut Context,
) -> JsResult<JsObject> { ) -> 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. // 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() return Err(JsNativeError::range()
.with_message("Date is not within ISO date time limits.") .with_message("Date is not within ISO date time limits.")
.into()); .into());
@ -513,7 +513,7 @@ pub(crate) fn to_temporal_date(
// 13. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], calendar). // 13. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], calendar).
let result = date_like_string let result = date_like_string
.to_std_string_escaped() .to_std_string_escaped()
.parse::<InnerDate>() .parse::<InnerDate<JsCustomCalendar>>()
.map_err(|err| JsNativeError::range().with_message(err.to_string()))?; .map_err(|err| JsNativeError::range().with_message(err.to_string()))?;
Ok(PlainDate::new(result)) 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 boa_temporal::components::DateTime as InnerDateTime;
use super::JsCustomCalendar;
/// The `Temporal.PlainDateTime` object. /// The `Temporal.PlainDateTime` object.
#[derive(Debug, Clone, Trace, Finalize, JsData)] #[derive(Debug, Clone, Trace, Finalize, JsData)]
#[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerDateTime` could contain `Trace` types. #[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerDateTime` could contain `Trace` types.
pub struct PlainDateTime { pub struct PlainDateTime {
pub(crate) inner: InnerDateTime, pub(crate) inner: InnerDateTime<JsCustomCalendar>,
} }
impl PlainDateTime { impl PlainDateTime {
fn new(inner: InnerDateTime) -> Self { fn new(inner: InnerDateTime<JsCustomCalendar>) -> Self {
Self { inner } Self { inner }
} }
pub(crate) fn inner(&self) -> &InnerDateTime { pub(crate) fn inner(&self) -> &InnerDateTime<JsCustomCalendar> {
&self.inner &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 boa_temporal::components::{DateTime, MonthDay as InnerMonthDay};
use super::JsCustomCalendar;
/// The `Temporal.PlainMonthDay` object. /// The `Temporal.PlainMonthDay` object.
#[derive(Debug, Clone, Trace, Finalize, JsData)] #[derive(Debug, Clone, Trace, Finalize, JsData)]
#[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerMonthDay` could contain `Trace` types. #[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerMonthDay` could contain `Trace` types.
pub struct PlainMonthDay { pub struct PlainMonthDay {
pub(crate) inner: InnerMonthDay, pub(crate) inner: InnerMonthDay<JsCustomCalendar>,
} }
impl PlainMonthDay { impl PlainMonthDay {
fn new(inner: InnerMonthDay) -> Self { fn new(inner: InnerMonthDay<JsCustomCalendar>) -> Self {
Self { inner } Self { inner }
} }
} }
@ -69,13 +71,13 @@ impl BuiltInConstructor for PlainMonthDay {
// ==== `PlainMonthDay` Abstract Operations ==== // ==== `PlainMonthDay` Abstract Operations ====
pub(crate) fn create_temporal_month_day( pub(crate) fn create_temporal_month_day(
inner: InnerMonthDay, inner: InnerMonthDay<JsCustomCalendar>,
new_target: Option<&JsValue>, new_target: Option<&JsValue>,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
// 1. If IsValidISODate(referenceISOYear, isoMonth, isoDay) is false, throw a RangeError exception. // 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. // 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() return Err(JsNativeError::range()
.with_message("PlainMonthDay is not a valid ISO date time.") .with_message("PlainMonthDay is not a valid ISO date time.")
.into()); .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_gc::{Finalize, Trace};
use boa_profiler::Profiler; 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}; use boa_temporal::{components::YearMonth as InnerYearMonth, options::ArithmeticOverflow};
/// The `Temporal.PlainYearMonth` object. /// The `Temporal.PlainYearMonth` object.
#[derive(Debug, Clone, Trace, Finalize, JsData)] #[derive(Debug, Clone, Trace, Finalize, JsData)]
#[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerYearMonth` could contain `Trace` types. #[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerYearMonth` could contain `Trace` types.
pub struct PlainYearMonth { pub struct PlainYearMonth {
pub(crate) inner: InnerYearMonth, pub(crate) inner: InnerYearMonth<JsCustomCalendar>,
} }
impl PlainYearMonth { impl PlainYearMonth {
pub(crate) fn new(inner: InnerYearMonth) -> Self { pub(crate) fn new(inner: InnerYearMonth<JsCustomCalendar>) -> Self {
Self { inner } Self { inner }
} }
} }
@ -269,7 +269,7 @@ impl PlainYearMonth {
// 9.5.5 `CreateTemporalYearMonth ( isoYear, isoMonth, calendar, referenceISODay [ , newTarget ] )` // 9.5.5 `CreateTemporalYearMonth ( isoYear, isoMonth, calendar, referenceISODay [ , newTarget ] )`
pub(crate) fn create_temporal_year_month( pub(crate) fn create_temporal_year_month(
ym: InnerYearMonth, ym: InnerYearMonth<JsCustomCalendar>,
new_target: Option<&JsValue>, new_target: Option<&JsValue>,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> 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_profiler::Profiler;
use boa_temporal::components::{Duration as TemporalDuration, ZonedDateTime as InnerZdt}; use boa_temporal::components::{Duration as TemporalDuration, ZonedDateTime as InnerZdt};
use super::JsCustomCalendar;
/// The `Temporal.ZonedDateTime` object. /// The `Temporal.ZonedDateTime` object.
#[derive(Debug, Clone, Finalize, Trace, JsData)] #[derive(Debug, Clone, Finalize, Trace, JsData)]
// SAFETY: ZonedDateTime does not contain any traceable types. // SAFETY: ZonedDateTime does not contain any traceable types.
#[boa_gc(unsafe_empty_trace)] #[boa_gc(unsafe_empty_trace)]
pub struct ZonedDateTime { pub struct ZonedDateTime {
inner: InnerZdt, pub(crate) inner: InnerZdt<JsCustomCalendar>,
} }
impl BuiltInObject for ZonedDateTime { impl BuiltInObject for ZonedDateTime {

2
core/temporal/Cargo.toml

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

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

@ -3,7 +3,10 @@
use std::str::FromStr; use std::str::FromStr;
use crate::{ use crate::{
components::{calendar::CalendarSlot, Instant}, components::{
calendar::{CalendarProtocol, CalendarSlot},
Instant,
},
iso::{IsoDate, IsoDateSlots, IsoDateTime, IsoTime}, iso::{IsoDate, IsoDateSlots, IsoDateTime, IsoTime},
options::ArithmeticOverflow, options::ArithmeticOverflow,
parser::parse_date_time, parser::parse_date_time,
@ -12,18 +15,18 @@ use crate::{
/// The native Rust implementation of `Temporal.PlainDateTime` /// The native Rust implementation of `Temporal.PlainDateTime`
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct DateTime { pub struct DateTime<C: CalendarProtocol> {
iso: IsoDateTime, iso: IsoDateTime,
calendar: CalendarSlot, calendar: CalendarSlot<C>,
} }
// ==== Private DateTime API ==== // ==== Private DateTime API ====
impl DateTime { impl<C: CalendarProtocol> DateTime<C> {
/// Creates a new unchecked `DateTime`. /// Creates a new unchecked `DateTime`.
#[inline] #[inline]
#[must_use] #[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 { Self {
iso: IsoDateTime::new_unchecked(date, time), iso: IsoDateTime::new_unchecked(date, time),
calendar, calendar,
@ -42,7 +45,7 @@ impl DateTime {
pub(crate) fn from_instant( pub(crate) fn from_instant(
instant: &Instant, instant: &Instant,
offset: f64, offset: f64,
calendar: CalendarSlot, calendar: CalendarSlot<C>,
) -> TemporalResult<Self> { ) -> TemporalResult<Self> {
let iso = IsoDateTime::from_epoch_nanos(&instant.nanos, offset)?; let iso = IsoDateTime::from_epoch_nanos(&instant.nanos, offset)?;
Ok(Self { iso, calendar }) Ok(Self { iso, calendar })
@ -51,7 +54,7 @@ impl DateTime {
// ==== Public DateTime API ==== // ==== Public DateTime API ====
impl DateTime { impl<C: CalendarProtocol> DateTime<C> {
/// Creates a new validated `DateTime`. /// Creates a new validated `DateTime`.
#[inline] #[inline]
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
@ -65,7 +68,7 @@ impl DateTime {
millisecond: i32, millisecond: i32,
microsecond: i32, microsecond: i32,
nanosecond: i32, nanosecond: i32,
calendar: CalendarSlot, calendar: CalendarSlot<C>,
) -> TemporalResult<Self> { ) -> TemporalResult<Self> {
let iso_date = IsoDate::new(year, month, day, ArithmeticOverflow::Reject)?; let iso_date = IsoDate::new(year, month, day, ArithmeticOverflow::Reject)?;
let iso_time = IsoTime::new( let iso_time = IsoTime::new(
@ -145,14 +148,14 @@ impl DateTime {
/// Returns the Calendar value. /// Returns the Calendar value.
#[inline] #[inline]
#[must_use] #[must_use]
pub fn calendar(&self) -> &CalendarSlot { pub fn calendar(&self) -> &CalendarSlot<C> {
&self.calendar &self.calendar
} }
} }
// ==== Trait impls ==== // ==== Trait impls ====
impl FromStr for DateTime { impl<C: CalendarProtocol> FromStr for DateTime<C> {
type Err = TemporalError; type Err = TemporalError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
@ -181,7 +184,7 @@ impl FromStr for DateTime {
Ok(Self::new_unchecked( Ok(Self::new_unchecked(
date, date,
time, 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 std::{any::Any, str::FromStr};
use super::calendar::CalendarProtocol;
// ==== `DateDuration` ==== // ==== `DateDuration` ====
/// `DateDuration` represents the [date duration record][spec] of the `Duration.` /// `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 )` /// 7.5.21 `UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit, plainRelativeTo )`
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) fn unbalance_duration_relative( pub(crate) fn unbalance_duration_relative<C: CalendarProtocol>(
&self, &self,
largest_unit: TemporalUnit, largest_unit: TemporalUnit,
plain_relative_to: Option<&Date>, plain_relative_to: Option<&Date<C>>,
context: &mut dyn Any, context: &mut dyn Any,
) -> TemporalResult<DateDuration> { ) -> TemporalResult<DateDuration> {
// 1. Let allZero be false. // 1. Let allZero be false.
@ -918,10 +920,10 @@ impl Duration {
// TODO: Move to DateDuration // TODO: Move to DateDuration
/// `BalanceDateDurationRelative` /// `BalanceDateDurationRelative`
#[allow(unused)] #[allow(unused)]
pub fn balance_date_duration_relative( pub fn balance_date_duration_relative<C: CalendarProtocol>(
&self, &self,
largest_unit: TemporalUnit, largest_unit: TemporalUnit,
plain_relative_to: Option<&Date>, plain_relative_to: Option<&Date<C>>,
context: &mut dyn Any, context: &mut dyn Any,
) -> TemporalResult<DateDuration> { ) -> TemporalResult<DateDuration> {
let mut result = self.date; let mut result = self.date;
@ -1151,13 +1153,18 @@ impl Duration {
/// Abstract Operation 7.5.26 `RoundDuration ( years, months, weeks, days, hours, minutes, /// Abstract Operation 7.5.26 `RoundDuration ( years, months, weeks, days, hours, minutes,
/// seconds, milliseconds, microseconds, nanoseconds, increment, unit, /// seconds, milliseconds, microseconds, nanoseconds, increment, unit,
/// roundingMode [ , plainRelativeTo [, zonedRelativeTo [, precalculatedDateTime]]] )` /// roundingMode [ , plainRelativeTo [, zonedRelativeTo [, precalculatedDateTime]]] )`
pub fn round_duration( #[allow(clippy::type_complexity)]
pub fn round_duration<C: CalendarProtocol>(
&self, &self,
unbalance_date_duration: DateDuration, unbalance_date_duration: DateDuration,
increment: f64, increment: f64,
unit: TemporalUnit, unit: TemporalUnit,
rounding_mode: TemporalRoundingMode, 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, context: &mut dyn Any,
) -> TemporalResult<(Self, f64)> { ) -> TemporalResult<(Self, f64)> {
let mut result = Duration::new_unchecked(unbalance_date_duration, self.time); 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, TemporalError, TemporalResult,
}; };
use super::calendar::CalendarProtocol;
/// The native Rust implementation of `Temporal.PlainMonthDay` /// The native Rust implementation of `Temporal.PlainMonthDay`
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct MonthDay { pub struct MonthDay<C: CalendarProtocol> {
iso: IsoDate, iso: IsoDate,
calendar: CalendarSlot, calendar: CalendarSlot<C>,
} }
impl MonthDay { impl<C: CalendarProtocol> MonthDay<C> {
/// Creates a new unchecked `MonthDay` /// Creates a new unchecked `MonthDay`
#[inline] #[inline]
#[must_use] #[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 } Self { iso, calendar }
} }
@ -29,7 +31,7 @@ impl MonthDay {
pub fn new( pub fn new(
month: i32, month: i32,
day: i32, day: i32,
calendar: CalendarSlot, calendar: CalendarSlot<C>,
overflow: ArithmeticOverflow, overflow: ArithmeticOverflow,
) -> TemporalResult<Self> { ) -> TemporalResult<Self> {
let iso = IsoDate::new(1972, month, day, overflow)?; let iso = IsoDate::new(1972, month, day, overflow)?;
@ -53,12 +55,12 @@ impl MonthDay {
/// Returns a reference to `MonthDay`'s `CalendarSlot` /// Returns a reference to `MonthDay`'s `CalendarSlot`
#[inline] #[inline]
#[must_use] #[must_use]
pub fn calendar(&self) -> &CalendarSlot { pub fn calendar(&self) -> &CalendarSlot<C> {
&self.calendar &self.calendar
} }
} }
impl IsoDateSlots for MonthDay { impl<C: CalendarProtocol> IsoDateSlots for MonthDay<C> {
#[inline] #[inline]
/// Returns this structs `IsoDate`. /// Returns this structs `IsoDate`.
fn iso_date(&self) -> 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; type Err = TemporalError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
@ -77,7 +79,7 @@ impl FromStr for MonthDay {
Self::new( Self::new(
record.date.month, record.date.month,
record.date.day, record.date.day,
CalendarSlot::Identifier(calendar), CalendarSlot::from_str(&calendar)?,
ArithmeticOverflow::Reject, ArithmeticOverflow::Reject,
) )
} }

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

@ -10,6 +10,8 @@ use crate::{
TemporalError, TemporalResult, TemporalError, TemporalResult,
}; };
use super::calendar::CalendarProtocol;
/// Any object that implements the `TzProtocol` must implement the below methods/properties. /// Any object that implements the `TzProtocol` must implement the below methods/properties.
pub const TIME_ZONE_PROPERTIES: [&str; 3] = pub const TIME_ZONE_PROPERTIES: [&str; 3] =
["getOffsetNanosecondsFor", "getPossibleInstantsFor", "id"]; ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "id"];
@ -74,12 +76,12 @@ impl Clone for TimeZoneSlot {
} }
impl TimeZoneSlot { impl TimeZoneSlot {
pub(crate) fn get_datetime_for( pub(crate) fn get_datetime_for<C: CalendarProtocol>(
&self, &self,
instant: &Instant, instant: &Instant,
calendar: &CalendarSlot, calendar: &CalendarSlot<C>,
context: &mut dyn Any, context: &mut dyn Any,
) -> TemporalResult<DateTime> { ) -> TemporalResult<DateTime<C>> {
let nanos = self.get_offset_nanos_for(context)?; let nanos = self.get_offset_nanos_for(context)?;
DateTime::from_instant(instant, nanos.to_f64().unwrap_or(0.0), calendar.clone()) 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, TemporalError, TemporalResult,
}; };
use super::calendar::CalendarProtocol;
/// The native Rust implementation of `Temporal.YearMonth`. /// The native Rust implementation of `Temporal.YearMonth`.
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct YearMonth { pub struct YearMonth<C: CalendarProtocol> {
iso: IsoDate, iso: IsoDate,
calendar: CalendarSlot, calendar: CalendarSlot<C>,
} }
impl YearMonth { impl<C: CalendarProtocol> YearMonth<C> {
/// Creates an unvalidated `YearMonth`. /// Creates an unvalidated `YearMonth`.
#[inline] #[inline]
#[must_use] #[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 } Self { iso, calendar }
} }
@ -30,7 +32,7 @@ impl YearMonth {
year: i32, year: i32,
month: i32, month: i32,
reference_day: Option<i32>, reference_day: Option<i32>,
calendar: CalendarSlot, calendar: CalendarSlot<C>,
overflow: ArithmeticOverflow, overflow: ArithmeticOverflow,
) -> TemporalResult<Self> { ) -> TemporalResult<Self> {
let day = reference_day.unwrap_or(1); let day = reference_day.unwrap_or(1);
@ -55,12 +57,12 @@ impl YearMonth {
#[inline] #[inline]
#[must_use] #[must_use]
/// Returns a reference to `YearMonth`'s `CalendarSlot` /// Returns a reference to `YearMonth`'s `CalendarSlot`
pub fn calendar(&self) -> &CalendarSlot { pub fn calendar(&self) -> &CalendarSlot<C> {
&self.calendar &self.calendar
} }
} }
impl IsoDateSlots for YearMonth { impl<C: CalendarProtocol> IsoDateSlots for YearMonth<C> {
#[inline] #[inline]
/// Returns this `YearMonth`'s `IsoDate` /// Returns this `YearMonth`'s `IsoDate`
fn iso_date(&self) -> 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; type Err = TemporalError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
@ -80,7 +82,7 @@ impl FromStr for YearMonth {
record.date.year, record.date.year,
record.date.month, record.date.month,
None, None,
CalendarSlot::Identifier(calendar), CalendarSlot::from_str(&calendar)?,
ArithmeticOverflow::Reject, ArithmeticOverflow::Reject,
) )
} }

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

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

8
core/temporal/src/error.rs

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

293
core/temporal/src/fields.rs

@ -1,8 +1,12 @@
//! This module implements a native Rust `TemporalField` and components. //! 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 bitflags::bitflags;
// use rustc_hash::FxHashSet; // use rustc_hash::FxHashSet;
@ -56,6 +60,12 @@ pub enum FieldValue {
String(String), String(String),
} }
impl From<i32> for FieldValue {
fn from(value: i32) -> Self {
FieldValue::Integer(value)
}
}
/// The Conversion type of a field. /// The Conversion type of a field.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum FieldConversion { pub enum FieldConversion {
@ -149,6 +159,10 @@ impl Default for TemporalFields {
} }
impl 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> { pub(crate) const fn year(&self) -> Option<i32> {
self.year self.year
} }
@ -157,6 +171,12 @@ impl TemporalFields {
self.month 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> { pub(crate) const fn day(&self) -> Option<i32> {
self.day self.day
} }
@ -213,6 +233,53 @@ impl TemporalFields {
Ok(()) 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] #[inline]
fn set_year(&mut self, value: &FieldValue) -> TemporalResult<()> { fn set_year(&mut self, value: &FieldValue) -> TemporalResult<()> {
let FieldValue::Integer(y) = value else { let FieldValue::Integer(y) = value else {
@ -358,78 +425,28 @@ impl TemporalFields {
} }
} }
// TODO: optimize into iter.
impl TemporalFields { impl TemporalFields {
/// Returns a vector filled with the key-value pairs marked as active. /// Returns a vector filled with the key-value pairs marked as active.
#[must_use]
pub fn active_kvs(&self) -> Vec<(String, FieldValue)> { 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 { /// Returns an iterator over the current keys.
FieldMap::YEAR => result.push(( #[must_use]
"year".to_owned(), pub fn keys(&self) -> Keys {
self.year.map_or(FieldValue::Undefined, FieldValue::Integer), Keys {
)), iter: self.bit_map.iter(),
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),
)),
_ => {}
}
} }
}
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. /// Resolve `TemporalFields` month and monthCode fields.
@ -463,6 +480,144 @@ impl TemporalFields {
Ok(()) 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> { 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 = "20201108";
let basic_separated = "2020-11-08"; 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().year(), 2020);
assert_eq!(basic_result.iso_date().month(), 11); assert_eq!(basic_result.iso_date().month(), 11);
@ -32,7 +32,7 @@ fn temporal_date_time_max() {
let date_time = let date_time =
"+002020-11-08T12:28:32.329402834[!America/Argentina/ComodRivadavia][!u-ca=iso8601]"; "+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(); let time_results = result.iso_time();
@ -49,10 +49,10 @@ fn temporal_year_parsing() {
let long = "+002020-11-08"; let long = "+002020-11-08";
let bad_year = "-000000-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); assert_eq!(result_good.iso_date().year(), 2020);
let err_result = bad_year.parse::<DateTime>(); let err_result = bad_year.parse::<DateTime<()>>();
assert!( assert!(
err_result.is_err(), err_result.is_err(),
"Invalid extended year parsing: \"{bad_year}\" should fail to parse." "Invalid extended year parsing: \"{bad_year}\" should fail to parse."
@ -90,7 +90,7 @@ fn temporal_year_month() {
]; ];
for ym in possible_year_months { 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.year(), 2020);
assert_eq!(result.month(), 11); assert_eq!(result.month(), 11);
@ -108,7 +108,7 @@ fn temporal_month_day() {
]; ];
for md in possible_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.month(), 11);
assert_eq!(result.day(), 7); assert_eq!(result.day(), 7);
@ -124,7 +124,7 @@ fn temporal_invalid_annotations() {
]; ];
for invalid in invalid_annotations { for invalid in invalid_annotations {
let err_result = invalid.parse::<MonthDay>(); let err_result = invalid.parse::<MonthDay<()>>();
assert!( assert!(
err_result.is_err(), err_result.is_err(),
"Invalid ISO annotation parsing: \"{invalid}\" should fail parsing." "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 { for invalid_target in INVALID_DATETIME_STRINGS {
let error_result = invalid_target.parse::<DateTime>(); let error_result = invalid_target.parse::<DateTime<()>>();
assert!( assert!(
error_result.is_err(), error_result.is_err(),
"Invalid ISO8601 `DateTime` target: \"{invalid_target}\" should fail parsing." "Invalid ISO8601 `DateTime` target: \"{invalid_target}\" should fail parsing."

Loading…
Cancel
Save