Skip to content

Commit

Permalink
Add MonthCode and PartialDate, implement Date::with
Browse files Browse the repository at this point in the history
  • Loading branch information
nekevss committed Jul 25, 2024
1 parent b2ef216 commit 22fee3f
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 60 deletions.
25 changes: 24 additions & 1 deletion src/components/calendar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,10 @@ impl Calendar {
let month_code = MonthCode(
fields
.month_code
.map(|mc| {
TinyAsciiStr::from_bytes(mc.as_str().as_bytes())
.expect("MonthCode as_str is always valid.")
})
.ok_or(TemporalError::range().with_message("No MonthCode provided."))?,
);
// NOTE: This might preemptively throw as `ICU4X` does not support constraining.
Expand Down Expand Up @@ -373,6 +377,10 @@ impl Calendar {
let month_code = MonthCode(
fields
.month_code
.map(|mc| {
TinyAsciiStr::from_bytes(mc.as_str().as_bytes())
.expect("MonthCode as_str is always valid.")
})
.ok_or(TemporalError::range().with_message("No MonthCode provided."))?,
);

Expand Down Expand Up @@ -628,8 +636,23 @@ impl Calendar {
/// Provides field keys to be ignored depending on the calendar.
pub fn field_keys_to_ignore(
&self,
_keys: &[TemporalFieldKey],
keys: &[TemporalFieldKey],
) -> TemporalResult<Vec<TemporalFieldKey>> {
let mut ignored_keys = Vec::default();
if self.is_iso() {
// NOTE: It is okay for ignored keys to have duplicates?
for key in keys {
ignored_keys.push(*key);
if key == &TemporalFieldKey::Month {
ignored_keys.push(TemporalFieldKey::MonthCode)
} else if key == &TemporalFieldKey::MonthCode {
ignored_keys.push(TemporalFieldKey::Month)
}
}

return Ok(ignored_keys);
}

// TODO: Research and implement the appropriate KeysToIgnore for all `BuiltinCalendars.`
Err(TemporalError::range().with_message("FieldKeysToIgnore is not yet implemented."))
}
Expand Down
60 changes: 53 additions & 7 deletions src/components/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,38 @@ use std::str::FromStr;

use super::{
duration::{normalized::NormalizedDurationRecord, TimeDuration},
MonthDay, Time, YearMonth,
MonthCode, MonthDay, Time, YearMonth,
};

// TODO: PrepareTemporalFields expects a type error to be thrown when all partial fields are None/undefined.
/// A partial Date that may or may not be complete.
#[derive(Debug, Clone, Copy)]
pub struct PartialDate {
pub year: Option<i32>,
pub month: Option<i32>,
pub month_code: Option<&str>,
pub day: Option<i32>,
pub(crate) year: Option<i32>,
pub(crate) month: Option<i32>,
pub(crate) month_code: Option<MonthCode>,
pub(crate) day: Option<i32>,
}

impl PartialDate {
/// Create a new `PartialDate`
pub fn new(
year: Option<i32>,
month: Option<i32>,
month_code: Option<MonthCode>,
day: Option<i32>,
) -> TemporalResult<Self> {
if year.is_none() && month.is_none() && month_code.is_none() && day.is_none() {
return Err(TemporalError::r#type()
.with_message("A partial date must have at least one defined field."));
}
Ok(Self {
year,
month,
month_code,
day,
})
}
}

/// The native Rust implementation of `Temporal.PlainDate`.
Expand Down Expand Up @@ -217,6 +241,28 @@ impl Date {
Ok(Self::new_unchecked(iso, calendar))
}

/// Creates a date time with values from a `PartialDate`.
pub fn with(
&self,
partial: PartialDate,
overflow: Option<ArithmeticOverflow>,
) -> TemporalResult<Self> {
// 6. Let fieldsResult be ? PrepareCalendarFieldsAndFieldNames(calendarRec, temporalDate, « "day", "month", "monthCode", "year" »).
let fields = TemporalFields::from(self);
// 7. Let partialDate be ? PrepareTemporalFields(temporalDateLike, fieldsResult.[[FieldNames]], partial).
let partial_fields = TemporalFields::from(partial);

// 8. Let fields be ? CalendarMergeFields(calendarRec, fieldsResult.[[Fields]], partialDate).
let mut merge_result = fields.merge_fields(&partial_fields, self.calendar())?;

// 9. Set fields to ? PrepareTemporalFields(fields, fieldsResult.[[FieldNames]], «»).
// 10. Return ? CalendarDateFromFields(calendarRec, fields, resolvedOptions).
self.calendar.date_from_fields(
&mut merge_result,
overflow.unwrap_or(ArithmeticOverflow::Constrain),
)
}

/// Creates a new `Date` from the current `Date` and the provided calendar.
pub fn with_calendar(&self, calendar: Calendar) -> TemporalResult<Self> {
Self::new(
Expand Down Expand Up @@ -403,15 +449,15 @@ impl Date {
/// Converts the current `Date<C>` into a `YearMonth<C>`
#[inline]
pub fn to_year_month(&self) -> TemporalResult<YearMonth> {
let mut fields: TemporalFields = self.iso_date().into();
let mut fields: TemporalFields = self.into();
self.get_calendar()
.year_month_from_fields(&mut fields, ArithmeticOverflow::Constrain)
}

/// Converts the current `Date<C>` into a `MonthDay<C>`
#[inline]
pub fn to_month_day(&self) -> TemporalResult<MonthDay> {
let mut fields: TemporalFields = self.iso_date().into();
let mut fields: TemporalFields = self.into();
self.get_calendar()
.month_day_from_fields(&mut fields, ArithmeticOverflow::Constrain)
}
Expand Down
73 changes: 71 additions & 2 deletions src/components/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ mod time;
mod year_month;
mod zoneddatetime;

use std::str::FromStr;

#[doc(inline)]
pub use date::Date;
pub use date::{Date, PartialDate};
#[doc(inline)]
pub use datetime::DateTime;
#[doc(inline)]
Expand All @@ -46,8 +48,9 @@ pub use year_month::YearMonthFields;
#[doc(inline)]
pub use zoneddatetime::ZonedDateTime;

use crate::TemporalError;

#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
pub enum MonthCode {
One = 1,
Expand All @@ -65,3 +68,69 @@ pub enum MonthCode {
Thirteen,
}

impl MonthCode {
pub fn as_str(&self) -> &str {
match self {
Self::One => "M01",
Self::Two => "M02",
Self::Three => "M03",
Self::Four => "M04",
Self::Five => "M05",
Self::Six => "M06",
Self::Seven => "M07",
Self::Eight => "M08",
Self::Nine => "M09",
Self::Ten => "M10",
Self::Eleven => "M11",
Self::Twelve => "M12",
Self::Thirteen => "M13",
}
}
}

impl FromStr for MonthCode {
type Err = TemporalError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"M01" => Ok(Self::One),
"M02" => Ok(Self::Two),
"M03" => Ok(Self::Three),
"M04" => Ok(Self::Four),
"M05" => Ok(Self::Five),
"M06" => Ok(Self::Six),
"M07" => Ok(Self::Seven),
"M08" => Ok(Self::Eight),
"M09" => Ok(Self::Nine),
"M10" => Ok(Self::Ten),
"M11" => Ok(Self::Eleven),
"M12" => Ok(Self::Twelve),
"M13" => Ok(Self::Thirteen),
_ => {
Err(TemporalError::range()
.with_message("monthCode is not within the valid values."))
}
}
}
}

impl TryFrom<u8> for MonthCode {
type Error = TemporalError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
1 => Ok(Self::One),
2 => Ok(Self::Two),
3 => Ok(Self::Three),
4 => Ok(Self::Four),
5 => Ok(Self::Five),
6 => Ok(Self::Six),
7 => Ok(Self::Seven),
8 => Ok(Self::Eight),
9 => Ok(Self::Nine),
10 => Ok(Self::Ten),
11 => Ok(Self::Eleven),
12 => Ok(Self::Twelve),
13 => Ok(Self::Thirteen),
_ => Err(TemporalError::range().with_message("Invalid MonthCode value.")),
}
}
}
Loading

0 comments on commit 22fee3f

Please sign in to comment.