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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
// 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 crate::provider::calendar::*;
use crate::{calendar, options::length, raw};
use crate::{input::DateInput, DateTimeError, FormattedDateTime};
use alloc::string::String;
use icu_calendar::any_calendar::{AnyCalendar, AnyCalendarKind};
use icu_calendar::provider::{
    JapaneseErasV1Marker, JapaneseExtendedErasV1Marker, WeekDataV1Marker,
};
use icu_calendar::Date;
use icu_decimal::provider::DecimalSymbolsV1Marker;
use icu_plurals::provider::OrdinalV1Marker;
use icu_provider::prelude::*;
use icu_provider::DataLocale;
use writeable::Writeable;

/// [`DateFormatter`] is a formatter capable of formatting
/// dates from any calendar, selected at runtime. For the difference between this and [`TypedDateFormatter`](crate::TypedDateFormatter),
/// please read the [crate root docs][crate].
///
/// When constructed, it uses data from the [data provider], selected locale and provided options to
/// collect all data necessary to format any dates into that locale.
///
/// For that reason, one should think of the process of formatting a date in two steps - first, a computational
/// heavy construction of [`DateFormatter`], and then fast formatting of [`DateTime`](icu_calendar::DateTime) data using the instance.
///
/// [`icu_datetime`]: crate
///
/// # Examples
///
/// ```
/// use icu::calendar::{any_calendar::AnyCalendar, Date, Gregorian};
/// use icu::datetime::{options::length, DateFormatter};
/// use icu::locid::locale;
/// use std::str::FromStr;
/// use writeable::assert_writeable_eq;
///
/// let length = length::Date::Medium;
///
/// let df = DateFormatter::try_new_with_length(
///     &locale!("en-u-ca-gregory").into(),
///     length,
/// )
/// .expect("Failed to create TypedDateFormatter instance.");
///
/// let date =
///     Date::try_new_iso_date(2020, 9, 1).expect("Failed to construct Date.");
/// let any_date = date.to_any();
///
/// assert_writeable_eq!(
///     df.format(&any_date).expect("Calendars should match"),
///     "Sep 1, 2020"
/// );
/// ```
///
/// This model replicates that of `ICU` and `ECMA402`.
///
/// [data provider]: icu_provider
#[derive(Debug)]
pub struct DateFormatter(pub(crate) raw::DateFormatter, pub(crate) AnyCalendar);

impl DateFormatter {
    /// Construct a new [`DateFormatter`] from compiled data.
    ///
    /// This method will pick the calendar off of the locale; and if unspecified or unknown will fall back to the default
    /// calendar for the locale. See [`AnyCalendarKind`] for a list of supported calendars.
    ///
    /// ✨ *Enabled with the `compiled_data` Cargo feature.*
    ///
    /// [📚 Help choosing a constructor](icu_provider::constructors)
    ///
    /// # Examples
    ///
    /// ```
    /// use icu::calendar::{any_calendar::AnyCalendar, Date, Gregorian};
    /// use icu::datetime::{options::length, DateFormatter};
    /// use icu::locid::locale;
    /// use icu_provider::any::DynamicDataProviderAnyMarkerWrap;
    /// use std::str::FromStr;
    /// use writeable::assert_writeable_eq;
    ///
    /// let length = length::Date::Medium;
    /// let locale = locale!("en-u-ca-gregory");
    ///
    /// let df = DateFormatter::try_new_with_length(&locale.into(), length)
    ///     .expect("Failed to create TypedDateFormatter instance.");
    ///
    /// let datetime =
    ///     Date::try_new_iso_date(2020, 9, 1).expect("Failed to construct Date.");
    /// let any_datetime = datetime.to_any();
    ///
    /// assert_writeable_eq!(
    ///     df.format(&any_datetime).expect("Calendars should match"),
    ///     "Sep 1, 2020"
    /// );
    /// ```
    #[inline(never)]
    #[cfg(feature = "compiled_data")]
    pub fn try_new_with_length(
        locale: &DataLocale,
        length: length::Date,
    ) -> Result<Self, DateTimeError> {
        let calendar = AnyCalendar::new_for_locale(locale);
        let kind = calendar.kind();

        Ok(Self(
            raw::DateFormatter::try_new(
                calendar::load_lengths_for_any_calendar_kind(
                    &crate::provider::Baked,
                    locale,
                    kind,
                )?,
                || {
                    calendar::load_symbols_for_any_calendar_kind(
                        &crate::provider::Baked,
                        locale,
                        kind,
                    )
                },
                locale,
                length,
            )?,
            calendar,
        ))
    }

    #[doc = icu_provider::gen_any_buffer_unstable_docs!(ANY, Self::try_new_with_length)]
    #[inline]
    pub fn try_new_with_length_with_any_provider(
        provider: &impl AnyProvider,
        locale: &DataLocale,
        length: length::Date,
    ) -> Result<Self, DateTimeError> {
        let downcasting = provider.as_downcasting();
        Self::try_new_with_length_unstable(&downcasting, locale, length)
    }

    #[doc = icu_provider::gen_any_buffer_unstable_docs!(BUFFER, Self::try_new_with_length)]
    #[inline]
    #[cfg(feature = "serde")]
    pub fn try_new_with_length_with_buffer_provider(
        provider: &impl BufferProvider,
        locale: &DataLocale,
        length: length::Date,
    ) -> Result<Self, DateTimeError> {
        let deserializing = provider.as_deserializing();
        Self::try_new_with_length_unstable(&deserializing, locale, length)
    }

    #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::try_new_with_length)]
    #[inline(never)]
    pub fn try_new_with_length_unstable<P>(
        provider: &P,
        locale: &DataLocale,
        length: length::Date,
    ) -> Result<Self, DateTimeError>
    where
        P: DataProvider<TimeSymbolsV1Marker>
            + DataProvider<TimeLengthsV1Marker>
            + DataProvider<OrdinalV1Marker>
            + DataProvider<WeekDataV1Marker>
            + DataProvider<DecimalSymbolsV1Marker>
            + DataProvider<BuddhistDateLengthsV1Marker>
            + DataProvider<BuddhistDateSymbolsV1Marker>
            + DataProvider<ChineseDateLengthsV1Marker>
            + DataProvider<ChineseDateSymbolsV1Marker>
            + DataProvider<CopticDateLengthsV1Marker>
            + DataProvider<CopticDateSymbolsV1Marker>
            + DataProvider<DangiDateLengthsV1Marker>
            + DataProvider<DangiDateSymbolsV1Marker>
            + DataProvider<EthiopianDateLengthsV1Marker>
            + DataProvider<EthiopianDateSymbolsV1Marker>
            + DataProvider<GregorianDateLengthsV1Marker>
            + DataProvider<GregorianDateSymbolsV1Marker>
            + DataProvider<HebrewDateLengthsV1Marker>
            + DataProvider<HebrewDateSymbolsV1Marker>
            + DataProvider<IndianDateLengthsV1Marker>
            + DataProvider<IndianDateSymbolsV1Marker>
            + DataProvider<IslamicDateLengthsV1Marker>
            + DataProvider<IslamicDateSymbolsV1Marker>
            + DataProvider<JapaneseDateLengthsV1Marker>
            + DataProvider<JapaneseDateSymbolsV1Marker>
            + DataProvider<JapaneseErasV1Marker>
            + DataProvider<JapaneseExtendedDateLengthsV1Marker>
            + DataProvider<JapaneseExtendedDateSymbolsV1Marker>
            + DataProvider<JapaneseExtendedErasV1Marker>
            + DataProvider<PersianDateLengthsV1Marker>
            + DataProvider<PersianDateSymbolsV1Marker>
            + DataProvider<RocDateLengthsV1Marker>
            + DataProvider<RocDateSymbolsV1Marker>
            + ?Sized,
    {
        let calendar = AnyCalendar::try_new_for_locale_unstable(provider, locale)?;
        let kind = calendar.kind();

        Ok(Self(
            raw::DateFormatter::try_new_unstable(
                provider,
                calendar::load_lengths_for_any_calendar_kind(provider, locale, kind)?,
                || calendar::load_symbols_for_any_calendar_kind(provider, locale, kind),
                locale,
                length,
            )?,
            calendar,
        ))
    }

    /// Takes a [`DateInput`] implementer and returns an instance of a [`FormattedDateTime`]
    /// that contains all information necessary to display a formatted date and operate on it.
    ///
    /// This function will fail if the date passed in uses a different calendar than that of the
    /// AnyCalendar. Please convert dates before passing them in if necessary. This function
    /// will automatically convert and format dates that are associated with the ISO calendar.
    #[inline]
    pub fn format<'l, T>(&'l self, value: &T) -> Result<FormattedDateTime<'l>, DateTimeError>
    where
        T: DateInput<Calendar = AnyCalendar>,
    {
        if let Some(converted) = self.convert_if_necessary(value)? {
            Ok(self.0.format(&converted))
        } else {
            Ok(self.0.format(value))
        }
    }

    /// Takes a [`DateInput`] implementer and returns it formatted as a string.
    ///
    /// This function will fail if the date passed in uses a different calendar than that of the
    /// AnyCalendar. Please convert dates before passing them in if necessary. This function
    /// will automatically convert and format dates that are associated with the ISO calendar.
    #[inline]
    pub fn format_to_string(
        &self,
        value: &impl DateInput<Calendar = AnyCalendar>,
    ) -> Result<String, DateTimeError> {
        Ok(self.format(value)?.write_to_string().into_owned())
    }

    /// Converts a date to the correct calendar if necessary
    ///
    /// Returns `Err` if the date is not ISO or compatible with the current calendar, returns `Ok(None)`
    /// if the date is compatible with the current calendar and doesn't need conversion
    fn convert_if_necessary<'a>(
        &'a self,
        value: &impl DateInput<Calendar = AnyCalendar>,
    ) -> Result<Option<Date<icu_calendar::Ref<'a, AnyCalendar>>>, DateTimeError> {
        let this_calendar = self.1.kind();
        let date_calendar = value.any_calendar_kind();

        if Some(this_calendar) != date_calendar {
            if date_calendar != Some(AnyCalendarKind::Iso) {
                return Err(DateTimeError::MismatchedAnyCalendar(
                    this_calendar,
                    date_calendar,
                ));
            }
            let date = value.to_iso().to_any();
            let converted = self.1.convert_any_date(&date);
            Ok(Some(converted))
        } else {
            Ok(None)
        }
    }
}

#[test]
#[cfg(feature = "serde")]
fn serde_constructor() {
    use icu::calendar::Date;
    use icu::datetime::{options::length, DateFormatter};
    use icu::locid::locale;
    use writeable::assert_writeable_eq;

    let provider = icu_provider_blob::BlobDataProvider::try_new_from_static_blob(include_bytes!(
        "../../tests/data/blob.postcard"
    ))
    .unwrap();

    let df = DateFormatter::try_new_with_length_with_buffer_provider(
        &provider,
        &locale!("en").into(),
        length::Date::Medium,
    )
    .unwrap();

    assert_writeable_eq!(
        df.format(&Date::try_new_iso_date(2020, 9, 1).unwrap().to_any())
            .unwrap(),
        "Sep 1, 2020"
    );
}