From 1ef04a757f86279d6f310a028661f05588dbd9ec Mon Sep 17 00:00:00 2001 From: Kevin <46825870+nekevss@users.noreply.github.com> Date: Wed, 27 Dec 2023 22:57:38 -0500 Subject: [PATCH] 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 --- Cargo.lock | 8 + .../src/builtins/temporal/calendar/mod.rs | 388 +++------ .../src/builtins/temporal/calendar/object.rs | 166 +++- core/engine/src/builtins/temporal/fields.rs | 80 +- .../src/builtins/temporal/plain_date/mod.rs | 12 +- .../builtins/temporal/plain_date_time/mod.rs | 8 +- .../builtins/temporal/plain_month_day/mod.rs | 10 +- .../builtins/temporal/plain_year_month/mod.rs | 8 +- .../builtins/temporal/zoned_date_time/mod.rs | 4 +- core/temporal/Cargo.toml | 2 +- core/temporal/src/components/calendar.rs | 818 +++++++++++++----- core/temporal/src/components/calendar/iso.rs | 281 ------ core/temporal/src/components/date.rs | 27 +- core/temporal/src/components/datetime.rs | 25 +- core/temporal/src/components/duration.rs | 19 +- core/temporal/src/components/month_day.rs | 20 +- core/temporal/src/components/tz.rs | 8 +- core/temporal/src/components/year_month.rs | 20 +- core/temporal/src/components/zoneddatetime.rs | 31 +- core/temporal/src/error.rs | 8 + core/temporal/src/fields.rs | 293 +++++-- core/temporal/src/parser/tests.rs | 18 +- 22 files changed, 1307 insertions(+), 947 deletions(-) delete mode 100644 core/temporal/src/components/calendar/iso.rs diff --git a/Cargo.lock b/Cargo.lock index 049575a14c..02e123cd84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1667,7 +1667,9 @@ dependencies = [ "calendrical_calculations", "databake", "displaydoc", + "icu_calendar_data", "icu_locid", + "icu_locid_transform", "icu_provider", "serde", "tinystr", @@ -1675,6 +1677,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "icu_calendar_data" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22aec7d032735d9acb256eeef72adcac43c3b7572f19b51576a63d664b524ca2" + [[package]] name = "icu_casemap" version = "1.4.0" diff --git a/core/engine/src/builtins/temporal/calendar/mod.rs b/core/engine/src/builtins/temporal/calendar/mod.rs index 1afc88edb6..6f4a7f3a1f 100644 --- a/core/engine/src/builtins/temporal/calendar/mod.rs +++ b/core/engine/src/builtins/temporal/calendar/mod.rs @@ -21,11 +21,11 @@ use crate::{ string::{common::StaticJsStrings, utf16}, Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; -use boa_gc::{Finalize, Trace}; +use boa_gc::{custom_trace, Finalize, Trace}; use boa_profiler::Profiler; use boa_temporal::{ components::calendar::{ - AvailableCalendars, CalendarDateLike, CalendarFieldsType, CalendarSlot, + CalendarDateLike, CalendarFieldsType, CalendarProtocol, CalendarSlot, CALENDAR_PROTOCOL_METHODS, }, options::{ArithmeticOverflow, TemporalUnit}, @@ -33,22 +33,29 @@ use boa_temporal::{ mod object; -use object::CustomRuntimeCalendar; +#[doc(inline)] +pub(crate) use object::JsCustomCalendar; -#[cfg(feature = "experimental")] #[cfg(test)] mod tests; - /// The `Temporal.Calendar` object. -#[derive(Debug, Trace, Finalize, JsData)] -// SAFETY: `Calendar` doesn't contain traceable types. -#[boa_gc(unsafe_empty_trace)] +#[derive(Debug, Finalize, JsData)] pub struct Calendar { - slot: CalendarSlot, + slot: CalendarSlot, +} + +unsafe impl Trace for Calendar { + custom_trace!(this, mark, { + match &this.slot { + CalendarSlot::Protocol(custom) => mark(custom), + // SAFETY: CalendarSlot::Builtin does not contain any JsValues for the gc to trace. + CalendarSlot::Builtin(_) => {} + } + }); } impl Calendar { - pub(crate) fn new(slot: CalendarSlot) -> Self { + pub(crate) fn new(slot: CalendarSlot) -> Self { Self { slot } } } @@ -145,11 +152,10 @@ impl BuiltInConstructor for Calendar { // 3. If IsBuiltinCalendar(id) is false, then // a. Throw a RangeError exception. - let _ = AvailableCalendars::from_str(&id.to_std_string_escaped())?; // 4. Return ? CreateTemporalCalendar(id, NewTarget). create_temporal_calendar( - CalendarSlot::Identifier(id.to_std_string_escaped()), + CalendarSlot::::from_str(&id.to_std_string_escaped())?, Some(new_target.clone()), context, ) @@ -172,12 +178,7 @@ impl Calendar { .with_message("the this value of Calendar must be a Calendar object.") })?; - let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), - CalendarSlot::Protocol(proto) => proto.clone(), - }; - - Ok(JsString::from(protocol.identifier(context)?.as_str()).into()) + Ok(JsString::from(calendar.slot.identifier(context)?.as_str()).into()) } /// 15.8.2.1 `Temporal.Calendar.prototype.dateFromFields ( fields [ , options ] )` - Supercedes 12.5.4 @@ -196,12 +197,6 @@ impl Calendar { .with_message("this value of Calendar must be a Calendar object.") })?; - // Retrieve the current CalendarProtocol. - let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), - CalendarSlot::Protocol(proto) => proto.clone(), - }; - // 3. If Type(fields) is not Object, throw a TypeError exception. let fields = args.get_or_undefined(0); let fields_obj = fields.as_object().ok_or_else(|| { @@ -220,7 +215,7 @@ impl Calendar { ]); // 6. If calendar.[[Identifier]] is "iso8601", then - let mut fields = if protocol.identifier(context)?.as_str() == "iso8601" { + let mut fields = if calendar.slot.is_iso() { // a. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « "year", "day" »). let mut required_fields = Vec::from([js_string!("year"), js_string!("day")]); fields::prepare_temporal_fields( @@ -235,7 +230,8 @@ impl Calendar { // 7. Else, } else { // a. Let calendarRelevantFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], date). - let calendar_relevant_fields = protocol.field_descriptors(CalendarFieldsType::Date); + let calendar_relevant_fields = + calendar.slot.field_descriptors(CalendarFieldsType::Date)?; // b. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « », calendarRelevantFieldDescriptors). fields::prepare_temporal_fields( fields_obj, @@ -260,7 +256,9 @@ impl Calendar { // a. Perform ? CalendarResolveFields(calendar.[[Identifier]], fields, date). // b. Let result be ? CalendarDateToISO(calendar.[[Identifier]], fields, overflow). - let result = protocol.date_from_fields(&mut fields, overflow, context)?; + let result = calendar + .slot + .date_from_fields(&mut fields, overflow, context)?; create_temporal_date(result, None, context).map(Into::into) } @@ -279,11 +277,6 @@ impl Calendar { .with_message("this value of Calendar must be a Calendar object.") })?; - let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), - CalendarSlot::Protocol(proto) => proto.clone(), - }; - let fields = args.get_or_undefined(0); let fields_obj = fields.as_object().ok_or_else(|| { JsNativeError::typ().with_message("fields parameter must be an object.") @@ -299,7 +292,7 @@ impl Calendar { ]); // 6. Set fields to ? PrepareTemporalFields(fields, « "month", "monthCode", "year" », « "year" »). - let mut fields = if protocol.identifier(context)?.as_str() == "iso8601" { + let mut fields = if calendar.slot.identifier(context)?.as_str() == "iso8601" { // a. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « "year" »). let mut required_fields = Vec::from([js_string!("year")]); fields::prepare_temporal_fields( @@ -315,8 +308,9 @@ impl Calendar { // a. Let calendarRelevantFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], year-month). // b. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « », calendarRelevantFieldDescriptors). - let calendar_relevant_fields = - protocol.field_descriptors(CalendarFieldsType::YearMonth); + let calendar_relevant_fields = calendar + .slot + .field_descriptors(CalendarFieldsType::YearMonth)?; fields::prepare_temporal_fields( fields_obj, &mut relevant_field_names, @@ -336,7 +330,9 @@ impl Calendar { let overflow = get_option::(&options, utf16!("overflow"), context)? .unwrap_or(ArithmeticOverflow::Constrain); - let result = protocol.year_month_from_fields(&mut fields, overflow, context)?; + let result = calendar + .slot + .year_month_from_fields(&mut fields, overflow, context)?; create_temporal_year_month(result, None, context) } @@ -357,11 +353,6 @@ impl Calendar { .with_message("this value of Calendar must be a Calendar object.") })?; - let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), - CalendarSlot::Protocol(proto) => proto.clone(), - }; - // 3. If Type(fields) is not Object, throw a TypeError exception. let fields = args.get_or_undefined(0); let fields_obj = fields.as_object().ok_or_else(|| { @@ -380,7 +371,7 @@ impl Calendar { ]); // 6. If calendar.[[Identifier]] is "iso8601", then - let mut fields = if protocol.identifier(context)?.as_str() == "iso8601" { + let mut fields = if calendar.slot.identifier(context)?.as_str() == "iso8601" { // a. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « "day" »). let mut required_fields = Vec::from([js_string!("day")]); fields::prepare_temporal_fields( @@ -395,7 +386,9 @@ impl Calendar { // 7. Else, } else { // a. Let calendarRelevantFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], month-day). - let calendar_relevant_fields = protocol.field_descriptors(CalendarFieldsType::MonthDay); + let calendar_relevant_fields = calendar + .slot + .field_descriptors(CalendarFieldsType::MonthDay)?; // b. Set fields to ? PrepareTemporalFields(fields, relevantFieldNames, « », calendarRelevantFieldDescriptors). fields::prepare_temporal_fields( fields_obj, @@ -412,7 +405,9 @@ impl Calendar { let overflow = get_option(&options, utf16!("overflow"), context)? .unwrap_or(ArithmeticOverflow::Constrain); - let result = protocol.month_day_from_fields(&mut fields, overflow, context)?; + let result = calendar + .slot + .month_day_from_fields(&mut fields, overflow, context)?; create_temporal_month_day(result, None, context) } @@ -430,11 +425,6 @@ impl Calendar { .with_message("this value of Calendar must be a Calendar object.") })?; - let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), - CalendarSlot::Protocol(proto) => proto.clone(), - }; - // 4. Set date to ? ToTemporalDate(date). let date_like = args.get_or_undefined(0); let date = temporal::plain_date::to_temporal_date(date_like, None, context)?; @@ -454,7 +444,9 @@ impl Calendar { // 8. Let balanceResult be ? BalanceTimeDuration(duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], "day"). duration.balance_time_duration(TemporalUnit::Day)?; - let result = protocol.date_add(&date.inner, &duration, overflow, context)?; + let result = calendar + .slot + .date_add(&date.inner, &duration, overflow, context)?; create_temporal_date(result, None, context).map(Into::into) } @@ -472,11 +464,6 @@ impl Calendar { .with_message("this value of Calendar must be a Calendar object.") })?; - let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), - CalendarSlot::Protocol(proto) => proto.clone(), - }; - // 4. Set one to ? ToTemporalDate(one). let one = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; // 5. Set two to ? ToTemporalDate(two). @@ -496,7 +483,9 @@ impl Calendar { )? .unwrap_or(TemporalUnit::Day); - let result = protocol.date_until(&one.inner, &two.inner, largest_unit, context)?; + let result = calendar + .slot + .date_until(&one.inner, &two.inner, largest_unit, context)?; create_temporal_duration(result, None, context).map(Into::into) } @@ -511,14 +500,10 @@ impl Calendar { .with_message("this value of Calendar must be a Calendar object.") })?; - let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), - CalendarSlot::Protocol(proto) => proto.clone(), - }; - let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - let result = protocol + let result = calendar + .slot .era(&date_like, context)? .map_or(JsValue::undefined(), |r| JsString::from(r.as_str()).into()); @@ -535,14 +520,10 @@ impl Calendar { .with_message("this value of Calendar must be a Calendar object.") })?; - let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), - CalendarSlot::Protocol(proto) => proto.clone(), - }; - let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - let result = protocol + let result = calendar + .slot .era_year(&date_like, context)? .map_or(JsValue::undefined(), JsValue::from); @@ -559,14 +540,9 @@ impl Calendar { .with_message("this value of Calendar must be a Calendar object.") })?; - let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), - CalendarSlot::Protocol(proto) => proto.clone(), - }; - let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - let result = protocol.year(&date_like, context)?; + let result = calendar.slot.year(&date_like, context)?; Ok(result.into()) } @@ -581,11 +557,6 @@ impl Calendar { .with_message("this value of Calendar must be a Calendar object.") })?; - let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), - CalendarSlot::Protocol(proto) => proto.clone(), - }; - let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; // 3. If Type(temporalDateLike) is Object and temporalDateLike has an [[InitializedTemporalMonthDay]] internal slot, then @@ -593,7 +564,7 @@ impl Calendar { // 4. If Type(temporalDateLike) is not Object or temporalDateLike does not have an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], or [[InitializedTemporalYearMonth]] internal slot, then // 4.a. Set temporalDateLike to ? ToTemporalDate(temporalDateLike). - let result = protocol.month(&date_like, context)?; + let result = calendar.slot.month(&date_like, context)?; Ok(result.into()) } @@ -608,14 +579,9 @@ impl Calendar { .with_message("this value of Calendar must be a Calendar object.") })?; - let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), - CalendarSlot::Protocol(proto) => proto.clone(), - }; - let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - let result = protocol.month_code(&date_like, context)?; + let result = calendar.slot.month_code(&date_like, context)?; Ok(JsString::from(result.as_str()).into()) } @@ -630,14 +596,9 @@ impl Calendar { .with_message("this value of Calendar must be a Calendar object.") })?; - let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), - CalendarSlot::Protocol(proto) => proto.clone(), - }; - let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - let result = protocol.day(&date_like, context)?; + let result = calendar.slot.day(&date_like, context)?; Ok(result.into()) } @@ -654,15 +615,12 @@ impl Calendar { .with_message("this value of Calendar must be a Calendar object.") })?; - let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), - CalendarSlot::Protocol(proto) => proto.clone(), - }; - // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike). let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; - let result = protocol.day_of_week(&CalendarDateLike::Date(date.inner.clone()), context)?; + let result = calendar + .slot + .day_of_week(&CalendarDateLike::Date(date.inner.clone()), context)?; Ok(result.into()) } @@ -677,15 +635,12 @@ impl Calendar { .with_message("this value of Calendar must be a Calendar object.") })?; - let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), - CalendarSlot::Protocol(proto) => proto.clone(), - }; - // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike). let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; - let result = protocol.day_of_year(&CalendarDateLike::Date(date.inner.clone()), context)?; + let result = calendar + .slot + .day_of_year(&CalendarDateLike::Date(date.inner.clone()), context)?; Ok(result.into()) } @@ -700,15 +655,12 @@ impl Calendar { .with_message("this value of Calendar must be a Calendar object.") })?; - let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), - CalendarSlot::Protocol(proto) => proto.clone(), - }; - // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike). let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; - let result = protocol.week_of_year(&CalendarDateLike::Date(date.inner.clone()), context)?; + let result = calendar + .slot + .week_of_year(&CalendarDateLike::Date(date.inner.clone()), context)?; Ok(result.into()) } @@ -723,15 +675,12 @@ impl Calendar { .with_message("this value of Calendar must be a Calendar object.") })?; - let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), - CalendarSlot::Protocol(proto) => proto.clone(), - }; - // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike). let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; - let result = protocol.year_of_week(&CalendarDateLike::Date(date.inner.clone()), context)?; + let result = calendar + .slot + .year_of_week(&CalendarDateLike::Date(date.inner.clone()), context)?; Ok(result.into()) } @@ -746,15 +695,12 @@ impl Calendar { .with_message("this value of Calendar must be a Calendar object.") })?; - let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), - CalendarSlot::Protocol(proto) => proto.clone(), - }; - // 3. Let temporalDate be ? ToTemporalDate(temporalDateLike). let date = temporal::plain_date::to_temporal_date(args.get_or_undefined(0), None, context)?; - let result = protocol.days_in_week(&CalendarDateLike::Date(date.inner.clone()), context)?; + let result = calendar + .slot + .days_in_week(&CalendarDateLike::Date(date.inner.clone()), context)?; Ok(result.into()) } @@ -769,14 +715,9 @@ impl Calendar { .with_message("this value of Calendar must be a Calendar object.") })?; - let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), - CalendarSlot::Protocol(proto) => proto.clone(), - }; - let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - let result = protocol.days_in_month(&date_like, context)?; + let result = calendar.slot.days_in_month(&date_like, context)?; Ok(result.into()) } @@ -791,13 +732,8 @@ impl Calendar { .with_message("this value of Calendar must be a Calendar object.") })?; - let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), - CalendarSlot::Protocol(proto) => proto.clone(), - }; - let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - let result = protocol.days_in_year(&date_like, context)?; + let result = calendar.slot.days_in_year(&date_like, context)?; Ok(result.into()) } @@ -816,14 +752,9 @@ impl Calendar { .with_message("this value of Calendar must be a Calendar object.") })?; - let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), - CalendarSlot::Protocol(proto) => proto.clone(), - }; - let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - let result = protocol.months_in_year(&date_like, context)?; + let result = calendar.slot.months_in_year(&date_like, context)?; Ok(result.into()) } @@ -838,14 +769,9 @@ impl Calendar { .with_message("this value of Calendar must be a Calendar object.") })?; - let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), - CalendarSlot::Protocol(proto) => proto.clone(), - }; - let date_like = to_calendar_date_like(args.get_or_undefined(0), context)?; - let result = protocol.in_leap_year(&date_like, context)?; + let result = calendar.slot.in_leap_year(&date_like, context)?; Ok(result.into()) } @@ -862,10 +788,36 @@ impl Calendar { .with_message("this value of Calendar must be a Calendar object.") })?; - let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), - CalendarSlot::Protocol(proto) => proto.clone(), - }; + // Custom Calendars override the `fields` method. + if let CalendarSlot::Protocol(proto) = &calendar.slot { + // TODO: Is there a more efficient way to convert from iterable <-> Vec; + let mut iterator_record = + args.get_or_undefined(0) + .get_iterator(context, Some(IteratorHint::Sync), None)?; + let mut fields_list = Vec::default(); + + while iterator_record.step(context)? { + let next_val = iterator_record.value(context)?; + + if let JsValue::String(item) = next_val { + fields_list.push(item.to_std_string_escaped()); + } else { + // 1. Let completion be ThrowCompletion(a newly created TypeError object). + let completion = Err(JsNativeError::typ() + .with_message("field must be of type string") + .into()); + // 2. Return ? IteratorClose(iteratorRecord, completion). + return iterator_record.close(completion, context); + } + } + + let result = proto.fields(fields_list, context)?; + return Ok(Array::create_array_from_list( + result.iter().map(|s| JsString::from(s.clone()).into()), + context, + ) + .into()); + } // 3. Let iteratorRecord be ? GetIterator(fields, sync). let mut iterator_record = @@ -918,11 +870,12 @@ impl Calendar { // 7. Let result be fieldNames. // 8. If calendar.[[Identifier]] is not "iso8601", then - if protocol.identifier(context)?.as_str() != "iso8601" { + if !calendar.slot.is_iso() { // a. NOTE: Every built-in calendar preserves all input field names in output. // b. Let extraFieldDescriptors be CalendarFieldDescriptors(calendar.[[Identifier]], fieldNames). - let extended_fields = - protocol.field_descriptors(CalendarFieldsType::from(&fields_names[..])); + let extended_fields = calendar + .slot + .field_descriptors(CalendarFieldsType::from(&fields_names[..]))?; // c. For each Calendar Field Descriptor Record desc of extraFieldDescriptors, do for descriptor in extended_fields { // i. Append desc.[[Property]] to result. @@ -952,87 +905,46 @@ impl Calendar { .with_message("this value of Calendar must be a Calendar object.") })?; - let protocol = match &calendar.slot { - CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), - CalendarSlot::Protocol(proto) => proto.clone(), - }; - let fields = args.get_or_undefined(0).to_object(context)?; let additional_fields = args.get_or_undefined(1).to_object(context)?; // 3. Let fieldsCopy be ? SnapshotOwnProperties(? ToObject(fields), null, « », « undefined »). - let fields_copy = temporal::snapshot_own_properties( - &fields, - Some(Vec::new()), - Some(Vec::from([JsValue::undefined()])), - context, - )?; + let fields_copy = temporal::fields::object_to_temporal_fields(&fields, context)?; // 4. Let additionalFieldsCopy be ? SnapshotOwnProperties(? ToObject(additionalFields), null, « », « undefined »). - let additional_fields_copy = temporal::snapshot_own_properties( - &additional_fields, - Some(Vec::new()), - Some(Vec::from([JsValue::undefined()])), - context, - )?; + let additional_copy = + temporal::fields::object_to_temporal_fields(&additional_fields, context)?; - // 5. NOTE: Every property of fieldsCopy and additionalFieldsCopy is an enumerable data property with non-undefined value, but some property keys may be Symbols. - // 6. Let additionalKeys be ! additionalFieldsCopy.[[OwnPropertyKeys]](). - let add_keys = additional_fields_copy - .__own_property_keys__(context)? - .iter() - .map(ToString::to_string) - .collect::>(); + // Custom Calendars override the `fields` method. + if let CalendarSlot::Protocol(proto) = &calendar.slot { + let result = proto.merge_fields(&fields_copy, &additional_copy, context)?; // TBD + return JsObject::from_temporal_fields(&result, context).map(Into::into); + } + // 5. NOTE: Every property of fieldsCopy and additionalFieldsCopy is an enumerable data property with non-undefined value, + // but some property keys may be Symbols. + // 6. Let additionalKeys be ! additionalFieldsCopy.[[OwnPropertyKeys]](). // 7. If calendar.[[Identifier]] is "iso8601", then // a. Let overriddenKeys be ISOFieldKeysToIgnore(additionalKeys). // 8. Else, // a. Let overriddenKeys be CalendarFieldKeysToIgnore(calendar, additionalKeys). - let overridden_keys = protocol.field_keys_to_ignore(add_keys); - // 9. Let merged be OrdinaryObjectCreate(null). - let merged = JsObject::with_null_proto(); - // 10. NOTE: The following steps ensure that property iteration order of merged // matches that of fields as modified by omitting overridden properties and // appending non-overlapping properties from additionalFields in iteration order. // 11. Let fieldsKeys be ! fieldsCopy.[[OwnPropertyKeys]](). - let field_keys = fields_copy - .__own_property_keys__(context)? - .iter() - .map(|k| JsString::from(k.to_string())) - .collect::>(); - // 12. For each element key of fieldsKeys, do - for key in field_keys { - // a. Let propValue be undefined. - // b. If overriddenKeys contains key, then - let prop_value = if overridden_keys.contains(&key.to_std_string_escaped()) { - // i. Set propValue to ! Get(additionalFieldsCopy, key). - additional_fields_copy.get(key.as_slice(), context)? - // c. Else, - } else { - // i. Set propValue to ! Get(fieldsCopy, key). - fields_copy.get(key.as_slice(), context)? - }; - - // d. If propValue is not undefined, perform ! CreateDataPropertyOrThrow(merged, key, propValue). - if !prop_value.is_undefined() { - merged.create_data_property_or_throw(key.as_slice(), prop_value, context)?; - } - } + // a. Let propValue be undefined. + // b. If overriddenKeys contains key, then + // i. Set propValue to ! Get(additionalFieldsCopy, key). + // c. Else, + // i. Set propValue to ! Get(fieldsCopy, key). + // d. If propValue is not undefined, perform ! CreateDataPropertyOrThrow(merged, key, propValue). + let merged = fields_copy.merge_fields(&additional_copy, &calendar.slot)?; // 13. Perform ! CopyDataProperties(merged, additionalFieldsCopy, « »). - temporal::copy_data_properties( - &merged, - &additional_fields_copy.into(), - &Vec::new(), - None, - context, - )?; - // 14. Return merged. - Ok(merged.into()) + JsObject::from_temporal_fields(&merged, context).map(Into::into) } } @@ -1040,7 +952,7 @@ impl Calendar { /// 12.2.1 `CreateTemporalCalendar ( identifier [ , newTarget ] )` pub(crate) fn create_temporal_calendar( - identifier: CalendarSlot, + identifier: CalendarSlot, new_target: Option, context: &mut Context, ) -> JsResult { @@ -1104,7 +1016,7 @@ where pub(crate) fn get_temporal_calendar_slot_value_with_default( item: &JsObject, context: &mut Context, -) -> JsResult { +) -> JsResult> { // 1. If item has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal slot, then // a. Return item.[[Calendar]]. if let Some(calendar) = extract_from_temporal_type( @@ -1133,12 +1045,12 @@ pub(crate) fn get_temporal_calendar_slot_value_with_default( pub(crate) fn to_temporal_calendar_slot_value( calendar_like: &JsValue, context: &mut Context, -) -> JsResult { +) -> JsResult> { // 1. If temporalCalendarLike is undefined and default is present, then // a. Assert: IsBuiltinCalendar(default) is true. // b. Return default. if calendar_like.is_undefined() { - return Ok(CalendarSlot::Identifier("iso8601".to_owned())); + return Ok(CalendarSlot::default()); // 2. If Type(temporalCalendarLike) is Object, then } else if let Some(calendar_like) = calendar_like.as_object() { // a. If temporalCalendarLike has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal slot, then @@ -1146,26 +1058,10 @@ pub(crate) fn to_temporal_calendar_slot_value( if let Some(calendar) = extract_from_temporal_type( calendar_like, |d| Ok(Some(d.inner.calendar().clone())), - |_dt| { - Err(JsNativeError::range() - .with_message("Not yet implemented.") - .into()) - }, - |_ym| { - Err(JsNativeError::range() - .with_message("Not yet implemented.") - .into()) - }, - |_md| { - Err(JsNativeError::range() - .with_message("Not yet implemented.") - .into()) - }, - |_zdt| { - Err(JsNativeError::range() - .with_message("Not yet implemented.") - .into()) - }, + |dt| Ok(Some(dt.inner.calendar().clone())), + |ym| Ok(Some(ym.inner.calendar().clone())), + |md| Ok(Some(md.inner.calendar().clone())), + |zdt| Ok(Some(zdt.inner.calendar().clone())), )? { return Ok(calendar); } @@ -1179,23 +1075,24 @@ pub(crate) fn to_temporal_calendar_slot_value( } // Types: Box <- UserCalendar - let protocol = Box::new(CustomRuntimeCalendar::new(calendar_like)); + let custom = JsCustomCalendar::new(calendar_like); // c. Return temporalCalendarLike. - return Ok(CalendarSlot::Protocol(protocol)); + return Ok(CalendarSlot::Protocol(custom)); } // 3. If temporalCalendarLike is not a String, throw a TypeError exception. - if !calendar_like.is_string() { + let JsValue::String(calendar_id) = calendar_like else { return Err(JsNativeError::typ() .with_message("temporalCalendarLike is not a string.") .into()); - } + }; - // TODO: 4-6 // 4. Let identifier be ? ParseTemporalCalendarString(temporalCalendarLike). // 5. If IsBuiltinCalendar(identifier) is false, throw a RangeError exception. // 6. Return the ASCII-lowercase of identifier. - Ok(CalendarSlot::Identifier("iso8601".to_owned())) + Ok(CalendarSlot::::from_str( + &calendar_id.to_std_string_escaped(), + )?) } fn object_implements_calendar_protocol(calendar_like: &JsObject, context: &mut Context) -> bool { @@ -1207,7 +1104,10 @@ fn object_implements_calendar_protocol(calendar_like: &JsObject, context: &mut C } /// Utility function for taking a `JsValue` and converting it to a temporal library `CalendarDateLike` enum. -fn to_calendar_date_like(date_like: &JsValue, context: &mut Context) -> JsResult { +fn to_calendar_date_like( + date_like: &JsValue, + context: &mut Context, +) -> JsResult> { let Some(obj) = date_like.as_object() else { let date = temporal::plain_date::to_temporal_date(date_like, None, context)?; diff --git a/core/engine/src/builtins/temporal/calendar/object.rs b/core/engine/src/builtins/temporal/calendar/object.rs index 76d7fcad54..198d2f58ee 100644 --- a/core/engine/src/builtins/temporal/calendar/object.rs +++ b/core/engine/src/builtins/temporal/calendar/object.rs @@ -1,16 +1,23 @@ //! Boa's implementation of a user-defined Anonymous Calendar. use crate::{ - builtins::temporal::{plain_date, plain_month_day, plain_year_month}, + builtins::{ + iterable::IteratorHint, + temporal::{ + fields::object_to_temporal_fields, plain_date, plain_month_day, plain_year_month, + }, + Array, + }, property::PropertyKey, Context, JsObject, JsString, JsValue, }; use std::any::Any; +use boa_gc::{Finalize, Trace}; use boa_macros::utf16; use boa_temporal::{ components::{ - calendar::{CalendarDateLike, CalendarFieldsType, CalendarProtocol}, + calendar::{CalendarDateLike, CalendarProtocol}, Date, Duration, MonthDay, YearMonth, }, options::ArithmeticOverflow, @@ -26,12 +33,12 @@ use plain_year_month::PlainYearMonth; /// /// A user-defined calendar implements all of the `CalendarProtocolMethods` /// and therefore satisfies the requirements to be used as a calendar. -#[derive(Debug, Clone)] -pub(crate) struct CustomRuntimeCalendar { +#[derive(Debug, Clone, Trace, Finalize)] +pub(crate) struct JsCustomCalendar { calendar: JsObject, } -impl CustomRuntimeCalendar { +impl JsCustomCalendar { pub(crate) fn new(calendar: &JsObject) -> Self { Self { calendar: calendar.clone(), @@ -39,13 +46,13 @@ impl CustomRuntimeCalendar { } } -impl CalendarProtocol for CustomRuntimeCalendar { +impl CalendarProtocol for JsCustomCalendar { fn date_from_fields( &self, fields: &mut TemporalFields, overflow: ArithmeticOverflow, context: &mut dyn Any, - ) -> TemporalResult { + ) -> TemporalResult> { let context = context .downcast_mut::() .expect("Context was not provided for a CustomCalendar."); @@ -97,7 +104,7 @@ impl CalendarProtocol for CustomRuntimeCalendar { fields: &mut TemporalFields, overflow: ArithmeticOverflow, context: &mut dyn Any, - ) -> TemporalResult { + ) -> TemporalResult> { let context = context .downcast_mut::() .expect("Context was not provided for a CustomCalendar."); @@ -151,7 +158,7 @@ impl CalendarProtocol for CustomRuntimeCalendar { fields: &mut TemporalFields, overflow: ArithmeticOverflow, context: &mut dyn Any, - ) -> TemporalResult { + ) -> TemporalResult> { let context = context .downcast_mut::() .expect("Context was not provided for a CustomCalendar."); @@ -202,19 +209,19 @@ impl CalendarProtocol for CustomRuntimeCalendar { fn date_add( &self, - _date: &Date, + _date: &Date, _duration: &Duration, _overflow: ArithmeticOverflow, _context: &mut dyn Any, - ) -> TemporalResult { + ) -> TemporalResult> { // TODO Err(TemporalError::general("Not yet implemented.")) } fn date_until( &self, - _one: &Date, - _two: &Date, + _one: &Date, + _two: &Date, _largest_unit: boa_temporal::options::TemporalUnit, _context: &mut dyn Any, ) -> TemporalResult { @@ -224,19 +231,27 @@ impl CalendarProtocol for CustomRuntimeCalendar { fn era( &self, - _: &CalendarDateLike, + _: &CalendarDateLike, _: &mut dyn Any, - ) -> TemporalResult>> { + ) -> TemporalResult>> { // Return undefined as custom calendars do not implement -> Currently. Ok(None) } - fn era_year(&self, _: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult> { + fn era_year( + &self, + _: &CalendarDateLike, + _: &mut dyn Any, + ) -> TemporalResult> { // Return undefined as custom calendars do not implement -> Currently. Ok(None) } - fn year(&self, date_like: &CalendarDateLike, context: &mut dyn Any) -> TemporalResult { + fn year( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult { let context = context .downcast_mut::() .expect("Context was not provided for a CustomCalendar."); @@ -279,7 +294,11 @@ impl CalendarProtocol for CustomRuntimeCalendar { Ok(result) } - fn month(&self, date_like: &CalendarDateLike, context: &mut dyn Any) -> TemporalResult { + fn month( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult { let context = context .downcast_mut::() .expect("Context was not provided for a CustomCalendar."); @@ -324,7 +343,7 @@ impl CalendarProtocol for CustomRuntimeCalendar { fn month_code( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult> { let context = context @@ -354,7 +373,11 @@ impl CalendarProtocol for CustomRuntimeCalendar { Ok(result) } - fn day(&self, date_like: &CalendarDateLike, context: &mut dyn Any) -> TemporalResult { + fn day( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult { let context = context .downcast_mut::() .expect("Context was not provided for a CustomCalendar."); @@ -399,7 +422,7 @@ impl CalendarProtocol for CustomRuntimeCalendar { fn day_of_week( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult { let context = context @@ -448,7 +471,7 @@ impl CalendarProtocol for CustomRuntimeCalendar { fn day_of_year( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult { let context = context @@ -497,7 +520,7 @@ impl CalendarProtocol for CustomRuntimeCalendar { fn week_of_year( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult { let context = context @@ -546,7 +569,7 @@ impl CalendarProtocol for CustomRuntimeCalendar { fn year_of_week( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult { let context = context @@ -588,7 +611,7 @@ impl CalendarProtocol for CustomRuntimeCalendar { fn days_in_week( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult { let context = context @@ -637,7 +660,7 @@ impl CalendarProtocol for CustomRuntimeCalendar { fn days_in_month( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult { let context = context @@ -687,7 +710,7 @@ impl CalendarProtocol for CustomRuntimeCalendar { fn days_in_year( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult { let context = context @@ -736,7 +759,7 @@ impl CalendarProtocol for CustomRuntimeCalendar { fn months_in_year( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult { let context = context @@ -787,7 +810,7 @@ impl CalendarProtocol for CustomRuntimeCalendar { fn in_leap_year( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult { let context = context @@ -816,18 +839,85 @@ impl CalendarProtocol for CustomRuntimeCalendar { Ok(result) } - // TODO: Determine fate of fn fields() + fn fields(&self, fields: Vec, context: &mut dyn Any) -> TemporalResult> { + let context = context + .downcast_mut::() + .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)> { - Vec::default() - } + let method = self + .calendar + .get(PropertyKey::from(utf16!("fields")), context) + .expect("method must exist on an object that implements the CalendarProtocol."); + + let result = method + .as_callable() + .expect("is method") + .call(&method, &[fields_js.into()], context) + .map_err(|e| TemporalError::general(e.to_string()))?; + + // validate result and map to a `Vec` + 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) -> Vec { - Vec::default() + Ok(result) } - fn resolve_fields(&self, _: &mut TemporalFields, _: CalendarFieldsType) -> TemporalResult<()> { - Ok(()) + fn merge_fields( + &self, + fields: &TemporalFields, + additional_fields: &TemporalFields, + context: &mut dyn Any, + ) -> TemporalResult { + let context = context + .downcast_mut::() + .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 { @@ -854,7 +944,7 @@ impl CalendarProtocol for CustomRuntimeCalendar { /// Utility function for converting `Temporal`'s `CalendarDateLike` to it's `Boa` specific `JsObject`. pub(crate) fn date_like_to_object( - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut Context, ) -> TemporalResult { match date_like { diff --git a/core/engine/src/builtins/temporal/fields.rs b/core/engine/src/builtins/temporal/fields.rs index c842816c07..6c6b00d655 100644 --- a/core/engine/src/builtins/temporal/fields.rs +++ b/core/engine/src/builtins/temporal/fields.rs @@ -3,8 +3,8 @@ use std::str::FromStr; use crate::{ - js_string, property::PropertyKey, value::PreferredType, Context, JsNativeError, JsObject, - JsResult, JsString, JsValue, + js_string, object::internal_methods::InternalMethodContext, property::PropertyKey, + value::PreferredType, Context, JsNativeError, JsObject, JsResult, JsString, JsValue, }; use rustc_hash::FxHashSet; @@ -152,6 +152,82 @@ pub(crate) fn prepare_temporal_fields( Ok(result) } +// NOTE(nekevss): The below serves as a replacement for `Snapshot` on `Calendar.prototype.mergeFields`. +// +// Some potential issues here: `Calendar.prototype.mergeFields` appears to allow extra fields that +// are not part of a `TemporalFields` record; however, the specification only calls `mergeFields` on an +// object returned by `PrepareTemporalFields`, so the translation should be fine sound. +// +// The restriction/trade-off would occur if someone wanted to include non-normative calendar fields (i.e. something +// not accounted for in the specification) in a Custom Calendar or use `Calendar.prototype.mergeFields` in +// general as a way to merge two objects. +pub(crate) fn object_to_temporal_fields( + source: &JsObject, + context: &mut Context, +) -> JsResult { + // Adapted from `CopyDataProperties` with ExcludedKeys -> << >> && ExcludedValues -> << Undefined >> + const VALID_FIELDS: [&str; 14] = [ + "year", + "month", + "monthCode", + "day", + "hour", + "minute", + "second", + "millisecond", + "microsecond", + "nanosecond", + "offset", + "timeZone", + "era", + "eraYear", + ]; + let mut copy = TemporalFields::default(); + + let keys = source.__own_property_keys__(context)?; + + for key in &keys { + let desc = source.__get_own_property__(key, &mut InternalMethodContext::new(context))?; + match desc { + // Enforce that the `PropertyKey` is a valid field here. + Some(desc) + if desc.expect_enumerable() && VALID_FIELDS.contains(&key.to_string().as_str()) => + { + let value = source.get(key.clone(), context)?; + // Below is repurposed from `PrepareTemporalFields`. + if !value.is_undefined() { + let conversion = FieldConversion::from_str(&key.to_string())?; + let converted_value = match conversion { + FieldConversion::ToIntegerWithTruncation => { + let v = to_integer_with_truncation(&value, context)?; + FieldValue::Integer(v) + } + FieldConversion::ToPositiveIntegerWithTruncation => { + let v = to_positive_integer_with_trunc(&value, context)?; + FieldValue::Integer(v) + } + FieldConversion::ToPrimativeAndRequireString => { + let primitive = value.to_primitive(context, PreferredType::String)?; + FieldValue::String( + primitive.to_string(context)?.to_std_string_escaped(), + ) + } + FieldConversion::None => { + unreachable!("todo need to implement conversion handling for tz.") + } + }; + // TODO: Test the below further and potentially expand handling. + copy.set_field_value(&key.to_string(), &converted_value) + .expect("FieldConversion enforces the appropriate type"); + } + } + _ => {} + }; + } + + Ok(copy) +} + impl JsObject { pub(crate) fn from_temporal_fields( fields: &TemporalFields, diff --git a/core/engine/src/builtins/temporal/plain_date/mod.rs b/core/engine/src/builtins/temporal/plain_date/mod.rs index 138273cd22..6b3d4b9832 100644 --- a/core/engine/src/builtins/temporal/plain_date/mod.rs +++ b/core/engine/src/builtins/temporal/plain_date/mod.rs @@ -21,17 +21,17 @@ use boa_temporal::{ options::ArithmeticOverflow, }; -use super::{calendar, PlainDateTime, ZonedDateTime}; +use super::{calendar, JsCustomCalendar, PlainDateTime, ZonedDateTime}; /// The `Temporal.PlainDate` object. #[derive(Debug, Clone, Trace, Finalize, JsData)] #[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerDate` could contain `Trace` types. pub struct PlainDate { - pub(crate) inner: InnerDate, + pub(crate) inner: InnerDate, } impl PlainDate { - pub(crate) fn new(inner: InnerDate) -> Self { + pub(crate) fn new(inner: InnerDate) -> Self { Self { inner } } } @@ -400,7 +400,7 @@ impl PlainDate { /// 3.5.3 `CreateTemporalDate ( isoYear, isoMonth, isoDay, calendar [ , newTarget ] )` pub(crate) fn create_temporal_date( - inner: InnerDate, + inner: InnerDate, new_target: Option<&JsValue>, context: &mut Context, ) -> JsResult { @@ -412,7 +412,7 @@ pub(crate) fn create_temporal_date( }; // 2. If ISODateTimeWithinLimits(isoYear, isoMonth, isoDay, 12, 0, 0, 0, 0, 0) is false, throw a RangeError exception. - if !DateTime::validate(&inner) { + if !DateTime::::validate(&inner) { return Err(JsNativeError::range() .with_message("Date is not within ISO date time limits.") .into()); @@ -513,7 +513,7 @@ pub(crate) fn to_temporal_date( // 13. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], calendar). let result = date_like_string .to_std_string_escaped() - .parse::() + .parse::>() .map_err(|err| JsNativeError::range().with_message(err.to_string()))?; Ok(PlainDate::new(result)) diff --git a/core/engine/src/builtins/temporal/plain_date_time/mod.rs b/core/engine/src/builtins/temporal/plain_date_time/mod.rs index a8c3a8e793..b655965531 100644 --- a/core/engine/src/builtins/temporal/plain_date_time/mod.rs +++ b/core/engine/src/builtins/temporal/plain_date_time/mod.rs @@ -14,19 +14,21 @@ use boa_profiler::Profiler; use boa_temporal::components::DateTime as InnerDateTime; +use super::JsCustomCalendar; + /// The `Temporal.PlainDateTime` object. #[derive(Debug, Clone, Trace, Finalize, JsData)] #[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerDateTime` could contain `Trace` types. pub struct PlainDateTime { - pub(crate) inner: InnerDateTime, + pub(crate) inner: InnerDateTime, } impl PlainDateTime { - fn new(inner: InnerDateTime) -> Self { + fn new(inner: InnerDateTime) -> Self { Self { inner } } - pub(crate) fn inner(&self) -> &InnerDateTime { + pub(crate) fn inner(&self) -> &InnerDateTime { &self.inner } } diff --git a/core/engine/src/builtins/temporal/plain_month_day/mod.rs b/core/engine/src/builtins/temporal/plain_month_day/mod.rs index 0caf7f895a..719061dd51 100644 --- a/core/engine/src/builtins/temporal/plain_month_day/mod.rs +++ b/core/engine/src/builtins/temporal/plain_month_day/mod.rs @@ -14,15 +14,17 @@ use boa_profiler::Profiler; use boa_temporal::components::{DateTime, MonthDay as InnerMonthDay}; +use super::JsCustomCalendar; + /// The `Temporal.PlainMonthDay` object. #[derive(Debug, Clone, Trace, Finalize, JsData)] #[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerMonthDay` could contain `Trace` types. pub struct PlainMonthDay { - pub(crate) inner: InnerMonthDay, + pub(crate) inner: InnerMonthDay, } impl PlainMonthDay { - fn new(inner: InnerMonthDay) -> Self { + fn new(inner: InnerMonthDay) -> Self { Self { inner } } } @@ -69,13 +71,13 @@ impl BuiltInConstructor for PlainMonthDay { // ==== `PlainMonthDay` Abstract Operations ==== pub(crate) fn create_temporal_month_day( - inner: InnerMonthDay, + inner: InnerMonthDay, new_target: Option<&JsValue>, context: &mut Context, ) -> JsResult { // 1. If IsValidISODate(referenceISOYear, isoMonth, isoDay) is false, throw a RangeError exception. // 2. If ISODateTimeWithinLimits(referenceISOYear, isoMonth, isoDay, 12, 0, 0, 0, 0, 0) is false, throw a RangeError exception. - if DateTime::validate(&inner) { + if DateTime::::validate(&inner) { return Err(JsNativeError::range() .with_message("PlainMonthDay is not a valid ISO date time.") .into()); diff --git a/core/engine/src/builtins/temporal/plain_year_month/mod.rs b/core/engine/src/builtins/temporal/plain_year_month/mod.rs index 5d3f4b7929..b3808f546f 100644 --- a/core/engine/src/builtins/temporal/plain_year_month/mod.rs +++ b/core/engine/src/builtins/temporal/plain_year_month/mod.rs @@ -13,18 +13,18 @@ use crate::{ use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; -use super::calendar::to_temporal_calendar_slot_value; +use super::{calendar::to_temporal_calendar_slot_value, JsCustomCalendar}; use boa_temporal::{components::YearMonth as InnerYearMonth, options::ArithmeticOverflow}; /// The `Temporal.PlainYearMonth` object. #[derive(Debug, Clone, Trace, Finalize, JsData)] #[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerYearMonth` could contain `Trace` types. pub struct PlainYearMonth { - pub(crate) inner: InnerYearMonth, + pub(crate) inner: InnerYearMonth, } impl PlainYearMonth { - pub(crate) fn new(inner: InnerYearMonth) -> Self { + pub(crate) fn new(inner: InnerYearMonth) -> Self { Self { inner } } } @@ -269,7 +269,7 @@ impl PlainYearMonth { // 9.5.5 `CreateTemporalYearMonth ( isoYear, isoMonth, calendar, referenceISODay [ , newTarget ] )` pub(crate) fn create_temporal_year_month( - ym: InnerYearMonth, + ym: InnerYearMonth, new_target: Option<&JsValue>, context: &mut Context, ) -> JsResult { diff --git a/core/engine/src/builtins/temporal/zoned_date_time/mod.rs b/core/engine/src/builtins/temporal/zoned_date_time/mod.rs index 64295e547d..eab3c7b1e3 100644 --- a/core/engine/src/builtins/temporal/zoned_date_time/mod.rs +++ b/core/engine/src/builtins/temporal/zoned_date_time/mod.rs @@ -11,12 +11,14 @@ use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; use boa_temporal::components::{Duration as TemporalDuration, ZonedDateTime as InnerZdt}; +use super::JsCustomCalendar; + /// The `Temporal.ZonedDateTime` object. #[derive(Debug, Clone, Finalize, Trace, JsData)] // SAFETY: ZonedDateTime does not contain any traceable types. #[boa_gc(unsafe_empty_trace)] pub struct ZonedDateTime { - inner: InnerZdt, + pub(crate) inner: InnerZdt, } impl BuiltInObject for ZonedDateTime { diff --git a/core/temporal/Cargo.toml b/core/temporal/Cargo.toml index 2c3a128620..75f47b33d0 100644 --- a/core/temporal/Cargo.toml +++ b/core/temporal/Cargo.toml @@ -13,7 +13,7 @@ rust-version.workspace = true [dependencies] tinystr = "0.7.4" -icu_calendar = { workspace = true, default-features = false } +icu_calendar = { workspace = true, default-features = true } rustc-hash = { workspace = true, features = ["std"] } num-bigint = { workspace = true, features = ["serde"] } bitflags.workspace = true diff --git a/core/temporal/src/components/calendar.rs b/core/temporal/src/components/calendar.rs index ff34b6e8e0..8e35ea0754 100644 --- a/core/temporal/src/components/calendar.rs +++ b/core/temporal/src/components/calendar.rs @@ -17,12 +17,13 @@ use crate::{ TemporalError, TemporalFields, TemporalResult, }; +use icu_calendar::{ + types::{Era, MonthCode}, + week::{RelativeUnit, WeekCalculator}, + AnyCalendar, AnyCalendarKind, Calendar, Iso, +}; use tinystr::TinyAsciiStr; -use self::iso::IsoCalendar; - -pub mod iso; - /// The ECMAScript defined protocol methods pub const CALENDAR_PROTOCOL_METHODS: [&str; 21] = [ "dateAdd", @@ -75,50 +76,20 @@ impl From<&[String]> for CalendarFieldsType { } } -/// `AvailableCalendars` lists the currently implemented `CalendarProtocols` -#[derive(Debug, Clone, Copy)] -pub enum AvailableCalendars { - /// The ISO8601 calendar. - Iso, -} - -// NOTE: Should `s` be forced to lowercase or should the user be expected to provide the lowercase. -impl FromStr for AvailableCalendars { - type Err = TemporalError; - fn from_str(s: &str) -> Result { - match s { - "iso8601" => Ok(Self::Iso), - _ => { - Err(TemporalError::range().with_message("CalendarId is not an available Calendar")) - } - } - } -} - -impl AvailableCalendars { - /// Returns the `CalendarProtocol` for the `AvailableCalendar` - #[must_use] - pub fn to_protocol(&self) -> Box { - match self { - Self::Iso => Box::new(IsoCalendar), - } - } -} - /// The `DateLike` objects that can be provided to the `CalendarProtocol`. #[derive(Debug)] -pub enum CalendarDateLike { +pub enum CalendarDateLike { /// Represents a `Date` datelike - Date(Date), + Date(Date), /// Represents a `DateTime` datelike - DateTime(DateTime), + DateTime(DateTime), /// Represents a `YearMonth` datelike - YearMonth(YearMonth), + YearMonth(YearMonth), /// Represents a `MonthDay` datelike - MonthDay(MonthDay), + MonthDay(MonthDay), } -impl CalendarDateLike { +impl CalendarDateLike { /// Retrieves the internal `IsoDate` field. #[inline] #[must_use] @@ -134,441 +105,852 @@ impl CalendarDateLike { // ==== CalendarProtocol trait ==== -/// The `CalendarProtocol`'s Clone supertrait. -pub trait CalendarProtocolClone { - /// Clone's the current `CalendarProtocol` - fn clone_box(&self) -> Box; -} - -impl

CalendarProtocolClone for P -where - P: 'static + CalendarProtocol + Clone, -{ - fn clone_box(&self) -> Box { - Box::new(self.clone()) - } -} - -// TODO: Split further into `CalendarProtocol` and `BuiltinCalendar` to better handle -// fields and mergeFields. /// A trait for implementing a Builtin Calendar's Calendar Protocol in Rust. -pub trait CalendarProtocol: CalendarProtocolClone { +pub trait CalendarProtocol: Clone { /// Creates a `Temporal.PlainDate` object from provided fields. fn date_from_fields( &self, fields: &mut TemporalFields, overflow: ArithmeticOverflow, context: &mut dyn Any, - ) -> TemporalResult; + ) -> TemporalResult>; /// Creates a `Temporal.PlainYearMonth` object from the provided fields. fn year_month_from_fields( &self, fields: &mut TemporalFields, overflow: ArithmeticOverflow, context: &mut dyn Any, - ) -> TemporalResult; + ) -> TemporalResult>; /// Creates a `Temporal.PlainMonthDay` object from the provided fields. fn month_day_from_fields( &self, fields: &mut TemporalFields, overflow: ArithmeticOverflow, context: &mut dyn Any, - ) -> TemporalResult; + ) -> TemporalResult>; /// Returns a `Temporal.PlainDate` based off an added date. fn date_add( &self, - date: &Date, + date: &Date, duration: &Duration, overflow: ArithmeticOverflow, context: &mut dyn Any, - ) -> TemporalResult; + ) -> TemporalResult>; /// Returns a `Temporal.Duration` representing the duration between two dates. fn date_until( &self, - one: &Date, - two: &Date, + one: &Date, + two: &Date, largest_unit: TemporalUnit, context: &mut dyn Any, ) -> TemporalResult; /// Returns the era for a given `temporaldatelike`. fn era( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, - ) -> TemporalResult>>; + ) -> TemporalResult>>; /// Returns the era year for a given `temporaldatelike` fn era_year( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult>; /// Returns the `year` for a given `temporaldatelike` - fn year(&self, date_like: &CalendarDateLike, context: &mut dyn Any) -> TemporalResult; + fn year( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult; /// Returns the `month` for a given `temporaldatelike` - fn month(&self, date_like: &CalendarDateLike, context: &mut dyn Any) -> TemporalResult; + fn month( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult; // Note: Best practice would probably be to switch to a MonthCode enum after extraction. /// Returns the `monthCode` for a given `temporaldatelike` fn month_code( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult>; /// Returns the `day` for a given `temporaldatelike` - fn day(&self, date_like: &CalendarDateLike, context: &mut dyn Any) -> TemporalResult; + fn day(&self, date_like: &CalendarDateLike, context: &mut dyn Any) -> TemporalResult; /// Returns a value representing the day of the week for a date. fn day_of_week( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult; /// Returns a value representing the day of the year for a given calendar. fn day_of_year( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult; /// Returns a value representing the week of the year for a given calendar. fn week_of_year( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult; /// Returns the year of a given week. fn year_of_week( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult; /// Returns the days in a week for a given calendar. fn days_in_week( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult; /// Returns the days in a month for a given calendar. fn days_in_month( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult; /// Returns the days in a year for a given calendar. fn days_in_year( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult; /// Returns the months in a year for a given calendar. fn months_in_year( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult; /// Returns whether a value is within a leap year according to the designated calendar. fn in_leap_year( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult; - /// Resolve the `TemporalFields` for the implemented Calendar - fn resolve_fields( + /// Return the fields for a value. + fn fields(&self, fields: Vec, context: &mut dyn Any) -> TemporalResult>; + /// Merge fields based on the calendar and provided values. + fn merge_fields( &self, - fields: &mut TemporalFields, - r#type: CalendarFieldsType, - ) -> TemporalResult<()>; - /// Return this calendar's a fieldName and whether it is required depending on type (date, day-month). - fn field_descriptors(&self, r#type: CalendarFieldsType) -> Vec<(String, bool)>; - /// Return the fields to ignore for this Calendar based on provided keys. - fn field_keys_to_ignore(&self, additional_keys: Vec) -> Vec; + fields: &TemporalFields, + additional_fields: &TemporalFields, + context: &mut dyn Any, + ) -> TemporalResult; /// Debug name fn identifier(&self, context: &mut dyn Any) -> TemporalResult; } -impl core::fmt::Debug for dyn CalendarProtocol { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!( - f, - "{}", - self.identifier(&mut ()).unwrap_or_default().as_str() - ) - } -} - +// NOTE(nekevss): Builtin could be `Rc`, but doing so may +// have an effect on the pattern matching for `CalendarSlot`'s methods. /// The `[[Calendar]]` field slot of a Temporal Object. #[derive(Debug)] -pub enum CalendarSlot { +pub enum CalendarSlot { /// The calendar identifier string. - Identifier(String), + Builtin(AnyCalendar), /// A `CalendarProtocol` implementation. - Protocol(Box), + Protocol(C), } -impl Clone for CalendarSlot { +impl Clone for CalendarSlot { fn clone(&self) -> Self { match self { - Self::Identifier(s) => Self::Identifier(s.clone()), - Self::Protocol(b) => Self::Protocol(b.clone_box()), + Self::Builtin(any) => { + let clone = match any { + AnyCalendar::Buddhist(c) => AnyCalendar::Buddhist(*c), + AnyCalendar::Chinese(c) => AnyCalendar::Chinese(c.clone()), + AnyCalendar::Coptic(c) => AnyCalendar::Coptic(*c), + AnyCalendar::Dangi(c) => AnyCalendar::Dangi(c.clone()), + AnyCalendar::Ethiopian(c) => AnyCalendar::Ethiopian(*c), + AnyCalendar::Gregorian(c) => AnyCalendar::Gregorian(*c), + AnyCalendar::Hebrew(c) => AnyCalendar::Hebrew(c.clone()), + AnyCalendar::Indian(c) => AnyCalendar::Indian(*c), + AnyCalendar::IslamicCivil(c) => AnyCalendar::IslamicCivil(c.clone()), + AnyCalendar::IslamicObservational(c) => { + AnyCalendar::IslamicObservational(c.clone()) + } + AnyCalendar::IslamicTabular(c) => AnyCalendar::IslamicTabular(c.clone()), + AnyCalendar::IslamicUmmAlQura(c) => AnyCalendar::IslamicUmmAlQura(c.clone()), + AnyCalendar::Iso(c) => AnyCalendar::Iso(*c), + AnyCalendar::Japanese(c) => AnyCalendar::Japanese(c.clone()), + AnyCalendar::JapaneseExtended(c) => AnyCalendar::JapaneseExtended(c.clone()), + AnyCalendar::Persian(c) => AnyCalendar::Persian(*c), + AnyCalendar::Roc(c) => AnyCalendar::Roc(*c), + _ => unimplemented!("There is a calendar that is missing a clone impl."), + }; + Self::Builtin(clone) + } + + Self::Protocol(proto) => CalendarSlot::Protocol(proto.clone()), } } } -impl Clone for Box { - fn clone(&self) -> Self { - self.clone_box() +// `FromStr` essentially serves as a stand in for `IsBuiltinCalendar`. +impl FromStr for CalendarSlot { + type Err = TemporalError; + + fn from_str(s: &str) -> Result { + // NOTE(nekesss): Catch the iso identifier here, as `iso8601` is not a valid ID below. + if s == "iso8601" { + return Ok(CalendarSlot::Builtin(AnyCalendar::Iso(Iso))); + } + + let Some(cal) = AnyCalendarKind::get_for_bcp47_bytes(s.as_bytes()) else { + return Err(TemporalError::range().with_message("Not a builtin calendar.")); + }; + + let any_calendar = AnyCalendar::new(cal); + + Ok(CalendarSlot::Builtin(any_calendar)) } } -impl Default for CalendarSlot { +impl Default for CalendarSlot { fn default() -> Self { - Self::Identifier("iso8601".to_owned()) + Self::Builtin(AnyCalendar::Iso(Iso)) } } -// TODO: Handle `CalendarFields` and `CalendarMergeFields` -impl CalendarSlot { +// ==== Public `CalendarSlot` methods ==== + +impl CalendarSlot { + /// Returns whether the current calendar is `ISO` + pub fn is_iso(&self) -> bool { + matches!(self, CalendarSlot::Builtin(AnyCalendar::Iso(_))) + } +} + +// ==== Abstract `CalendarProtocol` Methods ==== + +// NOTE: Below is functionally the `CalendarProtocol` implementation on `CalendarSlot`. + +impl CalendarSlot { + /// `CalendarDateFromFields` + pub fn date_from_fields( + &self, + fields: &mut TemporalFields, + overflow: ArithmeticOverflow, + context: &mut dyn Any, + ) -> TemporalResult> { + match self { + CalendarSlot::Builtin(AnyCalendar::Iso(_)) => { + // Resolve month and monthCode; + fields.iso_resolve_month()?; + Date::new( + fields.year().unwrap_or(0), + fields.month().unwrap_or(0), + fields.day().unwrap_or(0), + self.clone(), + overflow, + ) + } + CalendarSlot::Builtin(builtin) => { + // NOTE: This might preemptively throw as `ICU4X` does not support constraining. + // Resolve month and monthCode; + let calendar_date = builtin.date_from_codes( + Era::from(fields.era()), + fields.year().unwrap_or(0), + MonthCode(fields.month_code()), + fields.day().unwrap_or(0) as u8, + )?; + let iso = builtin.date_to_iso(&calendar_date); + Date::new( + iso.year().number, + iso.month().ordinal as i32, + iso.day_of_month().0 as i32, + self.clone(), + overflow, + ) + } + CalendarSlot::Protocol(protocol) => { + protocol.date_from_fields(fields, overflow, context) + } + } + } + + /// `CalendarMonthDayFromFields` + pub fn month_day_from_fields( + &self, + fields: &mut TemporalFields, + overflow: ArithmeticOverflow, + context: &mut dyn Any, + ) -> TemporalResult> { + match self { + CalendarSlot::Builtin(AnyCalendar::Iso(_)) => { + fields.iso_resolve_month()?; + MonthDay::new( + fields.month().unwrap_or(0), + fields.day().unwrap_or(0), + self.clone(), + overflow, + ) + } + CalendarSlot::Builtin(_) => { + // TODO: This may get complicated... + // For reference: https://github.com/tc39/proposal-temporal/blob/main/polyfill/lib/calendar.mjs#L1275. + Err(TemporalError::range().with_message("Not yet implemented/supported.")) + } + CalendarSlot::Protocol(protocol) => { + protocol.month_day_from_fields(fields, overflow, context) + } + } + } + + /// `CalendarYearMonthFromFields` + pub fn year_month_from_fields( + &self, + fields: &mut TemporalFields, + overflow: ArithmeticOverflow, + context: &mut dyn Any, + ) -> TemporalResult> { + match self { + CalendarSlot::Builtin(AnyCalendar::Iso(_)) => { + fields.iso_resolve_month()?; + YearMonth::new( + fields.year().unwrap_or(0), + fields.month().unwrap_or(0), + fields.day(), + self.clone(), + overflow, + ) + } + CalendarSlot::Builtin(builtin) => { + // NOTE: This might preemptively throw as `ICU4X` does not support regulating. + let calendar_date = builtin.date_from_codes( + Era::from(fields.era()), + fields.year().unwrap_or(0), + MonthCode(fields.month_code()), + fields.day().unwrap_or(1) as u8, + )?; + let iso = builtin.date_to_iso(&calendar_date); + YearMonth::new( + iso.year().number, + iso.month().ordinal as i32, + Some(iso.day_of_month().0 as i32), + self.clone(), + overflow, + ) + } + CalendarSlot::Protocol(protocol) => { + protocol.year_month_from_fields(fields, overflow, context) + } + } + } + /// `CalendarDateAdd` - /// - /// TODO: More Docs pub fn date_add( &self, - date: &Date, + date: &Date, duration: &Duration, overflow: ArithmeticOverflow, context: &mut dyn Any, - ) -> TemporalResult { + ) -> TemporalResult> { match self { - Self::Identifier(id) => { - let protocol = AvailableCalendars::from_str(id)?.to_protocol(); + CalendarSlot::Builtin(_) => { + Err(TemporalError::range().with_message("Not yet implemented.")) + } + CalendarSlot::Protocol(protocol) => { protocol.date_add(date, duration, overflow, context) } - Self::Protocol(protocol) => protocol.date_add(date, duration, overflow, context), } } /// `CalendarDateUntil` - /// - /// TODO: More Docs pub fn date_until( &self, - one: &Date, - two: &Date, + one: &Date, + two: &Date, largest_unit: TemporalUnit, context: &mut dyn Any, ) -> TemporalResult { match self { - Self::Identifier(id) => { - let protocol = AvailableCalendars::from_str(id)?.to_protocol(); + CalendarSlot::Builtin(_) => { + Err(TemporalError::range().with_message("Not yet implemented.")) + } + CalendarSlot::Protocol(protocol) => { protocol.date_until(one, two, largest_unit, context) } - Self::Protocol(protocol) => protocol.date_until(one, two, largest_unit, context), + } + } + + /// `CalendarEra` + pub fn era( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult>> { + match self { + CalendarSlot::Builtin(AnyCalendar::Iso(_)) => Ok(None), + CalendarSlot::Builtin(builtin) => { + let calendar_date = builtin.date_from_iso(date_like.as_iso_date().as_icu4x()?); + Ok(Some(builtin.year(&calendar_date).era.0)) + } + CalendarSlot::Protocol(protocol) => protocol.era(date_like, context), + } + } + + /// `CalendarEraYear` + pub fn era_year( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult> { + match self { + CalendarSlot::Builtin(AnyCalendar::Iso(_)) => Ok(None), + CalendarSlot::Builtin(builtin) => { + let calendar_date = builtin.date_from_iso(date_like.as_iso_date().as_icu4x()?); + Ok(Some(builtin.year(&calendar_date).number)) + } + CalendarSlot::Protocol(protocol) => protocol.era_year(date_like, context), } } /// `CalendarYear` - /// - /// TODO: More docs. - pub fn year(&self, date_like: &CalendarDateLike, context: &mut dyn Any) -> TemporalResult { + pub fn year( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult { match self { - Self::Identifier(id) => { - let protocol = AvailableCalendars::from_str(id)?.to_protocol(); - protocol.year(date_like, &mut ()) + CalendarSlot::Builtin(AnyCalendar::Iso(_)) => Ok(date_like.as_iso_date().year()), + CalendarSlot::Builtin(builtin) => { + let calendar_date = builtin.date_from_iso(date_like.as_iso_date().as_icu4x()?); + Ok(builtin.year(&calendar_date).number) } - Self::Protocol(protocol) => protocol.year(date_like, context), + CalendarSlot::Protocol(protocol) => protocol.year(date_like, context), } } /// `CalendarMonth` - /// - /// TODO: More docs. - pub fn month(&self, date_like: &CalendarDateLike, context: &mut dyn Any) -> TemporalResult { + pub fn month( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult { match self { - Self::Identifier(id) => { - let protocol = AvailableCalendars::from_str(id)?.to_protocol(); - protocol.month(date_like, &mut ()) + CalendarSlot::Builtin(AnyCalendar::Iso(_)) => Ok(date_like.as_iso_date().month()), + CalendarSlot::Builtin(_) => { + Err(TemporalError::range().with_message("Not yet implemented.")) } - Self::Protocol(protocol) => protocol.month(date_like, context), + CalendarSlot::Protocol(protocol) => protocol.month(date_like, context), } } /// `CalendarMonthCode` - /// - /// TODO: More docs. pub fn month_code( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult> { match self { - Self::Identifier(id) => { - let protocol = AvailableCalendars::from_str(id)?.to_protocol(); - protocol.month_code(date_like, &mut ()) + CalendarSlot::Builtin(AnyCalendar::Iso(_)) => { + Ok(date_like.as_iso_date().as_icu4x()?.month().code.0) + } + CalendarSlot::Builtin(_) => { + Err(TemporalError::range().with_message("Not yet implemented.")) } - Self::Protocol(protocol) => protocol.month_code(date_like, context), + CalendarSlot::Protocol(protocol) => protocol.month_code(date_like, context), } } /// `CalendarDay` - /// - /// TODO: More docs. - pub fn day(&self, date_like: &CalendarDateLike, context: &mut dyn Any) -> TemporalResult { + pub fn day( + &self, + date_like: &CalendarDateLike, + context: &mut dyn Any, + ) -> TemporalResult { match self { - Self::Identifier(id) => { - let protocol = AvailableCalendars::from_str(id)?.to_protocol(); - protocol.day(date_like, &mut ()) + CalendarSlot::Builtin(AnyCalendar::Iso(_)) => Ok(date_like.as_iso_date().day()), + CalendarSlot::Builtin(_) => { + Err(TemporalError::range().with_message("Not yet implemented.")) } - Self::Protocol(protocol) => protocol.day(date_like, context), + CalendarSlot::Protocol(protocol) => protocol.day(date_like, context), } } /// `CalendarDayOfWeek` - /// - /// TODO: More docs. pub fn day_of_week( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult { match self { - Self::Identifier(id) => { - let protocol = AvailableCalendars::from_str(id)?.to_protocol(); - protocol.day_of_week(date_like, &mut ()) + CalendarSlot::Builtin(AnyCalendar::Iso(_)) => { + Ok(date_like.as_iso_date().as_icu4x()?.day_of_week() as u16) + } + CalendarSlot::Builtin(_) => { + Err(TemporalError::range().with_message("Not yet implemented.")) } - Self::Protocol(protocol) => protocol.day_of_week(date_like, context), + CalendarSlot::Protocol(protocol) => protocol.day_of_week(date_like, context), } } /// `CalendarDayOfYear` - /// - /// TODO: More docs. pub fn day_of_year( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult { match self { - Self::Identifier(id) => { - let protocol = AvailableCalendars::from_str(id)?.to_protocol(); - protocol.day_of_year(date_like, &mut ()) + CalendarSlot::Builtin(AnyCalendar::Iso(_)) => Ok(date_like + .as_iso_date() + .as_icu4x()? + .day_of_year_info() + .day_of_year), + CalendarSlot::Builtin(_) => { + Err(TemporalError::range().with_message("Not yet implemented.")) } - Self::Protocol(protocol) => protocol.day_of_year(date_like, context), + CalendarSlot::Protocol(protocol) => protocol.day_of_year(date_like, context), } } /// `CalendarWeekOfYear` - /// - /// TODO: More docs. pub fn week_of_year( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult { match self { - Self::Identifier(id) => { - let protocol = AvailableCalendars::from_str(id)?.to_protocol(); - protocol.week_of_year(date_like, &mut ()) + CalendarSlot::Builtin(AnyCalendar::Iso(_)) => { + 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) + } + CalendarSlot::Builtin(_) => { + Err(TemporalError::range().with_message("Not yet implemented.")) } - Self::Protocol(protocol) => protocol.week_of_year(date_like, context), + CalendarSlot::Protocol(protocol) => protocol.week_of_year(date_like, context), } } /// `CalendarYearOfWeek` - /// - /// TODO: More docs. pub fn year_of_week( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult { match self { - Self::Identifier(id) => { - let protocol = AvailableCalendars::from_str(id)?.to_protocol(); - protocol.year_of_week(date_like, &mut ()) + CalendarSlot::Builtin(AnyCalendar::Iso(_)) => { + 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()))?; + + match week_of.unit { + RelativeUnit::Previous => Ok(date.year().number - 1), + RelativeUnit::Current => Ok(date.year().number), + RelativeUnit::Next => Ok(date.year().number + 1), + } } - Self::Protocol(protocol) => protocol.year_of_week(date_like, context), + CalendarSlot::Builtin(_) => { + Err(TemporalError::range().with_message("Not yet implemented.")) + } + CalendarSlot::Protocol(protocol) => protocol.year_of_week(date_like, context), } } /// `CalendarDaysInWeek` - /// - /// TODO: More docs. pub fn days_in_week( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult { match self { - Self::Identifier(id) => { - let protocol = AvailableCalendars::from_str(id)?.to_protocol(); - protocol.days_in_week(date_like, &mut ()) + CalendarSlot::Builtin(AnyCalendar::Iso(_)) => Ok(7), + CalendarSlot::Builtin(_) => { + Err(TemporalError::range().with_message("Not yet implemented.")) } - Self::Protocol(protocol) => protocol.days_in_week(date_like, context), + CalendarSlot::Protocol(protocol) => protocol.days_in_week(date_like, context), } } /// `CalendarDaysInMonth` - /// - /// TODO: More docs. pub fn days_in_month( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult { match self { - Self::Identifier(id) => { - let protocol = AvailableCalendars::from_str(id)?.to_protocol(); - protocol.days_in_month(date_like, &mut ()) + CalendarSlot::Builtin(AnyCalendar::Iso(_)) => { + // NOTE: Cast shouldn't fail in this instance. + Ok(date_like.as_iso_date().as_icu4x()?.day_of_month().0 as u16) + } + CalendarSlot::Builtin(_) => { + Err(TemporalError::range().with_message("Not yet implemented.")) } - Self::Protocol(protocol) => protocol.days_in_month(date_like, context), + CalendarSlot::Protocol(protocol) => protocol.days_in_month(date_like, context), } } /// `CalendarDaysInYear` - /// - /// TODO: More docs. pub fn days_in_year( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult { match self { - Self::Identifier(id) => { - let protocol = AvailableCalendars::from_str(id)?.to_protocol(); - protocol.days_in_year(date_like, &mut ()) + CalendarSlot::Builtin(AnyCalendar::Iso(_)) => { + Ok(date_like.as_iso_date().as_icu4x()?.days_in_year()) } - Self::Protocol(protocol) => protocol.days_in_year(date_like, context), + CalendarSlot::Builtin(_) => { + Err(TemporalError::range().with_message("Not yet implemented.")) + } + CalendarSlot::Protocol(protocol) => protocol.days_in_year(date_like, context), } } /// `CalendarMonthsInYear` - /// - /// TODO: More docs. pub fn months_in_year( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult { match self { - Self::Identifier(id) => { - let protocol = AvailableCalendars::from_str(id)?.to_protocol(); - protocol.months_in_year(date_like, &mut ()) + CalendarSlot::Builtin(AnyCalendar::Iso(_)) => Ok(12), + CalendarSlot::Builtin(_) => { + Err(TemporalError::range().with_message("Not yet implemented.")) } - Self::Protocol(protocol) => protocol.months_in_year(date_like, context), + CalendarSlot::Protocol(protocol) => protocol.months_in_year(date_like, context), } } /// `CalendarInLeapYear` - /// - /// TODO: More docs. pub fn in_leap_year( &self, - date_like: &CalendarDateLike, + date_like: &CalendarDateLike, context: &mut dyn Any, ) -> TemporalResult { match self { - Self::Identifier(id) => { - let protocol = AvailableCalendars::from_str(id)?.to_protocol(); - protocol.in_leap_year(date_like, &mut ()) + CalendarSlot::Builtin(AnyCalendar::Iso(_)) => { + Ok(date_like.as_iso_date().as_icu4x()?.is_in_leap_year()) + } + CalendarSlot::Builtin(_) => { + Err(TemporalError::range().with_message("Not yet implemented.")) + } + CalendarSlot::Protocol(protocol) => protocol.in_leap_year(date_like, context), + } + } + + /// `CalendarFields` + pub fn fields( + &self, + fields: Vec, + context: &mut dyn Any, + ) -> TemporalResult> { + match self { + CalendarSlot::Builtin(AnyCalendar::Iso(_)) => Ok(fields), + CalendarSlot::Builtin(_) => { + Err(TemporalError::range().with_message("Not yet implemented.")) + } + CalendarSlot::Protocol(protocol) => protocol.fields(fields, context), + } + } + + /// `CalendarMergeFields` + pub fn merge_fields( + &self, + fields: &TemporalFields, + additional_fields: &TemporalFields, + context: &mut dyn Any, + ) -> TemporalResult { + match self { + CalendarSlot::Builtin(_) => fields.merge_fields(additional_fields, self), + CalendarSlot::Protocol(protocol) => { + protocol.merge_fields(fields, additional_fields, context) } - Self::Protocol(protocol) => protocol.in_leap_year(date_like, context), } } + + /// Returns the identifier of this calendar slot. + pub fn identifier(&self, context: &mut dyn Any) -> TemporalResult { + match self { + CalendarSlot::Builtin(AnyCalendar::Iso(_)) => Ok(String::from("iso8601")), + CalendarSlot::Builtin(builtin) => Ok(String::from(builtin.debug_name())), + CalendarSlot::Protocol(protocol) => protocol.identifier(context), + } + } +} + +impl CalendarSlot { + /// Returns the designated field descriptors for builtin calendars. + pub fn field_descriptors( + &self, + _fields_type: CalendarFieldsType, + ) -> TemporalResult> { + // NOTE(nekevss): Can be called on a custom. + if let CalendarSlot::Protocol(_) = self { + return Ok(Vec::default()); + } + + // TODO: Research and implement the appropriate descriptors for all `BuiltinCalendars.` + Err(TemporalError::range().with_message("FieldDescriptors is not yet implemented.")) + } + + /// Provides field keys to be ignored depending on the calendar. + pub fn field_keys_to_ignore(&self, _keys: &[String]) -> TemporalResult> { + // TODO: Research and implement the appropriate KeysToIgnore for all `BuiltinCalendars.` + Err(TemporalError::range().with_message("FieldKeysToIgnore is not yet implemented.")) + } + + /// `CalendarResolveFields` + pub fn resolve_fields( + &self, + _fields: &mut TemporalFields, + _typ: CalendarFieldsType, + ) -> TemporalResult<()> { + // TODO: Research and implement the appropriate ResolveFields for all `BuiltinCalendars.` + Err(TemporalError::range().with_message("CalendarResolveFields is not yet implemented.")) + } +} + +/// An empty `CalendarProtocol` implementation on `()`. +/// +/// # Panics +/// +/// Attempting to use this empty calendar implementation as a valid calendar is an error and will cause a panic. +impl CalendarProtocol for () { + fn date_from_fields( + &self, + _: &mut TemporalFields, + _: ArithmeticOverflow, + _: &mut dyn Any, + ) -> TemporalResult> { + unreachable!(); + } + + fn month_day_from_fields( + &self, + _: &mut TemporalFields, + _: ArithmeticOverflow, + _: &mut dyn Any, + ) -> TemporalResult> { + unreachable!(); + } + + fn year_month_from_fields( + &self, + _: &mut TemporalFields, + _: ArithmeticOverflow, + _: &mut dyn Any, + ) -> TemporalResult> { + unreachable!() + } + + fn date_add( + &self, + _: &Date, + _: &Duration, + _: ArithmeticOverflow, + _: &mut dyn Any, + ) -> TemporalResult> { + unreachable!(); + } + + fn date_until( + &self, + _: &Date<()>, + _: &Date<()>, + _: TemporalUnit, + _: &mut dyn Any, + ) -> TemporalResult { + unreachable!(); + } + + fn era( + &self, + _: &CalendarDateLike<()>, + _: &mut dyn Any, + ) -> TemporalResult>> { + unreachable!(); + } + + fn era_year(&self, _: &CalendarDateLike<()>, _: &mut dyn Any) -> TemporalResult> { + unreachable!(); + } + + fn year(&self, _: &CalendarDateLike<()>, _: &mut dyn Any) -> TemporalResult { + unreachable!(); + } + + fn month(&self, _: &CalendarDateLike<()>, _: &mut dyn Any) -> TemporalResult { + unreachable!(); + } + + fn month_code( + &self, + _: &CalendarDateLike<()>, + _: &mut dyn Any, + ) -> TemporalResult> { + unreachable!(); + } + + fn day(&self, _: &CalendarDateLike<()>, _: &mut dyn Any) -> TemporalResult { + unreachable!(); + } + + fn day_of_week(&self, _: &CalendarDateLike<()>, _: &mut dyn Any) -> TemporalResult { + unreachable!(); + } + + fn day_of_year(&self, _: &CalendarDateLike<()>, _: &mut dyn Any) -> TemporalResult { + unreachable!(); + } + + fn week_of_year(&self, _: &CalendarDateLike<()>, _: &mut dyn Any) -> TemporalResult { + unreachable!(); + } + + fn year_of_week(&self, _: &CalendarDateLike<()>, _: &mut dyn Any) -> TemporalResult { + unreachable!(); + } + + fn days_in_week(&self, _: &CalendarDateLike<()>, _: &mut dyn Any) -> TemporalResult { + unreachable!(); + } + + fn days_in_month(&self, _: &CalendarDateLike<()>, _: &mut dyn Any) -> TemporalResult { + unreachable!(); + } + + fn days_in_year(&self, _: &CalendarDateLike<()>, _: &mut dyn Any) -> TemporalResult { + unreachable!(); + } + + fn months_in_year(&self, _: &CalendarDateLike<()>, _: &mut dyn Any) -> TemporalResult { + unreachable!(); + } + + fn in_leap_year(&self, _: &CalendarDateLike<()>, _: &mut dyn Any) -> TemporalResult { + unreachable!(); + } + + fn fields(&self, _: Vec, _: &mut dyn Any) -> TemporalResult> { + unreachable!(); + } + + fn merge_fields( + &self, + _: &TemporalFields, + _: &TemporalFields, + _: &mut dyn Any, + ) -> TemporalResult { + unreachable!(); + } + + fn identifier(&self, _: &mut dyn Any) -> TemporalResult { + unreachable!(); + } } diff --git a/core/temporal/src/components/calendar/iso.rs b/core/temporal/src/components/calendar/iso.rs deleted file mode 100644 index 51e232d940..0000000000 --- a/core/temporal/src/components/calendar/iso.rs +++ /dev/null @@ -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 { - // 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 { - // 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 { - // 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 { - // 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 { - // 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>> { - // Returns undefined on iso8601. - Ok(None) - } - - /// `Temporal.Calendar.prototype.eraYear( dateLike )` for iso8601 calendar. - fn era_year(&self, _: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult> { - // Returns undefined on iso8601. - Ok(None) - } - - /// Returns the `year` for the `Iso` calendar. - fn year(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult { - Ok(date_like.as_iso_date().year()) - } - - /// Returns the `month` for the `Iso` calendar. - fn month(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult { - 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> { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - Ok(7) - } - - /// Returns the `daysInMonth` value for the `Iso` calendar. - fn days_in_month(&self, date_like: &CalendarDateLike, _: &mut dyn Any) -> TemporalResult { - 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 { - 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 { - 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 { - // `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) -> Vec { - 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 { - Ok("iso8601".to_string()) - } -} diff --git a/core/temporal/src/components/date.rs b/core/temporal/src/components/date.rs index e31eac331b..13c800ba98 100644 --- a/core/temporal/src/components/date.rs +++ b/core/temporal/src/components/date.rs @@ -2,7 +2,7 @@ use crate::{ components::{ - calendar::{AvailableCalendars, CalendarSlot}, + calendar::{CalendarProtocol, CalendarSlot}, duration::DateDuration, DateTime, Duration, }, @@ -15,18 +15,18 @@ use std::{any::Any, str::FromStr}; /// The native Rust implementation of `Temporal.PlainDate`. #[derive(Debug, Default, Clone)] -pub struct Date { +pub struct Date { iso: IsoDate, - calendar: CalendarSlot, + calendar: CalendarSlot, } // ==== Private API ==== -impl Date { +impl Date { /// Create a new `Date` with the date values and calendar slot. #[inline] #[must_use] - pub(crate) fn new_unchecked(iso: IsoDate, calendar: CalendarSlot) -> Self { + pub(crate) fn new_unchecked(iso: IsoDate, calendar: CalendarSlot) -> Self { Self { iso, calendar } } @@ -46,13 +46,13 @@ impl Date { // ==== Public API ==== -impl Date { +impl Date { /// Creates a new `Date` while checking for validity. pub fn new( year: i32, month: i32, day: i32, - calendar: CalendarSlot, + calendar: CalendarSlot, overflow: ArithmeticOverflow, ) -> TemporalResult { let iso = IsoDate::new(year, month, day, overflow)?; @@ -61,7 +61,7 @@ impl Date { #[must_use] /// Creates a `Date` from a `DateTime`. - pub fn from_datetime(dt: &DateTime) -> Self { + pub fn from_datetime(dt: &DateTime) -> Self { Self { iso: dt.iso_date(), calendar: dt.calendar().clone(), @@ -99,7 +99,7 @@ impl Date { #[inline] #[must_use] /// Returns a reference to this `Date`'s calendar slot. - pub fn calendar(&self) -> &CalendarSlot { + pub fn calendar(&self) -> &CalendarSlot { &self.calendar } @@ -121,7 +121,7 @@ impl Date { } } -impl IsoDateSlots for Date { +impl IsoDateSlots for Date { /// Returns the structs `IsoDate` fn iso_date(&self) -> IsoDate { self.iso @@ -130,7 +130,7 @@ impl IsoDateSlots for Date { // ==== Context based API ==== -impl Date { +impl Date { /// Returns the date after adding the given duration to date with a provided context. /// /// Temporal Equivalent: 3.5.13 `AddDate ( calendar, plainDate, duration [ , options [ , dateAdd ] ] )` @@ -224,14 +224,13 @@ impl Date { // ==== Trait impls ==== -impl FromStr for Date { +impl FromStr for Date { type Err = TemporalError; fn from_str(s: &str) -> Result { let parse_record = parse_date_time(s)?; let calendar = parse_record.calendar.unwrap_or("iso8601".to_owned()); - let _ = AvailableCalendars::from_str(calendar.to_ascii_lowercase().as_str())?; let date = IsoDate::new( parse_record.date.year, @@ -242,7 +241,7 @@ impl FromStr for Date { Ok(Self::new_unchecked( date, - CalendarSlot::Identifier(calendar), + CalendarSlot::from_str(&calendar)?, )) } } diff --git a/core/temporal/src/components/datetime.rs b/core/temporal/src/components/datetime.rs index 59703d2ff8..e6417aeaf7 100644 --- a/core/temporal/src/components/datetime.rs +++ b/core/temporal/src/components/datetime.rs @@ -3,7 +3,10 @@ use std::str::FromStr; use crate::{ - components::{calendar::CalendarSlot, Instant}, + components::{ + calendar::{CalendarProtocol, CalendarSlot}, + Instant, + }, iso::{IsoDate, IsoDateSlots, IsoDateTime, IsoTime}, options::ArithmeticOverflow, parser::parse_date_time, @@ -12,18 +15,18 @@ use crate::{ /// The native Rust implementation of `Temporal.PlainDateTime` #[derive(Debug, Default, Clone)] -pub struct DateTime { +pub struct DateTime { iso: IsoDateTime, - calendar: CalendarSlot, + calendar: CalendarSlot, } // ==== Private DateTime API ==== -impl DateTime { +impl DateTime { /// Creates a new unchecked `DateTime`. #[inline] #[must_use] - pub(crate) fn new_unchecked(date: IsoDate, time: IsoTime, calendar: CalendarSlot) -> Self { + pub(crate) fn new_unchecked(date: IsoDate, time: IsoTime, calendar: CalendarSlot) -> Self { Self { iso: IsoDateTime::new_unchecked(date, time), calendar, @@ -42,7 +45,7 @@ impl DateTime { pub(crate) fn from_instant( instant: &Instant, offset: f64, - calendar: CalendarSlot, + calendar: CalendarSlot, ) -> TemporalResult { let iso = IsoDateTime::from_epoch_nanos(&instant.nanos, offset)?; Ok(Self { iso, calendar }) @@ -51,7 +54,7 @@ impl DateTime { // ==== Public DateTime API ==== -impl DateTime { +impl DateTime { /// Creates a new validated `DateTime`. #[inline] #[allow(clippy::too_many_arguments)] @@ -65,7 +68,7 @@ impl DateTime { millisecond: i32, microsecond: i32, nanosecond: i32, - calendar: CalendarSlot, + calendar: CalendarSlot, ) -> TemporalResult { let iso_date = IsoDate::new(year, month, day, ArithmeticOverflow::Reject)?; let iso_time = IsoTime::new( @@ -145,14 +148,14 @@ impl DateTime { /// Returns the Calendar value. #[inline] #[must_use] - pub fn calendar(&self) -> &CalendarSlot { + pub fn calendar(&self) -> &CalendarSlot { &self.calendar } } // ==== Trait impls ==== -impl FromStr for DateTime { +impl FromStr for DateTime { type Err = TemporalError; fn from_str(s: &str) -> Result { @@ -181,7 +184,7 @@ impl FromStr for DateTime { Ok(Self::new_unchecked( date, time, - CalendarSlot::Identifier(calendar), + CalendarSlot::from_str(&calendar)?, )) } } diff --git a/core/temporal/src/components/duration.rs b/core/temporal/src/components/duration.rs index abdaf17cde..2f8198d2a3 100644 --- a/core/temporal/src/components/duration.rs +++ b/core/temporal/src/components/duration.rs @@ -8,6 +8,8 @@ use crate::{ }; use std::{any::Any, str::FromStr}; +use super::calendar::CalendarProtocol; + // ==== `DateDuration` ==== /// `DateDuration` represents the [date duration record][spec] of the `Duration.` @@ -697,10 +699,10 @@ impl Duration { /// 7.5.21 `UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit, plainRelativeTo )` #[allow(dead_code)] - pub(crate) fn unbalance_duration_relative( + pub(crate) fn unbalance_duration_relative( &self, largest_unit: TemporalUnit, - plain_relative_to: Option<&Date>, + plain_relative_to: Option<&Date>, context: &mut dyn Any, ) -> TemporalResult { // 1. Let allZero be false. @@ -918,10 +920,10 @@ impl Duration { // TODO: Move to DateDuration /// `BalanceDateDurationRelative` #[allow(unused)] - pub fn balance_date_duration_relative( + pub fn balance_date_duration_relative( &self, largest_unit: TemporalUnit, - plain_relative_to: Option<&Date>, + plain_relative_to: Option<&Date>, context: &mut dyn Any, ) -> TemporalResult { let mut result = self.date; @@ -1151,13 +1153,18 @@ impl Duration { /// Abstract Operation 7.5.26 `RoundDuration ( years, months, weeks, days, hours, minutes, /// seconds, milliseconds, microseconds, nanoseconds, increment, unit, /// roundingMode [ , plainRelativeTo [, zonedRelativeTo [, precalculatedDateTime]]] )` - pub fn round_duration( + #[allow(clippy::type_complexity)] + pub fn round_duration( &self, unbalance_date_duration: DateDuration, increment: f64, unit: TemporalUnit, rounding_mode: TemporalRoundingMode, - relative_targets: (Option<&Date>, Option<&ZonedDateTime>, Option<&DateTime>), + relative_targets: ( + Option<&Date>, + Option<&ZonedDateTime>, + Option<&DateTime>, + ), context: &mut dyn Any, ) -> TemporalResult<(Self, f64)> { let mut result = Duration::new_unchecked(unbalance_date_duration, self.time); diff --git a/core/temporal/src/components/month_day.rs b/core/temporal/src/components/month_day.rs index 7c3b261dc1..0253c8f4dd 100644 --- a/core/temporal/src/components/month_day.rs +++ b/core/temporal/src/components/month_day.rs @@ -9,18 +9,20 @@ use crate::{ TemporalError, TemporalResult, }; +use super::calendar::CalendarProtocol; + /// The native Rust implementation of `Temporal.PlainMonthDay` #[derive(Debug, Default, Clone)] -pub struct MonthDay { +pub struct MonthDay { iso: IsoDate, - calendar: CalendarSlot, + calendar: CalendarSlot, } -impl MonthDay { +impl MonthDay { /// Creates a new unchecked `MonthDay` #[inline] #[must_use] - pub(crate) fn new_unchecked(iso: IsoDate, calendar: CalendarSlot) -> Self { + pub(crate) fn new_unchecked(iso: IsoDate, calendar: CalendarSlot) -> Self { Self { iso, calendar } } @@ -29,7 +31,7 @@ impl MonthDay { pub fn new( month: i32, day: i32, - calendar: CalendarSlot, + calendar: CalendarSlot, overflow: ArithmeticOverflow, ) -> TemporalResult { let iso = IsoDate::new(1972, month, day, overflow)?; @@ -53,12 +55,12 @@ impl MonthDay { /// Returns a reference to `MonthDay`'s `CalendarSlot` #[inline] #[must_use] - pub fn calendar(&self) -> &CalendarSlot { + pub fn calendar(&self) -> &CalendarSlot { &self.calendar } } -impl IsoDateSlots for MonthDay { +impl IsoDateSlots for MonthDay { #[inline] /// Returns this structs `IsoDate`. fn iso_date(&self) -> IsoDate { @@ -66,7 +68,7 @@ impl IsoDateSlots for MonthDay { } } -impl FromStr for MonthDay { +impl FromStr for MonthDay { type Err = TemporalError; fn from_str(s: &str) -> Result { @@ -77,7 +79,7 @@ impl FromStr for MonthDay { Self::new( record.date.month, record.date.day, - CalendarSlot::Identifier(calendar), + CalendarSlot::from_str(&calendar)?, ArithmeticOverflow::Reject, ) } diff --git a/core/temporal/src/components/tz.rs b/core/temporal/src/components/tz.rs index 77c256f659..4025e83493 100644 --- a/core/temporal/src/components/tz.rs +++ b/core/temporal/src/components/tz.rs @@ -10,6 +10,8 @@ use crate::{ TemporalError, TemporalResult, }; +use super::calendar::CalendarProtocol; + /// Any object that implements the `TzProtocol` must implement the below methods/properties. pub const TIME_ZONE_PROPERTIES: [&str; 3] = ["getOffsetNanosecondsFor", "getPossibleInstantsFor", "id"]; @@ -74,12 +76,12 @@ impl Clone for TimeZoneSlot { } impl TimeZoneSlot { - pub(crate) fn get_datetime_for( + pub(crate) fn get_datetime_for( &self, instant: &Instant, - calendar: &CalendarSlot, + calendar: &CalendarSlot, context: &mut dyn Any, - ) -> TemporalResult { + ) -> TemporalResult> { let nanos = self.get_offset_nanos_for(context)?; DateTime::from_instant(instant, nanos.to_f64().unwrap_or(0.0), calendar.clone()) } diff --git a/core/temporal/src/components/year_month.rs b/core/temporal/src/components/year_month.rs index be9b912a7f..a1a399ce97 100644 --- a/core/temporal/src/components/year_month.rs +++ b/core/temporal/src/components/year_month.rs @@ -9,18 +9,20 @@ use crate::{ TemporalError, TemporalResult, }; +use super::calendar::CalendarProtocol; + /// The native Rust implementation of `Temporal.YearMonth`. #[derive(Debug, Default, Clone)] -pub struct YearMonth { +pub struct YearMonth { iso: IsoDate, - calendar: CalendarSlot, + calendar: CalendarSlot, } -impl YearMonth { +impl YearMonth { /// Creates an unvalidated `YearMonth`. #[inline] #[must_use] - pub(crate) fn new_unchecked(iso: IsoDate, calendar: CalendarSlot) -> Self { + pub(crate) fn new_unchecked(iso: IsoDate, calendar: CalendarSlot) -> Self { Self { iso, calendar } } @@ -30,7 +32,7 @@ impl YearMonth { year: i32, month: i32, reference_day: Option, - calendar: CalendarSlot, + calendar: CalendarSlot, overflow: ArithmeticOverflow, ) -> TemporalResult { let day = reference_day.unwrap_or(1); @@ -55,12 +57,12 @@ impl YearMonth { #[inline] #[must_use] /// Returns a reference to `YearMonth`'s `CalendarSlot` - pub fn calendar(&self) -> &CalendarSlot { + pub fn calendar(&self) -> &CalendarSlot { &self.calendar } } -impl IsoDateSlots for YearMonth { +impl IsoDateSlots for YearMonth { #[inline] /// Returns this `YearMonth`'s `IsoDate` fn iso_date(&self) -> IsoDate { @@ -68,7 +70,7 @@ impl IsoDateSlots for YearMonth { } } -impl FromStr for YearMonth { +impl FromStr for YearMonth { type Err = TemporalError; fn from_str(s: &str) -> Result { @@ -80,7 +82,7 @@ impl FromStr for YearMonth { record.date.year, record.date.month, None, - CalendarSlot::Identifier(calendar), + CalendarSlot::from_str(&calendar)?, ArithmeticOverflow::Reject, ) } diff --git a/core/temporal/src/components/zoneddatetime.rs b/core/temporal/src/components/zoneddatetime.rs index cea711f799..b766e09810 100644 --- a/core/temporal/src/components/zoneddatetime.rs +++ b/core/temporal/src/components/zoneddatetime.rs @@ -5,7 +5,7 @@ use tinystr::TinyStr4; use crate::{ components::{ - calendar::{CalendarDateLike, CalendarSlot}, + calendar::{CalendarDateLike, CalendarProtocol, CalendarSlot}, tz::TimeZoneSlot, Instant, }, @@ -16,21 +16,21 @@ use core::any::Any; /// The native Rust implementation of `Temporal.ZonedDateTime`. #[derive(Debug, Clone)] -pub struct ZonedDateTime { +pub struct ZonedDateTime { instant: Instant, - calendar: CalendarSlot, + calendar: CalendarSlot, tz: TimeZoneSlot, } // ==== Private API ==== -impl ZonedDateTime { +impl ZonedDateTime { /// Creates a `ZonedDateTime` without validating the input. #[inline] #[must_use] pub(crate) fn new_unchecked( instant: Instant, - calendar: CalendarSlot, + calendar: CalendarSlot, tz: TimeZoneSlot, ) -> Self { Self { @@ -43,10 +43,10 @@ impl ZonedDateTime { // ==== Public API ==== -impl ZonedDateTime { +impl ZonedDateTime { /// Creates a new valid `ZonedDateTime`. #[inline] - pub fn new(nanos: BigInt, calendar: CalendarSlot, tz: TimeZoneSlot) -> TemporalResult { + pub fn new(nanos: BigInt, calendar: CalendarSlot, tz: TimeZoneSlot) -> TemporalResult { let instant = Instant::new(nanos)?; Ok(Self::new_unchecked(instant, calendar, tz)) } @@ -54,9 +54,8 @@ impl ZonedDateTime { /// Returns the `ZonedDateTime`'s Calendar identifier. #[inline] #[must_use] - pub fn calendar_id(&self) -> String { - // TODO: Implement Identifier method on `CalendarSlot` - String::from("Not yet implemented.") + pub fn calendar(&self) -> &CalendarSlot { + &self.calendar } /// Returns the `epochSeconds` value of this `ZonedDateTime`. @@ -86,7 +85,7 @@ impl ZonedDateTime { // ==== Context based API ==== -impl ZonedDateTime { +impl ZonedDateTime { /// Returns the `year` value for this `ZonedDateTime`. #[inline] pub fn contextual_year(&self, context: &mut dyn Any) -> TemporalResult { @@ -226,6 +225,8 @@ impl ZonedDateTime { #[cfg(test)] mod tests { + use std::str::FromStr; + use crate::components::tz::TimeZone; use num_bigint::BigInt; @@ -235,9 +236,9 @@ mod tests { fn basic_zdt_test() { let nov_30_2023_utc = BigInt::from(1_701_308_952_000_000_000i64); - let zdt = ZonedDateTime::new( + let zdt = ZonedDateTime::<()>::new( nov_30_2023_utc.clone(), - CalendarSlot::Identifier("iso8601".to_owned()), + CalendarSlot::from_str("iso8601").unwrap(), TimeZoneSlot::Tz(TimeZone { iana: None, offset: Some(0), @@ -252,9 +253,9 @@ mod tests { assert_eq!(zdt.minute().unwrap(), 49); assert_eq!(zdt.second().unwrap(), 12); - let zdt_minus_five = ZonedDateTime::new( + let zdt_minus_five = ZonedDateTime::<()>::new( nov_30_2023_utc, - CalendarSlot::Identifier("iso8601".to_owned()), + CalendarSlot::from_str("iso8601").unwrap(), TimeZoneSlot::Tz(TimeZone { iana: None, offset: Some(-300), diff --git a/core/temporal/src/error.rs b/core/temporal/src/error.rs index 414871b107..6aee99a8d1 100644 --- a/core/temporal/src/error.rs +++ b/core/temporal/src/error.rs @@ -2,6 +2,8 @@ use core::fmt; +use icu_calendar::CalendarError; + /// `TemporalError`'s error type. #[derive(Debug, Default, Clone, Copy)] pub enum ErrorKind { @@ -111,3 +113,9 @@ impl fmt::Display for TemporalError { Ok(()) } } + +impl From for TemporalError { + fn from(value: CalendarError) -> Self { + TemporalError::general(value.to_string()) + } +} diff --git a/core/temporal/src/fields.rs b/core/temporal/src/fields.rs index e4308c6298..a02047a5dc 100644 --- a/core/temporal/src/fields.rs +++ b/core/temporal/src/fields.rs @@ -1,8 +1,12 @@ //! This module implements a native Rust `TemporalField` and components. -use std::str::FromStr; +use std::{fmt, str::FromStr}; -use crate::{error::TemporalError, TemporalResult}; +use crate::{ + components::calendar::{CalendarProtocol, CalendarSlot}, + error::TemporalError, + TemporalResult, +}; use bitflags::bitflags; // use rustc_hash::FxHashSet; @@ -56,6 +60,12 @@ pub enum FieldValue { String(String), } +impl From for FieldValue { + fn from(value: i32) -> Self { + FieldValue::Integer(value) + } +} + /// The Conversion type of a field. #[derive(Debug, Clone, Copy)] pub enum FieldConversion { @@ -149,6 +159,10 @@ impl Default for TemporalFields { } impl TemporalFields { + pub(crate) fn era(&self) -> TinyAsciiStr<16> { + self.era.unwrap_or("default".parse().expect("less than 8")) + } + pub(crate) const fn year(&self) -> Option { self.year } @@ -157,6 +171,12 @@ impl TemporalFields { self.month } + pub(crate) fn month_code(&self) -> TinyAsciiStr<4> { + // Passing along an invalid MonthCode to ICU...might be better to figure out a different approach...TBD. + self.month_code + .unwrap_or("M00".parse().expect("less than 4")) + } + pub(crate) const fn day(&self) -> Option { self.day } @@ -213,6 +233,53 @@ impl TemporalFields { Ok(()) } + /// Retrieves a field value if set, else None. + pub fn get(&self, field: &str) -> Option { + if !self.is_set_field(field) { + return None; + } + match field { + "year" => self.year.map(FieldValue::Integer), + "month" => self.month.map(FieldValue::Integer), + "monthCode" => self.month_code.map(|s| FieldValue::String(s.to_string())), + "day" => self.day.map(FieldValue::from), + "hour" => Some(FieldValue::Integer(self.hour)), + "minute" => Some(FieldValue::Integer(self.minute)), + "second" => Some(FieldValue::Integer(self.second)), + "millisecond" => Some(FieldValue::Integer(self.millisecond)), + "microsecond" => Some(FieldValue::Integer(self.microsecond)), + "nanosecond" => Some(FieldValue::Integer(self.nanosecond)), + "offset" => self.offset.as_ref().map(|s| FieldValue::String(s.clone())), + "era" => self.era.map(|s| FieldValue::String(s.to_string())), + "eraYear" => self.era_year.map(FieldValue::Integer), + "timeZone" => self + .time_zone + .as_ref() + .map(|s| FieldValue::String(s.clone())), + _ => unreachable!(), + } + } + + fn is_set_field(&self, field: &str) -> bool { + match field { + "year" => self.bit_map.contains(FieldMap::YEAR), + "month" => self.bit_map.contains(FieldMap::MONTH), + "monthCode" => self.bit_map.contains(FieldMap::MONTH_CODE), + "day" => self.bit_map.contains(FieldMap::DAY), + "hour" => self.bit_map.contains(FieldMap::HOUR), + "minute" => self.bit_map.contains(FieldMap::MINUTE), + "second" => self.bit_map.contains(FieldMap::SECOND), + "millisecond" => self.bit_map.contains(FieldMap::MILLISECOND), + "microsecond" => self.bit_map.contains(FieldMap::MICROSECOND), + "nanosecond" => self.bit_map.contains(FieldMap::NANOSECOND), + "offset" => self.bit_map.contains(FieldMap::OFFSET), + "era" => self.bit_map.contains(FieldMap::ERA), + "eraYear" => self.bit_map.contains(FieldMap::ERA_YEAR), + "timeZone" => self.bit_map.contains(FieldMap::TIME_ZONE), + _ => unreachable!(), + } + } + #[inline] fn set_year(&mut self, value: &FieldValue) -> TemporalResult<()> { let FieldValue::Integer(y) = value else { @@ -358,78 +425,28 @@ impl TemporalFields { } } -// TODO: optimize into iter. impl TemporalFields { /// Returns a vector filled with the key-value pairs marked as active. + #[must_use] pub fn active_kvs(&self) -> Vec<(String, FieldValue)> { - let mut result = Vec::default(); - - for field in self.bit_map.iter() { - match field { - FieldMap::YEAR => result.push(( - "year".to_owned(), - self.year.map_or(FieldValue::Undefined, FieldValue::Integer), - )), - FieldMap::MONTH => result.push(( - "month".to_owned(), - self.month - .map_or(FieldValue::Undefined, FieldValue::Integer), - )), - FieldMap::MONTH_CODE => result.push(( - "monthCode".to_owned(), - self.month_code - .map_or(FieldValue::Undefined, |s| FieldValue::String(s.to_string())), - )), - FieldMap::DAY => result.push(( - "day".to_owned(), - self.day.map_or(FieldValue::Undefined, FieldValue::Integer), - )), - FieldMap::HOUR => result.push(("hour".to_owned(), FieldValue::Integer(self.hour))), - FieldMap::MINUTE => { - result.push(("minute".to_owned(), FieldValue::Integer(self.minute))); - } - FieldMap::SECOND => { - result.push(("second".to_owned(), FieldValue::Integer(self.second))); - } - FieldMap::MILLISECOND => result.push(( - "millisecond".to_owned(), - FieldValue::Integer(self.millisecond), - )), - FieldMap::MICROSECOND => result.push(( - "microsecond".to_owned(), - FieldValue::Integer(self.microsecond), - )), - FieldMap::NANOSECOND => result.push(( - "nanosecond".to_owned(), - FieldValue::Integer(self.nanosecond), - )), - FieldMap::OFFSET => result.push(( - "offset".to_owned(), - self.offset - .clone() - .map_or(FieldValue::Undefined, FieldValue::String), - )), - FieldMap::ERA => result.push(( - "era".to_owned(), - self.era - .map_or(FieldValue::Undefined, |s| FieldValue::String(s.to_string())), - )), - FieldMap::ERA_YEAR => result.push(( - "eraYear".to_owned(), - self.era_year - .map_or(FieldValue::Undefined, FieldValue::Integer), - )), - FieldMap::TIME_ZONE => result.push(( - "timeZone".to_owned(), - self.time_zone - .clone() - .map_or(FieldValue::Undefined, FieldValue::String), - )), - _ => {} - } + self.keys().zip(self.values()).collect() + } + + /// Returns an iterator over the current keys. + #[must_use] + pub fn keys(&self) -> Keys { + Keys { + iter: self.bit_map.iter(), } + } - result + /// Returns an iterator over the current values. + #[must_use] + pub fn values(&self) -> Values<'_> { + Values { + fields: self, + iter: self.bit_map.iter(), + } } /// Resolve `TemporalFields` month and monthCode fields. @@ -463,6 +480,144 @@ impl TemporalFields { Ok(()) } + + /// Merges two `TemporalFields` values given a specific `CalendarSlot`. + pub fn merge_fields( + &self, + other: &Self, + calendar: &CalendarSlot, + ) -> TemporalResult { + let add_keys = other.keys().collect::>(); + 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, +} + +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 { + 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, +} + +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 { + 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 { diff --git a/core/temporal/src/parser/tests.rs b/core/temporal/src/parser/tests.rs index 846b720d38..a51fb3c5b6 100644 --- a/core/temporal/src/parser/tests.rs +++ b/core/temporal/src/parser/tests.rs @@ -10,9 +10,9 @@ fn temporal_parser_basic() { let basic = "20201108"; let basic_separated = "2020-11-08"; - let basic_result = basic.parse::().unwrap(); + let basic_result = basic.parse::>().unwrap(); - let sep_result = basic_separated.parse::().unwrap(); + let sep_result = basic_separated.parse::>().unwrap(); assert_eq!(basic_result.iso_date().year(), 2020); assert_eq!(basic_result.iso_date().month(), 11); @@ -32,7 +32,7 @@ fn temporal_date_time_max() { let date_time = "+002020-11-08T12:28:32.329402834[!America/Argentina/ComodRivadavia][!u-ca=iso8601]"; - let result = date_time.parse::().unwrap(); + let result = date_time.parse::>().unwrap(); let time_results = result.iso_time(); @@ -49,10 +49,10 @@ fn temporal_year_parsing() { let long = "+002020-11-08"; let bad_year = "-000000-11-08"; - let result_good = long.parse::().unwrap(); + let result_good = long.parse::>().unwrap(); assert_eq!(result_good.iso_date().year(), 2020); - let err_result = bad_year.parse::(); + let err_result = bad_year.parse::>(); assert!( err_result.is_err(), "Invalid extended year parsing: \"{bad_year}\" should fail to parse." @@ -90,7 +90,7 @@ fn temporal_year_month() { ]; for ym in possible_year_months { - let result = ym.parse::().unwrap(); + let result = ym.parse::>().unwrap(); assert_eq!(result.year(), 2020); assert_eq!(result.month(), 11); @@ -108,7 +108,7 @@ fn temporal_month_day() { ]; for md in possible_month_day { - let result = md.parse::().unwrap(); + let result = md.parse::>().unwrap(); assert_eq!(result.month(), 11); assert_eq!(result.day(), 7); @@ -124,7 +124,7 @@ fn temporal_invalid_annotations() { ]; for invalid in invalid_annotations { - let err_result = invalid.parse::(); + let err_result = invalid.parse::>(); assert!( err_result.is_err(), "Invalid ISO annotation parsing: \"{invalid}\" should fail parsing." @@ -240,7 +240,7 @@ fn temporal_invalid_iso_datetime_strings() { ]; for invalid_target in INVALID_DATETIME_STRINGS { - let error_result = invalid_target.parse::(); + let error_result = invalid_target.parse::>(); assert!( error_result.is_err(), "Invalid ISO8601 `DateTime` target: \"{invalid_target}\" should fail parsing."