icu_datetime/provider/pattern/
hour_cycle.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#[cfg(feature = "datagen")]
6use super::runtime;
7use super::{reference, PatternItem};
8use crate::provider::fields;
9#[cfg(feature = "datagen")]
10use crate::provider::{self, skeleton};
11#[cfg(feature = "datagen")]
12use icu_locale_core::preferences::extensions::unicode::keywords::HourCycle;
13use icu_provider::prelude::*;
14
15/// Used to represent either H11/H12, or H23. Skeletons only store these
16/// hour cycles as H12 or H23.
17#[derive(Debug, PartialEq, Clone, Copy, yoke::Yokeable, zerofrom::ZeroFrom)]
18#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
19#[cfg_attr(feature = "datagen", databake(path = icu_datetime::provider::pattern))]
20#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
21#[allow(clippy::exhaustive_enums)] // this type is stable
22pub enum CoarseHourCycle {
23    /// Can either be fields::Hour::H11 or fields::Hour::H12
24    H11H12,
25    /// fields::Hour::H23
26    H23,
27}
28
29/// Default is required for serialization. H23 is the more locale-agnostic choice, as it's
30/// less likely to have a day period in it.
31impl Default for CoarseHourCycle {
32    fn default() -> Self {
33        CoarseHourCycle::H23
34    }
35}
36
37impl CoarseHourCycle {
38    /// Figure out the coarse hour cycle given a pattern, which is useful for generating the provider
39    /// patterns for `length::Bag`.
40    pub fn determine(pattern: &reference::Pattern) -> Option<Self> {
41        for item in pattern.items.iter() {
42            if let PatternItem::Field(fields::Field {
43                symbol: fields::FieldSymbol::Hour(pattern_hour),
44                length: _,
45            }) = item
46            {
47                return Some(match pattern_hour {
48                    fields::Hour::H11 | fields::Hour::H12 => CoarseHourCycle::H11H12,
49                    fields::Hour::H23 => CoarseHourCycle::H23,
50                });
51            }
52        }
53
54        None
55    }
56
57    /// Invoke the pattern matching machinery to transform the hour cycle of a pattern. This provides
58    /// a safe mapping from a h11/h12 to h23/h24 for transforms.
59    #[doc(hidden)]
60    #[cfg(feature = "datagen")]
61    pub fn apply_on_pattern<'data>(
62        &self,
63        date_time: &provider::calendar::patterns::GenericLengthPatterns<'data>,
64        skeletons: &provider::calendar::DateSkeletonPatterns<'data>,
65        pattern_str: &str,
66        mut pattern: reference::Pattern,
67    ) -> Option<reference::Pattern> {
68        for item in pattern.items_mut() {
69            if let PatternItem::Field(fields::Field { symbol, length: _ }) = item {
70                if let fields::FieldSymbol::Hour(pattern_hour) = symbol {
71                    if match self {
72                        CoarseHourCycle::H11H12 => match pattern_hour {
73                            fields::Hour::H11 | fields::Hour::H12 => true,
74                            fields::Hour::H23 => false,
75                        },
76                        CoarseHourCycle::H23 => match pattern_hour {
77                            fields::Hour::H11 | fields::Hour::H12 => false,
78                            fields::Hour::H23 => true,
79                        },
80                    } {
81                        // The preference hour cycle matches the pattern, bail out early and
82                        // return the current pattern.
83                        return Some(pattern_str.into());
84                    } else {
85                        // Mutate the pattern with the new symbol, so that it can be matched against.
86                        *symbol = fields::FieldSymbol::Hour(match self {
87                            CoarseHourCycle::H11H12 => fields::Hour::H12,
88                            CoarseHourCycle::H23 => fields::Hour::H23,
89                        });
90                        break;
91                    }
92                }
93            }
94        }
95
96        let skeleton = skeleton::reference::Skeleton::from(&pattern);
97
98        match skeleton::create_best_pattern_for_fields(
99            skeletons,
100            date_time,
101            skeleton.as_slice(),
102            &Default::default(),
103            // Prefer using the matched pattern directly, rather than mutating it to match the
104            // requested fields.
105            true,
106        ) {
107            skeleton::BestSkeleton::AllFieldsMatch(patterns, _)
108            | skeleton::BestSkeleton::MissingOrExtraFields(patterns, _) => {
109                Some(reference::Pattern::from(&patterns.expect_pattern(
110                    "Only week-of patterns have plural variants",
111                )))
112            }
113            skeleton::BestSkeleton::NoMatch => None,
114        }
115    }
116
117    /// Get the other coarse hour cycle (map h11/h12 to h23/h24, and vice versa)
118    pub fn invert(self) -> Self {
119        match self {
120            CoarseHourCycle::H11H12 => CoarseHourCycle::H23,
121            CoarseHourCycle::H23 => CoarseHourCycle::H11H12,
122        }
123    }
124}
125
126/// The hour cycle can be set by preferences. This function switches between h11 and h12,
127/// and between h23 and h24. This function is naive as it is assumed that this application of
128/// the hour cycle will not change between h1x to h2x.
129#[cfg(feature = "datagen")]
130pub(crate) fn naively_apply_preferences(
131    pattern: &mut runtime::Pattern,
132    hour_cycle: Option<HourCycle>,
133) {
134    // If there is a preference overriding the hour cycle, apply it now.
135    if let Some(hour_cycle) = hour_cycle {
136        runtime::helpers::maybe_replace_first(pattern, |item| {
137            if let PatternItem::Field(fields::Field {
138                symbol: fields::FieldSymbol::Hour(current_hour),
139                length,
140            }) = item
141            {
142                let candidate_field = fields::Hour::from_hour_cycle(hour_cycle);
143                if *current_hour != candidate_field {
144                    Some(PatternItem::from((
145                        fields::FieldSymbol::Hour(candidate_field),
146                        *length,
147                    )))
148                } else {
149                    None
150                }
151            } else {
152                None
153            }
154        });
155    }
156}