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