chrono/format/
formatting.rs

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)))]
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/// 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)]
34pub 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")]
49impl<'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]
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]
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]
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]
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    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")]
115    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")]
204    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")]
321impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> Display for DelayedFormat<I> {
322    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")]
333pub 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
340where
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")]
357pub 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"))]
375impl OffsetFormat {
376    /// Writes an offset from UTC with the format defined by `self`.
377    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)]
460pub 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"))]
485pub(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`
549pub(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.
595pub(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")]
608mod 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")]
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")]
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")]
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")]
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]
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}