Skip to content

Commit

Permalink
Merge branch 'main' into ns
Browse files Browse the repository at this point in the history
  • Loading branch information
robertbastian committed Jul 23, 2024
2 parents bb0c305 + e2a03b2 commit 3b26ff8
Show file tree
Hide file tree
Showing 83 changed files with 1,662 additions and 222 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion components/calendar/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ calendrical_calculations = { workspace = true }
displaydoc = { workspace = true }
icu_provider = { workspace = true, features = ["macros"] }
icu_locale_core = { workspace = true }
ixdtf = { workspace = true, optional = true }
tinystr = { workspace = true, features = ["alloc", "zerovec"] }
zerovec = { workspace = true, features = ["derive"] }
writeable = { workspace = true }
Expand All @@ -46,7 +47,8 @@ criterion = { workspace = true }


[features]
default = ["compiled_data"]
default = ["compiled_data", "ixdtf"]
ixdtf = ["dep:ixdtf"]
logging = ["calendrical_calculations/logging"]
std = ["icu_provider/std", "icu_locale_core/std", "calendrical_calculations/std"]
serde = ["dep:serde", "zerovec/serde", "tinystr/serde", "icu_provider/serde"]
Expand Down
301 changes: 301 additions & 0 deletions components/calendar/src/ixdtf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
// 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 core::str::FromStr;

use crate::{AnyCalendar, Date, DateTime, Iso, RangeError, Time};
use ixdtf::parsers::records::IxdtfParseRecord;
use ixdtf::parsers::IxdtfParser;
use ixdtf::ParserError;

/// An error returned from parsing an IXDTF string to an `icu_calendar` type.
#[derive(Debug)]
#[non_exhaustive]
pub enum ParseError {
/// Syntax error in the IXDTF string.
Syntax(ParserError),
/// Value is out of range.
Range(RangeError),
/// The IXDTF is missing fields required for parsing into the chosen type.
MissingFields,
/// The IXDTF specifies an unknown calendar.
UnknownCalendar,
}

impl From<RangeError> for ParseError {
fn from(value: RangeError) -> Self {
Self::Range(value)
}
}

impl From<ParserError> for ParseError {
fn from(value: ParserError) -> Self {
Self::Syntax(value)
}
}

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 Date<Iso> {
/// Creates a [`Date`] in the ISO-8601 calendar from an IXDTF syntax string.
///
/// Ignores any calendar annotations in the string.
///
/// ✨ *Enabled with the `ixdtf` Cargo feature.*
///
/// # Examples
///
/// ```
/// use icu::calendar::Date;
///
/// let date = Date::try_iso_from_str("2024-07-17").unwrap();
///
/// assert_eq!(date.year().number, 2024);
/// assert_eq!(
/// date.month().code,
/// icu::calendar::types::MonthCode(tinystr::tinystr!(4, "M07"))
/// );
/// assert_eq!(date.day_of_month().0, 17);
/// ```
pub fn try_iso_from_str(ixdtf_str: &str) -> Result<Self, ParseError> {
Self::try_iso_from_utf8(ixdtf_str.as_bytes())
}

/// Creates a [`Date`] in the ISO-8601 calendar from an IXDTF syntax string.
///
/// See [`Self::try_iso_from_str()`].
///
/// ✨ *Enabled with the `ixdtf` Cargo feature.*
pub fn try_iso_from_utf8(ixdtf_str: &[u8]) -> Result<Self, ParseError> {
let ixdtf_record = IxdtfParser::from_utf8(ixdtf_str).parse()?;
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 = Self::try_new_iso_date(date_record.year, date_record.month, date_record.day)?;
Ok(date)
}
}

impl FromStr for Date<Iso> {
type Err = ParseError;
fn from_str(ixdtf_str: &str) -> Result<Self, Self::Err> {
Self::try_iso_from_str(ixdtf_str)
}
}

impl Date<AnyCalendar> {
/// Creates a [`Date`] in any calendar from an IXDTF syntax string with compiled data.
///
/// ✨ *Enabled with the `compiled_data` and `ixdtf` Cargo features.*
///
/// # Examples
///
/// ```
/// use icu::calendar::Date;
///
/// let date = Date::try_from_str("2024-07-17[u-ca=hebrew]").unwrap();
///
/// assert_eq!(date.year().number, 5784);
/// assert_eq!(
/// date.month().code,
/// icu::calendar::types::MonthCode(tinystr::tinystr!(4, "M10"))
/// );
/// assert_eq!(date.day_of_month().0, 11);
/// ```
#[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.*
///
/// See [`Self::try_from_str()`].
#[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_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)
}
}

#[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.
///
/// Does not support parsing an IXDTF string with a date and time; for that, use [`DateTime`].
///
/// ✨ *Enabled with the `ixdtf` Cargo feature.*
///
/// # Examples
///
/// ```
/// use icu::calendar::Time;
///
/// let time = Time::try_from_str("16:01:17.045").unwrap();
///
/// assert_eq!(time.hour.number(), 16);
/// assert_eq!(time.minute.number(), 1);
/// assert_eq!(time.second.number(), 17);
/// assert_eq!(time.nanosecond.number(), 45000000);
/// ```
pub fn try_from_str(ixdtf_str: &str) -> Result<Self, ParseError> {
Self::try_from_utf8(ixdtf_str.as_bytes())
}

/// Creates a [`Time`] in the ISO-8601 calendar from an IXDTF syntax string.
///
/// ✨ *Enabled with the `ixdtf` Cargo feature.*
///
/// See [`Self::try_from_str()`].
pub fn try_from_utf8(ixdtf_str: &[u8]) -> Result<Self, ParseError> {
let ixdtf_record = IxdtfParser::from_utf8(ixdtf_str).parse_time()?;
Self::try_from_ixdtf_record(&ixdtf_record)
}

fn try_from_ixdtf_record(ixdtf_record: &IxdtfParseRecord) -> Result<Self, ParseError> {
let time_record = ixdtf_record.time.ok_or(ParseError::MissingFields)?;
let time = Self::try_new(
time_record.hour,
time_record.minute,
time_record.second,
time_record.nanosecond,
)?;
Ok(time)
}
}

impl FromStr for Time {
type Err = ParseError;
fn from_str(ixdtf_str: &str) -> Result<Self, Self::Err> {
Self::try_from_str(ixdtf_str)
}
}

impl DateTime<Iso> {
/// Creates a [`DateTime`] in the ISO-8601 calendar from an IXDTF syntax string.
///
/// Ignores any calendar annotations in the string.
///
/// ✨ *Enabled with the `ixdtf` Cargo feature.*
///
/// # Examples
///
/// ```
/// use icu::calendar::DateTime;
///
/// let datetime = DateTime::try_iso_from_str("2024-07-17T16:01:17.045").unwrap();
///
/// assert_eq!(datetime.date.year().number, 2024);
/// assert_eq!(
/// datetime.date.month().code,
/// icu::calendar::types::MonthCode(tinystr::tinystr!(4, "M07"))
/// );
/// assert_eq!(datetime.date.day_of_month().0, 17);
///
/// 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);
/// ```
pub fn try_iso_from_str(ixdtf_str: &str) -> Result<Self, ParseError> {
Self::try_iso_from_utf8(ixdtf_str.as_bytes())
}

/// Creates a [`DateTime`] in the ISO-8601 calendar from an IXDTF syntax string.
///
/// ✨ *Enabled with the `ixdtf` Cargo feature.*
///
/// See [`Self::try_iso_from_str()`].
pub fn try_iso_from_utf8(ixdtf_str: &[u8]) -> Result<Self, ParseError> {
let ixdtf_record = IxdtfParser::from_utf8(ixdtf_str).parse()?;
Self::try_from_ixdtf_record(&ixdtf_record)
}

fn try_from_ixdtf_record(ixdtf_record: &IxdtfParseRecord) -> Result<Self, ParseError> {
let date = Date::<Iso>::try_from_ixdtf_record(ixdtf_record)?;
let time = Time::try_from_ixdtf_record(ixdtf_record)?;
Ok(Self::new(date, time))
}
}

impl FromStr for DateTime<Iso> {
type Err = ParseError;
fn from_str(ixdtf_str: &str) -> Result<Self, Self::Err> {
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().number, 5784);
/// assert_eq!(
/// datetime.date.month().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)
}
}
4 changes: 4 additions & 0 deletions components/calendar/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ pub mod hebrew;
pub mod indian;
pub mod islamic;
pub mod iso;
#[cfg(feature = "ixdtf")]
mod ixdtf;
pub mod japanese;
pub mod julian;
pub mod persian;
Expand All @@ -151,6 +153,8 @@ pub mod week {
pub use week_of::MIN_UNIT_DAYS;
}

#[cfg(feature = "ixdtf")]
pub use crate::ixdtf::ParseError;
#[doc(no_inline)]
pub use any_calendar::{AnyCalendar, AnyCalendarKind};
pub use calendar::Calendar;
Expand Down
1 change: 1 addition & 0 deletions components/datetime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ litemap = { workspace = true, optional = true }
[dev-dependencies]
icu = { path = "../../components/icu", default-features = false }
icu_benchmark_macros = { path = "../../tools/benchmark/macros" }
icu_calendar = { path = "../calendar", features = ["ixdtf"] }
icu_provider_adapters = { path = "../../provider/adapters" }
icu_provider_blob = { path = "../../provider/blob" }
litemap = { path = "../../utils/litemap" }
Expand Down
Loading

0 comments on commit 3b26ff8

Please sign in to comment.