Skip to content
Merged
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
4 changes: 2 additions & 2 deletions components/calendar/src/cal/chinese.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1779,7 +1779,7 @@ mod test {
fn test_from_fields_constrain() {
let fields = DateFields {
day: Some(31),
month_code: Some(MonthCode("M01".parse().unwrap())),
month_code: Some(b"M01"),
extended_year: Some(1972),
..Default::default()
};
Expand All @@ -1799,7 +1799,7 @@ mod test {
// 2022 did not have M01L, the month should be constrained back down
let fields = DateFields {
day: Some(1),
month_code: Some(MonthCode("M01L".parse().unwrap())),
month_code: Some(b"M01L"),
extended_year: Some(2022),
..Default::default()
};
Expand Down
4 changes: 2 additions & 2 deletions components/calendar/src/cal/coptic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ impl Date<Coptic> {
mod tests {
use super::*;
use crate::options::{DateFromFieldsOptions, MissingFieldsStrategy, Overflow};
use crate::types::{DateFields, MonthCode};
use crate::types::DateFields;

#[test]
fn test_coptic_regression() {
Expand All @@ -280,7 +280,7 @@ mod tests {
// M13-7 is not a real day, however this should resolve to M12-6
// with Overflow::Constrain
let fields = DateFields {
month_code: Some(MonthCode("M13".parse().unwrap())),
month_code: Some(b"M13"),
day: Some(7),
..Default::default()
};
Expand Down
4 changes: 2 additions & 2 deletions components/calendar/src/calendar_arithmetic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ impl<C: DateFieldsResolver> ArithmeticDate<C> {
MissingFieldsStrategy::Ecma => {
match (fields.month_code, fields.ordinal_month) {
(Some(month_code), None) => {
let validated = month_code.validated()?;
let validated = ValidMonthCode::from_bytes(month_code)?;
valid_month_code = Some(validated);
calendar.reference_year_from_month_day(validated, day)?
}
Expand Down Expand Up @@ -296,7 +296,7 @@ impl<C: DateFieldsResolver> ArithmeticDate<C> {
Some(month_code) => {
let validated = match valid_month_code {
Some(validated) => validated,
None => month_code.validated()?,
None => ValidMonthCode::from_bytes(month_code)?,
};
let computed_month = calendar.ordinal_month_from_code(&year, validated, options)?;
if let Some(ordinal_month) = fields.ordinal_month {
Expand Down
15 changes: 4 additions & 11 deletions components/calendar/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,10 @@ pub enum DateFromFieldsError {
/// use icu_calendar::Iso;
/// use icu_calendar::types::MonthCode;
/// use icu_calendar::types::DateFields;
/// use tinystr::tinystr;
///
/// let mut fields = DateFields::default();
/// fields.extended_year = Some(2000);
/// fields.month_code = Some(MonthCode(tinystr!(4, "????")));
/// fields.month_code = Some(b"????");
/// fields.day = Some(1);
///
/// let err = Date::try_from_fields(
Expand All @@ -114,12 +113,10 @@ pub enum DateFromFieldsError {
/// use icu_calendar::error::DateFromFieldsError;
/// use icu_calendar::cal::Hebrew;
/// use icu_calendar::types::DateFields;
/// use icu_calendar::types::MonthCode;
/// use tinystr::tinystr;
///
/// let mut fields = DateFields::default();
/// fields.extended_year = Some(5783);
/// fields.month_code = Some(MonthCode::new_normal(13).unwrap());
/// fields.month_code = Some(b"M13");
/// fields.day = Some(1);
///
/// let err = Date::try_from_fields(
Expand All @@ -141,12 +138,10 @@ pub enum DateFromFieldsError {
/// use icu_calendar::error::DateFromFieldsError;
/// use icu_calendar::cal::Hebrew;
/// use icu_calendar::types::DateFields;
/// use icu_calendar::types::MonthCode;
/// use tinystr::tinystr;
///
/// let mut fields = DateFields::default();
/// fields.extended_year = Some(5783);
/// fields.month_code = Some(MonthCode::new_leap(5).unwrap());
/// fields.month_code = Some(b"M05L");
/// fields.day = Some(1);
///
/// let err = Date::try_from_fields(
Expand Down Expand Up @@ -204,12 +199,11 @@ pub enum DateFromFieldsError {
/// use icu_calendar::error::DateFromFieldsError;
/// use icu_calendar::cal::Hebrew;
/// use icu_calendar::types::DateFields;
/// use icu_calendar::types::MonthCode;
/// use tinystr::tinystr;
///
/// let mut fields = DateFields::default();
/// fields.extended_year = Some(5783);
/// fields.month_code = Some(MonthCode(tinystr!(4, "M06")));
/// fields.month_code = Some(b"M06");
/// fields.ordinal_month = Some(6);
/// fields.day = Some(1);
///
Expand Down Expand Up @@ -242,7 +236,6 @@ pub enum DateFromFieldsError {
/// use icu_calendar::error::DateFromFieldsError;
/// use icu_calendar::cal::Hebrew;
/// use icu_calendar::types::DateFields;
/// use icu_calendar::types::MonthCode;
/// use tinystr::tinystr;
///
/// let mut fields = DateFields::default();
Expand Down
24 changes: 7 additions & 17 deletions components/calendar/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,13 @@ pub struct DateFromFieldsOptions {
/// ```
/// use icu::calendar::Date;
/// use icu::calendar::types::DateFields;
/// use icu::calendar::types::MonthCode;
/// use icu::calendar::options::MissingFieldsStrategy;
/// use icu::calendar::options::DateFromFieldsOptions;
/// use icu::calendar::Iso;
///
/// // These options are missing a year.
/// let mut fields = DateFields::default();
/// fields.month_code = MonthCode::new_normal(2);
/// fields.month_code = Some(b"M02");
/// fields.day = Some(1);
///
/// let options_default = DateFromFieldsOptions::default();
Expand Down Expand Up @@ -232,16 +231,14 @@ pub enum Overflow {
/// use icu_calendar::options::DateFromFieldsOptions;
/// use icu_calendar::options::Overflow;
/// use icu_calendar::types::DateFields;
/// use icu_calendar::types::MonthCode;
/// use tinystr::tinystr;
///
/// let mut options = DateFromFieldsOptions::default();
/// options.overflow = Some(Overflow::Constrain);
///
/// // 5784, a leap year, contains M05L, but the day is too big.
/// let mut fields = DateFields::default();
/// fields.extended_year = Some(5784);
/// fields.month_code = Some(MonthCode(tinystr!(4, "M05L")));
/// fields.month_code = Some(b"M05L");
/// fields.day = Some(50);
///
/// let date = Date::try_from_fields(
Expand All @@ -253,7 +250,7 @@ pub enum Overflow {
///
/// // Constrained to the 30th day of M05L of year 5784
/// assert_eq!(date.year().extended_year(), 5784);
/// assert_eq!(date.month().standard_code, MonthCode(tinystr!(4, "M05L")));
/// assert_eq!(date.month().standard_code.0, "M05L");
/// assert_eq!(date.day_of_month().0, 30);
///
/// // 5785, a common year, does not contain M05L.
Expand All @@ -268,7 +265,7 @@ pub enum Overflow {
///
/// // Constrained to the 29th day of M06 of year 5785
/// assert_eq!(date.year().extended_year(), 5785);
/// assert_eq!(date.month().standard_code, MonthCode(tinystr!(4, "M06")));
/// assert_eq!(date.month().standard_code.0, "M06");
/// assert_eq!(date.day_of_month().0, 29);
/// ```
Constrain,
Expand All @@ -283,7 +280,6 @@ pub enum Overflow {
/// use icu_calendar::options::DateFromFieldsOptions;
/// use icu_calendar::options::Overflow;
/// use icu_calendar::types::DateFields;
/// use icu_calendar::types::MonthCode;
/// use tinystr::tinystr;
///
/// let mut options = DateFromFieldsOptions::default();
Expand All @@ -292,7 +288,7 @@ pub enum Overflow {
/// // 5784, a leap year, contains M05L, but the day is too big.
/// let mut fields = DateFields::default();
/// fields.extended_year = Some(5784);
/// fields.month_code = Some(MonthCode(tinystr!(4, "M05L")));
/// fields.month_code = Some(b"M05L");
/// fields.day = Some(50);
///
/// let err = Date::try_from_fields(
Expand Down Expand Up @@ -384,11 +380,7 @@ pub enum MissingFieldsStrategy {

#[cfg(test)]
mod tests {
use crate::{
error::DateFromFieldsError,
types::{DateFields, MonthCode},
Date, Gregorian,
};
use crate::{error::DateFromFieldsError, types::DateFields, Date, Gregorian};
use itertools::Itertools;
use std::collections::{BTreeMap, BTreeSet};

Expand Down Expand Up @@ -462,9 +454,7 @@ mod tests {
field_fns.insert("era", &|fields| fields.era = Some(b"ad"));
field_fns.insert("era_year", &|fields| fields.era_year = Some(2000));
field_fns.insert("extended_year", &|fields| fields.extended_year = Some(2000));
field_fns.insert("month_code", &|fields| {
fields.month_code = MonthCode::new_normal(4)
});
field_fns.insert("month_code", &|fields| fields.month_code = Some(b"M04"));
field_fns.insert("ordinal_month", &|fields| fields.ordinal_month = Some(4));
field_fns.insert("day", &|fields| fields.day = Some(20));

Expand Down
6 changes: 3 additions & 3 deletions components/calendar/src/tests/not_enough_fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use crate::error::DateFromFieldsError;
use crate::options::{DateFromFieldsOptions, MissingFieldsStrategy, Overflow};
use crate::types::{DateFields, MonthCode};
use crate::types::DateFields;
use crate::Date;

#[test]
Expand All @@ -16,8 +16,8 @@ fn test_from_fields_not_enough_fields() {
let big_u8 = Some(u8::MAX);
let small_u8 = Some(1);
let small_i32 = Some(5000);
let valid_month_code = MonthCode::new_normal(1);
let invalid_month_code = MonthCode::new_normal(99);
let valid_month_code: Option<&[_]> = Some(b"M01");
let invalid_month_code: Option<&[_]> = Some(b"M99");

// We want to ensure that most NotEnoughFields cases return NotEnoughFields
// even when we're providing out-of-range values, so that
Expand Down
51 changes: 47 additions & 4 deletions components/calendar/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ use crate::error::MonthCodeParseError;
/// A bag of various ways of expressing the year, month, and/or day.
///
/// Pass this into [`Date::try_from_fields`](crate::Date::try_from_fields).
#[derive(Copy, Clone, Debug, PartialEq, Default)]
#[derive(Copy, Clone, PartialEq, Default)]
#[non_exhaustive]
pub struct DateFields<'a> {
/// The era code as defined by CLDR.
/// The era code as defined by CLDR, represented as ASCII bytes.
///
/// If set, [`Self::era_year`] must also be set.
pub era: Option<&'a [u8]>,
Expand All @@ -35,8 +35,11 @@ pub struct DateFields<'a> {
/// If both this and [`Self::era`]/[`Self::era_year`] are set, they must
/// refer to the same year.
pub extended_year: Option<i32>,
/// The [`MonthCode`] representing a valid month in this calendar year.
pub month_code: Option<MonthCode>,
/// The month code representing a valid month in this calendar year,
/// represented as ASCII bytes.
///
/// See [`MonthCode`] for information on the syntax.
pub month_code: Option<&'a [u8]>,
/// See [`MonthInfo::ordinal`].
///
/// If both this and [`Self::month_code`] are set, they must refer to
Expand All @@ -46,6 +49,37 @@ pub struct DateFields<'a> {
pub day: Option<u8>,
}

// Custom impl to stringify era and month_code where possible.
impl fmt::Debug for DateFields<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Ensures we catch future fields
let Self {
era,
era_year,
extended_year,
month_code,
ordinal_month,
day,
} = *self;
let mut builder = f.debug_struct("DateFields");
if let Some(s) = era.and_then(|s| core::str::from_utf8(s).ok()) {
builder.field("era", &Some(s));
} else {
builder.field("era", &era);
}
builder.field("era_year", &era_year);
builder.field("extended_year", &extended_year);
if let Some(s) = month_code.and_then(|s| core::str::from_utf8(s).ok()) {
builder.field("month_code", &Some(s));
} else {
builder.field("month_code", &month_code);
}
builder.field("ordinal_month", &ordinal_month);
builder.field("day", &day);
builder.finish()
}
}

/// The type of year: Calendars like Chinese don't have an era and instead format with cyclic years.
#[derive(Copy, Clone, Debug, PartialEq)]
#[non_exhaustive]
Expand Down Expand Up @@ -292,6 +326,15 @@ pub(crate) struct ValidMonthCode {
}

impl ValidMonthCode {
#[inline]
pub(crate) fn from_bytes(x: &[u8]) -> Result<Self, MonthCodeParseError> {
let Ok(s) = TinyStr4::try_from_utf8(x) else {
return Err(MonthCodeParseError::InvalidSyntax);
};

MonthCode(s).validated()
}

/// Create a new ValidMonthCode without checking that the number is between 0 and 99
#[inline]
pub(crate) fn new_unchecked(number: u8, is_leap: bool) -> Self {
Expand Down
22 changes: 13 additions & 9 deletions components/calendar/tests/exhaustive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,28 @@ fn check_round_trip() {
fn check_from_fields() {
fn test<C: Calendar>(cal: C) {
let cal = Ref(&cal);

let codes = (1..19)
.flat_map(|i| {
[
types::MonthCode::new_normal(i).unwrap(),
types::MonthCode::new_leap(i).unwrap(),
]
.into_iter()
})
.collect::<Vec<_>>();
for year in -MAGNITUDE..=MAGNITUDE {
if year % 50000 == 0 {
println!("{} {year:?}", cal.as_calendar().debug_name());
}
for overflow in [options::Overflow::Constrain, options::Overflow::Reject] {
let mut options = options::DateFromFieldsOptions::default();
options.overflow = Some(overflow);
for mut fields in (1..19)
.flat_map(|i| {
[
types::MonthCode::new_normal(i).unwrap(),
types::MonthCode::new_leap(i).unwrap(),
]
.into_iter()
})
for mut fields in codes
.iter()
.map(|m| {
let mut fields = types::DateFields::default();
fields.month_code = Some(m);
fields.month_code = Some(m.0.as_bytes());
fields
})
.chain((1..20).map(|m| {
Expand Down
7 changes: 4 additions & 3 deletions components/calendar/tests/reference_year.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ where
let date = Date::from_rata_die(rd, Ref(&cal));
let month_day = (date.month().standard_code, date.day_of_month().0);
let mut fields = DateFields::default();
fields.month_code = Some(month_day.0);
fields.month_code = Some(month_day.0 .0.as_bytes());
fields.day = Some(month_day.1);
let mut options = DateFromFieldsOptions::default();
options.missing_fields_strategy = Some(MissingFieldsStrategy::Ecma);
Expand All @@ -50,10 +50,11 @@ where
valid_day_number = day_number;
}
let mut fields = DateFields::default();
fields.month_code = match is_leap {
let mc = match is_leap {
false => MonthCode::new_normal(month_number),
true => MonthCode::new_leap(month_number),
};
fields.month_code = mc.as_ref().map(|m| m.0.as_bytes());
fields.day = Some(day_number);
let mut options = DateFromFieldsOptions::default();
options.overflow = Some(Overflow::Constrain);
Expand Down Expand Up @@ -81,7 +82,7 @@ where
// Test round-trip (to valid day number)
assert_eq!(
fields.month_code.unwrap(),
reference_date.month().standard_code,
reference_date.month().standard_code.0.as_bytes(),
"{fields:?} {cal:?}"
);
assert_eq!(
Expand Down
Loading