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}