Coverage Report

Created: 2025-07-12 07:16

/rust/registry/src/index.crates.io-6f17d22bba15001f/icu_calendar-1.5.2/src/buddhist.rs
Line
Count
Source (jump to first uncovered line)
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 Buddhist calendar.
6
//!
7
//! ```rust
8
//! use icu::calendar::{buddhist::Buddhist, Date, DateTime};
9
//!
10
//! // `Date` type
11
//! let date_iso = Date::try_new_iso_date(1970, 1, 2)
12
//!     .expect("Failed to initialize ISO Date instance.");
13
//! let date_buddhist = Date::new_from_iso(date_iso, Buddhist);
14
//!
15
//! // `DateTime` type
16
//! let datetime_iso = DateTime::try_new_iso_datetime(1970, 1, 2, 13, 1, 0)
17
//!     .expect("Failed to initialize ISO DateTime instance.");
18
//! let datetime_buddhist = DateTime::new_from_iso(datetime_iso, Buddhist);
19
//!
20
//! // `Date` checks
21
//! assert_eq!(date_buddhist.year().number, 2513);
22
//! assert_eq!(date_buddhist.month().ordinal, 1);
23
//! assert_eq!(date_buddhist.day_of_month().0, 2);
24
//!
25
//! // `DateTime` type
26
//! assert_eq!(datetime_buddhist.date.year().number, 2513);
27
//! assert_eq!(datetime_buddhist.date.month().ordinal, 1);
28
//! assert_eq!(datetime_buddhist.date.day_of_month().0, 2);
29
//! assert_eq!(datetime_buddhist.time.hour.number(), 13);
30
//! assert_eq!(datetime_buddhist.time.minute.number(), 1);
31
//! assert_eq!(datetime_buddhist.time.second.number(), 0);
32
//! ```
33
34
use crate::any_calendar::AnyCalendarKind;
35
use crate::calendar_arithmetic::ArithmeticDate;
36
use crate::iso::{Iso, IsoDateInner};
37
use crate::{types, Calendar, CalendarError, Date, DateDuration, DateDurationUnit, DateTime, Time};
38
use tinystr::tinystr;
39
40
/// The number of years the Buddhist Era is ahead of C.E. by
41
///
42
/// (1 AD = 544 BE)
43
const BUDDHIST_ERA_OFFSET: i32 = 543;
44
45
#[derive(Copy, Clone, Debug, Default)]
46
/// The [Thai Solar Buddhist Calendar][cal]
47
///
48
/// The [Thai Solar Buddhist Calendar][cal] is a solar calendar used in Thailand, with twelve months.
49
/// The months and days are identical to that of the Gregorian calendar, however the years are counted
50
/// differently using the Buddhist Era.
51
///
52
/// This type can be used with [`Date`] or [`DateTime`] to represent dates in this calendar.
53
///
54
/// [cal]: https://en.wikipedia.org/wiki/Thai_solar_calendar
55
///
56
/// # Era codes
57
///
58
/// This calendar supports one era, `"be"`, with 1 B.E. being 543 BCE.
59
///
60
/// # Month codes
61
///
62
/// This calendar supports 12 solar month codes (`"M01" - "M12"`)
63
#[allow(clippy::exhaustive_structs)] // this type is stable
64
pub struct Buddhist;
65
66
impl Calendar for Buddhist {
67
    type DateInner = IsoDateInner;
68
69
0
    fn date_from_codes(
70
0
        &self,
71
0
        era: types::Era,
72
0
        year: i32,
73
0
        month_code: types::MonthCode,
74
0
        day: u8,
75
0
    ) -> Result<Self::DateInner, CalendarError> {
76
0
        if era.0 != tinystr!(16, "be") {
77
0
            return Err(CalendarError::UnknownEra(era.0, self.debug_name()));
78
0
        }
79
0
        let year = year - BUDDHIST_ERA_OFFSET;
80
0
81
0
        ArithmeticDate::new_from_codes(self, year, month_code, day).map(IsoDateInner)
82
0
    }
83
0
    fn date_from_iso(&self, iso: Date<Iso>) -> IsoDateInner {
84
0
        *iso.inner()
85
0
    }
86
87
0
    fn date_to_iso(&self, date: &Self::DateInner) -> Date<Iso> {
88
0
        Date::from_raw(*date, Iso)
89
0
    }
90
91
0
    fn months_in_year(&self, date: &Self::DateInner) -> u8 {
92
0
        Iso.months_in_year(date)
93
0
    }
94
95
0
    fn days_in_year(&self, date: &Self::DateInner) -> u16 {
96
0
        Iso.days_in_year(date)
97
0
    }
98
99
0
    fn days_in_month(&self, date: &Self::DateInner) -> u8 {
100
0
        Iso.days_in_month(date)
101
0
    }
102
103
0
    fn offset_date(&self, date: &mut Self::DateInner, offset: DateDuration<Self>) {
104
0
        Iso.offset_date(date, offset.cast_unit())
105
0
    }
106
107
    #[allow(clippy::field_reassign_with_default)] // it's more clear this way
108
0
    fn until(
109
0
        &self,
110
0
        date1: &Self::DateInner,
111
0
        date2: &Self::DateInner,
112
0
        _calendar2: &Self,
113
0
        largest_unit: DateDurationUnit,
114
0
        smallest_unit: DateDurationUnit,
115
0
    ) -> DateDuration<Self> {
116
0
        Iso.until(date1, date2, &Iso, largest_unit, smallest_unit)
117
0
            .cast_unit()
118
0
    }
119
120
    /// The calendar-specific year represented by `date`
121
0
    fn year(&self, date: &Self::DateInner) -> types::FormattableYear {
122
0
        iso_year_as_buddhist(date.0.year)
123
0
    }
124
125
0
    fn is_in_leap_year(&self, date: &Self::DateInner) -> bool {
126
0
        Iso.is_in_leap_year(date)
127
0
    }
128
129
    /// The calendar-specific month represented by `date`
130
0
    fn month(&self, date: &Self::DateInner) -> types::FormattableMonth {
131
0
        Iso.month(date)
132
0
    }
133
134
    /// The calendar-specific day-of-month represented by `date`
135
0
    fn day_of_month(&self, date: &Self::DateInner) -> types::DayOfMonth {
136
0
        Iso.day_of_month(date)
137
0
    }
138
139
    /// Information of the day of the year
140
0
    fn day_of_year_info(&self, date: &Self::DateInner) -> types::DayOfYearInfo {
141
0
        let prev_year = date.0.year - 1;
142
0
        let next_year = date.0.year + 1;
143
0
        types::DayOfYearInfo {
144
0
            day_of_year: Iso::day_of_year(*date),
145
0
            days_in_year: Iso::days_in_year_direct(date.0.year),
146
0
            prev_year: iso_year_as_buddhist(prev_year),
147
0
            days_in_prev_year: Iso::days_in_year_direct(prev_year),
148
0
            next_year: iso_year_as_buddhist(next_year),
149
0
        }
150
0
    }
151
152
0
    fn debug_name(&self) -> &'static str {
153
0
        "Buddhist"
154
0
    }
155
156
0
    fn any_calendar_kind(&self) -> Option<AnyCalendarKind> {
157
0
        Some(AnyCalendarKind::Buddhist)
158
0
    }
159
}
160
161
impl Date<Buddhist> {
162
    /// Construct a new Buddhist Date.
163
    ///
164
    /// Years are specified as BE years.
165
    ///
166
    /// ```rust
167
    /// use icu::calendar::Date;
168
    ///
169
    /// let date_buddhist = Date::try_new_buddhist_date(1970, 1, 2)
170
    ///     .expect("Failed to initialize Buddhist Date instance.");
171
    ///
172
    /// assert_eq!(date_buddhist.year().number, 1970);
173
    /// assert_eq!(date_buddhist.month().ordinal, 1);
174
    /// assert_eq!(date_buddhist.day_of_month().0, 2);
175
    /// ```
176
0
    pub fn try_new_buddhist_date(
177
0
        year: i32,
178
0
        month: u8,
179
0
        day: u8,
180
0
    ) -> Result<Date<Buddhist>, CalendarError> {
181
0
        Date::try_new_iso_date(year - BUDDHIST_ERA_OFFSET, month, day)
182
0
            .map(|d| Date::new_from_iso(d, Buddhist))
183
0
    }
184
}
185
186
impl DateTime<Buddhist> {
187
    /// Construct a new Buddhist datetime from integers.
188
    ///
189
    /// Years are specified as BE years.
190
    ///
191
    /// ```rust
192
    /// use icu::calendar::DateTime;
193
    ///
194
    /// let datetime_buddhist =
195
    ///     DateTime::try_new_buddhist_datetime(1970, 1, 2, 13, 1, 0)
196
    ///         .expect("Failed to initialize Buddhist DateTime instance.");
197
    ///
198
    /// assert_eq!(datetime_buddhist.date.year().number, 1970);
199
    /// assert_eq!(datetime_buddhist.date.month().ordinal, 1);
200
    /// assert_eq!(datetime_buddhist.date.day_of_month().0, 2);
201
    /// assert_eq!(datetime_buddhist.time.hour.number(), 13);
202
    /// assert_eq!(datetime_buddhist.time.minute.number(), 1);
203
    /// assert_eq!(datetime_buddhist.time.second.number(), 0);
204
    /// ```
205
0
    pub fn try_new_buddhist_datetime(
206
0
        year: i32,
207
0
        month: u8,
208
0
        day: u8,
209
0
        hour: u8,
210
0
        minute: u8,
211
0
        second: u8,
212
0
    ) -> Result<DateTime<Buddhist>, CalendarError> {
213
0
        Ok(DateTime {
214
0
            date: Date::try_new_buddhist_date(year, month, day)?,
215
0
            time: Time::try_new(hour, minute, second, 0)?,
216
        })
217
0
    }
218
}
219
220
0
fn iso_year_as_buddhist(year: i32) -> types::FormattableYear {
221
0
    let buddhist_year = year + BUDDHIST_ERA_OFFSET;
222
0
    types::FormattableYear {
223
0
        era: types::Era(tinystr!(16, "be")),
224
0
        number: buddhist_year,
225
0
        cyclic: None,
226
0
        related_iso: None,
227
0
    }
228
0
}
229
230
#[cfg(test)]
231
mod test {
232
    use calendrical_calculations::rata_die::RataDie;
233
234
    use super::*;
235
236
    #[test]
237
    fn test_buddhist_roundtrip_near_rd_zero() {
238
        for i in -10000..=10000 {
239
            let rd = RataDie::new(i);
240
            let iso1 = Iso::iso_from_fixed(rd);
241
            let buddhist = iso1.to_calendar(Buddhist);
242
            let iso2 = buddhist.to_calendar(Iso);
243
            let result = Iso::fixed_from_iso(iso2.inner);
244
            assert_eq!(rd, result);
245
        }
246
    }
247
248
    #[test]
249
    fn test_buddhist_roundtrip_near_epoch() {
250
        // Buddhist epoch start RD: -198326
251
        for i in -208326..=-188326 {
252
            let rd = RataDie::new(i);
253
            let iso1 = Iso::iso_from_fixed(rd);
254
            let buddhist = iso1.to_calendar(Buddhist);
255
            let iso2 = buddhist.to_calendar(Iso);
256
            let result = Iso::fixed_from_iso(iso2.inner);
257
            assert_eq!(rd, result);
258
        }
259
    }
260
261
    #[test]
262
    fn test_buddhist_directionality_near_rd_zero() {
263
        for i in -100..=100 {
264
            for j in -100..=100 {
265
                let iso_i = Iso::iso_from_fixed(RataDie::new(i));
266
                let iso_j = Iso::iso_from_fixed(RataDie::new(j));
267
268
                let buddhist_i = Date::new_from_iso(iso_i, Buddhist);
269
                let buddhist_j = Date::new_from_iso(iso_j, Buddhist);
270
271
                assert_eq!(
272
                    i.cmp(&j),
273
                    iso_i.cmp(&iso_j),
274
                    "ISO directionality inconsistent with directionality for i: {i}, j: {j}"
275
                );
276
277
                assert_eq!(
278
                    i.cmp(&j),
279
                    buddhist_i.cmp(&buddhist_j),
280
                    "Buddhist directionality inconsistent with directionality for i: {i}, j: {j}"
281
                );
282
            }
283
        }
284
    }
285
286
    #[test]
287
    fn test_buddhist_directionality_near_epoch() {
288
        // Buddhist epoch start RD: -198326
289
        for i in -198426..=-198226 {
290
            for j in -198426..=-198226 {
291
                let iso_i = Iso::iso_from_fixed(RataDie::new(i));
292
                let iso_j = Iso::iso_from_fixed(RataDie::new(j));
293
294
                let buddhist_i = Date::new_from_iso(iso_i, Buddhist);
295
                let buddhist_j = Date::new_from_iso(iso_j, Buddhist);
296
297
                assert_eq!(
298
                    i.cmp(&j),
299
                    iso_i.cmp(&iso_j),
300
                    "ISO directionality inconsistent with directionality for i: {i}, j: {j}"
301
                );
302
303
                assert_eq!(
304
                    i.cmp(&j),
305
                    buddhist_i.cmp(&buddhist_j),
306
                    "Buddhist directionality inconsistent with directionality for i: {i}, j: {j}"
307
                );
308
            }
309
        }
310
    }
311
312
    #[derive(Debug)]
313
    struct TestCase {
314
        iso_year: i32,
315
        iso_month: u8,
316
        iso_day: u8,
317
        buddhist_year: i32,
318
        buddhist_month: u8,
319
        buddhist_day: u8,
320
    }
321
322
    fn check_test_case(case: TestCase) {
323
        let iso_year = case.iso_year;
324
        let iso_month = case.iso_month;
325
        let iso_day = case.iso_day;
326
        let buddhist_year = case.buddhist_year;
327
        let buddhist_month = case.buddhist_month;
328
        let buddhist_day = case.buddhist_day;
329
330
        let iso1 = Date::try_new_iso_date(iso_year, iso_month, iso_day).unwrap();
331
        let buddhist1 = iso1.to_calendar(Buddhist);
332
        assert_eq!(
333
            buddhist1.year().number,
334
            buddhist_year,
335
            "Iso -> Buddhist year check failed for case: {case:?}"
336
        );
337
        assert_eq!(
338
            buddhist1.month().ordinal,
339
            buddhist_month as u32,
340
            "Iso -> Buddhist month check failed for case: {case:?}"
341
        );
342
        assert_eq!(
343
            buddhist1.day_of_month().0,
344
            buddhist_day as u32,
345
            "Iso -> Buddhist day check failed for case: {case:?}"
346
        );
347
348
        let buddhist2 =
349
            Date::try_new_buddhist_date(buddhist_year, buddhist_month, buddhist_day).unwrap();
350
        let iso2 = buddhist2.to_calendar(Iso);
351
        assert_eq!(
352
            iso2.year().number,
353
            iso_year,
354
            "Buddhist -> Iso year check failed for case: {case:?}"
355
        );
356
        assert_eq!(
357
            iso2.month().ordinal,
358
            iso_month as u32,
359
            "Buddhist -> Iso month check failed for case: {case:?}"
360
        );
361
        assert_eq!(
362
            iso2.day_of_month().0,
363
            iso_day as u32,
364
            "Buddhist -> Iso day check failed for case: {case:?}"
365
        );
366
    }
367
368
    #[test]
369
    fn test_buddhist_cases_near_rd_zero() {
370
        let cases = [
371
            TestCase {
372
                iso_year: -100,
373
                iso_month: 2,
374
                iso_day: 15,
375
                buddhist_year: 443,
376
                buddhist_month: 2,
377
                buddhist_day: 15,
378
            },
379
            TestCase {
380
                iso_year: -3,
381
                iso_month: 10,
382
                iso_day: 29,
383
                buddhist_year: 540,
384
                buddhist_month: 10,
385
                buddhist_day: 29,
386
            },
387
            TestCase {
388
                iso_year: 0,
389
                iso_month: 12,
390
                iso_day: 31,
391
                buddhist_year: 543,
392
                buddhist_month: 12,
393
                buddhist_day: 31,
394
            },
395
            TestCase {
396
                iso_year: 1,
397
                iso_month: 1,
398
                iso_day: 1,
399
                buddhist_year: 544,
400
                buddhist_month: 1,
401
                buddhist_day: 1,
402
            },
403
            TestCase {
404
                iso_year: 4,
405
                iso_month: 2,
406
                iso_day: 29,
407
                buddhist_year: 547,
408
                buddhist_month: 2,
409
                buddhist_day: 29,
410
            },
411
        ];
412
413
        for case in cases {
414
            check_test_case(case);
415
        }
416
    }
417
418
    #[test]
419
    fn test_buddhist_cases_near_epoch() {
420
        // 1 BE = 543 BCE = -542 ISO
421
        let cases = [
422
            TestCase {
423
                iso_year: -554,
424
                iso_month: 12,
425
                iso_day: 31,
426
                buddhist_year: -11,
427
                buddhist_month: 12,
428
                buddhist_day: 31,
429
            },
430
            TestCase {
431
                iso_year: -553,
432
                iso_month: 1,
433
                iso_day: 1,
434
                buddhist_year: -10,
435
                buddhist_month: 1,
436
                buddhist_day: 1,
437
            },
438
            TestCase {
439
                iso_year: -544,
440
                iso_month: 8,
441
                iso_day: 31,
442
                buddhist_year: -1,
443
                buddhist_month: 8,
444
                buddhist_day: 31,
445
            },
446
            TestCase {
447
                iso_year: -543,
448
                iso_month: 5,
449
                iso_day: 12,
450
                buddhist_year: 0,
451
                buddhist_month: 5,
452
                buddhist_day: 12,
453
            },
454
            TestCase {
455
                iso_year: -543,
456
                iso_month: 12,
457
                iso_day: 31,
458
                buddhist_year: 0,
459
                buddhist_month: 12,
460
                buddhist_day: 31,
461
            },
462
            TestCase {
463
                iso_year: -542,
464
                iso_month: 1,
465
                iso_day: 1,
466
                buddhist_year: 1,
467
                buddhist_month: 1,
468
                buddhist_day: 1,
469
            },
470
            TestCase {
471
                iso_year: -541,
472
                iso_month: 7,
473
                iso_day: 9,
474
                buddhist_year: 2,
475
                buddhist_month: 7,
476
                buddhist_day: 9,
477
            },
478
        ];
479
480
        for case in cases {
481
            check_test_case(case);
482
        }
483
    }
484
}