1use super::{fixed, internal_fixed, num, num0, nums};
160#[cfg(feature = "unstable-locales")]
161use super::{locales, Locale};
162use super::{Fixed, InternalInternal, Item, Numeric, Pad};
163
164#[derive(Clone, Debug)]
166pub struct StrftimeItems<'a> {
167 remainder: &'a str,
169 queue: &'static [Item<'static>],
172 #[cfg(feature = "unstable-locales")]
173 locale_str: &'a str,
174 #[cfg(feature = "unstable-locales")]
175 locale: Option<Locale>,
176}
177
178impl<'a> StrftimeItems<'a> {
179 #[must_use]
181 pub const fn new(s: &'a str) -> StrftimeItems<'a> {
182 #[cfg(not(feature = "unstable-locales"))]
183 {
184 StrftimeItems { remainder: s, queue: &[] }
185 }
186 #[cfg(feature = "unstable-locales")]
187 {
188 StrftimeItems { remainder: s, queue: &[], locale_str: "", locale: None }
189 }
190 }
191
192 #[cfg(feature = "unstable-locales")]
194 #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))]
195 #[must_use]
196 pub const fn new_with_locale(s: &'a str, locale: Locale) -> StrftimeItems<'a> {
197 StrftimeItems { remainder: s, queue: &[], locale_str: "", locale: Some(locale) }
198 }
199}
200
201const HAVE_ALTERNATES: &str = "z";
202
203impl<'a> Iterator for StrftimeItems<'a> {
204 type Item = Item<'a>;
205
206 fn next(&mut self) -> Option<Item<'a>> {
207 if let Some((item, remainder)) = self.queue.split_first() {
209 self.queue = remainder;
210 return Some(item.clone());
211 }
212
213 #[cfg(feature = "unstable-locales")]
215 if !self.locale_str.is_empty() {
216 let (remainder, item) = self.parse_next_item(self.locale_str)?;
217 self.locale_str = remainder;
218 return Some(item);
219 }
220
221 let (remainder, item) = self.parse_next_item(self.remainder)?;
223 self.remainder = remainder;
224 Some(item)
225 }
226}
227
228impl<'a> StrftimeItems<'a> {
229 fn parse_next_item(&mut self, mut remainder: &'a str) -> Option<(&'a str, Item<'a>)> {
230 use InternalInternal::*;
231 use Item::{Literal, Space};
232 use Numeric::*;
233
234 static D_FMT: &[Item<'static>] =
235 &[num0(Month), Literal("/"), num0(Day), Literal("/"), num0(YearMod100)];
236 static D_T_FMT: &[Item<'static>] = &[
237 fixed(Fixed::ShortWeekdayName),
238 Space(" "),
239 fixed(Fixed::ShortMonthName),
240 Space(" "),
241 nums(Day),
242 Space(" "),
243 num0(Hour),
244 Literal(":"),
245 num0(Minute),
246 Literal(":"),
247 num0(Second),
248 Space(" "),
249 num0(Year),
250 ];
251 static T_FMT: &[Item<'static>] =
252 &[num0(Hour), Literal(":"), num0(Minute), Literal(":"), num0(Second)];
253 static T_FMT_AMPM: &[Item<'static>] = &[
254 num0(Hour12),
255 Literal(":"),
256 num0(Minute),
257 Literal(":"),
258 num0(Second),
259 Space(" "),
260 fixed(Fixed::UpperAmPm),
261 ];
262
263 match remainder.chars().next() {
264 None => None,
266
267 Some('%') => {
269 remainder = &remainder[1..];
270
271 macro_rules! next {
272 () => {
273 match remainder.chars().next() {
274 Some(x) => {
275 remainder = &remainder[x.len_utf8()..];
276 x
277 }
278 None => return Some((remainder, Item::Error)), }
280 };
281 }
282
283 let spec = next!();
284 let pad_override = match spec {
285 '-' => Some(Pad::None),
286 '0' => Some(Pad::Zero),
287 '_' => Some(Pad::Space),
288 _ => None,
289 };
290 let is_alternate = spec == '#';
291 let spec = if pad_override.is_some() || is_alternate { next!() } else { spec };
292 if is_alternate && !HAVE_ALTERNATES.contains(spec) {
293 return Some((remainder, Item::Error));
294 }
295
296 macro_rules! queue {
297 [$head:expr, $($tail:expr),+ $(,)*] => ({
298 const QUEUE: &'static [Item<'static>] = &[$($tail),+];
299 self.queue = QUEUE;
300 $head
301 })
302 }
303 #[cfg(not(feature = "unstable-locales"))]
304 macro_rules! queue_from_slice {
305 ($slice:expr) => {{
306 self.queue = &$slice[1..];
307 $slice[0].clone()
308 }};
309 }
310
311 let item = match spec {
312 'A' => fixed(Fixed::LongWeekdayName),
313 'B' => fixed(Fixed::LongMonthName),
314 'C' => num0(YearDiv100),
315 'D' => {
316 queue![num0(Month), Literal("/"), num0(Day), Literal("/"), num0(YearMod100)]
317 }
318 'F' => queue![num0(Year), Literal("-"), num0(Month), Literal("-"), num0(Day)],
319 'G' => num0(IsoYear),
320 'H' => num0(Hour),
321 'I' => num0(Hour12),
322 'M' => num0(Minute),
323 'P' => fixed(Fixed::LowerAmPm),
324 'R' => queue![num0(Hour), Literal(":"), num0(Minute)],
325 'S' => num0(Second),
326 'T' => {
327 queue![num0(Hour), Literal(":"), num0(Minute), Literal(":"), num0(Second)]
328 }
329 'U' => num0(WeekFromSun),
330 'V' => num0(IsoWeek),
331 'W' => num0(WeekFromMon),
332 #[cfg(not(feature = "unstable-locales"))]
333 'X' => queue_from_slice!(T_FMT),
334 #[cfg(feature = "unstable-locales")]
335 'X' => self.switch_to_locale_str(locales::t_fmt, T_FMT),
336 'Y' => num0(Year),
337 'Z' => fixed(Fixed::TimezoneName),
338 'a' => fixed(Fixed::ShortWeekdayName),
339 'b' | 'h' => fixed(Fixed::ShortMonthName),
340 #[cfg(not(feature = "unstable-locales"))]
341 'c' => queue_from_slice!(D_T_FMT),
342 #[cfg(feature = "unstable-locales")]
343 'c' => self.switch_to_locale_str(locales::d_t_fmt, D_T_FMT),
344 'd' => num0(Day),
345 'e' => nums(Day),
346 'f' => num0(Nanosecond),
347 'g' => num0(IsoYearMod100),
348 'j' => num0(Ordinal),
349 'k' => nums(Hour),
350 'l' => nums(Hour12),
351 'm' => num0(Month),
352 'n' => Space("\n"),
353 'p' => fixed(Fixed::UpperAmPm),
354 #[cfg(not(feature = "unstable-locales"))]
355 'r' => queue_from_slice!(T_FMT_AMPM),
356 #[cfg(feature = "unstable-locales")]
357 'r' => {
358 if self.locale.is_some()
359 && locales::t_fmt_ampm(self.locale.unwrap()).is_empty()
360 {
361 self.switch_to_locale_str(locales::t_fmt, T_FMT)
363 } else {
364 self.switch_to_locale_str(locales::t_fmt_ampm, T_FMT_AMPM)
365 }
366 }
367 's' => num(Timestamp),
368 't' => Space("\t"),
369 'u' => num(WeekdayFromMon),
370 'v' => {
371 queue![
372 nums(Day),
373 Literal("-"),
374 fixed(Fixed::ShortMonthName),
375 Literal("-"),
376 num0(Year)
377 ]
378 }
379 'w' => num(NumDaysFromSun),
380 #[cfg(not(feature = "unstable-locales"))]
381 'x' => queue_from_slice!(D_FMT),
382 #[cfg(feature = "unstable-locales")]
383 'x' => self.switch_to_locale_str(locales::d_fmt, D_FMT),
384 'y' => num0(YearMod100),
385 'z' => {
386 if is_alternate {
387 internal_fixed(TimezoneOffsetPermissive)
388 } else {
389 fixed(Fixed::TimezoneOffset)
390 }
391 }
392 '+' => fixed(Fixed::RFC3339),
393 ':' => {
394 if remainder.starts_with("::z") {
395 remainder = &remainder[3..];
396 fixed(Fixed::TimezoneOffsetTripleColon)
397 } else if remainder.starts_with(":z") {
398 remainder = &remainder[2..];
399 fixed(Fixed::TimezoneOffsetDoubleColon)
400 } else if remainder.starts_with('z') {
401 remainder = &remainder[1..];
402 fixed(Fixed::TimezoneOffsetColon)
403 } else {
404 Item::Error
405 }
406 }
407 '.' => match next!() {
408 '3' => match next!() {
409 'f' => fixed(Fixed::Nanosecond3),
410 _ => Item::Error,
411 },
412 '6' => match next!() {
413 'f' => fixed(Fixed::Nanosecond6),
414 _ => Item::Error,
415 },
416 '9' => match next!() {
417 'f' => fixed(Fixed::Nanosecond9),
418 _ => Item::Error,
419 },
420 'f' => fixed(Fixed::Nanosecond),
421 _ => Item::Error,
422 },
423 '3' => match next!() {
424 'f' => internal_fixed(Nanosecond3NoDot),
425 _ => Item::Error,
426 },
427 '6' => match next!() {
428 'f' => internal_fixed(Nanosecond6NoDot),
429 _ => Item::Error,
430 },
431 '9' => match next!() {
432 'f' => internal_fixed(Nanosecond9NoDot),
433 _ => Item::Error,
434 },
435 '%' => Literal("%"),
436 _ => Item::Error, };
438
439 if let Some(new_pad) = pad_override {
443 match item {
444 Item::Numeric(ref kind, _pad) if self.queue.is_empty() => {
445 Some((remainder, Item::Numeric(kind.clone(), new_pad)))
446 }
447 _ => Some((remainder, Item::Error)),
448 }
449 } else {
450 Some((remainder, item))
451 }
452 }
453
454 Some(c) if c.is_whitespace() => {
456 let nextspec =
458 remainder.find(|c: char| !c.is_whitespace()).unwrap_or(remainder.len());
459 assert!(nextspec > 0);
460 let item = Space(&remainder[..nextspec]);
461 remainder = &remainder[nextspec..];
462 Some((remainder, item))
463 }
464
465 _ => {
467 let nextspec = remainder
468 .find(|c: char| c.is_whitespace() || c == '%')
469 .unwrap_or(remainder.len());
470 assert!(nextspec > 0);
471 let item = Literal(&remainder[..nextspec]);
472 remainder = &remainder[nextspec..];
473 Some((remainder, item))
474 }
475 }
476 }
477
478 #[cfg(feature = "unstable-locales")]
479 fn switch_to_locale_str(
480 &mut self,
481 localized_fmt_str: impl Fn(Locale) -> &'static str,
482 fallback: &'static [Item<'static>],
483 ) -> Item<'a> {
484 if let Some(locale) = self.locale {
485 assert!(self.locale_str.is_empty());
486 let (fmt_str, item) = self.parse_next_item(localized_fmt_str(locale)).unwrap();
487 self.locale_str = fmt_str;
488 item
489 } else {
490 self.queue = &fallback[1..];
491 fallback[0].clone()
492 }
493 }
494}
495
496#[cfg(test)]
497mod tests {
498 use super::StrftimeItems;
499 use crate::format::Item::{self, Literal, Space};
500 #[cfg(feature = "unstable-locales")]
501 use crate::format::Locale;
502 use crate::format::{fixed, internal_fixed, num, num0, nums};
503 use crate::format::{Fixed, InternalInternal, Numeric::*};
504 #[cfg(any(feature = "alloc", feature = "std"))]
505 use crate::{DateTime, FixedOffset, NaiveDate, TimeZone, Timelike, Utc};
506
507 #[test]
508 fn test_strftime_items() {
509 fn parse_and_collect(s: &str) -> Vec<Item<'_>> {
510 eprintln!("test_strftime_items: parse_and_collect({:?})", s);
512 let items = StrftimeItems::new(s);
513 let items = items.map(|spec| if spec == Item::Error { None } else { Some(spec) });
514 items.collect::<Option<Vec<_>>>().unwrap_or_else(|| vec![Item::Error])
515 }
516
517 assert_eq!(parse_and_collect(""), []);
518 assert_eq!(parse_and_collect(" "), [Space(" ")]);
519 assert_eq!(parse_and_collect(" "), [Space(" ")]);
520 assert_ne!(parse_and_collect(" "), [Space(" "), Space(" ")]);
522 assert_eq!(parse_and_collect(" "), [Space(" ")]);
524 assert_eq!(parse_and_collect("a"), [Literal("a")]);
525 assert_eq!(parse_and_collect("ab"), [Literal("ab")]);
526 assert_eq!(parse_and_collect("π½"), [Literal("π½")]);
527 assert_eq!(parse_and_collect("aπ½"), [Literal("aπ½")]);
528 assert_eq!(parse_and_collect("π½a"), [Literal("π½a")]);
529 assert_eq!(parse_and_collect(" π½"), [Space(" "), Literal("π½")]);
530 assert_eq!(parse_and_collect("π½ "), [Literal("π½"), Space(" ")]);
531 assert_ne!(parse_and_collect("π½π½"), [Literal("π½")]);
533 assert_ne!(parse_and_collect("π½"), [Literal("π½π½")]);
534 assert_ne!(parse_and_collect("π½π½"), [Literal("π½π½"), Literal("π½")]);
535 assert_eq!(parse_and_collect("π½π½"), [Literal("π½π½")]);
537 assert_eq!(parse_and_collect(" \t\n\r "), [Space(" \t\n\r ")]);
538 assert_eq!(parse_and_collect("hello?"), [Literal("hello?")]);
539 assert_eq!(
540 parse_and_collect("a b\t\nc"),
541 [Literal("a"), Space(" "), Literal("b"), Space("\t\n"), Literal("c")]
542 );
543 assert_eq!(parse_and_collect("100%%"), [Literal("100"), Literal("%")]);
544 assert_eq!(
545 parse_and_collect("100%% ok"),
546 [Literal("100"), Literal("%"), Space(" "), Literal("ok")]
547 );
548 assert_eq!(parse_and_collect("%%PDF-1.0"), [Literal("%"), Literal("PDF-1.0")]);
549 assert_eq!(
550 parse_and_collect("%Y-%m-%d"),
551 [num0(Year), Literal("-"), num0(Month), Literal("-"), num0(Day)]
552 );
553 assert_eq!(parse_and_collect("π½ "), [Literal("π½"), Space(" ")]);
554 assert_eq!(parse_and_collect("π½π½"), [Literal("π½π½")]);
555 assert_eq!(parse_and_collect("π½π½π½"), [Literal("π½π½π½")]);
556 assert_eq!(parse_and_collect("π½π½ π½"), [Literal("π½π½"), Space(" "), Literal("π½")]);
557 assert_eq!(parse_and_collect("π½π½a π½"), [Literal("π½π½a"), Space(" "), Literal("π½")]);
558 assert_eq!(parse_and_collect("π½π½a bπ½"), [Literal("π½π½a"), Space(" "), Literal("bπ½")]);
559 assert_eq!(
560 parse_and_collect("π½π½a bπ½c"),
561 [Literal("π½π½a"), Space(" "), Literal("bπ½c")]
562 );
563 assert_eq!(parse_and_collect("π½π½ "), [Literal("π½π½"), Space(" ")]);
564 assert_eq!(parse_and_collect("π½π½ π½"), [Literal("π½π½"), Space(" "), Literal("π½")]);
565 assert_eq!(parse_and_collect(" π½"), [Space(" "), Literal("π½")]);
566 assert_eq!(parse_and_collect(" π½ "), [Space(" "), Literal("π½"), Space(" ")]);
567 assert_eq!(
568 parse_and_collect(" π½ π½"),
569 [Space(" "), Literal("π½"), Space(" "), Literal("π½")]
570 );
571 assert_eq!(
572 parse_and_collect(" π½ π½ "),
573 [Space(" "), Literal("π½"), Space(" "), Literal("π½"), Space(" ")]
574 );
575 assert_eq!(
576 parse_and_collect(" π½ π½ "),
577 [Space(" "), Literal("π½"), Space(" "), Literal("π½"), Space(" ")]
578 );
579 assert_eq!(
580 parse_and_collect(" π½ π½π½ "),
581 [Space(" "), Literal("π½"), Space(" "), Literal("π½π½"), Space(" ")]
582 );
583 assert_eq!(parse_and_collect(" π½π½"), [Space(" "), Literal("π½π½")]);
584 assert_eq!(parse_and_collect(" π½π½ "), [Space(" "), Literal("π½π½"), Space(" ")]);
585 assert_eq!(
586 parse_and_collect(" π½π½ "),
587 [Space(" "), Literal("π½π½"), Space(" ")]
588 );
589 assert_eq!(
590 parse_and_collect(" π½π½ "),
591 [Space(" "), Literal("π½π½"), Space(" ")]
592 );
593 assert_eq!(parse_and_collect(" π½π½ "), [Space(" "), Literal("π½π½"), Space(" ")]);
594 assert_eq!(
595 parse_and_collect(" π½ π½π½ "),
596 [Space(" "), Literal("π½"), Space(" "), Literal("π½π½"), Space(" ")]
597 );
598 assert_eq!(
599 parse_and_collect(" π½ π½γ―γπ½ γγ³γγΌγ¬γΌ"),
600 [
601 Space(" "),
602 Literal("π½"),
603 Space(" "),
604 Literal("π½γ―γπ½"),
605 Space(" "),
606 Literal("γγ³γγΌγ¬γΌ")
607 ]
608 );
609 assert_eq!(
610 parse_and_collect("%%π½%%π½"),
611 [Literal("%"), Literal("π½"), Literal("%"), Literal("π½")]
612 );
613 assert_eq!(parse_and_collect("%Y--%m"), [num0(Year), Literal("--"), num0(Month)]);
614 assert_eq!(parse_and_collect("[%F]"), parse_and_collect("[%Y-%m-%d]"));
615 assert_eq!(parse_and_collect("100%%π½"), [Literal("100"), Literal("%"), Literal("π½")]);
616 assert_eq!(
617 parse_and_collect("100%%π½%%a"),
618 [Literal("100"), Literal("%"), Literal("π½"), Literal("%"), Literal("a")]
619 );
620 assert_eq!(parse_and_collect("π½100%%"), [Literal("π½100"), Literal("%")]);
621 assert_eq!(parse_and_collect("%m %d"), [num0(Month), Space(" "), num0(Day)]);
622 assert_eq!(parse_and_collect("%"), [Item::Error]);
623 assert_eq!(parse_and_collect("%%"), [Literal("%")]);
624 assert_eq!(parse_and_collect("%%%"), [Item::Error]);
625 assert_eq!(parse_and_collect("%a"), [fixed(Fixed::ShortWeekdayName)]);
626 assert_eq!(parse_and_collect("%aa"), [fixed(Fixed::ShortWeekdayName), Literal("a")]);
627 assert_eq!(parse_and_collect("%%a%"), [Item::Error]);
628 assert_eq!(parse_and_collect("%π½"), [Item::Error]);
629 assert_eq!(parse_and_collect("%π½π½"), [Item::Error]);
630 assert_eq!(parse_and_collect("%%%%"), [Literal("%"), Literal("%")]);
631 assert_eq!(
632 parse_and_collect("%%%%γγ³γγΌγ¬γΌ"),
633 [Literal("%"), Literal("%"), Literal("γγ³γγΌγ¬γΌ")]
634 );
635 assert_eq!(parse_and_collect("foo%?"), [Item::Error]);
636 assert_eq!(parse_and_collect("bar%42"), [Item::Error]);
637 assert_eq!(parse_and_collect("quux% +"), [Item::Error]);
638 assert_eq!(parse_and_collect("%.Z"), [Item::Error]);
639 assert_eq!(parse_and_collect("%:Z"), [Item::Error]);
640 assert_eq!(parse_and_collect("%-Z"), [Item::Error]);
641 assert_eq!(parse_and_collect("%0Z"), [Item::Error]);
642 assert_eq!(parse_and_collect("%_Z"), [Item::Error]);
643 assert_eq!(parse_and_collect("%.j"), [Item::Error]);
644 assert_eq!(parse_and_collect("%:j"), [Item::Error]);
645 assert_eq!(parse_and_collect("%-j"), [num(Ordinal)]);
646 assert_eq!(parse_and_collect("%0j"), [num0(Ordinal)]);
647 assert_eq!(parse_and_collect("%_j"), [nums(Ordinal)]);
648 assert_eq!(parse_and_collect("%.e"), [Item::Error]);
649 assert_eq!(parse_and_collect("%:e"), [Item::Error]);
650 assert_eq!(parse_and_collect("%-e"), [num(Day)]);
651 assert_eq!(parse_and_collect("%0e"), [num0(Day)]);
652 assert_eq!(parse_and_collect("%_e"), [nums(Day)]);
653 assert_eq!(parse_and_collect("%z"), [fixed(Fixed::TimezoneOffset)]);
654 assert_eq!(parse_and_collect("%:z"), [fixed(Fixed::TimezoneOffsetColon)]);
655 assert_eq!(parse_and_collect("%Z"), [fixed(Fixed::TimezoneName)]);
656 assert_eq!(parse_and_collect("%ZZZZ"), [fixed(Fixed::TimezoneName), Literal("ZZZ")]);
657 assert_eq!(parse_and_collect("%Zπ½"), [fixed(Fixed::TimezoneName), Literal("π½")]);
658 assert_eq!(
659 parse_and_collect("%#z"),
660 [internal_fixed(InternalInternal::TimezoneOffsetPermissive)]
661 );
662 assert_eq!(parse_and_collect("%#m"), [Item::Error]);
663 }
664
665 #[test]
666 #[cfg(any(feature = "alloc", feature = "std"))]
667 fn test_strftime_docs() {
668 let dt = FixedOffset::east_opt(34200)
669 .unwrap()
670 .from_local_datetime(
671 &NaiveDate::from_ymd_opt(2001, 7, 8)
672 .unwrap()
673 .and_hms_nano_opt(0, 34, 59, 1_026_490_708)
674 .unwrap(),
675 )
676 .unwrap();
677
678 assert_eq!(dt.format("%Y").to_string(), "2001");
680 assert_eq!(dt.format("%C").to_string(), "20");
681 assert_eq!(dt.format("%y").to_string(), "01");
682 assert_eq!(dt.format("%m").to_string(), "07");
683 assert_eq!(dt.format("%b").to_string(), "Jul");
684 assert_eq!(dt.format("%B").to_string(), "July");
685 assert_eq!(dt.format("%h").to_string(), "Jul");
686 assert_eq!(dt.format("%d").to_string(), "08");
687 assert_eq!(dt.format("%e").to_string(), " 8");
688 assert_eq!(dt.format("%e").to_string(), dt.format("%_d").to_string());
689 assert_eq!(dt.format("%a").to_string(), "Sun");
690 assert_eq!(dt.format("%A").to_string(), "Sunday");
691 assert_eq!(dt.format("%w").to_string(), "0");
692 assert_eq!(dt.format("%u").to_string(), "7");
693 assert_eq!(dt.format("%U").to_string(), "27");
694 assert_eq!(dt.format("%W").to_string(), "27");
695 assert_eq!(dt.format("%G").to_string(), "2001");
696 assert_eq!(dt.format("%g").to_string(), "01");
697 assert_eq!(dt.format("%V").to_string(), "27");
698 assert_eq!(dt.format("%j").to_string(), "189");
699 assert_eq!(dt.format("%D").to_string(), "07/08/01");
700 assert_eq!(dt.format("%x").to_string(), "07/08/01");
701 assert_eq!(dt.format("%F").to_string(), "2001-07-08");
702 assert_eq!(dt.format("%v").to_string(), " 8-Jul-2001");
703
704 assert_eq!(dt.format("%H").to_string(), "00");
706 assert_eq!(dt.format("%k").to_string(), " 0");
707 assert_eq!(dt.format("%k").to_string(), dt.format("%_H").to_string());
708 assert_eq!(dt.format("%I").to_string(), "12");
709 assert_eq!(dt.format("%l").to_string(), "12");
710 assert_eq!(dt.format("%l").to_string(), dt.format("%_I").to_string());
711 assert_eq!(dt.format("%P").to_string(), "am");
712 assert_eq!(dt.format("%p").to_string(), "AM");
713 assert_eq!(dt.format("%M").to_string(), "34");
714 assert_eq!(dt.format("%S").to_string(), "60");
715 assert_eq!(dt.format("%f").to_string(), "026490708");
716 assert_eq!(dt.format("%.f").to_string(), ".026490708");
717 assert_eq!(dt.with_nanosecond(1_026_490_000).unwrap().format("%.f").to_string(), ".026490");
718 assert_eq!(dt.format("%.3f").to_string(), ".026");
719 assert_eq!(dt.format("%.6f").to_string(), ".026490");
720 assert_eq!(dt.format("%.9f").to_string(), ".026490708");
721 assert_eq!(dt.format("%3f").to_string(), "026");
722 assert_eq!(dt.format("%6f").to_string(), "026490");
723 assert_eq!(dt.format("%9f").to_string(), "026490708");
724 assert_eq!(dt.format("%R").to_string(), "00:34");
725 assert_eq!(dt.format("%T").to_string(), "00:34:60");
726 assert_eq!(dt.format("%X").to_string(), "00:34:60");
727 assert_eq!(dt.format("%r").to_string(), "12:34:60 AM");
728
729 assert_eq!(dt.format("%z").to_string(), "+0930");
732 assert_eq!(dt.format("%:z").to_string(), "+09:30");
733 assert_eq!(dt.format("%::z").to_string(), "+09:30:00");
734 assert_eq!(dt.format("%:::z").to_string(), "+09");
735
736 assert_eq!(dt.format("%c").to_string(), "Sun Jul 8 00:34:60 2001");
738 assert_eq!(dt.format("%+").to_string(), "2001-07-08T00:34:60.026490708+09:30");
739
740 assert_eq!(
741 dt.with_timezone(&Utc).format("%+").to_string(),
742 "2001-07-07T15:04:60.026490708+00:00"
743 );
744 assert_eq!(
745 dt.with_timezone(&Utc),
746 DateTime::parse_from_str("2001-07-07T15:04:60.026490708Z", "%+").unwrap()
747 );
748 assert_eq!(
749 dt.with_timezone(&Utc),
750 DateTime::parse_from_str("2001-07-07T15:04:60.026490708UTC", "%+").unwrap()
751 );
752 assert_eq!(
753 dt.with_timezone(&Utc),
754 DateTime::parse_from_str("2001-07-07t15:04:60.026490708utc", "%+").unwrap()
755 );
756
757 assert_eq!(
758 dt.with_nanosecond(1_026_490_000).unwrap().format("%+").to_string(),
759 "2001-07-08T00:34:60.026490+09:30"
760 );
761 assert_eq!(dt.format("%s").to_string(), "994518299");
762
763 assert_eq!(dt.format("%t").to_string(), "\t");
765 assert_eq!(dt.format("%n").to_string(), "\n");
766 assert_eq!(dt.format("%%").to_string(), "%");
767
768 assert_eq!(dt.format(" %Y%d%m%%%%%t%H%M%S\t").to_string(), " 20010807%%\t003460\t");
770 assert_eq!(
771 dt.format(" %Y%d%m%%%%%t%H:%P:%M%S%:::z\t").to_string(),
772 " 20010807%%\t00:am:3460+09\t"
773 );
774 }
775
776 #[test]
777 #[cfg(all(feature = "unstable-locales", any(feature = "alloc", feature = "std")))]
778 fn test_strftime_docs_localized() {
779 let dt = FixedOffset::east_opt(34200)
780 .unwrap()
781 .with_ymd_and_hms(2001, 7, 8, 0, 34, 59)
782 .unwrap()
783 .with_nanosecond(1_026_490_708)
784 .unwrap();
785
786 assert_eq!(dt.format_localized("%b", Locale::fr_BE).to_string(), "jui");
788 assert_eq!(dt.format_localized("%B", Locale::fr_BE).to_string(), "juillet");
789 assert_eq!(dt.format_localized("%h", Locale::fr_BE).to_string(), "jui");
790 assert_eq!(dt.format_localized("%a", Locale::fr_BE).to_string(), "dim");
791 assert_eq!(dt.format_localized("%A", Locale::fr_BE).to_string(), "dimanche");
792 assert_eq!(dt.format_localized("%D", Locale::fr_BE).to_string(), "07/08/01");
793 assert_eq!(dt.format_localized("%x", Locale::fr_BE).to_string(), "08/07/01");
794 assert_eq!(dt.format_localized("%F", Locale::fr_BE).to_string(), "2001-07-08");
795 assert_eq!(dt.format_localized("%v", Locale::fr_BE).to_string(), " 8-jui-2001");
796
797 assert_eq!(dt.format_localized("%P", Locale::fr_BE).to_string(), "");
799 assert_eq!(dt.format_localized("%p", Locale::fr_BE).to_string(), "");
800 assert_eq!(dt.format_localized("%R", Locale::fr_BE).to_string(), "00:34");
801 assert_eq!(dt.format_localized("%T", Locale::fr_BE).to_string(), "00:34:60");
802 assert_eq!(dt.format_localized("%X", Locale::fr_BE).to_string(), "00:34:60");
803 assert_eq!(dt.format_localized("%r", Locale::fr_BE).to_string(), "00:34:60");
804
805 assert_eq!(
807 dt.format_localized("%c", Locale::fr_BE).to_string(),
808 "dim 08 jui 2001 00:34:60 +09:30"
809 );
810
811 let nd = NaiveDate::from_ymd_opt(2001, 7, 8).unwrap();
812
813 assert_eq!(nd.format_localized("%b", Locale::de_DE).to_string(), "Jul");
815 assert_eq!(nd.format_localized("%B", Locale::de_DE).to_string(), "Juli");
816 assert_eq!(nd.format_localized("%h", Locale::de_DE).to_string(), "Jul");
817 assert_eq!(nd.format_localized("%a", Locale::de_DE).to_string(), "So");
818 assert_eq!(nd.format_localized("%A", Locale::de_DE).to_string(), "Sonntag");
819 assert_eq!(nd.format_localized("%D", Locale::de_DE).to_string(), "07/08/01");
820 assert_eq!(nd.format_localized("%x", Locale::de_DE).to_string(), "08.07.2001");
821 assert_eq!(nd.format_localized("%F", Locale::de_DE).to_string(), "2001-07-08");
822 assert_eq!(nd.format_localized("%v", Locale::de_DE).to_string(), " 8-Jul-2001");
823 }
824
825 #[test]
830 #[cfg(any(feature = "alloc", feature = "std"))]
831 fn test_parse_only_timezone_offset_permissive_no_panic() {
832 use crate::NaiveDate;
833 use crate::{FixedOffset, TimeZone};
834 use std::fmt::Write;
835
836 let dt = FixedOffset::east_opt(34200)
837 .unwrap()
838 .from_local_datetime(
839 &NaiveDate::from_ymd_opt(2001, 7, 8)
840 .unwrap()
841 .and_hms_nano_opt(0, 34, 59, 1_026_490_708)
842 .unwrap(),
843 )
844 .unwrap();
845
846 let mut buf = String::new();
847 let _ = write!(buf, "{}", dt.format("%#z")).expect_err("parse-only formatter should fail");
848 }
849
850 #[test]
851 #[cfg(all(feature = "unstable-locales", any(feature = "alloc", feature = "std")))]
852 fn test_strftime_localized_korean() {
853 let dt = FixedOffset::east_opt(34200)
854 .unwrap()
855 .with_ymd_and_hms(2001, 7, 8, 0, 34, 59)
856 .unwrap()
857 .with_nanosecond(1_026_490_708)
858 .unwrap();
859
860 assert_eq!(dt.format_localized("%b", Locale::ko_KR).to_string(), " 7μ");
862 assert_eq!(dt.format_localized("%B", Locale::ko_KR).to_string(), "7μ");
863 assert_eq!(dt.format_localized("%h", Locale::ko_KR).to_string(), " 7μ");
864 assert_eq!(dt.format_localized("%a", Locale::ko_KR).to_string(), "μΌ");
865 assert_eq!(dt.format_localized("%A", Locale::ko_KR).to_string(), "μΌμμΌ");
866 assert_eq!(dt.format_localized("%D", Locale::ko_KR).to_string(), "07/08/01");
867 assert_eq!(dt.format_localized("%x", Locale::ko_KR).to_string(), "2001λ
07μ 08μΌ");
868 assert_eq!(dt.format_localized("%F", Locale::ko_KR).to_string(), "2001-07-08");
869 assert_eq!(dt.format_localized("%v", Locale::ko_KR).to_string(), " 8- 7μ-2001");
870 assert_eq!(dt.format_localized("%r", Locale::ko_KR).to_string(), "μ€μ 12μ 34λΆ 60μ΄");
871
872 assert_eq!(
874 dt.format_localized("%c", Locale::ko_KR).to_string(),
875 "2001λ
07μ 08μΌ (μΌ) μ€μ 12μ 34λΆ 60μ΄"
876 );
877 }
878
879 #[test]
880 #[cfg(all(feature = "unstable-locales", any(feature = "alloc", feature = "std")))]
881 fn test_strftime_localized_japanese() {
882 let dt = FixedOffset::east_opt(34200)
883 .unwrap()
884 .with_ymd_and_hms(2001, 7, 8, 0, 34, 59)
885 .unwrap()
886 .with_nanosecond(1_026_490_708)
887 .unwrap();
888
889 assert_eq!(dt.format_localized("%b", Locale::ja_JP).to_string(), " 7ζ");
891 assert_eq!(dt.format_localized("%B", Locale::ja_JP).to_string(), "7ζ");
892 assert_eq!(dt.format_localized("%h", Locale::ja_JP).to_string(), " 7ζ");
893 assert_eq!(dt.format_localized("%a", Locale::ja_JP).to_string(), "ζ₯");
894 assert_eq!(dt.format_localized("%A", Locale::ja_JP).to_string(), "ζ₯ζζ₯");
895 assert_eq!(dt.format_localized("%D", Locale::ja_JP).to_string(), "07/08/01");
896 assert_eq!(dt.format_localized("%x", Locale::ja_JP).to_string(), "2001εΉ΄07ζ08ζ₯");
897 assert_eq!(dt.format_localized("%F", Locale::ja_JP).to_string(), "2001-07-08");
898 assert_eq!(dt.format_localized("%v", Locale::ja_JP).to_string(), " 8- 7ζ-2001");
899 assert_eq!(dt.format_localized("%r", Locale::ja_JP).to_string(), "εε12ζ34ε60η§");
900
901 assert_eq!(
903 dt.format_localized("%c", Locale::ja_JP).to_string(),
904 "2001εΉ΄07ζ08ζ₯ 00ζ34ε60η§"
905 );
906 }
907
908 #[test]
909 #[cfg(all(feature = "unstable-locales", target_pointer_width = "64"))]
910 fn test_type_sizes() {
911 use core::mem::size_of;
912 assert_eq!(size_of::<Item>(), 24);
913 assert_eq!(size_of::<StrftimeItems>(), 56);
914 assert_eq!(size_of::<Locale>(), 2);
915 }
916
917 #[test]
918 #[cfg(all(feature = "unstable-locales", target_pointer_width = "32"))]
919 fn test_type_sizes() {
920 use core::mem::size_of;
921 assert_eq!(size_of::<Item>(), 12);
922 assert_eq!(size_of::<StrftimeItems>(), 28);
923 assert_eq!(size_of::<Locale>(), 2);
924 }
925}