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