icu_datetime/
neo.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//! High-level entrypoints for Neo DateTime Formatter
6
7use crate::error::DateTimeFormatterLoadError;
8use crate::external_loaders::*;
9use crate::fieldsets::builder::FieldSetBuilder;
10use crate::fieldsets::enums::CompositeFieldSet;
11use crate::format::datetime::try_write_pattern_items;
12use crate::format::DateTimeInputUnchecked;
13use crate::pattern::*;
14use crate::preferences::{CalendarAlgorithm, HourCycle, NumberingSystem};
15use crate::raw::neo::*;
16use crate::scaffold::*;
17use crate::scaffold::{
18    AllInputMarkers, ConvertCalendar, DateDataMarkers, DateInputMarkers, DateTimeMarkers, GetField,
19    InFixedCalendar, InSameCalendar, TimeMarkers, TypedDateDataMarkers, ZoneMarkers,
20};
21use crate::size_test_macro::size_test;
22use crate::MismatchedCalendarError;
23use core::fmt;
24use core::marker::PhantomData;
25use icu_calendar::{preferences::CalendarPreferences, AnyCalendar, IntoAnyCalendar};
26use icu_decimal::DecimalFormatterPreferences;
27use icu_locale_core::preferences::{define_preferences, prefs_convert};
28use icu_provider::prelude::*;
29use writeable::{impl_display_with_writeable, Writeable};
30
31define_preferences!(
32    /// The user locale preferences for datetime formatting.
33    ///
34    /// # Examples
35    ///
36    /// Two ways to build a preferences bag with a custom hour cycle and calendar system:
37    ///
38    /// ```
39    /// use icu::datetime::DateTimeFormatterPreferences;
40    /// use icu::locale::Locale;
41    /// use icu::locale::preferences::extensions::unicode::keywords::CalendarAlgorithm;
42    /// use icu::locale::preferences::extensions::unicode::keywords::HourCycle;
43    /// use icu::locale::subtags::Language;
44    ///
45    /// let prefs1: DateTimeFormatterPreferences = Locale::try_from_str("fr-u-ca-buddhist-hc-h12").unwrap().into();
46    ///
47    /// let locale = Locale::try_from_str("fr").unwrap();
48    /// let mut prefs2 = DateTimeFormatterPreferences::default();
49    /// prefs2.locale_preferences = (&locale).into();
50    /// prefs2.hour_cycle = Some(HourCycle::H12);
51    /// prefs2.calendar_algorithm = Some(CalendarAlgorithm::Buddhist);
52    ///
53    /// assert_eq!(prefs1, prefs2);
54    /// ```
55    [Copy]
56    DateTimeFormatterPreferences,
57    {
58        /// The user's preferred numbering system.
59        ///
60        /// Corresponds to the `-u-nu` in Unicode Locale Identifier.
61        ///
62        /// To get the resolved numbering system, you can inspect the data provider.
63        /// See the [`icu_decimal::provider`] module for an example.
64        numbering_system: NumberingSystem,
65        /// The user's preferred hour cycle.
66        ///
67        /// Corresponds to the `-u-hc` in Unicode Locale Identifier.
68        ///
69        /// To get the resolved hour cycle, you can inspect the formatting pattern.
70        /// See [`DateTimePattern`](crate::pattern::DateTimePattern) for an example.
71        hour_cycle: HourCycle,
72        /// The user's preferred calendar system
73        ///
74        /// Corresponds to the `-u-ca` in Unicode Locale Identifier.
75        ///
76        /// To get the resolved calendar system, use [`DateTimeFormatter::calendar_kind()`].
77        calendar_algorithm: CalendarAlgorithm
78    }
79);
80
81prefs_convert!(DateTimeFormatterPreferences, DecimalFormatterPreferences, {
82    numbering_system
83});
84
85prefs_convert!(DateTimeFormatterPreferences, CalendarPreferences, {
86    calendar_algorithm
87});
88
89/// Helper macro for generating any/buffer constructors in this file.
90macro_rules! gen_buffer_constructors_with_external_loader {
91    (@runtime_fset, $fset:ident, $compiled_fn:ident $buffer_fn:ident, $internal_fn:ident) => {
92        #[doc = icu_provider::gen_buffer_unstable_docs!(BUFFER, Self::$compiled_fn)]
93        #[cfg(feature = "serde")]
94        pub fn $buffer_fn<P>(
95            provider: &P,
96            prefs: DateTimeFormatterPreferences,
97            field_set_with_options: $fset,
98        ) -> Result<Self, DateTimeFormatterLoadError>
99        where
100            P: BufferProvider + ?Sized,
101        {
102            Self::$internal_fn(
103                &provider.as_deserializing(),
104                &ExternalLoaderBuffer(provider),
105                prefs,
106                field_set_with_options.get_field(),
107            )
108        }
109    };
110    (@compiletime_fset, $fset:ident, $compiled_fn:ident, $buffer_fn:ident, $internal_fn:ident) => {
111        #[doc = icu_provider::gen_buffer_unstable_docs!(BUFFER, Self::$compiled_fn)]
112        #[cfg(feature = "serde")]
113        pub fn $buffer_fn<P>(
114            provider: &P,
115            prefs: DateTimeFormatterPreferences,
116            field_set_with_options: $fset,
117        ) -> Result<Self, DateTimeFormatterLoadError>
118        where
119            P: BufferProvider + ?Sized,
120        {
121            Self::$internal_fn(
122                &provider.as_deserializing(),
123                &ExternalLoaderBuffer(provider),
124                prefs,
125                field_set_with_options.get_field(),
126            )
127        }
128    };
129}
130
131size_test!(FixedCalendarDateTimeFormatter<icu_calendar::Gregorian, crate::fieldsets::YMD>, typed_neo_year_month_day_formatter_size, 328);
132
133/// [`FixedCalendarDateTimeFormatter`] is a formatter capable of formatting dates and/or times from
134/// a calendar selected at compile time.
135///
136/// For more details, please read the [crate root docs][crate].
137///
138/// # Examples
139///
140/// Basic usage:
141///
142/// ```
143/// use icu::calendar::cal::JapaneseExtended;
144/// use icu::datetime::fieldsets::YMD;
145/// use icu::datetime::input::Date;
146/// use icu::datetime::FixedCalendarDateTimeFormatter;
147/// use icu::locale::locale;
148/// use writeable::assert_writeable_eq;
149///
150/// // The JapaneseExtended generic is inferred by passing this a JapaneseExtended date later
151/// let formatter = FixedCalendarDateTimeFormatter::try_new(
152///     locale!("es-MX").into(),
153///     YMD::long(),
154/// )
155/// .unwrap();
156///
157/// assert_writeable_eq!(
158///     formatter.format(&Date::try_new_iso(2023, 12, 20).unwrap().to_calendar(JapaneseExtended::new())),
159///     "20 de diciembre de 5 Reiwa"
160/// );
161/// ```
162///
163/// Mismatched calendars will not compile:
164///
165/// ```compile_fail
166/// use icu::datetime::input::Date;
167/// use icu::datetime::input::cal::Buddhist;
168/// use icu::datetime::FixedCalendarDateTimeFormatter;
169/// use icu::datetime::fieldsets::YMD;
170/// use icu::locale::locale;
171///
172/// let formatter =
173///     FixedCalendarDateTimeFormatter::<Buddhist, _>::try_new(
174///         locale!("es-MX").into(),
175///         YMD::long(),
176///     )
177///     .unwrap();
178///
179/// // type mismatch resolving `<Gregorian as AsCalendar>::Calendar == Buddhist`
180/// formatter.format(&Date::try_new_gregorian(2023, 12, 20).unwrap());
181/// ```
182///
183/// As with [`DateTimeFormatter`], a time cannot be passed into the formatter when a date is expected:
184///
185/// ```compile_fail,E0277
186/// use icu::datetime::input::Time;
187/// use icu::calendar::Gregorian;
188/// use icu::datetime::FixedCalendarDateTimeFormatter;
189/// use icu::datetime::fieldsets::YMD;
190/// use icu::locale::locale;
191///
192/// let formatter =
193///     FixedCalendarDateTimeFormatter::<Gregorian, _>::try_new(
194///         locale!("es-MX").into(),
195///         YMD::long(),
196///     )
197///     .unwrap();
198///
199/// // error[E0277]: the trait bound `Time: AllInputMarkers<fieldsets::YMD>` is not satisfied
200/// formatter.format(&Time::start_of_day());
201/// ```
202#[doc = typed_neo_year_month_day_formatter_size!()]
203#[derive(Debug, Clone)]
204pub struct FixedCalendarDateTimeFormatter<C: CldrCalendar, FSet: DateTimeNamesMarker> {
205    pub(crate) selection: DateTimeZonePatternSelectionData,
206    pub(crate) names: RawDateTimeNames<FSet>,
207    _calendar: PhantomData<C>,
208}
209
210impl<C: CldrCalendar, FSet: DateTimeMarkers> FixedCalendarDateTimeFormatter<C, FSet>
211where
212    FSet::D: TypedDateDataMarkers<C>,
213    FSet::T: TimeMarkers,
214    FSet::Z: ZoneMarkers,
215    FSet: GetField<CompositeFieldSet>,
216{
217    /// Creates a new [`FixedCalendarDateTimeFormatter`] from compiled data with
218    /// datetime components specified at build time.
219    ///
220    /// This ignores the `calendar_kind` preference and instead uses the static calendar type,
221    /// and supports calendars that are not expressible as preferences, such as [`JapaneseExtended`](icu_calendar::cal::JapaneseExtended).
222    ///
223    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
224    ///
225    /// [📚 Help choosing a constructor](icu_provider::constructors)
226    #[cfg(feature = "compiled_data")]
227    pub fn try_new(
228        prefs: DateTimeFormatterPreferences,
229        field_set_with_options: FSet,
230    ) -> Result<Self, DateTimeFormatterLoadError>
231    where
232        crate::provider::Baked: AllFixedCalendarFormattingDataMarkers<C, FSet>,
233    {
234        Self::try_new_internal(
235            &crate::provider::Baked,
236            &ExternalLoaderCompiledData,
237            prefs,
238            field_set_with_options.get_field(),
239        )
240    }
241
242    gen_buffer_constructors_with_external_loader!(
243        @compiletime_fset,
244        FSet,
245        try_new,
246        try_new_with_buffer_provider,
247        try_new_internal
248    );
249
250    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::try_new)]
251    pub fn try_new_unstable<P>(
252        provider: &P,
253        prefs: DateTimeFormatterPreferences,
254        field_set_with_options: FSet,
255    ) -> Result<Self, DateTimeFormatterLoadError>
256    where
257        P: ?Sized
258            + AllFixedCalendarFormattingDataMarkers<C, FSet>
259            + AllFixedCalendarExternalDataMarkers,
260    {
261        Self::try_new_internal(
262            provider,
263            &ExternalLoaderUnstable(provider),
264            prefs,
265            field_set_with_options.get_field(),
266        )
267    }
268}
269
270impl<C: CldrCalendar, FSet: DateTimeMarkers> FixedCalendarDateTimeFormatter<C, FSet>
271where
272    FSet::D: TypedDateDataMarkers<C>,
273    FSet::T: TimeMarkers,
274    FSet::Z: ZoneMarkers,
275{
276    fn try_new_internal<P, L>(
277        provider: &P,
278        loader: &L,
279        prefs: DateTimeFormatterPreferences,
280        field_set_with_options: CompositeFieldSet,
281    ) -> Result<Self, DateTimeFormatterLoadError>
282    where
283        P: ?Sized + AllFixedCalendarFormattingDataMarkers<C, FSet>,
284        L: DecimalFormatterLoader,
285    {
286        let names = RawDateTimeNames::new_without_number_formatting();
287        Self::try_new_internal_with_names(
288            provider,
289            provider,
290            loader,
291            prefs,
292            field_set_with_options,
293            names,
294            DateTimeNamesMetadata::new_empty(), // OK: this is a constructor
295        )
296        .map_err(|e| e.0)
297    }
298
299    #[allow(clippy::result_large_err)] // returning ownership of an argument to the caller
300    pub(crate) fn try_new_internal_with_names<P0, P1, L>(
301        provider_p: &P0,
302        provider: &P1,
303        loader: &L,
304        prefs: DateTimeFormatterPreferences,
305        field_set_with_options: CompositeFieldSet,
306        mut names: RawDateTimeNames<FSet>,
307        mut names_metadata: DateTimeNamesMetadata,
308    ) -> Result<
309        Self,
310        (
311            DateTimeFormatterLoadError,
312            (RawDateTimeNames<FSet>, DateTimeNamesMetadata),
313        ),
314    >
315    where
316        P0: ?Sized + AllFixedCalendarPatternDataMarkers<C, FSet>,
317        P1: ?Sized + AllFixedCalendarFormattingDataMarkers<C, FSet>,
318        L: DecimalFormatterLoader,
319    {
320        let selection = DateTimeZonePatternSelectionData::try_new_with_skeleton(
321            &<FSet::D as TypedDateDataMarkers<C>>::DateSkeletonPatternsV1::bind(provider_p),
322            &<FSet::T as TimeMarkers>::TimeSkeletonPatternsV1::bind(provider_p),
323            &FSet::GluePatternV1::bind(provider_p),
324            prefs,
325            field_set_with_options,
326        );
327        let selection = match selection {
328            Ok(selection) => selection,
329            Err(e) => return Err((DateTimeFormatterLoadError::Data(e), (names, names_metadata))),
330        };
331        let result = names.load_for_pattern(
332            &<FSet::D as TypedDateDataMarkers<C>>::YearNamesV1::bind(provider),
333            &<FSet::D as TypedDateDataMarkers<C>>::MonthNamesV1::bind(provider),
334            &<FSet::D as TypedDateDataMarkers<C>>::WeekdayNamesV1::bind(provider),
335            &<FSet::T as TimeMarkers>::DayPeriodNamesV1::bind(provider),
336            &<FSet::Z as ZoneMarkers>::EssentialsV1::bind(provider),
337            &<FSet::Z as ZoneMarkers>::LocationsV1::bind(provider),
338            &<FSet::Z as ZoneMarkers>::LocationsRootV1::bind(provider),
339            &<FSet::Z as ZoneMarkers>::ExemplarCitiesV1::bind(provider),
340            &<FSet::Z as ZoneMarkers>::ExemplarCitiesRootV1::bind(provider),
341            &<FSet::Z as ZoneMarkers>::GenericLongV1::bind(provider),
342            &<FSet::Z as ZoneMarkers>::GenericShortV1::bind(provider),
343            &<FSet::Z as ZoneMarkers>::StandardLongV1::bind(provider),
344            &<FSet::Z as ZoneMarkers>::SpecificLongV1::bind(provider),
345            &<FSet::Z as ZoneMarkers>::SpecificShortV1::bind(provider),
346            &<FSet::Z as ZoneMarkers>::MetazonePeriodV1::bind(provider),
347            loader, // fixed decimal formatter
348            prefs,
349            selection.pattern_items_for_data_loading(),
350            &mut names_metadata,
351        );
352        match result {
353            Ok(()) => (),
354            Err(e) => {
355                return Err((
356                    DateTimeFormatterLoadError::Names(e),
357                    (names, names_metadata),
358                ))
359            }
360        };
361        Ok(Self {
362            selection,
363            names,
364            _calendar: PhantomData,
365        })
366    }
367}
368
369impl<C: CldrCalendar, FSet: DateTimeMarkers> FixedCalendarDateTimeFormatter<C, FSet>
370where
371    FSet::D: DateInputMarkers,
372    FSet::T: TimeMarkers,
373    FSet::Z: ZoneMarkers,
374{
375    /// Formats a datetime. Calendars and fields must match at compile time.
376    pub fn format<I>(&self, input: &I) -> FormattedDateTime
377    where
378        I: ?Sized + InFixedCalendar<C> + AllInputMarkers<FSet>,
379    {
380        let input =
381            DateTimeInputUnchecked::extract_from_neo_input::<FSet::D, FSet::T, FSet::Z, I>(input);
382        FormattedDateTime {
383            pattern: self.selection.select(&input),
384            input,
385            names: self.names.as_borrowed(),
386        }
387    }
388}
389
390size_test!(
391    DateTimeFormatter<crate::fieldsets::YMD>,
392    neo_year_month_day_formatter_size,
393    384
394);
395
396/// [`DateTimeFormatter`] is a formatter capable of formatting dates and/or times from
397/// a calendar selected at runtime.
398///
399/// For more details, please read the [crate root docs][crate].
400///
401/// # Examples
402///
403/// Basic usage:
404///
405/// ```
406/// use icu::datetime::fieldsets::YMD;
407/// use icu::datetime::input::Date;
408/// use icu::datetime::DateTimeFormatter;
409/// use icu::locale::locale;
410/// use writeable::assert_writeable_eq;
411///
412/// let formatter = DateTimeFormatter::try_new(
413///     locale!("en-u-ca-hebrew").into(),
414///     YMD::medium(),
415/// )
416/// .unwrap();
417///
418/// let date = Date::try_new_iso(2024, 5, 8).unwrap();
419///
420/// assert_writeable_eq!(formatter.format(&date), "30 Nisan 5784");
421/// ```
422#[doc = neo_year_month_day_formatter_size!()]
423#[derive(Debug, Clone)]
424pub struct DateTimeFormatter<FSet: DateTimeNamesMarker> {
425    pub(crate) selection: DateTimeZonePatternSelectionData,
426    pub(crate) names: RawDateTimeNames<FSet>,
427    pub(crate) calendar: UntaggedFormattableAnyCalendar,
428}
429
430impl<FSet: DateTimeMarkers> DateTimeFormatter<FSet>
431where
432    FSet::D: DateDataMarkers,
433    FSet::T: TimeMarkers,
434    FSet::Z: ZoneMarkers,
435    FSet: GetField<CompositeFieldSet>,
436{
437    /// Creates a new [`DateTimeFormatter`] from compiled data with
438    /// datetime components specified at build time.
439    ///
440    /// This method will use the calendar specified in the `calendar_algorithm` preference, or fall back to the default
441    /// calendar for the preferences if unspecified or unsupported. See [`IntoFormattableAnyCalendar`] for a list of supported calendars.
442    ///
443    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
444    ///
445    /// [📚 Help choosing a constructor](icu_provider::constructors)
446    #[inline(never)]
447    #[cfg(feature = "compiled_data")]
448    pub fn try_new(
449        prefs: DateTimeFormatterPreferences,
450        field_set_with_options: FSet,
451    ) -> Result<Self, DateTimeFormatterLoadError>
452    where
453        crate::provider::Baked: AllAnyCalendarFormattingDataMarkers<FSet>,
454    {
455        Self::try_new_internal(
456            &crate::provider::Baked,
457            &ExternalLoaderCompiledData,
458            prefs,
459            field_set_with_options.get_field(),
460        )
461    }
462
463    gen_buffer_constructors_with_external_loader!(
464        @compiletime_fset,
465        FSet,
466        try_new,
467        try_new_with_buffer_provider,
468        try_new_internal
469    );
470
471    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::try_new)]
472    pub fn try_new_unstable<P>(
473        provider: &P,
474        prefs: DateTimeFormatterPreferences,
475        field_set_with_options: FSet,
476    ) -> Result<Self, DateTimeFormatterLoadError>
477    where
478        P: ?Sized + AllAnyCalendarFormattingDataMarkers<FSet> + AllAnyCalendarExternalDataMarkers,
479    {
480        Self::try_new_internal(
481            provider,
482            &ExternalLoaderUnstable(provider),
483            prefs,
484            field_set_with_options.get_field(),
485        )
486    }
487}
488
489impl<FSet: DateTimeMarkers> DateTimeFormatter<FSet>
490where
491    FSet::D: DateDataMarkers,
492    FSet::T: TimeMarkers,
493    FSet::Z: ZoneMarkers,
494{
495    fn try_new_internal<P, L>(
496        provider: &P,
497        loader: &L,
498        prefs: DateTimeFormatterPreferences,
499        field_set_with_options: CompositeFieldSet,
500    ) -> Result<Self, DateTimeFormatterLoadError>
501    where
502        P: ?Sized + AllAnyCalendarFormattingDataMarkers<FSet>,
503        L: DecimalFormatterLoader + FormattableAnyCalendarLoader,
504    {
505        let kind = FormattableAnyCalendarKind::from_preferences(prefs);
506        let calendar = FormattableAnyCalendarLoader::load(loader, kind)?;
507        let names = RawDateTimeNames::new_without_number_formatting();
508        Self::try_new_internal_with_calendar_and_names(
509            provider,
510            provider,
511            loader,
512            prefs,
513            field_set_with_options,
514            calendar,
515            names,
516            DateTimeNamesMetadata::new_empty(), // OK: this is a constructor
517        )
518        .map_err(|e| e.0)
519    }
520
521    #[allow(clippy::result_large_err)] // returning ownership of an argument to the caller
522    #[allow(clippy::too_many_arguments)] // internal function with lots of generics
523    #[allow(clippy::type_complexity)] // return type has all the parts inside
524    pub(crate) fn try_new_internal_with_calendar_and_names<P0, P1, L>(
525        provider_p: &P0,
526        provider: &P1,
527        loader: &L,
528        prefs: DateTimeFormatterPreferences,
529        field_set_with_options: CompositeFieldSet,
530        calendar: FormattableAnyCalendar,
531        mut names: RawDateTimeNames<FSet>,
532        mut names_metadata: DateTimeNamesMetadata,
533    ) -> Result<
534        Self,
535        (
536            DateTimeFormatterLoadError,
537            (
538                FormattableAnyCalendar,
539                RawDateTimeNames<FSet>,
540                DateTimeNamesMetadata,
541            ),
542        ),
543    >
544    where
545        P0: ?Sized + AllAnyCalendarPatternDataMarkers<FSet>,
546        P1: ?Sized + AllAnyCalendarFormattingDataMarkers<FSet>,
547        L: DecimalFormatterLoader,
548    {
549        let selection = DateTimeZonePatternSelectionData::try_new_with_skeleton(
550            &FormattableAnyCalendarNamesLoader::<<FSet::D as DateDataMarkers>::Skel, _>::new(
551                provider_p,
552                calendar.kind(),
553            ),
554            &<FSet::T as TimeMarkers>::TimeSkeletonPatternsV1::bind(provider_p),
555            &FSet::GluePatternV1::bind(provider_p),
556            prefs,
557            field_set_with_options,
558        );
559        let selection = match selection {
560            Ok(selection) => selection,
561            Err(e) => {
562                return Err((
563                    DateTimeFormatterLoadError::Data(e),
564                    (calendar, names, names_metadata),
565                ))
566            }
567        };
568        let result = names.load_for_pattern(
569            &FormattableAnyCalendarNamesLoader::<<FSet::D as DateDataMarkers>::Year, _>::new(
570                provider,
571                calendar.kind(),
572            ),
573            &FormattableAnyCalendarNamesLoader::<<FSet::D as DateDataMarkers>::Month, _>::new(
574                provider,
575                calendar.kind(),
576            ),
577            &<FSet::D as DateDataMarkers>::WeekdayNamesV1::bind(provider),
578            &<FSet::T as TimeMarkers>::DayPeriodNamesV1::bind(provider),
579            &<FSet::Z as ZoneMarkers>::EssentialsV1::bind(provider),
580            &<FSet::Z as ZoneMarkers>::LocationsV1::bind(provider),
581            &<FSet::Z as ZoneMarkers>::LocationsRootV1::bind(provider),
582            &<FSet::Z as ZoneMarkers>::ExemplarCitiesRootV1::bind(provider),
583            &<FSet::Z as ZoneMarkers>::ExemplarCitiesV1::bind(provider),
584            &<FSet::Z as ZoneMarkers>::GenericLongV1::bind(provider),
585            &<FSet::Z as ZoneMarkers>::GenericShortV1::bind(provider),
586            &<FSet::Z as ZoneMarkers>::StandardLongV1::bind(provider),
587            &<FSet::Z as ZoneMarkers>::SpecificLongV1::bind(provider),
588            &<FSet::Z as ZoneMarkers>::SpecificShortV1::bind(provider),
589            &<FSet::Z as ZoneMarkers>::MetazonePeriodV1::bind(provider),
590            loader, // fixed decimal formatter
591            prefs,
592            selection.pattern_items_for_data_loading(),
593            &mut names_metadata,
594        );
595        match result {
596            Ok(()) => (),
597            Err(e) => {
598                return Err((
599                    DateTimeFormatterLoadError::Names(e),
600                    (calendar, names, names_metadata),
601                ))
602            }
603        };
604        Ok(Self {
605            selection,
606            names,
607            calendar: calendar.into_untagged(),
608        })
609    }
610}
611
612impl<FSet: DateTimeMarkers> DateTimeFormatter<FSet>
613where
614    FSet::D: DateInputMarkers,
615    FSet::T: TimeMarkers,
616    FSet::Z: ZoneMarkers,
617{
618    /// Formats a datetime, checking that the calendar system is correct.
619    ///
620    /// If the datetime is not in the same calendar system as the formatter,
621    /// an error is returned.
622    ///
623    /// # Examples
624    ///
625    /// Mismatched calendars will return an error:
626    ///
627    /// ```
628    /// use icu::datetime::fieldsets::YMD;
629    /// use icu::datetime::input::Date;
630    /// use icu::datetime::DateTimeFormatter;
631    /// use icu::datetime::MismatchedCalendarError;
632    /// use icu::locale::locale;
633    ///
634    /// let formatter = DateTimeFormatter::try_new(
635    ///     locale!("en-u-ca-hebrew").into(),
636    ///     YMD::long(),
637    /// )
638    /// .unwrap();
639    ///
640    /// let date = Date::try_new_gregorian(2023, 12, 20).unwrap();
641    ///
642    /// assert!(matches!(
643    ///     formatter.format_same_calendar(&date),
644    ///     Err(MismatchedCalendarError { .. })
645    /// ));
646    /// ```
647    ///
648    /// A time cannot be passed into the formatter when a date is expected:
649    ///
650    /// ```compile_fail,E0277
651    /// use icu::datetime::input::Time;
652    /// use icu::datetime::DateTimeFormatter;
653    /// use icu::datetime::fieldsets::YMD;
654    /// use icu::locale::locale;
655    ///
656    /// let formatter = DateTimeFormatter::try_new(
657    ///     locale!("es-MX").into(),
658    ///     YMD::long(),
659    /// )
660    /// .unwrap();
661    ///
662    /// // error[E0277]: the trait bound `Time: AllInputMarkers<fieldsets::YMD>` is not satisfied
663    /// formatter.format_same_calendar(&Time::start_of_day());
664    /// ```
665    pub fn format_same_calendar<I>(
666        &self,
667        datetime: &I,
668    ) -> Result<FormattedDateTime, crate::MismatchedCalendarError>
669    where
670        I: ?Sized + InSameCalendar + AllInputMarkers<FSet>,
671    {
672        datetime.check_any_calendar_kind(self.calendar.any_calendar().kind())?;
673        let datetime = DateTimeInputUnchecked::extract_from_neo_input::<FSet::D, FSet::T, FSet::Z, I>(
674            datetime,
675        );
676        Ok(FormattedDateTime {
677            pattern: self.selection.select(&datetime),
678            input: datetime,
679            names: self.names.as_borrowed(),
680        })
681    }
682
683    /// Formats a datetime after first converting it
684    /// to the formatter's calendar.
685    ///
686    /// # Examples
687    ///
688    /// Mismatched calendars convert and format automatically:
689    ///
690    /// ```
691    /// use icu::datetime::fieldsets::YMD;
692    /// use icu::datetime::input::Date;
693    /// use icu::datetime::DateTimeFormatter;
694    /// use icu::datetime::MismatchedCalendarError;
695    /// use icu::locale::locale;
696    /// use writeable::assert_writeable_eq;
697    ///
698    /// let formatter = DateTimeFormatter::try_new(
699    ///     locale!("en-u-ca-hebrew").into(),
700    ///     YMD::long(),
701    /// )
702    /// .unwrap();
703    ///
704    /// let date = Date::try_new_roc(113, 5, 8).unwrap();
705    ///
706    /// assert_writeable_eq!(formatter.format(&date), "30 Nisan 5784");
707    /// ```
708    ///
709    /// A time cannot be passed into the formatter when a date is expected:
710    ///
711    /// ```compile_fail,E0277
712    /// use icu::datetime::input::Time;
713    /// use icu::datetime::DateTimeFormatter;
714    /// use icu::datetime::fieldsets::YMD;
715    /// use icu::locale::locale;
716    ///
717    /// let formatter = DateTimeFormatter::try_new(
718    ///     locale!("es-MX").into(),
719    ///     YMD::long(),
720    /// )
721    /// .unwrap();
722    ///
723    /// // error[E0277]: the trait bound `Time: AllInputMarkers<fieldsets::YMD>` is not satisfied
724    /// formatter.format(&Time::start_of_day());
725    /// ```
726    pub fn format<'a, I>(&'a self, datetime: &I) -> FormattedDateTime<'a>
727    where
728        I: ?Sized + ConvertCalendar,
729        I::Converted<'a>: Sized + AllInputMarkers<FSet>,
730    {
731        let datetime = datetime.to_calendar(self.calendar.any_calendar());
732        let datetime = DateTimeInputUnchecked::extract_from_neo_input::<
733            FSet::D,
734            FSet::T,
735            FSet::Z,
736            I::Converted<'a>,
737        >(&datetime);
738        FormattedDateTime {
739            pattern: self.selection.select(&datetime),
740            input: datetime,
741            names: self.names.as_borrowed(),
742        }
743    }
744}
745
746impl<C: CldrCalendar, FSet: DateTimeMarkers> FixedCalendarDateTimeFormatter<C, FSet> {
747    /// Make this [`FixedCalendarDateTimeFormatter`] adopt a calendar so it can format any date.
748    ///
749    /// This is useful if you need a [`DateTimeFormatter`] but know the calendar system ahead of time,
750    /// so that you do not need to link extra data you aren't using.
751    ///
752    /// [`DateTimeFormatter`] does not necesarily support all calendars that are supported by
753    /// [`FixedCalendarDateTimeFormatter`], which is why this function can fail.
754    ///
755    /// # Examples
756    ///
757    /// ```
758    /// use icu::calendar::cal::Hebrew;
759    /// use icu::datetime::fieldsets::YMD;
760    /// use icu::datetime::input::Date;
761    /// use icu::datetime::FixedCalendarDateTimeFormatter;
762    /// use icu::locale::locale;
763    /// use writeable::assert_writeable_eq;
764    ///
765    /// let formatter = FixedCalendarDateTimeFormatter::try_new(
766    ///     locale!("en").into(),
767    ///     YMD::long(),
768    /// )
769    /// .unwrap()
770    /// .into_formatter(Hebrew::new());
771    ///
772    /// let date = Date::try_new_iso(2024, 10, 14).unwrap();
773    ///
774    /// assert_writeable_eq!(formatter.format(&date), "12 Tishri 5785");
775    /// ```
776    pub fn into_formatter(self, calendar: C) -> DateTimeFormatter<FSet>
777    where
778        C: IntoFormattableAnyCalendar,
779    {
780        DateTimeFormatter {
781            selection: self.selection,
782            names: self.names,
783            calendar: FormattableAnyCalendar::from_calendar(calendar).into_untagged(),
784        }
785    }
786
787    /// Maps a [`FixedCalendarDateTimeFormatter`] of a specific `FSet` to a more general `FSet`.
788    ///
789    /// For example, this can transform a formatter for [`YMD`] to one for [`DateFieldSet`].
790    ///
791    /// [`YMD`]: crate::fieldsets::YMD
792    /// [`DateFieldSet`]: crate::fieldsets::enums::DateFieldSet
793    ///
794    /// # Examples
795    ///
796    /// ```
797    /// use icu::calendar::Gregorian;
798    /// use icu::datetime::fieldsets::{enums::DateFieldSet, YMD};
799    /// use icu::datetime::input::Date;
800    /// use icu::datetime::FixedCalendarDateTimeFormatter;
801    /// use icu::locale::locale;
802    /// use writeable::assert_writeable_eq;
803    ///
804    /// let specific_formatter = FixedCalendarDateTimeFormatter::try_new(
805    ///     locale!("fr").into(),
806    ///     YMD::medium(),
807    /// )
808    /// .unwrap();
809    ///
810    /// // Test that the specific formatter works:
811    /// let date = Date::try_new_gregorian(2024, 12, 20).unwrap();
812    /// assert_writeable_eq!(specific_formatter.format(&date), "20 déc. 2024");
813    ///
814    /// // Make a more general formatter:
815    /// let general_formatter = specific_formatter.cast_into_fset::<DateFieldSet>();
816    ///
817    /// // Test that it still works:
818    /// assert_writeable_eq!(general_formatter.format(&date), "20 déc. 2024");
819    /// ```
820    pub fn cast_into_fset<FSet2: DateTimeNamesFrom<FSet>>(
821        self,
822    ) -> FixedCalendarDateTimeFormatter<C, FSet2> {
823        FixedCalendarDateTimeFormatter {
824            selection: self.selection,
825            names: self.names.cast_into_fset(),
826            _calendar: PhantomData,
827        }
828    }
829
830    /// Gets a [`FieldSetBuilder`] corresponding to the fields and options configured in this
831    /// formatter. The builder can be used to recreate the formatter.
832    ///
833    /// # Examples
834    ///
835    /// ```
836    /// use icu::datetime::fieldsets::builder::*;
837    /// use icu::datetime::fieldsets::YMD;
838    /// use icu::datetime::input::*;
839    /// use icu::datetime::options::*;
840    /// use icu::datetime::FixedCalendarDateTimeFormatter;
841    /// use icu::locale::locale;
842    /// use writeable::assert_writeable_eq;
843    ///
844    /// // Create a simple YMDT formatter:
845    /// let formatter = FixedCalendarDateTimeFormatter::try_new(
846    ///     locale!("und").into(),
847    ///     YMD::long().with_time_hm().with_alignment(Alignment::Column),
848    /// )
849    /// .unwrap();
850    ///
851    /// // Get the builder corresponding to it:
852    /// let builder = formatter.to_field_set_builder();
853    ///
854    /// // Check that the builder is what we expect:
855    /// let mut equivalent_builder = FieldSetBuilder::default();
856    /// equivalent_builder.length = Some(Length::Long);
857    /// equivalent_builder.date_fields = Some(DateFields::YMD);
858    /// equivalent_builder.time_precision = Some(TimePrecision::Minute);
859    /// equivalent_builder.alignment = Some(Alignment::Column);
860    /// equivalent_builder.year_style = None;
861    /// assert_eq!(builder, equivalent_builder,);
862    ///
863    /// // Check that it creates a formatter with equivalent behavior:
864    /// let built_formatter = FixedCalendarDateTimeFormatter::try_new(
865    ///     locale!("und").into(),
866    ///     builder.build_composite_datetime().unwrap(),
867    /// )
868    /// .unwrap();
869    /// let datetime = DateTime {
870    ///     date: Date::try_new_gregorian(2025, 3, 6).unwrap(),
871    ///     time: Time::try_new(16, 41, 0, 0).unwrap(),
872    /// };
873    /// assert_eq!(
874    ///     formatter.format(&datetime).to_string(),
875    ///     built_formatter.format(&datetime).to_string(),
876    /// );
877    /// ```
878    pub fn to_field_set_builder(&self) -> FieldSetBuilder {
879        self.selection.to_builder()
880    }
881}
882
883impl<FSet: DateTimeMarkers> DateTimeFormatter<FSet> {
884    /// Attempt to convert this [`DateTimeFormatter`] into one with a specific calendar.
885    ///
886    /// Returns an error if the type parameter does not match the inner calendar.
887    ///
888    /// # Examples
889    ///
890    /// ```
891    /// use icu::calendar::cal::Hebrew;
892    /// use icu::datetime::fieldsets::YMD;
893    /// use icu::datetime::input::Date;
894    /// use icu::datetime::DateTimeFormatter;
895    /// use icu::locale::locale;
896    /// use writeable::assert_writeable_eq;
897    ///
898    /// let formatter = DateTimeFormatter::try_new(
899    ///     locale!("en-u-ca-hebrew").into(),
900    ///     YMD::long(),
901    /// )
902    /// .unwrap()
903    /// .try_into_typed_formatter::<Hebrew>()
904    /// .unwrap();
905    ///
906    /// let date = Date::try_new_hebrew(5785, 1, 12).unwrap();
907    ///
908    /// assert_writeable_eq!(formatter.format(&date), "12 Tishri 5785");
909    /// ```
910    ///
911    /// An error occurs if the calendars don't match:
912    ///
913    /// ```
914    /// use icu::calendar::cal::Hebrew;
915    /// use icu::datetime::fieldsets::YMD;
916    /// use icu::datetime::input::Date;
917    /// use icu::datetime::DateTimeFormatter;
918    /// use icu::datetime::MismatchedCalendarError;
919    /// use icu::locale::locale;
920    ///
921    /// let result = DateTimeFormatter::try_new(
922    ///     locale!("en-u-ca-buddhist").into(),
923    ///     YMD::long(),
924    /// )
925    /// .unwrap()
926    /// .try_into_typed_formatter::<Hebrew>();
927    ///
928    /// assert!(matches!(result, Err(MismatchedCalendarError { .. })));
929    /// ```
930    pub fn try_into_typed_formatter<C>(
931        self,
932    ) -> Result<FixedCalendarDateTimeFormatter<C, FSet>, MismatchedCalendarError>
933    where
934        C: CldrCalendar + IntoAnyCalendar,
935    {
936        if let Err(cal) = C::from_any(self.calendar.take_any_calendar()) {
937            return Err(MismatchedCalendarError {
938                this_kind: cal.kind(),
939                date_kind: None,
940            });
941        }
942        Ok(FixedCalendarDateTimeFormatter {
943            selection: self.selection,
944            names: self.names,
945            _calendar: PhantomData,
946        })
947    }
948
949    /// Maps a [`DateTimeFormatter`] of a specific `FSet` to a more general `FSet`.
950    ///
951    /// For example, this can transform a formatter for [`YMD`] to one for [`DateFieldSet`].
952    ///
953    /// [`YMD`]: crate::fieldsets::YMD
954    /// [`DateFieldSet`]: crate::fieldsets::enums::DateFieldSet
955    ///
956    /// # Examples
957    ///
958    /// ```
959    /// use icu::calendar::Gregorian;
960    /// use icu::datetime::fieldsets::{enums::DateFieldSet, YMD};
961    /// use icu::datetime::input::Date;
962    /// use icu::datetime::DateTimeFormatter;
963    /// use icu::locale::locale;
964    /// use writeable::assert_writeable_eq;
965    ///
966    /// let specific_formatter =
967    ///     DateTimeFormatter::try_new(locale!("fr").into(), YMD::medium())
968    ///         .unwrap();
969    ///
970    /// // Test that the specific formatter works:
971    /// let date = Date::try_new_gregorian(2024, 12, 20).unwrap();
972    /// assert_writeable_eq!(specific_formatter.format(&date), "20 déc. 2024");
973    ///
974    /// // Make a more general formatter:
975    /// let general_formatter = specific_formatter.cast_into_fset::<DateFieldSet>();
976    ///
977    /// // Test that it still works:
978    /// assert_writeable_eq!(general_formatter.format(&date), "20 déc. 2024");
979    /// ```
980    pub fn cast_into_fset<FSet2: DateTimeNamesFrom<FSet>>(self) -> DateTimeFormatter<FSet2> {
981        DateTimeFormatter {
982            selection: self.selection,
983            names: self.names.cast_into_fset(),
984            calendar: self.calendar,
985        }
986    }
987
988    /// Returns the calendar used in this formatter.
989    ///
990    /// # Examples
991    ///
992    /// ```
993    /// use icu::calendar::AnyCalendarKind;
994    /// use icu::datetime::fieldsets::YMD;
995    /// use icu::datetime::input::Date;
996    /// use icu::datetime::DateTimeFormatter;
997    /// use icu::locale::locale;
998    /// use writeable::assert_writeable_eq;
999    ///
1000    /// let formatter =
1001    ///     DateTimeFormatter::try_new(locale!("th-TH").into(), YMD::long())
1002    ///         .unwrap();
1003    ///
1004    /// assert_writeable_eq!(
1005    ///     formatter.format(&Date::try_new_iso(2024, 12, 16).unwrap()),
1006    ///     "16 ธันวาคม 2567"
1007    /// );
1008    ///
1009    /// assert_eq!(formatter.calendar().kind(), AnyCalendarKind::Buddhist);
1010    /// ```
1011    pub fn calendar(&self) -> icu_calendar::Ref<AnyCalendar> {
1012        icu_calendar::Ref(self.calendar.any_calendar())
1013    }
1014
1015    /// Gets a [`FieldSetBuilder`] corresponding to the fields and options configured in this
1016    /// formatter. The builder can be used to recreate the formatter.
1017    ///
1018    /// # Examples
1019    ///
1020    /// ```
1021    /// use icu::datetime::fieldsets::builder::*;
1022    /// use icu::datetime::fieldsets::YMDT;
1023    /// use icu::datetime::input::*;
1024    /// use icu::datetime::options::*;
1025    /// use icu::datetime::DateTimeFormatter;
1026    /// use icu::locale::locale;
1027    /// use writeable::assert_writeable_eq;
1028    ///
1029    /// // Create a simple YMDT formatter:
1030    /// let formatter = DateTimeFormatter::try_new(
1031    ///     locale!("und").into(),
1032    ///     YMDT::long().with_alignment(Alignment::Column)
1033    /// )
1034    /// .unwrap();
1035    ///
1036    /// // Get the builder corresponding to it:
1037    /// let builder = formatter.to_field_set_builder();
1038    ///
1039    /// // Check that the builder is what we expect:
1040    /// let mut equivalent_builder = FieldSetBuilder::default();
1041    /// equivalent_builder.length = Some(Length::Long);
1042    /// equivalent_builder.date_fields = Some(DateFields::YMD);
1043    /// equivalent_builder.time_precision = Some(TimePrecision::Second); // set automatically
1044    /// equivalent_builder.alignment = Some(Alignment::Column);
1045    /// equivalent_builder.year_style = None;
1046    /// assert_eq!(
1047    ///     builder,
1048    ///     equivalent_builder,
1049    /// );
1050    ///
1051    /// // Check that it creates a formatter with equivalent behavior:
1052    /// let built_formatter = DateTimeFormatter::try_new(
1053    ///     locale!("und").into(),
1054    ///     builder.build_composite_datetime().unwrap(),
1055    /// )
1056    /// .unwrap();
1057    /// let datetime = DateTime {
1058    ///     date: Date::try_new_iso(2025, 3, 6).unwrap(),
1059    ///     time: Time::try_new(16, 41, 0, 0).unwrap(),
1060    /// };
1061    /// assert_eq!(
1062    ///     formatter.format(&datetime).to_string(),
1063    ///     built_formatter.format(&datetime).to_string(),
1064    /// );
1065    /// ```
1066    pub fn to_field_set_builder(&self) -> FieldSetBuilder {
1067        self.selection.to_builder()
1068    }
1069}
1070
1071/// A formatter optimized for time and time zone formatting, when a calendar is not needed.
1072///
1073/// # Examples
1074///
1075/// A [`NoCalendarFormatter`] can be used to format a time:
1076///
1077/// ```
1078/// use icu::datetime::fieldsets::T;
1079/// use icu::datetime::input::Time;
1080/// use icu::datetime::NoCalendarFormatter;
1081/// use icu::locale::locale;
1082///
1083/// let formatter =
1084///     NoCalendarFormatter::try_new(locale!("bn").into(), T::long()).unwrap();
1085/// assert_eq!(
1086///     formatter.format(&Time::start_of_day()).to_string(),
1087///     "১২:০০:০০ AM"
1088/// );
1089/// ```
1090///
1091/// A [`NoCalendarFormatter`] cannot be constructed with a fieldset that involves dates:
1092///
1093/// ```
1094/// use icu::datetime::fieldsets::Y;
1095/// use icu::datetime::NoCalendarFormatter;
1096/// use icu::locale::locale;
1097///
1098/// assert!(
1099///     NoCalendarFormatter::try_new(locale!("und").into(), Y::medium())
1100///         .is_err()
1101/// );
1102/// ```
1103///
1104/// Furthermore, it is a compile error in the format function:
1105///
1106/// ```compile_fail,E0271
1107/// use icu::datetime::NoCalendarFormatter;
1108/// use icu::datetime::fieldsets::Y;
1109/// use icu::locale::locale;
1110///
1111/// let date: icu::calendar::Date<icu::calendar::Gregorian> = unimplemented!();
1112/// let formatter = NoCalendarFormatter::try_new(locale!("und").into(), Y::medium()).unwrap();
1113///
1114/// // error[E0271]: type mismatch resolving `<Gregorian as AsCalendar>::Calendar == ()`
1115/// formatter.format(&date);
1116/// ```
1117pub type NoCalendarFormatter<FSet> = FixedCalendarDateTimeFormatter<(), FSet>;
1118
1119/// An intermediate type during a datetime formatting operation.
1120///
1121/// Not intended to be stored: convert to a string first.
1122#[derive(Debug)]
1123pub struct FormattedDateTime<'a> {
1124    pattern: DateTimeZonePatternDataBorrowed<'a>,
1125    input: DateTimeInputUnchecked,
1126    names: RawDateTimeNamesBorrowed<'a>,
1127}
1128
1129impl Writeable for FormattedDateTime<'_> {
1130    fn write_to_parts<S: writeable::PartsWrite + ?Sized>(
1131        &self,
1132        sink: &mut S,
1133    ) -> Result<(), fmt::Error> {
1134        let result = try_write_pattern_items(
1135            self.pattern.metadata(),
1136            self.pattern.iter_items(),
1137            &self.input,
1138            &self.names,
1139            self.names.decimal_formatter,
1140            sink,
1141        );
1142        // A DateTimeWriteError should not occur in normal usage because DateTimeFormatter
1143        // guarantees that all names for the pattern have been loaded and that the input type
1144        // is compatible with the pattern. However, this code path might be reachable with
1145        // invalid data. In that case, debug-panic and return the fallback string.
1146        match result {
1147            Ok(Ok(())) => Ok(()),
1148            Err(fmt::Error) => Err(fmt::Error),
1149            Ok(Err(e)) => {
1150                debug_assert!(false, "unexpected error in FormattedDateTime: {e:?}");
1151                Ok(())
1152            }
1153        }
1154    }
1155
1156    // TODO(#489): Implement writeable_length_hint
1157}
1158
1159impl_display_with_writeable!(FormattedDateTime<'_>);
1160
1161impl FormattedDateTime<'_> {
1162    /// Gets the pattern used in this formatted value.
1163    ///
1164    /// From the pattern, one can check the properties of the included components, such as
1165    /// the hour cycle being used for formatting. See [`DateTimePattern`].
1166    pub fn pattern(&self) -> DateTimePattern {
1167        self.pattern.to_pattern()
1168    }
1169}