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