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