1#[cfg(feature = "alloc")]
7use alloc::string::{String, ToString};
8#[cfg(any(feature = "alloc", feature = "std"))]
9use core::borrow::Borrow;
10use core::fmt;
11use core::fmt::Write;
12
13#[cfg(any(
14 feature = "alloc",
15 feature = "std",
16 feature = "serde",
17 feature = "rustc-serialize"
18))]
19use crate::datetime::SecondsFormat;
20#[cfg(any(feature = "alloc", feature = "std"))]
21use crate::offset::Offset;
22#[cfg(any(
23 feature = "alloc",
24 feature = "std",
25 feature = "serde",
26 feature = "rustc-serialize"
27))]
28use crate::{Datelike, FixedOffset, NaiveDateTime, Timelike};
29#[cfg(any(feature = "alloc", feature = "std"))]
30use crate::{NaiveDate, NaiveTime, Weekday};
31
32#[cfg(any(feature = "alloc", feature = "std"))]
33use super::locales;
34#[cfg(any(
35 feature = "alloc",
36 feature = "std",
37 feature = "serde",
38 feature = "rustc-serialize"
39))]
40use super::{Colons, OffsetFormat, OffsetPrecision, Pad};
41#[cfg(any(feature = "alloc", feature = "std"))]
42use super::{Fixed, InternalFixed, InternalInternal, Item, Locale, Numeric};
43#[cfg(any(feature = "alloc", feature = "std"))]
44use locales::*;
45
46#[cfg(any(feature = "alloc", feature = "std"))]
49#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
50#[derive(Debug)]
51pub struct DelayedFormat<I> {
52 date: Option<NaiveDate>,
54 time: Option<NaiveTime>,
56 off: Option<(String, FixedOffset)>,
58 items: I,
60 #[cfg(feature = "unstable-locales")]
64 locale: Option<Locale>,
65}
66
67#[cfg(any(feature = "alloc", feature = "std"))]
68impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> DelayedFormat<I> {
69 #[must_use]
71 pub fn new(date: Option<NaiveDate>, time: Option<NaiveTime>, items: I) -> DelayedFormat<I> {
72 DelayedFormat {
73 date,
74 time,
75 off: None,
76 items,
77 #[cfg(feature = "unstable-locales")]
78 locale: None,
79 }
80 }
81
82 #[must_use]
84 pub fn new_with_offset<Off>(
85 date: Option<NaiveDate>,
86 time: Option<NaiveTime>,
87 offset: &Off,
88 items: I,
89 ) -> DelayedFormat<I>
90 where
91 Off: Offset + fmt::Display,
92 {
93 let name_and_diff = (offset.to_string(), offset.fix());
94 DelayedFormat {
95 date,
96 time,
97 off: Some(name_and_diff),
98 items,
99 #[cfg(feature = "unstable-locales")]
100 locale: None,
101 }
102 }
103
104 #[cfg(feature = "unstable-locales")]
106 #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))]
107 #[must_use]
108 pub fn new_with_locale(
109 date: Option<NaiveDate>,
110 time: Option<NaiveTime>,
111 items: I,
112 locale: Locale,
113 ) -> DelayedFormat<I> {
114 DelayedFormat { date, time, off: None, items, locale: Some(locale) }
115 }
116
117 #[cfg(feature = "unstable-locales")]
119 #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))]
120 #[must_use]
121 pub fn new_with_offset_and_locale<Off>(
122 date: Option<NaiveDate>,
123 time: Option<NaiveTime>,
124 offset: &Off,
125 items: I,
126 locale: Locale,
127 ) -> DelayedFormat<I>
128 where
129 Off: Offset + fmt::Display,
130 {
131 let name_and_diff = (offset.to_string(), offset.fix());
132 DelayedFormat { date, time, off: Some(name_and_diff), items, locale: Some(locale) }
133 }
134}
135
136#[cfg(any(feature = "alloc", feature = "std"))]
137impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> fmt::Display for DelayedFormat<I> {
138 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
139 #[cfg(feature = "unstable-locales")]
140 {
141 if let Some(locale) = self.locale {
142 return format_localized(
143 f,
144 self.date.as_ref(),
145 self.time.as_ref(),
146 self.off.as_ref(),
147 self.items.clone(),
148 locale,
149 );
150 }
151 }
152
153 format(f, self.date.as_ref(), self.time.as_ref(), self.off.as_ref(), self.items.clone())
154 }
155}
156
157#[cfg(any(feature = "alloc", feature = "std"))]
160#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
161pub fn format<'a, I, B>(
162 w: &mut fmt::Formatter,
163 date: Option<&NaiveDate>,
164 time: Option<&NaiveTime>,
165 off: Option<&(String, FixedOffset)>,
166 items: I,
167) -> fmt::Result
168where
169 I: Iterator<Item = B> + Clone,
170 B: Borrow<Item<'a>>,
171{
172 let mut result = String::new();
173 for item in items {
174 format_inner(&mut result, date, time, off, item.borrow(), None)?;
175 }
176 w.pad(&result)
177}
178#[cfg(any(feature = "alloc", feature = "std"))]
180#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
181pub fn format_item(
182 w: &mut fmt::Formatter,
183 date: Option<&NaiveDate>,
184 time: Option<&NaiveTime>,
185 off: Option<&(String, FixedOffset)>,
186 item: &Item<'_>,
187) -> fmt::Result {
188 let mut result = String::new();
189 format_inner(&mut result, date, time, off, item, None)?;
190 w.pad(&result)
191}
192
193#[cfg(feature = "unstable-locales")]
196#[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))]
197pub fn format_localized<'a, I, B>(
198 w: &mut fmt::Formatter,
199 date: Option<&NaiveDate>,
200 time: Option<&NaiveTime>,
201 off: Option<&(String, FixedOffset)>,
202 items: I,
203 locale: Locale,
204) -> fmt::Result
205where
206 I: Iterator<Item = B> + Clone,
207 B: Borrow<Item<'a>>,
208{
209 let mut result = String::new();
210 for item in items {
211 format_inner(&mut result, date, time, off, item.borrow(), Some(locale))?;
212 }
213 w.pad(&result)
214}
215
216#[cfg(feature = "unstable-locales")]
218#[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))]
219pub fn format_item_localized(
220 w: &mut fmt::Formatter,
221 date: Option<&NaiveDate>,
222 time: Option<&NaiveTime>,
223 off: Option<&(String, FixedOffset)>,
224 item: &Item<'_>,
225 locale: Locale,
226) -> fmt::Result {
227 let mut result = String::new();
228 format_inner(&mut result, date, time, off, item, Some(locale))?;
229 w.pad(&result)
230}
231
232#[cfg(any(feature = "alloc", feature = "std"))]
233fn format_inner(
234 w: &mut impl Write,
235 date: Option<&NaiveDate>,
236 time: Option<&NaiveTime>,
237 off: Option<&(String, FixedOffset)>,
238 item: &Item<'_>,
239 locale: Option<Locale>,
240) -> fmt::Result {
241 let locale = locale.unwrap_or(default_locale());
242
243 match *item {
244 Item::Literal(s) | Item::Space(s) => w.write_str(s),
245 #[cfg(any(feature = "alloc", feature = "std"))]
246 Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => w.write_str(s),
247
248 Item::Numeric(ref spec, ref pad) => {
249 use self::Numeric::*;
250
251 let week_from_sun = |d: &NaiveDate| d.weeks_from(Weekday::Sun);
252 let week_from_mon = |d: &NaiveDate| d.weeks_from(Weekday::Mon);
253
254 let (width, v) = match *spec {
255 Year => (4, date.map(|d| i64::from(d.year()))),
256 YearDiv100 => (2, date.map(|d| i64::from(d.year()).div_euclid(100))),
257 YearMod100 => (2, date.map(|d| i64::from(d.year()).rem_euclid(100))),
258 IsoYear => (4, date.map(|d| i64::from(d.iso_week().year()))),
259 IsoYearDiv100 => (2, date.map(|d| i64::from(d.iso_week().year()).div_euclid(100))),
260 IsoYearMod100 => (2, date.map(|d| i64::from(d.iso_week().year()).rem_euclid(100))),
261 Month => (2, date.map(|d| i64::from(d.month()))),
262 Day => (2, date.map(|d| i64::from(d.day()))),
263 WeekFromSun => (2, date.map(|d| i64::from(week_from_sun(d)))),
264 WeekFromMon => (2, date.map(|d| i64::from(week_from_mon(d)))),
265 IsoWeek => (2, date.map(|d| i64::from(d.iso_week().week()))),
266 NumDaysFromSun => (1, date.map(|d| i64::from(d.weekday().num_days_from_sunday()))),
267 WeekdayFromMon => (1, date.map(|d| i64::from(d.weekday().number_from_monday()))),
268 Ordinal => (3, date.map(|d| i64::from(d.ordinal()))),
269 Hour => (2, time.map(|t| i64::from(t.hour()))),
270 Hour12 => (2, time.map(|t| i64::from(t.hour12().1))),
271 Minute => (2, time.map(|t| i64::from(t.minute()))),
272 Second => (2, time.map(|t| i64::from(t.second() + t.nanosecond() / 1_000_000_000))),
273 Nanosecond => (9, time.map(|t| i64::from(t.nanosecond() % 1_000_000_000))),
274 Timestamp => (
275 1,
276 match (date, time, off) {
277 (Some(d), Some(t), None) => Some(d.and_time(*t).timestamp()),
278 (Some(d), Some(t), Some(&(_, off))) => {
279 Some(d.and_time(*t).timestamp() - i64::from(off.local_minus_utc()))
280 }
281 (_, _, _) => None,
282 },
283 ),
284
285 Internal(ref int) => match int._dummy {},
287 };
288
289 if let Some(v) = v {
290 if (spec == &Year || spec == &IsoYear) && !(0..10_000).contains(&v) {
291 match *pad {
293 Pad::None => write!(w, "{:+}", v),
294 Pad::Zero => write!(w, "{:+01$}", v, width + 1),
295 Pad::Space => write!(w, "{:+1$}", v, width + 1),
296 }
297 } else {
298 match *pad {
299 Pad::None => write!(w, "{}", v),
300 Pad::Zero => write!(w, "{:01$}", v, width),
301 Pad::Space => write!(w, "{:1$}", v, width),
302 }
303 }
304 } else {
305 Err(fmt::Error) }
307 }
308
309 Item::Fixed(ref spec) => {
310 use self::Fixed::*;
311
312 let ret = match *spec {
313 ShortMonthName => date.map(|d| {
314 w.write_str(short_months(locale)[d.month0() as usize])?;
315 Ok(())
316 }),
317 LongMonthName => date.map(|d| {
318 w.write_str(long_months(locale)[d.month0() as usize])?;
319 Ok(())
320 }),
321 ShortWeekdayName => date.map(|d| {
322 w.write_str(
323 short_weekdays(locale)[d.weekday().num_days_from_sunday() as usize],
324 )?;
325 Ok(())
326 }),
327 LongWeekdayName => date.map(|d| {
328 w.write_str(
329 long_weekdays(locale)[d.weekday().num_days_from_sunday() as usize],
330 )?;
331 Ok(())
332 }),
333 LowerAmPm => time.map(|t| {
334 let ampm = if t.hour12().0 { am_pm(locale)[1] } else { am_pm(locale)[0] };
335 for c in ampm.chars().flat_map(|c| c.to_lowercase()) {
336 w.write_char(c)?
337 }
338 Ok(())
339 }),
340 UpperAmPm => time.map(|t| {
341 w.write_str(if t.hour12().0 { am_pm(locale)[1] } else { am_pm(locale)[0] })?;
342 Ok(())
343 }),
344 Nanosecond => time.map(|t| {
345 let nano = t.nanosecond() % 1_000_000_000;
346 if nano == 0 {
347 Ok(())
348 } else {
349 w.write_str(decimal_point(locale))?;
350 if nano % 1_000_000 == 0 {
351 write!(w, "{:03}", nano / 1_000_000)
352 } else if nano % 1_000 == 0 {
353 write!(w, "{:06}", nano / 1_000)
354 } else {
355 write!(w, "{:09}", nano)
356 }
357 }
358 }),
359 Nanosecond3 => time.map(|t| {
360 let nano = t.nanosecond() % 1_000_000_000;
361 w.write_str(decimal_point(locale))?;
362 write!(w, "{:03}", nano / 1_000_000)
363 }),
364 Nanosecond6 => time.map(|t| {
365 let nano = t.nanosecond() % 1_000_000_000;
366 w.write_str(decimal_point(locale))?;
367 write!(w, "{:06}", nano / 1_000)
368 }),
369 Nanosecond9 => time.map(|t| {
370 let nano = t.nanosecond() % 1_000_000_000;
371 w.write_str(decimal_point(locale))?;
372 write!(w, "{:09}", nano)
373 }),
374 Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => {
375 time.map(|t| {
376 let nano = t.nanosecond() % 1_000_000_000;
377 write!(w, "{:03}", nano / 1_000_000)
378 })
379 }
380 Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => {
381 time.map(|t| {
382 let nano = t.nanosecond() % 1_000_000_000;
383 write!(w, "{:06}", nano / 1_000)
384 })
385 }
386 Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => {
387 time.map(|t| {
388 let nano = t.nanosecond() % 1_000_000_000;
389 write!(w, "{:09}", nano)
390 })
391 }
392 TimezoneName => off.map(|(name, _)| {
393 w.write_str(name)?;
394 Ok(())
395 }),
396 TimezoneOffset | TimezoneOffsetZ => off.map(|&(_, off)| {
397 OffsetFormat {
398 precision: OffsetPrecision::Minutes,
399 colons: Colons::Maybe,
400 allow_zulu: *spec == TimezoneOffsetZ,
401 padding: Pad::Zero,
402 }
403 .format(w, off)
404 }),
405 TimezoneOffsetColon | TimezoneOffsetColonZ => off.map(|&(_, off)| {
406 OffsetFormat {
407 precision: OffsetPrecision::Minutes,
408 colons: Colons::Colon,
409 allow_zulu: *spec == TimezoneOffsetColonZ,
410 padding: Pad::Zero,
411 }
412 .format(w, off)
413 }),
414 TimezoneOffsetDoubleColon => off.map(|&(_, off)| {
415 OffsetFormat {
416 precision: OffsetPrecision::Seconds,
417 colons: Colons::Colon,
418 allow_zulu: false,
419 padding: Pad::Zero,
420 }
421 .format(w, off)
422 }),
423 TimezoneOffsetTripleColon => off.map(|&(_, off)| {
424 OffsetFormat {
425 precision: OffsetPrecision::Hours,
426 colons: Colons::None,
427 allow_zulu: false,
428 padding: Pad::Zero,
429 }
430 .format(w, off)
431 }),
432 Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => {
433 return Err(fmt::Error);
434 }
435 RFC2822 =>
436 {
438 if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) {
439 Some(write_rfc2822_inner(w, *d, *t, off, locale))
440 } else {
441 None
442 }
443 }
444 RFC3339 =>
445 {
447 if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) {
448 Some(write_rfc3339(
449 w,
450 crate::NaiveDateTime::new(*d, *t),
451 off.fix(),
452 SecondsFormat::AutoSi,
453 false,
454 ))
455 } else {
456 None
457 }
458 }
459 };
460
461 ret.unwrap_or(Err(fmt::Error)) }
463
464 Item::Error => Err(fmt::Error),
465 }
466}
467
468#[cfg(any(feature = "alloc", feature = "std", feature = "serde", feature = "rustc-serialize"))]
469impl OffsetFormat {
470 fn format(&self, w: &mut impl Write, off: FixedOffset) -> fmt::Result {
472 let off = off.local_minus_utc();
473 if self.allow_zulu && off == 0 {
474 w.write_char('Z')?;
475 return Ok(());
476 }
477 let (sign, off) = if off < 0 { ('-', -off) } else { ('+', off) };
478
479 let hours;
480 let mut mins = 0;
481 let mut secs = 0;
482 let precision = match self.precision {
483 OffsetPrecision::Hours => {
484 hours = (off / 3600) as u8;
486 OffsetPrecision::Hours
487 }
488 OffsetPrecision::Minutes | OffsetPrecision::OptionalMinutes => {
489 let minutes = (off + 30) / 60;
491 mins = (minutes % 60) as u8;
492 hours = (minutes / 60) as u8;
493 if self.precision == OffsetPrecision::OptionalMinutes && mins == 0 {
494 OffsetPrecision::Hours
495 } else {
496 OffsetPrecision::Minutes
497 }
498 }
499 OffsetPrecision::Seconds
500 | OffsetPrecision::OptionalSeconds
501 | OffsetPrecision::OptionalMinutesAndSeconds => {
502 let minutes = off / 60;
503 secs = (off % 60) as u8;
504 mins = (minutes % 60) as u8;
505 hours = (minutes / 60) as u8;
506 if self.precision != OffsetPrecision::Seconds && secs == 0 {
507 if self.precision == OffsetPrecision::OptionalMinutesAndSeconds && mins == 0 {
508 OffsetPrecision::Hours
509 } else {
510 OffsetPrecision::Minutes
511 }
512 } else {
513 OffsetPrecision::Seconds
514 }
515 }
516 };
517 let colons = self.colons == Colons::Colon;
518
519 if hours < 10 {
520 if self.padding == Pad::Space {
521 w.write_char(' ')?;
522 }
523 w.write_char(sign)?;
524 if self.padding == Pad::Zero {
525 w.write_char('0')?;
526 }
527 w.write_char((b'0' + hours) as char)?;
528 } else {
529 w.write_char(sign)?;
530 write_hundreds(w, hours)?;
531 }
532 if let OffsetPrecision::Minutes | OffsetPrecision::Seconds = precision {
533 if colons {
534 w.write_char(':')?;
535 }
536 write_hundreds(w, mins)?;
537 }
538 if let OffsetPrecision::Seconds = precision {
539 if colons {
540 w.write_char(':')?;
541 }
542 write_hundreds(w, secs)?;
543 }
544 Ok(())
545 }
546}
547
548#[inline]
550#[cfg(any(feature = "alloc", feature = "std", feature = "serde", feature = "rustc-serialize"))]
551pub(crate) fn write_rfc3339(
552 w: &mut impl Write,
553 dt: NaiveDateTime,
554 off: FixedOffset,
555 secform: SecondsFormat,
556 use_z: bool,
557) -> fmt::Result {
558 let year = dt.date().year();
559 if (0..=9999).contains(&year) {
560 write_hundreds(w, (year / 100) as u8)?;
561 write_hundreds(w, (year % 100) as u8)?;
562 } else {
563 write!(w, "{:+05}", year)?;
565 }
566 w.write_char('-')?;
567 write_hundreds(w, dt.date().month() as u8)?;
568 w.write_char('-')?;
569 write_hundreds(w, dt.date().day() as u8)?;
570
571 w.write_char('T')?;
572
573 let (hour, min, mut sec) = dt.time().hms();
574 let mut nano = dt.nanosecond();
575 if nano >= 1_000_000_000 {
576 sec += 1;
577 nano -= 1_000_000_000;
578 }
579 write_hundreds(w, hour as u8)?;
580 w.write_char(':')?;
581 write_hundreds(w, min as u8)?;
582 w.write_char(':')?;
583 let sec = sec;
584 write_hundreds(w, sec as u8)?;
585
586 match secform {
587 SecondsFormat::Secs => {}
588 SecondsFormat::Millis => write!(w, ".{:03}", nano / 1_000_000)?,
589 SecondsFormat::Micros => write!(w, ".{:06}", nano / 1000)?,
590 SecondsFormat::Nanos => write!(w, ".{:09}", nano)?,
591 SecondsFormat::AutoSi => {
592 if nano == 0 {
593 } else if nano % 1_000_000 == 0 {
594 write!(w, ".{:03}", nano / 1_000_000)?
595 } else if nano % 1_000 == 0 {
596 write!(w, ".{:06}", nano / 1_000)?
597 } else {
598 write!(w, ".{:09}", nano)?
599 }
600 }
601 SecondsFormat::__NonExhaustive => unreachable!(),
602 };
603
604 OffsetFormat {
605 precision: OffsetPrecision::Minutes,
606 colons: Colons::Colon,
607 allow_zulu: use_z,
608 padding: Pad::Zero,
609 }
610 .format(w, off)
611}
612
613#[cfg(any(feature = "alloc", feature = "std"))]
614pub(crate) fn write_rfc2822(
616 w: &mut impl Write,
617 dt: NaiveDateTime,
618 off: FixedOffset,
619) -> fmt::Result {
620 write_rfc2822_inner(w, dt.date(), dt.time(), off, default_locale())
621}
622
623#[cfg(any(feature = "alloc", feature = "std"))]
624fn write_rfc2822_inner(
626 w: &mut impl Write,
627 d: NaiveDate,
628 t: NaiveTime,
629 off: FixedOffset,
630 locale: Locale,
631) -> fmt::Result {
632 let year = d.year();
633 if !(0..=9999).contains(&year) {
635 return Err(fmt::Error);
636 }
637
638 w.write_str(short_weekdays(locale)[d.weekday().num_days_from_sunday() as usize])?;
639 w.write_str(", ")?;
640 write_hundreds(w, d.day() as u8)?;
641 w.write_char(' ')?;
642 w.write_str(short_months(locale)[d.month0() as usize])?;
643 w.write_char(' ')?;
644 write_hundreds(w, (year / 100) as u8)?;
645 write_hundreds(w, (year % 100) as u8)?;
646 w.write_char(' ')?;
647
648 let (hour, min, sec) = t.hms();
649 write_hundreds(w, hour as u8)?;
650 w.write_char(':')?;
651 write_hundreds(w, min as u8)?;
652 w.write_char(':')?;
653 let sec = sec + t.nanosecond() / 1_000_000_000;
654 write_hundreds(w, sec as u8)?;
655 w.write_char(' ')?;
656 OffsetFormat {
657 precision: OffsetPrecision::Minutes,
658 colons: Colons::None,
659 allow_zulu: false,
660 padding: Pad::Zero,
661 }
662 .format(w, off)
663}
664
665pub(crate) fn write_hundreds(w: &mut impl Write, n: u8) -> fmt::Result {
667 if n >= 100 {
668 return Err(fmt::Error);
669 }
670
671 let tens = b'0' + n / 10;
672 let ones = b'0' + n % 10;
673 w.write_char(tens as char)?;
674 w.write_char(ones as char)
675}
676
677#[cfg(test)]
678#[cfg(any(feature = "alloc", feature = "std"))]
679mod tests {
680 use super::{Colons, OffsetFormat, OffsetPrecision, Pad};
681 use crate::FixedOffset;
682 #[cfg(any(feature = "alloc", feature = "std"))]
683 use crate::{NaiveDate, NaiveTime, TimeZone, Timelike, Utc};
684
685 #[test]
686 #[cfg(any(feature = "alloc", feature = "std"))]
687 fn test_date_format() {
688 let d = NaiveDate::from_ymd_opt(2012, 3, 4).unwrap();
689 assert_eq!(d.format("%Y,%C,%y,%G,%g").to_string(), "2012,20,12,2012,12");
690 assert_eq!(d.format("%m,%b,%h,%B").to_string(), "03,Mar,Mar,March");
691 assert_eq!(d.format("%d,%e").to_string(), "04, 4");
692 assert_eq!(d.format("%U,%W,%V").to_string(), "10,09,09");
693 assert_eq!(d.format("%a,%A,%w,%u").to_string(), "Sun,Sunday,0,7");
694 assert_eq!(d.format("%j").to_string(), "064"); assert_eq!(d.format("%D,%x").to_string(), "03/04/12,03/04/12");
696 assert_eq!(d.format("%F").to_string(), "2012-03-04");
697 assert_eq!(d.format("%v").to_string(), " 4-Mar-2012");
698 assert_eq!(d.format("%t%n%%%n%t").to_string(), "\t\n%\n\t");
699
700 assert_eq!(
702 NaiveDate::from_ymd_opt(12345, 1, 1).unwrap().format("%Y").to_string(),
703 "+12345"
704 );
705 assert_eq!(NaiveDate::from_ymd_opt(1234, 1, 1).unwrap().format("%Y").to_string(), "1234");
706 assert_eq!(NaiveDate::from_ymd_opt(123, 1, 1).unwrap().format("%Y").to_string(), "0123");
707 assert_eq!(NaiveDate::from_ymd_opt(12, 1, 1).unwrap().format("%Y").to_string(), "0012");
708 assert_eq!(NaiveDate::from_ymd_opt(1, 1, 1).unwrap().format("%Y").to_string(), "0001");
709 assert_eq!(NaiveDate::from_ymd_opt(0, 1, 1).unwrap().format("%Y").to_string(), "0000");
710 assert_eq!(NaiveDate::from_ymd_opt(-1, 1, 1).unwrap().format("%Y").to_string(), "-0001");
711 assert_eq!(NaiveDate::from_ymd_opt(-12, 1, 1).unwrap().format("%Y").to_string(), "-0012");
712 assert_eq!(NaiveDate::from_ymd_opt(-123, 1, 1).unwrap().format("%Y").to_string(), "-0123");
713 assert_eq!(NaiveDate::from_ymd_opt(-1234, 1, 1).unwrap().format("%Y").to_string(), "-1234");
714 assert_eq!(
715 NaiveDate::from_ymd_opt(-12345, 1, 1).unwrap().format("%Y").to_string(),
716 "-12345"
717 );
718
719 assert_eq!(
721 NaiveDate::from_ymd_opt(2007, 12, 31).unwrap().format("%G,%g,%U,%W,%V").to_string(),
722 "2008,08,52,53,01"
723 );
724 assert_eq!(
725 NaiveDate::from_ymd_opt(2010, 1, 3).unwrap().format("%G,%g,%U,%W,%V").to_string(),
726 "2009,09,01,00,53"
727 );
728 }
729
730 #[test]
731 #[cfg(any(feature = "alloc", feature = "std"))]
732 fn test_time_format() {
733 let t = NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap();
734 assert_eq!(t.format("%H,%k,%I,%l,%P,%p").to_string(), "03, 3,03, 3,am,AM");
735 assert_eq!(t.format("%M").to_string(), "05");
736 assert_eq!(t.format("%S,%f,%.f").to_string(), "07,098765432,.098765432");
737 assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".098,.098765,.098765432");
738 assert_eq!(t.format("%R").to_string(), "03:05");
739 assert_eq!(t.format("%T,%X").to_string(), "03:05:07,03:05:07");
740 assert_eq!(t.format("%r").to_string(), "03:05:07 AM");
741 assert_eq!(t.format("%t%n%%%n%t").to_string(), "\t\n%\n\t");
742
743 let t = NaiveTime::from_hms_micro_opt(3, 5, 7, 432100).unwrap();
744 assert_eq!(t.format("%S,%f,%.f").to_string(), "07,432100000,.432100");
745 assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".432,.432100,.432100000");
746
747 let t = NaiveTime::from_hms_milli_opt(3, 5, 7, 210).unwrap();
748 assert_eq!(t.format("%S,%f,%.f").to_string(), "07,210000000,.210");
749 assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".210,.210000,.210000000");
750
751 let t = NaiveTime::from_hms_opt(3, 5, 7).unwrap();
752 assert_eq!(t.format("%S,%f,%.f").to_string(), "07,000000000,");
753 assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".000,.000000,.000000000");
754
755 assert_eq!(
757 NaiveTime::from_hms_opt(13, 57, 9).unwrap().format("%r").to_string(),
758 "01:57:09 PM"
759 );
760 assert_eq!(
761 NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap().format("%X").to_string(),
762 "23:59:60"
763 );
764 }
765
766 #[test]
767 #[cfg(any(feature = "alloc", feature = "std"))]
768 fn test_datetime_format() {
769 let dt =
770 NaiveDate::from_ymd_opt(2010, 9, 8).unwrap().and_hms_milli_opt(7, 6, 54, 321).unwrap();
771 assert_eq!(dt.format("%c").to_string(), "Wed Sep 8 07:06:54 2010");
772 assert_eq!(dt.format("%s").to_string(), "1283929614");
773 assert_eq!(dt.format("%t%n%%%n%t").to_string(), "\t\n%\n\t");
774
775 let dt = NaiveDate::from_ymd_opt(2012, 6, 30)
777 .unwrap()
778 .and_hms_milli_opt(23, 59, 59, 1_000)
779 .unwrap();
780 assert_eq!(dt.format("%c").to_string(), "Sat Jun 30 23:59:60 2012");
781 assert_eq!(dt.format("%s").to_string(), "1341100799"); }
783
784 #[test]
785 #[cfg(any(feature = "alloc", feature = "std"))]
786 fn test_datetime_format_alignment() {
787 let datetime = Utc
788 .with_ymd_and_hms(2007, 1, 2, 12, 34, 56)
789 .unwrap()
790 .with_nanosecond(123456789)
791 .unwrap();
792
793 let percent = datetime.format("%%");
795 assert_eq!(" %", format!("{:>4}", percent));
796 assert_eq!("% ", format!("{:<4}", percent));
797 assert_eq!(" % ", format!("{:^4}", percent));
798
799 let year = datetime.format("%Y");
801 assert_eq!("——2007", format!("{:—>6}", year));
802 assert_eq!("2007——", format!("{:—<6}", year));
803 assert_eq!("—2007—", format!("{:—^6}", year));
804
805 let tz = datetime.format("%Z");
807 assert_eq!(" UTC", format!("{:>5}", tz));
808 assert_eq!("UTC ", format!("{:<5}", tz));
809 assert_eq!(" UTC ", format!("{:^5}", tz));
810
811 let ymd = datetime.format("%Y %B %d");
813 assert_eq!(" 2007 January 02", format!("{:>17}", ymd));
814 assert_eq!("2007 January 02 ", format!("{:<17}", ymd));
815 assert_eq!(" 2007 January 02 ", format!("{:^17}", ymd));
816
817 let time = datetime.format("%T%.6f");
819 assert_eq!("12:34:56.1234", format!("{:.13}", time));
820 }
821
822 #[test]
823 fn test_offset_formatting() {
824 fn check_all(precision: OffsetPrecision, expected: [[&str; 7]; 12]) {
825 fn check(
826 precision: OffsetPrecision,
827 colons: Colons,
828 padding: Pad,
829 allow_zulu: bool,
830 offsets: [FixedOffset; 7],
831 expected: [&str; 7],
832 ) {
833 let offset_format = OffsetFormat { precision, colons, allow_zulu, padding };
834 for (offset, expected) in offsets.iter().zip(expected.iter()) {
835 let mut output = String::new();
836 offset_format.format(&mut output, *offset).unwrap();
837 assert_eq!(&output, expected);
838 }
839 }
840 let offsets = [
842 FixedOffset::east_opt(13_500).unwrap(),
843 FixedOffset::east_opt(-12_600).unwrap(),
844 FixedOffset::east_opt(39_600).unwrap(),
845 FixedOffset::east_opt(-39_622).unwrap(),
846 FixedOffset::east_opt(9266).unwrap(),
847 FixedOffset::east_opt(-45270).unwrap(),
848 FixedOffset::east_opt(0).unwrap(),
849 ];
850 check(precision, Colons::Colon, Pad::Zero, false, offsets, expected[0]);
851 check(precision, Colons::Colon, Pad::Zero, true, offsets, expected[1]);
852 check(precision, Colons::Colon, Pad::Space, false, offsets, expected[2]);
853 check(precision, Colons::Colon, Pad::Space, true, offsets, expected[3]);
854 check(precision, Colons::Colon, Pad::None, false, offsets, expected[4]);
855 check(precision, Colons::Colon, Pad::None, true, offsets, expected[5]);
856 check(precision, Colons::None, Pad::Zero, false, offsets, expected[6]);
857 check(precision, Colons::None, Pad::Zero, true, offsets, expected[7]);
858 check(precision, Colons::None, Pad::Space, false, offsets, expected[8]);
859 check(precision, Colons::None, Pad::Space, true, offsets, expected[9]);
860 check(precision, Colons::None, Pad::None, false, offsets, expected[10]);
861 check(precision, Colons::None, Pad::None, true, offsets, expected[11]);
862 check(precision, Colons::Maybe, Pad::Zero, false, offsets, expected[6]);
864 check(precision, Colons::Maybe, Pad::Zero, true, offsets, expected[7]);
865 check(precision, Colons::Maybe, Pad::Space, false, offsets, expected[8]);
866 check(precision, Colons::Maybe, Pad::Space, true, offsets, expected[9]);
867 check(precision, Colons::Maybe, Pad::None, false, offsets, expected[10]);
868 check(precision, Colons::Maybe, Pad::None, true, offsets, expected[11]);
869 }
870 check_all(
871 OffsetPrecision::Hours,
872 [
873 ["+03", "-03", "+11", "-11", "+02", "-12", "+00"],
874 ["+03", "-03", "+11", "-11", "+02", "-12", "Z"],
875 [" +3", " -3", "+11", "-11", " +2", "-12", " +0"],
876 [" +3", " -3", "+11", "-11", " +2", "-12", "Z"],
877 ["+3", "-3", "+11", "-11", "+2", "-12", "+0"],
878 ["+3", "-3", "+11", "-11", "+2", "-12", "Z"],
879 ["+03", "-03", "+11", "-11", "+02", "-12", "+00"],
880 ["+03", "-03", "+11", "-11", "+02", "-12", "Z"],
881 [" +3", " -3", "+11", "-11", " +2", "-12", " +0"],
882 [" +3", " -3", "+11", "-11", " +2", "-12", "Z"],
883 ["+3", "-3", "+11", "-11", "+2", "-12", "+0"],
884 ["+3", "-3", "+11", "-11", "+2", "-12", "Z"],
885 ],
886 );
887 check_all(
888 OffsetPrecision::Minutes,
889 [
890 ["+03:45", "-03:30", "+11:00", "-11:00", "+02:34", "-12:35", "+00:00"],
891 ["+03:45", "-03:30", "+11:00", "-11:00", "+02:34", "-12:35", "Z"],
892 [" +3:45", " -3:30", "+11:00", "-11:00", " +2:34", "-12:35", " +0:00"],
893 [" +3:45", " -3:30", "+11:00", "-11:00", " +2:34", "-12:35", "Z"],
894 ["+3:45", "-3:30", "+11:00", "-11:00", "+2:34", "-12:35", "+0:00"],
895 ["+3:45", "-3:30", "+11:00", "-11:00", "+2:34", "-12:35", "Z"],
896 ["+0345", "-0330", "+1100", "-1100", "+0234", "-1235", "+0000"],
897 ["+0345", "-0330", "+1100", "-1100", "+0234", "-1235", "Z"],
898 [" +345", " -330", "+1100", "-1100", " +234", "-1235", " +000"],
899 [" +345", " -330", "+1100", "-1100", " +234", "-1235", "Z"],
900 ["+345", "-330", "+1100", "-1100", "+234", "-1235", "+000"],
901 ["+345", "-330", "+1100", "-1100", "+234", "-1235", "Z"],
902 ],
903 );
904 #[rustfmt::skip]
905 check_all(
906 OffsetPrecision::Seconds,
907 [
908 ["+03:45:00", "-03:30:00", "+11:00:00", "-11:00:22", "+02:34:26", "-12:34:30", "+00:00:00"],
909 ["+03:45:00", "-03:30:00", "+11:00:00", "-11:00:22", "+02:34:26", "-12:34:30", "Z"],
910 [" +3:45:00", " -3:30:00", "+11:00:00", "-11:00:22", " +2:34:26", "-12:34:30", " +0:00:00"],
911 [" +3:45:00", " -3:30:00", "+11:00:00", "-11:00:22", " +2:34:26", "-12:34:30", "Z"],
912 ["+3:45:00", "-3:30:00", "+11:00:00", "-11:00:22", "+2:34:26", "-12:34:30", "+0:00:00"],
913 ["+3:45:00", "-3:30:00", "+11:00:00", "-11:00:22", "+2:34:26", "-12:34:30", "Z"],
914 ["+034500", "-033000", "+110000", "-110022", "+023426", "-123430", "+000000"],
915 ["+034500", "-033000", "+110000", "-110022", "+023426", "-123430", "Z"],
916 [" +34500", " -33000", "+110000", "-110022", " +23426", "-123430", " +00000"],
917 [" +34500", " -33000", "+110000", "-110022", " +23426", "-123430", "Z"],
918 ["+34500", "-33000", "+110000", "-110022", "+23426", "-123430", "+00000"],
919 ["+34500", "-33000", "+110000", "-110022", "+23426", "-123430", "Z"],
920 ],
921 );
922 check_all(
923 OffsetPrecision::OptionalMinutes,
924 [
925 ["+03:45", "-03:30", "+11", "-11", "+02:34", "-12:35", "+00"],
926 ["+03:45", "-03:30", "+11", "-11", "+02:34", "-12:35", "Z"],
927 [" +3:45", " -3:30", "+11", "-11", " +2:34", "-12:35", " +0"],
928 [" +3:45", " -3:30", "+11", "-11", " +2:34", "-12:35", "Z"],
929 ["+3:45", "-3:30", "+11", "-11", "+2:34", "-12:35", "+0"],
930 ["+3:45", "-3:30", "+11", "-11", "+2:34", "-12:35", "Z"],
931 ["+0345", "-0330", "+11", "-11", "+0234", "-1235", "+00"],
932 ["+0345", "-0330", "+11", "-11", "+0234", "-1235", "Z"],
933 [" +345", " -330", "+11", "-11", " +234", "-1235", " +0"],
934 [" +345", " -330", "+11", "-11", " +234", "-1235", "Z"],
935 ["+345", "-330", "+11", "-11", "+234", "-1235", "+0"],
936 ["+345", "-330", "+11", "-11", "+234", "-1235", "Z"],
937 ],
938 );
939 check_all(
940 OffsetPrecision::OptionalSeconds,
941 [
942 ["+03:45", "-03:30", "+11:00", "-11:00:22", "+02:34:26", "-12:34:30", "+00:00"],
943 ["+03:45", "-03:30", "+11:00", "-11:00:22", "+02:34:26", "-12:34:30", "Z"],
944 [" +3:45", " -3:30", "+11:00", "-11:00:22", " +2:34:26", "-12:34:30", " +0:00"],
945 [" +3:45", " -3:30", "+11:00", "-11:00:22", " +2:34:26", "-12:34:30", "Z"],
946 ["+3:45", "-3:30", "+11:00", "-11:00:22", "+2:34:26", "-12:34:30", "+0:00"],
947 ["+3:45", "-3:30", "+11:00", "-11:00:22", "+2:34:26", "-12:34:30", "Z"],
948 ["+0345", "-0330", "+1100", "-110022", "+023426", "-123430", "+0000"],
949 ["+0345", "-0330", "+1100", "-110022", "+023426", "-123430", "Z"],
950 [" +345", " -330", "+1100", "-110022", " +23426", "-123430", " +000"],
951 [" +345", " -330", "+1100", "-110022", " +23426", "-123430", "Z"],
952 ["+345", "-330", "+1100", "-110022", "+23426", "-123430", "+000"],
953 ["+345", "-330", "+1100", "-110022", "+23426", "-123430", "Z"],
954 ],
955 );
956 check_all(
957 OffsetPrecision::OptionalMinutesAndSeconds,
958 [
959 ["+03:45", "-03:30", "+11", "-11:00:22", "+02:34:26", "-12:34:30", "+00"],
960 ["+03:45", "-03:30", "+11", "-11:00:22", "+02:34:26", "-12:34:30", "Z"],
961 [" +3:45", " -3:30", "+11", "-11:00:22", " +2:34:26", "-12:34:30", " +0"],
962 [" +3:45", " -3:30", "+11", "-11:00:22", " +2:34:26", "-12:34:30", "Z"],
963 ["+3:45", "-3:30", "+11", "-11:00:22", "+2:34:26", "-12:34:30", "+0"],
964 ["+3:45", "-3:30", "+11", "-11:00:22", "+2:34:26", "-12:34:30", "Z"],
965 ["+0345", "-0330", "+11", "-110022", "+023426", "-123430", "+00"],
966 ["+0345", "-0330", "+11", "-110022", "+023426", "-123430", "Z"],
967 [" +345", " -330", "+11", "-110022", " +23426", "-123430", " +0"],
968 [" +345", " -330", "+11", "-110022", " +23426", "-123430", "Z"],
969 ["+345", "-330", "+11", "-110022", "+23426", "-123430", "+0"],
970 ["+345", "-330", "+11", "-110022", "+23426", "-123430", "Z"],
971 ],
972 );
973 }
974}