icu_datetime/
builder.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//! Builder APIs for [dynamic field sets](crate::fieldsets::enums).
6//!
7//! These APIs are designed for when the field set is not known at compile time. This could
8//! happen if:
9//!
10//! 1. The field set is sent over the network or read from a data file
11//! 2. Implementing another interface with different types
12//!
13//! If the field set is known at compile time, use the static fieldset APIs instead of the
14//! builder exported in this module.
15//!
16//! A field set builder can be serialized with the right set of Cargo features.
17//!
18//! All examples below will show both ways to build a field set.
19//!
20//! # Examples
21//!
22//! ```
23//! use icu::datetime::fieldsets;
24//! use icu::datetime::fieldsets::builder::*;
25//! use icu::datetime::fieldsets::enums::*;
26//! use icu::datetime::options::*;
27//!
28//! // Year, Month, Day
29//! // Medium length
30//! // Always display the era
31//!
32//! let static_field_set =
33//!     fieldsets::YMD::medium().with_year_style(YearStyle::WithEra);
34//!
35//! let mut builder = FieldSetBuilder::new();
36//! builder.date_fields = Some(DateFields::YMD);
37//! builder.length = Some(Length::Medium);
38//! builder.year_style = Some(YearStyle::WithEra);
39//! let dynamic_field_set = builder.build_date().unwrap();
40//!
41//! assert_eq!(dynamic_field_set, DateFieldSet::YMD(static_field_set),);
42//!
43//! // Standalone Month
44//! // Long length
45//!
46//! let static_field_set = fieldsets::M::long();
47//!
48//! let mut builder = FieldSetBuilder::new();
49//! builder.length = Some(Length::Long);
50//! builder.date_fields = Some(DateFields::M);
51//! let dynamic_field_set = builder.build_calendar_period().unwrap();
52//!
53//! assert_eq!(
54//!     dynamic_field_set,
55//!     CalendarPeriodFieldSet::M(static_field_set),
56//! );
57//!
58//! // Weekday and Time of day
59//! // Medium length, implicit in the builder
60//! // Display time to the minute
61//!
62//! let static_field_set =
63//!     fieldsets::ET::medium().with_time_precision(TimePrecision::Minute);
64//!
65//! let mut builder = FieldSetBuilder::new();
66//! builder.date_fields = Some(DateFields::E);
67//! builder.time_precision = Some(TimePrecision::Minute);
68//! let dynamic_field_set = builder.build_date_and_time().unwrap();
69//!
70//! assert_eq!(dynamic_field_set, DateAndTimeFieldSet::ET(static_field_set),);
71//!
72//! // Time and Time Zone
73//! // Short length
74//! // Long specific non-location time zone
75//! // Display time to the millisecond
76//! // Render for column alignment
77//!
78//! let static_field_set = fieldsets::T::short()
79//!     .with_time_precision(TimePrecision::Subsecond(SubsecondDigits::S3))
80//!     .with_alignment(Alignment::Column)
81//!     .with_zone(fieldsets::zone::SpecificLong);
82//!
83//! let mut builder = FieldSetBuilder::new();
84//! builder.length = Some(Length::Short);
85//! builder.time_precision =
86//!     Some(TimePrecision::Subsecond(SubsecondDigits::S3));
87//! builder.alignment = Some(Alignment::Column);
88//! builder.zone_style = Some(ZoneStyle::SpecificLong);
89//! let dynamic_field_set = builder.build_composite().unwrap();
90//!
91//! assert_eq!(
92//!     dynamic_field_set,
93//!     CompositeFieldSet::TimeZone(static_field_set.into_enums()),
94//! );
95//! ```
96
97use crate::fieldsets::{self, enums::*, Combo};
98use crate::options::*;
99
100/// An enumeration over all possible date and calendar period field sets
101/// without options.
102///
103/// This is a builder enum. See [`builder`](crate::fieldsets::builder).
104#[derive(Debug, Copy, Clone, PartialEq, Eq)]
105#[cfg_attr(
106    all(feature = "serde", feature = "experimental"),
107    derive(serde::Serialize, serde::Deserialize)
108)]
109#[non_exhaustive]
110pub enum DateFields {
111    /// The day of the month, as in
112    /// “on the 1st”.
113    D,
114    /// The month and day of the month, as in
115    /// “January 1st”.
116    MD,
117    /// The year, month, and day of the month, as in
118    /// “January 1st, 2000”.
119    YMD,
120    /// The day of the month and day of the week, as in
121    /// “Saturday 1st”.
122    DE,
123    /// The month, day of the month, and day of the week, as in
124    /// “Saturday, January 1st”.
125    MDE,
126    /// The year, month, day of the month, and day of the week, as in
127    /// “Saturday, January 1st, 2000”.
128    YMDE,
129    /// The day of the week alone, as in
130    /// “Saturday”.
131    E,
132    /// A standalone month, as in
133    /// “January”.
134    M,
135    /// A month and year, as in
136    /// “January 2000”.
137    YM,
138    /// A year, as in
139    /// “2000”.
140    Y,
141}
142
143impl DateFields {
144    /// All values of this enumeration.
145    pub const VALUES: &[Self] = &[
146        Self::D,
147        Self::MD,
148        Self::YMD,
149        Self::DE,
150        Self::MDE,
151        Self::YMDE,
152        Self::E,
153        Self::M,
154        Self::YM,
155        Self::Y,
156    ];
157
158    /// Returns whether this [`DateFields`] variant represents a [`CalendarPeriodFieldSet`].
159    pub fn is_calendar_period(self) -> bool {
160        match self {
161            DateFields::D => false,
162            DateFields::MD => false,
163            DateFields::YMD => false,
164            DateFields::DE => false,
165            DateFields::MDE => false,
166            DateFields::YMDE => false,
167            DateFields::E => false,
168            DateFields::M => true,
169            DateFields::YM => true,
170            DateFields::Y => true,
171        }
172    }
173}
174
175/// An enumeration over all possible time zone styles.
176///
177/// This is a builder enum. See [`builder`](crate::fieldsets::builder).
178#[derive(Debug, Copy, Clone, PartialEq, Eq)]
179#[cfg_attr(
180    all(feature = "serde", feature = "experimental"),
181    derive(serde::Serialize, serde::Deserialize)
182)]
183#[non_exhaustive]
184pub enum ZoneStyle {
185    /// The long specific non-location format, as in
186    /// “Pacific Daylight Time”.
187    SpecificLong,
188    /// The short specific non-location format, as in
189    /// “PDT”.
190    SpecificShort,
191    /// The long offset format, as in
192    /// “GMT−8:00”.
193    LocalizedOffsetLong,
194    /// The short offset format, as in
195    /// “GMT−8”.
196    LocalizedOffsetShort,
197    /// The long generic non-location format, as in
198    /// “Pacific Time”.
199    GenericLong,
200    /// The short generic non-location format, as in
201    /// “PT”.
202    GenericShort,
203    /// The location format, as in
204    /// “Los Angeles time”.
205    Location,
206    /// The exemplar city format, as in
207    /// “Los Angeles”.
208    ExemplarCity,
209}
210
211impl ZoneStyle {
212    /// All values of this enumeration.
213    pub const VALUES: &[Self] = &[
214        Self::SpecificLong,
215        Self::SpecificShort,
216        Self::LocalizedOffsetLong,
217        Self::LocalizedOffsetShort,
218        Self::GenericLong,
219        Self::GenericShort,
220        Self::Location,
221        Self::ExemplarCity,
222    ];
223}
224
225/// An error that occurs when creating a [field set](crate::fieldsets) from a builder.
226// Not Copy: one of the variants contains a non-Copy type
227#[derive(Debug, Clone, displaydoc::Display)]
228#[ignore_extra_doc_attributes] // lines after the first won't go into `impl Display`
229#[non_exhaustive]
230pub enum BuilderError {
231    /// The builder needs [`DateFields`] in order to build the specified field set.
232    ///
233    /// This variant is also returned when building a composite field set if none of the
234    /// possible required options were set (date fields, time precision, zone style).
235    MissingDateFields,
236    /// The builder needs [`TimePrecision`] in order to build the specified field set.
237    MissingTimePrecision,
238    /// The builder needs [`ZoneStyle`] in order to build the specified field set.
239    MissingZoneStyle,
240    /// The value in [`DateFields`] is not a valid for the specified field set.
241    ///
242    /// This can happen if, for example:
243    ///
244    /// - You requested [`DateFieldSet`] but the fields are for a calendar period
245    /// - You requested [`CalendarPeriodFieldSet`] but the fields are for a date
246    /// - You requested a field set with time but the fields are for a calendar period
247    InvalidDateFields,
248    /// Superfluous options were specified.
249    ///
250    /// For example, you cannot set a [`YearStyle`] unless the field set contains years.
251    ///
252    /// The options that were _not_ read are returned back to the user.
253    ///
254    /// # Examples
255    ///
256    /// ```
257    /// use icu::datetime::fieldsets;
258    /// use icu::datetime::fieldsets::builder::*;
259    /// use icu::datetime::options::*;
260    ///
261    /// let mut builder = FieldSetBuilder::new();
262    /// builder.length = Some(Length::Short);
263    /// builder.time_precision = Some(TimePrecision::Minute);
264    /// builder.year_style = Some(YearStyle::WithEra);
265    ///
266    /// let err = builder.build_composite().unwrap_err();
267    ///
268    /// let BuilderError::SuperfluousOptions(superfluous_options) = err else {
269    ///     panic!("error type should be SuperfluousOptions");
270    /// };
271    ///
272    /// assert!(superfluous_options.year_style.is_some());
273    /// assert!(superfluous_options.time_precision.is_none());
274    /// ```
275    SuperfluousOptions(FieldSetBuilder),
276}
277
278impl core::error::Error for BuilderError {}
279
280/// Serde impls: We can't directly use `derive(Serialize)` and also hide null fields
281/// due to <https://github.com/serde-rs/serde/issues/2191>
282#[cfg(all(feature = "serde", feature = "experimental"))]
283mod _serde {
284    use super::*;
285    use serde::{Deserialize, Serialize};
286
287    #[derive(Serialize, Deserialize)]
288    #[serde(rename_all = "camelCase")]
289    struct FieldSetBuilderHuman {
290        #[serde(skip_serializing_if = "Option::is_none")]
291        pub length: Option<Length>,
292        #[serde(skip_serializing_if = "Option::is_none")]
293        pub date_fields: Option<DateFields>,
294        #[serde(skip_serializing_if = "Option::is_none")]
295        pub time_precision: Option<TimePrecision>,
296        #[serde(skip_serializing_if = "Option::is_none")]
297        pub zone_style: Option<ZoneStyle>,
298        #[serde(skip_serializing_if = "Option::is_none")]
299        pub alignment: Option<Alignment>,
300        #[serde(skip_serializing_if = "Option::is_none")]
301        pub year_style: Option<YearStyle>,
302    }
303
304    #[derive(Serialize)]
305    #[serde(rename_all = "camelCase")]
306    struct FieldSetBuilderMachine {
307        pub length: Option<Length>,
308        pub date_fields: Option<DateFields>,
309        pub time_precision: Option<TimePrecision>,
310        pub zone_style: Option<ZoneStyle>,
311        pub alignment: Option<Alignment>,
312        pub year_style: Option<YearStyle>,
313    }
314
315    /// Serialization for [`FieldSetBuilder`].
316    ///
317    /// ✨ *Enabled with the `serde` and `experimental` Cargo features.*
318    ///
319    /// # Examples
320    ///
321    /// ```
322    /// use icu::datetime::fieldsets::builder::*;
323    /// use icu::datetime::fieldsets::enums::*;
324    /// use icu::datetime::options::*;
325    ///
326    /// let mut builder = FieldSetBuilder::new();
327    /// builder.date_fields = Some(DateFields::YMD);
328    /// builder.length = Some(Length::Medium);
329    /// builder.year_style = Some(YearStyle::WithEra);
330    ///
331    /// let json_str = serde_json::to_string(&builder).unwrap();
332    ///
333    /// assert_eq!(
334    ///     json_str,
335    ///     r#"{"length":"medium","dateFields":"YMD","yearStyle":"withEra"}"#
336    /// );
337    ///
338    /// let json_parsed = serde_json::from_str(&json_str).unwrap();
339    ///
340    /// assert_eq!(builder, json_parsed);
341    /// ```
342    impl Serialize for FieldSetBuilder {
343        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
344        where
345            S: serde::Serializer,
346        {
347            let FieldSetBuilder {
348                length,
349                date_fields,
350                time_precision,
351                zone_style,
352                alignment,
353                year_style,
354            } = *self;
355            if serializer.is_human_readable() {
356                FieldSetBuilderHuman {
357                    length,
358                    date_fields,
359                    time_precision,
360                    zone_style,
361                    alignment,
362                    year_style,
363                }
364                .serialize(serializer)
365            } else {
366                FieldSetBuilderMachine {
367                    length,
368                    date_fields,
369                    time_precision,
370                    zone_style,
371                    alignment,
372                    year_style,
373                }
374                .serialize(serializer)
375            }
376        }
377    }
378
379    /// Deserialization for [`FieldSetBuilder`].
380    ///
381    /// ✨ *Enabled with the `serde` and `experimental` Cargo features.*
382    ///
383    /// For an example, see the `Serialize` impl.
384    impl<'de> Deserialize<'de> for FieldSetBuilder {
385        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
386        where
387            D: serde::Deserializer<'de>,
388        {
389            // Note: the Deserialize impls are the same. We could even derive this
390            // directly on FieldSetBuilder.
391            let FieldSetBuilderHuman {
392                length,
393                date_fields,
394                time_precision,
395                zone_style,
396                alignment,
397                year_style,
398            } = FieldSetBuilderHuman::deserialize(deserializer)?;
399            Ok(FieldSetBuilder {
400                length,
401                date_fields,
402                time_precision,
403                zone_style,
404                alignment,
405                year_style,
406            })
407        }
408    }
409}
410
411/// A builder for [dynamic field sets](crate::fieldsets::enums).
412///
413/// This builder is useful if you do not know the field set at code compilation time. If you do,
414/// the static field set APIs should yield smaller binary size.
415///
416/// For examples, see the [module docs](crate::fieldsets::builder).
417// Note: could be Copy but we don't want implicit moves
418#[derive(Debug, Clone, PartialEq, Eq, Default)]
419#[non_exhaustive]
420pub struct FieldSetBuilder {
421    /// The length of a formatted date/time string.
422    ///
423    /// If `None`, defaults to [`Length::Medium`].
424    pub length: Option<Length>,
425    /// The set of date fields, such as "year and month" or "weekday".
426    ///
427    /// If `None`, a date will not be displayed.
428    pub date_fields: Option<DateFields>,
429    /// The precision to display the time of day.
430    ///
431    /// If `None`, a time will not be displayed.
432    pub time_precision: Option<TimePrecision>,
433    /// The style to display the time zone.
434    ///
435    /// If `None`, a time zone will not be displayed.
436    pub zone_style: Option<ZoneStyle>,
437    /// The alignment context, such as when displaying dates in a table.
438    ///
439    /// This option may be specified only if the field set can honor it.
440    pub alignment: Option<Alignment>,
441    /// How to display the year and era.
442    ///
443    /// This option may be specified only if the year is included in [`Self::date_fields`].
444    pub year_style: Option<YearStyle>,
445}
446
447enum DateOrCalendarPeriodFieldSet {
448    Date(DateFieldSet),
449    CalendarPeriod(CalendarPeriodFieldSet),
450}
451
452impl FieldSetBuilder {
453    /// Creates a new, empty [`FieldSetBuilder`].
454    pub fn new() -> Self {
455        Self::default()
456    }
457
458    fn build_date_or_calendar_period_without_checking_options(
459        &mut self,
460    ) -> Result<DateOrCalendarPeriodFieldSet, BuilderError> {
461        use DateOrCalendarPeriodFieldSet::*;
462        let field_set = match self.date_fields.take() {
463            Some(DateFields::D) => Date(DateFieldSet::D(fieldsets::D::take_from_builder(self))),
464            Some(DateFields::MD) => Date(DateFieldSet::MD(fieldsets::MD::take_from_builder(self))),
465            Some(DateFields::YMD) => {
466                Date(DateFieldSet::YMD(fieldsets::YMD::take_from_builder(self)))
467            }
468            Some(DateFields::DE) => Date(DateFieldSet::DE(fieldsets::DE::take_from_builder(self))),
469            Some(DateFields::MDE) => {
470                Date(DateFieldSet::MDE(fieldsets::MDE::take_from_builder(self)))
471            }
472            Some(DateFields::YMDE) => {
473                Date(DateFieldSet::YMDE(fieldsets::YMDE::take_from_builder(self)))
474            }
475            Some(DateFields::E) => Date(DateFieldSet::E(fieldsets::E::take_from_builder(self))),
476            Some(DateFields::M) => CalendarPeriod(CalendarPeriodFieldSet::M(
477                fieldsets::M::take_from_builder(self),
478            )),
479            Some(DateFields::YM) => CalendarPeriod(CalendarPeriodFieldSet::YM(
480                fieldsets::YM::take_from_builder(self),
481            )),
482            Some(DateFields::Y) => CalendarPeriod(CalendarPeriodFieldSet::Y(
483                fieldsets::Y::take_from_builder(self),
484            )),
485            Option::None => return Err(BuilderError::MissingDateFields),
486        };
487        Ok(field_set)
488    }
489
490    /// Builds a [`DateFieldSet`].
491    ///
492    /// An error will occur if incompatible fields or options were set in the builder.
493    pub fn build_date(mut self) -> Result<DateFieldSet, BuilderError> {
494        let date_field_set = match self.build_date_or_calendar_period_without_checking_options()? {
495            DateOrCalendarPeriodFieldSet::Date(fs) => fs,
496            DateOrCalendarPeriodFieldSet::CalendarPeriod(_) => {
497                return Err(BuilderError::InvalidDateFields)
498            }
499        };
500        self.check_options_consumed()?;
501        Ok(date_field_set)
502    }
503
504    /// Builds a [`CalendarPeriodFieldSet`].
505    ///
506    /// An error will occur if incompatible fields or options were set in the builder.
507    pub fn build_calendar_period(mut self) -> Result<CalendarPeriodFieldSet, BuilderError> {
508        let calendar_period_field_set = match self
509            .build_date_or_calendar_period_without_checking_options()?
510        {
511            DateOrCalendarPeriodFieldSet::Date(_) => return Err(BuilderError::InvalidDateFields),
512            DateOrCalendarPeriodFieldSet::CalendarPeriod(fs) => fs,
513        };
514        self.check_options_consumed()?;
515        Ok(calendar_period_field_set)
516    }
517
518    /// Builds a [`TimeFieldSet`].
519    ///
520    /// An error will occur if incompatible fields or options were set in the builder.
521    pub fn build_time(mut self) -> Result<TimeFieldSet, BuilderError> {
522        let time_field_set = TimeFieldSet::T(fieldsets::T::take_from_builder(&mut self));
523        self.check_options_consumed()?;
524        Ok(time_field_set)
525    }
526
527    fn build_zone_without_checking_options(&mut self) -> Result<ZoneFieldSet, BuilderError> {
528        let zone_field_set = match self.zone_style.take() {
529            Some(ZoneStyle::SpecificShort) => {
530                ZoneFieldSet::SpecificShort(fieldsets::zone::SpecificShort)
531            }
532            Some(ZoneStyle::SpecificLong) => {
533                ZoneFieldSet::SpecificLong(fieldsets::zone::SpecificLong)
534            }
535            Some(ZoneStyle::LocalizedOffsetLong) => {
536                ZoneFieldSet::LocalizedOffsetLong(fieldsets::zone::LocalizedOffsetLong)
537            }
538            Some(ZoneStyle::LocalizedOffsetShort) => {
539                ZoneFieldSet::LocalizedOffsetShort(fieldsets::zone::LocalizedOffsetShort)
540            }
541            Some(ZoneStyle::GenericLong) => ZoneFieldSet::GenericLong(fieldsets::zone::GenericLong),
542            Some(ZoneStyle::GenericShort) => {
543                ZoneFieldSet::GenericShort(fieldsets::zone::GenericShort)
544            }
545            Some(ZoneStyle::Location) => ZoneFieldSet::Location(fieldsets::zone::Location),
546            Some(ZoneStyle::ExemplarCity) => {
547                ZoneFieldSet::ExemplarCity(fieldsets::zone::ExemplarCity)
548            }
549            Option::None => return Err(BuilderError::MissingZoneStyle),
550        };
551        Ok(zone_field_set)
552    }
553
554    /// Builds a [`ZoneFieldSet`].
555    ///
556    /// An error will occur if incompatible fields or options were set in the builder.
557    pub fn build_zone(mut self) -> Result<ZoneFieldSet, BuilderError> {
558        let zone_field_set = self.build_zone_without_checking_options()?;
559        self.check_options_consumed()?;
560        Ok(zone_field_set)
561    }
562
563    /// Builds a [`DateAndTimeFieldSet`].
564    ///
565    /// An error will occur if incompatible fields or options were set in the builder.
566    pub fn build_date_and_time(mut self) -> Result<DateAndTimeFieldSet, BuilderError> {
567        if self.time_precision.is_none() {
568            return Err(BuilderError::MissingTimePrecision);
569        }
570        let date_and_time_field_set = match self.date_fields.take() {
571            Some(DateFields::D) => {
572                DateAndTimeFieldSet::DT(fieldsets::DT::take_from_builder(&mut self))
573            }
574            Some(DateFields::MD) => {
575                DateAndTimeFieldSet::MDT(fieldsets::MDT::take_from_builder(&mut self))
576            }
577            Some(DateFields::YMD) => {
578                DateAndTimeFieldSet::YMDT(fieldsets::YMDT::take_from_builder(&mut self))
579            }
580            Some(DateFields::DE) => {
581                DateAndTimeFieldSet::DET(fieldsets::DET::take_from_builder(&mut self))
582            }
583            Some(DateFields::MDE) => {
584                DateAndTimeFieldSet::MDET(fieldsets::MDET::take_from_builder(&mut self))
585            }
586            Some(DateFields::YMDE) => {
587                DateAndTimeFieldSet::YMDET(fieldsets::YMDET::take_from_builder(&mut self))
588            }
589            Some(DateFields::E) => {
590                DateAndTimeFieldSet::ET(fieldsets::ET::take_from_builder(&mut self))
591            }
592            Some(DateFields::M) | Some(DateFields::YM) | Some(DateFields::Y) | Option::None => {
593                return Err(BuilderError::InvalidDateFields)
594            }
595        };
596        self.check_options_consumed()?;
597        Ok(date_and_time_field_set)
598    }
599
600    /// Builds a [`CompositeDateTimeFieldSet`].
601    ///
602    /// An error will occur if incompatible fields or options were set in the builder.
603    pub fn build_composite_datetime(mut self) -> Result<CompositeDateTimeFieldSet, BuilderError> {
604        // Check for the presence of date and time, then delegate to the correct impl.
605        match (self.date_fields.is_some(), self.time_precision.is_some()) {
606            (true, false) => {
607                let field_set = match self
608                    .build_date_or_calendar_period_without_checking_options()?
609                {
610                    DateOrCalendarPeriodFieldSet::Date(fs) => CompositeDateTimeFieldSet::Date(fs),
611                    DateOrCalendarPeriodFieldSet::CalendarPeriod(fs) => {
612                        CompositeDateTimeFieldSet::CalendarPeriod(fs)
613                    }
614                };
615                self.check_options_consumed()?;
616                Ok(field_set)
617            }
618            (false, true) => self.build_time().map(CompositeDateTimeFieldSet::Time),
619            (true, true) => self
620                .build_date_and_time()
621                .map(CompositeDateTimeFieldSet::DateTime),
622            (false, false) => Err(BuilderError::MissingDateFields),
623        }
624    }
625
626    /// Builds a [`Combo`] for a zoned date.
627    ///
628    /// An error will occur if incompatible fields or options were set in the builder.
629    pub fn build_zoned_date(mut self) -> Result<ZonedDateFieldSet, BuilderError> {
630        let zone_field_set = self.build_zone_without_checking_options()?;
631        let date_field_set = self.build_date()?;
632        Ok(date_field_set.with_zone(zone_field_set))
633    }
634
635    /// Builds a [`Combo`] for a zoned time.
636    ///
637    /// An error will occur if incompatible fields or options were set in the builder.
638    pub fn build_zoned_time(mut self) -> Result<ZonedTimeFieldSet, BuilderError> {
639        let zone_field_set = self.build_zone_without_checking_options()?;
640        let time_field_set = self.build_time()?;
641        Ok(time_field_set.with_zone(zone_field_set))
642    }
643
644    /// Builds a [`Combo`] for a zoned date and time.
645    ///
646    /// An error will occur if incompatible fields or options were set in the builder.
647    pub fn build_zoned_date_and_time(mut self) -> Result<ZonedDateAndTimeFieldSet, BuilderError> {
648        let zone_field_set = self.build_zone_without_checking_options()?;
649        let datetime_field_set = self.build_date_and_time()?;
650        Ok(datetime_field_set.with_zone(zone_field_set))
651    }
652
653    /// Builds a [`CompositeFieldSet`].
654    ///
655    /// An error will occur if incompatible fields or options were set in the builder.
656    pub fn build_composite(mut self) -> Result<CompositeFieldSet, BuilderError> {
657        // Check for the presence of date, time, and zone, then delegate to the correct impl.
658        match (
659            self.date_fields.is_some(),
660            self.time_precision.is_some(),
661            self.zone_style.is_some(),
662        ) {
663            (true, false, false) => {
664                let field_set =
665                    match self.build_date_or_calendar_period_without_checking_options()? {
666                        DateOrCalendarPeriodFieldSet::Date(fs) => CompositeFieldSet::Date(fs),
667                        DateOrCalendarPeriodFieldSet::CalendarPeriod(fs) => {
668                            CompositeFieldSet::CalendarPeriod(fs)
669                        }
670                    };
671                self.check_options_consumed()?;
672                Ok(field_set)
673            }
674            (false, true, false) => self.build_time().map(CompositeFieldSet::Time),
675            (true, true, false) => self.build_date_and_time().map(CompositeFieldSet::DateTime),
676            (false, false, true) => self.build_zone().map(CompositeFieldSet::Zone),
677            (true, false, true) => {
678                let zone_field_set = self.build_zone_without_checking_options()?;
679                let date_field_set = self.build_date()?;
680                Ok(CompositeFieldSet::DateZone(Combo::new(
681                    date_field_set,
682                    zone_field_set,
683                )))
684            }
685            (false, true, true) => {
686                let zone_field_set = self.build_zone_without_checking_options()?;
687                let time_field_set = self.build_time()?;
688                Ok(CompositeFieldSet::TimeZone(Combo::new(
689                    time_field_set,
690                    zone_field_set,
691                )))
692            }
693            (true, true, true) => {
694                let zone_field_set = self.build_zone_without_checking_options()?;
695                let date_and_time_field_set = self.build_date_and_time()?;
696                Ok(CompositeFieldSet::DateTimeZone(Combo::new(
697                    date_and_time_field_set,
698                    zone_field_set,
699                )))
700            }
701            (false, false, false) => Err(BuilderError::MissingDateFields),
702        }
703    }
704
705    fn check_options_consumed(self) -> Result<(), BuilderError> {
706        if self != Self::default() {
707            Err(BuilderError::SuperfluousOptions(self))
708        } else {
709            Ok(())
710        }
711    }
712}
713
714#[cfg(test)]
715mod tests {
716    use super::*;
717
718    static DATE_FIELD_SETS: &[DateFields] = &[
719        DateFields::D,
720        DateFields::MD,
721        DateFields::YMD,
722        DateFields::DE,
723        DateFields::MDE,
724        DateFields::YMDE,
725        DateFields::E,
726    ];
727
728    static CALENDAR_PERIOD_FIELD_SETS: &[DateFields] =
729        &[DateFields::M, DateFields::YM, DateFields::Y];
730
731    static ZONE_STYLES: &[ZoneStyle] = &[
732        ZoneStyle::SpecificLong,
733        ZoneStyle::SpecificShort,
734        ZoneStyle::LocalizedOffsetLong,
735        ZoneStyle::LocalizedOffsetShort,
736        ZoneStyle::GenericLong,
737        ZoneStyle::GenericShort,
738        ZoneStyle::Location,
739        ZoneStyle::ExemplarCity,
740    ];
741
742    #[cfg(all(feature = "serde", feature = "experimental"))]
743    fn check_serde(value: &FieldSetBuilder) {
744        let json_str = serde_json::to_string(value).unwrap();
745        let json_parsed: FieldSetBuilder = serde_json::from_str(&json_str).unwrap();
746        assert_eq!(value, &json_parsed);
747        let bincode_bytes = bincode::serialize(value).unwrap();
748        let bincode_parsed: FieldSetBuilder = bincode::deserialize(&bincode_bytes).unwrap();
749        assert_eq!(value, &bincode_parsed);
750    }
751
752    #[test]
753    fn test_date_field_sets() {
754        for date_fields in DATE_FIELD_SETS.iter() {
755            let mut builder = FieldSetBuilder::new();
756            builder.date_fields = Some(*date_fields);
757            builder.clone().build_date().unwrap();
758            builder.clone().build_calendar_period().unwrap_err();
759            builder.clone().build_time().unwrap_err();
760            builder.clone().build_zone().unwrap_err();
761            builder.clone().build_date_and_time().unwrap_err();
762            builder.clone().build_composite_datetime().unwrap();
763            builder.clone().build_composite().unwrap();
764            #[cfg(all(feature = "serde", feature = "experimental"))]
765            check_serde(&builder);
766        }
767    }
768
769    #[test]
770    fn test_calendar_period_field_sets() {
771        for date_fields in CALENDAR_PERIOD_FIELD_SETS.iter() {
772            let mut builder = FieldSetBuilder::new();
773            builder.date_fields = Some(*date_fields);
774            builder.clone().build_date().unwrap_err();
775            builder.clone().build_calendar_period().unwrap();
776            builder.clone().build_time().unwrap_err();
777            builder.clone().build_zone().unwrap_err();
778            builder.clone().build_date_and_time().unwrap_err();
779            builder.clone().build_composite_datetime().unwrap();
780            builder.clone().build_composite().unwrap();
781            #[cfg(all(feature = "serde", feature = "experimental"))]
782            check_serde(&builder);
783        }
784    }
785
786    #[test]
787    fn test_time_field_sets() {
788        let mut builder = FieldSetBuilder::new();
789        builder.time_precision = Some(TimePrecision::Minute);
790        builder.clone().build_date().unwrap_err();
791        builder.clone().build_calendar_period().unwrap_err();
792        builder.clone().build_time().unwrap();
793        builder.clone().build_zone().unwrap_err();
794        builder.clone().build_date_and_time().unwrap_err();
795        builder.clone().build_composite_datetime().unwrap();
796        builder.clone().build_composite().unwrap();
797        #[cfg(all(feature = "serde", feature = "experimental"))]
798        check_serde(&builder);
799    }
800
801    #[test]
802    fn test_zone_field_sets() {
803        for zone_style in ZONE_STYLES.iter() {
804            let mut builder = FieldSetBuilder::new();
805            builder.zone_style = Some(*zone_style);
806            builder.clone().build_date().unwrap_err();
807            builder.clone().build_calendar_period().unwrap_err();
808            builder.clone().build_time().unwrap_err();
809            builder.clone().build_zone().unwrap();
810            builder.clone().build_date_and_time().unwrap_err();
811            builder.clone().build_composite_datetime().unwrap_err();
812            builder.clone().build_composite().unwrap();
813            #[cfg(all(feature = "serde", feature = "experimental"))]
814            check_serde(&builder);
815        }
816    }
817
818    #[test]
819    fn test_date_time_field_sets() {
820        for date_fields in DATE_FIELD_SETS.iter() {
821            let mut builder = FieldSetBuilder::new();
822            builder.date_fields = Some(*date_fields);
823            builder.time_precision = Some(TimePrecision::Minute);
824            builder.clone().build_date().unwrap_err();
825            builder.clone().build_calendar_period().unwrap_err();
826            builder.clone().build_time().unwrap_err();
827            builder.clone().build_zone().unwrap_err();
828            builder.clone().build_date_and_time().unwrap();
829            builder.clone().build_composite_datetime().unwrap();
830            builder.clone().build_composite().unwrap();
831            #[cfg(all(feature = "serde", feature = "experimental"))]
832            check_serde(&builder);
833        }
834    }
835
836    #[test]
837    fn test_calendar_period_time_field_sets() {
838        // Should always fail
839        for date_fields in CALENDAR_PERIOD_FIELD_SETS.iter() {
840            let mut builder = FieldSetBuilder::new();
841            builder.date_fields = Some(*date_fields);
842            builder.time_precision = Some(TimePrecision::Minute);
843            builder.clone().build_date().unwrap_err();
844            builder.clone().build_calendar_period().unwrap_err();
845            builder.clone().build_time().unwrap_err();
846            builder.clone().build_zone().unwrap_err();
847            builder.clone().build_date_and_time().unwrap_err();
848            builder.clone().build_composite_datetime().unwrap_err();
849            builder.clone().build_composite().unwrap_err();
850            #[cfg(all(feature = "serde", feature = "experimental"))]
851            check_serde(&builder);
852        }
853    }
854
855    #[test]
856    fn test_date_zone_field_sets() {
857        for date_fields in DATE_FIELD_SETS.iter() {
858            for zone_style in ZONE_STYLES.iter() {
859                let mut builder = FieldSetBuilder::new();
860                builder.date_fields = Some(*date_fields);
861                builder.zone_style = Some(*zone_style);
862                builder.clone().build_date().unwrap_err();
863                builder.clone().build_calendar_period().unwrap_err();
864                builder.clone().build_time().unwrap_err();
865                builder.clone().build_zone().unwrap_err();
866                builder.clone().build_date_and_time().unwrap_err();
867                builder.clone().build_composite_datetime().unwrap_err();
868                builder.clone().build_composite().unwrap();
869                #[cfg(all(feature = "serde", feature = "experimental"))]
870                check_serde(&builder);
871            }
872        }
873    }
874
875    #[test]
876    fn test_calendar_period_zone_field_sets() {
877        // Should always fail
878        for date_fields in CALENDAR_PERIOD_FIELD_SETS.iter() {
879            for zone_style in ZONE_STYLES.iter() {
880                let mut builder = FieldSetBuilder::new();
881                builder.date_fields = Some(*date_fields);
882                builder.zone_style = Some(*zone_style);
883                builder.clone().build_date().unwrap_err();
884                builder.clone().build_calendar_period().unwrap_err();
885                builder.clone().build_time().unwrap_err();
886                builder.clone().build_zone().unwrap_err();
887                builder.clone().build_date_and_time().unwrap_err();
888                builder.clone().build_composite_datetime().unwrap_err();
889                builder.clone().build_composite().unwrap_err();
890                #[cfg(all(feature = "serde", feature = "experimental"))]
891                check_serde(&builder);
892            }
893        }
894    }
895
896    #[test]
897    fn test_time_zone_field_sets() {
898        for zone_style in ZONE_STYLES.iter() {
899            let mut builder = FieldSetBuilder::new();
900            builder.time_precision = Some(TimePrecision::Minute);
901            builder.zone_style = Some(*zone_style);
902            builder.clone().build_date().unwrap_err();
903            builder.clone().build_calendar_period().unwrap_err();
904            builder.clone().build_time().unwrap_err();
905            builder.clone().build_zone().unwrap_err();
906            builder.clone().build_date_and_time().unwrap_err();
907            builder.clone().build_composite_datetime().unwrap_err();
908            builder.clone().build_composite().unwrap();
909            #[cfg(all(feature = "serde", feature = "experimental"))]
910            check_serde(&builder);
911        }
912    }
913
914    #[test]
915    fn test_date_time_zone_field_sets() {
916        for date_fields in DATE_FIELD_SETS.iter() {
917            for zone_style in ZONE_STYLES.iter() {
918                let mut builder = FieldSetBuilder::new();
919                builder.date_fields = Some(*date_fields);
920                builder.time_precision = Some(TimePrecision::Minute);
921                builder.zone_style = Some(*zone_style);
922                builder.clone().build_date().unwrap_err();
923                builder.clone().build_calendar_period().unwrap_err();
924                builder.clone().build_time().unwrap_err();
925                builder.clone().build_zone().unwrap_err();
926                builder.clone().build_date_and_time().unwrap_err();
927                builder.clone().build_composite_datetime().unwrap_err();
928                builder.clone().build_composite().unwrap();
929                #[cfg(all(feature = "serde", feature = "experimental"))]
930                check_serde(&builder);
931            }
932        }
933    }
934
935    #[test]
936    fn test_calendar_period_time_zone_field_sets() {
937        // Should always fail
938        for date_fields in CALENDAR_PERIOD_FIELD_SETS.iter() {
939            for zone_style in ZONE_STYLES.iter() {
940                let mut builder = FieldSetBuilder::new();
941                builder.date_fields = Some(*date_fields);
942                builder.time_precision = Some(TimePrecision::Minute);
943                builder.zone_style = Some(*zone_style);
944                builder.clone().build_date().unwrap_err();
945                builder.clone().build_calendar_period().unwrap_err();
946                builder.clone().build_time().unwrap_err();
947                builder.clone().build_zone().unwrap_err();
948                builder.clone().build_date_and_time().unwrap_err();
949                builder.clone().build_composite_datetime().unwrap_err();
950                builder.clone().build_composite().unwrap_err();
951                #[cfg(all(feature = "serde", feature = "experimental"))]
952                check_serde(&builder);
953            }
954        }
955    }
956}