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