diff --git a/src/builtins/core/calendar/types.rs b/src/builtins/core/calendar/types.rs index e01efa614..63936fcb3 100644 --- a/src/builtins/core/calendar/types.rs +++ b/src/builtins/core/calendar/types.rs @@ -11,6 +11,8 @@ use crate::{TemporalError, TemporalResult}; use crate::builtins::core::{calendar::Calendar, PartialDate}; +use super::era::ISO_ERA; + #[derive(Debug, Clone, Copy, PartialEq)] pub enum ResolutionType { Date, @@ -34,7 +36,7 @@ impl ResolvedCalendarFields { overflow: ArithmeticOverflow, resolve_type: ResolutionType, ) -> TemporalResult { - let era_year = EraYear::try_from_partial_date(partial_date)?; + let era_year = EraYear::try_from_partial_date(partial_date, resolve_type)?; if partial_date.calendar.is_iso() { let month_code = resolve_iso_month(partial_date, overflow)?; let day = resolve_day(partial_date.day, resolve_type == ResolutionType::YearMonth)?; @@ -85,7 +87,10 @@ pub struct EraYear { } impl EraYear { - pub(crate) fn try_from_partial_date(partial: &PartialDate) -> TemporalResult { + pub(crate) fn try_from_partial_date( + partial: &PartialDate, + resolution_type: ResolutionType, + ) -> TemporalResult { match (partial.year, partial.era, partial.era_year) { (Some(year), None, None) => { let Some(era) = partial.calendar.get_calendar_default_era() else { @@ -112,6 +117,10 @@ impl EraYear { era: Era(era_info.name), }) } + (None, None, None) if resolution_type == ResolutionType::MonthDay => Ok(Self { + era: Era(ISO_ERA.name), + year: 1972, + }), _ => Err(TemporalError::r#type() .with_message("Required fields missing to determine an era and year.")), } diff --git a/src/builtins/core/month_day.rs b/src/builtins/core/month_day.rs index a20088f61..d7bc83577 100644 --- a/src/builtins/core/month_day.rs +++ b/src/builtins/core/month_day.rs @@ -10,7 +10,7 @@ use crate::{ Calendar, MonthCode, TemporalError, TemporalResult, TemporalUnwrap, }; -use super::{PartialDate, PlainDate}; +use super::{calendar::month_to_month_code, PartialDate, PlainDate}; use writeable::Writeable; /// The native Rust implementation of `Temporal.PlainMonthDay` @@ -82,12 +82,45 @@ impl PlainMonthDay { ) } + /// Create a `PlainMonthDay` with the provided fields from a [`PartialDate`]. pub fn with( &self, - _partial: PartialDate, - _overflow: ArithmeticOverflow, + partial: PartialDate, + overflow: Option, ) -> TemporalResult { - Err(TemporalError::general("Not yet implemented.")) + // Steps 1-6 are engine specific. + // 5. Let fields be ISODateToFields(calendar, monthDay.[[ISODate]], month-day). + // 6. Let partialMonthDay be ? PrepareCalendarFields(calendar, temporalMonthDayLike, « year, month, month-code, day », « », partial). + // + // NOTE: We assert that partial is not empty per step 6 + if partial.is_empty() { + return Err(TemporalError::r#type().with_message("partial object must have a field.")); + } + + // NOTE: We only need to set month / month_code and day, per spec. + // 7. Set fields to CalendarMergeFields(calendar, fields, partialMonthDay). + let (month, month_code) = match (partial.month, partial.month_code) { + (Some(m), Some(mc)) => (Some(m), Some(mc)), + (Some(m), None) => (Some(m), Some(month_to_month_code(m)?)), + (None, Some(mc)) => (Some(mc.to_month_integer()), Some(mc)), + (None, None) => ( + Some(self.month_code().to_month_integer()), + Some(self.month_code()), + ), + }; + let merged_day = partial.day.unwrap_or(self.day()); + let merged = partial + .with_month(month) + .with_month_code(month_code) + .with_day(Some(merged_day)); + + // Step 8-9 already handled by engine. + // 8. Let resolvedOptions be ? GetOptionsObject(options). + // 9. Let overflow be ? GetTemporalOverflowOption(resolvedOptions). + // 10. Let isoDate be ? CalendarMonthDayFromFields(calendar, fields, overflow). + // 11. Return ! CreateTemporalMonthDay(isoDate, calendar). + self.calendar + .month_day_from_partial(&merged, overflow.unwrap_or(ArithmeticOverflow::Constrain)) } /// Returns the ISO day value of `PlainMonthDay`. @@ -137,6 +170,7 @@ impl PlainMonthDay { self.calendar.day(&self.iso) } + /// Create a [`PlainDate`] from the current `PlainMonthDay`. pub fn to_plain_date(&self, year: Option) -> TemporalResult { let year_partial = match &year { Some(partial) => partial, @@ -164,6 +198,7 @@ impl PlainMonthDay { .date_from_partial(&partial_date, ArithmeticOverflow::Reject) } + /// Creates a RFC9557 IXDTF string from the current `PlainMonthDay`. pub fn to_ixdtf_string(&self, display_calendar: DisplayCalendar) -> String { self.to_ixdtf_writeable(display_calendar) .write_to_string() @@ -197,6 +232,29 @@ mod tests { use crate::Calendar; use tinystr::tinystr; + #[test] + fn test_plain_month_day_with() { + let month_day = PlainMonthDay::from_utf8("01-15".as_bytes()).unwrap(); + + let new = month_day + .with(PartialDate::new().with_day(Some(22)), None) + .unwrap(); + assert_eq!( + new.month_code(), + MonthCode::try_from_utf8("M01".as_bytes()).unwrap() + ); + assert_eq!(new.day(), 22,); + + let new = month_day + .with(PartialDate::new().with_month(Some(12)), None) + .unwrap(); + assert_eq!( + new.month_code(), + MonthCode::try_from_utf8("M12".as_bytes()).unwrap() + ); + assert_eq!(new.day(), 15,); + } + #[test] fn test_to_plain_date_with_year() { let month_day = PlainMonthDay::new_with_overflow( diff --git a/temporal_capi/bindings/c/PlainMonthDay.h b/temporal_capi/bindings/c/PlainMonthDay.h index a17f5af41..971b31284 100644 --- a/temporal_capi/bindings/c/PlainMonthDay.h +++ b/temporal_capi/bindings/c/PlainMonthDay.h @@ -26,7 +26,7 @@ typedef struct temporal_rs_PlainMonthDay_try_new_with_overflow_result {union {Pl temporal_rs_PlainMonthDay_try_new_with_overflow_result temporal_rs_PlainMonthDay_try_new_with_overflow(uint8_t month, uint8_t day, AnyCalendarKind calendar, ArithmeticOverflow overflow, OptionI32 ref_year); typedef struct temporal_rs_PlainMonthDay_with_result {union {PlainMonthDay* ok; TemporalError err;}; bool is_ok;} temporal_rs_PlainMonthDay_with_result; -temporal_rs_PlainMonthDay_with_result temporal_rs_PlainMonthDay_with(const PlainMonthDay* self, PartialDate partial, ArithmeticOverflow overflow); +temporal_rs_PlainMonthDay_with_result temporal_rs_PlainMonthDay_with(const PlainMonthDay* self, PartialDate partial, ArithmeticOverflow_option overflow); bool temporal_rs_PlainMonthDay_equals(const PlainMonthDay* self, const PlainMonthDay* other); diff --git a/temporal_capi/bindings/cpp/temporal_rs/PlainMonthDay.d.hpp b/temporal_capi/bindings/cpp/temporal_rs/PlainMonthDay.d.hpp index c92c89b1e..2b4116348 100644 --- a/temporal_capi/bindings/cpp/temporal_rs/PlainMonthDay.d.hpp +++ b/temporal_capi/bindings/cpp/temporal_rs/PlainMonthDay.d.hpp @@ -38,7 +38,7 @@ class PlainMonthDay { inline static diplomat::result, temporal_rs::TemporalError> try_new_with_overflow(uint8_t month, uint8_t day, temporal_rs::AnyCalendarKind calendar, temporal_rs::ArithmeticOverflow overflow, std::optional ref_year); - inline diplomat::result, temporal_rs::TemporalError> with(temporal_rs::PartialDate partial, temporal_rs::ArithmeticOverflow overflow) const; + inline diplomat::result, temporal_rs::TemporalError> with(temporal_rs::PartialDate partial, std::optional overflow) const; inline bool equals(const temporal_rs::PlainMonthDay& other) const; diff --git a/temporal_capi/bindings/cpp/temporal_rs/PlainMonthDay.hpp b/temporal_capi/bindings/cpp/temporal_rs/PlainMonthDay.hpp index 62b1ca44c..a6a2fdfc2 100644 --- a/temporal_capi/bindings/cpp/temporal_rs/PlainMonthDay.hpp +++ b/temporal_capi/bindings/cpp/temporal_rs/PlainMonthDay.hpp @@ -29,7 +29,7 @@ namespace capi { temporal_rs_PlainMonthDay_try_new_with_overflow_result temporal_rs_PlainMonthDay_try_new_with_overflow(uint8_t month, uint8_t day, temporal_rs::capi::AnyCalendarKind calendar, temporal_rs::capi::ArithmeticOverflow overflow, diplomat::capi::OptionI32 ref_year); typedef struct temporal_rs_PlainMonthDay_with_result {union {temporal_rs::capi::PlainMonthDay* ok; temporal_rs::capi::TemporalError err;}; bool is_ok;} temporal_rs_PlainMonthDay_with_result; - temporal_rs_PlainMonthDay_with_result temporal_rs_PlainMonthDay_with(const temporal_rs::capi::PlainMonthDay* self, temporal_rs::capi::PartialDate partial, temporal_rs::capi::ArithmeticOverflow overflow); + temporal_rs_PlainMonthDay_with_result temporal_rs_PlainMonthDay_with(const temporal_rs::capi::PlainMonthDay* self, temporal_rs::capi::PartialDate partial, temporal_rs::capi::ArithmeticOverflow_option overflow); bool temporal_rs_PlainMonthDay_equals(const temporal_rs::capi::PlainMonthDay* self, const temporal_rs::capi::PlainMonthDay* other); @@ -71,10 +71,10 @@ inline diplomat::result, temporal_rs return result.is_ok ? diplomat::result, temporal_rs::TemporalError>(diplomat::Ok>(std::unique_ptr(temporal_rs::PlainMonthDay::FromFFI(result.ok)))) : diplomat::result, temporal_rs::TemporalError>(diplomat::Err(temporal_rs::TemporalError::FromFFI(result.err))); } -inline diplomat::result, temporal_rs::TemporalError> temporal_rs::PlainMonthDay::with(temporal_rs::PartialDate partial, temporal_rs::ArithmeticOverflow overflow) const { +inline diplomat::result, temporal_rs::TemporalError> temporal_rs::PlainMonthDay::with(temporal_rs::PartialDate partial, std::optional overflow) const { auto result = temporal_rs::capi::temporal_rs_PlainMonthDay_with(this->AsFFI(), partial.AsFFI(), - overflow.AsFFI()); + overflow.has_value() ? (temporal_rs::capi::ArithmeticOverflow_option{ { overflow.value().AsFFI() }, true }) : (temporal_rs::capi::ArithmeticOverflow_option{ {}, false })); return result.is_ok ? diplomat::result, temporal_rs::TemporalError>(diplomat::Ok>(std::unique_ptr(temporal_rs::PlainMonthDay::FromFFI(result.ok)))) : diplomat::result, temporal_rs::TemporalError>(diplomat::Err(temporal_rs::TemporalError::FromFFI(result.err))); } diff --git a/temporal_capi/src/plain_month_day.rs b/temporal_capi/src/plain_month_day.rs index 6ba4eb0ec..2118f59b4 100644 --- a/temporal_capi/src/plain_month_day.rs +++ b/temporal_capi/src/plain_month_day.rs @@ -41,10 +41,10 @@ pub mod ffi { pub fn with( &self, partial: PartialDate, - overflow: ArithmeticOverflow, + overflow: Option, ) -> Result, TemporalError> { self.0 - .with(partial.try_into()?, overflow.into()) + .with(partial.try_into()?, overflow.map(Into::into)) .map(|x| Box::new(PlainMonthDay(x))) .map_err(Into::into) }