Skip to content

Commit

Permalink
Add calendar argument to IXDTF parsing (#5982)
Browse files Browse the repository at this point in the history
Part of #5739
  • Loading branch information
robertbastian authored Jan 14, 2025
1 parent 77ee3e8 commit 7fdebb5
Show file tree
Hide file tree
Showing 22 changed files with 202 additions and 185 deletions.
118 changes: 59 additions & 59 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::{AnyCalendarKind, AsCalendar, Calendar, Date, DateTime, Iso, RangeError, Time};
use ixdtf::parsers::records::IxdtfParseRecord;
use ixdtf::parsers::IxdtfParser;
use ixdtf::ParseError as IxdtfError;
Expand All @@ -23,6 +23,9 @@ pub enum ParseError {
MissingFields,
/// The IXDTF specifies an unknown calendar.
UnknownCalendar,
/// Expected a different calendar.
#[displaydoc("Expected calendar {0} but found calendar {1}")]
MismatchedCalendar(AnyCalendarKind, AnyCalendarKind),
}

impl From<RangeError> for ParseError {
Expand All @@ -37,17 +40,6 @@ 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_for_kind(calendar_kind);
Ok(calendar)
}
}

impl Date<Iso> {
/// Creates a [`Date`] in the ISO-8601 calendar from an IXDTF syntax string.
///
Expand Down Expand Up @@ -97,53 +89,61 @@ impl FromStr for Date<Iso> {
}
}

impl Date<AnyCalendar> {
/// Creates a [`Date`] in any calendar from an IXDTF syntax string with compiled data.
impl<A: AsCalendar> Date<A> {
/// Creates a [`Date`] in the given calendar from an IXDTF syntax string.
///
/// ✨ *Enabled with the `compiled_data` and `ixdtf` Cargo features.*
/// Ignores any calendar annotations in the string.
///
/// ✨ *Enabled with the `ixdtf` Cargo feature.*
///
/// # Examples
///
/// ```
/// use icu::calendar::Date;
/// use icu::calendar::{Date, Gregorian};
///
/// let date = Date::try_from_str("2024-07-17[u-ca=hebrew]").unwrap();
/// let date = Date::try_from_str("2024-07-17", Gregorian).unwrap();
/// let date = Date::try_from_str("2024-07-17[u-ca=gregory]", Gregorian).unwrap();
/// let _ = Date::try_from_str("2024-07-17[u-ca=julian]", Gregorian).unwrap_err();
///
/// assert_eq!(date.year().era_year_or_extended(), 5784);
/// assert_eq!(date.year().era_year_or_extended(), 2024);
/// assert_eq!(
/// date.month().standard_code,
/// icu::calendar::types::MonthCode(tinystr::tinystr!(4, "M10"))
/// icu::calendar::types::MonthCode(tinystr::tinystr!(4, "M07"))
/// );
/// assert_eq!(date.day_of_month().0, 11);
/// assert_eq!(date.day_of_month().0, 17);
/// ```
#[cfg(feature = "compiled_data")]
pub fn try_from_str(ixdtf_str: &str) -> Result<Self, ParseError> {
Self::try_from_utf8(ixdtf_str.as_bytes())
pub fn try_from_str(ixdtf_str: &str, calendar: A) -> Result<Self, ParseError> {
Self::try_from_utf8(ixdtf_str.as_bytes(), calendar)
}

/// 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 [`Date`] in the given calendar from an IXDTF syntax string.
///
/// See [`Self::try_from_str()`].
#[cfg(feature = "compiled_data")]
pub fn try_from_utf8(ixdtf_str: &[u8]) -> Result<Self, ParseError> {
///
/// ✨ *Enabled with the `ixdtf` Cargo feature.*
pub fn try_from_utf8(ixdtf_str: &[u8], calendar: A) -> 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);
if let Some(ixdtf_calendar) = ixdtf_record.calendar {
let parsed_calendar = crate::AnyCalendarKind::get_for_bcp47_bytes(ixdtf_calendar)
.ok_or(ParseError::UnknownCalendar)?;
let expected_calendar = calendar
.as_calendar()
.any_calendar_kind()
.ok_or(ParseError::UnknownCalendar)?;
if parsed_calendar != expected_calendar {
return Err(ParseError::MismatchedCalendar(
expected_calendar,
parsed_calendar,
));
}
}
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)?
.to_calendar(calendar);
Ok(date)
}
}

#[cfg(feature = "compiled_data")]
impl FromStr for Date<AnyCalendar> {
type Err = ParseError;
fn from_str(ixdtf_str: &str) -> Result<Self, Self::Err> {
Self::try_from_str(ixdtf_str)
}
}

impl Time {
/// Creates a [`Time`] from an IXDTF syntax string of a time.
///
Expand Down Expand Up @@ -251,18 +251,17 @@ impl FromStr for DateTime<Iso> {
}
}

impl DateTime<AnyCalendar> {
impl<A: AsCalendar> DateTime<A> {
/// 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::cal::Hebrew;
/// use icu::calendar::DateTime;
///
/// let datetime =
/// DateTime::try_from_str("2024-07-17T16:01:17.045[u-ca=hebrew]").unwrap();
/// DateTime::try_from_str("2024-07-17T16:01:17.045[u-ca=hebrew]", Hebrew).unwrap();
///
/// assert_eq!(datetime.date.year().era_year_or_extended(), 5784);
/// assert_eq!(
Expand All @@ -276,30 +275,31 @@ impl DateTime<AnyCalendar> {
/// 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())
pub fn try_from_str(ixdtf_str: &str, calendar: A) -> Result<Self, ParseError> {
Self::try_from_utf8(ixdtf_str.as_bytes(), calendar)
}

/// 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> {
pub fn try_from_utf8(ixdtf_str: &[u8], calendar: A) -> Result<Self, ParseError> {
let ixdtf_record = IxdtfParser::from_utf8(ixdtf_str).parse()?;
if let Some(ixdtf_calendar) = ixdtf_record.calendar {
let parsed_calendar = crate::AnyCalendarKind::get_for_bcp47_bytes(ixdtf_calendar)
.ok_or(ParseError::UnknownCalendar)?;
let expected_calendar = calendar
.as_calendar()
.any_calendar_kind()
.ok_or(ParseError::UnknownCalendar)?;
if parsed_calendar != expected_calendar {
return Err(ParseError::MismatchedCalendar(
expected_calendar,
parsed_calendar,
));
}
}
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)
}
}
14 changes: 6 additions & 8 deletions components/datetime/src/combo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use crate::{provider::neo::*, scaffold::*};
/// .unwrap();
///
/// let zdt = IxdtfParser::new()
/// .try_location_only_from_str("2024-10-18T15:44[America/Los_Angeles]")
/// .try_location_only_iso_from_str("2024-10-18T15:44[America/Los_Angeles]")
/// .unwrap();
///
/// assert_writeable_eq!(
Expand All @@ -63,9 +63,8 @@ use crate::{provider::neo::*, scaffold::*};
/// .unwrap();
///
/// let zdt = IxdtfParser::new()
/// .try_location_only_iso_from_str("2024-10-18T15:44[America/Los_Angeles]")
/// .unwrap()
/// .to_calendar(Gregorian);
/// .try_location_only_from_str("2024-10-18T15:44[America/Los_Angeles]", Gregorian)
/// .unwrap();
///
/// assert_writeable_eq!(
/// formatter.format(&zdt),
Expand All @@ -91,7 +90,7 @@ use crate::{provider::neo::*, scaffold::*};
/// .unwrap();
///
/// let zdt = IxdtfParser::new()
/// .try_location_only_from_str("2024-10-18T15:44[America/Los_Angeles]")
/// .try_location_only_iso_from_str("2024-10-18T15:44[America/Los_Angeles]")
/// .unwrap();
///
/// assert_writeable_eq!(
Expand All @@ -117,9 +116,8 @@ use crate::{provider::neo::*, scaffold::*};
/// .unwrap();
///
/// let zdt = IxdtfParser::new()
/// .try_iso_from_str("2024-10-18T15:44-0700[America/Los_Angeles]")
/// .unwrap()
/// .to_calendar(Gregorian);
/// .try_from_str("2024-10-18T15:44-0700[America/Los_Angeles]", Gregorian)
/// .unwrap();
///
/// assert_writeable_eq!(
/// formatter.format(&zdt),
Expand Down
2 changes: 1 addition & 1 deletion components/datetime/src/parts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
//! )
//! .unwrap();
//!
//! let dtz = IxdtfParser::new().try_from_str("2023-11-20T11:35:03.5+00:00[Europe/London]").unwrap();
//! let dtz = IxdtfParser::new().try_from_str("2023-11-20T11:35:03.5+00:00[Europe/London]", Gregorian).unwrap();
//!
//! // Missing data is filled in on a best-effort basis, and an error is signaled.
//! assert_writeable_parts_eq!(
Expand Down
10 changes: 4 additions & 6 deletions components/datetime/src/pattern/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,13 +161,11 @@ where
/// use writeable::assert_try_writeable_eq;
///
/// let mut london_winter = IxdtfParser::new()
/// .try_from_str("2024-01-01T00:00:00+00:00[Europe/London]")
/// .unwrap()
/// .to_calendar(Gregorian);
/// .try_from_str("2024-01-01T00:00:00+00:00[Europe/London]", Gregorian)
/// .unwrap();
/// let mut london_summer = IxdtfParser::new()
/// .try_from_str("2024-07-01T00:00:00+01:00[Europe/London]")
/// .unwrap()
/// .to_calendar(Gregorian);
/// .try_from_str("2024-07-01T00:00:00+01:00[Europe/London]", Gregorian)
/// .unwrap();
///
/// let mut names = TypedDateTimeNames::<Gregorian, ZoneFieldSet>::try_new(
/// locale!("en-GB").into(),
Expand Down
24 changes: 12 additions & 12 deletions components/datetime/src/pattern/names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ size_test!(
/// // The pattern string contains lots of symbols including "E", "MMM", and "a",
/// // but we did not load any data!
///
/// let mut dtz = IxdtfParser::new().try_from_str("2023-11-20T11:35:03+00:00[Europe/London]").unwrap().to_calendar(Gregorian);
/// let mut dtz = IxdtfParser::new().try_from_str("2023-11-20T11:35:03+00:00[Europe/London]", Gregorian).unwrap();
///
/// // Missing data is filled in on a best-effort basis, and an error is signaled.
/// assert_try_writeable_parts_eq!(
Expand Down Expand Up @@ -1016,11 +1016,11 @@ impl<C: CldrCalendar, FSet: DateTimeNamesMarker> TypedDateTimeNames<C, FSet> {
/// use writeable::assert_try_writeable_eq;
///
/// let mut zone_london_winter = IxdtfParser::new()
/// .try_from_str("2024-01-01T00:00:00+00:00[Europe/London]")
/// .try_from_str("2024-01-01T00:00:00+00:00[Europe/London]", Gregorian)
/// .unwrap()
/// .zone;
/// let mut zone_london_summer = IxdtfParser::new()
/// .try_from_str("2024-07-01T00:00:00+01:00[Europe/London]")
/// .try_from_str("2024-07-01T00:00:00+01:00[Europe/London]", Gregorian)
/// .unwrap()
/// .zone;
///
Expand Down Expand Up @@ -1128,7 +1128,7 @@ impl<C: CldrCalendar, FSet: DateTimeNamesMarker> TypedDateTimeNames<C, FSet> {
/// use writeable::assert_try_writeable_eq;
///
/// let mut zone_london_winter = IxdtfParser::new()
/// .try_from_str("2024-01-01T00:00:00+00:00[Europe/London]")
/// .try_from_str("2024-01-01T00:00:00+00:00[Europe/London]", Gregorian)
/// .unwrap()
/// .zone;
///
Expand Down Expand Up @@ -1195,11 +1195,11 @@ impl<C: CldrCalendar, FSet: DateTimeNamesMarker> TypedDateTimeNames<C, FSet> {
/// use writeable::assert_try_writeable_eq;
///
/// let mut zone_london_winter = IxdtfParser::new()
/// .try_from_str("2024-01-01T00:00:00+00:00[Europe/London]")
/// .try_from_str("2024-01-01T00:00:00+00:00[Europe/London]", Gregorian)
/// .unwrap()
/// .zone;
/// let mut zone_london_summer = IxdtfParser::new()
/// .try_from_str("2024-07-01T00:00:00+01:00[Europe/London]")
/// .try_from_str("2024-07-01T00:00:00+01:00[Europe/London]", Gregorian)
/// .unwrap()
/// .zone;
///
Expand Down Expand Up @@ -1272,11 +1272,11 @@ impl<C: CldrCalendar, FSet: DateTimeNamesMarker> TypedDateTimeNames<C, FSet> {
/// use writeable::assert_try_writeable_eq;
///
/// let mut zone_london_winter = IxdtfParser::new()
/// .try_from_str("2024-01-01T00:00:00+00:00[Europe/London]")
/// .try_from_str("2024-01-01T00:00:00+00:00[Europe/London]", Gregorian)
/// .unwrap()
/// .zone;
/// let mut zone_london_summer = IxdtfParser::new()
/// .try_from_str("2024-07-01T00:00:00+01:00[Europe/London]")
/// .try_from_str("2024-07-01T00:00:00+01:00[Europe/London]", Gregorian)
/// .unwrap()
/// .zone;
///
Expand Down Expand Up @@ -1349,11 +1349,11 @@ impl<C: CldrCalendar, FSet: DateTimeNamesMarker> TypedDateTimeNames<C, FSet> {
/// use writeable::assert_try_writeable_eq;
///
/// let mut zone_london_winter = IxdtfParser::new()
/// .try_from_str("2024-01-01T00:00:00+00:00[Europe/London]")
/// .try_from_str("2024-01-01T00:00:00+00:00[Europe/London]", Gregorian)
/// .unwrap()
/// .zone;
/// let mut zone_london_summer = IxdtfParser::new()
/// .try_from_str("2024-07-01T00:00:00+01:00[Europe/London]")
/// .try_from_str("2024-07-01T00:00:00+01:00[Europe/London]", Gregorian)
/// .unwrap()
/// .zone;
///
Expand Down Expand Up @@ -1426,11 +1426,11 @@ impl<C: CldrCalendar, FSet: DateTimeNamesMarker> TypedDateTimeNames<C, FSet> {
/// use writeable::assert_try_writeable_eq;
///
/// let mut zone_london_winter = IxdtfParser::new()
/// .try_from_str("2024-01-01T00:00:00+00:00[Europe/London]")
/// .try_from_str("2024-01-01T00:00:00+00:00[Europe/London]", Gregorian)
/// .unwrap()
/// .zone;
/// let mut zone_london_summer = IxdtfParser::new()
/// .try_from_str("2024-07-01T00:00:00+01:00[Europe/London]")
/// .try_from_str("2024-07-01T00:00:00+01:00[Europe/London]", Gregorian)
/// .unwrap()
/// .zone;
///
Expand Down
5 changes: 2 additions & 3 deletions components/datetime/src/pattern/pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,8 @@ size_test!(DateTimePattern, date_time_pattern_size, 32);
/// .unwrap()
/// // The pattern can depend on the datetime being formatted.
/// .format(
/// &Date::try_new_iso(2024, 1, 1)
/// .unwrap()
/// .to_calendar(Gregorian),
/// &Date::try_new_gregorian(2024, 1, 1)
/// .unwrap(),
/// )
/// .pattern();
/// assert_writeable_eq!(data_pattern, pattern_str);
Expand Down
10 changes: 4 additions & 6 deletions components/datetime/tests/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ use icu_timezone::{models, CustomZonedDateTime, IxdtfParser, TimeZoneInfo, ZoneV
/// assert_eq!(u32::from(date.time.nanosecond), 101_000_000);
/// ```
pub fn parse_gregorian_from_str(input: &str) -> DateTime<Gregorian> {
let datetime_iso = DateTime::try_iso_from_str(input).unwrap();
datetime_iso.to_calendar(Gregorian)
DateTime::try_from_str(input, Gregorian).unwrap()
}

/// Parse a [`DateTime`] and [`TimeZoneInfo`] from a string.
Expand All @@ -58,10 +57,10 @@ pub fn parse_gregorian_from_str(input: &str) -> DateTime<Gregorian> {
pub fn parse_zoned_gregorian_from_str(
input: &str,
) -> CustomZonedDateTime<Gregorian, TimeZoneInfo<models::Full>> {
let iso_zdt = match IxdtfParser::new().try_iso_from_str(input) {
match IxdtfParser::new().try_from_str(input, Gregorian) {
Ok(zdt) => zdt,
Err(icu_timezone::ParseError::MismatchedTimeZoneFields) => {
match IxdtfParser::new().try_loose_iso_from_str(input) {
match IxdtfParser::new().try_loose_from_str(input, Gregorian) {
Ok(zdt) => {
CustomZonedDateTime {
date: zdt.date,
Expand All @@ -74,6 +73,5 @@ pub fn parse_zoned_gregorian_from_str(
}
}
Err(e) => panic!("could not parse input: {input}: {e:?}"),
};
iso_zdt.to_calendar(Gregorian)
}
}
Loading

0 comments on commit 7fdebb5

Please sign in to comment.