Skip to main content

ferroui_base/utilities/
date_time_format_info.rs

1//! Culture-specific date and time conventions: patterns, names and calendar
2//! rules (.NET `System.Globalization.DateTimeFormatInfo`).
3//!
4//! Only the conventions of the invariant culture are built in. The data of
5//! every other culture comes from the registered
6//! [`ICultureDataProvider`](super::ICultureDataProvider); a culture without
7//! data uses the data of its nearest parent that has some and finally the
8//! invariant data (.NET has data for every culture of the operating system).
9//! The calendar is always the Gregorian one.
10
11use super::i_culture_data_provider::find_culture_data;
12use super::{CultureInfo, DayOfWeek, GregorianCalendar};
13use std::borrow::Cow;
14use std::ops::Deref;
15use std::rc::Rc;
16
17/// The rule that determines the first week of a year
18/// (.NET `System.Globalization.CalendarWeekRule`).
19#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
20#[repr(u8)]
21pub enum CalendarWeekRule {
22    /// The first week starts on the first day of the year.
23    #[default]
24    FirstDay = 0,
25    /// The first week is the first one that lies completely in the year.
26    FirstFullWeek = 1,
27    /// The first week is the first one with four or more days in the year.
28    FirstFourDayWeek = 2,
29}
30
31const DAY_NAMES: [&str; 7] = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
32const ABBREVIATED_DAY_NAMES: [&str; 7] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
33const SHORTEST_DAY_NAMES: [&str; 7] = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
34const MONTH_NAMES: [&str; 12] = [
35    "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November",
36    "December",
37];
38const ABBREVIATED_MONTH_NAMES: [&str; 12] =
39    ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
40
41crate::ferro_thread_local! {
42    static INVARIANT: Rc<DateTimeFormatInfo> = Rc::new(DateTimeFormatInfo::new());
43}
44
45/// The date and time conventions of a culture.
46///
47/// The instances handed out by [`CultureInfo::date_time_format`] are shared
48/// and read-only; to change conventions clone the value, use the setters and
49/// attach it to a culture with [`CultureInfo::with_date_time_format`].
50#[derive(Clone, Debug, PartialEq, Eq)]
51pub struct DateTimeFormatInfo {
52    short_date_pattern: String,
53    long_date_pattern: String,
54    short_time_pattern: String,
55    long_time_pattern: String,
56    full_date_time_pattern: Option<String>,
57    month_day_pattern: String,
58    year_month_pattern: String,
59    am_designator: String,
60    pm_designator: String,
61    date_separator: String,
62    time_separator: String,
63    era_name: String,
64    first_day_of_week: DayOfWeek,
65    calendar_week_rule: CalendarWeekRule,
66    day_names: [String; 7],
67    abbreviated_day_names: [String; 7],
68    shortest_day_names: [String; 7],
69    month_names: [String; 13],
70    abbreviated_month_names: [String; 13],
71}
72
73fn day_strings(names: &[&str; 7]) -> [String; 7] {
74    std::array::from_fn(|index| names[index].to_owned())
75}
76
77fn month_strings(names: &[&str; 12]) -> [String; 13] {
78    std::array::from_fn(|index| names.get(index).map_or_else(String::new, |name| (*name).to_owned()))
79}
80
81impl DateTimeFormatInfo {
82    /// Conventions with the values of the invariant culture (C#
83    /// `new DateTimeFormatInfo()`); change them with the setters.
84    pub fn new() -> DateTimeFormatInfo {
85        DateTimeFormatInfo {
86            short_date_pattern: "MM/dd/yyyy".to_owned(),
87            long_date_pattern: "dddd, dd MMMM yyyy".to_owned(),
88            short_time_pattern: "HH:mm".to_owned(),
89            long_time_pattern: "HH:mm:ss".to_owned(),
90            full_date_time_pattern: None,
91            month_day_pattern: "MMMM dd".to_owned(),
92            year_month_pattern: "yyyy MMMM".to_owned(),
93            am_designator: "AM".to_owned(),
94            pm_designator: "PM".to_owned(),
95            date_separator: "/".to_owned(),
96            time_separator: ":".to_owned(),
97            era_name: "A.D.".to_owned(),
98            first_day_of_week: DayOfWeek::Sunday,
99            calendar_week_rule: CalendarWeekRule::FirstDay,
100            day_names: day_strings(&DAY_NAMES),
101            abbreviated_day_names: day_strings(&ABBREVIATED_DAY_NAMES),
102            shortest_day_names: day_strings(&SHORTEST_DAY_NAMES),
103            month_names: month_strings(&MONTH_NAMES),
104            abbreviated_month_names: month_strings(&ABBREVIATED_MONTH_NAMES),
105        }
106    }
107
108    /// The conventions of a culture without conventions of its own attached:
109    /// the data the registered provider has for the culture or the nearest
110    /// of its parents, else the invariant data.
111    pub(crate) fn for_culture(culture: &CultureInfo) -> Rc<DateTimeFormatInfo> {
112        find_culture_data(culture, |provider, name| provider.get_date_time_format(name)).unwrap_or_else(Self::invariant_info)
113    }
114
115    /// The conventions of the invariant culture.
116    pub fn invariant_info() -> Rc<DateTimeFormatInfo> {
117        INVARIANT.with(Rc::clone)
118    }
119
120    /// The conventions of the current culture.
121    pub fn current_info() -> Rc<DateTimeFormatInfo> {
122        CultureInfo::current_culture().date_time_format()
123    }
124
125    /// The calendar of the conventions; always the Gregorian one.
126    pub fn calendar(&self) -> GregorianCalendar {
127        GregorianCalendar
128    }
129
130    /// The pattern of a short date (format `"d"`).
131    pub fn short_date_pattern(&self) -> &str {
132        &self.short_date_pattern
133    }
134
135    /// Sets the pattern of a short date.
136    pub fn set_short_date_pattern(&mut self, value: impl Into<String>) {
137        self.short_date_pattern = value.into();
138    }
139
140    /// The pattern of a long date (format `"D"`).
141    pub fn long_date_pattern(&self) -> &str {
142        &self.long_date_pattern
143    }
144
145    /// Sets the pattern of a long date.
146    pub fn set_long_date_pattern(&mut self, value: impl Into<String>) {
147        self.long_date_pattern = value.into();
148    }
149
150    /// The pattern of a short time (format `"t"`).
151    pub fn short_time_pattern(&self) -> &str {
152        &self.short_time_pattern
153    }
154
155    /// Sets the pattern of a short time.
156    pub fn set_short_time_pattern(&mut self, value: impl Into<String>) {
157        self.short_time_pattern = value.into();
158    }
159
160    /// The pattern of a long time (format `"T"`).
161    pub fn long_time_pattern(&self) -> &str {
162        &self.long_time_pattern
163    }
164
165    /// Sets the pattern of a long time.
166    pub fn set_long_time_pattern(&mut self, value: impl Into<String>) {
167        self.long_time_pattern = value.into();
168    }
169
170    /// The pattern of a long date and long time (format `"F"`): the long
171    /// date pattern, a space and the long time pattern unless set explicitly.
172    pub fn full_date_time_pattern(&self) -> Cow<'_, str> {
173        match &self.full_date_time_pattern {
174            Some(pattern) => Cow::Borrowed(pattern),
175            None => Cow::Owned(format!("{} {}", self.long_date_pattern, self.long_time_pattern)),
176        }
177    }
178
179    /// Sets the pattern of a long date and long time.
180    pub fn set_full_date_time_pattern(&mut self, value: impl Into<String>) {
181        self.full_date_time_pattern = Some(value.into());
182    }
183
184    /// The pattern of a month and day (format `"M"`).
185    pub fn month_day_pattern(&self) -> &str {
186        &self.month_day_pattern
187    }
188
189    /// Sets the pattern of a month and day.
190    pub fn set_month_day_pattern(&mut self, value: impl Into<String>) {
191        self.month_day_pattern = value.into();
192    }
193
194    /// The pattern of a year and month (format `"Y"`).
195    pub fn year_month_pattern(&self) -> &str {
196        &self.year_month_pattern
197    }
198
199    /// Sets the pattern of a year and month.
200    pub fn set_year_month_pattern(&mut self, value: impl Into<String>) {
201        self.year_month_pattern = value.into();
202    }
203
204    /// The pattern of the round-trip format `"R"` (RFC 1123).
205    pub fn rfc1123_pattern(&self) -> &'static str {
206        "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'"
207    }
208
209    /// The pattern of the sortable format `"s"`.
210    pub fn sortable_date_time_pattern(&self) -> &'static str {
211        "yyyy'-'MM'-'dd'T'HH':'mm':'ss"
212    }
213
214    /// The pattern of the universal sortable format `"u"`.
215    pub fn universal_sortable_date_time_pattern(&self) -> &'static str {
216        "yyyy'-'MM'-'dd HH':'mm':'ss'Z'"
217    }
218
219    /// The designator of hours before noon.
220    pub fn am_designator(&self) -> &str {
221        &self.am_designator
222    }
223
224    /// Sets the designator of hours before noon.
225    pub fn set_am_designator(&mut self, value: impl Into<String>) {
226        self.am_designator = value.into();
227    }
228
229    /// The designator of hours after noon.
230    pub fn pm_designator(&self) -> &str {
231        &self.pm_designator
232    }
233
234    /// Sets the designator of hours after noon.
235    pub fn set_pm_designator(&mut self, value: impl Into<String>) {
236        self.pm_designator = value.into();
237    }
238
239    /// The text the `/` specifier of a custom format produces.
240    pub fn date_separator(&self) -> &str {
241        &self.date_separator
242    }
243
244    /// Sets the date separator.
245    pub fn set_date_separator(&mut self, value: impl Into<String>) {
246        self.date_separator = value.into();
247    }
248
249    /// The text the `:` specifier of a custom format produces.
250    pub fn time_separator(&self) -> &str {
251        &self.time_separator
252    }
253
254    /// Sets the time separator.
255    pub fn set_time_separator(&mut self, value: impl Into<String>) {
256        self.time_separator = value.into();
257    }
258
259    /// The first day of the week.
260    pub fn first_day_of_week(&self) -> DayOfWeek {
261        self.first_day_of_week
262    }
263
264    /// Sets the first day of the week.
265    pub fn set_first_day_of_week(&mut self, value: DayOfWeek) {
266        self.first_day_of_week = value;
267    }
268
269    /// The rule that determines the first week of a year.
270    pub fn calendar_week_rule(&self) -> CalendarWeekRule {
271        self.calendar_week_rule
272    }
273
274    /// Sets the rule that determines the first week of a year.
275    pub fn set_calendar_week_rule(&mut self, value: CalendarWeekRule) {
276        self.calendar_week_rule = value;
277    }
278
279    /// The full names of the days, indexed by [`DayOfWeek`] (Sunday first).
280    pub fn day_names(&self) -> &[String; 7] {
281        &self.day_names
282    }
283
284    /// Sets the full names of the days.
285    pub fn set_day_names(&mut self, value: [String; 7]) {
286        self.day_names = value;
287    }
288
289    /// The abbreviated names of the days, indexed by [`DayOfWeek`].
290    pub fn abbreviated_day_names(&self) -> &[String; 7] {
291        &self.abbreviated_day_names
292    }
293
294    /// Sets the abbreviated names of the days.
295    pub fn set_abbreviated_day_names(&mut self, value: [String; 7]) {
296        self.abbreviated_day_names = value;
297    }
298
299    /// The shortest unique names of the days, indexed by [`DayOfWeek`].
300    pub fn shortest_day_names(&self) -> &[String; 7] {
301        &self.shortest_day_names
302    }
303
304    /// Sets the shortest names of the days.
305    pub fn set_shortest_day_names(&mut self, value: [String; 7]) {
306        self.shortest_day_names = value;
307    }
308
309    /// The full names of the months; the thirteenth entry is empty, as in .NET.
310    pub fn month_names(&self) -> &[String; 13] {
311        &self.month_names
312    }
313
314    /// Sets the full names of the months.
315    pub fn set_month_names(&mut self, value: [String; 13]) {
316        self.month_names = value;
317    }
318
319    /// The abbreviated names of the months; the thirteenth entry is empty.
320    pub fn abbreviated_month_names(&self) -> &[String; 13] {
321        &self.abbreviated_month_names
322    }
323
324    /// Sets the abbreviated names of the months.
325    pub fn set_abbreviated_month_names(&mut self, value: [String; 13]) {
326        self.abbreviated_month_names = value;
327    }
328
329    /// The full name of a day.
330    pub fn get_day_name(&self, day_of_week: DayOfWeek) -> &str {
331        &self.day_names[day_of_week as usize]
332    }
333
334    /// The abbreviated name of a day.
335    pub fn get_abbreviated_day_name(&self, day_of_week: DayOfWeek) -> &str {
336        &self.abbreviated_day_names[day_of_week as usize]
337    }
338
339    /// The shortest name of a day.
340    pub fn get_shortest_day_name(&self, day_of_week: DayOfWeek) -> &str {
341        &self.shortest_day_names[day_of_week as usize]
342    }
343
344    /// The full name of a month, 1..=13. Panics for other values.
345    #[track_caller]
346    pub fn get_month_name(&self, month: i32) -> &str {
347        assert!((1..=13).contains(&month), "Valid values are between 1 and 13, inclusive. (Parameter 'month')");
348        &self.month_names[(month - 1) as usize]
349    }
350
351    /// The abbreviated name of a month, 1..=13. Panics for other values.
352    #[track_caller]
353    pub fn get_abbreviated_month_name(&self, month: i32) -> &str {
354        assert!((1..=13).contains(&month), "Valid values are between 1 and 13, inclusive. (Parameter 'month')");
355        &self.abbreviated_month_names[(month - 1) as usize]
356    }
357
358    /// The name of the only era of the Gregorian calendar (the `g` specifier).
359    pub fn get_era_name(&self, _era: i32) -> &str {
360        &self.era_name
361    }
362
363    /// Sets the name of the era.
364    pub fn set_era_name(&mut self, value: impl Into<String>) {
365        self.era_name = value.into();
366    }
367}
368
369impl Default for DateTimeFormatInfo {
370    fn default() -> Self {
371        Self::new()
372    }
373}
374
375/// The conventions a [`DateTimeFormatProvider`] hands out: borrowed from the
376/// provider or shared.
377pub enum DateTimeFormatInfoRef<'a> {
378    /// Conventions owned by the provider.
379    Borrowed(&'a DateTimeFormatInfo),
380    /// Shared conventions.
381    Shared(Rc<DateTimeFormatInfo>),
382}
383
384impl Deref for DateTimeFormatInfoRef<'_> {
385    type Target = DateTimeFormatInfo;
386    fn deref(&self) -> &DateTimeFormatInfo {
387        match self {
388            DateTimeFormatInfoRef::Borrowed(info) => info,
389            DateTimeFormatInfoRef::Shared(info) => info,
390        }
391    }
392}
393
394/// Something that supplies date and time conventions: a [`CultureInfo`] or a
395/// [`DateTimeFormatInfo`] (the role of `IFormatProvider` in the .NET date and
396/// time members).
397pub trait DateTimeFormatProvider {
398    /// The conventions to format and parse with.
399    fn get_date_time_format(&self) -> DateTimeFormatInfoRef<'_>;
400}
401
402impl DateTimeFormatProvider for DateTimeFormatInfo {
403    fn get_date_time_format(&self) -> DateTimeFormatInfoRef<'_> {
404        DateTimeFormatInfoRef::Borrowed(self)
405    }
406}
407
408impl DateTimeFormatProvider for Rc<DateTimeFormatInfo> {
409    fn get_date_time_format(&self) -> DateTimeFormatInfoRef<'_> {
410        DateTimeFormatInfoRef::Borrowed(self)
411    }
412}
413
414impl DateTimeFormatProvider for CultureInfo {
415    fn get_date_time_format(&self) -> DateTimeFormatInfoRef<'_> {
416        DateTimeFormatInfoRef::Shared(self.date_time_format())
417    }
418}
419
420impl<T: DateTimeFormatProvider + ?Sized> DateTimeFormatProvider for &T {
421    fn get_date_time_format(&self) -> DateTimeFormatInfoRef<'_> {
422        (**self).get_date_time_format()
423    }
424}