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