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