// This is a part of Chrono.
// See README.md and LICENSE.txt for details.

/*!
`strftime`/`strptime`-inspired date and time formatting syntax.

## Specifiers

The following specifiers are available both to formatting and parsing.

| Spec. | Example  | Description                                                                |
|-------|----------|----------------------------------------------------------------------------|
|       |          | **DATE SPECIFIERS:**                                                       |
| `%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 (+/-).|
| `%C`  | `20`     | The proleptic Gregorian year divided by 100, zero-padded to 2 digits. [^1] |
| `%y`  | `01`     | The proleptic Gregorian year modulo 100, zero-padded to 2 digits. [^1]     |
|       |          |                                                                            |
| `%m`  | `07`     | Month number (01--12), zero-padded to 2 digits.                            |
| `%b`  | `Jul`    | Abbreviated month name. Always 3 letters.                                  |
| `%B`  | `July`   | Full month name. Also accepts corresponding abbreviation in parsing.       |
| `%h`  | `Jul`    | Same as `%b`.                                                              |
|       |          |                                                                            |
| `%d`  | `08`     | Day number (01--31), zero-padded to 2 digits.                              |
| `%e`  | ` 8`     | Same as `%d` but space-padded. Same as `%_d`.                              |
|       |          |                                                                            |
| `%a`  | `Sun`    | Abbreviated weekday name. Always 3 letters.                                |
| `%A`  | `Sunday` | Full weekday name. Also accepts corresponding abbreviation in parsing.     |
| `%w`  | `0`      | Sunday = 0, Monday = 1, ..., Saturday = 6.                                 |
| `%u`  | `7`      | Monday = 1, Tuesday = 2, ..., Sunday = 7. (ISO 8601)                       |
|       |          |                                                                            |
| `%U`  | `28`     | Week number starting with Sunday (00--53), zero-padded to 2 digits. [^2]   |
| `%W`  | `27`     | Same as `%U`, but week 1 starts with the first Monday in that year instead.|
|       |          |                                                                            |
| `%G`  | `2001`   | Same as `%Y` but uses the year number in ISO 8601 week date. [^3]          |
| `%g`  | `01`     | Same as `%y` but uses the year number in ISO 8601 week date. [^3]          |
| `%V`  | `27`     | Same as `%U` but uses the week number in ISO 8601 week date (01--53). [^3] |
|       |          |                                                                            |
| `%j`  | `189`    | Day of the year (001--366), zero-padded to 3 digits.                       |
|       |          |                                                                            |
| `%D`  | `07/08/01`    | Month-day-year format. Same as `%m/%d/%y`.                            |
| `%x`  | `07/08/01`    | Locale's date representation (e.g., 12/31/99).                        |
| `%F`  | `2001-07-08`  | Year-month-day format (ISO 8601). Same as `%Y-%m-%d`.                 |
| `%v`  | ` 8-Jul-2001` | Day-month-year format. Same as `%e-%b-%Y`.                            |
|       |          |                                                                            |
|       |          | **TIME SPECIFIERS:**                                                       |
| `%H`  | `00`     | Hour number (00--23), zero-padded to 2 digits.                             |
| `%k`  | ` 0`     | Same as `%H` but space-padded. Same as `%_H`.                              |
| `%I`  | `12`     | Hour number in 12-hour clocks (01--12), zero-padded to 2 digits.           |
| `%l`  | `12`     | Same as `%I` but space-padded. Same as `%_I`.                              |
|       |          |                                                                            |
| `%P`  | `am`     | `am` or `pm` in 12-hour clocks.                                            |
| `%p`  | `AM`     | `AM` or `PM` in 12-hour clocks.                                            |
|       |          |                                                                            |
| `%M`  | `34`     | Minute number (00--59), zero-padded to 2 digits.                           |
| `%S`  | `60`     | Second number (00--60), zero-padded to 2 digits. [^4]                      |
| `%f`  | `26490000`    | Number of nanoseconds since last whole second. [^7]                   |
| `%.f` | `.026490`| Decimal fraction of a second. Consumes the leading dot. [^7]               |
| `%.3f`| `.026`        | Decimal fraction of a second with a fixed length of 3.                |
| `%.6f`| `.026490`     | Decimal fraction of a second with a fixed length of 6.                |
| `%.9f`| `.026490000`  | Decimal fraction of a second with a fixed length of 9.                |
| `%3f` | `026`         | Decimal fraction of a second like `%.3f` but without the leading dot. |
| `%6f` | `026490`      | Decimal fraction of a second like `%.6f` but without the leading dot. |
| `%9f` | `026490000`   | Decimal fraction of a second like `%.9f` but without the leading dot. |
|       |               |                                                                       |
| `%R`  | `00:34`       | Hour-minute format. Same as `%H:%M`.                                  |
| `%T`  | `00:34:60`    | Hour-minute-second format. Same as `%H:%M:%S`.                        |
| `%X`  | `00:34:60`    | Locale's time representation (e.g., 23:13:48).                        |
| `%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. |
|       |          |                                                                            |
|       |          | **TIME ZONE SPECIFIERS:**                                                  |
| `%Z`  | `ACST`   | Local time zone name. Skips all non-whitespace characters during parsing. Identical to `%:z` when formatting. [^8] |
| `%z`  | `+0930`  | Offset from the local time to UTC (with UTC being `+0000`).                |
| `%:z` | `+09:30` | Same as `%z` but with a colon.                                             |
|`%::z`|`+09:30:00`| Offset from the local time to UTC with seconds.                            |
|`%:::z`| `+09`    | Offset from the local time to UTC without minutes.                         |
| `%#z` | `+09`    | *Parsing only:* Same as `%z` but allows minutes to be missing or present.  |
|       |          |                                                                            |
|       |          | **DATE & TIME SPECIFIERS:**                                                |
|`%c`|`Sun Jul  8 00:34:60 2001`|Locale's date and time (e.g., Thu Mar  3 23:05:25 2005).       |
| `%+`  | `2001-07-08T00:34:60.026490+09:30` | ISO 8601 / RFC 3339 date & time format. [^5]     |
|       |               |                                                                       |
| `%s`  | `994518299`   | UNIX timestamp, the number of seconds since 1970-01-01 00:00 UTC. [^6]|
|       |          |                                                                            |
|       |          | **SPECIAL SPECIFIERS:**                                                    |
| `%t`  |          | Literal tab (`\t`).                                                        |
| `%n`  |          | Literal newline (`\n`).                                                    |
| `%%`  |          | Literal percent sign.                                                      |

It is possible to override the default padding behavior of numeric specifiers `%?`.
This is not allowed for other specifiers and will result in the `BAD_FORMAT` error.

Modifier | Description
-------- | -----------
`%-?`    | Suppresses any padding including spaces and zeroes. (e.g. `%j` = `012`, `%-j` = `12`)
`%_?`    | Uses spaces as a padding. (e.g. `%j` = `012`, `%_j` = ` 12`)
`%0?`    | Uses zeroes as a padding. (e.g. `%e` = ` 9`, `%0e` = `09`)

Notes:

[^1]: `%C`, `%y`:
   This is floor division, so 100 BCE (year number -99) will print `-1` and `99` respectively.

[^2]: `%U`:
   Week 1 starts with the first Sunday in that year.
   It is possible to have week 0 for days before the first Sunday.

[^3]: `%G`, `%g`, `%V`:
   Week 1 is the first week with at least 4 days in that year.
   Week 0 does not exist, so this should be used with `%G` or `%g`.

[^4]: `%S`:
   It accounts for leap seconds, so `60` is possible.

[^5]: `%+`: Same as `%Y-%m-%dT%H:%M:%S%.f%:z`, i.e. 0, 3, 6 or 9 fractional
   digits for seconds and colons in the time zone offset.
   <br>
   <br>
   This format also supports having a `Z` or `UTC` in place of `%:z`. They
   are equivalent to `+00:00`.
   <br>
   <br>
   Note that all `T`, `Z`, and `UTC` are parsed case-insensitively.
   <br>
   <br>
   The typical `strftime` implementations have different (and locale-dependent)
   formats for this specifier. While Chrono's format for `%+` is far more
   stable, it is best to avoid this specifier if you want to control the exact
   output.

[^6]: `%s`:
   This is not padded and can be negative.
   For the purpose of Chrono, it only accounts for non-leap seconds
   so it slightly differs from ISO C `strftime` behavior.

[^7]: `%f`, `%.f`:
   <br>
   `%f` and `%.f` are notably different formatting specifiers.<br>
   `%f` counts the number of nanoseconds since the last whole second, while `%.f` is a fraction of a
   second.<br>
   Example: 7μs is formatted as `7000` with `%f`, and formatted as `.000007` with `%.f`.

[^8]: `%Z`:
   Since `chrono` is not aware of timezones beyond their offsets, this specifier
   **only prints the offset** when used for formatting. The timezone abbreviation
   will NOT be printed. See [this issue](https://github.com/chronotope/chrono/issues/960)
   for more information.
   <br>
   <br>
   Offset will not be populated from the parsed data, nor will it be validated.
   Timezone is completely ignored. Similar to the glibc `strptime` treatment of
   this format code.
   <br>
   <br>
   It is not possible to reliably convert from an abbreviation to an offset,
   for example CDT can mean either Central Daylight Time (North America) or
   China Daylight Time.
*/

#[cfg(feature = "alloc")]
extern crate alloc;

use super::{fixed, internal_fixed, num, num0, nums};
#[cfg(feature = "unstable-locales")]
use super::{locales, Locale};
use super::{Fixed, InternalInternal, Item, Numeric, Pad};
#[cfg(any(feature = "alloc", feature = "std"))]
use super::{ParseError, BAD_FORMAT};
#[cfg(all(feature = "alloc", not(feature = "std"), not(test)))]
use alloc::vec::Vec;

/// Parsing iterator for `strftime`-like format strings.
///
/// See the [`format::strftime` module](crate::format::strftime) for supported formatting
/// specifiers.
///
/// `StrftimeItems` is used in combination with more low-level methods such as [`format::parse()`]
/// or [`format_with_items`].
///
/// If formatting or parsing date and time values is not performance-critical, the methods
/// [`parse_from_str`] and [`format`] on types such as [`DateTime`](crate::DateTime) are easier to
/// use.
///
/// [`format`]: crate::DateTime::format
/// [`format_with_items`]: crate::DateTime::format
/// [`parse_from_str`]: crate::DateTime::parse_from_str
/// [`DateTime`]: crate::DateTime
/// [`format::parse()`]: crate::format::parse()
#[derive(Clone, Debug)]
pub struct StrftimeItems<'a> {
    /// Remaining portion of the string.
    remainder: &'a str,
    /// If the current specifier is composed of multiple formatting items (e.g. `%+`),
    /// `queue` stores a slice of `Item`s that have to be returned one by one.
    queue: &'static [Item<'static>],
    #[cfg(feature = "unstable-locales")]
    locale_str: &'a str,
    #[cfg(feature = "unstable-locales")]
    locale: Option<Locale>,
}

impl<'a> StrftimeItems<'a> {
    /// Creates a new parsing iterator from a `strftime`-like format string.
    ///
    /// # Errors
    ///
    /// While iterating [`Item::Error`] will be returned if the format string contains an invalid
    /// or unrecognized formatting specifier.
    ///
    /// # Example
    ///
    /// ```
    /// use chrono::format::*;
    ///
    /// let strftime_parser = StrftimeItems::new("%F"); // %F: year-month-day (ISO 8601)
    ///
    /// const ISO8601_YMD_ITEMS: &[Item<'static>] = &[
    ///     Item::Numeric(Numeric::Year, Pad::Zero),
    ///     Item::Literal("-"),
    ///     Item::Numeric(Numeric::Month, Pad::Zero),
    ///     Item::Literal("-"),
    ///     Item::Numeric(Numeric::Day, Pad::Zero),
    /// ];
    /// assert!(strftime_parser.eq(ISO8601_YMD_ITEMS.iter().cloned()));
    /// ```
    #[must_use]
    pub const fn new(s: &'a str) -> StrftimeItems<'a> {
        #[cfg(not(feature = "unstable-locales"))]
        {
            StrftimeItems { remainder: s, queue: &[] }
        }
        #[cfg(feature = "unstable-locales")]
        {
            StrftimeItems { remainder: s, queue: &[], locale_str: "", locale: None }
        }
    }

    /// Creates a new parsing iterator from a `strftime`-like format string, with some formatting
    /// specifiers adjusted to match [`Locale`].
    ///
    /// Note: `StrftimeItems::new_with_locale` only localizes the *format*. You usually want to
    /// combine it with other locale-aware methods such as
    /// [`DateTime::format_localized_with_items`] to get things like localized month or day names.
    ///
    /// The `%x` formatting specifier will use the local date format, `%X` the local time format,
    ///  and `%c` the local format for date and time.
    /// `%r` will use the local 12-hour clock format (e.g., 11:11:04 PM). Not all locales have such
    /// a format, in which case we fall back to a 24-hour clock (`%X`).
    ///
    /// See the [`format::strftime` module](crate::format::strftime) for all supported formatting
    /// specifiers.
    ///
    ///  [`DateTime::format_localized_with_items`]: crate::DateTime::format_localized_with_items
    ///
    /// # Errors
    ///
    /// While iterating [`Item::Error`] will be returned if the format string contains an invalid
    /// or unrecognized formatting specifier.
    ///
    /// # Example
    ///
    /// ```
    /// # #[cfg(feature = "alloc")] {
    /// use chrono::format::{Locale, StrftimeItems};
    /// use chrono::{FixedOffset, TimeZone};
    ///
    /// let dt = FixedOffset::east_opt(9 * 60 * 60)
    ///     .unwrap()
    ///     .with_ymd_and_hms(2023, 7, 11, 0, 34, 59)
    ///     .unwrap();
    ///
    /// // Note: you usually want to combine `StrftimeItems::new_with_locale` with other
    /// // locale-aware methods such as `DateTime::format_localized_with_items`.
    /// // We use the regular `format_with_items` to show only how the formatting changes.
    ///
    /// let fmtr = dt.format_with_items(StrftimeItems::new_with_locale("%x", Locale::en_US));
    /// assert_eq!(fmtr.to_string(), "07/11/2023");
    /// let fmtr = dt.format_with_items(StrftimeItems::new_with_locale("%x", Locale::ko_KR));
    /// assert_eq!(fmtr.to_string(), "2023년 07월 11일");
    /// let fmtr = dt.format_with_items(StrftimeItems::new_with_locale("%x", Locale::ja_JP));
    /// assert_eq!(fmtr.to_string(), "2023年07月11日");
    /// # }
    /// ```
    #[cfg(feature = "unstable-locales")]
    #[must_use]
    pub const fn new_with_locale(s: &'a str, locale: Locale) -> StrftimeItems<'a> {
        StrftimeItems { remainder: s, queue: &[], locale_str: "", locale: Some(locale) }
    }

    /// Parse format string into a `Vec` of formatting [`Item`]'s.
    ///
    /// If you need to format or parse multiple values with the same format string, it is more
    /// efficient to convert it to a `Vec` of formatting [`Item`]'s than to re-parse the format
    /// string on every use.
    ///
    /// The `format_with_items` methods on [`DateTime`], [`NaiveDateTime`], [`NaiveDate`] and
    /// [`NaiveTime`] accept the result for formatting. [`format::parse()`] can make use of it for
    /// parsing.
    ///
    /// [`DateTime`]: crate::DateTime::format_with_items
    /// [`NaiveDateTime`]: crate::NaiveDateTime::format_with_items
    /// [`NaiveDate`]: crate::NaiveDate::format_with_items
    /// [`NaiveTime`]: crate::NaiveTime::format_with_items
    /// [`format::parse()`]: crate::format::parse()
    ///
    /// # Errors
    ///
    /// Returns an error if the format string contains an invalid or unrecognized formatting
    /// specifier.
    ///
    /// # Example
    ///
    /// ```
    /// use chrono::format::{parse, Parsed, StrftimeItems};
    /// use chrono::NaiveDate;
    ///
    /// let fmt_items = StrftimeItems::new("%e %b %Y %k.%M").parse()?;
    /// let datetime = NaiveDate::from_ymd_opt(2023, 7, 11).unwrap().and_hms_opt(9, 0, 0).unwrap();
    ///
    /// // Formatting
    /// assert_eq!(
    ///     datetime.format_with_items(fmt_items.as_slice().iter()).to_string(),
    ///     "11 Jul 2023  9.00"
    /// );
    ///
    /// // Parsing
    /// let mut parsed = Parsed::new();
    /// parse(&mut parsed, "11 Jul 2023  9.00", fmt_items.as_slice().iter())?;
    /// let parsed_dt = parsed.to_naive_datetime_with_offset(0)?;
    /// assert_eq!(parsed_dt, datetime);
    /// # Ok::<(), chrono::ParseError>(())
    /// ```
    #[cfg(any(feature = "alloc", feature = "std"))]
    pub fn parse(self) -> Result<Vec<Item<'a>>, ParseError> {
        self.into_iter()
            .map(|item| match item == Item::Error {
                false => Ok(item),
                true => Err(BAD_FORMAT),
            })
            .collect()
    }

    /// Parse format string into a `Vec` of [`Item`]'s that contain no references to slices of the
    /// format string.
    ///
    /// A `Vec` created with [`StrftimeItems::parse`] contains references to the format string,
    /// binding the lifetime of the `Vec` to that string. [`StrftimeItems::parse_to_owned`] will
    /// convert the references to owned types.
    ///
    /// # Errors
    ///
    /// Returns an error if the format string contains an invalid or unrecognized formatting
    /// specifier.
    ///
    /// # Example
    ///
    /// ```
    /// use chrono::format::{Item, ParseError, StrftimeItems};
    /// use chrono::NaiveDate;
    ///
    /// fn format_items(date_fmt: &str, time_fmt: &str) -> Result<Vec<Item<'static>>, ParseError> {
    ///     // `fmt_string` is dropped at the end of this function.
    ///     let fmt_string = format!("{} {}", date_fmt, time_fmt);
    ///     StrftimeItems::new(&fmt_string).parse_to_owned()
    /// }
    ///
    /// let fmt_items = format_items("%e %b %Y", "%k.%M")?;
    /// let datetime = NaiveDate::from_ymd_opt(2023, 7, 11).unwrap().and_hms_opt(9, 0, 0).unwrap();
    ///
    /// assert_eq!(
    ///     datetime.format_with_items(fmt_items.as_slice().iter()).to_string(),
    ///     "11 Jul 2023  9.00"
    /// );
    /// # Ok::<(), ParseError>(())
    /// ```
    #[cfg(any(feature = "alloc", feature = "std"))]
    pub fn parse_to_owned(self) -> Result<Vec<Item<'static>>, ParseError> {
        self.into_iter()
            .map(|item| match item == Item::Error {
                false => Ok(item.to_owned()),
                true => Err(BAD_FORMAT),
            })
            .collect()
    }
}

const HAVE_ALTERNATES: &str = "z";

impl<'a> Iterator for StrftimeItems<'a> {
    type Item = Item<'a>;

    fn next(&mut self) -> Option<Item<'a>> {
        // We have items queued to return from a specifier composed of multiple formatting items.
        if let Some((item, remainder)) = self.queue.split_first() {
            self.queue = remainder;
            return Some(item.clone());
        }

        // We are in the middle of parsing the localized formatting string of a specifier.
        #[cfg(feature = "unstable-locales")]
        if !self.locale_str.is_empty() {
            let (remainder, item) = self.parse_next_item(self.locale_str)?;
            self.locale_str = remainder;
            return Some(item);
        }

        // Normal: we are parsing the formatting string.
        let (remainder, item) = self.parse_next_item(self.remainder)?;
        self.remainder = remainder;
        Some(item)
    }
}

impl<'a> StrftimeItems<'a> {
    fn parse_next_item(&mut self, mut remainder: &'a str) -> Option<(&'a str, Item<'a>)> {
        use InternalInternal::*;
        use Item::{Literal, Space};
        use Numeric::*;

        static D_FMT: &[Item<'static>] =
            &[num0(Month), Literal("/"), num0(Day), Literal("/"), num0(YearMod100)];
        static D_T_FMT: &[Item<'static>] = &[
            fixed(Fixed::ShortWeekdayName),
            Space(" "),
            fixed(Fixed::ShortMonthName),
            Space(" "),
            nums(Day),
            Space(" "),
            num0(Hour),
            Literal(":"),
            num0(Minute),
            Literal(":"),
            num0(Second),
            Space(" "),
            num0(Year),
        ];
        static T_FMT: &[Item<'static>] =
            &[num0(Hour), Literal(":"), num0(Minute), Literal(":"), num0(Second)];
        static T_FMT_AMPM: &[Item<'static>] = &[
            num0(Hour12),
            Literal(":"),
            num0(Minute),
            Literal(":"),
            num0(Second),
            Space(" "),
            fixed(Fixed::UpperAmPm),
        ];

        match remainder.chars().next() {
            // we are done
            None => None,

            // the next item is a specifier
            Some('%') => {
                remainder = &remainder[1..];

                macro_rules! next {
                    () => {
                        match remainder.chars().next() {
                            Some(x) => {
                                remainder = &remainder[x.len_utf8()..];
                                x
                            }
                            None => return Some((remainder, Item::Error)), // premature end of string
                        }
                    };
                }

                let spec = next!();
                let pad_override = match spec {
                    '-' => Some(Pad::None),
                    '0' => Some(Pad::Zero),
                    '_' => Some(Pad::Space),
                    _ => None,
                };
                let is_alternate = spec == '#';
                let spec = if pad_override.is_some() || is_alternate { next!() } else { spec };
                if is_alternate && !HAVE_ALTERNATES.contains(spec) {
                    return Some((remainder, Item::Error));
                }

                macro_rules! queue {
                    [$head:expr, $($tail:expr),+ $(,)*] => ({
                        const QUEUE: &'static [Item<'static>] = &[$($tail),+];
                        self.queue = QUEUE;
                        $head
                    })
                }
                #[cfg(not(feature = "unstable-locales"))]
                macro_rules! queue_from_slice {
                    ($slice:expr) => {{
                        self.queue = &$slice[1..];
                        $slice[0].clone()
                    }};
                }

                let item = match spec {
                    'A' => fixed(Fixed::LongWeekdayName),
                    'B' => fixed(Fixed::LongMonthName),
                    'C' => num0(YearDiv100),
                    'D' => {
                        queue![num0(Month), Literal("/"), num0(Day), Literal("/"), num0(YearMod100)]
                    }
                    'F' => queue![num0(Year), Literal("-"), num0(Month), Literal("-"), num0(Day)],
                    'G' => num0(IsoYear),
                    'H' => num0(Hour),
                    'I' => num0(Hour12),
                    'M' => num0(Minute),
                    'P' => fixed(Fixed::LowerAmPm),
                    'R' => queue![num0(Hour), Literal(":"), num0(Minute)],
                    'S' => num0(Second),
                    'T' => {
                        queue![num0(Hour), Literal(":"), num0(Minute), Literal(":"), num0(Second)]
                    }
                    'U' => num0(WeekFromSun),
                    'V' => num0(IsoWeek),
                    'W' => num0(WeekFromMon),
                    #[cfg(not(feature = "unstable-locales"))]
                    'X' => queue_from_slice!(T_FMT),
                    #[cfg(feature = "unstable-locales")]
                    'X' => self.switch_to_locale_str(locales::t_fmt, T_FMT),
                    'Y' => num0(Year),
                    'Z' => fixed(Fixed::TimezoneName),
                    'a' => fixed(Fixed::ShortWeekdayName),
                    'b' | 'h' => fixed(Fixed::ShortMonthName),
                    #[cfg(not(feature = "unstable-locales"))]
                    'c' => queue_from_slice!(D_T_FMT),
                    #[cfg(feature = "unstable-locales")]
                    'c' => self.switch_to_locale_str(locales::d_t_fmt, D_T_FMT),
                    'd' => num0(Day),
                    'e' => nums(Day),
                    'f' => num0(Nanosecond),
                    'g' => num0(IsoYearMod100),
                    'j' => num0(Ordinal),
                    'k' => nums(Hour),
                    'l' => nums(Hour12),
                    'm' => num0(Month),
                    'n' => Space("\n"),
                    'p' => fixed(Fixed::UpperAmPm),
                    #[cfg(not(feature = "unstable-locales"))]
                    'r' => queue_from_slice!(T_FMT_AMPM),
                    #[cfg(feature = "unstable-locales")]
                    'r' => {
                        if self.locale.is_some()
                            && locales::t_fmt_ampm(self.locale.unwrap()).is_empty()
                        {
                            // 12-hour clock not supported by this locale. Switch to 24-hour format.
                            self.switch_to_locale_str(locales::t_fmt, T_FMT)
                        } else {
                            self.switch_to_locale_str(locales::t_fmt_ampm, T_FMT_AMPM)
                        }
                    }
                    's' => num(Timestamp),
                    't' => Space("\t"),
                    'u' => num(WeekdayFromMon),
                    'v' => {
                        queue![
                            nums(Day),
                            Literal("-"),
                            fixed(Fixed::ShortMonthName),
                            Literal("-"),
                            num0(Year)
                        ]
                    }
                    'w' => num(NumDaysFromSun),
                    #[cfg(not(feature = "unstable-locales"))]
                    'x' => queue_from_slice!(D_FMT),
                    #[cfg(feature = "unstable-locales")]
                    'x' => self.switch_to_locale_str(locales::d_fmt, D_FMT),
                    'y' => num0(YearMod100),
                    'z' => {
                        if is_alternate {
                            internal_fixed(TimezoneOffsetPermissive)
                        } else {
                            fixed(Fixed::TimezoneOffset)
                        }
                    }
                    '+' => fixed(Fixed::RFC3339),
                    ':' => {
                        if remainder.starts_with("::z") {
                            remainder = &remainder[3..];
                            fixed(Fixed::TimezoneOffsetTripleColon)
                        } else if remainder.starts_with(":z") {
                            remainder = &remainder[2..];
                            fixed(Fixed::TimezoneOffsetDoubleColon)
                        } else if remainder.starts_with('z') {
                            remainder = &remainder[1..];
                            fixed(Fixed::TimezoneOffsetColon)
                        } else {
                            Item::Error
                        }
                    }
                    '.' => match next!() {
                        '3' => match next!() {
                            'f' => fixed(Fixed::Nanosecond3),
                            _ => Item::Error,
                        },
                        '6' => match next!() {
                            'f' => fixed(Fixed::Nanosecond6),
                            _ => Item::Error,
                        },
                        '9' => match next!() {
                            'f' => fixed(Fixed::Nanosecond9),
                            _ => Item::Error,
                        },
                        'f' => fixed(Fixed::Nanosecond),
                        _ => Item::Error,
                    },
                    '3' => match next!() {
                        'f' => internal_fixed(Nanosecond3NoDot),
                        _ => Item::Error,
                    },
                    '6' => match next!() {
                        'f' => internal_fixed(Nanosecond6NoDot),
                        _ => Item::Error,
                    },
                    '9' => match next!() {
                        'f' => internal_fixed(Nanosecond9NoDot),
                        _ => Item::Error,
                    },
                    '%' => Literal("%"),
                    _ => Item::Error, // no such specifier
                };

                // Adjust `item` if we have any padding modifier.
                // Not allowed on non-numeric items or on specifiers composed out of multiple
                // formatting items.
                if let Some(new_pad) = pad_override {
                    match item {
                        Item::Numeric(ref kind, _pad) if self.queue.is_empty() => {
                            Some((remainder, Item::Numeric(kind.clone(), new_pad)))
                        }
                        _ => Some((remainder, Item::Error)),
                    }
                } else {
                    Some((remainder, item))
                }
            }

            // the next item is space
            Some(c) if c.is_whitespace() => {
                // `%` is not a whitespace, so `c != '%'` is redundant
                let nextspec =
                    remainder.find(|c: char| !c.is_whitespace()).unwrap_or(remainder.len());
                assert!(nextspec > 0);
                let item = Space(&remainder[..nextspec]);
                remainder = &remainder[nextspec..];
                Some((remainder, item))
            }

            // the next item is literal
            _ => {
                let nextspec = remainder
                    .find(|c: char| c.is_whitespace() || c == '%')
                    .unwrap_or(remainder.len());
                assert!(nextspec > 0);
                let item = Literal(&remainder[..nextspec]);
                remainder = &remainder[nextspec..];
                Some((remainder, item))
            }
        }
    }

    #[cfg(feature = "unstable-locales")]
    fn switch_to_locale_str(
        &mut self,
        localized_fmt_str: impl Fn(Locale) -> &'static str,
        fallback: &'static [Item<'static>],
    ) -> Item<'a> {
        if let Some(locale) = self.locale {
            assert!(self.locale_str.is_empty());
            let (fmt_str, item) = self.parse_next_item(localized_fmt_str(locale)).unwrap();
            self.locale_str = fmt_str;
            item
        } else {
            self.queue = &fallback[1..];
            fallback[0].clone()
        }
    }
}

#[cfg(test)]
mod tests {
    use super::StrftimeItems;
    use crate::format::Item::{self, Literal, Space};
    #[cfg(feature = "unstable-locales")]
    use crate::format::Locale;
    use crate::format::{fixed, internal_fixed, num, num0, nums};
    use crate::format::{Fixed, InternalInternal, Numeric::*};
    #[cfg(feature = "alloc")]
    use crate::{DateTime, FixedOffset, NaiveDate, TimeZone, Timelike, Utc};

    #[test]
    fn test_strftime_items() {
        fn parse_and_collect(s: &str) -> Vec<Item<'_>> {
            // map any error into `[Item::Error]`. useful for easy testing.
            eprintln!("test_strftime_items: parse_and_collect({:?})", s);
            let items = StrftimeItems::new(s);
            let items = items.map(|spec| if spec == Item::Error { None } else { Some(spec) });
            items.collect::<Option<Vec<_>>>().unwrap_or_else(|| vec![Item::Error])
        }

        assert_eq!(parse_and_collect(""), []);
        assert_eq!(parse_and_collect(" "), [Space(" ")]);
        assert_eq!(parse_and_collect("  "), [Space("  ")]);
        // ne!
        assert_ne!(parse_and_collect("  "), [Space(" "), Space(" ")]);
        // eq!
        assert_eq!(parse_and_collect("  "), [Space("  ")]);
        assert_eq!(parse_and_collect("a"), [Literal("a")]);
        assert_eq!(parse_and_collect("ab"), [Literal("ab")]);
        assert_eq!(parse_and_collect("😽"), [Literal("😽")]);
        assert_eq!(parse_and_collect("a😽"), [Literal("a😽")]);
        assert_eq!(parse_and_collect("😽a"), [Literal("😽a")]);
        assert_eq!(parse_and_collect(" 😽"), [Space(" "), Literal("😽")]);
        assert_eq!(parse_and_collect("😽 "), [Literal("😽"), Space(" ")]);
        // ne!
        assert_ne!(parse_and_collect("😽😽"), [Literal("😽")]);
        assert_ne!(parse_and_collect("😽"), [Literal("😽😽")]);
        assert_ne!(parse_and_collect("😽😽"), [Literal("😽😽"), Literal("😽")]);
        // eq!
        assert_eq!(parse_and_collect("😽😽"), [Literal("😽😽")]);
        assert_eq!(parse_and_collect(" \t\n\r "), [Space(" \t\n\r ")]);
        assert_eq!(parse_and_collect("hello?"), [Literal("hello?")]);
        assert_eq!(
            parse_and_collect("a  b\t\nc"),
            [Literal("a"), Space("  "), Literal("b"), Space("\t\n"), Literal("c")]
        );
        assert_eq!(parse_and_collect("100%%"), [Literal("100"), Literal("%")]);
        assert_eq!(
            parse_and_collect("100%% ok"),
            [Literal("100"), Literal("%"), Space(" "), Literal("ok")]
        );
        assert_eq!(parse_and_collect("%%PDF-1.0"), [Literal("%"), Literal("PDF-1.0")]);
        assert_eq!(
            parse_and_collect("%Y-%m-%d"),
            [num0(Year), Literal("-"), num0(Month), Literal("-"), num0(Day)]
        );
        assert_eq!(parse_and_collect("😽   "), [Literal("😽"), Space("   ")]);
        assert_eq!(parse_and_collect("😽😽"), [Literal("😽😽")]);
        assert_eq!(parse_and_collect("😽😽😽"), [Literal("😽😽😽")]);
        assert_eq!(parse_and_collect("😽😽 😽"), [Literal("😽😽"), Space(" "), Literal("😽")]);
        assert_eq!(parse_and_collect("😽😽a 😽"), [Literal("😽😽a"), Space(" "), Literal("😽")]);
        assert_eq!(parse_and_collect("😽😽a b😽"), [Literal("😽😽a"), Space(" "), Literal("b😽")]);
        assert_eq!(
            parse_and_collect("😽😽a b😽c"),
            [Literal("😽😽a"), Space(" "), Literal("b😽c")]
        );
        assert_eq!(parse_and_collect("😽😽   "), [Literal("😽😽"), Space("   ")]);
        assert_eq!(parse_and_collect("😽😽   😽"), [Literal("😽😽"), Space("   "), Literal("😽")]);
        assert_eq!(parse_and_collect("   😽"), [Space("   "), Literal("😽")]);
        assert_eq!(parse_and_collect("   😽 "), [Space("   "), Literal("😽"), Space(" ")]);
        assert_eq!(
            parse_and_collect("   😽 😽"),
            [Space("   "), Literal("😽"), Space(" "), Literal("😽")]
        );
        assert_eq!(
            parse_and_collect("   😽 😽 "),
            [Space("   "), Literal("😽"), Space(" "), Literal("😽"), Space(" ")]
        );
        assert_eq!(
            parse_and_collect("   😽  😽 "),
            [Space("   "), Literal("😽"), Space("  "), Literal("😽"), Space(" ")]
        );
        assert_eq!(
            parse_and_collect("   😽  😽😽 "),
            [Space("   "), Literal("😽"), Space("  "), Literal("😽😽"), Space(" ")]
        );
        assert_eq!(parse_and_collect("   😽😽"), [Space("   "), Literal("😽😽")]);
        assert_eq!(parse_and_collect("   😽😽 "), [Space("   "), Literal("😽😽"), Space(" ")]);
        assert_eq!(
            parse_and_collect("   😽😽    "),
            [Space("   "), Literal("😽😽"), Space("    ")]
        );
        assert_eq!(
            parse_and_collect("   😽😽    "),
            [Space("   "), Literal("😽😽"), Space("    ")]
        );
        assert_eq!(parse_and_collect(" 😽😽    "), [Space(" "), Literal("😽😽"), Space("    ")]);
        assert_eq!(
            parse_and_collect(" 😽 😽😽    "),
            [Space(" "), Literal("😽"), Space(" "), Literal("😽😽"), Space("    ")]
        );
        assert_eq!(
            parse_and_collect(" 😽 😽はい😽    ハンバーガー"),
            [
                Space(" "),
                Literal("😽"),
                Space(" "),
                Literal("😽はい😽"),
                Space("    "),
                Literal("ハンバーガー")
            ]
        );
        assert_eq!(
            parse_and_collect("%%😽%%😽"),
            [Literal("%"), Literal("😽"), Literal("%"), Literal("😽")]
        );
        assert_eq!(parse_and_collect("%Y--%m"), [num0(Year), Literal("--"), num0(Month)]);
        assert_eq!(parse_and_collect("[%F]"), parse_and_collect("[%Y-%m-%d]"));
        assert_eq!(parse_and_collect("100%%😽"), [Literal("100"), Literal("%"), Literal("😽")]);
        assert_eq!(
            parse_and_collect("100%%😽%%a"),
            [Literal("100"), Literal("%"), Literal("😽"), Literal("%"), Literal("a")]
        );
        assert_eq!(parse_and_collect("😽100%%"), [Literal("😽100"), Literal("%")]);
        assert_eq!(parse_and_collect("%m %d"), [num0(Month), Space(" "), num0(Day)]);
        assert_eq!(parse_and_collect("%"), [Item::Error]);
        assert_eq!(parse_and_collect("%%"), [Literal("%")]);
        assert_eq!(parse_and_collect("%%%"), [Item::Error]);
        assert_eq!(parse_and_collect("%a"), [fixed(Fixed::ShortWeekdayName)]);
        assert_eq!(parse_and_collect("%aa"), [fixed(Fixed::ShortWeekdayName), Literal("a")]);
        assert_eq!(parse_and_collect("%%a%"), [Item::Error]);
        assert_eq!(parse_and_collect("%😽"), [Item::Error]);
        assert_eq!(parse_and_collect("%😽😽"), [Item::Error]);
        assert_eq!(parse_and_collect("%%%%"), [Literal("%"), Literal("%")]);
        assert_eq!(
            parse_and_collect("%%%%ハンバーガー"),
            [Literal("%"), Literal("%"), Literal("ハンバーガー")]
        );
        assert_eq!(parse_and_collect("foo%?"), [Item::Error]);
        assert_eq!(parse_and_collect("bar%42"), [Item::Error]);
        assert_eq!(parse_and_collect("quux% +"), [Item::Error]);
        assert_eq!(parse_and_collect("%.Z"), [Item::Error]);
        assert_eq!(parse_and_collect("%:Z"), [Item::Error]);
        assert_eq!(parse_and_collect("%-Z"), [Item::Error]);
        assert_eq!(parse_and_collect("%0Z"), [Item::Error]);
        assert_eq!(parse_and_collect("%_Z"), [Item::Error]);
        assert_eq!(parse_and_collect("%.j"), [Item::Error]);
        assert_eq!(parse_and_collect("%:j"), [Item::Error]);
        assert_eq!(parse_and_collect("%-j"), [num(Ordinal)]);
        assert_eq!(parse_and_collect("%0j"), [num0(Ordinal)]);
        assert_eq!(parse_and_collect("%_j"), [nums(Ordinal)]);
        assert_eq!(parse_and_collect("%.e"), [Item::Error]);
        assert_eq!(parse_and_collect("%:e"), [Item::Error]);
        assert_eq!(parse_and_collect("%-e"), [num(Day)]);
        assert_eq!(parse_and_collect("%0e"), [num0(Day)]);
        assert_eq!(parse_and_collect("%_e"), [nums(Day)]);
        assert_eq!(parse_and_collect("%z"), [fixed(Fixed::TimezoneOffset)]);
        assert_eq!(parse_and_collect("%:z"), [fixed(Fixed::TimezoneOffsetColon)]);
        assert_eq!(parse_and_collect("%Z"), [fixed(Fixed::TimezoneName)]);
        assert_eq!(parse_and_collect("%ZZZZ"), [fixed(Fixed::TimezoneName), Literal("ZZZ")]);
        assert_eq!(parse_and_collect("%Z😽"), [fixed(Fixed::TimezoneName), Literal("😽")]);
        assert_eq!(
            parse_and_collect("%#z"),
            [internal_fixed(InternalInternal::TimezoneOffsetPermissive)]
        );
        assert_eq!(parse_and_collect("%#m"), [Item::Error]);
    }

    #[test]
    #[cfg(feature = "alloc")]
    fn test_strftime_docs() {
        let dt = FixedOffset::east_opt(34200)
            .unwrap()
            .from_local_datetime(
                &NaiveDate::from_ymd_opt(2001, 7, 8)
                    .unwrap()
                    .and_hms_nano_opt(0, 34, 59, 1_026_490_708)
                    .unwrap(),
            )
            .unwrap();

        // date specifiers
        assert_eq!(dt.format("%Y").to_string(), "2001");
        assert_eq!(dt.format("%C").to_string(), "20");
        assert_eq!(dt.format("%y").to_string(), "01");
        assert_eq!(dt.format("%m").to_string(), "07");
        assert_eq!(dt.format("%b").to_string(), "Jul");
        assert_eq!(dt.format("%B").to_string(), "July");
        assert_eq!(dt.format("%h").to_string(), "Jul");
        assert_eq!(dt.format("%d").to_string(), "08");
        assert_eq!(dt.format("%e").to_string(), " 8");
        assert_eq!(dt.format("%e").to_string(), dt.format("%_d").to_string());
        assert_eq!(dt.format("%a").to_string(), "Sun");
        assert_eq!(dt.format("%A").to_string(), "Sunday");
        assert_eq!(dt.format("%w").to_string(), "0");
        assert_eq!(dt.format("%u").to_string(), "7");
        assert_eq!(dt.format("%U").to_string(), "27");
        assert_eq!(dt.format("%W").to_string(), "27");
        assert_eq!(dt.format("%G").to_string(), "2001");
        assert_eq!(dt.format("%g").to_string(), "01");
        assert_eq!(dt.format("%V").to_string(), "27");
        assert_eq!(dt.format("%j").to_string(), "189");
        assert_eq!(dt.format("%D").to_string(), "07/08/01");
        assert_eq!(dt.format("%x").to_string(), "07/08/01");
        assert_eq!(dt.format("%F").to_string(), "2001-07-08");
        assert_eq!(dt.format("%v").to_string(), " 8-Jul-2001");

        // time specifiers
        assert_eq!(dt.format("%H").to_string(), "00");
        assert_eq!(dt.format("%k").to_string(), " 0");
        assert_eq!(dt.format("%k").to_string(), dt.format("%_H").to_string());
        assert_eq!(dt.format("%I").to_string(), "12");
        assert_eq!(dt.format("%l").to_string(), "12");
        assert_eq!(dt.format("%l").to_string(), dt.format("%_I").to_string());
        assert_eq!(dt.format("%P").to_string(), "am");
        assert_eq!(dt.format("%p").to_string(), "AM");
        assert_eq!(dt.format("%M").to_string(), "34");
        assert_eq!(dt.format("%S").to_string(), "60");
        assert_eq!(dt.format("%f").to_string(), "026490708");
        assert_eq!(dt.format("%.f").to_string(), ".026490708");
        assert_eq!(dt.with_nanosecond(1_026_490_000).unwrap().format("%.f").to_string(), ".026490");
        assert_eq!(dt.format("%.3f").to_string(), ".026");
        assert_eq!(dt.format("%.6f").to_string(), ".026490");
        assert_eq!(dt.format("%.9f").to_string(), ".026490708");
        assert_eq!(dt.format("%3f").to_string(), "026");
        assert_eq!(dt.format("%6f").to_string(), "026490");
        assert_eq!(dt.format("%9f").to_string(), "026490708");
        assert_eq!(dt.format("%R").to_string(), "00:34");
        assert_eq!(dt.format("%T").to_string(), "00:34:60");
        assert_eq!(dt.format("%X").to_string(), "00:34:60");
        assert_eq!(dt.format("%r").to_string(), "12:34:60 AM");

        // time zone specifiers
        //assert_eq!(dt.format("%Z").to_string(), "ACST");
        assert_eq!(dt.format("%z").to_string(), "+0930");
        assert_eq!(dt.format("%:z").to_string(), "+09:30");
        assert_eq!(dt.format("%::z").to_string(), "+09:30:00");
        assert_eq!(dt.format("%:::z").to_string(), "+09");

        // date & time specifiers
        assert_eq!(dt.format("%c").to_string(), "Sun Jul  8 00:34:60 2001");
        assert_eq!(dt.format("%+").to_string(), "2001-07-08T00:34:60.026490708+09:30");

        assert_eq!(
            dt.with_timezone(&Utc).format("%+").to_string(),
            "2001-07-07T15:04:60.026490708+00:00"
        );
        assert_eq!(
            dt.with_timezone(&Utc),
            DateTime::parse_from_str("2001-07-07T15:04:60.026490708Z", "%+").unwrap()
        );
        assert_eq!(
            dt.with_timezone(&Utc),
            DateTime::parse_from_str("2001-07-07T15:04:60.026490708UTC", "%+").unwrap()
        );
        assert_eq!(
            dt.with_timezone(&Utc),
            DateTime::parse_from_str("2001-07-07t15:04:60.026490708utc", "%+").unwrap()
        );

        assert_eq!(
            dt.with_nanosecond(1_026_490_000).unwrap().format("%+").to_string(),
            "2001-07-08T00:34:60.026490+09:30"
        );
        assert_eq!(dt.format("%s").to_string(), "994518299");

        // special specifiers
        assert_eq!(dt.format("%t").to_string(), "\t");
        assert_eq!(dt.format("%n").to_string(), "\n");
        assert_eq!(dt.format("%%").to_string(), "%");

        // complex format specifiers
        assert_eq!(dt.format("  %Y%d%m%%%%%t%H%M%S\t").to_string(), "  20010807%%\t003460\t");
        assert_eq!(
            dt.format("  %Y%d%m%%%%%t%H:%P:%M%S%:::z\t").to_string(),
            "  20010807%%\t00:am:3460+09\t"
        );
    }

    #[test]
    #[cfg(all(feature = "unstable-locales", feature = "alloc"))]
    fn test_strftime_docs_localized() {
        let dt = FixedOffset::east_opt(34200)
            .unwrap()
            .with_ymd_and_hms(2001, 7, 8, 0, 34, 59)
            .unwrap()
            .with_nanosecond(1_026_490_708)
            .unwrap();

        // date specifiers
        assert_eq!(dt.format_localized("%b", Locale::fr_BE).to_string(), "jui");
        assert_eq!(dt.format_localized("%B", Locale::fr_BE).to_string(), "juillet");
        assert_eq!(dt.format_localized("%h", Locale::fr_BE).to_string(), "jui");
        assert_eq!(dt.format_localized("%a", Locale::fr_BE).to_string(), "dim");
        assert_eq!(dt.format_localized("%A", Locale::fr_BE).to_string(), "dimanche");
        assert_eq!(dt.format_localized("%D", Locale::fr_BE).to_string(), "07/08/01");
        assert_eq!(dt.format_localized("%x", Locale::fr_BE).to_string(), "08/07/01");
        assert_eq!(dt.format_localized("%F", Locale::fr_BE).to_string(), "2001-07-08");
        assert_eq!(dt.format_localized("%v", Locale::fr_BE).to_string(), " 8-jui-2001");

        // time specifiers
        assert_eq!(dt.format_localized("%P", Locale::fr_BE).to_string(), "");
        assert_eq!(dt.format_localized("%p", Locale::fr_BE).to_string(), "");
        assert_eq!(dt.format_localized("%R", Locale::fr_BE).to_string(), "00:34");
        assert_eq!(dt.format_localized("%T", Locale::fr_BE).to_string(), "00:34:60");
        assert_eq!(dt.format_localized("%X", Locale::fr_BE).to_string(), "00:34:60");
        assert_eq!(dt.format_localized("%r", Locale::fr_BE).to_string(), "00:34:60");

        // date & time specifiers
        assert_eq!(
            dt.format_localized("%c", Locale::fr_BE).to_string(),
            "dim 08 jui 2001 00:34:60 +09:30"
        );

        let nd = NaiveDate::from_ymd_opt(2001, 7, 8).unwrap();

        // date specifiers
        assert_eq!(nd.format_localized("%b", Locale::de_DE).to_string(), "Jul");
        assert_eq!(nd.format_localized("%B", Locale::de_DE).to_string(), "Juli");
        assert_eq!(nd.format_localized("%h", Locale::de_DE).to_string(), "Jul");
        assert_eq!(nd.format_localized("%a", Locale::de_DE).to_string(), "So");
        assert_eq!(nd.format_localized("%A", Locale::de_DE).to_string(), "Sonntag");
        assert_eq!(nd.format_localized("%D", Locale::de_DE).to_string(), "07/08/01");
        assert_eq!(nd.format_localized("%x", Locale::de_DE).to_string(), "08.07.2001");
        assert_eq!(nd.format_localized("%F", Locale::de_DE).to_string(), "2001-07-08");
        assert_eq!(nd.format_localized("%v", Locale::de_DE).to_string(), " 8-Jul-2001");
    }

    /// Ensure parsing a timestamp with the parse-only stftime formatter "%#z" does
    /// not cause a panic.
    ///
    /// See <https://github.com/chronotope/chrono/issues/1139>.
    #[test]
    #[cfg(feature = "alloc")]
    fn test_parse_only_timezone_offset_permissive_no_panic() {
        use crate::NaiveDate;
        use crate::{FixedOffset, TimeZone};
        use std::fmt::Write;

        let dt = FixedOffset::east_opt(34200)
            .unwrap()
            .from_local_datetime(
                &NaiveDate::from_ymd_opt(2001, 7, 8)
                    .unwrap()
                    .and_hms_nano_opt(0, 34, 59, 1_026_490_708)
                    .unwrap(),
            )
            .unwrap();

        let mut buf = String::new();
        let _ = write!(buf, "{}", dt.format("%#z")).expect_err("parse-only formatter should fail");
    }

    #[test]
    #[cfg(all(feature = "unstable-locales", feature = "alloc"))]
    fn test_strftime_localized_korean() {
        let dt = FixedOffset::east_opt(34200)
            .unwrap()
            .with_ymd_and_hms(2001, 7, 8, 0, 34, 59)
            .unwrap()
            .with_nanosecond(1_026_490_708)
            .unwrap();

        // date specifiers
        assert_eq!(dt.format_localized("%b", Locale::ko_KR).to_string(), " 7월");
        assert_eq!(dt.format_localized("%B", Locale::ko_KR).to_string(), "7월");
        assert_eq!(dt.format_localized("%h", Locale::ko_KR).to_string(), " 7월");
        assert_eq!(dt.format_localized("%a", Locale::ko_KR).to_string(), "일");
        assert_eq!(dt.format_localized("%A", Locale::ko_KR).to_string(), "일요일");
        assert_eq!(dt.format_localized("%D", Locale::ko_KR).to_string(), "07/08/01");
        assert_eq!(dt.format_localized("%x", Locale::ko_KR).to_string(), "2001년 07월 08일");
        assert_eq!(dt.format_localized("%F", Locale::ko_KR).to_string(), "2001-07-08");
        assert_eq!(dt.format_localized("%v", Locale::ko_KR).to_string(), " 8- 7월-2001");
        assert_eq!(dt.format_localized("%r", Locale::ko_KR).to_string(), "오전 12시 34분 60초");

        // date & time specifiers
        assert_eq!(
            dt.format_localized("%c", Locale::ko_KR).to_string(),
            "2001년 07월 08일 (일) 오전 12시 34분 60초"
        );
    }

    #[test]
    #[cfg(all(feature = "unstable-locales", feature = "alloc"))]
    fn test_strftime_localized_japanese() {
        let dt = FixedOffset::east_opt(34200)
            .unwrap()
            .with_ymd_and_hms(2001, 7, 8, 0, 34, 59)
            .unwrap()
            .with_nanosecond(1_026_490_708)
            .unwrap();

        // date specifiers
        assert_eq!(dt.format_localized("%b", Locale::ja_JP).to_string(), " 7月");
        assert_eq!(dt.format_localized("%B", Locale::ja_JP).to_string(), "7月");
        assert_eq!(dt.format_localized("%h", Locale::ja_JP).to_string(), " 7月");
        assert_eq!(dt.format_localized("%a", Locale::ja_JP).to_string(), "日");
        assert_eq!(dt.format_localized("%A", Locale::ja_JP).to_string(), "日曜日");
        assert_eq!(dt.format_localized("%D", Locale::ja_JP).to_string(), "07/08/01");
        assert_eq!(dt.format_localized("%x", Locale::ja_JP).to_string(), "2001年07月08日");
        assert_eq!(dt.format_localized("%F", Locale::ja_JP).to_string(), "2001-07-08");
        assert_eq!(dt.format_localized("%v", Locale::ja_JP).to_string(), " 8- 7月-2001");
        assert_eq!(dt.format_localized("%r", Locale::ja_JP).to_string(), "午前12時34分60秒");

        // date & time specifiers
        assert_eq!(
            dt.format_localized("%c", Locale::ja_JP).to_string(),
            "2001年07月08日 00時34分60秒"
        );
    }

    #[test]
    #[cfg(all(feature = "unstable-locales", feature = "alloc"))]
    fn test_strftime_localized_time() {
        let dt1 = Utc.with_ymd_and_hms(2024, 2, 9, 6, 54, 32).unwrap();
        let dt2 = Utc.with_ymd_and_hms(2024, 2, 9, 18, 54, 32).unwrap();
        // Some of these locales gave issues before pure-rust-locales 0.8.0 with chrono 0.4.27+
        assert_eq!(dt1.format_localized("%X", Locale::nl_NL).to_string(), "06:54:32");
        assert_eq!(dt2.format_localized("%X", Locale::nl_NL).to_string(), "18:54:32");
        assert_eq!(dt1.format_localized("%X", Locale::en_US).to_string(), "06:54:32 AM");
        assert_eq!(dt2.format_localized("%X", Locale::en_US).to_string(), "06:54:32 PM");
        assert_eq!(dt1.format_localized("%X", Locale::hy_AM).to_string(), "06:54:32");
        assert_eq!(dt2.format_localized("%X", Locale::hy_AM).to_string(), "18:54:32");
        assert_eq!(dt1.format_localized("%X", Locale::chr_US).to_string(), "06:54:32 ᏌᎾᎴ");
        assert_eq!(dt2.format_localized("%X", Locale::chr_US).to_string(), "06:54:32 ᏒᎯᏱᎢᏗᏢ");
    }

    #[test]
    #[cfg(all(feature = "unstable-locales", target_pointer_width = "64"))]
    fn test_type_sizes() {
        use core::mem::size_of;
        assert_eq!(size_of::<Item>(), 24);
        assert_eq!(size_of::<StrftimeItems>(), 56);
        assert_eq!(size_of::<Locale>(), 2);
    }

    #[test]
    #[cfg(all(feature = "unstable-locales", target_pointer_width = "32"))]
    fn test_type_sizes() {
        use core::mem::size_of;
        assert_eq!(size_of::<Item>(), 12);
        assert_eq!(size_of::<StrftimeItems>(), 28);
        assert_eq!(size_of::<Locale>(), 2);
    }

    #[test]
    #[cfg(any(feature = "alloc", feature = "std"))]
    fn test_strftime_parse() {
        let fmt_str = StrftimeItems::new("%Y-%m-%dT%H:%M:%S%z");
        let fmt_items = fmt_str.parse().unwrap();
        let dt = Utc.with_ymd_and_hms(2014, 5, 7, 12, 34, 56).unwrap();
        assert_eq!(&dt.format_with_items(fmt_items.iter()).to_string(), "2014-05-07T12:34:56+0000");
    }
}
