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