use crate::cal::abstract_gregorian::{impl_with_abstract_gregorian, GregorianYears};
use crate::cal::gregorian::CeBce;
use crate::calendar_arithmetic::ArithmeticDate;
use crate::error::{DateError, UnknownEraError};
use crate::provider::{CalendarJapaneseExtendedV1, CalendarJapaneseModernV1, EraStartDate};
use crate::{types, AsCalendar, Date};
use icu_provider::prelude::*;
use tinystr::tinystr;
#[derive(Clone, Debug, Default)]
pub struct Japanese {
eras: DataPayload<CalendarJapaneseModernV1>,
}
#[derive(Clone, Debug, Default)]
pub struct JapaneseExtended(Japanese);
impl Japanese {
#[cfg(feature = "compiled_data")]
pub const fn new() -> Self {
Self {
eras: DataPayload::from_static_ref(
crate::provider::Baked::SINGLETON_CALENDAR_JAPANESE_MODERN_V1,
),
}
}
icu_provider::gen_buffer_data_constructors!(() -> error: DataError,
functions: [
new: skip,
try_new_with_buffer_provider,
try_new_unstable,
Self,
]);
#[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new)]
pub fn try_new_unstable<D: DataProvider<CalendarJapaneseModernV1> + ?Sized>(
provider: &D,
) -> Result<Self, DataError> {
Ok(Self {
eras: provider.load(Default::default())?.payload,
})
}
}
impl JapaneseExtended {
#[cfg(feature = "compiled_data")]
pub const fn new() -> Self {
Self(Japanese {
eras: DataPayload::from_static_ref(
crate::provider::Baked::SINGLETON_CALENDAR_JAPANESE_EXTENDED_V1,
),
})
}
icu_provider::gen_buffer_data_constructors!(() -> error: DataError,
functions: [
new: skip,
try_new_with_buffer_provider,
try_new_unstable,
Self,
]);
#[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new)]
pub fn try_new_unstable<D: DataProvider<CalendarJapaneseExtendedV1> + ?Sized>(
provider: &D,
) -> Result<Self, DataError> {
Ok(Self(Japanese {
eras: provider.load(Default::default())?.payload.cast(),
}))
}
}
const MEIJI_START: EraStartDate = EraStartDate {
year: 1868,
month: 10,
day: 23,
};
const TAISHO_START: EraStartDate = EraStartDate {
year: 1912,
month: 7,
day: 30,
};
const SHOWA_START: EraStartDate = EraStartDate {
year: 1926,
month: 12,
day: 25,
};
const HEISEI_START: EraStartDate = EraStartDate {
year: 1989,
month: 1,
day: 8,
};
const REIWA_START: EraStartDate = EraStartDate {
year: 2019,
month: 5,
day: 1,
};
impl GregorianYears for &'_ Japanese {
fn extended_from_era_year(
&self,
era: Option<&[u8]>,
year: i32,
) -> Result<i32, UnknownEraError> {
if let Ok(g) = CeBce.extended_from_era_year(era, year) {
return Ok(g);
}
let Some(era) = era else {
return Err(UnknownEraError);
};
if era == b"reiwa" {
return Ok(year - 1 + REIWA_START.year);
} else if era == b"heisei" {
return Ok(year - 1 + HEISEI_START.year);
} else if era == b"showa" {
return Ok(year - 1 + SHOWA_START.year);
} else if era == b"taisho" {
return Ok(year - 1 + TAISHO_START.year);
} else if era == b"meiji" {
return Ok(year - 1 + MEIJI_START.year);
}
let data = &self.eras.get().dates_to_eras;
if let Some(start_year) = era
.split(|x| *x == b'-')
.nth(1)
.and_then(|y| core::str::from_utf8(y).ok()?.parse::<i32>().ok())
{
if let Ok(index) = data.binary_search_by(|(d, _)| d.year.cmp(&start_year)) {
#[expect(clippy::unwrap_used)] if data.get(index).unwrap().1.as_bytes() == era {
return Ok(start_year + year - 1);
}
}
}
let era_start = data
.iter()
.rev()
.find_map(|(s, e)| (e.as_bytes() == era).then_some(s))
.ok_or(UnknownEraError)?;
Ok(era_start.year + year - 1)
}
fn era_year_from_extended(&self, year: i32, month: u8, day: u8) -> types::EraYear {
let date: EraStartDate = EraStartDate { year, month, day };
let (start, era) = if date >= MEIJI_START
&& self
.eras
.get()
.dates_to_eras
.last()
.is_some_and(|(_, e)| e == tinystr!(16, "reiwa"))
{
if date >= REIWA_START {
(REIWA_START, tinystr!(16, "reiwa"))
} else if date >= HEISEI_START {
(HEISEI_START, tinystr!(16, "heisei"))
} else if date >= SHOWA_START {
(SHOWA_START, tinystr!(16, "showa"))
} else if date >= TAISHO_START {
(TAISHO_START, tinystr!(16, "taisho"))
} else {
(MEIJI_START, tinystr!(16, "meiji"))
}
} else {
let data = &self.eras.get().dates_to_eras;
#[allow(clippy::unwrap_used)] match data.binary_search_by(|(d, _)| d.cmp(&date)) {
Err(0) => {
return types::EraYear {
era_index: None,
..CeBce.era_year_from_extended(year, month, day)
};
}
Ok(index) => data.get(index).unwrap(),
Err(index) => data.get(index - 1).unwrap(),
}
};
types::EraYear {
era,
era_index: None,
year: year - start.year + 1,
extended_year: year,
ambiguity: types::YearAmbiguity::CenturyRequired,
}
}
fn debug_name(&self) -> &'static str {
if self.eras.get().dates_to_eras.len() > 10 {
"Japanese (historical era data)"
} else {
"Japanese"
}
}
fn calendar_algorithm(&self) -> Option<crate::preferences::CalendarAlgorithm> {
if self.eras.get().dates_to_eras.len() > 10 {
None
} else {
Some(crate::preferences::CalendarAlgorithm::Japanese)
}
}
}
impl_with_abstract_gregorian!(Japanese, JapaneseDateInner, Japanese, this, this);
impl_with_abstract_gregorian!(
JapaneseExtended,
JapaneseExtendedDateInner,
Japanese,
this,
&this.0
);
impl Date<Japanese> {
pub fn try_new_japanese_with_calendar<A: AsCalendar<Calendar = Japanese>>(
era: &str,
year: i32,
month: u8,
day: u8,
japanese_calendar: A,
) -> Result<Date<A>, DateError> {
let extended = japanese_calendar
.as_calendar()
.extended_from_era_year(Some(era.as_bytes()), year)?;
Ok(Date::from_raw(
JapaneseDateInner(ArithmeticDate::new_gregorian::<&Japanese>(
extended, month, day,
)?),
japanese_calendar,
))
}
}
impl Date<JapaneseExtended> {
pub fn try_new_japanese_extended_with_calendar<A: AsCalendar<Calendar = JapaneseExtended>>(
era: &str,
year: i32,
month: u8,
day: u8,
japanext_calendar: A,
) -> Result<Date<A>, DateError> {
let extended = (&japanext_calendar.as_calendar().0)
.extended_from_era_year(Some(era.as_bytes()), year)?;
Ok(Date::from_raw(
JapaneseExtendedDateInner(ArithmeticDate::new_gregorian::<&Japanese>(
extended, month, day,
)?),
japanext_calendar,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Ref;
fn single_test_roundtrip(calendar: Ref<Japanese>, era: &str, year: i32, month: u8, day: u8) {
let date = Date::try_new_japanese_with_calendar(era, year, month, day, calendar)
.unwrap_or_else(|e| {
panic!("Failed to construct date with {era:?}, {year}, {month}, {day}: {e:?}")
});
let iso = date.to_iso();
let reconstructed = Date::new_from_iso(iso, calendar);
assert_eq!(
date, reconstructed,
"Failed to roundtrip with {era:?}, {year}, {month}, {day}"
);
assert_eq!(reconstructed.era_year().era, era);
assert_eq!(reconstructed.era_year().year, year);
}
fn single_test_roundtrip_ext(
calendar: Ref<JapaneseExtended>,
era: &str,
year: i32,
month: u8,
day: u8,
) {
let date = Date::try_new_japanese_extended_with_calendar(era, year, month, day, calendar)
.unwrap_or_else(|e| {
panic!("Failed to construct date with {era:?}, {year}, {month}, {day}: {e:?}")
});
let iso = date.to_iso();
let reconstructed = Date::new_from_iso(iso, calendar);
assert_eq!(
date, reconstructed,
"Failed to roundtrip with {era:?}, {year}, {month}, {day}"
)
}
fn single_test_era_range_roundtrip(
calendar: Ref<Japanese>,
era: &str,
year: i32,
month: u8,
day: u8,
era2: &str,
year2: i32,
) {
let expected = Date::try_new_japanese_with_calendar(era2, year2, month, day, calendar)
.unwrap_or_else(|e| {
panic!(
"Failed to construct expectation date with {era2:?}, {year2}, {month}, {day}: {e:?}"
)
});
let date = Date::try_new_japanese_with_calendar(era, year, month, day, calendar)
.unwrap_or_else(|e| {
panic!("Failed to construct date with {era:?}, {year}, {month}, {day}: {e:?}")
});
let iso = date.to_iso();
let reconstructed = Date::new_from_iso(iso, calendar);
assert_eq!(
expected, reconstructed,
"Failed to roundtrip with {era:?}, {year}, {month}, {day} == {era2:?}, {year}"
)
}
fn single_test_era_range_roundtrip_ext(
calendar: Ref<JapaneseExtended>,
era: &str,
year: i32,
month: u8,
day: u8,
era2: &str,
year2: i32,
) {
let expected = Date::try_new_japanese_extended_with_calendar(era2, year2, month, day, calendar)
.unwrap_or_else(|e| {
panic!(
"Failed to construct expectation date with {era2:?}, {year2}, {month}, {day}: {e:?}"
)
});
let date = Date::try_new_japanese_extended_with_calendar(era, year, month, day, calendar)
.unwrap_or_else(|e| {
panic!("Failed to construct date with {era:?}, {year}, {month}, {day}: {e:?}")
});
let iso = date.to_iso();
let reconstructed = Date::new_from_iso(iso, calendar);
assert_eq!(
expected, reconstructed,
"Failed to roundtrip with {era:?}, {year}, {month}, {day} == {era2:?}, {year}"
)
}
fn single_test_error(
calendar: Ref<Japanese>,
era: &str,
year: i32,
month: u8,
day: u8,
error: DateError,
) {
let date = Date::try_new_japanese_with_calendar(era, year, month, day, calendar);
assert_eq!(
date,
Err(error),
"Construction with {era:?}, {year}, {month}, {day} did not return {error:?}"
)
}
#[test]
fn test_japanese() {
let calendar = Japanese::new();
let calendar_ext = JapaneseExtended::new();
let calendar = Ref(&calendar);
let calendar_ext = Ref(&calendar_ext);
single_test_roundtrip(calendar, "heisei", 12, 3, 1);
single_test_roundtrip(calendar, "taisho", 3, 3, 1);
single_test_era_range_roundtrip(calendar, "heisei", 1, 1, 1, "showa", 64);
single_test_roundtrip_ext(calendar_ext, "heisei", 12, 3, 1);
single_test_roundtrip_ext(calendar_ext, "taisho", 3, 3, 1);
single_test_era_range_roundtrip_ext(calendar_ext, "heisei", 1, 1, 1, "showa", 64);
single_test_roundtrip_ext(calendar_ext, "hakuho-672", 4, 3, 1);
single_test_error(calendar, "hakuho-672", 4, 3, 1, DateError::UnknownEra);
single_test_roundtrip(calendar, "bce", 100, 3, 1);
single_test_roundtrip(calendar, "bce", 1, 3, 1);
single_test_roundtrip(calendar, "ce", 1, 3, 1);
single_test_roundtrip(calendar, "ce", 100, 3, 1);
single_test_roundtrip_ext(calendar_ext, "ce", 100, 3, 1);
single_test_roundtrip(calendar, "ce", 1000, 3, 1);
single_test_era_range_roundtrip(calendar, "ce", 0, 3, 1, "bce", 1);
single_test_era_range_roundtrip(calendar, "bce", -1, 3, 1, "ce", 2);
single_test_era_range_roundtrip_ext(calendar_ext, "ce", 1000, 3, 1, "choho-999", 2);
single_test_era_range_roundtrip_ext(calendar_ext, "ce", 749, 5, 10, "tenpyokampo-749", 1);
single_test_era_range_roundtrip_ext(calendar_ext, "bce", 10, 3, 1, "bce", 10);
single_test_era_range_roundtrip_ext(calendar_ext, "ce", -1, 3, 1, "bce", 2);
single_test_roundtrip_ext(calendar_ext, "tenpyokampo-749", 1, 4, 20);
single_test_roundtrip_ext(calendar_ext, "tenpyokampo-749", 1, 4, 14);
single_test_roundtrip_ext(calendar_ext, "tenpyokampo-749", 1, 7, 1);
single_test_era_range_roundtrip_ext(
calendar_ext,
"tenpyokampo-749",
1,
7,
5,
"tenpyoshoho-749",
1,
);
single_test_era_range_roundtrip_ext(
calendar_ext,
"tenpyokampo-749",
1,
4,
13,
"tenpyoshoho-749",
1,
);
}
}