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
5//! This module contains types and implementations for the Japanese calendar.
6//!
7//! ```rust
8//! use icu::calendar::cal::Japanese;
9//! use icu::calendar::Date;
10//! use tinystr::tinystr;
11//!
12//! let japanese_calendar = Japanese::new();
13//!
14//! let date_iso = Date::try_new_iso(1970, 1, 2)
15//!     .expect("Failed to initialize ISO Date instance.");
16//! let date_japanese = Date::new_from_iso(date_iso, japanese_calendar);
17//!
18//! assert_eq!(date_japanese.era_year().year, 45);
19//! assert_eq!(date_japanese.month().ordinal, 1);
20//! assert_eq!(date_japanese.day_of_month().0, 2);
21//! assert_eq!(date_japanese.era_year().era, "showa");
22//! ```
23
24use crate::cal::iso::{Iso, IsoDateInner};
25use crate::error::{year_check, DateError};
26use crate::provider::{CalendarJapaneseExtendedV1, CalendarJapaneseModernV1, EraStartDate};
27use crate::{types, AsCalendar, Calendar, Date, DateDuration, DateDurationUnit, Ref};
28use calendrical_calculations::rata_die::RataDie;
29use icu_provider::prelude::*;
30use tinystr::{tinystr, TinyStr16};
31
32/// The [Japanese Calendar] (with modern eras only)
33///
34/// The [Japanese calendar] is a solar calendar used in Japan, with twelve months.
35/// The months and days are identical to that of the Gregorian calendar, however the years are counted
36/// differently using the Japanese era system.
37///
38/// This calendar only contains eras after Meiji, for all historical eras, check out [`JapaneseExtended`].
39///
40/// This type can be used with [`Date`] to represent dates in this calendar.
41///
42/// [Japanese calendar]: https://en.wikipedia.org/wiki/Japanese_calendar
43///
44/// # Era codes
45///
46/// This calendar currently supports seven era codes. It supports the five post-Meiji eras
47/// (`meiji`, `taisho`, `showa`, `heisei`, `reiwa`), as well as using the Gregorian
48/// `bce` (alias `bc`), and `ce` (alias `ad`) for dates before the Meiji era.
49///
50/// Future eras will also be added to this type when they are decided.
51///
52/// These eras are loaded from data, requiring a data provider capable of providing [`CalendarJapaneseModernV1`]
53/// data.
54///
55/// # Month codes
56///
57/// This calendar supports 12 solar month codes (`M01` - `M12`)
58#[derive(Clone, Debug, Default)]
59pub struct Japanese {
60    eras: DataPayload<CalendarJapaneseModernV1>,
61}
62
63/// The [Japanese Calendar] (with historical eras)
64///
65/// The [Japanese calendar] is a solar calendar used in Japan, with twelve months.
66/// The months and days are identical to that of the Gregorian calendar, however the years are counted
67/// differently using the Japanese era system.
68///
69/// This type can be used with [`Date`] to represent dates in this calendar.
70///
71/// [Japanese calendar]: https://en.wikipedia.org/wiki/Japanese_calendar
72///
73/// # Era codes
74///
75/// This calendar supports a large number of era codes. It supports the five post-Meiji eras
76/// (`meiji`, `taisho`, `showa`, `heisei`, `reiwa`). Pre-Meiji eras are represented
77/// with their names converted to lowercase ascii and followed by their start year. E.g. the *Ten'ō*
78/// era (781 - 782 CE) has the code `teno-781`. The  Gregorian `bce` (alias `bc`), and `ce` (alias `ad`)
79/// are used for dates before the first known era era.
80///
81///
82/// These eras are loaded from data, requiring a data provider capable of providing [`CalendarJapaneseExtendedV1`]
83/// data.
84///
85/// # Month codes
86///
87/// This calendar supports 12 solar month codes (`M01` - `M12`)
88#[derive(Clone, Debug, Default)]
89pub struct JapaneseExtended(Japanese);
90
91#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
92/// The inner date type used for representing [`Date`]s of [`Japanese`]. See [`Date`] and [`Japanese`] for more details.
93pub struct JapaneseDateInner {
94    inner: IsoDateInner,
95    adjusted_year: i32,
96    era: TinyStr16,
97}
98
99impl Japanese {
100    /// Creates a new [`Japanese`] using only modern eras (post-meiji) from compiled data.
101    ///
102    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
103    ///
104    /// [📚 Help choosing a constructor](icu_provider::constructors)
105    #[cfg(feature = "compiled_data")]
106    pub const fn new() -> Self {
107        Self {
108            eras: DataPayload::from_static_ref(
109                crate::provider::Baked::SINGLETON_CALENDAR_JAPANESE_MODERN_V1,
110            ),
111        }
112    }
113
114    icu_provider::gen_buffer_data_constructors!(() -> error: DataError,
115        functions: [
116            new: skip,
117            try_new_with_buffer_provider,
118            try_new_unstable,
119            Self,
120    ]);
121
122    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new)]
123    pub fn try_new_unstable<D: DataProvider<CalendarJapaneseModernV1> + ?Sized>(
124        provider: &D,
125    ) -> Result<Self, DataError> {
126        Ok(Self {
127            eras: provider.load(Default::default())?.payload,
128        })
129    }
130
131    pub(crate) const DEBUG_NAME: &'static str = "Japanese";
132}
133
134impl JapaneseExtended {
135    /// Creates a new [`Japanese`] from using all eras (including pre-meiji) from compiled data.
136    ///
137    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
138    ///
139    /// [📚 Help choosing a constructor](icu_provider::constructors)
140    #[cfg(feature = "compiled_data")]
141    pub const fn new() -> Self {
142        Self(Japanese {
143            eras: DataPayload::from_static_ref(
144                crate::provider::Baked::SINGLETON_CALENDAR_JAPANESE_EXTENDED_V1,
145            ),
146        })
147    }
148
149    icu_provider::gen_buffer_data_constructors!(() -> error: DataError,
150        functions: [
151            new: skip,
152            try_new_with_buffer_provider,
153            try_new_unstable,
154            Self,
155    ]);
156
157    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new)]
158    pub fn try_new_unstable<D: DataProvider<CalendarJapaneseExtendedV1> + ?Sized>(
159        provider: &D,
160    ) -> Result<Self, DataError> {
161        Ok(Self(Japanese {
162            eras: provider.load(Default::default())?.payload.cast(),
163        }))
164    }
165
166    pub(crate) const DEBUG_NAME: &'static str = "Japanese (historical era data)";
167}
168
169impl crate::cal::scaffold::UnstableSealed for Japanese {}
170impl Calendar for Japanese {
171    type DateInner = JapaneseDateInner;
172    type Year = types::EraYear;
173
174    fn from_codes(
175        &self,
176        era: Option<&str>,
177        year: i32,
178        month_code: types::MonthCode,
179        day: u8,
180    ) -> Result<Self::DateInner, DateError> {
181        let Some((month, false)) = month_code.parsed() else {
182            return Err(DateError::UnknownMonthCode(month_code));
183        };
184
185        if month > 12 {
186            return Err(DateError::UnknownMonthCode(month_code));
187        }
188
189        self.new_japanese_date_inner(era.unwrap_or("ce"), year, month, day)
190    }
191
192    fn from_rata_die(&self, rd: RataDie) -> Self::DateInner {
193        self.from_iso(Iso.from_rata_die(rd))
194    }
195
196    fn to_rata_die(&self, date: &Self::DateInner) -> RataDie {
197        Iso.to_rata_die(&self.to_iso(date))
198    }
199
200    fn from_iso(&self, iso: IsoDateInner) -> JapaneseDateInner {
201        let (adjusted_year, era) = self.adjusted_year_for(iso);
202        JapaneseDateInner {
203            inner: iso,
204            adjusted_year,
205            era,
206        }
207    }
208
209    fn to_iso(&self, date: &Self::DateInner) -> IsoDateInner {
210        date.inner
211    }
212
213    fn months_in_year(&self, date: &Self::DateInner) -> u8 {
214        Iso.months_in_year(&date.inner)
215    }
216
217    fn days_in_year(&self, date: &Self::DateInner) -> u16 {
218        Iso.days_in_year(&date.inner)
219    }
220
221    fn days_in_month(&self, date: &Self::DateInner) -> u8 {
222        Iso.days_in_month(&date.inner)
223    }
224
225    fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
226        Iso.offset_date(&mut date.inner, offset.cast_unit());
227        let (adjusted_year, era) = self.adjusted_year_for(date.inner);
228        date.adjusted_year = adjusted_year;
229        date.era = era
230    }
231
232    fn until(
233        &self,
234        date1: &Self::DateInner,
235        date2: &Self::DateInner,
236        _calendar2: &Self,
237        largest_unit: DateDurationUnit,
238        smallest_unit: DateDurationUnit,
239    ) -> DateDuration<Self> {
240        Iso.until(
241            &date1.inner,
242            &date2.inner,
243            &Iso,
244            largest_unit,
245            smallest_unit,
246        )
247        .cast_unit()
248    }
249
250    fn year_info(&self, date: &Self::DateInner) -> Self::Year {
251        types::EraYear {
252            era: date.era,
253            era_index: None,
254            year: date.adjusted_year,
255            ambiguity: types::YearAmbiguity::CenturyRequired,
256        }
257    }
258
259    fn extended_year(&self, date: &Self::DateInner) -> i32 {
260        Iso.extended_year(&date.inner)
261    }
262
263    fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
264        Iso.is_in_leap_year(&date.inner)
265    }
266
267    /// The calendar-specific month represented by `date`
268    fn month(&self, date: &Self::DateInner) -> types::MonthInfo {
269        Iso.month(&date.inner)
270    }
271
272    /// The calendar-specific day-of-month represented by `date`
273    fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
274        Iso.day_of_month(&date.inner)
275    }
276
277    fn day_of_year(&self, date: &Self::DateInner) -> types::DayOfYear {
278        Iso.day_of_year(&date.inner)
279    }
280
281    fn debug_name(&self) -> &'static str {
282        Self::DEBUG_NAME
283    }
284
285    fn calendar_algorithm(&self) -> Option<crate::preferences::CalendarAlgorithm> {
286        Some(crate::preferences::CalendarAlgorithm::Japanese)
287    }
288}
289
290impl crate::cal::scaffold::UnstableSealed for JapaneseExtended {}
291impl Calendar for JapaneseExtended {
292    type DateInner = JapaneseDateInner;
293    type Year = types::EraYear;
294
295    fn from_codes(
296        &self,
297        era: Option<&str>,
298        year: i32,
299        month_code: types::MonthCode,
300        day: u8,
301    ) -> Result<Self::DateInner, DateError> {
302        self.0.from_codes(era, year, month_code, day)
303    }
304
305    fn from_rata_die(&self, rd: RataDie) -> Self::DateInner {
306        Japanese::from_rata_die(&self.0, rd)
307    }
308
309    fn to_rata_die(&self, date: &Self::DateInner) -> RataDie {
310        Japanese::to_rata_die(&self.0, date)
311    }
312
313    fn from_iso(&self, iso: IsoDateInner) -> JapaneseDateInner {
314        Japanese::from_iso(&self.0, iso)
315    }
316
317    fn to_iso(&self, date: &Self::DateInner) -> IsoDateInner {
318        Japanese::to_iso(&self.0, date)
319    }
320
321    fn months_in_year(&self, date: &Self::DateInner) -> u8 {
322        Japanese::months_in_year(&self.0, date)
323    }
324
325    fn days_in_year(&self, date: &Self::DateInner) -> u16 {
326        Japanese::days_in_year(&self.0, date)
327    }
328
329    fn days_in_month(&self, date: &Self::DateInner) -> u8 {
330        Japanese::days_in_month(&self.0, date)
331    }
332
333    fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
334        Japanese::offset_date(&self.0, date, offset.cast_unit())
335    }
336
337    fn until(
338        &self,
339        date1: &Self::DateInner,
340        date2: &Self::DateInner,
341        calendar2: &Self,
342        largest_unit: DateDurationUnit,
343        smallest_unit: DateDurationUnit,
344    ) -> DateDuration<Self> {
345        Japanese::until(
346            &self.0,
347            date1,
348            date2,
349            &calendar2.0,
350            largest_unit,
351            smallest_unit,
352        )
353        .cast_unit()
354    }
355
356    fn year_info(&self, date: &Self::DateInner) -> Self::Year {
357        Japanese::year_info(&self.0, date)
358    }
359
360    fn extended_year(&self, date: &Self::DateInner) -> i32 {
361        Japanese::extended_year(&self.0, date)
362    }
363
364    fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
365        Japanese::is_in_leap_year(&self.0, date)
366    }
367
368    /// The calendar-specific month represented by `date`
369    fn month(&self, date: &Self::DateInner) -> types::MonthInfo {
370        Japanese::month(&self.0, date)
371    }
372
373    /// The calendar-specific day-of-month represented by `date`
374    fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
375        Japanese::day_of_month(&self.0, date)
376    }
377
378    /// Information of the day of the year
379    fn day_of_year(&self, date: &Self::DateInner) -> types::DayOfYear {
380        Japanese::day_of_year(&self.0, date)
381    }
382
383    fn debug_name(&self) -> &'static str {
384        Self::DEBUG_NAME
385    }
386
387    fn calendar_algorithm(&self) -> Option<crate::preferences::CalendarAlgorithm> {
388        Some(crate::preferences::CalendarAlgorithm::Japanese)
389    }
390}
391
392impl Date<Japanese> {
393    /// Construct a new Japanese Date.
394    ///
395    /// Years are specified in the era provided, and must be in range for Japanese
396    /// eras (e.g. dates past April 30 Heisei 31 must be in Reiwa; "Jun 5 Heisei 31" and "Jan 1 Heisei 32"
397    /// will not be adjusted to being in Reiwa 1 and 2 respectively)
398    ///
399    /// However, dates may always be specified in "bce" or "ce" and they will be adjusted as necessary.
400    ///
401    /// ```rust
402    /// use icu::calendar::cal::Japanese;
403    /// use icu::calendar::{Date, Ref};
404    /// use tinystr::tinystr;
405    ///
406    /// let japanese_calendar = Japanese::new();
407    /// // for easy sharing
408    /// let japanese_calendar = Ref(&japanese_calendar);
409    ///
410    /// let era = "heisei";
411    ///
412    /// let date =
413    ///     Date::try_new_japanese_with_calendar(era, 14, 1, 2, japanese_calendar)
414    ///         .expect("Constructing a date should succeed");
415    ///
416    /// assert_eq!(date.era_year().era, era);
417    /// assert_eq!(date.era_year().year, 14);
418    /// assert_eq!(date.month().ordinal, 1);
419    /// assert_eq!(date.day_of_month().0, 2);
420    ///
421    /// // This function will error for eras that are out of bounds:
422    /// // (Heisei was 32 years long, Heisei 33 is in Reiwa)
423    /// let oob_date =
424    ///     Date::try_new_japanese_with_calendar(era, 33, 1, 2, japanese_calendar);
425    /// assert!(oob_date.is_err());
426    ///
427    /// // and for unknown eras
428    /// let fake_era = "neko"; // 🐱
429    /// let fake_date = Date::try_new_japanese_with_calendar(
430    ///     fake_era,
431    ///     10,
432    ///     1,
433    ///     2,
434    ///     japanese_calendar,
435    /// );
436    /// assert!(fake_date.is_err());
437    /// ```
438    pub fn try_new_japanese_with_calendar<A: AsCalendar<Calendar = Japanese>>(
439        era: &str,
440        year: i32,
441        month: u8,
442        day: u8,
443        japanese_calendar: A,
444    ) -> Result<Date<A>, DateError> {
445        let inner = japanese_calendar
446            .as_calendar()
447            .new_japanese_date_inner(era, year, month, day)?;
448        Ok(Date::from_raw(inner, japanese_calendar))
449    }
450}
451
452impl Date<JapaneseExtended> {
453    /// Construct a new Japanese Date with all eras.
454    ///
455    /// Years are specified in the era provided, and must be in range for Japanese
456    /// eras (e.g. dates past April 30 Heisei 31 must be in Reiwa; "Jun 5 Heisei 31" and "Jan 1 Heisei 32"
457    /// will not be adjusted to being in Reiwa 1 and 2 respectively)
458    ///
459    /// However, dates may always be specified in "bce" or "ce" and they will be adjusted as necessary.
460    ///
461    /// ```rust
462    /// use icu::calendar::cal::JapaneseExtended;
463    /// use icu::calendar::{Date, Ref};
464    /// use tinystr::tinystr;
465    ///
466    /// let japanext_calendar = JapaneseExtended::new();
467    /// // for easy sharing
468    /// let japanext_calendar = Ref(&japanext_calendar);
469    ///
470    /// let era = "kansei-1789";
471    ///
472    /// let date = Date::try_new_japanese_extended_with_calendar(
473    ///     era,
474    ///     7,
475    ///     1,
476    ///     2,
477    ///     japanext_calendar,
478    /// )
479    /// .expect("Constructing a date should succeed");
480    ///
481    /// assert_eq!(date.era_year().era, era);
482    /// assert_eq!(date.era_year().year, 7);
483    /// assert_eq!(date.month().ordinal, 1);
484    /// assert_eq!(date.day_of_month().0, 2);
485    /// ```
486    pub fn try_new_japanese_extended_with_calendar<A: AsCalendar<Calendar = JapaneseExtended>>(
487        era: &str,
488        year: i32,
489        month: u8,
490        day: u8,
491        japanext_calendar: A,
492    ) -> Result<Date<A>, DateError> {
493        let inner = japanext_calendar
494            .as_calendar()
495            .0
496            .new_japanese_date_inner(era, year, month, day)?;
497        Ok(Date::from_raw(inner, japanext_calendar))
498    }
499}
500
501const MEIJI_START: EraStartDate = EraStartDate {
502    year: 1868,
503    month: 10,
504    day: 23,
505};
506const TAISHO_START: EraStartDate = EraStartDate {
507    year: 1912,
508    month: 7,
509    day: 30,
510};
511const SHOWA_START: EraStartDate = EraStartDate {
512    year: 1926,
513    month: 12,
514    day: 25,
515};
516const HEISEI_START: EraStartDate = EraStartDate {
517    year: 1989,
518    month: 1,
519    day: 8,
520};
521const REIWA_START: EraStartDate = EraStartDate {
522    year: 2019,
523    month: 5,
524    day: 1,
525};
526
527impl Japanese {
528    /// Given an ISO date, give year and era for that date in the Japanese calendar
529    ///
530    /// This will also use Gregorian eras for eras that are before the earliest era
531    fn adjusted_year_for(&self, date: IsoDateInner) -> (i32, TinyStr16) {
532        let date: EraStartDate = EraStartDate {
533            year: date.0.year,
534            month: date.0.month,
535            day: date.0.day,
536        };
537        let (start, era) = self.japanese_era_for(date);
538        // The year in which an era starts is Year 1, and it may be short
539        // The only time this function will experience dates that are *before*
540        // the era start date are for the first era (Currently, taika-645
541        // for japanext, meiji for japanese),
542        // In such a case, we instead fall back to Gregorian era codes
543        if date < start {
544            if date.year <= 0 {
545                (1 - date.year, tinystr!(16, "bce"))
546            } else {
547                (date.year, tinystr!(16, "ce"))
548            }
549        } else {
550            (date.year - start.year + 1, era)
551        }
552    }
553
554    /// Given an date, obtain the era data (not counting spliced gregorian eras)
555    fn japanese_era_for(&self, date: EraStartDate) -> (EraStartDate, TinyStr16) {
556        let era_data = self.eras.get();
557        // We optimize for the five "modern" post-Meiji eras, which are stored in a smaller
558        // array and also hardcoded. The hardcoded version is not used if data indicates the
559        // presence of newer eras.
560        if date >= MEIJI_START
561            && era_data.dates_to_eras.last().map(|x| x.1) == Some(tinystr!(16, "reiwa"))
562        {
563            // Fast path in case eras have not changed since this code was written
564            return if date >= REIWA_START {
565                (REIWA_START, tinystr!(16, "reiwa"))
566            } else if date >= HEISEI_START {
567                (HEISEI_START, tinystr!(16, "heisei"))
568            } else if date >= SHOWA_START {
569                (SHOWA_START, tinystr!(16, "showa"))
570            } else if date >= TAISHO_START {
571                (TAISHO_START, tinystr!(16, "taisho"))
572            } else {
573                (MEIJI_START, tinystr!(16, "meiji"))
574            };
575        }
576        let data = &era_data.dates_to_eras;
577        match data.binary_search_by(|(d, _)| d.cmp(&date)) {
578            Ok(index) => data.get(index),
579            Err(index) if index == 0 => data.get(index),
580            Err(index) => data.get(index - 1).or_else(|| data.iter().next_back()),
581        }
582        .unwrap_or((REIWA_START, tinystr!(16, "reiwa")))
583    }
584
585    /// Returns the range of dates for a given Japanese era code,
586    /// not handling "bce" or "ce"
587    ///
588    /// Returns (era_start, era_end)
589    fn japanese_era_range_for(
590        &self,
591        era: TinyStr16,
592    ) -> Result<(EraStartDate, Option<EraStartDate>), DateError> {
593        // Avoid linear search by trying well known eras
594        if era == tinystr!(16, "reiwa") {
595            // Check if we're the last
596            if let Some(last) = self.eras.get().dates_to_eras.last() {
597                if last.1 == era {
598                    return Ok((REIWA_START, None));
599                }
600            }
601        } else if era == tinystr!(16, "heisei") {
602            return Ok((HEISEI_START, Some(REIWA_START)));
603        } else if era == tinystr!(16, "showa") {
604            return Ok((SHOWA_START, Some(HEISEI_START)));
605        } else if era == tinystr!(16, "taisho") {
606            return Ok((TAISHO_START, Some(SHOWA_START)));
607        } else if era == tinystr!(16, "meiji") {
608            return Ok((MEIJI_START, Some(TAISHO_START)));
609        }
610
611        let era_data = self.eras.get();
612        let data = &era_data.dates_to_eras;
613        // Try to avoid linear search by binary searching for the year suffix
614        if let Some(year) = era.split('-').nth(1) {
615            if let Ok(ref int) = year.parse::<i32>() {
616                if let Ok(index) = data.binary_search_by(|(d, _)| d.year.cmp(int)) {
617                    #[allow(clippy::expect_used)] // see expect message
618                    let (era_start, code) = data
619                        .get(index)
620                        .expect("Indexing from successful binary search must succeed");
621                    // There is a slight chance we hit the case where there are two eras in the same year
622                    // There are a couple of rare cases of this, but it's not worth writing a range-based binary search
623                    // to catch them since this is an optimization
624                    if code == era {
625                        return Ok((era_start, data.get(index + 1).map(|e| e.0)));
626                    }
627                }
628            }
629        }
630
631        // Avoidance didn't work. Let's find the era manually, searching back from the present
632        if let Some((index, (start, _))) = data.iter().enumerate().rev().find(|d| d.1 .1 == era) {
633            return Ok((start, data.get(index + 1).map(|e| e.0)));
634        }
635
636        Err(DateError::UnknownEra)
637    }
638
639    fn new_japanese_date_inner(
640        &self,
641        era: &str,
642        year: i32,
643        month: u8,
644        day: u8,
645    ) -> Result<JapaneseDateInner, DateError> {
646        let cal = Ref(self);
647        let era = match era {
648            "ce" | "ad" => {
649                return Ok(Date::try_new_gregorian(year_check(year, 1..)?, month, day)?
650                    .to_calendar(cal)
651                    .inner);
652            }
653            "bce" | "bc" => {
654                return Ok(
655                    Date::try_new_gregorian(1 - year_check(year, 1..)?, month, day)?
656                        .to_calendar(cal)
657                        .inner,
658                );
659            }
660            e => e.parse().map_err(|_| DateError::UnknownEra)?,
661        };
662
663        let (era_start, next_era_start) = self.japanese_era_range_for(era)?;
664
665        let next_era_start = next_era_start.unwrap_or(EraStartDate {
666            year: i32::MAX,
667            month: 12,
668            day: 31,
669        });
670
671        let date_in_iso = EraStartDate {
672            year: era_start.year + year - 1,
673            month,
674            day,
675        };
676
677        if date_in_iso < era_start {
678            return Err(if date_in_iso.year < era_start.year {
679                DateError::Range {
680                    field: "year",
681                    value: year,
682                    min: 1,
683                    max: 1 + next_era_start.year - era_start.year,
684                }
685            } else if date_in_iso.month < era_start.month {
686                DateError::Range {
687                    field: "month",
688                    value: month as i32,
689                    min: era_start.month as i32,
690                    max: 12,
691                }
692            } else
693            /* if date_in_iso.day < era_start.day */
694            {
695                DateError::Range {
696                    field: "day",
697                    value: day as i32,
698                    min: era_start.day as i32,
699                    max: 31,
700                }
701            });
702        } else if date_in_iso >= next_era_start {
703            return Err(if date_in_iso.year > era_start.year {
704                DateError::Range {
705                    field: "year",
706                    value: year,
707                    min: 1,
708                    max: 1 + next_era_start.year - era_start.year,
709                }
710            } else if date_in_iso.month > era_start.month {
711                DateError::Range {
712                    field: "month",
713                    value: month as i32,
714                    min: 1,
715                    max: next_era_start.month as i32 - 1,
716                }
717            } else
718            /* if date_in_iso.day >= era_start.day */
719            {
720                DateError::Range {
721                    field: "day",
722                    value: day as i32,
723                    min: 1,
724                    max: next_era_start.day as i32 - 1,
725                }
726            });
727        }
728
729        let iso = Date::try_new_iso(date_in_iso.year, date_in_iso.month, date_in_iso.day)?;
730        Ok(JapaneseDateInner {
731            inner: iso.inner,
732            adjusted_year: year,
733            era,
734        })
735    }
736}
737
738#[cfg(test)]
739mod tests {
740    use super::*;
741    use crate::Ref;
742
743    fn single_test_roundtrip(calendar: Ref<Japanese>, era: &str, year: i32, month: u8, day: u8) {
744        let date = Date::try_new_japanese_with_calendar(era, year, month, day, calendar)
745            .unwrap_or_else(|e| {
746                panic!("Failed to construct date with {era:?}, {year}, {month}, {day}: {e:?}")
747            });
748        let iso = date.to_iso();
749        let reconstructed = Date::new_from_iso(iso, calendar);
750        assert_eq!(
751            date, reconstructed,
752            "Failed to roundtrip with {era:?}, {year}, {month}, {day}"
753        );
754
755        // Extra coverage for https://github.com/unicode-org/icu4x/issues/4968
756        assert_eq!(reconstructed.era_year().era, era);
757        assert_eq!(reconstructed.era_year().year, year);
758    }
759
760    fn single_test_roundtrip_ext(
761        calendar: Ref<JapaneseExtended>,
762        era: &str,
763        year: i32,
764        month: u8,
765        day: u8,
766    ) {
767        let date = Date::try_new_japanese_extended_with_calendar(era, year, month, day, calendar)
768            .unwrap_or_else(|e| {
769                panic!("Failed to construct date with {era:?}, {year}, {month}, {day}: {e:?}")
770            });
771        let iso = date.to_iso();
772        let reconstructed = Date::new_from_iso(iso, calendar);
773        assert_eq!(
774            date, reconstructed,
775            "Failed to roundtrip with {era:?}, {year}, {month}, {day}"
776        )
777    }
778
779    // test that the Gregorian eras roundtrip to Japanese ones
780    fn single_test_gregorian_roundtrip_ext(
781        calendar: Ref<JapaneseExtended>,
782        era: &str,
783        year: i32,
784        month: u8,
785        day: u8,
786        era2: &str,
787        year2: i32,
788    ) {
789        let expected = Date::try_new_japanese_extended_with_calendar(era2, year2, month, day, calendar)
790            .unwrap_or_else(|e| {
791                panic!(
792                    "Failed to construct expectation date with {era2:?}, {year2}, {month}, {day}: {e:?}"
793                )
794            });
795
796        let date = Date::try_new_japanese_extended_with_calendar(era, year, month, day, calendar)
797            .unwrap_or_else(|e| {
798                panic!("Failed to construct date with {era:?}, {year}, {month}, {day}: {e:?}")
799            });
800        let iso = date.to_iso();
801        let reconstructed = Date::new_from_iso(iso, calendar);
802        assert_eq!(
803            expected, reconstructed,
804            "Failed to roundtrip with {era:?}, {year}, {month}, {day} == {era2:?}, {year}"
805        )
806    }
807
808    fn single_test_error(
809        calendar: Ref<Japanese>,
810        era: &str,
811        year: i32,
812        month: u8,
813        day: u8,
814        error: DateError,
815    ) {
816        let date = Date::try_new_japanese_with_calendar(era, year, month, day, calendar);
817        assert_eq!(
818            date,
819            Err(error),
820            "Construction with {era:?}, {year}, {month}, {day} did not return {error:?}"
821        )
822    }
823
824    fn single_test_error_ext(
825        calendar: Ref<JapaneseExtended>,
826        era: &str,
827        year: i32,
828        month: u8,
829        day: u8,
830        error: DateError,
831    ) {
832        let date = Date::try_new_japanese_extended_with_calendar(era, year, month, day, calendar);
833        assert_eq!(
834            date,
835            Err(error),
836            "Construction with {era:?}, {year}, {month}, {day} did not return {error:?}"
837        )
838    }
839
840    #[test]
841    fn test_japanese() {
842        let calendar = Japanese::new();
843        let calendar_ext = JapaneseExtended::new();
844        let calendar = Ref(&calendar);
845        let calendar_ext = Ref(&calendar_ext);
846
847        single_test_roundtrip(calendar, "heisei", 12, 3, 1);
848        single_test_roundtrip(calendar, "taisho", 3, 3, 1);
849        // Heisei did not start until later in the year
850        single_test_error(
851            calendar,
852            "heisei",
853            1,
854            1,
855            1,
856            DateError::Range {
857                field: "day",
858                value: 1,
859                min: 8,
860                max: 31,
861            },
862        );
863
864        single_test_roundtrip_ext(calendar_ext, "heisei", 12, 3, 1);
865        single_test_roundtrip_ext(calendar_ext, "taisho", 3, 3, 1);
866        single_test_error_ext(
867            calendar_ext,
868            "heisei",
869            1,
870            1,
871            1,
872            DateError::Range {
873                field: "day",
874                value: 1,
875                min: 8,
876                max: 31,
877            },
878        );
879
880        single_test_roundtrip_ext(calendar_ext, "hakuho-672", 4, 3, 1);
881        single_test_error(calendar, "hakuho-672", 4, 3, 1, DateError::UnknownEra);
882
883        // handle bce/ce
884        single_test_roundtrip(calendar, "bce", 100, 3, 1);
885        single_test_roundtrip(calendar, "bce", 1, 3, 1);
886        single_test_roundtrip(calendar, "ce", 1, 3, 1);
887        single_test_roundtrip(calendar, "ce", 100, 3, 1);
888        single_test_roundtrip_ext(calendar_ext, "ce", 100, 3, 1);
889        single_test_roundtrip(calendar, "ce", 1000, 3, 1);
890        single_test_error(
891            calendar,
892            "ce",
893            0,
894            3,
895            1,
896            DateError::Range {
897                field: "year",
898                value: 0,
899                min: 1,
900                max: i32::MAX,
901            },
902        );
903        single_test_error(
904            calendar,
905            "bce",
906            -1,
907            3,
908            1,
909            DateError::Range {
910                field: "year",
911                value: -1,
912                min: 1,
913                max: i32::MAX,
914            },
915        );
916
917        // handle the cases where bce/ce get adjusted to different eras
918        // single_test_gregorian_roundtrip(calendar, "ce", 2021, 3, 1, "reiwa", 3);
919        single_test_gregorian_roundtrip_ext(calendar_ext, "ce", 1000, 3, 1, "choho-999", 2);
920        single_test_gregorian_roundtrip_ext(calendar_ext, "ce", 749, 5, 10, "tenpyokampo-749", 1);
921        single_test_gregorian_roundtrip_ext(calendar_ext, "bce", 10, 3, 1, "bce", 10);
922
923        // There were multiple eras in this year
924        // This one is from Apr 14 to July 2
925        single_test_roundtrip_ext(calendar_ext, "tenpyokampo-749", 1, 4, 20);
926        single_test_roundtrip_ext(calendar_ext, "tenpyokampo-749", 1, 4, 14);
927        single_test_roundtrip_ext(calendar_ext, "tenpyokampo-749", 1, 7, 1);
928        single_test_error_ext(
929            calendar_ext,
930            "tenpyokampo-749",
931            1,
932            7,
933            5,
934            DateError::Range {
935                field: "month",
936                value: 7,
937                min: 1,
938                max: 6,
939            },
940        );
941        single_test_error_ext(
942            calendar_ext,
943            "tenpyokampo-749",
944            1,
945            4,
946            13,
947            DateError::Range {
948                field: "day",
949                value: 13,
950                min: 14,
951                max: 31,
952            },
953        );
954    }
955}