icu_calendar/cal/
japanese.rs

1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5use crate::cal::abstract_gregorian::{impl_with_abstract_gregorian, GregorianYears};
6use crate::cal::gregorian::CeBce;
7use crate::calendar_arithmetic::ArithmeticDate;
8use crate::error::{DateError, UnknownEraError};
9use crate::provider::{CalendarJapaneseExtendedV1, CalendarJapaneseModernV1, EraStartDate};
10use crate::{types, AsCalendar, Date};
11use icu_provider::prelude::*;
12use tinystr::tinystr;
13
14/// The [Japanese Calendar] (with modern eras only)
15///
16/// The [Japanese Calendar] is a variant of the [`Gregorian`](crate::cal::Gregorian) calendar
17/// created by the Japanese government. It is identical to the Gregorian calendar except that
18/// is uses Japanese eras instead of the Common Era.
19///
20/// This implementation extends proleptically for dates before the calendar's creation
21/// in 6 Meiji (1873 CE).
22/// The Meiji era is used proleptically back to and including 1868-10-23, Gregorian eras are used before that.
23///
24/// For a variant that uses approximations of historical Japanese eras proleptically, check out [`JapaneseExtended`].
25///
26/// This corresponds to the `"japanese"` [CLDR calendar](https://unicode.org/reports/tr35/#UnicodeCalendarIdentifier).
27///
28/// [Japanese calendar]: https://en.wikipedia.org/wiki/Japanese_calendar
29///
30/// # Era codes
31///
32/// This calendar currently supports seven era codes. It supports the five eras since its
33/// introduction (`meiji`, `taisho`, `showa`, `heisei`, `reiwa`), as well as the Gregorian
34/// `bce` (alias `bc`), and `ce` (alias `ad`) for earlier dates.
35///
36/// Future eras will also be added to this type when they are decided.
37///
38/// These eras are loaded from data, requiring a data provider capable of providing [`CalendarJapaneseModernV1`]
39/// data.
40#[derive(Clone, Debug, Default)]
41pub struct Japanese {
42    eras: DataPayload<CalendarJapaneseModernV1>,
43}
44
45/// The [Japanese Calendar] (with historical eras)
46///
47/// The [Japanese Calendar] is a variant of the [`Gregorian`](crate::cal::Gregorian) calendar
48/// created by the Japanese government. It is identical to the Gregorian calendar except that
49/// is uses Japanese eras instead of the Common Era.
50///
51/// This implementation extends proleptically for dates before the calendar's creation
52/// in 6 Meiji (1873 CE).
53/// This implementation uses approximations of earlier Japanese eras proleptically and uses the Gregorian eras for
54/// even earlier dates that don't have an approximate Japanese era.
55///
56/// For a variant whose Japanese eras start with Meiji, check out [`Japanese`].
57///
58/// This corresponds to the `"japanext"` [CLDR calendar](https://unicode.org/reports/tr35/#UnicodeCalendarIdentifier).
59///
60/// [Japanese calendar]: https://en.wikipedia.org/wiki/Japanese_calendar
61///
62/// # Era codes
63///
64/// This calendar supports a large number of era codes. It supports the five eras since its introduction
65/// (`meiji`, `taisho`, `showa`, `heisei`, `reiwa`). Proleptic eras are represented
66/// with their names converted to lowercase ASCII and followed by their start year. E.g. the *Ten'ō*
67/// era (781 - 782 CE) has the code `teno-781`. The  Gregorian `bce` (alias `bc`), and `ce` (alias `ad`)
68/// are used for dates before the first known era era.
69///
70///
71/// These eras are loaded from data, requiring a data provider capable of providing [`CalendarJapaneseExtendedV1`]
72/// data.
73#[derive(Clone, Debug, Default)]
74pub struct JapaneseExtended(Japanese);
75
76impl Japanese {
77    /// Creates a new [`Japanese`] using only modern eras (post-meiji) from compiled data.
78    ///
79    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
80    ///
81    /// [📚 Help choosing a constructor](icu_provider::constructors)
82    #[cfg(feature = "compiled_data")]
83    pub const fn new() -> Self {
84        Self {
85            eras: DataPayload::from_static_ref(
86                crate::provider::Baked::SINGLETON_CALENDAR_JAPANESE_MODERN_V1,
87            ),
88        }
89    }
90
91    icu_provider::gen_buffer_data_constructors!(() -> error: DataError,
92        functions: [
93            new: skip,
94            try_new_with_buffer_provider,
95            try_new_unstable,
96            Self,
97    ]);
98
99    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new)]
100    pub fn try_new_unstable<D: DataProvider<CalendarJapaneseModernV1> + ?Sized>(
101        provider: &D,
102    ) -> Result<Self, DataError> {
103        Ok(Self {
104            eras: provider.load(Default::default())?.payload,
105        })
106    }
107}
108
109impl JapaneseExtended {
110    /// Creates a new [`Japanese`] from using all eras (including pre-meiji) from compiled data.
111    ///
112    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
113    ///
114    /// [📚 Help choosing a constructor](icu_provider::constructors)
115    #[cfg(feature = "compiled_data")]
116    pub const fn new() -> Self {
117        Self(Japanese {
118            eras: DataPayload::from_static_ref(
119                crate::provider::Baked::SINGLETON_CALENDAR_JAPANESE_EXTENDED_V1,
120            ),
121        })
122    }
123
124    icu_provider::gen_buffer_data_constructors!(() -> error: DataError,
125        functions: [
126            new: skip,
127            try_new_with_buffer_provider,
128            try_new_unstable,
129            Self,
130    ]);
131
132    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new)]
133    pub fn try_new_unstable<D: DataProvider<CalendarJapaneseExtendedV1> + ?Sized>(
134        provider: &D,
135    ) -> Result<Self, DataError> {
136        Ok(Self(Japanese {
137            eras: provider.load(Default::default())?.payload.cast(),
138        }))
139    }
140}
141
142const MEIJI_START: EraStartDate = EraStartDate {
143    year: 1868,
144    month: 10,
145    day: 23,
146};
147const TAISHO_START: EraStartDate = EraStartDate {
148    year: 1912,
149    month: 7,
150    day: 30,
151};
152const SHOWA_START: EraStartDate = EraStartDate {
153    year: 1926,
154    month: 12,
155    day: 25,
156};
157const HEISEI_START: EraStartDate = EraStartDate {
158    year: 1989,
159    month: 1,
160    day: 8,
161};
162const REIWA_START: EraStartDate = EraStartDate {
163    year: 2019,
164    month: 5,
165    day: 1,
166};
167
168impl GregorianYears for &'_ Japanese {
169    fn extended_from_era_year(
170        &self,
171        era: Option<&[u8]>,
172        year: i32,
173    ) -> Result<i32, UnknownEraError> {
174        if let Ok(g) = CeBce.extended_from_era_year(era, year) {
175            return Ok(g);
176        }
177        let Some(era) = era else {
178            // unreachable, handled by CeBce
179            return Err(UnknownEraError);
180        };
181
182        // Avoid linear search by trying well known eras
183        if era == b"reiwa" {
184            return Ok(year - 1 + REIWA_START.year);
185        } else if era == b"heisei" {
186            return Ok(year - 1 + HEISEI_START.year);
187        } else if era == b"showa" {
188            return Ok(year - 1 + SHOWA_START.year);
189        } else if era == b"taisho" {
190            return Ok(year - 1 + TAISHO_START.year);
191        } else if era == b"meiji" {
192            return Ok(year - 1 + MEIJI_START.year);
193        }
194
195        let data = &self.eras.get().dates_to_eras;
196
197        // Try to avoid linear search by binary searching for the year suffix
198        if let Some(start_year) = era
199            .split(|x| *x == b'-')
200            .nth(1)
201            .and_then(|y| core::str::from_utf8(y).ok()?.parse::<i32>().ok())
202        {
203            if let Ok(index) = data.binary_search_by(|(d, _)| d.year.cmp(&start_year)) {
204                // There is a slight chance we hit the case where there are two eras in the same year
205                // There are a couple of rare cases of this, but it's not worth writing a range-based binary search
206                // to catch them since this is an optimization
207                #[expect(clippy::unwrap_used)] // binary search
208                if data.get(index).unwrap().1.as_bytes() == era {
209                    return Ok(start_year + year - 1);
210                }
211            }
212        }
213
214        // Avoidance didn't work. Let's find the era manually, searching back from the present
215        let era_start = data
216            .iter()
217            .rev()
218            .find_map(|(s, e)| (e.as_bytes() == era).then_some(s))
219            .ok_or(UnknownEraError)?;
220        Ok(era_start.year + year - 1)
221    }
222
223    fn era_year_from_extended(&self, year: i32, month: u8, day: u8) -> types::EraYear {
224        let date: EraStartDate = EraStartDate { year, month, day };
225
226        let (start, era) = if date >= MEIJI_START
227            && self
228                .eras
229                .get()
230                .dates_to_eras
231                .last()
232                .is_some_and(|(_, e)| e == tinystr!(16, "reiwa"))
233        {
234            // We optimize for the five "modern" post-Meiji eras, which are stored in a smaller
235            // array and also hardcoded. The hardcoded version is not used if data indicates the
236            // presence of newer eras.
237            if date >= REIWA_START {
238                (REIWA_START, tinystr!(16, "reiwa"))
239            } else if date >= HEISEI_START {
240                (HEISEI_START, tinystr!(16, "heisei"))
241            } else if date >= SHOWA_START {
242                (SHOWA_START, tinystr!(16, "showa"))
243            } else if date >= TAISHO_START {
244                (TAISHO_START, tinystr!(16, "taisho"))
245            } else {
246                (MEIJI_START, tinystr!(16, "meiji"))
247            }
248        } else {
249            let data = &self.eras.get().dates_to_eras;
250            #[allow(clippy::unwrap_used)] // binary search
251            match data.binary_search_by(|(d, _)| d.cmp(&date)) {
252                Err(0) => {
253                    return types::EraYear {
254                        // TODO: return era indices?
255                        era_index: None,
256                        ..CeBce.era_year_from_extended(year, month, day)
257                    };
258                }
259                Ok(index) => data.get(index).unwrap(),
260                Err(index) => data.get(index - 1).unwrap(),
261            }
262        };
263
264        types::EraYear {
265            era,
266            era_index: None,
267            year: year - start.year + 1,
268            extended_year: year,
269            ambiguity: types::YearAmbiguity::CenturyRequired,
270        }
271    }
272
273    fn debug_name(&self) -> &'static str {
274        if self.eras.get().dates_to_eras.len() > 10 {
275            "Japanese (historical era data)"
276        } else {
277            "Japanese"
278        }
279    }
280
281    fn calendar_algorithm(&self) -> Option<crate::preferences::CalendarAlgorithm> {
282        if self.eras.get().dates_to_eras.len() > 10 {
283            None
284        } else {
285            Some(crate::preferences::CalendarAlgorithm::Japanese)
286        }
287    }
288}
289
290impl_with_abstract_gregorian!(Japanese, JapaneseDateInner, Japanese, this, this);
291
292impl_with_abstract_gregorian!(
293    JapaneseExtended,
294    JapaneseExtendedDateInner,
295    Japanese,
296    this,
297    &this.0
298);
299
300impl Date<Japanese> {
301    /// Construct a new Japanese Date.
302    ///
303    /// Years are specified in the era provided, and must be in range for Japanese
304    /// eras (e.g. dates past April 30 Heisei 31 must be in Reiwa; "Jun 5 Heisei 31" and "Jan 1 Heisei 32"
305    /// will not be adjusted to being in Reiwa 1 and 2 respectively)
306    ///
307    /// However, dates may always be specified in "bce" or "ce" and they will be adjusted as necessary.
308    ///
309    /// ```rust
310    /// use icu::calendar::cal::Japanese;
311    /// use icu::calendar::{Date, Ref};
312    /// use tinystr::tinystr;
313    ///
314    /// let japanese_calendar = Japanese::new();
315    /// // for easy sharing
316    /// let japanese_calendar = Ref(&japanese_calendar);
317    ///
318    /// let era = "heisei";
319    ///
320    /// let date =
321    ///     Date::try_new_japanese_with_calendar(era, 14, 1, 2, japanese_calendar)
322    ///         .expect("Constructing a date should succeed");
323    ///
324    /// assert_eq!(date.era_year().era, era);
325    /// assert_eq!(date.era_year().year, 14);
326    /// assert_eq!(date.month().ordinal, 1);
327    /// assert_eq!(date.day_of_month().0, 2);
328    ///
329    /// // This function will error for unknown eras
330    /// let fake_era = "neko"; // 🐱
331    /// let fake_date = Date::try_new_japanese_with_calendar(
332    ///     fake_era,
333    ///     10,
334    ///     1,
335    ///     2,
336    ///     japanese_calendar,
337    /// );
338    /// assert!(fake_date.is_err());
339    /// ```
340    pub fn try_new_japanese_with_calendar<A: AsCalendar<Calendar = Japanese>>(
341        era: &str,
342        year: i32,
343        month: u8,
344        day: u8,
345        japanese_calendar: A,
346    ) -> Result<Date<A>, DateError> {
347        let extended = japanese_calendar
348            .as_calendar()
349            .extended_from_era_year(Some(era.as_bytes()), year)?;
350        Ok(Date::from_raw(
351            JapaneseDateInner(ArithmeticDate::new_gregorian::<&Japanese>(
352                extended, month, day,
353            )?),
354            japanese_calendar,
355        ))
356    }
357}
358
359impl Date<JapaneseExtended> {
360    /// Construct a new Japanese Date with all eras.
361    ///
362    /// Years are specified in the era provided, and must be in range for Japanese
363    /// eras (e.g. dates past April 30 Heisei 31 must be in Reiwa; "Jun 5 Heisei 31" and "Jan 1 Heisei 32"
364    /// will not be adjusted to being in Reiwa 1 and 2 respectively)
365    ///
366    /// However, dates may always be specified in "bce" or "ce" and they will be adjusted as necessary.
367    ///
368    /// ```rust
369    /// use icu::calendar::cal::JapaneseExtended;
370    /// use icu::calendar::{Date, Ref};
371    /// use tinystr::tinystr;
372    ///
373    /// let japanext_calendar = JapaneseExtended::new();
374    /// // for easy sharing
375    /// let japanext_calendar = Ref(&japanext_calendar);
376    ///
377    /// let era = "kansei-1789";
378    ///
379    /// let date = Date::try_new_japanese_extended_with_calendar(
380    ///     era,
381    ///     7,
382    ///     1,
383    ///     2,
384    ///     japanext_calendar,
385    /// )
386    /// .expect("Constructing a date should succeed");
387    ///
388    /// assert_eq!(date.era_year().era, era);
389    /// assert_eq!(date.era_year().year, 7);
390    /// assert_eq!(date.month().ordinal, 1);
391    /// assert_eq!(date.day_of_month().0, 2);
392    /// ```
393    pub fn try_new_japanese_extended_with_calendar<A: AsCalendar<Calendar = JapaneseExtended>>(
394        era: &str,
395        year: i32,
396        month: u8,
397        day: u8,
398        japanext_calendar: A,
399    ) -> Result<Date<A>, DateError> {
400        let extended = (&japanext_calendar.as_calendar().0)
401            .extended_from_era_year(Some(era.as_bytes()), year)?;
402        Ok(Date::from_raw(
403            JapaneseExtendedDateInner(ArithmeticDate::new_gregorian::<&Japanese>(
404                extended, month, day,
405            )?),
406            japanext_calendar,
407        ))
408    }
409}
410
411#[cfg(test)]
412mod tests {
413    use super::*;
414    use crate::Ref;
415
416    fn single_test_roundtrip(calendar: Ref<Japanese>, era: &str, year: i32, month: u8, day: u8) {
417        let date = Date::try_new_japanese_with_calendar(era, year, month, day, calendar)
418            .unwrap_or_else(|e| {
419                panic!("Failed to construct date with {era:?}, {year}, {month}, {day}: {e:?}")
420            });
421        let iso = date.to_iso();
422        let reconstructed = Date::new_from_iso(iso, calendar);
423        assert_eq!(
424            date, reconstructed,
425            "Failed to roundtrip with {era:?}, {year}, {month}, {day}"
426        );
427
428        // Extra coverage for https://github.com/unicode-org/icu4x/issues/4968
429        assert_eq!(reconstructed.era_year().era, era);
430        assert_eq!(reconstructed.era_year().year, year);
431    }
432
433    fn single_test_roundtrip_ext(
434        calendar: Ref<JapaneseExtended>,
435        era: &str,
436        year: i32,
437        month: u8,
438        day: u8,
439    ) {
440        let date = Date::try_new_japanese_extended_with_calendar(era, year, month, day, calendar)
441            .unwrap_or_else(|e| {
442                panic!("Failed to construct date with {era:?}, {year}, {month}, {day}: {e:?}")
443            });
444        let iso = date.to_iso();
445        let reconstructed = Date::new_from_iso(iso, calendar);
446        assert_eq!(
447            date, reconstructed,
448            "Failed to roundtrip with {era:?}, {year}, {month}, {day}"
449        )
450    }
451
452    // test that out-of-range era values roundtrip to other eras
453    fn single_test_era_range_roundtrip(
454        calendar: Ref<Japanese>,
455        era: &str,
456        year: i32,
457        month: u8,
458        day: u8,
459        era2: &str,
460        year2: i32,
461    ) {
462        let expected = Date::try_new_japanese_with_calendar(era2, year2, month, day, calendar)
463            .unwrap_or_else(|e| {
464                panic!(
465                    "Failed to construct expectation date with {era2:?}, {year2}, {month}, {day}: {e:?}"
466                )
467            });
468
469        let date = Date::try_new_japanese_with_calendar(era, year, month, day, calendar)
470            .unwrap_or_else(|e| {
471                panic!("Failed to construct date with {era:?}, {year}, {month}, {day}: {e:?}")
472            });
473        let iso = date.to_iso();
474        let reconstructed = Date::new_from_iso(iso, calendar);
475        assert_eq!(
476            expected, reconstructed,
477            "Failed to roundtrip with {era:?}, {year}, {month}, {day} == {era2:?}, {year}"
478        )
479    }
480    fn single_test_era_range_roundtrip_ext(
481        calendar: Ref<JapaneseExtended>,
482        era: &str,
483        year: i32,
484        month: u8,
485        day: u8,
486        era2: &str,
487        year2: i32,
488    ) {
489        let expected = Date::try_new_japanese_extended_with_calendar(era2, year2, month, day, calendar)
490            .unwrap_or_else(|e| {
491                panic!(
492                    "Failed to construct expectation date with {era2:?}, {year2}, {month}, {day}: {e:?}"
493                )
494            });
495
496        let date = Date::try_new_japanese_extended_with_calendar(era, year, month, day, calendar)
497            .unwrap_or_else(|e| {
498                panic!("Failed to construct date with {era:?}, {year}, {month}, {day}: {e:?}")
499            });
500        let iso = date.to_iso();
501        let reconstructed = Date::new_from_iso(iso, calendar);
502        assert_eq!(
503            expected, reconstructed,
504            "Failed to roundtrip with {era:?}, {year}, {month}, {day} == {era2:?}, {year}"
505        )
506    }
507
508    fn single_test_error(
509        calendar: Ref<Japanese>,
510        era: &str,
511        year: i32,
512        month: u8,
513        day: u8,
514        error: DateError,
515    ) {
516        let date = Date::try_new_japanese_with_calendar(era, year, month, day, calendar);
517        assert_eq!(
518            date,
519            Err(error),
520            "Construction with {era:?}, {year}, {month}, {day} did not return {error:?}"
521        )
522    }
523
524    #[test]
525    fn test_japanese() {
526        let calendar = Japanese::new();
527        let calendar_ext = JapaneseExtended::new();
528        let calendar = Ref(&calendar);
529        let calendar_ext = Ref(&calendar_ext);
530
531        single_test_roundtrip(calendar, "heisei", 12, 3, 1);
532        single_test_roundtrip(calendar, "taisho", 3, 3, 1);
533        // Heisei did not start until later in the year
534        single_test_era_range_roundtrip(calendar, "heisei", 1, 1, 1, "showa", 64);
535
536        single_test_roundtrip_ext(calendar_ext, "heisei", 12, 3, 1);
537        single_test_roundtrip_ext(calendar_ext, "taisho", 3, 3, 1);
538        single_test_era_range_roundtrip_ext(calendar_ext, "heisei", 1, 1, 1, "showa", 64);
539
540        single_test_roundtrip_ext(calendar_ext, "hakuho-672", 4, 3, 1);
541        single_test_error(calendar, "hakuho-672", 4, 3, 1, DateError::UnknownEra);
542
543        // handle bce/ce
544        single_test_roundtrip(calendar, "bce", 100, 3, 1);
545        single_test_roundtrip(calendar, "bce", 1, 3, 1);
546        single_test_roundtrip(calendar, "ce", 1, 3, 1);
547        single_test_roundtrip(calendar, "ce", 100, 3, 1);
548        single_test_roundtrip_ext(calendar_ext, "ce", 100, 3, 1);
549        single_test_roundtrip(calendar, "ce", 1000, 3, 1);
550        single_test_era_range_roundtrip(calendar, "ce", 0, 3, 1, "bce", 1);
551        single_test_era_range_roundtrip(calendar, "bce", -1, 3, 1, "ce", 2);
552
553        // handle the cases where bce/ce get adjusted to different eras
554        // single_test_gregorian_roundtrip(calendar, "ce", 2021, 3, 1, "reiwa", 3);
555        single_test_era_range_roundtrip_ext(calendar_ext, "ce", 1000, 3, 1, "choho-999", 2);
556        single_test_era_range_roundtrip_ext(calendar_ext, "ce", 749, 5, 10, "tenpyokampo-749", 1);
557        single_test_era_range_roundtrip_ext(calendar_ext, "bce", 10, 3, 1, "bce", 10);
558        single_test_era_range_roundtrip_ext(calendar_ext, "ce", -1, 3, 1, "bce", 2);
559
560        // There were multiple eras in this year
561        // This one is from Apr 14 to July 2
562        single_test_roundtrip_ext(calendar_ext, "tenpyokampo-749", 1, 4, 20);
563        single_test_roundtrip_ext(calendar_ext, "tenpyokampo-749", 1, 4, 14);
564        single_test_roundtrip_ext(calendar_ext, "tenpyokampo-749", 1, 7, 1);
565        single_test_era_range_roundtrip_ext(
566            calendar_ext,
567            "tenpyokampo-749",
568            1,
569            7,
570            5,
571            "tenpyoshoho-749",
572            1,
573        );
574        single_test_era_range_roundtrip_ext(
575            calendar_ext,
576            "tenpyokampo-749",
577            1,
578            4,
579            13,
580            "tenpyoshoho-749",
581            1,
582        );
583    }
584}