• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // This is a part of Chrono.
2 // See README.md and LICENSE.txt for details.
3 
4 //! Date and time formatting routines.
5 
6 #[cfg(all(feature = "alloc", not(feature = "std"), not(test)))]
7 use alloc::string::{String, ToString};
8 #[cfg(feature = "alloc")]
9 use core::borrow::Borrow;
10 #[cfg(feature = "alloc")]
11 use core::fmt::Display;
12 use core::fmt::{self, Write};
13 
14 #[cfg(feature = "alloc")]
15 use crate::offset::Offset;
16 #[cfg(any(feature = "alloc", feature = "serde"))]
17 use crate::{Datelike, FixedOffset, NaiveDateTime, Timelike};
18 #[cfg(feature = "alloc")]
19 use crate::{NaiveDate, NaiveTime, Weekday};
20 
21 #[cfg(feature = "alloc")]
22 use super::locales;
23 #[cfg(any(feature = "alloc", feature = "serde"))]
24 use super::{Colons, OffsetFormat, OffsetPrecision, Pad};
25 #[cfg(feature = "alloc")]
26 use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric};
27 #[cfg(feature = "alloc")]
28 use locales::*;
29 
30 /// A *temporary* object which can be used as an argument to `format!` or others.
31 /// This is normally constructed via `format` methods of each date and time type.
32 #[cfg(feature = "alloc")]
33 #[derive(Debug)]
34 pub struct DelayedFormat<I> {
35     /// The date view, if any.
36     date: Option<NaiveDate>,
37     /// The time view, if any.
38     time: Option<NaiveTime>,
39     /// The name and local-to-UTC difference for the offset (timezone), if any.
40     off: Option<(String, FixedOffset)>,
41     /// An iterator returning formatting items.
42     items: I,
43     /// Locale used for text.
44     /// ZST if the `unstable-locales` feature is not enabled.
45     locale: Locale,
46 }
47 
48 #[cfg(feature = "alloc")]
49 impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> DelayedFormat<I> {
50     /// Makes a new `DelayedFormat` value out of local date and time.
51     #[must_use]
new(date: Option<NaiveDate>, time: Option<NaiveTime>, items: I) -> DelayedFormat<I>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     /// Makes a new `DelayedFormat` value out of local date and time and UTC offset.
57     #[must_use]
new_with_offset<Off>( date: Option<NaiveDate>, time: Option<NaiveTime>, offset: &Off, items: I, ) -> DelayedFormat<I> where Off: Offset + Display,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     /// Makes a new `DelayedFormat` value out of local date and time and locale.
72     #[cfg(feature = "unstable-locales")]
73     #[must_use]
new_with_locale( date: Option<NaiveDate>, time: Option<NaiveTime>, items: I, locale: Locale, ) -> DelayedFormat<I>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     /// Makes a new `DelayedFormat` value out of local date and time, UTC offset and locale.
84     #[cfg(feature = "unstable-locales")]
85     #[must_use]
new_with_offset_and_locale<Off>( date: Option<NaiveDate>, time: Option<NaiveTime>, offset: &Off, items: I, locale: Locale, ) -> DelayedFormat<I> where Off: Offset + Display,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 
format(&self, w: &mut impl Write) -> fmt::Result100     fn format(&self, w: &mut impl Write) -> fmt::Result {
101         for item in self.items.clone() {
102             match *item.borrow() {
103                 Item::Literal(s) | Item::Space(s) => w.write_str(s),
104                 #[cfg(feature = "alloc")]
105                 Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => w.write_str(s),
106                 Item::Numeric(ref spec, pad) => self.format_numeric(w, spec, pad),
107                 Item::Fixed(ref spec) => self.format_fixed(w, spec),
108                 Item::Error => Err(fmt::Error),
109             }?;
110         }
111         Ok(())
112     }
113 
114     #[cfg(feature = "alloc")]
format_numeric(&self, w: &mut impl Write, spec: &Numeric, pad: Pad) -> fmt::Result115     fn format_numeric(&self, w: &mut impl Write, spec: &Numeric, pad: Pad) -> fmt::Result {
116         use self::Numeric::*;
117 
118         fn write_one(w: &mut impl Write, v: u8) -> fmt::Result {
119             w.write_char((b'0' + v) as char)
120         }
121 
122         fn write_two(w: &mut impl Write, v: u8, pad: Pad) -> fmt::Result {
123             let ones = b'0' + v % 10;
124             match (v / 10, pad) {
125                 (0, Pad::None) => {}
126                 (0, Pad::Space) => w.write_char(' ')?,
127                 (tens, _) => w.write_char((b'0' + tens) as char)?,
128             }
129             w.write_char(ones as char)
130         }
131 
132         #[inline]
133         fn write_year(w: &mut impl Write, year: i32, pad: Pad) -> fmt::Result {
134             if (1000..=9999).contains(&year) {
135                 // fast path
136                 write_hundreds(w, (year / 100) as u8)?;
137                 write_hundreds(w, (year % 100) as u8)
138             } else {
139                 write_n(w, 4, year as i64, pad, !(0..10_000).contains(&year))
140             }
141         }
142 
143         fn write_n(
144             w: &mut impl Write,
145             n: usize,
146             v: i64,
147             pad: Pad,
148             always_sign: bool,
149         ) -> fmt::Result {
150             if always_sign {
151                 match pad {
152                     Pad::None => write!(w, "{:+}", v),
153                     Pad::Zero => write!(w, "{:+01$}", v, n + 1),
154                     Pad::Space => write!(w, "{:+1$}", v, n + 1),
155                 }
156             } else {
157                 match pad {
158                     Pad::None => write!(w, "{}", v),
159                     Pad::Zero => write!(w, "{:01$}", v, n),
160                     Pad::Space => write!(w, "{:1$}", v, n),
161                 }
162             }
163         }
164 
165         match (spec, self.date, self.time) {
166             (Year, Some(d), _) => write_year(w, d.year(), pad),
167             (YearDiv100, Some(d), _) => write_two(w, d.year().div_euclid(100) as u8, pad),
168             (YearMod100, Some(d), _) => write_two(w, d.year().rem_euclid(100) as u8, pad),
169             (IsoYear, Some(d), _) => write_year(w, d.iso_week().year(), pad),
170             (IsoYearDiv100, Some(d), _) => {
171                 write_two(w, d.iso_week().year().div_euclid(100) as u8, pad)
172             }
173             (IsoYearMod100, Some(d), _) => {
174                 write_two(w, d.iso_week().year().rem_euclid(100) as u8, pad)
175             }
176             (Month, Some(d), _) => write_two(w, d.month() as u8, pad),
177             (Day, Some(d), _) => write_two(w, d.day() as u8, pad),
178             (WeekFromSun, Some(d), _) => write_two(w, d.weeks_from(Weekday::Sun) as u8, pad),
179             (WeekFromMon, Some(d), _) => write_two(w, d.weeks_from(Weekday::Mon) as u8, pad),
180             (IsoWeek, Some(d), _) => write_two(w, d.iso_week().week() as u8, pad),
181             (NumDaysFromSun, Some(d), _) => write_one(w, d.weekday().num_days_from_sunday() as u8),
182             (WeekdayFromMon, Some(d), _) => write_one(w, d.weekday().number_from_monday() as u8),
183             (Ordinal, Some(d), _) => write_n(w, 3, d.ordinal() as i64, pad, false),
184             (Hour, _, Some(t)) => write_two(w, t.hour() as u8, pad),
185             (Hour12, _, Some(t)) => write_two(w, t.hour12().1 as u8, pad),
186             (Minute, _, Some(t)) => write_two(w, t.minute() as u8, pad),
187             (Second, _, Some(t)) => {
188                 write_two(w, (t.second() + t.nanosecond() / 1_000_000_000) as u8, pad)
189             }
190             (Nanosecond, _, Some(t)) => {
191                 write_n(w, 9, (t.nanosecond() % 1_000_000_000) as i64, pad, false)
192             }
193             (Timestamp, Some(d), Some(t)) => {
194                 let offset = self.off.as_ref().map(|(_, o)| i64::from(o.local_minus_utc()));
195                 let timestamp = d.and_time(t).and_utc().timestamp() - offset.unwrap_or(0);
196                 write_n(w, 9, timestamp, pad, false)
197             }
198             (Internal(_), _, _) => Ok(()), // for future expansion
199             _ => Err(fmt::Error),          // insufficient arguments for given format
200         }
201     }
202 
203     #[cfg(feature = "alloc")]
format_fixed(&self, w: &mut impl Write, spec: &Fixed) -> fmt::Result204     fn format_fixed(&self, w: &mut impl Write, spec: &Fixed) -> fmt::Result {
205         use Fixed::*;
206         use InternalInternal::*;
207 
208         match (spec, self.date, self.time, self.off.as_ref()) {
209             (ShortMonthName, Some(d), _, _) => {
210                 w.write_str(short_months(self.locale)[d.month0() as usize])
211             }
212             (LongMonthName, Some(d), _, _) => {
213                 w.write_str(long_months(self.locale)[d.month0() as usize])
214             }
215             (ShortWeekdayName, Some(d), _, _) => w.write_str(
216                 short_weekdays(self.locale)[d.weekday().num_days_from_sunday() as usize],
217             ),
218             (LongWeekdayName, Some(d), _, _) => {
219                 w.write_str(long_weekdays(self.locale)[d.weekday().num_days_from_sunday() as usize])
220             }
221             (LowerAmPm, _, Some(t), _) => {
222                 let ampm = if t.hour12().0 { am_pm(self.locale)[1] } else { am_pm(self.locale)[0] };
223                 for c in ampm.chars().flat_map(|c| c.to_lowercase()) {
224                     w.write_char(c)?
225                 }
226                 Ok(())
227             }
228             (UpperAmPm, _, Some(t), _) => {
229                 let ampm = if t.hour12().0 { am_pm(self.locale)[1] } else { am_pm(self.locale)[0] };
230                 w.write_str(ampm)
231             }
232             (Nanosecond, _, Some(t), _) => {
233                 let nano = t.nanosecond() % 1_000_000_000;
234                 if nano == 0 {
235                     Ok(())
236                 } else {
237                     w.write_str(decimal_point(self.locale))?;
238                     if nano % 1_000_000 == 0 {
239                         write!(w, "{:03}", nano / 1_000_000)
240                     } else if nano % 1_000 == 0 {
241                         write!(w, "{:06}", nano / 1_000)
242                     } else {
243                         write!(w, "{:09}", nano)
244                     }
245                 }
246             }
247             (Nanosecond3, _, Some(t), _) => {
248                 w.write_str(decimal_point(self.locale))?;
249                 write!(w, "{:03}", t.nanosecond() / 1_000_000 % 1000)
250             }
251             (Nanosecond6, _, Some(t), _) => {
252                 w.write_str(decimal_point(self.locale))?;
253                 write!(w, "{:06}", t.nanosecond() / 1_000 % 1_000_000)
254             }
255             (Nanosecond9, _, Some(t), _) => {
256                 w.write_str(decimal_point(self.locale))?;
257                 write!(w, "{:09}", t.nanosecond() % 1_000_000_000)
258             }
259             (Internal(InternalFixed { val: Nanosecond3NoDot }), _, Some(t), _) => {
260                 write!(w, "{:03}", t.nanosecond() / 1_000_000 % 1_000)
261             }
262             (Internal(InternalFixed { val: Nanosecond6NoDot }), _, Some(t), _) => {
263                 write!(w, "{:06}", t.nanosecond() / 1_000 % 1_000_000)
264             }
265             (Internal(InternalFixed { val: Nanosecond9NoDot }), _, Some(t), _) => {
266                 write!(w, "{:09}", t.nanosecond() % 1_000_000_000)
267             }
268             (TimezoneName, _, _, Some((tz_name, _))) => write!(w, "{}", tz_name),
269             (TimezoneOffset | TimezoneOffsetZ, _, _, Some((_, off))) => {
270                 let offset_format = OffsetFormat {
271                     precision: OffsetPrecision::Minutes,
272                     colons: Colons::Maybe,
273                     allow_zulu: *spec == TimezoneOffsetZ,
274                     padding: Pad::Zero,
275                 };
276                 offset_format.format(w, *off)
277             }
278             (TimezoneOffsetColon | TimezoneOffsetColonZ, _, _, Some((_, off))) => {
279                 let offset_format = OffsetFormat {
280                     precision: OffsetPrecision::Minutes,
281                     colons: Colons::Colon,
282                     allow_zulu: *spec == TimezoneOffsetColonZ,
283                     padding: Pad::Zero,
284                 };
285                 offset_format.format(w, *off)
286             }
287             (TimezoneOffsetDoubleColon, _, _, Some((_, off))) => {
288                 let offset_format = OffsetFormat {
289                     precision: OffsetPrecision::Seconds,
290                     colons: Colons::Colon,
291                     allow_zulu: false,
292                     padding: Pad::Zero,
293                 };
294                 offset_format.format(w, *off)
295             }
296             (TimezoneOffsetTripleColon, _, _, Some((_, off))) => {
297                 let offset_format = OffsetFormat {
298                     precision: OffsetPrecision::Hours,
299                     colons: Colons::None,
300                     allow_zulu: false,
301                     padding: Pad::Zero,
302                 };
303                 offset_format.format(w, *off)
304             }
305             (RFC2822, Some(d), Some(t), Some((_, off))) => {
306                 write_rfc2822(w, crate::NaiveDateTime::new(d, t), *off)
307             }
308             (RFC3339, Some(d), Some(t), Some((_, off))) => write_rfc3339(
309                 w,
310                 crate::NaiveDateTime::new(d, t),
311                 *off,
312                 SecondsFormat::AutoSi,
313                 false,
314             ),
315             _ => Err(fmt::Error), // insufficient arguments for given format
316         }
317     }
318 }
319 
320 #[cfg(feature = "alloc")]
321 impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> Display for DelayedFormat<I> {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result322     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
323         let mut result = String::new();
324         self.format(&mut result)?;
325         f.pad(&result)
326     }
327 }
328 
329 /// Tries to format given arguments with given formatting items.
330 /// Internally used by `DelayedFormat`.
331 #[cfg(feature = "alloc")]
332 #[deprecated(since = "0.4.32", note = "Use DelayedFormat::fmt instead")]
format<'a, I, B>( w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Option<&NaiveTime>, off: Option<&(String, FixedOffset)>, items: I, ) -> fmt::Result where I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>,333 pub fn format<'a, I, B>(
334     w: &mut fmt::Formatter,
335     date: Option<&NaiveDate>,
336     time: Option<&NaiveTime>,
337     off: Option<&(String, FixedOffset)>,
338     items: I,
339 ) -> fmt::Result
340 where
341     I: Iterator<Item = B> + Clone,
342     B: Borrow<Item<'a>>,
343 {
344     DelayedFormat {
345         date: date.copied(),
346         time: time.copied(),
347         off: off.cloned(),
348         items,
349         locale: default_locale(),
350     }
351     .fmt(w)
352 }
353 
354 /// Formats single formatting item
355 #[cfg(feature = "alloc")]
356 #[deprecated(since = "0.4.32", note = "Use DelayedFormat::fmt instead")]
format_item( w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Option<&NaiveTime>, off: Option<&(String, FixedOffset)>, item: &Item<'_>, ) -> fmt::Result357 pub fn format_item(
358     w: &mut fmt::Formatter,
359     date: Option<&NaiveDate>,
360     time: Option<&NaiveTime>,
361     off: Option<&(String, FixedOffset)>,
362     item: &Item<'_>,
363 ) -> fmt::Result {
364     DelayedFormat {
365         date: date.copied(),
366         time: time.copied(),
367         off: off.cloned(),
368         items: [item].into_iter(),
369         locale: default_locale(),
370     }
371     .fmt(w)
372 }
373 
374 #[cfg(any(feature = "alloc", feature = "serde"))]
375 impl OffsetFormat {
376     /// Writes an offset from UTC with the format defined by `self`.
format(&self, w: &mut impl Write, off: FixedOffset) -> fmt::Result377     fn format(&self, w: &mut impl Write, off: FixedOffset) -> fmt::Result {
378         let off = off.local_minus_utc();
379         if self.allow_zulu && off == 0 {
380             w.write_char('Z')?;
381             return Ok(());
382         }
383         let (sign, off) = if off < 0 { ('-', -off) } else { ('+', off) };
384 
385         let hours;
386         let mut mins = 0;
387         let mut secs = 0;
388         let precision = match self.precision {
389             OffsetPrecision::Hours => {
390                 // Minutes and seconds are simply truncated
391                 hours = (off / 3600) as u8;
392                 OffsetPrecision::Hours
393             }
394             OffsetPrecision::Minutes | OffsetPrecision::OptionalMinutes => {
395                 // Round seconds to the nearest minute.
396                 let minutes = (off + 30) / 60;
397                 mins = (minutes % 60) as u8;
398                 hours = (minutes / 60) as u8;
399                 if self.precision == OffsetPrecision::OptionalMinutes && mins == 0 {
400                     OffsetPrecision::Hours
401                 } else {
402                     OffsetPrecision::Minutes
403                 }
404             }
405             OffsetPrecision::Seconds
406             | OffsetPrecision::OptionalSeconds
407             | OffsetPrecision::OptionalMinutesAndSeconds => {
408                 let minutes = off / 60;
409                 secs = (off % 60) as u8;
410                 mins = (minutes % 60) as u8;
411                 hours = (minutes / 60) as u8;
412                 if self.precision != OffsetPrecision::Seconds && secs == 0 {
413                     if self.precision == OffsetPrecision::OptionalMinutesAndSeconds && mins == 0 {
414                         OffsetPrecision::Hours
415                     } else {
416                         OffsetPrecision::Minutes
417                     }
418                 } else {
419                     OffsetPrecision::Seconds
420                 }
421             }
422         };
423         let colons = self.colons == Colons::Colon;
424 
425         if hours < 10 {
426             if self.padding == Pad::Space {
427                 w.write_char(' ')?;
428             }
429             w.write_char(sign)?;
430             if self.padding == Pad::Zero {
431                 w.write_char('0')?;
432             }
433             w.write_char((b'0' + hours) as char)?;
434         } else {
435             w.write_char(sign)?;
436             write_hundreds(w, hours)?;
437         }
438         if let OffsetPrecision::Minutes | OffsetPrecision::Seconds = precision {
439             if colons {
440                 w.write_char(':')?;
441             }
442             write_hundreds(w, mins)?;
443         }
444         if let OffsetPrecision::Seconds = precision {
445             if colons {
446                 w.write_char(':')?;
447             }
448             write_hundreds(w, secs)?;
449         }
450         Ok(())
451     }
452 }
453 
454 /// Specific formatting options for seconds. This may be extended in the
455 /// future, so exhaustive matching in external code is not recommended.
456 ///
457 /// See the `TimeZone::to_rfc3339_opts` function for usage.
458 #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
459 #[allow(clippy::manual_non_exhaustive)]
460 pub enum SecondsFormat {
461     /// Format whole seconds only, with no decimal point nor subseconds.
462     Secs,
463 
464     /// Use fixed 3 subsecond digits. This corresponds to [Fixed::Nanosecond3].
465     Millis,
466 
467     /// Use fixed 6 subsecond digits. This corresponds to [Fixed::Nanosecond6].
468     Micros,
469 
470     /// Use fixed 9 subsecond digits. This corresponds to [Fixed::Nanosecond9].
471     Nanos,
472 
473     /// Automatically select one of `Secs`, `Millis`, `Micros`, or `Nanos` to display all available
474     /// non-zero sub-second digits.  This corresponds to [Fixed::Nanosecond].
475     AutoSi,
476 
477     // Do not match against this.
478     #[doc(hidden)]
479     __NonExhaustive,
480 }
481 
482 /// Writes the date, time and offset to the string. same as `%Y-%m-%dT%H:%M:%S%.f%:z`
483 #[inline]
484 #[cfg(any(feature = "alloc", feature = "serde"))]
write_rfc3339( w: &mut impl Write, dt: NaiveDateTime, off: FixedOffset, secform: SecondsFormat, use_z: bool, ) -> fmt::Result485 pub(crate) fn write_rfc3339(
486     w: &mut impl Write,
487     dt: NaiveDateTime,
488     off: FixedOffset,
489     secform: SecondsFormat,
490     use_z: bool,
491 ) -> fmt::Result {
492     let year = dt.date().year();
493     if (0..=9999).contains(&year) {
494         write_hundreds(w, (year / 100) as u8)?;
495         write_hundreds(w, (year % 100) as u8)?;
496     } else {
497         // ISO 8601 requires the explicit sign for out-of-range years
498         write!(w, "{:+05}", year)?;
499     }
500     w.write_char('-')?;
501     write_hundreds(w, dt.date().month() as u8)?;
502     w.write_char('-')?;
503     write_hundreds(w, dt.date().day() as u8)?;
504 
505     w.write_char('T')?;
506 
507     let (hour, min, mut sec) = dt.time().hms();
508     let mut nano = dt.nanosecond();
509     if nano >= 1_000_000_000 {
510         sec += 1;
511         nano -= 1_000_000_000;
512     }
513     write_hundreds(w, hour as u8)?;
514     w.write_char(':')?;
515     write_hundreds(w, min as u8)?;
516     w.write_char(':')?;
517     let sec = sec;
518     write_hundreds(w, sec as u8)?;
519 
520     match secform {
521         SecondsFormat::Secs => {}
522         SecondsFormat::Millis => write!(w, ".{:03}", nano / 1_000_000)?,
523         SecondsFormat::Micros => write!(w, ".{:06}", nano / 1000)?,
524         SecondsFormat::Nanos => write!(w, ".{:09}", nano)?,
525         SecondsFormat::AutoSi => {
526             if nano == 0 {
527             } else if nano % 1_000_000 == 0 {
528                 write!(w, ".{:03}", nano / 1_000_000)?
529             } else if nano % 1_000 == 0 {
530                 write!(w, ".{:06}", nano / 1_000)?
531             } else {
532                 write!(w, ".{:09}", nano)?
533             }
534         }
535         SecondsFormat::__NonExhaustive => unreachable!(),
536     };
537 
538     OffsetFormat {
539         precision: OffsetPrecision::Minutes,
540         colons: Colons::Colon,
541         allow_zulu: use_z,
542         padding: Pad::Zero,
543     }
544     .format(w, off)
545 }
546 
547 #[cfg(feature = "alloc")]
548 /// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z`
write_rfc2822( w: &mut impl Write, dt: NaiveDateTime, off: FixedOffset, ) -> fmt::Result549 pub(crate) fn write_rfc2822(
550     w: &mut impl Write,
551     dt: NaiveDateTime,
552     off: FixedOffset,
553 ) -> fmt::Result {
554     let year = dt.year();
555     // RFC2822 is only defined on years 0 through 9999
556     if !(0..=9999).contains(&year) {
557         return Err(fmt::Error);
558     }
559 
560     let english = default_locale();
561 
562     w.write_str(short_weekdays(english)[dt.weekday().num_days_from_sunday() as usize])?;
563     w.write_str(", ")?;
564     let day = dt.day();
565     if day < 10 {
566         w.write_char((b'0' + day as u8) as char)?;
567     } else {
568         write_hundreds(w, day as u8)?;
569     }
570     w.write_char(' ')?;
571     w.write_str(short_months(english)[dt.month0() as usize])?;
572     w.write_char(' ')?;
573     write_hundreds(w, (year / 100) as u8)?;
574     write_hundreds(w, (year % 100) as u8)?;
575     w.write_char(' ')?;
576 
577     let (hour, min, sec) = dt.time().hms();
578     write_hundreds(w, hour as u8)?;
579     w.write_char(':')?;
580     write_hundreds(w, min as u8)?;
581     w.write_char(':')?;
582     let sec = sec + dt.nanosecond() / 1_000_000_000;
583     write_hundreds(w, sec as u8)?;
584     w.write_char(' ')?;
585     OffsetFormat {
586         precision: OffsetPrecision::Minutes,
587         colons: Colons::None,
588         allow_zulu: false,
589         padding: Pad::Zero,
590     }
591     .format(w, off)
592 }
593 
594 /// Equivalent to `{:02}` formatting for n < 100.
write_hundreds(w: &mut impl Write, n: u8) -> fmt::Result595 pub(crate) fn write_hundreds(w: &mut impl Write, n: u8) -> fmt::Result {
596     if n >= 100 {
597         return Err(fmt::Error);
598     }
599 
600     let tens = b'0' + n / 10;
601     let ones = b'0' + n % 10;
602     w.write_char(tens as char)?;
603     w.write_char(ones as char)
604 }
605 
606 #[cfg(test)]
607 #[cfg(feature = "alloc")]
608 mod tests {
609     use super::{Colons, OffsetFormat, OffsetPrecision, Pad};
610     use crate::FixedOffset;
611     #[cfg(feature = "alloc")]
612     use crate::{NaiveDate, NaiveTime, TimeZone, Timelike, Utc};
613 
614     #[test]
615     #[cfg(feature = "alloc")]
test_date_format()616     fn test_date_format() {
617         let d = NaiveDate::from_ymd_opt(2012, 3, 4).unwrap();
618         assert_eq!(d.format("%Y,%C,%y,%G,%g").to_string(), "2012,20,12,2012,12");
619         assert_eq!(d.format("%m,%b,%h,%B").to_string(), "03,Mar,Mar,March");
620         assert_eq!(d.format("%d,%e").to_string(), "04, 4");
621         assert_eq!(d.format("%U,%W,%V").to_string(), "10,09,09");
622         assert_eq!(d.format("%a,%A,%w,%u").to_string(), "Sun,Sunday,0,7");
623         assert_eq!(d.format("%j").to_string(), "064"); // since 2012 is a leap year
624         assert_eq!(d.format("%D,%x").to_string(), "03/04/12,03/04/12");
625         assert_eq!(d.format("%F").to_string(), "2012-03-04");
626         assert_eq!(d.format("%v").to_string(), " 4-Mar-2012");
627         assert_eq!(d.format("%t%n%%%n%t").to_string(), "\t\n%\n\t");
628 
629         // non-four-digit years
630         assert_eq!(
631             NaiveDate::from_ymd_opt(12345, 1, 1).unwrap().format("%Y").to_string(),
632             "+12345"
633         );
634         assert_eq!(NaiveDate::from_ymd_opt(1234, 1, 1).unwrap().format("%Y").to_string(), "1234");
635         assert_eq!(NaiveDate::from_ymd_opt(123, 1, 1).unwrap().format("%Y").to_string(), "0123");
636         assert_eq!(NaiveDate::from_ymd_opt(12, 1, 1).unwrap().format("%Y").to_string(), "0012");
637         assert_eq!(NaiveDate::from_ymd_opt(1, 1, 1).unwrap().format("%Y").to_string(), "0001");
638         assert_eq!(NaiveDate::from_ymd_opt(0, 1, 1).unwrap().format("%Y").to_string(), "0000");
639         assert_eq!(NaiveDate::from_ymd_opt(-1, 1, 1).unwrap().format("%Y").to_string(), "-0001");
640         assert_eq!(NaiveDate::from_ymd_opt(-12, 1, 1).unwrap().format("%Y").to_string(), "-0012");
641         assert_eq!(NaiveDate::from_ymd_opt(-123, 1, 1).unwrap().format("%Y").to_string(), "-0123");
642         assert_eq!(NaiveDate::from_ymd_opt(-1234, 1, 1).unwrap().format("%Y").to_string(), "-1234");
643         assert_eq!(
644             NaiveDate::from_ymd_opt(-12345, 1, 1).unwrap().format("%Y").to_string(),
645             "-12345"
646         );
647 
648         // corner cases
649         assert_eq!(
650             NaiveDate::from_ymd_opt(2007, 12, 31).unwrap().format("%G,%g,%U,%W,%V").to_string(),
651             "2008,08,52,53,01"
652         );
653         assert_eq!(
654             NaiveDate::from_ymd_opt(2010, 1, 3).unwrap().format("%G,%g,%U,%W,%V").to_string(),
655             "2009,09,01,00,53"
656         );
657     }
658 
659     #[test]
660     #[cfg(feature = "alloc")]
test_time_format()661     fn test_time_format() {
662         let t = NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap();
663         assert_eq!(t.format("%H,%k,%I,%l,%P,%p").to_string(), "03, 3,03, 3,am,AM");
664         assert_eq!(t.format("%M").to_string(), "05");
665         assert_eq!(t.format("%S,%f,%.f").to_string(), "07,098765432,.098765432");
666         assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".098,.098765,.098765432");
667         assert_eq!(t.format("%R").to_string(), "03:05");
668         assert_eq!(t.format("%T,%X").to_string(), "03:05:07,03:05:07");
669         assert_eq!(t.format("%r").to_string(), "03:05:07 AM");
670         assert_eq!(t.format("%t%n%%%n%t").to_string(), "\t\n%\n\t");
671 
672         let t = NaiveTime::from_hms_micro_opt(3, 5, 7, 432100).unwrap();
673         assert_eq!(t.format("%S,%f,%.f").to_string(), "07,432100000,.432100");
674         assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".432,.432100,.432100000");
675 
676         let t = NaiveTime::from_hms_milli_opt(3, 5, 7, 210).unwrap();
677         assert_eq!(t.format("%S,%f,%.f").to_string(), "07,210000000,.210");
678         assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".210,.210000,.210000000");
679 
680         let t = NaiveTime::from_hms_opt(3, 5, 7).unwrap();
681         assert_eq!(t.format("%S,%f,%.f").to_string(), "07,000000000,");
682         assert_eq!(t.format("%.3f,%.6f,%.9f").to_string(), ".000,.000000,.000000000");
683 
684         // corner cases
685         assert_eq!(
686             NaiveTime::from_hms_opt(13, 57, 9).unwrap().format("%r").to_string(),
687             "01:57:09 PM"
688         );
689         assert_eq!(
690             NaiveTime::from_hms_milli_opt(23, 59, 59, 1_000).unwrap().format("%X").to_string(),
691             "23:59:60"
692         );
693     }
694 
695     #[test]
696     #[cfg(feature = "alloc")]
test_datetime_format()697     fn test_datetime_format() {
698         let dt =
699             NaiveDate::from_ymd_opt(2010, 9, 8).unwrap().and_hms_milli_opt(7, 6, 54, 321).unwrap();
700         assert_eq!(dt.format("%c").to_string(), "Wed Sep  8 07:06:54 2010");
701         assert_eq!(dt.format("%s").to_string(), "1283929614");
702         assert_eq!(dt.format("%t%n%%%n%t").to_string(), "\t\n%\n\t");
703 
704         // a horror of leap second: coming near to you.
705         let dt = NaiveDate::from_ymd_opt(2012, 6, 30)
706             .unwrap()
707             .and_hms_milli_opt(23, 59, 59, 1_000)
708             .unwrap();
709         assert_eq!(dt.format("%c").to_string(), "Sat Jun 30 23:59:60 2012");
710         assert_eq!(dt.format("%s").to_string(), "1341100799"); // not 1341100800, it's intentional.
711     }
712 
713     #[test]
714     #[cfg(feature = "alloc")]
test_datetime_format_alignment()715     fn test_datetime_format_alignment() {
716         let datetime = Utc
717             .with_ymd_and_hms(2007, 1, 2, 12, 34, 56)
718             .unwrap()
719             .with_nanosecond(123456789)
720             .unwrap();
721 
722         // Item::Literal, odd number of padding bytes.
723         let percent = datetime.format("%%");
724         assert_eq!("   %", format!("{:>4}", percent));
725         assert_eq!("%   ", format!("{:<4}", percent));
726         assert_eq!(" %  ", format!("{:^4}", percent));
727 
728         // Item::Numeric, custom non-ASCII padding character
729         let year = datetime.format("%Y");
730         assert_eq!("——2007", format!("{:—>6}", year));
731         assert_eq!("2007——", format!("{:—<6}", year));
732         assert_eq!("—2007—", format!("{:—^6}", year));
733 
734         // Item::Fixed
735         let tz = datetime.format("%Z");
736         assert_eq!("  UTC", format!("{:>5}", tz));
737         assert_eq!("UTC  ", format!("{:<5}", tz));
738         assert_eq!(" UTC ", format!("{:^5}", tz));
739 
740         // [Item::Numeric, Item::Space, Item::Literal, Item::Space, Item::Numeric]
741         let ymd = datetime.format("%Y %B %d");
742         assert_eq!("  2007 January 02", format!("{:>17}", ymd));
743         assert_eq!("2007 January 02  ", format!("{:<17}", ymd));
744         assert_eq!(" 2007 January 02 ", format!("{:^17}", ymd));
745 
746         // Truncated
747         let time = datetime.format("%T%.6f");
748         assert_eq!("12:34:56.1234", format!("{:.13}", time));
749     }
750 
751     #[test]
test_offset_formatting()752     fn test_offset_formatting() {
753         fn check_all(precision: OffsetPrecision, expected: [[&str; 7]; 12]) {
754             fn check(
755                 precision: OffsetPrecision,
756                 colons: Colons,
757                 padding: Pad,
758                 allow_zulu: bool,
759                 offsets: [FixedOffset; 7],
760                 expected: [&str; 7],
761             ) {
762                 let offset_format = OffsetFormat { precision, colons, allow_zulu, padding };
763                 for (offset, expected) in offsets.iter().zip(expected.iter()) {
764                     let mut output = String::new();
765                     offset_format.format(&mut output, *offset).unwrap();
766                     assert_eq!(&output, expected);
767                 }
768             }
769             // +03:45, -03:30, +11:00, -11:00:22, +02:34:26, -12:34:30, +00:00
770             let offsets = [
771                 FixedOffset::east_opt(13_500).unwrap(),
772                 FixedOffset::east_opt(-12_600).unwrap(),
773                 FixedOffset::east_opt(39_600).unwrap(),
774                 FixedOffset::east_opt(-39_622).unwrap(),
775                 FixedOffset::east_opt(9266).unwrap(),
776                 FixedOffset::east_opt(-45270).unwrap(),
777                 FixedOffset::east_opt(0).unwrap(),
778             ];
779             check(precision, Colons::Colon, Pad::Zero, false, offsets, expected[0]);
780             check(precision, Colons::Colon, Pad::Zero, true, offsets, expected[1]);
781             check(precision, Colons::Colon, Pad::Space, false, offsets, expected[2]);
782             check(precision, Colons::Colon, Pad::Space, true, offsets, expected[3]);
783             check(precision, Colons::Colon, Pad::None, false, offsets, expected[4]);
784             check(precision, Colons::Colon, Pad::None, true, offsets, expected[5]);
785             check(precision, Colons::None, Pad::Zero, false, offsets, expected[6]);
786             check(precision, Colons::None, Pad::Zero, true, offsets, expected[7]);
787             check(precision, Colons::None, Pad::Space, false, offsets, expected[8]);
788             check(precision, Colons::None, Pad::Space, true, offsets, expected[9]);
789             check(precision, Colons::None, Pad::None, false, offsets, expected[10]);
790             check(precision, Colons::None, Pad::None, true, offsets, expected[11]);
791             // `Colons::Maybe` should format the same as `Colons::None`
792             check(precision, Colons::Maybe, Pad::Zero, false, offsets, expected[6]);
793             check(precision, Colons::Maybe, Pad::Zero, true, offsets, expected[7]);
794             check(precision, Colons::Maybe, Pad::Space, false, offsets, expected[8]);
795             check(precision, Colons::Maybe, Pad::Space, true, offsets, expected[9]);
796             check(precision, Colons::Maybe, Pad::None, false, offsets, expected[10]);
797             check(precision, Colons::Maybe, Pad::None, true, offsets, expected[11]);
798         }
799         check_all(
800             OffsetPrecision::Hours,
801             [
802                 ["+03", "-03", "+11", "-11", "+02", "-12", "+00"],
803                 ["+03", "-03", "+11", "-11", "+02", "-12", "Z"],
804                 [" +3", " -3", "+11", "-11", " +2", "-12", " +0"],
805                 [" +3", " -3", "+11", "-11", " +2", "-12", "Z"],
806                 ["+3", "-3", "+11", "-11", "+2", "-12", "+0"],
807                 ["+3", "-3", "+11", "-11", "+2", "-12", "Z"],
808                 ["+03", "-03", "+11", "-11", "+02", "-12", "+00"],
809                 ["+03", "-03", "+11", "-11", "+02", "-12", "Z"],
810                 [" +3", " -3", "+11", "-11", " +2", "-12", " +0"],
811                 [" +3", " -3", "+11", "-11", " +2", "-12", "Z"],
812                 ["+3", "-3", "+11", "-11", "+2", "-12", "+0"],
813                 ["+3", "-3", "+11", "-11", "+2", "-12", "Z"],
814             ],
815         );
816         check_all(
817             OffsetPrecision::Minutes,
818             [
819                 ["+03:45", "-03:30", "+11:00", "-11:00", "+02:34", "-12:35", "+00:00"],
820                 ["+03:45", "-03:30", "+11:00", "-11:00", "+02:34", "-12:35", "Z"],
821                 [" +3:45", " -3:30", "+11:00", "-11:00", " +2:34", "-12:35", " +0:00"],
822                 [" +3:45", " -3:30", "+11:00", "-11:00", " +2:34", "-12:35", "Z"],
823                 ["+3:45", "-3:30", "+11:00", "-11:00", "+2:34", "-12:35", "+0:00"],
824                 ["+3:45", "-3:30", "+11:00", "-11:00", "+2:34", "-12:35", "Z"],
825                 ["+0345", "-0330", "+1100", "-1100", "+0234", "-1235", "+0000"],
826                 ["+0345", "-0330", "+1100", "-1100", "+0234", "-1235", "Z"],
827                 [" +345", " -330", "+1100", "-1100", " +234", "-1235", " +000"],
828                 [" +345", " -330", "+1100", "-1100", " +234", "-1235", "Z"],
829                 ["+345", "-330", "+1100", "-1100", "+234", "-1235", "+000"],
830                 ["+345", "-330", "+1100", "-1100", "+234", "-1235", "Z"],
831             ],
832         );
833         #[rustfmt::skip]
834         check_all(
835             OffsetPrecision::Seconds,
836             [
837                 ["+03:45:00", "-03:30:00", "+11:00:00", "-11:00:22", "+02:34:26", "-12:34:30", "+00:00:00"],
838                 ["+03:45:00", "-03:30:00", "+11:00:00", "-11:00:22", "+02:34:26", "-12:34:30", "Z"],
839                 [" +3:45:00", " -3:30:00", "+11:00:00", "-11:00:22", " +2:34:26", "-12:34:30", " +0:00:00"],
840                 [" +3:45:00", " -3:30:00", "+11:00:00", "-11:00:22", " +2:34:26", "-12:34:30", "Z"],
841                 ["+3:45:00", "-3:30:00", "+11:00:00", "-11:00:22", "+2:34:26", "-12:34:30", "+0:00:00"],
842                 ["+3:45:00", "-3:30:00", "+11:00:00", "-11:00:22", "+2:34:26", "-12:34:30", "Z"],
843                 ["+034500", "-033000", "+110000", "-110022", "+023426", "-123430", "+000000"],
844                 ["+034500", "-033000", "+110000", "-110022", "+023426", "-123430", "Z"],
845                 [" +34500", " -33000", "+110000", "-110022", " +23426", "-123430", " +00000"],
846                 [" +34500", " -33000", "+110000", "-110022", " +23426", "-123430", "Z"],
847                 ["+34500", "-33000", "+110000", "-110022", "+23426", "-123430", "+00000"],
848                 ["+34500", "-33000", "+110000", "-110022", "+23426", "-123430", "Z"],
849             ],
850         );
851         check_all(
852             OffsetPrecision::OptionalMinutes,
853             [
854                 ["+03:45", "-03:30", "+11", "-11", "+02:34", "-12:35", "+00"],
855                 ["+03:45", "-03:30", "+11", "-11", "+02:34", "-12:35", "Z"],
856                 [" +3:45", " -3:30", "+11", "-11", " +2:34", "-12:35", " +0"],
857                 [" +3:45", " -3:30", "+11", "-11", " +2:34", "-12:35", "Z"],
858                 ["+3:45", "-3:30", "+11", "-11", "+2:34", "-12:35", "+0"],
859                 ["+3:45", "-3:30", "+11", "-11", "+2:34", "-12:35", "Z"],
860                 ["+0345", "-0330", "+11", "-11", "+0234", "-1235", "+00"],
861                 ["+0345", "-0330", "+11", "-11", "+0234", "-1235", "Z"],
862                 [" +345", " -330", "+11", "-11", " +234", "-1235", " +0"],
863                 [" +345", " -330", "+11", "-11", " +234", "-1235", "Z"],
864                 ["+345", "-330", "+11", "-11", "+234", "-1235", "+0"],
865                 ["+345", "-330", "+11", "-11", "+234", "-1235", "Z"],
866             ],
867         );
868         check_all(
869             OffsetPrecision::OptionalSeconds,
870             [
871                 ["+03:45", "-03:30", "+11:00", "-11:00:22", "+02:34:26", "-12:34:30", "+00:00"],
872                 ["+03:45", "-03:30", "+11:00", "-11:00:22", "+02:34:26", "-12:34:30", "Z"],
873                 [" +3:45", " -3:30", "+11:00", "-11:00:22", " +2:34:26", "-12:34:30", " +0:00"],
874                 [" +3:45", " -3:30", "+11:00", "-11:00:22", " +2:34:26", "-12:34:30", "Z"],
875                 ["+3:45", "-3:30", "+11:00", "-11:00:22", "+2:34:26", "-12:34:30", "+0:00"],
876                 ["+3:45", "-3:30", "+11:00", "-11:00:22", "+2:34:26", "-12:34:30", "Z"],
877                 ["+0345", "-0330", "+1100", "-110022", "+023426", "-123430", "+0000"],
878                 ["+0345", "-0330", "+1100", "-110022", "+023426", "-123430", "Z"],
879                 [" +345", " -330", "+1100", "-110022", " +23426", "-123430", " +000"],
880                 [" +345", " -330", "+1100", "-110022", " +23426", "-123430", "Z"],
881                 ["+345", "-330", "+1100", "-110022", "+23426", "-123430", "+000"],
882                 ["+345", "-330", "+1100", "-110022", "+23426", "-123430", "Z"],
883             ],
884         );
885         check_all(
886             OffsetPrecision::OptionalMinutesAndSeconds,
887             [
888                 ["+03:45", "-03:30", "+11", "-11:00:22", "+02:34:26", "-12:34:30", "+00"],
889                 ["+03:45", "-03:30", "+11", "-11:00:22", "+02:34:26", "-12:34:30", "Z"],
890                 [" +3:45", " -3:30", "+11", "-11:00:22", " +2:34:26", "-12:34:30", " +0"],
891                 [" +3:45", " -3:30", "+11", "-11:00:22", " +2:34:26", "-12:34:30", "Z"],
892                 ["+3:45", "-3:30", "+11", "-11:00:22", "+2:34:26", "-12:34:30", "+0"],
893                 ["+3:45", "-3:30", "+11", "-11:00:22", "+2:34:26", "-12:34:30", "Z"],
894                 ["+0345", "-0330", "+11", "-110022", "+023426", "-123430", "+00"],
895                 ["+0345", "-0330", "+11", "-110022", "+023426", "-123430", "Z"],
896                 [" +345", " -330", "+11", "-110022", " +23426", "-123430", " +0"],
897                 [" +345", " -330", "+11", "-110022", " +23426", "-123430", "Z"],
898                 ["+345", "-330", "+11", "-110022", "+23426", "-123430", "+0"],
899                 ["+345", "-330", "+11", "-110022", "+23426", "-123430", "Z"],
900             ],
901         );
902     }
903 }
904