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("%"),
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
430const HAVE_ALTERNATES: &str = "z";
431
432impl<'a> Iterator for StrftimeItems<'a> {
433    type Item = Item<'a>;
434
435    fn next(&mut self) -> Option<Item<'a>> {
436        // We have items queued to return from a specifier composed of multiple formatting items.
437        if let Some((item, remainder)) = self.queue.split_first() {
438            self.queue = remainder;
439            return Some(item.clone());
440        }
441
442        // We are in the middle of parsing the localized formatting string of a specifier.
443        #[cfg(feature = "unstable-locales")]
444        if !self.locale_str.is_empty() {
445            let (remainder, item) = self.parse_next_item(self.locale_str)?;
446            self.locale_str = remainder;
447            return Some(item);
448        }
449
450        // Normal: we are parsing the formatting string.
451        let (remainder, item) = self.parse_next_item(self.remainder)?;
452        self.remainder = remainder;
453        Some(item)
454    }
455}
456
457impl<'a> StrftimeItems<'a> {
458    fn error<'b>(
459        &mut self,
460        original: &'b str,
461        error_len: &mut usize,
462        ch: Option<char>,
463    ) -> (&'b str, Item<'b>) {
464        if !self.lenient {
465            return (&original[*error_len..], Item::Error);
466        }
467
468        if let Some(c) = ch {
469            *error_len -= c.len_utf8();
470        }
471        (&original[*error_len..], Item::Literal(&original[..*error_len]))
472    }
473
474    fn parse_next_item(&mut self, mut remainder: &'a str) -> Option<(&'a str, Item<'a>)> {
475        use InternalInternal::*;
476        use Item::{Literal, Space};
477        use Numeric::*;
478
479        static D_FMT: &[Item<'static>] =
480            &[num0(Month), Literal("/"), num0(Day), Literal("/"), num0(YearMod100)];
481        static D_T_FMT: &[Item<'static>] = &[
482            fixed(Fixed::ShortWeekdayName),
483            Space(" "),
484            fixed(Fixed::ShortMonthName),
485            Space(" "),
486            nums(Day),
487            Space(" "),
488            num0(Hour),
489            Literal(":"),
490            num0(Minute),
491            Literal(":"),
492            num0(Second),
493            Space(" "),
494            num0(Year),
495        ];
496        static T_FMT: &[Item<'static>] =
497            &[num0(Hour), Literal(":"), num0(Minute), Literal(":"), num0(Second)];
498        static T_FMT_AMPM: &[Item<'static>] = &[
499            num0(Hour12),
500            Literal(":"),
501            num0(Minute),
502            Literal(":"),
503            num0(Second),
504            Space(" "),
505            fixed(Fixed::UpperAmPm),
506        ];
507
508        match remainder.chars().next() {
509            // we are done
510            None => None,
511
512            // the next item is a specifier
513            Some('%') => {
514                let original = remainder;
515                remainder = &remainder[1..];
516                let mut error_len = 0;
517                if self.lenient {
518                    error_len += 1;
519                }
520
521                macro_rules! next {
522                    () => {
523                        match remainder.chars().next() {
524                            Some(x) => {
525                                remainder = &remainder[x.len_utf8()..];
526                                if self.lenient {
527                                    error_len += x.len_utf8();
528                                }
529                                x
530                            }
531                            None => return Some(self.error(original, &mut error_len, None)), // premature end of string
532                        }
533                    };
534                }
535
536                let spec = next!();
537                let pad_override = match spec {
538                    '-' => Some(Pad::None),
539                    '0' => Some(Pad::Zero),
540                    '_' => Some(Pad::Space),
541                    _ => None,
542                };
543                let is_alternate = spec == '#';
544                let spec = if pad_override.is_some() || is_alternate { next!() } else { spec };
545                if is_alternate && !HAVE_ALTERNATES.contains(spec) {
546                    return Some(self.error(original, &mut error_len, Some(spec)));
547                }
548
549                macro_rules! queue {
550                    [$head:expr, $($tail:expr),+ $(,)*] => ({
551                        const QUEUE: &'static [Item<'static>] = &[$($tail),+];
552                        self.queue = QUEUE;
553                        $head
554                    })
555                }
556                #[cfg(not(feature = "unstable-locales"))]
557                macro_rules! queue_from_slice {
558                    ($slice:expr) => {{
559                        self.queue = &$slice[1..];
560                        $slice[0].clone()
561                    }};
562                }
563
564                let item = match spec {
565                    'A' => fixed(Fixed::LongWeekdayName),
566                    'B' => fixed(Fixed::LongMonthName),
567                    'C' => num0(YearDiv100),
568                    'D' => {
569                        queue![num0(Month), Literal("/"), num0(Day), Literal("/"), num0(YearMod100)]
570                    }
571                    'F' => queue![num0(Year), Literal("-"), num0(Month), Literal("-"), num0(Day)],
572                    'G' => num0(IsoYear),
573                    'H' => num0(Hour),
574                    'I' => num0(Hour12),
575                    'M' => num0(Minute),
576                    'P' => fixed(Fixed::LowerAmPm),
577                    'R' => queue![num0(Hour), Literal(":"), num0(Minute)],
578                    'S' => num0(Second),
579                    'T' => {
580                        queue![num0(Hour), Literal(":"), num0(Minute), Literal(":"), num0(Second)]
581                    }
582                    'U' => num0(WeekFromSun),
583                    'V' => num0(IsoWeek),
584                    'W' => num0(WeekFromMon),
585                    #[cfg(not(feature = "unstable-locales"))]
586                    'X' => queue_from_slice!(T_FMT),
587                    #[cfg(feature = "unstable-locales")]
588                    'X' => self.switch_to_locale_str(locales::t_fmt, T_FMT),
589                    'Y' => num0(Year),
590                    'Z' => fixed(Fixed::TimezoneName),
591                    'a' => fixed(Fixed::ShortWeekdayName),
592                    'b' | 'h' => fixed(Fixed::ShortMonthName),
593                    #[cfg(not(feature = "unstable-locales"))]
594                    'c' => queue_from_slice!(D_T_FMT),
595                    #[cfg(feature = "unstable-locales")]
596                    'c' => self.switch_to_locale_str(locales::d_t_fmt, D_T_FMT),
597                    'd' => num0(Day),
598                    'e' => nums(Day),
599                    'f' => num0(Nanosecond),
600                    'g' => num0(IsoYearMod100),
601                    'j' => num0(Ordinal),
602                    'k' => nums(Hour),
603                    'l' => nums(Hour12),
604                    'm' => num0(Month),
605                    'n' => Space("\n"),
606                    'p' => fixed(Fixed::UpperAmPm),
607                    'q' => num(Quarter),
608                    #[cfg(not(feature = "unstable-locales"))]
609                    'r' => queue_from_slice!(T_FMT_AMPM),
610                    #[cfg(feature = "unstable-locales")]
611                    'r' => {
612                        if self.locale.is_some()
613                            && locales::t_fmt_ampm(self.locale.unwrap()).is_empty()
614                        {
615                            // 12-hour clock not supported by this locale. Switch to 24-hour format.
616                            self.switch_to_locale_str(locales::t_fmt, T_FMT)
617                        } else {
618                            self.switch_to_locale_str(locales::t_fmt_ampm, T_FMT_AMPM)
619                        }
620                    }
621                    's' => num(Timestamp),
622                    't' => Space("\t"),
623                    'u' => num(WeekdayFromMon),
624                    'v' => {
625                        queue![
626                            nums(Day),
627                            Literal("-"),
628                            fixed(Fixed::ShortMonthName),
629                            Literal("-"),
630                            num0(Year)
631                        ]
632                    }
633                    'w' => num(NumDaysFromSun),
634                    #[cfg(not(feature = "unstable-locales"))]
635                    'x' => queue_from_slice!(D_FMT),
636                    #[cfg(feature = "unstable-locales")]
637                    'x' => self.switch_to_locale_str(locales::d_fmt, D_FMT),
638                    'y' => num0(YearMod100),
639                    'z' => {
640                        if is_alternate {
641                            internal_fixed(TimezoneOffsetPermissive)
642                        } else {
643                            fixed(Fixed::TimezoneOffset)
644                        }
645                    }
646                    '+' => fixed(Fixed::RFC3339),
647                    ':' => {
648                        if remainder.starts_with("::z") {
649                            remainder = &remainder[3..];
650                            fixed(Fixed::TimezoneOffsetTripleColon)
651                        } else if remainder.starts_with(":z") {
652                            remainder = &remainder[2..];
653                            fixed(Fixed::TimezoneOffsetDoubleColon)
654                        } else if remainder.starts_with('z') {
655                            remainder = &remainder[1..];
656                            fixed(Fixed::TimezoneOffsetColon)
657                        } else {
658                            self.error(original, &mut error_len, None).1
659                        }
660                    }
661                    '.' => match next!() {
662                        '3' => match next!() {
663                            'f' => fixed(Fixed::Nanosecond3),
664                            c => {
665                                let res = self.error(original, &mut error_len, Some(c));
666                                remainder = res.0;
667                                res.1
668                            }
669                        },
670                        '6' => match next!() {
671                            'f' => fixed(Fixed::Nanosecond6),
672                            c => {
673                                let res = self.error(original, &mut error_len, Some(c));
674                                remainder = res.0;
675                                res.1
676                            }
677                        },
678                        '9' => match next!() {
679                            'f' => fixed(Fixed::Nanosecond9),
680                            c => {
681                                let res = self.error(original, &mut error_len, Some(c));
682                                remainder = res.0;
683                                res.1
684                            }
685                        },
686                        'f' => fixed(Fixed::Nanosecond),
687                        c => {
688                            let res = self.error(original, &mut error_len, Some(c));
689                            remainder = res.0;
690                            res.1
691                        }
692                    },
693                    '3' => match next!() {
694                        'f' => internal_fixed(Nanosecond3NoDot),
695                        c => {
696                            let res = self.error(original, &mut error_len, Some(c));
697                            remainder = res.0;
698                            res.1
699                        }
700                    },
701                    '6' => match next!() {
702                        'f' => internal_fixed(Nanosecond6NoDot),
703                        c => {
704                            let res = self.error(original, &mut error_len, Some(c));
705                            remainder = res.0;
706                            res.1
707                        }
708                    },
709                    '9' => match next!() {
710                        'f' => internal_fixed(Nanosecond9NoDot),
711                        c => {
712                            let res = self.error(original, &mut error_len, Some(c));
713                            remainder = res.0;
714                            res.1
715                        }
716                    },
717                    '%' => Literal("%"),
718                    c => {
719                        let res = self.error(original, &mut error_len, Some(c));
720                        remainder = res.0;
721                        res.1
722                    }
723                };
724
725                // Adjust `item` if we have any padding modifier.
726                // Not allowed on non-numeric items or on specifiers composed out of multiple
727                // formatting items.
728                if let Some(new_pad) = pad_override {
729                    match item {
730                        Item::Numeric(ref kind, _pad) if self.queue.is_empty() => {
731                            Some((remainder, Item::Numeric(kind.clone(), new_pad)))
732                        }
733                        _ => Some(self.error(original, &mut error_len, None)),
734                    }
735                } else {
736                    Some((remainder, item))
737                }
738            }
739
740            // the next item is space
741            Some(c) if c.is_whitespace() => {
742                // `%` is not a whitespace, so `c != '%'` is redundant
743                let nextspec =
744                    remainder.find(|c: char| !c.is_whitespace()).unwrap_or(remainder.len());
745                assert!(nextspec > 0);
746                let item = Space(&remainder[..nextspec]);
747                remainder = &remainder[nextspec..];
748                Some((remainder, item))
749            }
750
751            // the next item is literal
752            _ => {
753                let nextspec = remainder
754                    .find(|c: char| c.is_whitespace() || c == '%')
755                    .unwrap_or(remainder.len());
756                assert!(nextspec > 0);
757                let item = Literal(&remainder[..nextspec]);
758                remainder = &remainder[nextspec..];
759                Some((remainder, item))
760            }
761        }
762    }
763
764    #[cfg(feature = "unstable-locales")]
765    fn switch_to_locale_str(
766        &mut self,
767        localized_fmt_str: impl Fn(Locale) -> &'static str,
768        fallback: &'static [Item<'static>],
769    ) -> Item<'a> {
770        if let Some(locale) = self.locale {
771            assert!(self.locale_str.is_empty());
772            let (fmt_str, item) = self.parse_next_item(localized_fmt_str(locale)).unwrap();
773            self.locale_str = fmt_str;
774            item
775        } else {
776            self.queue = &fallback[1..];
777            fallback[0].clone()
778        }
779    }
780}
781
782#[cfg(test)]
783mod tests {
784    use super::StrftimeItems;
785    use crate::format::Item::{self, Literal, Space};
786    #[cfg(feature = "unstable-locales")]
787    use crate::format::Locale;
788    use crate::format::{Fixed, InternalInternal, Numeric::*};
789    use crate::format::{fixed, internal_fixed, num, num0, nums};
790    #[cfg(feature = "alloc")]
791    use crate::{DateTime, FixedOffset, NaiveDate, TimeZone, Timelike, Utc};
792
793    #[test]
794    fn test_strftime_items() {
795        fn parse_and_collect(s: &str) -> Vec<Item<'_>> {
796            // map any error into `[Item::Error]`. useful for easy testing.
797            eprintln!("test_strftime_items: parse_and_collect({:?})", s);
798            let items = StrftimeItems::new(s);
799            let items = items.map(|spec| if spec == Item::Error { None } else { Some(spec) });
800            items.collect::<Option<Vec<_>>>().unwrap_or_else(|| vec![Item::Error])
801        }
802
803        assert_eq!(parse_and_collect(""), []);
804        assert_eq!(parse_and_collect(" "), [Space(" ")]);
805        assert_eq!(parse_and_collect("  "), [Space("  ")]);
806        // ne!
807        assert_ne!(parse_and_collect("  "), [Space(" "), Space(" ")]);
808        // eq!
809        assert_eq!(parse_and_collect("  "), [Space("  ")]);
810        assert_eq!(parse_and_collect("a"), [Literal("a")]);
811        assert_eq!(parse_and_collect("ab"), [Literal("ab")]);
812        assert_eq!(parse_and_collect("😽"), [Literal("😽")]);
813        assert_eq!(parse_and_collect("a😽"), [Literal("a😽")]);
814        assert_eq!(parse_and_collect("😽a"), [Literal("😽a")]);
815        assert_eq!(parse_and_collect(" 😽"), [Space(" "), Literal("😽")]);
816        assert_eq!(parse_and_collect("😽 "), [Literal("😽"), Space(" ")]);
817        // ne!
818        assert_ne!(parse_and_collect("😽😽"), [Literal("😽")]);
819        assert_ne!(parse_and_collect("😽"), [Literal("😽😽")]);
820        assert_ne!(parse_and_collect("😽😽"), [Literal("😽😽"), Literal("😽")]);
821        // eq!
822        assert_eq!(parse_and_collect("😽😽"), [Literal("😽😽")]);
823        assert_eq!(parse_and_collect(" \t\n\r "), [Space(" \t\n\r ")]);
824        assert_eq!(parse_and_collect("hello?"), [Literal("hello?")]);
825        assert_eq!(
826            parse_and_collect("a  b\t\nc"),
827            [Literal("a"), Space("  "), Literal("b"), Space("\t\n"), Literal("c")]
828        );
829        assert_eq!(parse_and_collect("100%%"), [Literal("100"), Literal("%")]);
830        assert_eq!(
831            parse_and_collect("100%% ok"),
832            [Literal("100"), Literal("%"), Space(" "), Literal("ok")]
833        );
834        assert_eq!(parse_and_collect("%%PDF-1.0"), [Literal("%"), Literal("PDF-1.0")]);
835        assert_eq!(
836            parse_and_collect("%Y-%m-%d"),
837            [num0(Year), Literal("-"), num0(Month), Literal("-"), num0(Day)]
838        );
839        assert_eq!(parse_and_collect("😽   "), [Literal("😽"), Space("   ")]);
840        assert_eq!(parse_and_collect("😽😽"), [Literal("😽😽")]);
841        assert_eq!(parse_and_collect("😽😽😽"), [Literal("😽😽😽")]);
842        assert_eq!(parse_and_collect("😽😽 😽"), [Literal("😽😽"), Space(" "), Literal("😽")]);
843        assert_eq!(parse_and_collect("😽😽a 😽"), [Literal("😽😽a"), Space(" "), Literal("😽")]);
844        assert_eq!(parse_and_collect("😽😽a b😽"), [Literal("😽😽a"), Space(" "), Literal("b😽")]);
845        assert_eq!(
846            parse_and_collect("😽😽a b😽c"),
847            [Literal("😽😽a"), Space(" "), Literal("b😽c")]
848        );
849        assert_eq!(parse_and_collect("😽😽   "), [Literal("😽😽"), Space("   ")]);
850        assert_eq!(parse_and_collect("😽😽   😽"), [Literal("😽😽"), Space("   "), Literal("😽")]);
851        assert_eq!(parse_and_collect("   😽"), [Space("   "), Literal("😽")]);
852        assert_eq!(parse_and_collect("   😽 "), [Space("   "), Literal("😽"), Space(" ")]);
853        assert_eq!(
854            parse_and_collect("   😽 😽"),
855            [Space("   "), Literal("😽"), Space(" "), Literal("😽")]
856        );
857        assert_eq!(
858            parse_and_collect("   😽 😽 "),
859            [Space("   "), Literal("😽"), Space(" "), Literal("😽"), Space(" ")]
860        );
861        assert_eq!(
862            parse_and_collect("   😽  😽 "),
863            [Space("   "), Literal("😽"), Space("  "), Literal("😽"), Space(" ")]
864        );
865        assert_eq!(
866            parse_and_collect("   😽  😽😽 "),
867            [Space("   "), Literal("😽"), Space("  "), Literal("😽😽"), Space(" ")]
868        );
869        assert_eq!(parse_and_collect("   😽😽"), [Space("   "), Literal("😽😽")]);
870        assert_eq!(parse_and_collect("   😽😽 "), [Space("   "), Literal("😽😽"), Space(" ")]);
871        assert_eq!(
872            parse_and_collect("   😽😽    "),
873            [Space("   "), Literal("😽😽"), Space("    ")]
874        );
875        assert_eq!(
876            parse_and_collect("   😽😽    "),
877            [Space("   "), Literal("😽😽"), Space("    ")]
878        );
879        assert_eq!(parse_and_collect(" 😽😽    "), [Space(" "), Literal("😽😽"), Space("    ")]);
880        assert_eq!(
881            parse_and_collect(" 😽 😽😽    "),
882            [Space(" "), Literal("😽"), Space(" "), Literal("😽😽"), Space("    ")]
883        );
884        assert_eq!(
885            parse_and_collect(" 😽 😽はい😽    ハンバーガー"),
886            [
887                Space(" "),
888                Literal("😽"),
889                Space(" "),
890                Literal("😽はい😽"),
891                Space("    "),
892                Literal("ハンバーガー")
893            ]
894        );
895        assert_eq!(
896            parse_and_collect("%%😽%%😽"),
897            [Literal("%"), Literal("😽"), Literal("%"), Literal("😽")]
898        );
899        assert_eq!(parse_and_collect("%Y--%m"), [num0(Year), Literal("--"), num0(Month)]);
900        assert_eq!(parse_and_collect("[%F]"), parse_and_collect("[%Y-%m-%d]"));
901        assert_eq!(parse_and_collect("100%%😽"), [Literal("100"), Literal("%"), Literal("😽")]);
902        assert_eq!(
903            parse_and_collect("100%%😽%%a"),
904            [Literal("100"), Literal("%"), Literal("😽"), Literal("%"), Literal("a")]
905        );
906        assert_eq!(parse_and_collect("😽100%%"), [Literal("😽100"), Literal("%")]);
907        assert_eq!(parse_and_collect("%m %d"), [num0(Month), Space(" "), num0(Day)]);
908        assert_eq!(parse_and_collect("%"), [Item::Error]);
909        assert_eq!(parse_and_collect("%%"), [Literal("%")]);
910        assert_eq!(parse_and_collect("%%%"), [Item::Error]);
911        assert_eq!(parse_and_collect("%a"), [fixed(Fixed::ShortWeekdayName)]);
912        assert_eq!(parse_and_collect("%aa"), [fixed(Fixed::ShortWeekdayName), Literal("a")]);
913        assert_eq!(parse_and_collect("%%a%"), [Item::Error]);
914        assert_eq!(parse_and_collect("%😽"), [Item::Error]);
915        assert_eq!(parse_and_collect("%😽😽"), [Item::Error]);
916        assert_eq!(parse_and_collect("%%%%"), [Literal("%"), Literal("%")]);
917        assert_eq!(
918            parse_and_collect("%%%%ハンバーガー"),
919            [Literal("%"), Literal("%"), Literal("ハンバーガー")]
920        );
921        assert_eq!(parse_and_collect("foo%?"), [Item::Error]);
922        assert_eq!(parse_and_collect("bar%42"), [Item::Error]);
923        assert_eq!(parse_and_collect("quux% +"), [Item::Error]);
924        assert_eq!(parse_and_collect("%.Z"), [Item::Error]);
925        assert_eq!(parse_and_collect("%:Z"), [Item::Error]);
926        assert_eq!(parse_and_collect("%-Z"), [Item::Error]);
927        assert_eq!(parse_and_collect("%0Z"), [Item::Error]);
928        assert_eq!(parse_and_collect("%_Z"), [Item::Error]);
929        assert_eq!(parse_and_collect("%.j"), [Item::Error]);
930        assert_eq!(parse_and_collect("%:j"), [Item::Error]);
931        assert_eq!(parse_and_collect("%-j"), [num(Ordinal)]);
932        assert_eq!(parse_and_collect("%0j"), [num0(Ordinal)]);
933        assert_eq!(parse_and_collect("%_j"), [nums(Ordinal)]);
934        assert_eq!(parse_and_collect("%.e"), [Item::Error]);
935        assert_eq!(parse_and_collect("%:e"), [Item::Error]);
936        assert_eq!(parse_and_collect("%-e"), [num(Day)]);
937        assert_eq!(parse_and_collect("%0e"), [num0(Day)]);
938        assert_eq!(parse_and_collect("%_e"), [nums(Day)]);
939        assert_eq!(parse_and_collect("%z"), [fixed(Fixed::TimezoneOffset)]);
940        assert_eq!(parse_and_collect("%:z"), [fixed(Fixed::TimezoneOffsetColon)]);
941        assert_eq!(parse_and_collect("%Z"), [fixed(Fixed::TimezoneName)]);
942        assert_eq!(parse_and_collect("%ZZZZ"), [fixed(Fixed::TimezoneName), Literal("ZZZ")]);
943        assert_eq!(parse_and_collect("%Z😽"), [fixed(Fixed::TimezoneName), Literal("😽")]);
944        assert_eq!(
945            parse_and_collect("%#z"),
946            [internal_fixed(InternalInternal::TimezoneOffsetPermissive)]
947        );
948        assert_eq!(parse_and_collect("%#m"), [Item::Error]);
949    }
950
951    #[test]
952    #[cfg(feature = "alloc")]
953    fn test_strftime_docs() {
954        let dt = FixedOffset::east_opt(34200)
955            .unwrap()
956            .from_local_datetime(
957                &NaiveDate::from_ymd_opt(2001, 7, 8)
958                    .unwrap()
959                    .and_hms_nano_opt(0, 34, 59, 1_026_490_708)
960                    .unwrap(),
961            )
962            .unwrap();
963
964        // date specifiers
965        assert_eq!(dt.format("%Y").to_string(), "2001");
966        assert_eq!(dt.format("%C").to_string(), "20");
967        assert_eq!(dt.format("%y").to_string(), "01");
968        assert_eq!(dt.format("%q").to_string(), "3");
969        assert_eq!(dt.format("%m").to_string(), "07");
970        assert_eq!(dt.format("%b").to_string(), "Jul");
971        assert_eq!(dt.format("%B").to_string(), "July");
972        assert_eq!(dt.format("%h").to_string(), "Jul");
973        assert_eq!(dt.format("%d").to_string(), "08");
974        assert_eq!(dt.format("%e").to_string(), " 8");
975        assert_eq!(dt.format("%e").to_string(), dt.format("%_d").to_string());
976        assert_eq!(dt.format("%a").to_string(), "Sun");
977        assert_eq!(dt.format("%A").to_string(), "Sunday");
978        assert_eq!(dt.format("%w").to_string(), "0");
979        assert_eq!(dt.format("%u").to_string(), "7");
980        assert_eq!(dt.format("%U").to_string(), "27");
981        assert_eq!(dt.format("%W").to_string(), "27");
982        assert_eq!(dt.format("%G").to_string(), "2001");
983        assert_eq!(dt.format("%g").to_string(), "01");
984        assert_eq!(dt.format("%V").to_string(), "27");
985        assert_eq!(dt.format("%j").to_string(), "189");
986        assert_eq!(dt.format("%D").to_string(), "07/08/01");
987        assert_eq!(dt.format("%x").to_string(), "07/08/01");
988        assert_eq!(dt.format("%F").to_string(), "2001-07-08");
989        assert_eq!(dt.format("%v").to_string(), " 8-Jul-2001");
990
991        // time specifiers
992        assert_eq!(dt.format("%H").to_string(), "00");
993        assert_eq!(dt.format("%k").to_string(), " 0");
994        assert_eq!(dt.format("%k").to_string(), dt.format("%_H").to_string());
995        assert_eq!(dt.format("%I").to_string(), "12");
996        assert_eq!(dt.format("%l").to_string(), "12");
997        assert_eq!(dt.format("%l").to_string(), dt.format("%_I").to_string());
998        assert_eq!(dt.format("%P").to_string(), "am");
999        assert_eq!(dt.format("%p").to_string(), "AM");
1000        assert_eq!(dt.format("%M").to_string(), "34");
1001        assert_eq!(dt.format("%S").to_string(), "60");
1002        assert_eq!(dt.format("%f").to_string(), "026490708");
1003        assert_eq!(dt.format("%.f").to_string(), ".026490708");
1004        assert_eq!(dt.with_nanosecond(1_026_490_000).unwrap().format("%.f").to_string(), ".026490");
1005        assert_eq!(dt.format("%.3f").to_string(), ".026");
1006        assert_eq!(dt.format("%.6f").to_string(), ".026490");
1007        assert_eq!(dt.format("%.9f").to_string(), ".026490708");
1008        assert_eq!(dt.format("%3f").to_string(), "026");
1009        assert_eq!(dt.format("%6f").to_string(), "026490");
1010        assert_eq!(dt.format("%9f").to_string(), "026490708");
1011        assert_eq!(dt.format("%R").to_string(), "00:34");
1012        assert_eq!(dt.format("%T").to_string(), "00:34:60");
1013        assert_eq!(dt.format("%X").to_string(), "00:34:60");
1014        assert_eq!(dt.format("%r").to_string(), "12:34:60 AM");
1015
1016        // time zone specifiers
1017        //assert_eq!(dt.format("%Z").to_string(), "ACST");
1018        assert_eq!(dt.format("%z").to_string(), "+0930");
1019        assert_eq!(dt.format("%:z").to_string(), "+09:30");
1020        assert_eq!(dt.format("%::z").to_string(), "+09:30:00");
1021        assert_eq!(dt.format("%:::z").to_string(), "+09");
1022
1023        // date & time specifiers
1024        assert_eq!(dt.format("%c").to_string(), "Sun Jul  8 00:34:60 2001");
1025        assert_eq!(dt.format("%+").to_string(), "2001-07-08T00:34:60.026490708+09:30");
1026
1027        assert_eq!(
1028            dt.with_timezone(&Utc).format("%+").to_string(),
1029            "2001-07-07T15:04:60.026490708+00:00"
1030        );
1031        assert_eq!(
1032            dt.with_timezone(&Utc),
1033            DateTime::parse_from_str("2001-07-07T15:04:60.026490708Z", "%+").unwrap()
1034        );
1035        assert_eq!(
1036            dt.with_timezone(&Utc),
1037            DateTime::parse_from_str("2001-07-07T15:04:60.026490708UTC", "%+").unwrap()
1038        );
1039        assert_eq!(
1040            dt.with_timezone(&Utc),
1041            DateTime::parse_from_str("2001-07-07t15:04:60.026490708utc", "%+").unwrap()
1042        );
1043
1044        assert_eq!(
1045            dt.with_nanosecond(1_026_490_000).unwrap().format("%+").to_string(),
1046            "2001-07-08T00:34:60.026490+09:30"
1047        );
1048        assert_eq!(dt.format("%s").to_string(), "994518299");
1049
1050        // special specifiers
1051        assert_eq!(dt.format("%t").to_string(), "\t");
1052        assert_eq!(dt.format("%n").to_string(), "\n");
1053        assert_eq!(dt.format("%%").to_string(), "%");
1054
1055        // complex format specifiers
1056        assert_eq!(dt.format("  %Y%d%m%%%%%t%H%M%S\t").to_string(), "  20010807%%\t003460\t");
1057        assert_eq!(
1058            dt.format("  %Y%d%m%%%%%t%H:%P:%M%S%:::z\t").to_string(),
1059            "  20010807%%\t00:am:3460+09\t"
1060        );
1061    }
1062
1063    #[test]
1064    #[cfg(all(feature = "unstable-locales", feature = "alloc"))]
1065    fn test_strftime_docs_localized() {
1066        let dt = FixedOffset::east_opt(34200)
1067            .unwrap()
1068            .with_ymd_and_hms(2001, 7, 8, 0, 34, 59)
1069            .unwrap()
1070            .with_nanosecond(1_026_490_708)
1071            .unwrap();
1072
1073        // date specifiers
1074        assert_eq!(dt.format_localized("%b", Locale::fr_BE).to_string(), "jui");
1075        assert_eq!(dt.format_localized("%B", Locale::fr_BE).to_string(), "juillet");
1076        assert_eq!(dt.format_localized("%h", Locale::fr_BE).to_string(), "jui");
1077        assert_eq!(dt.format_localized("%a", Locale::fr_BE).to_string(), "dim");
1078        assert_eq!(dt.format_localized("%A", Locale::fr_BE).to_string(), "dimanche");
1079        assert_eq!(dt.format_localized("%D", Locale::fr_BE).to_string(), "07/08/01");
1080        assert_eq!(dt.format_localized("%x", Locale::fr_BE).to_string(), "08/07/01");
1081        assert_eq!(dt.format_localized("%F", Locale::fr_BE).to_string(), "2001-07-08");
1082        assert_eq!(dt.format_localized("%v", Locale::fr_BE).to_string(), " 8-jui-2001");
1083
1084        // time specifiers
1085        assert_eq!(dt.format_localized("%P", Locale::fr_BE).to_string(), "");
1086        assert_eq!(dt.format_localized("%p", Locale::fr_BE).to_string(), "");
1087        assert_eq!(dt.format_localized("%R", Locale::fr_BE).to_string(), "00:34");
1088        assert_eq!(dt.format_localized("%T", Locale::fr_BE).to_string(), "00:34:60");
1089        assert_eq!(dt.format_localized("%X", Locale::fr_BE).to_string(), "00:34:60");
1090        assert_eq!(dt.format_localized("%r", Locale::fr_BE).to_string(), "00:34:60");
1091
1092        // date & time specifiers
1093        assert_eq!(
1094            dt.format_localized("%c", Locale::fr_BE).to_string(),
1095            "dim 08 jui 2001 00:34:60 +09:30"
1096        );
1097
1098        let nd = NaiveDate::from_ymd_opt(2001, 7, 8).unwrap();
1099
1100        // date specifiers
1101        assert_eq!(nd.format_localized("%b", Locale::de_DE).to_string(), "Jul");
1102        assert_eq!(nd.format_localized("%B", Locale::de_DE).to_string(), "Juli");
1103        assert_eq!(nd.format_localized("%h", Locale::de_DE).to_string(), "Jul");
1104        assert_eq!(nd.format_localized("%a", Locale::de_DE).to_string(), "So");
1105        assert_eq!(nd.format_localized("%A", Locale::de_DE).to_string(), "Sonntag");
1106        assert_eq!(nd.format_localized("%D", Locale::de_DE).to_string(), "07/08/01");
1107        assert_eq!(nd.format_localized("%x", Locale::de_DE).to_string(), "08.07.2001");
1108        assert_eq!(nd.format_localized("%F", Locale::de_DE).to_string(), "2001-07-08");
1109        assert_eq!(nd.format_localized("%v", Locale::de_DE).to_string(), " 8-Jul-2001");
1110    }
1111
1112    /// Ensure parsing a timestamp with the parse-only stftime formatter "%#z" does
1113    /// not cause a panic.
1114    ///
1115    /// See <https://github.com/chronotope/chrono/issues/1139>.
1116    #[test]
1117    #[cfg(feature = "alloc")]
1118    fn test_parse_only_timezone_offset_permissive_no_panic() {
1119        use crate::NaiveDate;
1120        use crate::{FixedOffset, TimeZone};
1121        use std::fmt::Write;
1122
1123        let dt = FixedOffset::east_opt(34200)
1124            .unwrap()
1125            .from_local_datetime(
1126                &NaiveDate::from_ymd_opt(2001, 7, 8)
1127                    .unwrap()
1128                    .and_hms_nano_opt(0, 34, 59, 1_026_490_708)
1129                    .unwrap(),
1130            )
1131            .unwrap();
1132
1133        let mut buf = String::new();
1134        let _ = write!(buf, "{}", dt.format("%#z")).expect_err("parse-only formatter should fail");
1135    }
1136
1137    #[test]
1138    #[cfg(all(feature = "unstable-locales", feature = "alloc"))]
1139    fn test_strftime_localized_korean() {
1140        let dt = FixedOffset::east_opt(34200)
1141            .unwrap()
1142            .with_ymd_and_hms(2001, 7, 8, 0, 34, 59)
1143            .unwrap()
1144            .with_nanosecond(1_026_490_708)
1145            .unwrap();
1146
1147        // date specifiers
1148        assert_eq!(dt.format_localized("%b", Locale::ko_KR).to_string(), " 7월");
1149        assert_eq!(dt.format_localized("%B", Locale::ko_KR).to_string(), "7월");
1150        assert_eq!(dt.format_localized("%h", Locale::ko_KR).to_string(), " 7월");
1151        assert_eq!(dt.format_localized("%a", Locale::ko_KR).to_string(), "일");
1152        assert_eq!(dt.format_localized("%A", Locale::ko_KR).to_string(), "일요일");
1153        assert_eq!(dt.format_localized("%D", Locale::ko_KR).to_string(), "07/08/01");
1154        assert_eq!(dt.format_localized("%x", Locale::ko_KR).to_string(), "2001년 07월 08일");
1155        assert_eq!(dt.format_localized("%F", Locale::ko_KR).to_string(), "2001-07-08");
1156        assert_eq!(dt.format_localized("%v", Locale::ko_KR).to_string(), " 8- 7월-2001");
1157        assert_eq!(dt.format_localized("%r", Locale::ko_KR).to_string(), "오전 12시 34분 60초");
1158
1159        // date & time specifiers
1160        assert_eq!(
1161            dt.format_localized("%c", Locale::ko_KR).to_string(),
1162            "2001년 07월 08일 (일) 오전 12시 34분 60초"
1163        );
1164    }
1165
1166    #[test]
1167    #[cfg(all(feature = "unstable-locales", feature = "alloc"))]
1168    fn test_strftime_localized_japanese() {
1169        let dt = FixedOffset::east_opt(34200)
1170            .unwrap()
1171            .with_ymd_and_hms(2001, 7, 8, 0, 34, 59)
1172            .unwrap()
1173            .with_nanosecond(1_026_490_708)
1174            .unwrap();
1175
1176        // date specifiers
1177        assert_eq!(dt.format_localized("%b", Locale::ja_JP).to_string(), " 7月");
1178        assert_eq!(dt.format_localized("%B", Locale::ja_JP).to_string(), "7月");
1179        assert_eq!(dt.format_localized("%h", Locale::ja_JP).to_string(), " 7月");
1180        assert_eq!(dt.format_localized("%a", Locale::ja_JP).to_string(), "日");
1181        assert_eq!(dt.format_localized("%A", Locale::ja_JP).to_string(), "日曜日");
1182        assert_eq!(dt.format_localized("%D", Locale::ja_JP).to_string(), "07/08/01");
1183        assert_eq!(dt.format_localized("%x", Locale::ja_JP).to_string(), "2001年07月08日");
1184        assert_eq!(dt.format_localized("%F", Locale::ja_JP).to_string(), "2001-07-08");
1185        assert_eq!(dt.format_localized("%v", Locale::ja_JP).to_string(), " 8- 7月-2001");
1186        assert_eq!(dt.format_localized("%r", Locale::ja_JP).to_string(), "午前12時34分60秒");
1187
1188        // date & time specifiers
1189        assert_eq!(
1190            dt.format_localized("%c", Locale::ja_JP).to_string(),
1191            "2001年07月08日 00時34分60秒"
1192        );
1193    }
1194
1195    #[test]
1196    #[cfg(all(feature = "unstable-locales", feature = "alloc"))]
1197    fn test_strftime_localized_time() {
1198        let dt1 = Utc.with_ymd_and_hms(2024, 2, 9, 6, 54, 32).unwrap();
1199        let dt2 = Utc.with_ymd_and_hms(2024, 2, 9, 18, 54, 32).unwrap();
1200        // Some of these locales gave issues before pure-rust-locales 0.8.0 with chrono 0.4.27+
1201        assert_eq!(dt1.format_localized("%X", Locale::nl_NL).to_string(), "06:54:32");
1202        assert_eq!(dt2.format_localized("%X", Locale::nl_NL).to_string(), "18:54:32");
1203        assert_eq!(dt1.format_localized("%X", Locale::en_US).to_string(), "06:54:32 AM");
1204        assert_eq!(dt2.format_localized("%X", Locale::en_US).to_string(), "06:54:32 PM");
1205        assert_eq!(dt1.format_localized("%X", Locale::hy_AM).to_string(), "06:54:32");
1206        assert_eq!(dt2.format_localized("%X", Locale::hy_AM).to_string(), "18:54:32");
1207        assert_eq!(dt1.format_localized("%X", Locale::chr_US).to_string(), "06:54:32 ᏌᎾᎴ");
1208        assert_eq!(dt2.format_localized("%X", Locale::chr_US).to_string(), "06:54:32 ᏒᎯᏱᎢᏗᏢ");
1209    }
1210
1211    #[test]
1212    #[cfg(all(feature = "unstable-locales", target_pointer_width = "64"))]
1213    fn test_type_sizes() {
1214        use core::mem::size_of;
1215        assert_eq!(size_of::<Item>(), 24);
1216        assert_eq!(size_of::<StrftimeItems>(), 56);
1217        assert_eq!(size_of::<Locale>(), 2);
1218    }
1219
1220    #[test]
1221    #[cfg(all(feature = "unstable-locales", target_pointer_width = "32"))]
1222    fn test_type_sizes() {
1223        use core::mem::size_of;
1224        assert_eq!(size_of::<Item>(), 12);
1225        assert_eq!(size_of::<StrftimeItems>(), 28);
1226        assert_eq!(size_of::<Locale>(), 2);
1227    }
1228
1229    #[test]
1230    #[cfg(any(feature = "alloc", feature = "std"))]
1231    fn test_strftime_parse() {
1232        let fmt_str = StrftimeItems::new("%Y-%m-%dT%H:%M:%S%z");
1233        let fmt_items = fmt_str.parse().unwrap();
1234        let dt = Utc.with_ymd_and_hms(2014, 5, 7, 12, 34, 56).unwrap();
1235        assert_eq!(&dt.format_with_items(fmt_items.iter()).to_string(), "2014-05-07T12:34:56+0000");
1236    }
1237
1238    #[test]
1239    #[cfg(any(feature = "alloc", feature = "std"))]
1240    fn test_strftime_parse_lenient() {
1241        let fmt_str = StrftimeItems::new_lenient("%Y-%m-%dT%H:%M:%S%z%Q%.2f%%%");
1242        let fmt_items = fmt_str.parse().unwrap();
1243        let dt = Utc.with_ymd_and_hms(2014, 5, 7, 12, 34, 56).unwrap();
1244        assert_eq!(
1245            &dt.format_with_items(fmt_items.iter()).to_string(),
1246            "2014-05-07T12:34:56+0000%Q%.2f%%"
1247        );
1248    }
1249}