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