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