Skip to main content

icu_capi/
date.rs

1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5use ffi::IsoWeekOfYear;
6
7#[diplomat::bridge]
8#[diplomat::abi_rename = "icu4x_{0}_mv1"]
9pub mod ffi {
10    use alloc::boxed::Box;
11    use alloc::sync::Arc;
12    use core::fmt::Write;
13    #[cfg(feature = "unstable")]
14    use diplomat_runtime::DiplomatOption;
15    use icu_calendar::Iso;
16
17    use crate::unstable::calendar::ffi::Calendar;
18    #[cfg(feature = "unstable")]
19    use crate::unstable::errors::ffi::CalendarDateFromFieldsError;
20    use crate::unstable::errors::ffi::{CalendarError, Rfc9557ParseError};
21
22    use tinystr::TinyAsciiStr;
23
24    #[diplomat::enum_convert(icu_calendar::types::Weekday)]
25    #[diplomat::rust_link(icu::calendar::types::Weekday, Enum)]
26    #[non_exhaustive]
27    pub enum Weekday {
28        Monday = 1,
29        Tuesday,
30        Wednesday,
31        Thursday,
32        Friday,
33        Saturday,
34        Sunday,
35    }
36    #[diplomat::opaque]
37    #[diplomat::transparent_convert]
38    /// An ICU4X Date object capable of containing a ISO-8601 date
39    #[diplomat::rust_link(icu::calendar::Date, Struct)]
40    pub struct IsoDate(pub icu_calendar::Date<icu_calendar::Iso>);
41
42    impl IsoDate {
43        /// Creates a new [`IsoDate`] from the specified date.
44        #[diplomat::rust_link(icu::calendar::Date::try_new_iso, FnInStruct)]
45        #[diplomat::attr(supports = fallible_constructors, constructor)]
46        pub fn create(year: i32, month: u8, day: u8) -> Result<Box<IsoDate>, CalendarError> {
47            Ok(Box::new(IsoDate(icu_calendar::Date::try_new_iso(
48                year, month, day,
49            )?)))
50        }
51
52        /// Creates a new [`IsoDate`] from the given Rata Die
53        #[diplomat::rust_link(icu::calendar::Date::from_rata_die, FnInStruct)]
54        #[diplomat::attr(all(supports = named_constructors), named_constructor)]
55        #[diplomat::demo(default_constructor)]
56        #[diplomat::attr(demo_gen, disable)] // covered by Date
57        pub fn from_rata_die(rd: i64) -> Box<IsoDate> {
58            Box::new(IsoDate(icu_calendar::Date::from_rata_die(
59                icu_calendar::types::RataDie::new(rd),
60                Iso,
61            )))
62        }
63
64        /// Creates a new [`IsoDate`] from an IXDTF string.
65        #[diplomat::rust_link(icu::calendar::Date::try_from_str, FnInStruct)]
66        #[diplomat::rust_link(icu::calendar::Date::try_from_utf8, FnInStruct, hidden)]
67        #[diplomat::rust_link(icu::calendar::Date::from_str, FnInStruct, hidden)]
68        #[diplomat::attr(all(supports = fallible_constructors, supports = named_constructors), named_constructor)]
69        #[diplomat::attr(demo_gen, disable)] // covered by Date
70        pub fn from_string(v: &DiplomatStr) -> Result<Box<IsoDate>, Rfc9557ParseError> {
71            Ok(Box::new(IsoDate(icu_calendar::Date::try_from_utf8(
72                v, Iso,
73            )?)))
74        }
75
76        /// Convert this date to one in a different calendar
77        #[diplomat::rust_link(icu::calendar::Date::to_calendar, FnInStruct)]
78        #[diplomat::attr(demo_gen, disable)] // covered by Date
79        pub fn to_calendar(&self, calendar: &Calendar) -> Box<Date> {
80            Box::new(Date(self.0.to_calendar(calendar.0.clone())))
81        }
82
83        #[diplomat::rust_link(icu::calendar::Date::to_any, FnInStruct)]
84        #[diplomat::attr(demo_gen, disable)] // covered by Date
85        pub fn to_any(&self) -> Box<Date> {
86            Box::new(Date(self.0.to_any().into_atomic_ref_counted()))
87        }
88
89        /// Returns this date's Rata Die
90        #[diplomat::rust_link(icu::calendar::Date::to_rata_die, FnInStruct)]
91        #[diplomat::attr(auto, getter = "rata_die")]
92        #[diplomat::attr(demo_gen, disable)] // covered by Date
93        pub fn to_rata_die(&self) -> i64 {
94            self.0.to_rata_die().to_i64_date()
95        }
96
97        /// Returns the 1-indexed day in the year for this date
98        #[diplomat::rust_link(icu::calendar::Date::day_of_year, FnInStruct)]
99        #[diplomat::attr(auto, getter)]
100        #[diplomat::attr(demo_gen, disable)] // covered by Date
101        pub fn day_of_year(&self) -> u16 {
102            self.0.day_of_year().0
103        }
104
105        /// Returns the 1-indexed day in the month for this date
106        #[diplomat::rust_link(icu::calendar::Date::day_of_month, FnInStruct)]
107        #[diplomat::attr(auto, getter)]
108        #[diplomat::attr(demo_gen, disable)] // covered by Date
109        pub fn day_of_month(&self) -> u8 {
110            self.0.day_of_month().0
111        }
112
113        /// Returns the day in the week for this day
114        #[diplomat::rust_link(icu::calendar::Date::day_of_week, FnInStruct)]
115        #[diplomat::attr(auto, getter)]
116        #[diplomat::attr(demo_gen, disable)] // covered by Date
117        pub fn day_of_week(&self) -> Weekday {
118            self.0.day_of_week().into()
119        }
120
121        /// Returns the week number in this year, using week data
122        #[diplomat::rust_link(icu::calendar::Date::week_of_year, FnInStruct)]
123        #[cfg(feature = "calendar")]
124        #[diplomat::attr(demo_gen, disable)] // covered by Date
125        pub fn week_of_year(&self) -> IsoWeekOfYear {
126            self.0.week_of_year().into()
127        }
128
129        /// Returns 1-indexed number of the month of this date in its year
130        #[diplomat::rust_link(icu::calendar::types::MonthInfo::ordinal, StructField)]
131        #[diplomat::rust_link(icu::calendar::Date::month, FnInStruct, compact)]
132        #[diplomat::attr(auto, getter)]
133        #[diplomat::attr(demo_gen, disable)] // covered by Date
134        pub fn month(&self) -> u8 {
135            self.0.month().ordinal
136        }
137
138        /// Returns the year number in the current era for this date
139        ///
140        /// For calendars without an era, returns the extended year
141        #[diplomat::rust_link(icu::calendar::Date::year, FnInStruct)]
142        #[diplomat::attr(auto, getter)]
143        #[diplomat::attr(demo_gen, disable)] // covered by Date
144        pub fn year(&self) -> i32 {
145            self.0.extended_year()
146        }
147
148        /// Returns if the year is a leap year for this date
149        #[diplomat::rust_link(icu::calendar::Date::is_in_leap_year, FnInStruct)]
150        #[diplomat::attr(auto, getter)]
151        #[diplomat::attr(demo_gen, disable)] // covered by Date
152        pub fn is_in_leap_year(&self) -> bool {
153            self.0.is_in_leap_year()
154        }
155
156        /// Returns the number of months in the year represented by this date
157        #[diplomat::rust_link(icu::calendar::Date::months_in_year, FnInStruct)]
158        #[diplomat::attr(auto, getter)]
159        #[diplomat::attr(demo_gen, disable)] // covered by Date
160        pub fn months_in_year(&self) -> u8 {
161            self.0.months_in_year()
162        }
163
164        /// Returns the number of days in the month represented by this date
165        #[diplomat::rust_link(icu::calendar::Date::days_in_month, FnInStruct)]
166        #[diplomat::attr(auto, getter)]
167        #[diplomat::attr(demo_gen, disable)] // covered by Date
168        pub fn days_in_month(&self) -> u8 {
169            self.0.days_in_month()
170        }
171
172        /// Returns the number of days in the year represented by this date
173        #[diplomat::rust_link(icu::calendar::Date::days_in_year, FnInStruct)]
174        #[diplomat::attr(auto, getter)]
175        #[diplomat::attr(demo_gen, disable)] // covered by Date
176        pub fn days_in_year(&self) -> u16 {
177            self.0.days_in_year()
178        }
179    }
180
181    /// 🚧 This API is experimental and may experience breaking changes outside major releases.
182    #[diplomat::rust_link(icu::calendar::options::DateFromFieldsOptions, Struct)]
183    #[cfg(feature = "unstable")]
184    pub struct DateFromFieldsOptions {
185        pub overflow: DiplomatOption<DateOverflow>,
186        pub missing_fields_strategy: DiplomatOption<DateMissingFieldsStrategy>,
187    }
188
189    /// 🚧 This API is experimental and may experience breaking changes outside major releases.
190    #[diplomat::rust_link(icu::calendar::types::DateFields, Struct)]
191    #[cfg(feature = "unstable")]
192    pub struct DateFields<'a> {
193        pub era: DiplomatOption<&'a DiplomatStr>,
194        pub era_year: DiplomatOption<i32>,
195        pub extended_year: DiplomatOption<i32>,
196        pub month_code: DiplomatOption<&'a DiplomatStr>,
197        pub ordinal_month: DiplomatOption<u8>,
198        pub day: DiplomatOption<u8>,
199    }
200
201    /// 🚧 This API is experimental and may experience breaking changes outside major releases.
202    #[diplomat::enum_convert(icu_calendar::options::Overflow, needs_wildcard)]
203    #[diplomat::rust_link(icu::calendar::options::Overflow, Enum)]
204    #[non_exhaustive]
205    #[cfg(feature = "unstable")]
206    pub enum DateOverflow {
207        Constrain,
208        Reject,
209    }
210
211    /// 🚧 This API is experimental and may experience breaking changes outside major releases.
212    #[diplomat::enum_convert(icu_calendar::options::MissingFieldsStrategy, needs_wildcard)]
213    #[diplomat::rust_link(icu::calendar::options::MissingFieldsStrategy, Enum)]
214    #[non_exhaustive]
215    #[cfg(feature = "unstable")]
216    pub enum DateMissingFieldsStrategy {
217        Reject,
218        Ecma,
219    }
220
221    #[diplomat::opaque]
222    #[diplomat::transparent_convert]
223    /// An ICU4X Date object capable of containing a date for any calendar.
224    #[diplomat::rust_link(icu::calendar::Date, Struct)]
225    pub struct Date(pub icu_calendar::Date<Arc<icu_calendar::AnyCalendar>>);
226
227    impl Date {
228        /// Creates a new [`Date`] representing the ISO date
229        /// given but in a given calendar
230        #[diplomat::rust_link(icu::calendar::Date::new_from_iso, FnInStruct)]
231        #[diplomat::attr(all(supports = fallible_constructors, supports = named_constructors), named_constructor)]
232        #[diplomat::demo(default_constructor)]
233        pub fn from_iso_in_calendar(
234            iso_year: i32,
235            iso_month: u8,
236            iso_day: u8,
237            calendar: &Calendar,
238        ) -> Result<Box<Date>, CalendarError> {
239            let cal = calendar.0.clone();
240            Ok(Box::new(Date(
241                icu_calendar::Date::try_new_iso(iso_year, iso_month, iso_day)?.to_calendar(cal),
242            )))
243        }
244
245        /// Creates a new [`Date`] from the given fields, which are interpreted in the given calendar system.
246        ///
247        /// 🚧 This API is experimental and may experience breaking changes outside major releases.
248        #[diplomat::rust_link(icu::calendar::Date::try_from_fields, FnInStruct)]
249        #[diplomat::attr(all(supports = fallible_constructors, supports = named_constructors), named_constructor)]
250        #[cfg(feature = "unstable")]
251        pub fn from_fields_in_calendar(
252            fields: DateFields,
253            options: DateFromFieldsOptions,
254            calendar: &Calendar,
255        ) -> Result<Box<Date>, CalendarDateFromFieldsError> {
256            let cal = calendar.0.clone();
257            Ok(Box::new(Date(icu_calendar::Date::try_from_fields(
258                fields.into(),
259                options.into(),
260                cal,
261            )?)))
262        }
263
264        /// Creates a new [`Date`] from the given codes, which are interpreted in the given calendar system
265        ///
266        /// An empty era code will treat the year as an extended year
267        #[diplomat::rust_link(icu::calendar::Date::try_new_from_codes, FnInStruct)]
268        #[diplomat::attr(all(supports = fallible_constructors, supports = named_constructors), named_constructor)]
269        pub fn from_codes_in_calendar(
270            era_code: &DiplomatStr,
271            year: i32,
272            month_code: &DiplomatStr,
273            day: u8,
274            calendar: &Calendar,
275        ) -> Result<Box<Date>, CalendarError> {
276            let era = if !era_code.is_empty() {
277                Some(core::str::from_utf8(era_code).map_err(|_| CalendarError::UnknownEra)?)
278            } else {
279                None
280            };
281            let month = icu_calendar::types::MonthCode(
282                TinyAsciiStr::try_from_utf8(month_code)
283                    .map_err(|_| CalendarError::UnknownMonthCode)?,
284            );
285            let cal = calendar.0.clone();
286            Ok(Box::new(Date(icu_calendar::Date::try_new_from_codes(
287                era, year, month, day, cal,
288            )?)))
289        }
290
291        /// Creates a new [`Date`] from the given Rata Die
292        #[diplomat::rust_link(icu::calendar::Date::from_rata_die, FnInStruct)]
293        #[diplomat::attr(all(supports = named_constructors), named_constructor)]
294        #[diplomat::demo(default_constructor)]
295        pub fn from_rata_die(rd: i64, calendar: &Calendar) -> Result<Box<Date>, CalendarError> {
296            let cal = calendar.0.clone();
297            Ok(Box::new(Date(icu_calendar::Date::from_rata_die(
298                icu_calendar::types::RataDie::new(rd),
299                cal,
300            ))))
301        }
302
303        /// Creates a new [`Date`] from an IXDTF string.
304        #[diplomat::rust_link(icu::calendar::Date::try_from_str, FnInStruct)]
305        #[diplomat::rust_link(icu::calendar::Date::try_from_utf8, FnInStruct, hidden)]
306        #[diplomat::rust_link(icu::calendar::Date::from_str, FnInStruct, hidden)]
307        #[diplomat::attr(all(supports = fallible_constructors, supports = named_constructors), named_constructor)]
308        pub fn from_string(
309            v: &DiplomatStr,
310            calendar: &Calendar,
311        ) -> Result<Box<Date>, Rfc9557ParseError> {
312            Ok(Box::new(Date(icu_calendar::Date::try_from_utf8(
313                v,
314                calendar.0.clone(),
315            )?)))
316        }
317
318        /// Convert this date to one in a different calendar
319        #[diplomat::rust_link(icu::calendar::Date::to_calendar, FnInStruct)]
320        #[diplomat::rust_link(icu::calendar::Date::convert_any, FnInStruct, hidden)]
321        pub fn to_calendar(&self, calendar: &Calendar) -> Box<Date> {
322            Box::new(Date(self.0.to_calendar(calendar.0.clone())))
323        }
324
325        /// Converts this date to ISO
326        #[diplomat::rust_link(icu::calendar::Date::to_iso, FnInStruct)]
327        pub fn to_iso(&self) -> Box<IsoDate> {
328            Box::new(IsoDate(self.0.to_iso()))
329        }
330
331        /// Returns this date's Rata Die
332        #[diplomat::rust_link(icu::calendar::Date::to_rata_die, FnInStruct)]
333        #[diplomat::attr(auto, getter = "rata_die")]
334        pub fn to_rata_die(&self) -> i64 {
335            self.0.to_rata_die().to_i64_date()
336        }
337
338        /// Returns the 1-indexed day in the year for this date
339        #[diplomat::rust_link(icu::calendar::Date::day_of_year, FnInStruct)]
340        #[diplomat::attr(auto, getter)]
341        pub fn day_of_year(&self) -> u16 {
342            self.0.day_of_year().0
343        }
344
345        /// Returns the 1-indexed day in the month for this date
346        #[diplomat::rust_link(icu::calendar::Date::day_of_month, FnInStruct)]
347        #[diplomat::attr(auto, getter)]
348        pub fn day_of_month(&self) -> u8 {
349            self.0.day_of_month().0
350        }
351
352        /// Returns the day in the week for this day
353        #[diplomat::rust_link(icu::calendar::Date::day_of_week, FnInStruct)]
354        #[diplomat::attr(auto, getter)]
355        pub fn day_of_week(&self) -> Weekday {
356            self.0.day_of_week().into()
357        }
358
359        /// Returns 1-indexed number of the month of this date in its year
360        ///
361        /// Note that for lunar calendars this may not lead to the same month
362        /// having the same ordinal month across years; use month_code if you care
363        /// about month identity.
364        #[diplomat::rust_link(icu::calendar::Date::month, FnInStruct)]
365        #[diplomat::rust_link(icu::calendar::types::MonthInfo::ordinal, StructField)]
366        #[diplomat::attr(auto, getter)]
367        pub fn ordinal_month(&self) -> u8 {
368            self.0.month().ordinal
369        }
370
371        /// Returns the month code for this date. Typically something
372        /// like "M01", "M02", but can be more complicated for lunar calendars.
373        #[diplomat::rust_link(icu::calendar::types::MonthInfo::standard_code, StructField)]
374        #[diplomat::rust_link(icu::calendar::Date::month, FnInStruct, compact)]
375        #[diplomat::rust_link(icu::calendar::types::MonthInfo, Struct, hidden)]
376        #[diplomat::rust_link(
377            icu::calendar::types::MonthInfo::formatting_code,
378            StructField,
379            hidden
380        )]
381        #[diplomat::rust_link(icu::calendar::types::MonthInfo, Struct, hidden)]
382        #[diplomat::attr(auto, getter)]
383        pub fn month_code(&self, write: &mut diplomat_runtime::DiplomatWrite) {
384            let code = self.0.month().standard_code;
385            let _infallible = write.write_str(&code.0);
386        }
387
388        /// Returns the month number of this month.
389        #[diplomat::rust_link(icu::calendar::types::MonthInfo::month_number, FnInStruct)]
390        #[diplomat::attr(auto, getter)]
391        pub fn month_number(&self) -> u8 {
392            self.0.month().month_number()
393        }
394
395        /// Returns whether the month is a leap month.
396        #[diplomat::rust_link(icu::calendar::types::MonthInfo::is_leap, FnInStruct)]
397        #[diplomat::attr(auto, getter)]
398        pub fn month_is_leap(&self) -> bool {
399            self.0.month().is_leap()
400        }
401
402        /// Returns the year number in the current era for this date
403        ///
404        /// For calendars without an era, returns the related ISO year.
405        #[diplomat::rust_link(icu::calendar::types::YearInfo::era_year_or_related_iso, FnInEnum)]
406        #[diplomat::rust_link(icu::calendar::types::EraYear::year, StructField, compact)]
407        #[diplomat::rust_link(icu::calendar::types::CyclicYear::related_iso, StructField, compact)]
408        #[diplomat::rust_link(icu::calendar::Date::year, FnInStruct, compact)]
409        #[diplomat::rust_link(icu::calendar::Date::era_year, FnInStruct, hidden)]
410        #[diplomat::rust_link(icu::calendar::Date::cyclic_year, FnInStruct, hidden)]
411        #[diplomat::rust_link(icu::calendar::types::YearInfo, Enum, hidden)]
412        #[diplomat::rust_link(icu::calendar::types::YearInfo::era, FnInEnum, hidden)]
413        #[diplomat::rust_link(icu::calendar::types::YearInfo::cyclic, FnInEnum, hidden)]
414        #[diplomat::rust_link(icu::calendar::types::EraYear, Struct, hidden)]
415        #[diplomat::rust_link(icu::calendar::types::CyclicYear, Struct, hidden)]
416        #[diplomat::attr(auto, getter)]
417        pub fn era_year_or_related_iso(&self) -> i32 {
418            self.0.year().era_year_or_related_iso()
419        }
420
421        /// Returns the extended year, which can be used for
422        ///
423        /// This year number can be used when you need a simple numeric representation
424        /// of the year, and can be meaningfully compared with extended years from other
425        /// eras or used in arithmetic.
426        #[diplomat::rust_link(icu::calendar::Date::extended_year, FnInStruct)]
427        #[diplomat::rust_link(icu::calendar::types::YearInfo::extended_year, FnInEnum, hidden)]
428        #[diplomat::attr(auto, getter)]
429        pub fn extended_year(&self) -> i32 {
430            self.0.extended_year()
431        }
432
433        /// Returns the era for this date, or an empty string
434        #[diplomat::rust_link(icu::calendar::types::EraYear::era, StructField)]
435        #[diplomat::rust_link(icu::calendar::Date::year, FnInStruct, compact)]
436        #[diplomat::attr(auto, getter)]
437        pub fn era(&self, write: &mut diplomat_runtime::DiplomatWrite) {
438            if let Some(era) = self.0.year().era() {
439                let _infallible = write.write_str(&era.era);
440            }
441        }
442
443        /// Returns the number of months in the year represented by this date
444        #[diplomat::rust_link(icu::calendar::Date::months_in_year, FnInStruct)]
445        #[diplomat::attr(auto, getter)]
446        pub fn months_in_year(&self) -> u8 {
447            self.0.months_in_year()
448        }
449
450        /// Returns the number of days in the month represented by this date
451        #[diplomat::rust_link(icu::calendar::Date::days_in_month, FnInStruct)]
452        #[diplomat::attr(auto, getter)]
453        pub fn days_in_month(&self) -> u8 {
454            self.0.days_in_month()
455        }
456
457        /// Returns the number of days in the year represented by this date
458        #[diplomat::rust_link(icu::calendar::Date::days_in_year, FnInStruct)]
459        #[diplomat::attr(auto, getter)]
460        pub fn days_in_year(&self) -> u16 {
461            self.0.days_in_year()
462        }
463
464        /// Returns the [`Calendar`] object backing this date
465        #[diplomat::rust_link(icu::calendar::Date::calendar, FnInStruct)]
466        #[diplomat::rust_link(icu::calendar::Date::calendar_wrapper, FnInStruct, hidden)]
467        #[diplomat::attr(auto, getter)]
468        pub fn calendar(&self) -> Box<Calendar> {
469            Box::new(Calendar(self.0.calendar_wrapper().clone()))
470        }
471    }
472
473    #[diplomat::rust_link(icu::calendar::types::IsoWeekOfYear, Struct)]
474    pub struct IsoWeekOfYear {
475        pub week_number: u8,
476        pub iso_year: i32,
477    }
478}
479
480impl From<icu_calendar::types::IsoWeekOfYear> for IsoWeekOfYear {
481    fn from(
482        icu_calendar::types::IsoWeekOfYear {
483            week_number,
484            iso_year,
485        }: icu_calendar::types::IsoWeekOfYear,
486    ) -> Self {
487        Self {
488            week_number,
489            iso_year,
490        }
491    }
492}
493
494#[cfg(feature = "unstable")]
495impl From<ffi::DateFromFieldsOptions> for icu_calendar::options::DateFromFieldsOptions {
496    fn from(other: ffi::DateFromFieldsOptions) -> Self {
497        let mut options = Self::default();
498
499        options.overflow = other.overflow.into_converted_option();
500        options.missing_fields_strategy = other.missing_fields_strategy.into_converted_option();
501
502        options
503    }
504}
505
506#[cfg(feature = "unstable")]
507impl<'a> From<ffi::DateFields<'a>> for icu_calendar::types::DateFields<'a> {
508    fn from(other: ffi::DateFields<'a>) -> Self {
509        let mut fields = Self::default();
510        fields.era = other.era.into_option();
511        fields.era_year = other.era_year.into();
512        fields.extended_year = other.extended_year.into();
513        fields.month_code = other.month_code.into_option();
514        fields.ordinal_month = other.ordinal_month.into();
515        fields.day = other.day.into();
516
517        fields
518    }
519}