Coverage Report

Created: 2026-04-12 06:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/chrono/src/format/formatting.rs
Line
Count
Source
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)))]
7
use alloc::string::{String, ToString};
8
#[cfg(feature = "alloc")]
9
use core::borrow::Borrow;
10
#[cfg(feature = "alloc")]
11
use core::fmt::Display;
12
use core::fmt::{self, Write};
13
14
#[cfg(feature = "alloc")]
15
use crate::offset::Offset;
16
#[cfg(any(feature = "alloc", feature = "serde"))]
17
use crate::{Datelike, FixedOffset, NaiveDateTime, Timelike};
18
#[cfg(feature = "alloc")]
19
use crate::{NaiveDate, NaiveTime, Weekday};
20
21
#[cfg(feature = "alloc")]
22
use super::locales;
23
#[cfg(any(feature = "alloc", feature = "serde"))]
24
use super::{Colons, OffsetFormat, OffsetPrecision, Pad};
25
#[cfg(feature = "alloc")]
26
use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric};
27
#[cfg(feature = "alloc")]
28
use 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)]
34
pub 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")]
49
impl<'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
0
    pub fn write_to(&self, w: &mut (impl Write + ?Sized)) -> fmt::Result {
113
0
        for item in self.items.clone() {
114
0
            match *item.borrow() {
115
0
                Item::Literal(s) | Item::Space(s) => w.write_str(s),
116
                #[cfg(feature = "alloc")]
117
0
                Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => w.write_str(s),
118
0
                Item::Numeric(ref spec, pad) => self.format_numeric(w, spec, pad),
119
0
                Item::Fixed(ref spec) => self.format_fixed(w, spec),
120
0
                Item::Error => Err(fmt::Error),
121
0
            }?;
122
        }
123
0
        Ok(())
124
0
    }
125
126
    #[cfg(feature = "alloc")]
127
0
    fn format_numeric(
128
0
        &self,
129
0
        w: &mut (impl Write + ?Sized),
130
0
        spec: &Numeric,
131
0
        pad: Pad,
132
0
    ) -> fmt::Result {
133
        use self::Numeric::*;
134
135
0
        fn write_one(w: &mut (impl Write + ?Sized), v: u8) -> fmt::Result {
136
0
            w.write_char((b'0' + v) as char)
137
0
        }
138
139
0
        fn write_two(w: &mut (impl Write + ?Sized), v: u8, pad: Pad) -> fmt::Result {
140
0
            let ones = b'0' + v % 10;
141
0
            match (v / 10, pad) {
142
0
                (0, Pad::None) => {}
143
0
                (0, Pad::Space) => w.write_char(' ')?,
144
0
                (tens, _) => w.write_char((b'0' + tens) as char)?,
145
            }
146
0
            w.write_char(ones as char)
147
0
        }
148
149
        #[inline]
150
0
        fn write_year(w: &mut (impl Write + ?Sized), year: i32, pad: Pad) -> fmt::Result {
151
0
            if (1000..=9999).contains(&year) {
152
                // fast path
153
0
                write_hundreds(w, (year / 100) as u8)?;
154
0
                write_hundreds(w, (year % 100) as u8)
155
            } else {
156
0
                write_n(w, 4, year as i64, pad, !(0..10_000).contains(&year))
157
            }
158
0
        }
159
160
0
        fn write_n(
161
0
            w: &mut (impl Write + ?Sized),
162
0
            n: usize,
163
0
            v: i64,
164
0
            pad: Pad,
165
0
            always_sign: bool,
166
0
        ) -> fmt::Result {
167
0
            if always_sign {
168
0
                match pad {
169
0
                    Pad::None => write!(w, "{v:+}"),
170
0
                    Pad::Zero => write!(w, "{:+01$}", v, n + 1),
171
0
                    Pad::Space => write!(w, "{:+1$}", v, n + 1),
172
                }
173
            } else {
174
0
                match pad {
175
0
                    Pad::None => write!(w, "{v}"),
176
0
                    Pad::Zero => write!(w, "{v:0n$}"),
177
0
                    Pad::Space => write!(w, "{v:n$}"),
178
                }
179
            }
180
0
        }
181
182
0
        match (spec, self.date, self.time) {
183
0
            (Year, Some(d), _) => write_year(w, d.year(), pad),
184
0
            (YearDiv100, Some(d), _) => write_two(w, d.year().div_euclid(100) as u8, pad),
185
0
            (YearMod100, Some(d), _) => write_two(w, d.year().rem_euclid(100) as u8, pad),
186
0
            (IsoYear, Some(d), _) => write_year(w, d.iso_week().year(), pad),
187
0
            (IsoYearDiv100, Some(d), _) => {
188
0
                write_two(w, d.iso_week().year().div_euclid(100) as u8, pad)
189
            }
190
0
            (IsoYearMod100, Some(d), _) => {
191
0
                write_two(w, d.iso_week().year().rem_euclid(100) as u8, pad)
192
            }
193
0
            (Quarter, Some(d), _) => write_one(w, d.quarter() as u8),
194
0
            (Month, Some(d), _) => write_two(w, d.month() as u8, pad),
195
0
            (Day, Some(d), _) => write_two(w, d.day() as u8, pad),
196
0
            (WeekFromSun, Some(d), _) => write_two(w, d.weeks_from(Weekday::Sun) as u8, pad),
197
0
            (WeekFromMon, Some(d), _) => write_two(w, d.weeks_from(Weekday::Mon) as u8, pad),
198
0
            (IsoWeek, Some(d), _) => write_two(w, d.iso_week().week() as u8, pad),
199
0
            (NumDaysFromSun, Some(d), _) => write_one(w, d.weekday().num_days_from_sunday() as u8),
200
0
            (WeekdayFromMon, Some(d), _) => write_one(w, d.weekday().number_from_monday() as u8),
201
0
            (Ordinal, Some(d), _) => write_n(w, 3, d.ordinal() as i64, pad, false),
202
0
            (Hour, _, Some(t)) => write_two(w, t.hour() as u8, pad),
203
0
            (Hour12, _, Some(t)) => write_two(w, t.hour12().1 as u8, pad),
204
0
            (Minute, _, Some(t)) => write_two(w, t.minute() as u8, pad),
205
0
            (Second, _, Some(t)) => {
206
0
                write_two(w, (t.second() + t.nanosecond() / 1_000_000_000) as u8, pad)
207
            }
208
0
            (Nanosecond, _, Some(t)) => {
209
0
                write_n(w, 9, (t.nanosecond() % 1_000_000_000) as i64, pad, false)
210
            }
211
0
            (Timestamp, Some(d), Some(t)) => {
212
0
                let offset = self.off.as_ref().map(|(_, o)| i64::from(o.local_minus_utc()));
213
0
                let timestamp = d.and_time(t).and_utc().timestamp() - offset.unwrap_or(0);
214
0
                write_n(w, 9, timestamp, pad, false)
215
            }
216
0
            (Internal(_), _, _) => Ok(()), // for future expansion
217
0
            _ => Err(fmt::Error),          // insufficient arguments for given format
218
        }
219
0
    }
220
221
    #[cfg(feature = "alloc")]
222
0
    fn format_fixed(&self, w: &mut (impl Write + ?Sized), spec: &Fixed) -> fmt::Result {
223
        use Fixed::*;
224
        use InternalInternal::*;
225
226
0
        match (spec, self.date, self.time, self.off.as_ref()) {
227
0
            (ShortMonthName, Some(d), _, _) => {
228
0
                w.write_str(short_months(self.locale)[d.month0() as usize])
229
            }
230
0
            (LongMonthName, Some(d), _, _) => {
231
0
                w.write_str(long_months(self.locale)[d.month0() as usize])
232
            }
233
0
            (ShortWeekdayName, Some(d), _, _) => w.write_str(
234
0
                short_weekdays(self.locale)[d.weekday().num_days_from_sunday() as usize],
235
            ),
236
0
            (LongWeekdayName, Some(d), _, _) => {
237
0
                w.write_str(long_weekdays(self.locale)[d.weekday().num_days_from_sunday() as usize])
238
            }
239
0
            (LowerAmPm, _, Some(t), _) => {
240
0
                let ampm = if t.hour12().0 { am_pm(self.locale)[1] } else { am_pm(self.locale)[0] };
241
0
                for c in ampm.chars().flat_map(|c| c.to_lowercase()) {
242
0
                    w.write_char(c)?
243
                }
244
0
                Ok(())
245
            }
246
0
            (UpperAmPm, _, Some(t), _) => {
247
0
                let ampm = if t.hour12().0 { am_pm(self.locale)[1] } else { am_pm(self.locale)[0] };
248
0
                w.write_str(ampm)
249
            }
250
0
            (Nanosecond, _, Some(t), _) => {
251
0
                let nano = t.nanosecond() % 1_000_000_000;
252
0
                if nano == 0 {
253
0
                    Ok(())
254
                } else {
255
0
                    w.write_str(decimal_point(self.locale))?;
256
0
                    if nano % 1_000_000 == 0 {
257
0
                        write!(w, "{:03}", nano / 1_000_000)
258
0
                    } else if nano % 1_000 == 0 {
259
0
                        write!(w, "{:06}", nano / 1_000)
260
                    } else {
261
0
                        write!(w, "{nano:09}")
262
                    }
263
                }
264
            }
265
0
            (Nanosecond3, _, Some(t), _) => {
266
0
                w.write_str(decimal_point(self.locale))?;
267
0
                write!(w, "{:03}", t.nanosecond() / 1_000_000 % 1000)
268
            }
269
0
            (Nanosecond6, _, Some(t), _) => {
270
0
                w.write_str(decimal_point(self.locale))?;
271
0
                write!(w, "{:06}", t.nanosecond() / 1_000 % 1_000_000)
272
            }
273
0
            (Nanosecond9, _, Some(t), _) => {
274
0
                w.write_str(decimal_point(self.locale))?;
275
0
                write!(w, "{:09}", t.nanosecond() % 1_000_000_000)
276
            }
277
0
            (Internal(InternalFixed { val: Nanosecond3NoDot }), _, Some(t), _) => {
278
0
                write!(w, "{:03}", t.nanosecond() / 1_000_000 % 1_000)
279
            }
280
0
            (Internal(InternalFixed { val: Nanosecond6NoDot }), _, Some(t), _) => {
281
0
                write!(w, "{:06}", t.nanosecond() / 1_000 % 1_000_000)
282
            }
283
0
            (Internal(InternalFixed { val: Nanosecond9NoDot }), _, Some(t), _) => {
284
0
                write!(w, "{:09}", t.nanosecond() % 1_000_000_000)
285
            }
286
0
            (TimezoneName, _, _, Some((tz_name, _))) => write!(w, "{tz_name}"),
287
0
            (TimezoneOffset | TimezoneOffsetZ, _, _, Some((_, off))) => {
288
0
                let offset_format = OffsetFormat {
289
0
                    precision: OffsetPrecision::Minutes,
290
0
                    colons: Colons::Maybe,
291
0
                    allow_zulu: *spec == TimezoneOffsetZ,
292
0
                    padding: Pad::Zero,
293
0
                };
294
0
                offset_format.format(w, *off)
295
            }
296
0
            (TimezoneOffsetColon | TimezoneOffsetColonZ, _, _, Some((_, off))) => {
297
0
                let offset_format = OffsetFormat {
298
0
                    precision: OffsetPrecision::Minutes,
299
0
                    colons: Colons::Colon,
300
0
                    allow_zulu: *spec == TimezoneOffsetColonZ,
301
0
                    padding: Pad::Zero,
302
0
                };
303
0
                offset_format.format(w, *off)
304
            }
305
0
            (TimezoneOffsetDoubleColon, _, _, Some((_, off))) => {
306
0
                let offset_format = OffsetFormat {
307
0
                    precision: OffsetPrecision::Seconds,
308
0
                    colons: Colons::Colon,
309
0
                    allow_zulu: false,
310
0
                    padding: Pad::Zero,
311
0
                };
312
0
                offset_format.format(w, *off)
313
            }
314
0
            (TimezoneOffsetTripleColon, _, _, Some((_, off))) => {
315
0
                let offset_format = OffsetFormat {
316
0
                    precision: OffsetPrecision::Hours,
317
0
                    colons: Colons::None,
318
0
                    allow_zulu: false,
319
0
                    padding: Pad::Zero,
320
0
                };
321
0
                offset_format.format(w, *off)
322
            }
323
0
            (RFC2822, Some(d), Some(t), Some((_, off))) => {
324
0
                write_rfc2822(w, crate::NaiveDateTime::new(d, t), *off)
325
            }
326
0
            (RFC3339, Some(d), Some(t), Some((_, off))) => write_rfc3339(
327
0
                w,
328
0
                crate::NaiveDateTime::new(d, t),
329
0
                *off,
330
0
                SecondsFormat::AutoSi,
331
                false,
332
            ),
333
0
            _ => Err(fmt::Error), // insufficient arguments for given format
334
        }
335
0
    }
336
}
337
338
#[cfg(feature = "alloc")]
339
impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> Display for DelayedFormat<I> {
340
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
341
0
        let mut result = String::new();
342
0
        self.write_to(&mut result)?;
343
0
        f.pad(&result)
344
0
    }
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")]
351
pub 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
358
where
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")]
375
pub 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"))]
393
impl OffsetFormat {
394
    /// Writes an offset from UTC with the format defined by `self`.
395
0
    fn format(&self, w: &mut (impl Write + ?Sized), off: FixedOffset) -> fmt::Result {
396
0
        let off = off.local_minus_utc();
397
0
        if self.allow_zulu && off == 0 {
398
0
            w.write_char('Z')?;
399
0
            return Ok(());
400
0
        }
401
0
        let (sign, off) = if off < 0 { ('-', -off) } else { ('+', off) };
402
403
        let hours;
404
0
        let mut mins = 0;
405
0
        let mut secs = 0;
406
0
        let precision = match self.precision {
407
            OffsetPrecision::Hours => {
408
                // Minutes and seconds are simply truncated
409
0
                hours = (off / 3600) as u8;
410
0
                OffsetPrecision::Hours
411
            }
412
            OffsetPrecision::Minutes | OffsetPrecision::OptionalMinutes => {
413
                // Round seconds to the nearest minute.
414
0
                let minutes = (off + 30) / 60;
415
0
                mins = (minutes % 60) as u8;
416
0
                hours = (minutes / 60) as u8;
417
0
                if self.precision == OffsetPrecision::OptionalMinutes && mins == 0 {
418
0
                    OffsetPrecision::Hours
419
                } else {
420
0
                    OffsetPrecision::Minutes
421
                }
422
            }
423
            OffsetPrecision::Seconds
424
            | OffsetPrecision::OptionalSeconds
425
            | OffsetPrecision::OptionalMinutesAndSeconds => {
426
0
                let minutes = off / 60;
427
0
                secs = (off % 60) as u8;
428
0
                mins = (minutes % 60) as u8;
429
0
                hours = (minutes / 60) as u8;
430
0
                if self.precision != OffsetPrecision::Seconds && secs == 0 {
431
0
                    if self.precision == OffsetPrecision::OptionalMinutesAndSeconds && mins == 0 {
432
0
                        OffsetPrecision::Hours
433
                    } else {
434
0
                        OffsetPrecision::Minutes
435
                    }
436
                } else {
437
0
                    OffsetPrecision::Seconds
438
                }
439
            }
440
        };
441
0
        let colons = self.colons == Colons::Colon;
442
443
0
        if hours < 10 {
444
0
            if self.padding == Pad::Space {
445
0
                w.write_char(' ')?;
446
0
            }
447
0
            w.write_char(sign)?;
448
0
            if self.padding == Pad::Zero {
449
0
                w.write_char('0')?;
450
0
            }
451
0
            w.write_char((b'0' + hours) as char)?;
452
        } else {
453
0
            w.write_char(sign)?;
454
0
            write_hundreds(w, hours)?;
455
        }
456
0
        if let OffsetPrecision::Minutes | OffsetPrecision::Seconds = precision {
457
0
            if colons {
458
0
                w.write_char(':')?;
459
0
            }
460
0
            write_hundreds(w, mins)?;
461
0
        }
462
0
        if let OffsetPrecision::Seconds = precision {
463
0
            if colons {
464
0
                w.write_char(':')?;
465
0
            }
466
0
            write_hundreds(w, secs)?;
467
0
        }
468
0
        Ok(())
469
0
    }
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)]
479
pub 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"))]
504
0
pub(crate) fn write_rfc3339(
505
0
    w: &mut (impl Write + ?Sized),
506
0
    dt: NaiveDateTime,
507
0
    off: FixedOffset,
508
0
    secform: SecondsFormat,
509
0
    use_z: bool,
510
0
) -> fmt::Result {
511
0
    let year = dt.date().year();
512
0
    if (0..=9999).contains(&year) {
513
0
        write_hundreds(w, (year / 100) as u8)?;
514
0
        write_hundreds(w, (year % 100) as u8)?;
515
    } else {
516
        // ISO 8601 requires the explicit sign for out-of-range years
517
0
        write!(w, "{year:+05}")?;
518
    }
519
0
    w.write_char('-')?;
520
0
    write_hundreds(w, dt.date().month() as u8)?;
521
0
    w.write_char('-')?;
522
0
    write_hundreds(w, dt.date().day() as u8)?;
523
524
0
    w.write_char('T')?;
525
526
0
    let (hour, min, mut sec) = dt.time().hms();
527
0
    let mut nano = dt.nanosecond();
528
0
    if nano >= 1_000_000_000 {
529
0
        sec += 1;
530
0
        nano -= 1_000_000_000;
531
0
    }
532
0
    write_hundreds(w, hour as u8)?;
533
0
    w.write_char(':')?;
534
0
    write_hundreds(w, min as u8)?;
535
0
    w.write_char(':')?;
536
0
    let sec = sec;
537
0
    write_hundreds(w, sec as u8)?;
538
539
0
    match secform {
540
0
        SecondsFormat::Secs => {}
541
0
        SecondsFormat::Millis => write!(w, ".{:03}", nano / 1_000_000)?,
542
0
        SecondsFormat::Micros => write!(w, ".{:06}", nano / 1000)?,
543
0
        SecondsFormat::Nanos => write!(w, ".{nano:09}")?,
544
        SecondsFormat::AutoSi => {
545
0
            if nano == 0 {
546
0
            } else if nano % 1_000_000 == 0 {
547
0
                write!(w, ".{:03}", nano / 1_000_000)?
548
0
            } else if nano % 1_000 == 0 {
549
0
                write!(w, ".{:06}", nano / 1_000)?
550
            } else {
551
0
                write!(w, ".{nano:09}")?
552
            }
553
        }
554
0
        SecondsFormat::__NonExhaustive => unreachable!(),
555
    };
556
557
0
    OffsetFormat {
558
0
        precision: OffsetPrecision::Minutes,
559
0
        colons: Colons::Colon,
560
0
        allow_zulu: use_z,
561
0
        padding: Pad::Zero,
562
0
    }
563
0
    .format(w, off)
564
0
}
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`
568
0
pub(crate) fn write_rfc2822(
569
0
    w: &mut (impl Write + ?Sized),
570
0
    dt: NaiveDateTime,
571
0
    off: FixedOffset,
572
0
) -> fmt::Result {
573
0
    let year = dt.year();
574
    // RFC2822 is only defined on years 0 through 9999
575
0
    if !(0..=9999).contains(&year) {
576
0
        return Err(fmt::Error);
577
0
    }
578
579
0
    let english = default_locale();
580
581
0
    w.write_str(short_weekdays(english)[dt.weekday().num_days_from_sunday() as usize])?;
582
0
    w.write_str(", ")?;
583
0
    let day = dt.day();
584
0
    if day < 10 {
585
0
        w.write_char((b'0' + day as u8) as char)?;
586
    } else {
587
0
        write_hundreds(w, day as u8)?;
588
    }
589
0
    w.write_char(' ')?;
590
0
    w.write_str(short_months(english)[dt.month0() as usize])?;
591
0
    w.write_char(' ')?;
592
0
    write_hundreds(w, (year / 100) as u8)?;
593
0
    write_hundreds(w, (year % 100) as u8)?;
594
0
    w.write_char(' ')?;
595
596
0
    let (hour, min, sec) = dt.time().hms();
597
0
    write_hundreds(w, hour as u8)?;
598
0
    w.write_char(':')?;
599
0
    write_hundreds(w, min as u8)?;
600
0
    w.write_char(':')?;
601
0
    let sec = sec + dt.nanosecond() / 1_000_000_000;
602
0
    write_hundreds(w, sec as u8)?;
603
0
    w.write_char(' ')?;
604
0
    OffsetFormat {
605
0
        precision: OffsetPrecision::Minutes,
606
0
        colons: Colons::None,
607
0
        allow_zulu: false,
608
0
        padding: Pad::Zero,
609
0
    }
610
0
    .format(w, off)
611
0
}
612
613
/// Equivalent to `{:02}` formatting for n < 100.
614
0
pub(crate) fn write_hundreds(w: &mut (impl Write + ?Sized), n: u8) -> fmt::Result {
615
0
    if n >= 100 {
616
0
        return Err(fmt::Error);
617
0
    }
618
619
0
    let tens = b'0' + n / 10;
620
0
    let ones = b'0' + n % 10;
621
0
    w.write_char(tens as char)?;
622
0
    w.write_char(ones as char)
623
0
}
Unexecuted instantiation: chrono::format::formatting::write_hundreds::<core::fmt::Formatter>
Unexecuted instantiation: chrono::format::formatting::write_hundreds::<alloc::string::String>
624
625
#[cfg(test)]
626
#[cfg(feature = "alloc")]
627
mod 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
}