Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create TaggedDate #5740

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 36 additions & 81 deletions components/calendar/src/ixdtf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use core::str::FromStr;

use crate::{AnyCalendar, Date, DateTime, Iso, RangeError, Time};
use crate::{AnyCalendar, AnyCalendarKind, Date, DateTime, Iso, RangeError, TaggedDate, Time};
use ixdtf::parsers::records::IxdtfParseRecord;
use ixdtf::parsers::IxdtfParser;
use ixdtf::ParseError as IxdtfError;
Expand Down Expand Up @@ -35,21 +35,23 @@ impl From<IxdtfError> for ParseError {
}
}

impl AnyCalendar {
#[cfg(feature = "compiled_data")]
fn try_from_ixdtf_record(ixdtf_record: &IxdtfParseRecord) -> Result<Self, ParseError> {
let calendar_id = ixdtf_record.calendar.unwrap_or(b"iso");
let calendar_kind = crate::AnyCalendarKind::get_for_bcp47_bytes(calendar_id)
.ok_or(ParseError::UnknownCalendar)?;
let calendar = AnyCalendar::new(calendar_kind);
Ok(calendar)
impl AnyCalendarKind {
fn try_from_ixdtf_record(ixdtf_record: &IxdtfParseRecord) -> Result<Option<Self>, ParseError> {
let Some(calendar_id) = ixdtf_record.calendar else {
return Ok(None);
};
match AnyCalendarKind::get_for_bcp47_bytes(calendar_id) {
Some(kind) => Ok(Some(kind)),
None => Err(ParseError::UnknownCalendar),
}
}
}

impl Date<Iso> {
/// Creates a [`Date`] in the ISO-8601 calendar from an IXDTF syntax string.
///
/// Ignores any calendar annotations in the string.
/// Ignores any calendar annotations in the string. This might not be what
/// you want! Consider using [`TaggedDate::try_from_str`].
///
/// ✨ *Enabled with the `ixdtf` Cargo feature.*
///
Expand Down Expand Up @@ -95,47 +97,52 @@ impl FromStr for Date<Iso> {
}
}

impl Date<AnyCalendar> {
/// Creates a [`Date`] in any calendar from an IXDTF syntax string with compiled data.
impl TaggedDate {
/// Creates a [`TaggedDate`] from an IXDTF syntax string, retaining the
/// calendar annotation if present.
///
/// ✨ *Enabled with the `compiled_data` and `ixdtf` Cargo features.*
/// ✨ *Enabled with the `ixdtf` Cargo feature.*
///
/// # Examples
///
/// ```
/// use icu::calendar::Date;
/// use icu::calendar::TaggedDate;
///
/// let date = Date::try_from_str("2024-07-17[u-ca=hebrew]").unwrap();
/// let date = TaggedDate::try_from_str("2024-10-28[u-ca=hebrew]")
/// .unwrap()
/// .to_any_date();
///
/// assert_eq!(date.year().era_year_or_extended(), 5784);
/// assert_eq!(date.year().era_year_or_extended(), 5785);
/// assert_eq!(
/// date.month().standard_code,
/// icu::calendar::types::MonthCode(tinystr::tinystr!(4, "M10"))
/// icu::calendar::types::MonthCode(tinystr::tinystr!(4, "M01"))
/// );
/// assert_eq!(date.day_of_month().0, 11);
/// assert_eq!(date.day_of_month().0, 26);
/// ```
#[cfg(feature = "compiled_data")]
pub fn try_from_str(ixdtf_str: &str) -> Result<Self, ParseError> {
Self::try_from_utf8(ixdtf_str.as_bytes())
}

/// Creates a [`Date`] in any calendar from an IXDTF syntax string with compiled data.
///
/// ✨ *Enabled with the `compiled_data` and `ixdtf` Cargo features.*
/// Creates a [`TaggedDate`] from an IXDTF syntax string, retaining the
/// calendar annotation if present.
///
/// See [`Self::try_from_str()`].
#[cfg(feature = "compiled_data")]
///
/// ✨ *Enabled with the `ixdtf` Cargo feature.*
pub fn try_from_utf8(ixdtf_str: &[u8]) -> Result<Self, ParseError> {
let ixdtf_record = IxdtfParser::from_utf8(ixdtf_str).parse()?;
let iso_date = Date::<Iso>::try_from_ixdtf_record(&ixdtf_record)?;
let calendar = AnyCalendar::try_from_ixdtf_record(&ixdtf_record)?;
let date = iso_date.to_any().to_calendar(calendar);
Ok(date)
Self::try_from_ixdtf_record(&ixdtf_record)
}

fn try_from_ixdtf_record(ixdtf_record: &IxdtfParseRecord) -> Result<Self, ParseError> {
let date_record = ixdtf_record.date.ok_or(ParseError::MissingFields)?;
let date = Date::try_new_iso(date_record.year, date_record.month, date_record.day)?;
let kind = AnyCalendarKind::try_from_ixdtf_record(ixdtf_record)?;
Ok(Self::from_iso_date_and_kind(date, kind))
}
}

#[cfg(feature = "compiled_data")]
impl FromStr for Date<AnyCalendar> {
impl FromStr for TaggedDate {
type Err = ParseError;
fn from_str(ixdtf_str: &str) -> Result<Self, Self::Err> {
Self::try_from_str(ixdtf_str)
Expand Down Expand Up @@ -247,55 +254,3 @@ impl FromStr for DateTime<Iso> {
Self::try_iso_from_str(ixdtf_str)
}
}

impl DateTime<AnyCalendar> {
/// Creates a [`DateTime`] in any calendar from an IXDTF syntax string with compiled data.
///
/// ✨ *Enabled with the `compiled_data` and `ixdtf` Cargo features.*
///
/// # Examples
///
/// ```
/// use icu::calendar::DateTime;
///
/// let datetime = DateTime::try_from_str("2024-07-17T16:01:17.045[u-ca=hebrew]").unwrap();
///
/// assert_eq!(datetime.date.year().era_year_or_extended(), 5784);
/// assert_eq!(
/// datetime.date.month().standard_code,
/// icu::calendar::types::MonthCode(tinystr::tinystr!(4, "M10"))
/// );
/// assert_eq!(datetime.date.day_of_month().0, 11);
///
/// assert_eq!(datetime.time.hour.number(), 16);
/// assert_eq!(datetime.time.minute.number(), 1);
/// assert_eq!(datetime.time.second.number(), 17);
/// assert_eq!(datetime.time.nanosecond.number(), 45000000);
/// ```
#[cfg(feature = "compiled_data")]
pub fn try_from_str(ixdtf_str: &str) -> Result<Self, ParseError> {
Self::try_from_utf8(ixdtf_str.as_bytes())
}

/// Creates a [`DateTime`] in any calendar from an IXDTF syntax string with compiled data.
///
/// See [`Self::try_from_str()`].
///
/// ✨ *Enabled with the `compiled_data` and `ixdtf` Cargo features.*
#[cfg(feature = "compiled_data")]
pub fn try_from_utf8(ixdtf_str: &[u8]) -> Result<Self, ParseError> {
let ixdtf_record = IxdtfParser::from_utf8(ixdtf_str).parse()?;
let iso_datetime = DateTime::<Iso>::try_from_ixdtf_record(&ixdtf_record)?;
let calendar = AnyCalendar::try_from_ixdtf_record(&ixdtf_record)?;
let datetime = iso_datetime.to_any().to_calendar(calendar);
Ok(datetime)
}
}

#[cfg(feature = "compiled_data")]
impl FromStr for DateTime<AnyCalendar> {
type Err = ParseError;
fn from_str(ixdtf_str: &str) -> Result<Self, Self::Err> {
Self::try_from_str(ixdtf_str)
}
}
2 changes: 2 additions & 0 deletions components/calendar/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ mod julian;
mod persian;
pub mod provider;
mod roc;
mod tagged;
#[cfg(test)]
mod tests;
pub mod types;
Expand Down Expand Up @@ -211,4 +212,5 @@ pub use error::{DateError, RangeError};
pub use gregorian::Gregorian;
#[doc(no_inline)]
pub use iso::Iso;
pub use tagged::TaggedDate;
pub use types::Time;
101 changes: 101 additions & 0 deletions components/calendar/src/tagged.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

use crate::AnyCalendar;
use crate::AnyCalendarKind;
use crate::Date;
use crate::Iso;
use icu_provider::prelude::*;

/// A date in the ISO calendar tagged with an optional [`AnyCalendarKind`].
///
/// This is a useful intermediate type when the calendar is known but you wish
/// to defer data loading and conversion into the calendar.
///
/// This is the return type from parsing an IXDTF string.
///
/// # Examples
///
/// ```
/// use icu::calendar::AnyCalendarKind;
/// use icu::calendar::Date;
/// use icu::calendar::TaggedDate;
/// use icu::locale::locale;
///
/// let preferred_calendar = AnyCalendarKind::get_for_locale(&locale!("und-u-ca-chinese"));
///
/// // Create a TaggedDate without loading data
/// let date = TaggedDate::from_iso_date_and_kind(
/// Date::try_new_iso(2024, 10, 28).unwrap(),
/// preferred_calendar,
/// );
///
/// // Later, convert it to a Date<AnyCalendar>
/// let any_date = date.to_any_date();
/// assert_eq!(any_date.year().era_year_or_extended(), 4661);
/// ```
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct TaggedDate {
iso: Date<Iso>,
kind: Option<AnyCalendarKind>,
}

impl TaggedDate {
/// Creates a new [`TaggedDate`].
pub fn from_iso_date_and_kind(iso: Date<Iso>, kind: Option<AnyCalendarKind>) -> Self {
Self { iso, kind }
}

/// Converts this date to an ISO date, dropping the tag, if any.
pub fn to_iso_date(self) -> Date<Iso> {
self.iso
}

/// Converts this date to a calendared date with compiled data.
#[cfg(feature = "compiled_data")]
pub fn to_any_date(self) -> Date<AnyCalendar> {
if let Some(kind) = self.kind {
let calendar = AnyCalendar::new(kind);
Date::new_from_iso(self.iso, calendar)
} else {
self.iso.to_any()
}
}

/// Converts this date to a calendared date, loading data from a buffer provider.
#[cfg(feature = "serde")]
pub fn to_any_date_with_buffer_provider<P>(
self,
provider: &P,
) -> Result<Date<AnyCalendar>, DataError>
where
P: BufferProvider + ?Sized,
{
if let Some(kind) = self.kind {
let calendar = AnyCalendar::try_new_with_buffer_provider(provider, kind)?;
Ok(Date::new_from_iso(self.iso, calendar))
} else {
Ok(self.iso.to_any())
}
}

/// Converts this date to a calendared date, loading data from a data provider.
pub fn to_any_date_unstable<P>(self, provider: &P) -> Result<Date<AnyCalendar>, DataError>
where
P: DataProvider<crate::provider::JapaneseErasV1Marker>
+ DataProvider<crate::provider::JapaneseExtendedErasV1Marker>
+ DataProvider<crate::provider::ChineseCacheV1Marker>
+ DataProvider<crate::provider::DangiCacheV1Marker>
+ DataProvider<crate::provider::IslamicObservationalCacheV1Marker>
+ DataProvider<crate::provider::IslamicUmmAlQuraCacheV1Marker>
+ ?Sized,
{
if let Some(kind) = self.kind {
let calendar = AnyCalendar::try_new_unstable(provider, kind)?;
Ok(Date::new_from_iso(self.iso, calendar))
} else {
Ok(self.iso.to_any())
}
}
}
Loading