chrono/format/
strftime.rs

1// This is a part of Chrono.
2// See README.md and LICENSE.txt for details.
3
4/*!
5`strftime`/`strptime`-inspired date and time formatting syntax.
6
7## Specifiers
8
9The following specifiers are available both to formatting and parsing.
10
11| Spec. | Example  | Description                                                                |
12|-------|----------|----------------------------------------------------------------------------|
13|       |          | **DATE SPECIFIERS:**                                                       |
14| `%Y`  | `2001`   | The full proleptic Gregorian year, zero-padded to 4 digits. chrono supports years from -262144 to 262143. Note: years before 1 BCE or after 9999 CE, require an initial sign (+/-).|
15| `%C`  | `20`     | The proleptic Gregorian year divided by 100, zero-padded to 2 digits. [^1] |
16| `%y`  | `01`     | The proleptic Gregorian year modulo 100, zero-padded to 2 digits. [^1]     |
17|       |          |                                                                            |
18| `%m`  | `07`     | Month number (01--12), zero-padded to 2 digits.                            |
19| `%b`  | `Jul`    | Abbreviated month name. Always 3 letters.                                  |
20| `%B`  | `July`   | Full month name. Also accepts corresponding abbreviation in parsing.       |
21| `%h`  | `Jul`    | Same as `%b`.                                                              |
22|       |          |                                                                            |
23| `%d`  | `08`     | Day number (01--31), zero-padded to 2 digits.                              |
24| `%e`  | ` 8`     | Same as `%d` but space-padded. Same as `%_d`.                              |
25|       |          |                                                                            |
26| `%a`  | `Sun`    | Abbreviated weekday name. Always 3 letters.                                |
27| `%A`  | `Sunday` | Full weekday name. Also accepts corresponding abbreviation in parsing.     |
28| `%w`  | `0`      | Sunday = 0, Monday = 1, ..., Saturday = 6.                                 |
29| `%u`  | `7`      | Monday = 1, Tuesday = 2, ..., Sunday = 7. (ISO 8601)                       |
30|       |          |                                                                            |
31| `%U`  | `28`     | Week number starting with Sunday (00--53), zero-padded to 2 digits. [^2]   |
32| `%W`  | `27`     | Same as `%U`, but week 1 starts with the first Monday in that year instead.|
33|       |          |                                                                            |
34| `%G`  | `2001`   | Same as `%Y` but uses the year number in ISO 8601 week date. [^3]          |
35| `%g`  | `01`     | Same as `%y` but uses the year number in ISO 8601 week date. [^3]          |
36| `%V`  | `27`     | Same as `%U` but uses the week number in ISO 8601 week date (01--53). [^3] |
37|       |          |                                                                            |
38| `%j`  | `189`    | Day of the year (001--366), zero-padded to 3 digits.                       |
39|       |          |                                                                            |
40| `%D`  | `07/08/01`    | Month-day-year format. Same as `%m/%d/%y`.                            |
41| `%x`  | `07/08/01`    | Locale's date representation (e.g., 12/31/99).                        |
42| `%F`  | `2001-07-08`  | Year-month-day format (ISO 8601). Same as `%Y-%m-%d`.                 |
43| `%v`  | ` 8-Jul-2001` | Day-month-year format. Same as `%e-%b-%Y`.                            |
44|       |          |                                                                            |
45|       |          | **TIME SPECIFIERS:**                                                       |
46| `%H`  | `00`     | Hour number (00--23), zero-padded to 2 digits.                             |
47| `%k`  | ` 0`     | Same as `%H` but space-padded. Same as `%_H`.                              |
48| `%I`  | `12`     | Hour number in 12-hour clocks (01--12), zero-padded to 2 digits.           |
49| `%l`  | `12`     | Same as `%I` but space-padded. Same as `%_I`.                              |
50|       |          |                                                                            |
51| `%P`  | `am`     | `am` or `pm` in 12-hour clocks.                                            |
52| `%p`  | `AM`     | `AM` or `PM` in 12-hour clocks.                                            |
53|       |          |                                                                            |
54| `%M`  | `34`     | Minute number (00--59), zero-padded to 2 digits.                           |
55| `%S`  | `60`     | Second number (00--60), zero-padded to 2 digits. [^4]                      |
56| `%f`  | `26490000`    | Number of nanoseconds since last whole second. [^7]                   |
57| `%.f` | `.026490`| Decimal fraction of a second. Consumes the leading dot. [^7]               |
58| `%.3f`| `.026`        | Decimal fraction of a second with a fixed length of 3.                |
59| `%.6f`| `.026490`     | Decimal fraction of a second with a fixed length of 6.                |
60| `%.9f`| `.026490000`  | Decimal fraction of a second with a fixed length of 9.                |
61| `%3f` | `026`         | Decimal fraction of a second like `%.3f` but without the leading dot. |
62| `%6f` | `026490`      | Decimal fraction of a second like `%.6f` but without the leading dot. |
63| `%9f` | `026490000`   | Decimal fraction of a second like `%.9f` but without the leading dot. |
64|       |               |                                                                       |
65| `%R`  | `00:34`       | Hour-minute format. Same as `%H:%M`.                                  |
66| `%T`  | `00:34:60`    | Hour-minute-second format. Same as `%H:%M:%S`.                        |
67| `%X`  | `00:34:60`    | Locale's time representation (e.g., 23:13:48).                        |
68| `%r`  | `12:34:60 AM` | Locale's 12 hour clock time. (e.g., 11:11:04 PM). Falls back to `%X` if the locale does not have a 12 hour clock format. |
69|       |          |                                                                            |
70|       |          | **TIME ZONE SPECIFIERS:**                                                  |
71| `%Z`  | `ACST`   | Local time zone name. Skips all non-whitespace characters during parsing. Identical to `%:z` when formatting. [^8] |
72| `%z`  | `+0930`  | Offset from the local time to UTC (with UTC being `+0000`).                |
73| `%:z` | `+09:30` | Same as `%z` but with a colon.                                             |
74|`%::z`|`+09:30:00`| Offset from the local time to UTC with seconds.                            |
75|`%:::z`| `+09`    | Offset from the local time to UTC without minutes.                         |
76| `%#z` | `+09`    | *Parsing only:* Same as `%z` but allows minutes to be missing or present.  |
77|       |          |                                                                            |
78|       |          | **DATE & TIME SPECIFIERS:**                                                |
79|`%c`|`Sun Jul  8 00:34:60 2001`|Locale's date and time (e.g., Thu Mar  3 23:05:25 2005).       |
80| `%+`  | `2001-07-08T00:34:60.026490+09:30` | ISO 8601 / RFC 3339 date & time format. [^5]     |
81|       |               |                                                                       |
82| `%s`  | `994518299`   | UNIX timestamp, the number of seconds since 1970-01-01 00:00 UTC. [^6]|
83|       |          |                                                                            |
84|       |          | **SPECIAL SPECIFIERS:**                                                    |
85| `%t`  |          | Literal tab (`\t`).                                                        |
86| `%n`  |          | Literal newline (`\n`).                                                    |
87| `%%`  |          | Literal percent sign.                                                      |
88
89It is possible to override the default padding behavior of numeric specifiers `%?`.
90This is not allowed for other specifiers and will result in the `BAD_FORMAT` error.
91
92Modifier | Description
93-------- | -----------
94`%-?`    | Suppresses any padding including spaces and zeroes. (e.g. `%j` = `012`, `%-j` = `12`)
95`%_?`    | Uses spaces as a padding. (e.g. `%j` = `012`, `%_j` = ` 12`)
96`%0?`    | Uses zeroes as a padding. (e.g. `%e` = ` 9`, `%0e` = `09`)
97
98Notes:
99
100[^1]: `%C`, `%y`:
101   This is floor division, so 100 BCE (year number -99) will print `-1` and `99` respectively.
102   For `%y`, values greater or equal to 70 are interpreted as being in the 20th century,
103   values smaller than 70 in the 21st century.
104
105[^2]: `%U`:
106   Week 1 starts with the first Sunday in that year.
107   It is possible to have week 0 for days before the first Sunday.
108
109[^3]: `%G`, `%g`, `%V`:
110   Week 1 is the first week with at least 4 days in that year.
111   Week 0 does not exist, so this should be used with `%G` or `%g`.
112
113[^4]: `%S`:
114   It accounts for leap seconds, so `60` is possible.
115
116[^5]: `%+`: Same as `%Y-%m-%dT%H:%M:%S%.f%:z`, i.e. 0, 3, 6 or 9 fractional
117   digits for seconds and colons in the time zone offset.
118   <br>
119   <br>
120   This format also supports having a `Z` or `UTC` in place of `%:z`. They
121   are equivalent to `+00:00`.
122   <br>
123   <br>
124   Note that all `T`, `Z`, and `UTC` are parsed case-insensitively.
125   <br>
126   <br>
127   The typical `strftime` implementations have different (and locale-dependent)
128   formats for this specifier. While Chrono's format for `%+` is far more
129   stable, it is best to avoid this specifier if you want to control the exact
130   output.
131
132[^6]: `%s`:
133   This is not padded and can be negative.
134   For the purpose of Chrono, it only accounts for non-leap seconds
135   so it slightly differs from ISO C `strftime` behavior.
136
137[^7]: `%f`, `%.f`:
138   <br>
139   `%f` and `%.f` are notably different formatting specifiers.<br>
140   `%f` counts the number of nanoseconds since the last whole second, while `%.f` is a fraction of a
141   second.<br>
142   Example: 7μs is formatted as `7000` with `%f`, and formatted as `.000007` with `%.f`.
143
144[^8]: `%Z`:
145   Since `chrono` is not aware of timezones beyond their offsets, this specifier
146   **only prints the offset** when used for formatting. The timezone abbreviation
147   will NOT be printed. See [this issue](https://github.com/chronotope/chrono/issues/960)
148   for more information.
149   <br>
150   <br>
151   Offset will not be populated from the parsed data, nor will it be validated.
152   Timezone is completely ignored. Similar to the glibc `strptime` treatment of
153   this format code.
154   <br>
155   <br>
156   It is not possible to reliably convert from an abbreviation to an offset,
157   for example CDT can mean either Central Daylight Time (North America) or
158   China Daylight Time.
159*/
160
161#[cfg(feature = "alloc")]
162extern crate alloc;
163
164use super::{fixed, internal_fixed, num, num0, nums};
165#[cfg(feature = "unstable-locales")]
166use super::{locales, Locale};
167use super::{Fixed, InternalInternal, Item, Numeric, Pad};
168#[cfg(any(feature = "alloc", feature = "std"))]
169use super::{ParseError, BAD_FORMAT};
170#[cfg(all(feature = "alloc", not(feature = "std"), not(test)))]
171use alloc::vec::Vec;
172
173/// Parsing iterator for `strftime`-like format strings.
174///
175/// See the [`format::strftime` module](crate::format::strftime) for supported formatting
176/// specifiers.
177///
178/// `StrftimeItems` is used in combination with more low-level methods such as [`format::parse()`]
179/// or [`format_with_items`].
180///
181/// If formatting or parsing date and time values is not performance-critical, the methods
182/// [`parse_from_str`] and [`format`] on types such as [`DateTime`](crate::DateTime) are easier to
183/// use.
184///
185/// [`format`]: crate::DateTime::format
186/// [`format_with_items`]: crate::DateTime::format
187/// [`parse_from_str`]: crate::DateTime::parse_from_str
188/// [`DateTime`]: crate::DateTime
189/// [`format::parse()`]: crate::format::parse()
190#[derive(Clone, Debug)]
191pub struct StrftimeItems<'a> {
192    /// Remaining portion of the string.
193    remainder: &'a str,
194    /// If the current specifier is composed of multiple formatting items (e.g. `%+`),
195    /// `queue` stores a slice of `Item`s that have to be returned one by one.
196    queue: &'static [Item<'static>],
197    #[cfg(feature = "unstable-locales")]
198    locale_str: &'a str,
199    #[cfg(feature = "unstable-locales")]
200    locale: Option<Locale>,
201}
202
203impl<'a> StrftimeItems<'a> {
204    /// Creates a new parsing iterator from a `strftime`-like format string.
205    ///
206    /// # Errors
207    ///
208    /// While iterating [`Item::Error`] will be returned if the format string contains an invalid
209    /// or unrecognized formatting specifier.
210    ///
211    /// # Example
212    ///
213    /// ```
214    /// use chrono::format::*;
215    ///
216    /// let strftime_parser = StrftimeItems::new("%F"); // %F: year-month-day (ISO 8601)
217    ///
218    /// const ISO8601_YMD_ITEMS: &[Item<'static>] = &[
219    ///     Item::Numeric(Numeric::Year, Pad::Zero),
220    ///     Item::Literal("-"),
221    ///     Item::Numeric(Numeric::Month, Pad::Zero),
222    ///     Item::Literal("-"),
223    ///     Item::Numeric(Numeric::Day, Pad::Zero),
224    /// ];
225    /// assert!(strftime_parser.eq(ISO8601_YMD_ITEMS.iter().cloned()));
226    /// ```
227    #[must_use]
228    pub const fn new(s: &'a str) -> StrftimeItems<'a> {
229        #[cfg(not(feature = "unstable-locales"))]
230        {
231            StrftimeItems { remainder: s, queue: &[] }
232        }
233        #[cfg(feature = "unstable-locales")]
234        {
235            StrftimeItems { remainder: s, queue: &[], locale_str: "", locale: None }
236        }
237    }
238
239    /// Creates a new parsing iterator from a `strftime`-like format string, with some formatting
240    /// specifiers adjusted to match [`Locale`].
241    ///
242    /// Note: `StrftimeItems::new_with_locale` only localizes the *format*. You usually want to
243    /// combine it with other locale-aware methods such as
244    /// [`DateTime::format_localized_with_items`] to get things like localized month or day names.
245    ///
246    /// The `%x` formatting specifier will use the local date format, `%X` the local time format,
247    ///  and `%c` the local format for date and time.
248    /// `%r` will use the local 12-hour clock format (e.g., 11:11:04 PM). Not all locales have such
249    /// a format, in which case we fall back to a 24-hour clock (`%X`).
250    ///
251    /// See the [`format::strftime` module](crate::format::strftime) for all supported formatting
252    /// specifiers.
253    ///
254    ///  [`DateTime::format_localized_with_items`]: crate::DateTime::format_localized_with_items
255    ///
256    /// # Errors
257    ///
258    /// While iterating [`Item::Error`] will be returned if the format string contains an invalid
259    /// or unrecognized formatting specifier.
260    ///
261    /// # Example
262    ///
263    /// ```
264    /// # #[cfg(feature = "alloc")] {
265    /// use chrono::format::{Locale, StrftimeItems};
266    /// use chrono::{FixedOffset, TimeZone};
267    ///
268    /// let dt = FixedOffset::east_opt(9 * 60 * 60)
269    ///     .unwrap()
270    ///     .with_ymd_and_hms(2023, 7, 11, 0, 34, 59)
271    ///     .unwrap();
272    ///
273    /// // Note: you usually want to combine `StrftimeItems::new_with_locale` with other
274    /// // locale-aware methods such as `DateTime::format_localized_with_items`.
275    /// // We use the regular `format_with_items` to show only how the formatting changes.
276    ///
277    /// let fmtr = dt.format_with_items(StrftimeItems::new_with_locale("%x", Locale::en_US));
278    /// assert_eq!(fmtr.to_string(), "07/11/2023");
279    /// let fmtr = dt.format_with_items(StrftimeItems::new_with_locale("%x", Locale::ko_KR));
280    /// assert_eq!(fmtr.to_string(), "2023년 07월 11일");
281    /// let fmtr = dt.format_with_items(StrftimeItems::new_with_locale("%x", Locale::ja_JP));
282    /// assert_eq!(fmtr.to_string(), "2023年07月11日");
283    /// # }
284    /// ```
285    #[cfg(feature = "unstable-locales")]
286    #[must_use]
287    pub const fn new_with_locale(s: &'a str, locale: Locale) -> StrftimeItems<'a> {
288        StrftimeItems { remainder: s, queue: &[], locale_str: "", locale: Some(locale) }
289    }
290
291    /// Parse format string into a `Vec` of formatting [`Item`]'s.
292    ///
293    /// If you need to format or parse multiple values with the same format string, it is more
294    /// efficient to convert it to a `Vec` of formatting [`Item`]'s than to re-parse the format
295    /// string on every use.
296    ///
297    /// The `format_with_items` methods on [`DateTime`], [`NaiveDateTime`], [`NaiveDate`] and
298    /// [`NaiveTime`] accept the result for formatting. [`format::parse()`] can make use of it for
299    /// parsing.
300    ///
301    /// [`DateTime`]: crate::DateTime::format_with_items
302    /// [`NaiveDateTime`]: crate::NaiveDateTime::format_with_items
303    /// [`NaiveDate`]: crate::NaiveDate::format_with_items
304    /// [`NaiveTime`]: crate::NaiveTime::format_with_items
305    /// [`format::parse()`]: crate::format::parse()
306    ///
307    /// # Errors
308    ///
309    /// Returns an error if the format string contains an invalid or unrecognized formatting
310    /// specifier.
311    ///
312    /// # Example
313    ///
314    /// ```
315    /// use chrono::format::{parse, Parsed, StrftimeItems};
316    /// use chrono::NaiveDate;
317    ///
318    /// let fmt_items = StrftimeItems::new("%e %b %Y %k.%M").parse()?;
319    /// let datetime = NaiveDate::from_ymd_opt(2023, 7, 11).unwrap().and_hms_opt(9, 0, 0).unwrap();
320    ///
321    /// // Formatting
322    /// assert_eq!(
323    ///     datetime.format_with_items(fmt_items.as_slice().iter()).to_string(),
324    ///     "11 Jul 2023  9.00"
325    /// );
326    ///
327    /// // Parsing
328    /// let mut parsed = Parsed::new();
329    /// parse(&mut parsed, "11 Jul 2023  9.00", fmt_items.as_slice().iter())?;
330    /// let parsed_dt = parsed.to_naive_datetime_with_offset(0)?;
331    /// assert_eq!(parsed_dt, datetime);
332    /// # Ok::<(), chrono::ParseError>(())
333    /// ```
334    #[cfg(any(feature = "alloc", feature = "std"))]
335    pub fn parse(self) -> Result<Vec<Item<'a>>, ParseError> {
336        self.into_iter()
337            .map(|item| match item == Item::Error {
338                false => Ok(item),
339                true => Err(BAD_FORMAT),
340            })
341            .collect()
342    }
343
344    /// Parse format string into a `Vec` of [`Item`]'s that contain no references to slices of the
345    /// format string.
346    ///
347    /// A `Vec` created with [`StrftimeItems::parse`] contains references to the format string,
348    /// binding the lifetime of the `Vec` to that string. [`StrftimeItems::parse_to_owned`] will
349    /// convert the references to owned types.
350    ///
351    /// # Errors
352    ///
353    /// Returns an error if the format string contains an invalid or unrecognized formatting
354    /// specifier.
355    ///
356    /// # Example
357    ///
358    /// ```
359    /// use chrono::format::{Item, ParseError, StrftimeItems};
360    /// use chrono::NaiveDate;
361    ///
362    /// fn format_items(date_fmt: &str, time_fmt: &str) -> Result<Vec<Item<'static>>, ParseError> {
363    ///     // `fmt_string` is dropped at the end of this function.
364    ///     let fmt_string = format!("{} {}", date_fmt, time_fmt);
365    ///     StrftimeItems::new(&fmt_string).parse_to_owned()
366    /// }
367    ///
368    /// let fmt_items = format_items("%e %b %Y", "%k.%M")?;
369    /// let datetime = NaiveDate::from_ymd_opt(2023, 7, 11).unwrap().and_hms_opt(9, 0, 0).unwrap();
370    ///
371    /// assert_eq!(
372    ///     datetime.format_with_items(fmt_items.as_slice().iter()).to_string(),
373    ///     "11 Jul 2023  9.00"
374    /// );
375    /// # Ok::<(), ParseError>(())
376    /// ```
377    #[cfg(any(feature = "alloc", feature = "std"))]
378    pub fn parse_to_owned(self) -> Result<Vec<Item<'static>>, ParseError> {
379        self.into_iter()
380            .map(|item| match item == Item::Error {
381                false => Ok(item.to_owned()),
382                true => Err(BAD_FORMAT),
383            })
384            .collect()
385    }
386}
387
388const HAVE_ALTERNATES: &str = "z";
389
390impl<'a> Iterator for StrftimeItems<'a> {
391    type Item = Item<'a>;
392
393    fn next(&mut self) -> Option<Item<'a>> {
394        // We have items queued to return from a specifier composed of multiple formatting items.
395        if let Some((item, remainder)) = self.queue.split_first() {
396            self.queue = remainder;
397            return Some(item.clone());
398        }
399
400        // We are in the middle of parsing the localized formatting string of a specifier.
401        #[cfg(feature = "unstable-locales")]
402        if !self.locale_str.is_empty() {
403            let (remainder, item) = self.parse_next_item(self.locale_str)?;
404            self.locale_str = remainder;
405            return Some(item);
406        }
407
408        // Normal: we are parsing the formatting string.
409        let (remainder, item) = self.parse_next_item(self.remainder)?;
410        self.remainder = remainder;
411        Some(item)
412    }
413}
414
415impl<'a> StrftimeItems<'a> {
416    fn parse_next_item(&mut self, mut remainder: &'a str) -> Option<(&'a str, Item<'a>)> {
417        use InternalInternal::*;
418        use Item::{Literal, Space};
419        use Numeric::*;
420
421        static D_FMT: &[Item<'static>] =
422            &[num0(Month), Literal("/"), num0(Day), Literal("/"), num0(YearMod100)];
423        static D_T_FMT: &[Item<'static>] = &[
424            fixed(Fixed::ShortWeekdayName),
425            Space(" "),
426            fixed(Fixed::ShortMonthName),
427            Space(" "),
428            nums(Day),
429            Space(" "),
430            num0(Hour),
431            Literal(":"),
432            num0(Minute),
433            Literal(":"),
434            num0(Second),
435            Space(" "),
436            num0(Year),
437        ];
438        static T_FMT: &[Item<'static>] =
439            &[num0(Hour), Literal(":"), num0(Minute), Literal(":"), num0(Second)];
440        static T_FMT_AMPM: &[Item<'static>] = &[
441            num0(Hour12),
442            Literal(":"),
443            num0(Minute),
444            Literal(":"),
445            num0(Second),
446            Space(" "),
447            fixed(Fixed::UpperAmPm),
448        ];
449
450        match remainder.chars().next() {
451            // we are done
452            None => None,
453
454            // the next item is a specifier
455            Some('%') => {
456                remainder = &remainder[1..];
457
458                macro_rules! next {
459                    () => {
460                        match remainder.chars().next() {
461                            Some(x) => {
462                                remainder = &remainder[x.len_utf8()..];
463                                x
464                            }
465                            None => return Some((remainder, Item::Error)), // premature end of string
466                        }
467                    };
468                }
469
470                let spec = next!();
471                let pad_override = match spec {
472                    '-' => Some(Pad::None),
473                    '0' => Some(Pad::Zero),
474                    '_' => Some(Pad::Space),
475                    _ => None,
476                };
477                let is_alternate = spec == '#';
478                let spec = if pad_override.is_some() || is_alternate { next!() } else { spec };
479                if is_alternate && !HAVE_ALTERNATES.contains(spec) {
480                    return Some((remainder, Item::Error));
481                }
482
483                macro_rules! queue {
484                    [$head:expr, $($tail:expr),+ $(,)*] => ({
485                        const QUEUE: &'static [Item<'static>] = &[$($tail),+];
486                        self.queue = QUEUE;
487                        $head
488                    })
489                }
490                #[cfg(not(feature = "unstable-locales"))]
491                macro_rules! queue_from_slice {
492                    ($slice:expr) => {{
493                        self.queue = &$slice[1..];
494                        $slice[0].clone()
495                    }};
496                }
497
498                let item = match spec {
499                    'A' => fixed(Fixed::LongWeekdayName),
500                    'B' => fixed(Fixed::LongMonthName),
501                    'C' => num0(YearDiv100),
502                    'D' => {
503                        queue![num0(Month), Literal("/"), num0(Day), Literal("/"), num0(YearMod100)]
504                    }
505                    'F' => queue![num0(Year), Literal("-"), num0(Month), Literal("-"), num0(Day)],
506                    'G' => num0(IsoYear),
507                    'H' => num0(Hour),
508                    'I' => num0(Hour12),
509                    'M' => num0(Minute),
510                    'P' => fixed(Fixed::LowerAmPm),
511                    'R' => queue![num0(Hour), Literal(":"), num0(Minute)],
512                    'S' => num0(Second),
513                    'T' => {
514                        queue![num0(Hour), Literal(":"), num0(Minute), Literal(":"), num0(Second)]
515                    }
516                    'U' => num0(WeekFromSun),
517                    'V' => num0(IsoWeek),
518                    'W' => num0(WeekFromMon),
519                    #[cfg(not(feature = "unstable-locales"))]
520                    'X' => queue_from_slice!(T_FMT),
521                    #[cfg(feature = "unstable-locales")]
522                    'X' => self.switch_to_locale_str(locales::t_fmt, T_FMT),
523                    'Y' => num0(Year),
524                    'Z' => fixed(Fixed::TimezoneName),
525                    'a' => fixed(Fixed::ShortWeekdayName),
526                    'b' | 'h' => fixed(Fixed::ShortMonthName),
527                    #[cfg(not(feature = "unstable-locales"))]
528                    'c' => queue_from_slice!(D_T_FMT),
529                    #[cfg(feature = "unstable-locales")]
530                    'c' => self.switch_to_locale_str(locales::d_t_fmt, D_T_FMT),
531                    'd' => num0(Day),
532                    'e' => nums(Day),
533                    'f' => num0(Nanosecond),
534                    'g' => num0(IsoYearMod100),
535                    'j' => num0(Ordinal),
536                    'k' => nums(Hour),
537                    'l' => nums(Hour12),
538                    'm' => num0(Month),
539                    'n' => Space("\n"),
540                    'p' => fixed(Fixed::UpperAmPm),
541                    #[cfg(not(feature = "unstable-locales"))]
542                    'r' => queue_from_slice!(T_FMT_AMPM),
543                    #[cfg(feature = "unstable-locales")]
544                    'r' => {
545                        if self.locale.is_some()
546                            && locales::t_fmt_ampm(self.locale.unwrap()).is_empty()
547                        {
548                            // 12-hour clock not supported by this locale. Switch to 24-hour format.
549                            self.switch_to_locale_str(locales::t_fmt, T_FMT)
550                        } else {
551                            self.switch_to_locale_str(locales::t_fmt_ampm, T_FMT_AMPM)
552                        }
553                    }
554                    's' => num(Timestamp),
555                    't' => Space("\t"),
556                    'u' => num(WeekdayFromMon),
557                    'v' => {
558                        queue![
559                            nums(Day),
560                            Literal("-"),
561                            fixed(Fixed::ShortMonthName),
562                            Literal("-"),
563                            num0(Year)
564                        ]
565                    }
566                    'w' => num(NumDaysFromSun),
567                    #[cfg(not(feature = "unstable-locales"))]
568                    'x' => queue_from_slice!(D_FMT),
569                    #[cfg(feature = "unstable-locales")]
570                    'x' => self.switch_to_locale_str(locales::d_fmt, D_FMT),
571                    'y' => num0(YearMod100),
572                    'z' => {
573                        if is_alternate {
574                            internal_fixed(TimezoneOffsetPermissive)
575                        } else {
576                            fixed(Fixed::TimezoneOffset)
577                        }
578                    }
579                    '+' => fixed(Fixed::RFC3339),
580                    ':' => {
581                        if remainder.starts_with("::z") {
582                            remainder = &remainder[3..];
583                            fixed(Fixed::TimezoneOffsetTripleColon)
584                        } else if remainder.starts_with(":z") {
585                            remainder = &remainder[2..];
586                            fixed(Fixed::TimezoneOffsetDoubleColon)
587                        } else if remainder.starts_with('z') {
588                            remainder = &remainder[1..];
589                            fixed(Fixed::TimezoneOffsetColon)
590                        } else {
591                            Item::Error
592                        }
593                    }
594                    '.' => match next!() {
595                        '3' => match next!() {
596                            'f' => fixed(Fixed::Nanosecond3),
597                            _ => Item::Error,
598                        },
599                        '6' => match next!() {
600                            'f' => fixed(Fixed::Nanosecond6),
601                            _ => Item::Error,
602                        },
603                        '9' => match next!() {
604                            'f' => fixed(Fixed::Nanosecond9),
605                            _ => Item::Error,
606                        },
607                        'f' => fixed(Fixed::Nanosecond),
608                        _ => Item::Error,
609                    },
610                    '3' => match next!() {
611                        'f' => internal_fixed(Nanosecond3NoDot),
612                        _ => Item::Error,
613                    },
614                    '6' => match next!() {
615                        'f' => internal_fixed(Nanosecond6NoDot),
616                        _ => Item::Error,
617                    },
618                    '9' => match next!() {
619                        'f' => internal_fixed(Nanosecond9NoDot),
620                        _ => Item::Error,
621                    },
622                    '%' => Literal("%"),
623                    _ => Item::Error, // no such specifier
624                };
625
626                // Adjust `item` if we have any padding modifier.
627                // Not allowed on non-numeric items or on specifiers composed out of multiple
628                // formatting items.
629                if let Some(new_pad) = pad_override {
630                    match item {
631                        Item::Numeric(ref kind, _pad) if self.queue.is_empty() => {
632                            Some((remainder, Item::Numeric(kind.clone(), new_pad)))
633                        }
634                        _ => Some((remainder, Item::Error)),
635                    }
636                } else {
637                    Some((remainder, item))
638                }
639            }
640
641            // the next item is space
642            Some(c) if c.is_whitespace() => {
643                // `%` is not a whitespace, so `c != '%'` is redundant
644                let nextspec =
645                    remainder.find(|c: char| !c.is_whitespace()).unwrap_or(remainder.len());
646                assert!(nextspec > 0);
647                let item = Space(&remainder[..nextspec]);
648                remainder = &remainder[nextspec..];
649                Some((remainder, item))
650            }
651
652            // the next item is literal
653            _ => {
654                let nextspec = remainder
655                    .find(|c: char| c.is_whitespace() || c == '%')
656                    .unwrap_or(remainder.len());
657                assert!(nextspec > 0);
658                let item = Literal(&remainder[..nextspec]);
659                remainder = &remainder[nextspec..];
660                Some((remainder, item))
661            }
662        }
663    }
664
665    #[cfg(feature = "unstable-locales")]
666    fn switch_to_locale_str(
667        &mut self,
668        localized_fmt_str: impl Fn(Locale) -> &'static str,
669        fallback: &'static [Item<'static>],
670    ) -> Item<'a> {
671        if let Some(locale) = self.locale {
672            assert!(self.locale_str.is_empty());
673            let (fmt_str, item) = self.parse_next_item(localized_fmt_str(locale)).unwrap();
674            self.locale_str = fmt_str;
675            item
676        } else {
677            self.queue = &fallback[1..];
678            fallback[0].clone()
679        }
680    }
681}
682
683#[cfg(test)]
684mod tests {
685    use super::StrftimeItems;
686    use crate::format::Item::{self, Literal, Space};
687    #[cfg(feature = "unstable-locales")]
688    use crate::format::Locale;
689    use crate::format::{fixed, internal_fixed, num, num0, nums};
690    use crate::format::{Fixed, InternalInternal, Numeric::*};
691    #[cfg(feature = "alloc")]
692    use crate::{DateTime, FixedOffset, NaiveDate, TimeZone, Timelike, Utc};
693
694    #[test]
695    fn test_strftime_items() {
696        fn parse_and_collect(s: &str) -> Vec<Item<'_>> {
697            // map any error into `[Item::Error]`. useful for easy testing.
698            eprintln!("test_strftime_items: parse_and_collect({:?})", s);
699            let items = StrftimeItems::new(s);
700            let items = items.map(|spec| if spec == Item::Error { None } else { Some(spec) });
701            items.collect::<Option<Vec<_>>>().unwrap_or_else(|| vec![Item::Error])
702        }
703
704        assert_eq!(parse_and_collect(""), []);
705        assert_eq!(parse_and_collect(" "), [Space(" ")]);
706        assert_eq!(parse_and_collect("  "), [Space("  ")]);
707        // ne!
708        assert_ne!(parse_and_collect("  "), [Space(" "), Space(" ")]);
709        // eq!
710        assert_eq!(parse_and_collect("  "), [Space("  ")]);
711        assert_eq!(parse_and_collect("a"), [Literal("a")]);
712        assert_eq!(parse_and_collect("ab"), [Literal("ab")]);
713        assert_eq!(parse_and_collect("😽"), [Literal("😽")]);
714        assert_eq!(parse_and_collect("a😽"), [Literal("a😽")]);
715        assert_eq!(parse_and_collect("😽a"), [Literal("😽a")]);
716        assert_eq!(parse_and_collect(" 😽"), [Space(" "), Literal("😽")]);
717        assert_eq!(parse_and_collect("😽 "), [Literal("😽"), Space(" ")]);
718        // ne!
719        assert_ne!(parse_and_collect("😽😽"), [Literal("😽")]);
720        assert_ne!(parse_and_collect("😽"), [Literal("😽😽")]);
721        assert_ne!(parse_and_collect("😽😽"), [Literal("😽😽"), Literal("😽")]);
722        // eq!
723        assert_eq!(parse_and_collect("😽😽"), [Literal("😽😽")]);
724        assert_eq!(parse_and_collect(" \t\n\r "), [Space(" \t\n\r ")]);
725        assert_eq!(parse_and_collect("hello?"), [Literal("hello?")]);
726        assert_eq!(
727            parse_and_collect("a  b\t\nc"),
728            [Literal("a"), Space("  "), Literal("b"), Space("\t\n"), Literal("c")]
729        );
730        assert_eq!(parse_and_collect("100%%"), [Literal("100"), Literal("%")]);
731        assert_eq!(
732            parse_and_collect("100%% ok"),
733            [Literal("100"), Literal("%"), Space(" "), Literal("ok")]
734        );
735        assert_eq!(parse_and_collect("%%PDF-1.0"), [Literal("%"), Literal("PDF-1.0")]);
736        assert_eq!(
737            parse_and_collect("%Y-%m-%d"),
738            [num0(Year), Literal("-"), num0(Month), Literal("-"), num0(Day)]
739        );
740        assert_eq!(parse_and_collect("😽   "), [Literal("😽"), Space("   ")]);
741        assert_eq!(parse_and_collect("😽😽"), [Literal("😽😽")]);
742        assert_eq!(parse_and_collect("😽😽😽"), [Literal("😽😽😽")]);
743        assert_eq!(parse_and_collect("😽😽 😽"), [Literal("😽😽"), Space(" "), Literal("😽")]);
744        assert_eq!(parse_and_collect("😽😽a 😽"), [Literal("😽😽a"), Space(" "), Literal("😽")]);
745        assert_eq!(parse_and_collect("😽😽a b😽"), [Literal("😽😽a"), Space(" "), Literal("b😽")]);
746        assert_eq!(
747            parse_and_collect("😽😽a b😽c"),
748            [Literal("😽😽a"), Space(" "), Literal("b😽c")]
749        );
750        assert_eq!(parse_and_collect("😽😽   "), [Literal("😽😽"), Space("   ")]);
751        assert_eq!(parse_and_collect("😽😽   😽"), [Literal("😽😽"), Space("   "), Literal("😽")]);
752        assert_eq!(parse_and_collect("   😽"), [Space("   "), Literal("😽")]);
753        assert_eq!(parse_and_collect("   😽 "), [Space("   "), Literal("😽"), Space(" ")]);
754        assert_eq!(
755            parse_and_collect("   😽 😽"),
756            [Space("   "), Literal("😽"), Space(" "), Literal("😽")]
757        );
758        assert_eq!(
759            parse_and_collect("   😽 😽 "),
760            [Space("   "), Literal("😽"), Space(" "), Literal("😽"), Space(" ")]
761        );
762        assert_eq!(
763            parse_and_collect("   😽  😽 "),
764            [Space("   "), Literal("😽"), Space("  "), Literal("😽"), Space(" ")]
765        );
766        assert_eq!(
767            parse_and_collect("   😽  😽😽 "),
768            [Space("   "), Literal("😽"), Space("  "), Literal("😽😽"), Space(" ")]
769        );
770        assert_eq!(parse_and_collect("   😽😽"), [Space("   "), Literal("😽😽")]);
771        assert_eq!(parse_and_collect("   😽😽 "), [Space("   "), Literal("😽😽"), Space(" ")]);
772        assert_eq!(
773            parse_and_collect("   😽😽    "),
774            [Space("   "), Literal("😽😽"), Space("    ")]
775        );
776        assert_eq!(
777            parse_and_collect("   😽😽    "),
778            [Space("   "), Literal("😽😽"), Space("    ")]
779        );
780        assert_eq!(parse_and_collect(" 😽😽    "), [Space(" "), Literal("😽😽"), Space("    ")]);
781        assert_eq!(
782            parse_and_collect(" 😽 😽😽    "),
783            [Space(" "), Literal("😽"), Space(" "), Literal("😽😽"), Space("    ")]
784        );
785        assert_eq!(
786            parse_and_collect(" 😽 😽はい😽    ハンバーガー"),
787            [
788                Space(" "),
789                Literal("😽"),
790                Space(" "),
791                Literal("😽はい😽"),
792                Space("    "),
793                Literal("ハンバーガー")
794            ]
795        );
796        assert_eq!(
797            parse_and_collect("%%😽%%😽"),
798            [Literal("%"), Literal("😽"), Literal("%"), Literal("😽")]
799        );
800        assert_eq!(parse_and_collect("%Y--%m"), [num0(Year), Literal("--"), num0(Month)]);
801        assert_eq!(parse_and_collect("[%F]"), parse_and_collect("[%Y-%m-%d]"));
802        assert_eq!(parse_and_collect("100%%😽"), [Literal("100"), Literal("%"), Literal("😽")]);
803        assert_eq!(
804            parse_and_collect("100%%😽%%a"),
805            [Literal("100"), Literal("%"), Literal("😽"), Literal("%"), Literal("a")]
806        );
807        assert_eq!(parse_and_collect("😽100%%"), [Literal("😽100"), Literal("%")]);
808        assert_eq!(parse_and_collect("%m %d"), [num0(Month), Space(" "), num0(Day)]);
809        assert_eq!(parse_and_collect("%"), [Item::Error]);
810        assert_eq!(parse_and_collect("%%"), [Literal("%")]);
811        assert_eq!(parse_and_collect("%%%"), [Item::Error]);
812        assert_eq!(parse_and_collect("%a"), [fixed(Fixed::ShortWeekdayName)]);
813        assert_eq!(parse_and_collect("%aa"), [fixed(Fixed::ShortWeekdayName), Literal("a")]);
814        assert_eq!(parse_and_collect("%%a%"), [Item::Error]);
815        assert_eq!(parse_and_collect("%😽"), [Item::Error]);
816        assert_eq!(parse_and_collect("%😽😽"), [Item::Error]);
817        assert_eq!(parse_and_collect("%%%%"), [Literal("%"), Literal("%")]);
818        assert_eq!(
819            parse_and_collect("%%%%ハンバーガー"),
820            [Literal("%"), Literal("%"), Literal("ハンバーガー")]
821        );
822        assert_eq!(parse_and_collect("foo%?"), [Item::Error]);
823        assert_eq!(parse_and_collect("bar%42"), [Item::Error]);
824        assert_eq!(parse_and_collect("quux% +"), [Item::Error]);
825        assert_eq!(parse_and_collect("%.Z"), [Item::Error]);
826        assert_eq!(parse_and_collect("%:Z"), [Item::Error]);
827        assert_eq!(parse_and_collect("%-Z"), [Item::Error]);
828        assert_eq!(parse_and_collect("%0Z"), [Item::Error]);
829        assert_eq!(parse_and_collect("%_Z"), [Item::Error]);
830        assert_eq!(parse_and_collect("%.j"), [Item::Error]);
831        assert_eq!(parse_and_collect("%:j"), [Item::Error]);
832        assert_eq!(parse_and_collect("%-j"), [num(Ordinal)]);
833        assert_eq!(parse_and_collect("%0j"), [num0(Ordinal)]);
834        assert_eq!(parse_and_collect("%_j"), [nums(Ordinal)]);
835        assert_eq!(parse_and_collect("%.e"), [Item::Error]);
836        assert_eq!(parse_and_collect("%:e"), [Item::Error]);
837        assert_eq!(parse_and_collect("%-e"), [num(Day)]);
838        assert_eq!(parse_and_collect("%0e"), [num0(Day)]);
839        assert_eq!(parse_and_collect("%_e"), [nums(Day)]);
840        assert_eq!(parse_and_collect("%z"), [fixed(Fixed::TimezoneOffset)]);
841        assert_eq!(parse_and_collect("%:z"), [fixed(Fixed::TimezoneOffsetColon)]);
842        assert_eq!(parse_and_collect("%Z"), [fixed(Fixed::TimezoneName)]);
843        assert_eq!(parse_and_collect("%ZZZZ"), [fixed(Fixed::TimezoneName), Literal("ZZZ")]);
844        assert_eq!(parse_and_collect("%Z😽"), [fixed(Fixed::TimezoneName), Literal("😽")]);
845        assert_eq!(
846            parse_and_collect("%#z"),
847            [internal_fixed(InternalInternal::TimezoneOffsetPermissive)]
848        );
849        assert_eq!(parse_and_collect("%#m"), [Item::Error]);
850    }
851
852    #[test]
853    #[cfg(feature = "alloc")]
854    fn test_strftime_docs() {
855        let dt = FixedOffset::east_opt(34200)
856            .unwrap()
857            .from_local_datetime(
858                &NaiveDate::from_ymd_opt(2001, 7, 8)
859                    .unwrap()
860                    .and_hms_nano_opt(0, 34, 59, 1_026_490_708)
861                    .unwrap(),
862            )
863            .unwrap();
864
865        // date specifiers
866        assert_eq!(dt.format("%Y").to_string(), "2001");
867        assert_eq!(dt.format("%C").to_string(), "20");
868        assert_eq!(dt.format("%y").to_string(), "01");
869        assert_eq!(dt.format("%m").to_string(), "07");
870        assert_eq!(dt.format("%b").to_string(), "Jul");
871        assert_eq!(dt.format("%B").to_string(), "July");
872        assert_eq!(dt.format("%h").to_string(), "Jul");
873        assert_eq!(dt.format("%d").to_string(), "08");
874        assert_eq!(dt.format("%e").to_string(), " 8");
875        assert_eq!(dt.format("%e").to_string(), dt.format("%_d").to_string());
876        assert_eq!(dt.format("%a").to_string(), "Sun");
877        assert_eq!(dt.format("%A").to_string(), "Sunday");
878        assert_eq!(dt.format("%w").to_string(), "0");
879        assert_eq!(dt.format("%u").to_string(), "7");
880        assert_eq!(dt.format("%U").to_string(), "27");
881        assert_eq!(dt.format("%W").to_string(), "27");
882        assert_eq!(dt.format("%G").to_string(), "2001");
883        assert_eq!(dt.format("%g").to_string(), "01");
884        assert_eq!(dt.format("%V").to_string(), "27");
885        assert_eq!(dt.format("%j").to_string(), "189");
886        assert_eq!(dt.format("%D").to_string(), "07/08/01");
887        assert_eq!(dt.format("%x").to_string(), "07/08/01");
888        assert_eq!(dt.format("%F").to_string(), "2001-07-08");
889        assert_eq!(dt.format("%v").to_string(), " 8-Jul-2001");
890
891        // time specifiers
892        assert_eq!(dt.format("%H").to_string(), "00");
893        assert_eq!(dt.format("%k").to_string(), " 0");
894        assert_eq!(dt.format("%k").to_string(), dt.format("%_H").to_string());
895        assert_eq!(dt.format("%I").to_string(), "12");
896        assert_eq!(dt.format("%l").to_string(), "12");
897        assert_eq!(dt.format("%l").to_string(), dt.format("%_I").to_string());
898        assert_eq!(dt.format("%P").to_string(), "am");
899        assert_eq!(dt.format("%p").to_string(), "AM");
900        assert_eq!(dt.format("%M").to_string(), "34");
901        assert_eq!(dt.format("%S").to_string(), "60");
902        assert_eq!(dt.format("%f").to_string(), "026490708");
903        assert_eq!(dt.format("%.f").to_string(), ".026490708");
904        assert_eq!(dt.with_nanosecond(1_026_490_000).unwrap().format("%.f").to_string(), ".026490");
905        assert_eq!(dt.format("%.3f").to_string(), ".026");
906        assert_eq!(dt.format("%.6f").to_string(), ".026490");
907        assert_eq!(dt.format("%.9f").to_string(), ".026490708");
908        assert_eq!(dt.format("%3f").to_string(), "026");
909        assert_eq!(dt.format("%6f").to_string(), "026490");
910        assert_eq!(dt.format("%9f").to_string(), "026490708");
911        assert_eq!(dt.format("%R").to_string(), "00:34");
912        assert_eq!(dt.format("%T").to_string(), "00:34:60");
913        assert_eq!(dt.format("%X").to_string(), "00:34:60");
914        assert_eq!(dt.format("%r").to_string(), "12:34:60 AM");
915
916        // time zone specifiers
917        //assert_eq!(dt.format("%Z").to_string(), "ACST");
918        assert_eq!(dt.format("%z").to_string(), "+0930");
919        assert_eq!(dt.format("%:z").to_string(), "+09:30");
920        assert_eq!(dt.format("%::z").to_string(), "+09:30:00");
921        assert_eq!(dt.format("%:::z").to_string(), "+09");
922
923        // date & time specifiers
924        assert_eq!(dt.format("%c").to_string(), "Sun Jul  8 00:34:60 2001");
925        assert_eq!(dt.format("%+").to_string(), "2001-07-08T00:34:60.026490708+09:30");
926
927        assert_eq!(
928            dt.with_timezone(&Utc).format("%+").to_string(),
929            "2001-07-07T15:04:60.026490708+00:00"
930        );
931        assert_eq!(
932            dt.with_timezone(&Utc),
933            DateTime::parse_from_str("2001-07-07T15:04:60.026490708Z", "%+").unwrap()
934        );
935        assert_eq!(
936            dt.with_timezone(&Utc),
937            DateTime::parse_from_str("2001-07-07T15:04:60.026490708UTC", "%+").unwrap()
938        );
939        assert_eq!(
940            dt.with_timezone(&Utc),
941            DateTime::parse_from_str("2001-07-07t15:04:60.026490708utc", "%+").unwrap()
942        );
943
944        assert_eq!(
945            dt.with_nanosecond(1_026_490_000).unwrap().format("%+").to_string(),
946            "2001-07-08T00:34:60.026490+09:30"
947        );
948        assert_eq!(dt.format("%s").to_string(), "994518299");
949
950        // special specifiers
951        assert_eq!(dt.format("%t").to_string(), "\t");
952        assert_eq!(dt.format("%n").to_string(), "\n");
953        assert_eq!(dt.format("%%").to_string(), "%");
954
955        // complex format specifiers
956        assert_eq!(dt.format("  %Y%d%m%%%%%t%H%M%S\t").to_string(), "  20010807%%\t003460\t");
957        assert_eq!(
958            dt.format("  %Y%d%m%%%%%t%H:%P:%M%S%:::z\t").to_string(),
959            "  20010807%%\t00:am:3460+09\t"
960        );
961    }
962
963    #[test]
964    #[cfg(all(feature = "unstable-locales", feature = "alloc"))]
965    fn test_strftime_docs_localized() {
966        let dt = FixedOffset::east_opt(34200)
967            .unwrap()
968            .with_ymd_and_hms(2001, 7, 8, 0, 34, 59)
969            .unwrap()
970            .with_nanosecond(1_026_490_708)
971            .unwrap();
972
973        // date specifiers
974        assert_eq!(dt.format_localized("%b", Locale::fr_BE).to_string(), "jui");
975        assert_eq!(dt.format_localized("%B", Locale::fr_BE).to_string(), "juillet");
976        assert_eq!(dt.format_localized("%h", Locale::fr_BE).to_string(), "jui");
977        assert_eq!(dt.format_localized("%a", Locale::fr_BE).to_string(), "dim");
978        assert_eq!(dt.format_localized("%A", Locale::fr_BE).to_string(), "dimanche");
979        assert_eq!(dt.format_localized("%D", Locale::fr_BE).to_string(), "07/08/01");
980        assert_eq!(dt.format_localized("%x", Locale::fr_BE).to_string(), "08/07/01");
981        assert_eq!(dt.format_localized("%F", Locale::fr_BE).to_string(), "2001-07-08");
982        assert_eq!(dt.format_localized("%v", Locale::fr_BE).to_string(), " 8-jui-2001");
983
984        // time specifiers
985        assert_eq!(dt.format_localized("%P", Locale::fr_BE).to_string(), "");
986        assert_eq!(dt.format_localized("%p", Locale::fr_BE).to_string(), "");
987        assert_eq!(dt.format_localized("%R", Locale::fr_BE).to_string(), "00:34");
988        assert_eq!(dt.format_localized("%T", Locale::fr_BE).to_string(), "00:34:60");
989        assert_eq!(dt.format_localized("%X", Locale::fr_BE).to_string(), "00:34:60");
990        assert_eq!(dt.format_localized("%r", Locale::fr_BE).to_string(), "00:34:60");
991
992        // date & time specifiers
993        assert_eq!(
994            dt.format_localized("%c", Locale::fr_BE).to_string(),
995            "dim 08 jui 2001 00:34:60 +09:30"
996        );
997
998        let nd = NaiveDate::from_ymd_opt(2001, 7, 8).unwrap();
999
1000        // date specifiers
1001        assert_eq!(nd.format_localized("%b", Locale::de_DE).to_string(), "Jul");
1002        assert_eq!(nd.format_localized("%B", Locale::de_DE).to_string(), "Juli");
1003        assert_eq!(nd.format_localized("%h", Locale::de_DE).to_string(), "Jul");
1004        assert_eq!(nd.format_localized("%a", Locale::de_DE).to_string(), "So");
1005        assert_eq!(nd.format_localized("%A", Locale::de_DE).to_string(), "Sonntag");
1006        assert_eq!(nd.format_localized("%D", Locale::de_DE).to_string(), "07/08/01");
1007        assert_eq!(nd.format_localized("%x", Locale::de_DE).to_string(), "08.07.2001");
1008        assert_eq!(nd.format_localized("%F", Locale::de_DE).to_string(), "2001-07-08");
1009        assert_eq!(nd.format_localized("%v", Locale::de_DE).to_string(), " 8-Jul-2001");
1010    }
1011
1012    /// Ensure parsing a timestamp with the parse-only stftime formatter "%#z" does
1013    /// not cause a panic.
1014    ///
1015    /// See <https://github.com/chronotope/chrono/issues/1139>.
1016    #[test]
1017    #[cfg(feature = "alloc")]
1018    fn test_parse_only_timezone_offset_permissive_no_panic() {
1019        use crate::NaiveDate;
1020        use crate::{FixedOffset, TimeZone};
1021        use std::fmt::Write;
1022
1023        let dt = FixedOffset::east_opt(34200)
1024            .unwrap()
1025            .from_local_datetime(
1026                &NaiveDate::from_ymd_opt(2001, 7, 8)
1027                    .unwrap()
1028                    .and_hms_nano_opt(0, 34, 59, 1_026_490_708)
1029                    .unwrap(),
1030            )
1031            .unwrap();
1032
1033        let mut buf = String::new();
1034        let _ = write!(buf, "{}", dt.format("%#z")).expect_err("parse-only formatter should fail");
1035    }
1036
1037    #[test]
1038    #[cfg(all(feature = "unstable-locales", feature = "alloc"))]
1039    fn test_strftime_localized_korean() {
1040        let dt = FixedOffset::east_opt(34200)
1041            .unwrap()
1042            .with_ymd_and_hms(2001, 7, 8, 0, 34, 59)
1043            .unwrap()
1044            .with_nanosecond(1_026_490_708)
1045            .unwrap();
1046
1047        // date specifiers
1048        assert_eq!(dt.format_localized("%b", Locale::ko_KR).to_string(), " 7월");
1049        assert_eq!(dt.format_localized("%B", Locale::ko_KR).to_string(), "7월");
1050        assert_eq!(dt.format_localized("%h", Locale::ko_KR).to_string(), " 7월");
1051        assert_eq!(dt.format_localized("%a", Locale::ko_KR).to_string(), "일");
1052        assert_eq!(dt.format_localized("%A", Locale::ko_KR).to_string(), "일요일");
1053        assert_eq!(dt.format_localized("%D", Locale::ko_KR).to_string(), "07/08/01");
1054        assert_eq!(dt.format_localized("%x", Locale::ko_KR).to_string(), "2001년 07월 08일");
1055        assert_eq!(dt.format_localized("%F", Locale::ko_KR).to_string(), "2001-07-08");
1056        assert_eq!(dt.format_localized("%v", Locale::ko_KR).to_string(), " 8- 7월-2001");
1057        assert_eq!(dt.format_localized("%r", Locale::ko_KR).to_string(), "오전 12시 34분 60초");
1058
1059        // date & time specifiers
1060        assert_eq!(
1061            dt.format_localized("%c", Locale::ko_KR).to_string(),
1062            "2001년 07월 08일 (일) 오전 12시 34분 60초"
1063        );
1064    }
1065
1066    #[test]
1067    #[cfg(all(feature = "unstable-locales", feature = "alloc"))]
1068    fn test_strftime_localized_japanese() {
1069        let dt = FixedOffset::east_opt(34200)
1070            .unwrap()
1071            .with_ymd_and_hms(2001, 7, 8, 0, 34, 59)
1072            .unwrap()
1073            .with_nanosecond(1_026_490_708)
1074            .unwrap();
1075
1076        // date specifiers
1077        assert_eq!(dt.format_localized("%b", Locale::ja_JP).to_string(), " 7月");
1078        assert_eq!(dt.format_localized("%B", Locale::ja_JP).to_string(), "7月");
1079        assert_eq!(dt.format_localized("%h", Locale::ja_JP).to_string(), " 7月");
1080        assert_eq!(dt.format_localized("%a", Locale::ja_JP).to_string(), "日");
1081        assert_eq!(dt.format_localized("%A", Locale::ja_JP).to_string(), "日曜日");
1082        assert_eq!(dt.format_localized("%D", Locale::ja_JP).to_string(), "07/08/01");
1083        assert_eq!(dt.format_localized("%x", Locale::ja_JP).to_string(), "2001年07月08日");
1084        assert_eq!(dt.format_localized("%F", Locale::ja_JP).to_string(), "2001-07-08");
1085        assert_eq!(dt.format_localized("%v", Locale::ja_JP).to_string(), " 8- 7月-2001");
1086        assert_eq!(dt.format_localized("%r", Locale::ja_JP).to_string(), "午前12時34分60秒");
1087
1088        // date & time specifiers
1089        assert_eq!(
1090            dt.format_localized("%c", Locale::ja_JP).to_string(),
1091            "2001年07月08日 00時34分60秒"
1092        );
1093    }
1094
1095    #[test]
1096    #[cfg(all(feature = "unstable-locales", feature = "alloc"))]
1097    fn test_strftime_localized_time() {
1098        let dt1 = Utc.with_ymd_and_hms(2024, 2, 9, 6, 54, 32).unwrap();
1099        let dt2 = Utc.with_ymd_and_hms(2024, 2, 9, 18, 54, 32).unwrap();
1100        // Some of these locales gave issues before pure-rust-locales 0.8.0 with chrono 0.4.27+
1101        assert_eq!(dt1.format_localized("%X", Locale::nl_NL).to_string(), "06:54:32");
1102        assert_eq!(dt2.format_localized("%X", Locale::nl_NL).to_string(), "18:54:32");
1103        assert_eq!(dt1.format_localized("%X", Locale::en_US).to_string(), "06:54:32 AM");
1104        assert_eq!(dt2.format_localized("%X", Locale::en_US).to_string(), "06:54:32 PM");
1105        assert_eq!(dt1.format_localized("%X", Locale::hy_AM).to_string(), "06:54:32");
1106        assert_eq!(dt2.format_localized("%X", Locale::hy_AM).to_string(), "18:54:32");
1107        assert_eq!(dt1.format_localized("%X", Locale::chr_US).to_string(), "06:54:32 ᏌᎾᎴ");
1108        assert_eq!(dt2.format_localized("%X", Locale::chr_US).to_string(), "06:54:32 ᏒᎯᏱᎢᏗᏢ");
1109    }
1110
1111    #[test]
1112    #[cfg(all(feature = "unstable-locales", target_pointer_width = "64"))]
1113    fn test_type_sizes() {
1114        use core::mem::size_of;
1115        assert_eq!(size_of::<Item>(), 24);
1116        assert_eq!(size_of::<StrftimeItems>(), 56);
1117        assert_eq!(size_of::<Locale>(), 2);
1118    }
1119
1120    #[test]
1121    #[cfg(all(feature = "unstable-locales", target_pointer_width = "32"))]
1122    fn test_type_sizes() {
1123        use core::mem::size_of;
1124        assert_eq!(size_of::<Item>(), 12);
1125        assert_eq!(size_of::<StrftimeItems>(), 28);
1126        assert_eq!(size_of::<Locale>(), 2);
1127    }
1128
1129    #[test]
1130    #[cfg(any(feature = "alloc", feature = "std"))]
1131    fn test_strftime_parse() {
1132        let fmt_str = StrftimeItems::new("%Y-%m-%dT%H:%M:%S%z");
1133        let fmt_items = fmt_str.parse().unwrap();
1134        let dt = Utc.with_ymd_and_hms(2014, 5, 7, 12, 34, 56).unwrap();
1135        assert_eq!(&dt.format_with_items(fmt_items.iter()).to_string(), "2014-05-07T12:34:56+0000");
1136    }
1137}