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