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