icu_datetime/provider/fields/
mod.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//! Enums representing the fields in a date pattern, including the field's type, length and symbol.
6
7#![allow(clippy::exhaustive_structs)] // Field and FieldULE part of data struct
8
9mod length;
10pub(crate) mod symbols;
11
12use displaydoc::Display;
13pub use length::{FieldLength, FieldNumericOverrides, LengthError};
14pub use symbols::*;
15use writeable::Writeable;
16
17#[cfg(any(feature = "experimental", feature = "datagen"))]
18pub mod components;
19
20use core::{
21    cmp::{Ord, PartialOrd},
22    convert::TryFrom,
23};
24
25use crate::error::ErrorField;
26
27/// An error relating to the field for a date pattern field as a whole.
28///
29/// Separate error types exist for parts of a field, like the
30/// [`LengthError`](error for the field length) and the
31/// [`SymbolError`](error for the field symbol).
32#[derive(Display, Debug, Copy, Clone, PartialEq)]
33#[non_exhaustive]
34pub enum Error {
35    /// An error originating inside of the [data provider](icu_provider).
36    #[displaydoc("Field {0:?} is not a valid length")]
37    InvalidLength(FieldSymbol),
38}
39
40impl core::error::Error for Error {}
41
42/// A field within a date pattern string, also referred to as a date field.
43///
44/// A date field is the
45/// repetition of a specific pattern character one or more times within the pattern string.
46/// The pattern character is known as the field symbol, which indicates the particular meaning for the field.
47#[derive(Debug, Eq, PartialEq, Clone, Copy, Ord, PartialOrd)]
48#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
49#[cfg_attr(feature = "datagen", databake(path = icu_datetime::fields))]
50#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
51#[zerovec::make_ule(FieldULE)]
52pub struct Field {
53    /// The field symbol for the `Field`, which corresponds to the field's meaning with the
54    /// date pattern.
55    pub symbol: FieldSymbol,
56    /// The length of the `Field`, which in conjunction with the `FieldSymbol` informs the width or
57    /// style of the formatting output corresponding to this field.
58    pub length: FieldLength,
59}
60
61impl Field {
62    #[cfg(feature = "datagen")]
63    pub(crate) fn get_length_type(self) -> TextOrNumeric {
64        match self.symbol {
65            FieldSymbol::Era => TextOrNumeric::Text,
66            FieldSymbol::Year(year) => year.get_length_type(self.length),
67            FieldSymbol::Month(month) => month.get_length_type(self.length),
68            FieldSymbol::Week(week) => week.get_length_type(self.length),
69            FieldSymbol::Day(day) => day.get_length_type(self.length),
70            FieldSymbol::Weekday(weekday) => weekday.get_length_type(self.length),
71            FieldSymbol::DayPeriod(day_period) => day_period.get_length_type(self.length),
72            FieldSymbol::Hour(hour) => hour.get_length_type(self.length),
73            FieldSymbol::Minute => TextOrNumeric::Numeric,
74            FieldSymbol::Second(second) => second.get_length_type(self.length),
75            FieldSymbol::TimeZone(zone) => zone.get_length_type(self.length),
76            FieldSymbol::DecimalSecond(_) => TextOrNumeric::Numeric,
77        }
78    }
79}
80
81impl Writeable for Field {
82    fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W) -> core::fmt::Result {
83        let ch: char = self.symbol.into();
84        for _ in 0..self.length.to_len() {
85            sink.write_char(ch)?;
86        }
87        Ok(())
88    }
89    fn writeable_length_hint(&self) -> writeable::LengthHint {
90        writeable::LengthHint::exact(self.length.to_len())
91    }
92}
93
94writeable::impl_display_with_writeable!(Field);
95
96impl FieldULE {
97    #[inline]
98    pub(crate) fn validate_byte_pair(bytes: (u8, u8)) -> Result<(), zerovec::ule::UleError> {
99        symbols::FieldSymbolULE::validate_byte(bytes.0)?;
100        length::FieldLengthULE::validate_byte(bytes.1)?;
101        Ok(())
102    }
103}
104
105impl From<(FieldSymbol, FieldLength)> for Field {
106    fn from(input: (FieldSymbol, FieldLength)) -> Self {
107        Self {
108            symbol: input.0,
109            length: input.1,
110        }
111    }
112}
113
114impl TryFrom<(FieldSymbol, usize)> for Field {
115    type Error = Error;
116    fn try_from(input: (FieldSymbol, usize)) -> Result<Self, Self::Error> {
117        let length = FieldLength::from_idx(input.1 as u8)
118            .map_err(|_| Self::Error::InvalidLength(input.0))?;
119        Ok(Self {
120            symbol: input.0,
121            length,
122        })
123    }
124}
125
126impl From<ErrorField> for Field {
127    /// Recover a [`Field`] (unstable) from an [`ErrorField`] (stable wrapper)
128    fn from(value: ErrorField) -> Self {
129        value.0
130    }
131}
132
133impl From<Field> for ErrorField {
134    /// Create an [`ErrorField`] (stable wrapper) from a [`Field`] (unstable)
135    fn from(value: Field) -> Self {
136        Self(value)
137    }
138}
139
140#[cfg(test)]
141mod test {
142    use super::*;
143    use crate::provider::fields::{Field, FieldLength, FieldSymbol, Second, Year};
144    use zerovec::ule::{AsULE, ULE};
145
146    #[test]
147    fn test_field_as_ule() {
148        let samples = [
149            (
150                Field::from((FieldSymbol::Minute, FieldLength::Two)),
151                [FieldSymbol::Minute.idx(), FieldLength::Two.idx()],
152            ),
153            (
154                Field::from((FieldSymbol::Year(Year::Calendar), FieldLength::Four)),
155                [
156                    FieldSymbol::Year(Year::Calendar).idx(),
157                    FieldLength::Four.idx(),
158                ],
159            ),
160            (
161                Field::from((FieldSymbol::Year(Year::Cyclic), FieldLength::Four)),
162                [
163                    FieldSymbol::Year(Year::Cyclic).idx(),
164                    FieldLength::Four.idx(),
165                ],
166            ),
167            (
168                Field::from((FieldSymbol::Second(Second::MillisInDay), FieldLength::One)),
169                [
170                    FieldSymbol::Second(Second::MillisInDay).idx(),
171                    FieldLength::One.idx(),
172                ],
173            ),
174        ];
175
176        for (ref_field, ref_bytes) in samples {
177            let ule = ref_field.to_unaligned();
178            assert_eq!(ULE::slice_as_bytes(&[ule]), ref_bytes);
179            let field = Field::from_unaligned(ule);
180            assert_eq!(field, ref_field);
181        }
182    }
183
184    #[test]
185    fn test_field_ule() {
186        let samples = [(
187            [
188                Field::from((FieldSymbol::Year(Year::Calendar), FieldLength::Four)),
189                Field::from((FieldSymbol::Second(Second::MillisInDay), FieldLength::One)),
190            ],
191            [
192                [
193                    FieldSymbol::Year(Year::Calendar).idx(),
194                    FieldLength::Four.idx(),
195                ],
196                [
197                    FieldSymbol::Second(Second::MillisInDay).idx(),
198                    FieldLength::One.idx(),
199                ],
200            ],
201        )];
202
203        for (ref_field, ref_bytes) in samples {
204            let mut bytes: Vec<u8> = vec![];
205            for item in ref_field.iter() {
206                let ule = item.to_unaligned();
207                bytes.extend(ULE::slice_as_bytes(&[ule]));
208            }
209
210            let mut bytes2: Vec<u8> = vec![];
211            for seq in ref_bytes.iter() {
212                bytes2.extend_from_slice(seq);
213            }
214
215            assert!(FieldULE::validate_bytes(&bytes).is_ok());
216            assert_eq!(bytes, bytes2);
217        }
218    }
219}