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}