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