use tinystr::TinyAsciiStr;
use crate::{
error::ErrorMessage, Calendar, MonthCode, PlainDate, PlainDateTime, PlainMonthDay,
PlainYearMonth, TemporalError, TemporalResult,
};
use core::ops::Range;
#[derive(Copy, Clone, Default)]
pub(crate) struct FieldKeysToIgnore {
pub era: bool,
pub arithmetical_year: bool,
pub month: bool,
}
#[derive(Debug, Default, Clone, PartialEq)]
pub struct CalendarFields {
pub year: Option<i32>,
pub month: Option<u8>,
pub month_code: Option<MonthCode>,
pub day: Option<u8>,
pub era: Option<TinyAsciiStr<19>>,
pub era_year: Option<i32>,
}
impl CalendarFields {
pub const fn new() -> Self {
Self {
year: None,
month: None,
month_code: None,
day: None,
era: None,
era_year: None,
}
}
pub(crate) fn check_year_in_safe_arithmetical_range(&self) -> TemporalResult<()> {
const ROUGH_YEAR_RANGE: Range<i32> = -300000..300000;
if let Some(year) = self.year {
if !ROUGH_YEAR_RANGE.contains(&year) {
return Err(TemporalError::range().with_enum(ErrorMessage::DateOutOfRange));
}
}
if let Some(era_year) = self.era_year {
if !ROUGH_YEAR_RANGE.contains(&era_year) {
return Err(TemporalError::range().with_enum(ErrorMessage::DateOutOfRange));
}
}
Ok(())
}
pub const fn with_era(mut self, era: Option<TinyAsciiStr<19>>) -> Self {
self.era = era;
self
}
pub const fn with_era_year(mut self, era_year: Option<i32>) -> Self {
self.era_year = era_year;
self
}
pub const fn with_year(mut self, year: i32) -> Self {
self.year = Some(year);
self
}
pub const fn with_optional_year(mut self, year: Option<i32>) -> Self {
self.year = year;
self
}
pub const fn with_month(mut self, month: u8) -> Self {
self.month = Some(month);
self
}
pub const fn with_optional_month(mut self, month: Option<u8>) -> Self {
self.month = month;
self
}
pub const fn with_month_code(mut self, month_code: MonthCode) -> Self {
self.month_code = Some(month_code);
self
}
pub const fn with_optional_month_code(mut self, month_code: Option<MonthCode>) -> Self {
self.month_code = month_code;
self
}
pub const fn with_day(mut self, day: u8) -> Self {
self.day = Some(day);
self
}
pub const fn with_optional_day(mut self, day: Option<u8>) -> Self {
self.day = day;
self
}
}
impl CalendarFields {
pub fn is_empty(&self) -> bool {
*self == Self::new()
}
pub(crate) fn from_month_day(month_day: &PlainMonthDay) -> Self {
Self {
year: None,
month: None,
month_code: Some(month_day.month_code()),
era: None,
era_year: None,
day: Some(month_day.day()),
}
}
crate::impl_with_fallback_method!(with_fallback_date, CalendarFields, (with_day: day) PlainDate);
crate::impl_with_fallback_method!(with_fallback_datetime, CalendarFields, (with_day:day) PlainDateTime);
crate::impl_field_keys_to_ignore!((with_day:day));
}
impl From<YearMonthCalendarFields> for CalendarFields {
fn from(value: YearMonthCalendarFields) -> Self {
Self {
year: value.year,
month: value.month,
month_code: value.month_code,
day: None,
era: value.era,
era_year: value.era_year,
}
}
}
impl From<CalendarFields> for YearMonthCalendarFields {
fn from(value: CalendarFields) -> Self {
Self {
year: value.year,
month: value.month,
month_code: value.month_code,
era: value.era,
era_year: value.era_year,
}
}
}
#[derive(Debug, Default, Clone, PartialEq)]
pub struct YearMonthCalendarFields {
pub year: Option<i32>,
pub month: Option<u8>,
pub month_code: Option<MonthCode>,
pub era: Option<TinyAsciiStr<19>>,
pub era_year: Option<i32>,
}
impl YearMonthCalendarFields {
pub const fn new() -> Self {
Self {
year: None,
month: None,
month_code: None,
era: None,
era_year: None,
}
}
pub const fn with_era(mut self, era: Option<TinyAsciiStr<19>>) -> Self {
self.era = era;
self
}
pub const fn with_era_year(mut self, era_year: Option<i32>) -> Self {
self.era_year = era_year;
self
}
pub const fn with_year(mut self, year: i32) -> Self {
self.year = Some(year);
self
}
pub const fn with_optional_year(mut self, year: Option<i32>) -> Self {
self.year = year;
self
}
pub const fn with_month(mut self, month: u8) -> Self {
self.month = Some(month);
self
}
pub const fn with_optional_month(mut self, month: Option<u8>) -> Self {
self.month = month;
self
}
pub const fn with_month_code(mut self, month_code: MonthCode) -> Self {
self.month_code = Some(month_code);
self
}
pub const fn with_optional_month_code(mut self, month_code: Option<MonthCode>) -> Self {
self.month_code = month_code;
self
}
}
impl YearMonthCalendarFields {
pub(crate) fn try_from_year_month(year_month: &PlainYearMonth) -> TemporalResult<Self> {
let (year, era, era_year) = if year_month.era().is_some() {
(
None,
year_month
.era()
.map(|t| TinyAsciiStr::<19>::try_from_utf8(t.as_bytes()))
.transpose()
.map_err(|_| TemporalError::general("Invalid era"))?,
year_month.era_year(),
)
} else {
(Some(year_month.year()), None, None)
};
Ok(Self {
year,
month: Some(year_month.month()),
month_code: Some(year_month.month_code()),
era,
era_year,
})
}
pub fn is_empty(&self) -> bool {
*self == Self::new()
}
crate::impl_with_fallback_method!(with_fallback_year_month, CalendarFields, () PlainYearMonth);
crate::impl_field_keys_to_ignore!(());
}
#[doc(hidden)]
#[macro_export]
macro_rules! impl_with_fallback_method {
($method_name:ident, $fields_type:ident, ( $(with_day: $day:ident)? ) $component_type:ty) => {
pub(crate) fn $method_name(&self, fallback: &$component_type, calendar: icu_calendar::AnyCalendarKind) -> TemporalResult<Self> {
let keys_to_ignore = self.field_keys_to_ignore(calendar);
let mut era = self.era;
let mut era_year = self.era_year;
let mut year = self.year;
if !keys_to_ignore.era {
if era.is_none() {
era =
fallback.era().map(|e| {
TinyAsciiStr::<19>::try_from_utf8(e.as_bytes())
.map_err(|_| TemporalError::assert().with_message("Produced invalid era code"))
})
.transpose()?
}
if era_year.is_none() {
era_year = fallback.era_year();
}
}
if !keys_to_ignore.arithmetical_year {
if year.is_none() {
year = Some(fallback.year());
}
}
let month = self.month;
let mut month_code = self.month_code;
if month.is_none() && month_code.is_none() && !keys_to_ignore.month {
month_code = Some(fallback.month_code());
}
#[allow(clippy::needless_update)] {
Ok(Self {
year,
month,
month_code,
$($day: Some(self.day.unwrap_or(fallback.day().into())),)?
era,
era_year,
})
}
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! impl_field_keys_to_ignore {
(( $(with_day: $day:ident)? )) => {
fn field_keys_to_ignore(&self, calendar: icu_calendar::AnyCalendarKind) -> $crate::builtins::core::calendar::fields::FieldKeysToIgnore {
let mut keys = $crate::builtins::core::calendar::fields::FieldKeysToIgnore::default();
if self.month.is_some() || self.month_code.is_some() {
keys.month = true;
}
if Calendar::calendar_has_eras(calendar) {
if self.year.is_some() || self.era_year.is_some() || self.era.is_some() {
keys.era = true;
keys.arithmetical_year = true;
}
if calendar == icu_calendar::AnyCalendarKind::Japanese {
if self.month.is_some() || self.month_code.is_some() {
keys.era = true;
}
$(
if self.$day.is_some() {
keys.era = true;
}
)?
}
}
keys
}
};
}