1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

use super::{reference, runtime, PatternItem};
use crate::{fields, options::preferences};
#[cfg(feature = "datagen")]
use crate::{provider, skeleton};
use icu_provider::prelude::*;

/// Used to represent either H11/H12, or H23/H24. Skeletons only store these
/// hour cycles as H12 or H23.
#[derive(Debug, PartialEq, Clone, Copy, yoke::Yokeable, zerofrom::ZeroFrom)]
#[cfg_attr(
    feature = "datagen",
    derive(serde::Serialize, databake::Bake),
    databake(path = icu_datetime::pattern),
)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
#[allow(clippy::exhaustive_enums)] // this type is stable
pub enum CoarseHourCycle {
    /// Can either be fields::Hour::H11 or fields::Hour::H12
    H11H12,
    /// Can either be fields::Hour::H23 or fields::Hour::H24
    H23H24,
}

/// Default is required for serialization. H23H24 is the more locale-agnostic choice, as it's
/// less likely to have a day period in it.
impl Default for CoarseHourCycle {
    fn default() -> Self {
        CoarseHourCycle::H23H24
    }
}

impl CoarseHourCycle {
    /// Figure out the coarse hour cycle given a pattern, which is useful for generating the provider
    /// patterns for `length::Bag`.
    pub fn determine(pattern: &reference::Pattern) -> Option<Self> {
        for item in pattern.items.iter() {
            if let PatternItem::Field(fields::Field {
                symbol: fields::FieldSymbol::Hour(pattern_hour),
                length: _,
            }) = item
            {
                return Some(match pattern_hour {
                    fields::Hour::H11 | fields::Hour::H12 => CoarseHourCycle::H11H12,
                    fields::Hour::H23 | fields::Hour::H24 => CoarseHourCycle::H23H24,
                });
            }
        }

        None
    }

    /// Invoke the pattern matching machinery to transform the hour cycle of a pattern. This provides
    /// a safe mapping from a h11/h12 to h23/h24 for transforms.
    #[doc(hidden)]
    #[cfg(feature = "datagen")]
    pub fn apply_on_pattern<'data>(
        &self,
        date_time: &provider::calendar::patterns::GenericLengthPatternsV1<'data>,
        skeletons: &provider::calendar::DateSkeletonPatternsV1<'data>,
        pattern_str: &str,
        mut pattern: reference::Pattern,
    ) -> Option<reference::Pattern> {
        for item in pattern.items_mut() {
            if let PatternItem::Field(fields::Field { symbol, length: _ }) = item {
                if let fields::FieldSymbol::Hour(pattern_hour) = symbol {
                    if match self {
                        CoarseHourCycle::H11H12 => match pattern_hour {
                            fields::Hour::H11 | fields::Hour::H12 => true,
                            fields::Hour::H23 | fields::Hour::H24 => false,
                        },
                        CoarseHourCycle::H23H24 => match pattern_hour {
                            fields::Hour::H11 | fields::Hour::H12 => false,
                            fields::Hour::H23 | fields::Hour::H24 => true,
                        },
                    } {
                        // The preference hour cycle matches the pattern, bail out early and
                        // return the current pattern.
                        return Some(pattern_str.into());
                    } else {
                        // Mutate the pattern with the new symbol, so that it can be matched against.
                        *symbol = fields::FieldSymbol::Hour(match self {
                            CoarseHourCycle::H11H12 => fields::Hour::H12,
                            CoarseHourCycle::H23H24 => fields::Hour::H23,
                        });
                        break;
                    }
                }
            }
        }

        let skeleton = skeleton::reference::Skeleton::from(&pattern);

        match skeleton::create_best_pattern_for_fields(
            skeletons,
            date_time,
            skeleton.as_slice(),
            &Default::default(),
            // Prefer using the matched pattern directly, rather than mutating it to match the
            // requested fields.
            true,
        ) {
            skeleton::BestSkeleton::AllFieldsMatch(patterns)
            | skeleton::BestSkeleton::MissingOrExtraFields(patterns) => {
                Some(reference::Pattern::from(&patterns.expect_pattern(
                    "Only week-of patterns have plural variants",
                )))
            }
            skeleton::BestSkeleton::NoMatch => None,
        }
    }

    /// Get the other coarse hour cycle (map h11/h12 to h23/h24, and vice versa)
    pub fn invert(self) -> Self {
        match self {
            CoarseHourCycle::H11H12 => CoarseHourCycle::H23H24,
            CoarseHourCycle::H23H24 => CoarseHourCycle::H11H12,
        }
    }
}

/// The hour cycle can be set by preferences. This function switches between h11 and h12,
/// and between h23 and h24. This function is naive as it is assumed that this application of
/// the hour cycle will not change between h1x to h2x.
pub(crate) fn naively_apply_preferences(
    pattern: &mut runtime::Pattern,
    preferences: &Option<preferences::Bag>,
) {
    // If there is a preference overriding the hour cycle, apply it now.
    if let Some(preferences::Bag {
        hour_cycle: Some(hour_cycle),
    }) = preferences
    {
        runtime::helpers::maybe_replace_first(pattern, |item| {
            if let PatternItem::Field(fields::Field {
                symbol: fields::FieldSymbol::Hour(current_hour),
                length,
            }) = item
            {
                if *current_hour != hour_cycle.field() {
                    Some(PatternItem::from((
                        fields::FieldSymbol::Hour(hour_cycle.field()),
                        *length,
                    )))
                } else {
                    None
                }
            } else {
                None
            }
        });
    }
}