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