use crate::provider::time_zones::{MetazoneId, TimeZoneBcp47Id};
use icu_calendar::any_calendar::AnyCalendarKind;
use icu_calendar::week::{RelativeUnit, WeekCalculator};
use icu_calendar::Calendar;
use icu_calendar::{AsCalendar, Date, DateTime, Iso};
use icu_timezone::{CustomTimeZone, GmtOffset, ZoneVariant};
pub(crate) use icu_calendar::types::{
DayOfMonth, DayOfWeekInMonth, DayOfYearInfo, FormattableMonth, FormattableYear, IsoHour,
IsoMinute, IsoSecond, IsoWeekday, NanoSecond, Time, WeekOfMonth, WeekOfYear,
};
pub(crate) use icu_calendar::CalendarError;
pub trait DateInput {
type Calendar: Calendar;
fn year(&self) -> Option<FormattableYear>;
fn month(&self) -> Option<FormattableMonth>;
fn day_of_month(&self) -> Option<DayOfMonth>;
fn iso_weekday(&self) -> Option<IsoWeekday>;
fn day_of_year_info(&self) -> Option<DayOfYearInfo>;
fn any_calendar_kind(&self) -> Option<AnyCalendarKind>;
fn to_iso(&self) -> Date<Iso>;
}
pub trait IsoTimeInput {
fn hour(&self) -> Option<IsoHour>;
fn minute(&self) -> Option<IsoMinute>;
fn second(&self) -> Option<IsoSecond>;
fn nanosecond(&self) -> Option<NanoSecond>;
}
pub trait TimeZoneInput {
fn gmt_offset(&self) -> Option<GmtOffset>;
fn time_zone_id(&self) -> Option<TimeZoneBcp47Id>;
fn metazone_id(&self) -> Option<MetazoneId>;
fn zone_variant(&self) -> Option<ZoneVariant>;
}
pub trait DateTimeInput: DateInput + IsoTimeInput {}
impl<T> DateTimeInput for T where T: DateInput + IsoTimeInput {}
pub trait LocalizedDateTimeInput<T: DateTimeInput> {
fn datetime(&self) -> &T;
fn week_of_month(&self) -> Result<WeekOfMonth, CalendarError>;
fn week_of_year(&self) -> Result<(FormattableYear, WeekOfYear), CalendarError>;
fn day_of_week_in_month(&self) -> Result<DayOfWeekInMonth, CalendarError>;
fn flexible_day_period(&self);
}
pub(crate) struct DateTimeInputWithWeekConfig<'data, T: DateTimeInput> {
data: &'data T,
calendar: Option<&'data WeekCalculator>,
}
#[derive(Default, Debug)]
pub(crate) struct ExtractedDateTimeInput {
year: Option<FormattableYear>,
month: Option<FormattableMonth>,
day_of_month: Option<DayOfMonth>,
iso_weekday: Option<IsoWeekday>,
day_of_year_info: Option<DayOfYearInfo>,
any_calendar_kind: Option<AnyCalendarKind>,
hour: Option<IsoHour>,
minute: Option<IsoMinute>,
second: Option<IsoSecond>,
nanosecond: Option<NanoSecond>,
}
#[derive(Debug)]
pub(crate) struct ExtractedTimeZoneInput {
gmt_offset: Option<GmtOffset>,
time_zone_id: Option<TimeZoneBcp47Id>,
metazone_id: Option<MetazoneId>,
zone_variant: Option<ZoneVariant>,
}
impl ExtractedDateTimeInput {
pub(crate) fn extract_from<T: DateTimeInput>(input: &T) -> Self {
Self {
year: input.year(),
month: input.month(),
day_of_month: input.day_of_month(),
iso_weekday: input.iso_weekday(),
day_of_year_info: input.day_of_year_info(),
any_calendar_kind: input.any_calendar_kind(),
hour: input.hour(),
minute: input.minute(),
second: input.second(),
nanosecond: input.nanosecond(),
}
}
pub(crate) fn extract_from_date<T: DateInput>(input: &T) -> Self {
Self {
year: input.year(),
month: input.month(),
day_of_month: input.day_of_month(),
iso_weekday: input.iso_weekday(),
day_of_year_info: input.day_of_year_info(),
any_calendar_kind: input.any_calendar_kind(),
..Default::default()
}
}
pub(crate) fn extract_from_time<T: IsoTimeInput>(input: &T) -> Self {
Self {
hour: input.hour(),
minute: input.minute(),
second: input.second(),
nanosecond: input.nanosecond(),
..Default::default()
}
}
}
impl ExtractedTimeZoneInput {
pub(crate) fn extract_from<T: TimeZoneInput>(input: &T) -> Self {
Self {
gmt_offset: input.gmt_offset(),
time_zone_id: input.time_zone_id(),
metazone_id: input.metazone_id(),
zone_variant: input.zone_variant(),
}
}
}
impl DateInput for ExtractedDateTimeInput {
type Calendar = icu_calendar::any_calendar::AnyCalendar;
fn year(&self) -> Option<FormattableYear> {
self.year
}
fn month(&self) -> Option<FormattableMonth> {
self.month
}
fn day_of_month(&self) -> Option<DayOfMonth> {
self.day_of_month
}
fn iso_weekday(&self) -> Option<IsoWeekday> {
self.iso_weekday
}
fn day_of_year_info(&self) -> Option<DayOfYearInfo> {
self.day_of_year_info
}
fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
self.any_calendar_kind
}
fn to_iso(&self) -> Date<Iso> {
unreachable!("ExtractedDateTimeInput should never be directly passed to DateTimeFormatter")
}
}
impl IsoTimeInput for ExtractedDateTimeInput {
fn hour(&self) -> Option<IsoHour> {
self.hour
}
fn minute(&self) -> Option<IsoMinute> {
self.minute
}
fn second(&self) -> Option<IsoSecond> {
self.second
}
fn nanosecond(&self) -> Option<NanoSecond> {
self.nanosecond
}
}
impl TimeZoneInput for ExtractedTimeZoneInput {
fn gmt_offset(&self) -> Option<GmtOffset> {
self.gmt_offset
}
fn time_zone_id(&self) -> Option<TimeZoneBcp47Id> {
self.time_zone_id
}
fn metazone_id(&self) -> Option<MetazoneId> {
self.metazone_id
}
fn zone_variant(&self) -> Option<ZoneVariant> {
self.zone_variant
}
}
impl<'data, T: DateTimeInput> DateTimeInputWithWeekConfig<'data, T> {
pub(crate) fn new(data: &'data T, calendar: Option<&'data WeekCalculator>) -> Self {
Self { data, calendar }
}
}
impl<'data, T: DateTimeInput> LocalizedDateTimeInput<T> for DateTimeInputWithWeekConfig<'data, T> {
fn datetime(&self) -> &T {
self.data
}
fn week_of_month(&self) -> Result<WeekOfMonth, CalendarError> {
let config = self.calendar.ok_or(CalendarError::MissingCalendar)?;
let day_of_month = self
.data
.day_of_month()
.ok_or(CalendarError::MissingInput("DateTimeInput::day_of_month"))?;
let iso_weekday = self
.data
.iso_weekday()
.ok_or(CalendarError::MissingInput("DateTimeInput::iso_weekday"))?;
Ok(config.week_of_month(day_of_month, iso_weekday))
}
fn week_of_year(&self) -> Result<(FormattableYear, WeekOfYear), CalendarError> {
let config = self.calendar.ok_or(CalendarError::MissingCalendar)?;
let day_of_year_info = self
.data
.day_of_year_info()
.ok_or(CalendarError::MissingInput(
"DateTimeInput::day_of_year_info",
))?;
let iso_weekday = self
.data
.iso_weekday()
.ok_or(CalendarError::MissingInput("DateTimeInput::iso_weekday"))?;
let week_of = config.week_of_year(day_of_year_info, iso_weekday)?;
let year = match week_of.unit {
RelativeUnit::Previous => day_of_year_info.prev_year,
RelativeUnit::Current => self
.data
.year()
.ok_or(CalendarError::MissingInput("DateTimeInput::year"))?,
RelativeUnit::Next => day_of_year_info.next_year,
};
Ok((year, WeekOfYear(week_of.week as u32)))
}
fn day_of_week_in_month(&self) -> Result<DayOfWeekInMonth, CalendarError> {
let day_of_month = self
.data
.day_of_month()
.ok_or(CalendarError::MissingInput("DateTimeInput::day_of_month"))?;
Ok(day_of_month.into())
}
fn flexible_day_period(&self) {
todo!("#487")
}
}
impl<C: Calendar, A: AsCalendar<Calendar = C>> DateInput for Date<A> {
type Calendar = C;
fn year(&self) -> Option<FormattableYear> {
Some(self.year())
}
fn month(&self) -> Option<FormattableMonth> {
Some(self.month())
}
fn day_of_month(&self) -> Option<DayOfMonth> {
Some(self.day_of_month())
}
fn iso_weekday(&self) -> Option<IsoWeekday> {
Some(self.day_of_week())
}
fn day_of_year_info(&self) -> Option<DayOfYearInfo> {
Some(self.day_of_year_info())
}
fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
self.calendar().any_calendar_kind()
}
fn to_iso(&self) -> Date<Iso> {
Date::to_iso(self)
}
}
impl<C: Calendar, A: AsCalendar<Calendar = C>> DateInput for DateTime<A> {
type Calendar = C;
fn year(&self) -> Option<FormattableYear> {
Some(self.date.year())
}
fn month(&self) -> Option<FormattableMonth> {
Some(self.date.month())
}
fn day_of_month(&self) -> Option<DayOfMonth> {
Some(self.date.day_of_month())
}
fn iso_weekday(&self) -> Option<IsoWeekday> {
Some(self.date.day_of_week())
}
fn day_of_year_info(&self) -> Option<DayOfYearInfo> {
Some(self.date.day_of_year_info())
}
fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
self.date.calendar().any_calendar_kind()
}
fn to_iso(&self) -> Date<Iso> {
Date::to_iso(&self.date)
}
}
impl<A: AsCalendar> IsoTimeInput for DateTime<A> {
fn hour(&self) -> Option<IsoHour> {
Some(self.time.hour)
}
fn minute(&self) -> Option<IsoMinute> {
Some(self.time.minute)
}
fn second(&self) -> Option<IsoSecond> {
Some(self.time.second)
}
fn nanosecond(&self) -> Option<NanoSecond> {
Some(self.time.nanosecond)
}
}
impl TimeZoneInput for CustomTimeZone {
fn gmt_offset(&self) -> Option<GmtOffset> {
self.gmt_offset
}
fn time_zone_id(&self) -> Option<TimeZoneBcp47Id> {
self.time_zone_id
}
fn metazone_id(&self) -> Option<MetazoneId> {
self.metazone_id
}
fn zone_variant(&self) -> Option<ZoneVariant> {
self.zone_variant
}
}
impl IsoTimeInput for Time {
fn hour(&self) -> Option<IsoHour> {
Some(self.hour)
}
fn minute(&self) -> Option<IsoMinute> {
Some(self.minute)
}
fn second(&self) -> Option<IsoSecond> {
Some(self.second)
}
fn nanosecond(&self) -> Option<NanoSecond> {
Some(self.nanosecond)
}
}