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