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