Skip to main content

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