Skip to content

Commit a79a472

Browse files
committed
split japanese calendar implementations
1 parent c460c96 commit a79a472

File tree

1 file changed

+126
-18
lines changed

1 file changed

+126
-18
lines changed

components/calendar/src/cal/japanese.rs

Lines changed: 126 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ pub struct Japanese {
7373
/// These eras are loaded from data, requiring a data provider capable of providing [`CalendarJapaneseExtendedV1`]
7474
/// data.
7575
#[derive(Clone, Debug, Default)]
76-
pub struct JapaneseExtended(Japanese);
76+
pub struct JapaneseExtended {
77+
eras: DataPayload<CalendarJapaneseExtendedV1>,
78+
}
7779

7880
impl Japanese {
7981
/// Creates a new [`Japanese`] using only modern eras (post-meiji) from compiled data.
@@ -116,11 +118,11 @@ impl JapaneseExtended {
116118
/// [📚 Help choosing a constructor](icu_provider::constructors)
117119
#[cfg(feature = "compiled_data")]
118120
pub const fn new() -> Self {
119-
Self(Japanese {
121+
Self {
120122
eras: DataPayload::from_static_ref(
121123
crate::provider::Baked::SINGLETON_CALENDAR_JAPANESE_EXTENDED_V1,
122124
),
123-
})
125+
}
124126
}
125127

126128
icu_provider::gen_buffer_data_constructors!(() -> error: DataError,
@@ -135,9 +137,9 @@ impl JapaneseExtended {
135137
pub fn try_new_unstable<D: DataProvider<CalendarJapaneseExtendedV1> + ?Sized>(
136138
provider: &D,
137139
) -> Result<Self, DataError> {
138-
Ok(Self(Japanese {
139-
eras: provider.load(Default::default())?.payload.cast(),
140-
}))
140+
Ok(Self {
141+
eras: provider.load(Default::default())?.payload,
142+
})
141143
}
142144
}
143145

@@ -273,30 +275,136 @@ impl GregorianYears for &'_ Japanese {
273275
}
274276

275277
fn debug_name(&self) -> &'static str {
276-
if self.eras.get().dates_to_eras.len() > 10 {
277-
"Japanese (historical era data)"
278-
} else {
279-
"Japanese"
280-
}
278+
"Japanese"
281279
}
282280

283281
fn calendar_algorithm(&self) -> Option<crate::preferences::CalendarAlgorithm> {
284-
if self.eras.get().dates_to_eras.len() > 10 {
285-
None
286-
} else {
287-
Some(crate::preferences::CalendarAlgorithm::Japanese)
288-
}
282+
Some(crate::preferences::CalendarAlgorithm::Japanese)
289283
}
290284
}
291285

292286
impl_with_abstract_gregorian!(Japanese, JapaneseDateInner, Japanese, this, this);
293287

288+
impl GregorianYears for &'_ JapaneseExtended {
289+
fn extended_from_era_year(
290+
&self,
291+
era: Option<&[u8]>,
292+
year: i32,
293+
) -> Result<i32, UnknownEraError> {
294+
if let Ok(g) = CeBce.extended_from_era_year(era, year) {
295+
return Ok(g);
296+
}
297+
let Some(era) = era else {
298+
// unreachable, handled by CeBce
299+
return Err(UnknownEraError);
300+
};
301+
302+
// Avoid linear search by trying well known eras
303+
if era == b"reiwa" {
304+
return Ok(year - 1 + REIWA_START.year);
305+
} else if era == b"heisei" {
306+
return Ok(year - 1 + HEISEI_START.year);
307+
} else if era == b"showa" {
308+
return Ok(year - 1 + SHOWA_START.year);
309+
} else if era == b"taisho" {
310+
return Ok(year - 1 + TAISHO_START.year);
311+
} else if era == b"meiji" {
312+
return Ok(year - 1 + MEIJI_START.year);
313+
}
314+
315+
let data = &self.eras.get().dates_to_eras;
316+
317+
// Try to avoid linear search by binary searching for the year suffix
318+
if let Some(start_year) = era
319+
.split(|x| *x == b'-')
320+
.nth(1)
321+
.and_then(|y| core::str::from_utf8(y).ok()?.parse::<i32>().ok())
322+
{
323+
if let Ok(index) = data.binary_search_by(|(d, _)| d.year.cmp(&start_year)) {
324+
// There is a slight chance we hit the case where there are two eras in the same year
325+
// There are a couple of rare cases of this, but it's not worth writing a range-based binary search
326+
// to catch them since this is an optimization
327+
#[expect(clippy::unwrap_used)] // binary search
328+
if data.get(index).unwrap().1.as_bytes() == era {
329+
return Ok(start_year + year - 1);
330+
}
331+
}
332+
}
333+
334+
// Avoidance didn't work. Let's find the era manually, searching back from the present
335+
let era_start = data
336+
.iter()
337+
.rev()
338+
.find_map(|(s, e)| (e.as_bytes() == era).then_some(s))
339+
.ok_or(UnknownEraError)?;
340+
Ok(era_start.year + year - 1)
341+
}
342+
343+
fn era_year_from_extended(&self, year: i32, month: u8, day: u8) -> types::EraYear {
344+
let date: EraStartDate = EraStartDate { year, month, day };
345+
346+
let (start, era) = if date >= MEIJI_START
347+
&& self
348+
.eras
349+
.get()
350+
.dates_to_eras
351+
.last()
352+
.is_some_and(|(_, e)| e == tinystr!(16, "reiwa"))
353+
{
354+
// We optimize for the five "modern" post-Meiji eras, which are stored in a smaller
355+
// array and also hardcoded. The hardcoded version is not used if data indicates the
356+
// presence of newer eras.
357+
if date >= REIWA_START {
358+
(REIWA_START, tinystr!(16, "reiwa"))
359+
} else if date >= HEISEI_START {
360+
(HEISEI_START, tinystr!(16, "heisei"))
361+
} else if date >= SHOWA_START {
362+
(SHOWA_START, tinystr!(16, "showa"))
363+
} else if date >= TAISHO_START {
364+
(TAISHO_START, tinystr!(16, "taisho"))
365+
} else {
366+
(MEIJI_START, tinystr!(16, "meiji"))
367+
}
368+
} else {
369+
let data = &self.eras.get().dates_to_eras;
370+
#[allow(clippy::unwrap_used)] // binary search
371+
match data.binary_search_by(|(d, _)| d.cmp(&date)) {
372+
Err(0) => {
373+
return types::EraYear {
374+
// TODO: return era indices?
375+
era_index: None,
376+
..CeBce.era_year_from_extended(year, month, day)
377+
};
378+
}
379+
Ok(index) => data.get(index).unwrap(),
380+
Err(index) => data.get(index - 1).unwrap(),
381+
}
382+
};
383+
384+
types::EraYear {
385+
era,
386+
era_index: None,
387+
year: year - start.year + 1,
388+
extended_year: year,
389+
ambiguity: types::YearAmbiguity::CenturyRequired,
390+
}
391+
}
392+
393+
fn debug_name(&self) -> &'static str {
394+
"Japanese (historical era data)"
395+
}
396+
397+
fn calendar_algorithm(&self) -> Option<crate::preferences::CalendarAlgorithm> {
398+
None
399+
}
400+
}
401+
294402
impl_with_abstract_gregorian!(
295403
JapaneseExtended,
296404
JapaneseExtendedDateInner,
297405
Japanese,
298406
this,
299-
&this.0
407+
this
300408
);
301409

302410
impl Date<Japanese> {
@@ -411,7 +519,7 @@ impl Date<JapaneseExtended> {
411519
year,
412520
month,
413521
day,
414-
&AbstractGregorian(&japanext_calendar.as_calendar().0),
522+
&AbstractGregorian(japanext_calendar.as_calendar()),
415523
)
416524
.map(ArithmeticDate::cast)
417525
.map(JapaneseExtendedDateInner)

0 commit comments

Comments
 (0)