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