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