diff --git a/src/components/date.rs b/src/components/date.rs index 54f52aa0..87084e70 100644 --- a/src/components/date.rs +++ b/src/components/date.rs @@ -327,6 +327,43 @@ impl PlainDate { Ok(Self::new_unchecked(iso, calendar)) } + /// Create a `PlainDate` from a `PartialDate` + /// + /// ```rust + /// use temporal_rs::{PlainDate, partial::PartialDate}; + /// + /// let partial = PartialDate { + /// year: Some(2000), + /// month: Some(13), + /// day: Some(2), + /// ..Default::default() + /// }; + /// + /// let date = PlainDate::from_partial(partial, None, None).unwrap(); + /// + /// assert_eq!(date.year().unwrap(), 2000); + /// assert_eq!(date.month().unwrap(), 12); + /// assert_eq!(date.day().unwrap(), 2); + /// assert_eq!(date.calendar().identifier(), "iso8601"); + /// + /// ``` + #[inline] + pub fn from_partial( + partial: PartialDate, + calendar: Option, + overflow: Option, + ) -> TemporalResult { + let year_check = + partial.year.is_some() || (partial.era.is_some() && partial.era_year.is_some()); + let month_check = partial.month.is_some() || partial.month_code.is_some(); + if !year_check || !month_check || partial.day.is_none() { + return Err(TemporalError::range().with_message("Invalid PlainDate fields provided.")); + } + let calendar = calendar.unwrap_or_default(); + let overflow = overflow.unwrap_or_default(); + calendar.date_from_partial(&partial, overflow) + } + /// Creates a date time with values from a `PartialDate`. pub fn with( &self, diff --git a/src/components/datetime.rs b/src/components/datetime.rs index 1a8d3924..9429d1cf 100644 --- a/src/components/datetime.rs +++ b/src/components/datetime.rs @@ -60,9 +60,10 @@ impl PlainDateTime { Self { iso, calendar } } + // TODO: Potentially deprecate and remove. + /// Utility function for validating `IsoDate`s #[inline] #[must_use] - /// Utility function for validating `IsoDate`s fn validate_iso(iso: IsoDate) -> bool { IsoDateTime::new_unchecked(iso, IsoTime::noon()).is_within_limits() } @@ -321,7 +322,80 @@ impl PlainDateTime { )) } + /// Creates a `DateTime` from a `PartialDateTime`. + /// + /// ```rust + /// use temporal_rs::{PlainDateTime, partial::{PartialDateTime, PartialTime, PartialDate}}; + /// + /// let date = PartialDate { + /// year: Some(2000), + /// month: Some(13), + /// day: Some(2), + /// ..Default::default() + /// }; + /// + /// let time = PartialTime { + /// hour: Some(4), + /// minute: Some(25), + /// ..Default::default() + /// }; + /// + /// let partial = PartialDateTime { date, time }; + /// + /// let date = PlainDateTime::from_partial(partial, None, None).unwrap(); + /// + /// assert_eq!(date.year().unwrap(), 2000); + /// assert_eq!(date.month().unwrap(), 12); + /// assert_eq!(date.day().unwrap(), 2); + /// assert_eq!(date.calendar().identifier(), "iso8601"); + /// assert_eq!(date.hour(), 4); + /// assert_eq!(date.minute(), 25); + /// assert_eq!(date.second(), 0); + /// assert_eq!(date.millisecond(), 0); + /// + /// ``` + pub fn from_partial( + partial: PartialDateTime, + calendar: Option, + overflow: Option, + ) -> TemporalResult { + let date = PlainDate::from_partial(partial.date, calendar, overflow)?; + let time = PlainTime::from_partial(partial.time, overflow)?; + Self::from_date_and_time(date, time) + } + /// Creates a new `DateTime` with the fields of a `PartialDateTime`. + /// + /// ```rust + /// use temporal_rs::{Calendar, PlainDateTime, partial::{PartialDateTime, PartialTime, PartialDate}}; + /// + /// let initial = PlainDateTime::try_new(2000, 12, 2, 0,0,0,0,0,0, Calendar::default()).unwrap(); + /// + /// let date = PartialDate { + /// month: Some(5), + /// ..Default::default() + /// }; + /// + /// let time = PartialTime { + /// hour: Some(4), + /// second: Some(30), + /// ..Default::default() + /// }; + /// + /// let partial = PartialDateTime { date, time }; + /// + /// let date = initial.with(partial, None).unwrap(); + /// + /// assert_eq!(date.year().unwrap(), 2000); + /// assert_eq!(date.month().unwrap(), 5); + /// assert_eq!(date.day().unwrap(), 2); + /// assert_eq!(date.calendar().identifier(), "iso8601"); + /// assert_eq!(date.hour(), 4); + /// assert_eq!(date.minute(), 0); + /// assert_eq!(date.second(), 30); + /// assert_eq!(date.millisecond(), 0); + /// + /// ``` #[inline] pub fn with( &self, diff --git a/src/components/time.rs b/src/components/time.rs index 3e01a799..d7c03f4b 100644 --- a/src/components/time.rs +++ b/src/components/time.rs @@ -159,6 +159,15 @@ impl PlainTime { impl PlainTime { /// Creates a new `PlainTime`, constraining any field into a valid range. + /// + /// ```rust + /// use temporal_rs::PlainTime; + /// + /// let time = PlainTime::new(23, 59, 59, 999, 999, 999).unwrap(); + /// + /// let constrained_time = PlainTime::new(24, 59, 59, 999, 999, 999).unwrap(); + /// assert_eq!(time, constrained_time); + /// ``` pub fn new( hour: i32, minute: i32, @@ -179,6 +188,15 @@ impl PlainTime { } /// Creates a new `PlainTime`, rejecting any field that is not in a valid range. + /// + /// ```rust + /// use temporal_rs::PlainTime; + /// + /// let time = PlainTime::try_new(23, 59, 59, 999, 999, 999).unwrap(); + /// + /// let invalid_time = PlainTime::try_new(24, 59, 59, 999, 999, 999); + /// assert!(invalid_time.is_err()); + /// ``` pub fn try_new( hour: i32, minute: i32, @@ -221,11 +239,68 @@ impl PlainTime { Ok(Self::new_unchecked(time)) } + /// Creates a new `PlainTime` from a `PartialTime`. + /// + /// ```rust + /// use temporal_rs::{partial::PartialTime, PlainTime}; + /// + /// let partial_time = PartialTime { + /// hour: Some(22), + /// ..Default::default() + /// }; + /// + /// let time = PlainTime::from_partial(partial_time, None).unwrap(); + /// + /// assert_eq!(time.hour(), 22); + /// assert_eq!(time.minute(), 0); + /// assert_eq!(time.second(), 0); + /// assert_eq!(time.millisecond(), 0); + /// assert_eq!(time.microsecond(), 0); + /// assert_eq!(time.nanosecond(), 0); + /// + /// ``` + pub fn from_partial( + partial: PartialTime, + overflow: Option, + ) -> TemporalResult { + // NOTE: 4.5.12 ToTemporalTimeRecord requires one field to be set. + if partial.is_empty() { + return Err(TemporalError::r#type().with_message("PartialTime cannot be empty.")); + } + + let overflow = overflow.unwrap_or_default(); + let iso = IsoTime::default().with(partial, overflow)?; + Ok(Self::new_unchecked(iso)) + } + + /// Creates a new `PlainTime` using the current `PlainTime` fields as a fallback. + /// + /// ```rust + /// use temporal_rs::{partial::PartialTime, PlainTime}; + /// + /// let partial_time = PartialTime { + /// hour: Some(22), + /// ..Default::default() + /// }; + /// + /// let initial = PlainTime::try_new(15, 30, 12, 123, 456, 789).unwrap(); + /// + /// let time = initial.with(partial_time, None).unwrap(); + /// + /// assert_eq!(time.hour(), 22); + /// assert_eq!(time.minute(), 30); + /// assert_eq!(time.second(), 12); + /// assert_eq!(time.millisecond(), 123); + /// assert_eq!(time.microsecond(), 456); + /// assert_eq!(time.nanosecond(), 789); + /// + /// ``` pub fn with( &self, partial: PartialTime, overflow: Option, ) -> TemporalResult { + // NOTE: 4.5.12 ToTemporalTimeRecord requires one field to be set. if partial.is_empty() { return Err(TemporalError::r#type().with_message("PartialTime cannot be empty.")); } diff --git a/src/options.rs b/src/options.rs index 24dc0cd3..16bedd54 100644 --- a/src/options.rs +++ b/src/options.rs @@ -390,9 +390,10 @@ impl fmt::Display for TemporalUnit { /// `ArithmeticOverflow` can also be used as an /// assignment overflow and consists of the "constrain" /// and "reject" options. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub enum ArithmeticOverflow { /// Constrain option + #[default] Constrain, /// Constrain option Reject,