jiff/fmt/strtime/
format.rs

1use crate::{
2    error::{err, ErrorContext},
3    fmt::{
4        strtime::{
5            month_name_abbrev, month_name_full, weekday_name_abbrev,
6            weekday_name_full, BrokenDownTime, Config, Custom, Extension,
7            Flag,
8        },
9        util::{DecimalFormatter, FractionalFormatter},
10        Write, WriteExt,
11    },
12    tz::Offset,
13    util::{escape, t::C, utf8},
14    Error,
15};
16
17pub(super) struct Formatter<'c, 'f, 't, 'w, W, L> {
18    pub(super) config: &'c Config<L>,
19    pub(super) fmt: &'f [u8],
20    pub(super) tm: &'t BrokenDownTime,
21    pub(super) wtr: &'w mut W,
22}
23
24impl<'c, 'f, 't, 'w, W: Write, L: Custom> Formatter<'c, 'f, 't, 'w, W, L> {
25    pub(super) fn format(&mut self) -> Result<(), Error> {
26        while !self.fmt.is_empty() {
27            if self.f() != b'%' {
28                if self.f().is_ascii() {
29                    self.wtr.write_char(char::from(self.f()))?;
30                    self.bump_fmt();
31                } else {
32                    let ch = self.utf8_decode_and_bump()?;
33                    self.wtr.write_char(ch)?;
34                }
35                continue;
36            }
37            if !self.bump_fmt() {
38                if self.config.lenient {
39                    self.wtr.write_str("%")?;
40                    break;
41                }
42                return Err(err!(
43                    "invalid format string, expected byte after '%', \
44                     but found end of format string",
45                ));
46            }
47            let orig = self.fmt;
48            if let Err(err) = self.format_one() {
49                if !self.config.lenient {
50                    return Err(err);
51                }
52                // `orig` is whatever failed to parse immediately after a `%`.
53                // Since it failed, we write out the `%` and then proceed to
54                // handle what failed to parse literally.
55                self.wtr.write_str("%")?;
56                // Reset back to right after parsing the `%`.
57                self.fmt = orig;
58            }
59        }
60        Ok(())
61    }
62
63    fn format_one(&mut self) -> Result<(), Error> {
64        // Parse extensions like padding/case options and padding width.
65        let ext = self.parse_extension()?;
66        match self.f() {
67            b'%' => self.wtr.write_str("%").context("%% failed")?,
68            b'A' => self.fmt_weekday_full(&ext).context("%A failed")?,
69            b'a' => self.fmt_weekday_abbrev(&ext).context("%a failed")?,
70            b'B' => self.fmt_month_full(&ext).context("%B failed")?,
71            b'b' => self.fmt_month_abbrev(&ext).context("%b failed")?,
72            b'C' => self.fmt_century(&ext).context("%C failed")?,
73            b'c' => self.fmt_datetime(&ext).context("%c failed")?,
74            b'D' => self.fmt_american_date(&ext).context("%D failed")?,
75            b'd' => self.fmt_day_zero(&ext).context("%d failed")?,
76            b'e' => self.fmt_day_space(&ext).context("%e failed")?,
77            b'F' => self.fmt_iso_date(&ext).context("%F failed")?,
78            b'f' => self.fmt_fractional(&ext).context("%f failed")?,
79            b'G' => self.fmt_iso_week_year(&ext).context("%G failed")?,
80            b'g' => self.fmt_iso_week_year2(&ext).context("%g failed")?,
81            b'H' => self.fmt_hour24_zero(&ext).context("%H failed")?,
82            b'h' => self.fmt_month_abbrev(&ext).context("%b failed")?,
83            b'I' => self.fmt_hour12_zero(&ext).context("%H failed")?,
84            b'j' => self.fmt_day_of_year(&ext).context("%j failed")?,
85            b'k' => self.fmt_hour24_space(&ext).context("%k failed")?,
86            b'l' => self.fmt_hour12_space(&ext).context("%l failed")?,
87            b'M' => self.fmt_minute(&ext).context("%M failed")?,
88            b'm' => self.fmt_month(&ext).context("%m failed")?,
89            b'N' => self.fmt_nanoseconds(&ext).context("%N failed")?,
90            b'n' => self.fmt_literal("\n").context("%n failed")?,
91            b'P' => self.fmt_ampm_lower(&ext).context("%P failed")?,
92            b'p' => self.fmt_ampm_upper(&ext).context("%p failed")?,
93            b'Q' => match ext.colons {
94                0 => self.fmt_iana_nocolon().context("%Q failed")?,
95                1 => self.fmt_iana_colon().context("%:Q failed")?,
96                _ => {
97                    return Err(err!(
98                        "invalid number of `:` in `%Q` directive"
99                    ))
100                }
101            },
102            b'q' => self.fmt_quarter(&ext).context("%q failed")?,
103            b'R' => self.fmt_clock_nosecs(&ext).context("%R failed")?,
104            b'r' => self.fmt_12hour_time(&ext).context("%r failed")?,
105            b'S' => self.fmt_second(&ext).context("%S failed")?,
106            b's' => self.fmt_timestamp(&ext).context("%s failed")?,
107            b'T' => self.fmt_clock_secs(&ext).context("%T failed")?,
108            b't' => self.fmt_literal("\t").context("%t failed")?,
109            b'U' => self.fmt_week_sun(&ext).context("%U failed")?,
110            b'u' => self.fmt_weekday_mon(&ext).context("%u failed")?,
111            b'V' => self.fmt_week_iso(&ext).context("%V failed")?,
112            b'W' => self.fmt_week_mon(&ext).context("%W failed")?,
113            b'w' => self.fmt_weekday_sun(&ext).context("%w failed")?,
114            b'X' => self.fmt_time(&ext).context("%X failed")?,
115            b'x' => self.fmt_date(&ext).context("%x failed")?,
116            b'Y' => self.fmt_year(&ext).context("%Y failed")?,
117            b'y' => self.fmt_year2(&ext).context("%y failed")?,
118            b'Z' => self.fmt_tzabbrev(&ext).context("%Z failed")?,
119            b'z' => match ext.colons {
120                0 => self.fmt_offset_nocolon().context("%z failed")?,
121                1 => self.fmt_offset_colon().context("%:z failed")?,
122                2 => self.fmt_offset_colon2().context("%::z failed")?,
123                3 => self.fmt_offset_colon3().context("%:::z failed")?,
124                _ => {
125                    return Err(err!(
126                        "invalid number of `:` in `%z` directive"
127                    ))
128                }
129            },
130            b'.' => {
131                if !self.bump_fmt() {
132                    return Err(err!(
133                        "invalid format string, expected directive \
134                             after '%.'",
135                    ));
136                }
137                // Parse precision settings after the `.`, effectively
138                // overriding any digits that came before it.
139                let ext = Extension { width: self.parse_width()?, ..ext };
140                match self.f() {
141                    b'f' => {
142                        self.fmt_dot_fractional(&ext).context("%.f failed")?
143                    }
144                    unk => {
145                        return Err(err!(
146                            "found unrecognized directive %{unk} \
147                                 following %.",
148                            unk = escape::Byte(unk),
149                        ));
150                    }
151                }
152            }
153            unk => {
154                return Err(err!(
155                    "found unrecognized specifier directive %{unk}",
156                    unk = escape::Byte(unk),
157                ));
158            }
159        }
160        self.bump_fmt();
161        Ok(())
162    }
163
164    /// Returns the byte at the current position of the format string.
165    ///
166    /// # Panics
167    ///
168    /// This panics when the entire format string has been consumed.
169    fn f(&self) -> u8 {
170        self.fmt[0]
171    }
172
173    /// Bumps the position of the format string.
174    ///
175    /// This returns true in precisely the cases where `self.f()` will not
176    /// panic. i.e., When the end of the format string hasn't been reached yet.
177    fn bump_fmt(&mut self) -> bool {
178        self.fmt = &self.fmt[1..];
179        !self.fmt.is_empty()
180    }
181
182    /// Decodes a Unicode scalar value from the beginning of `fmt` and advances
183    /// the parser accordingly.
184    ///
185    /// If a Unicode scalar value could not be decoded, then an error is
186    /// returned.
187    ///
188    /// It would be nice to just pass through bytes as-is instead of doing
189    /// actual UTF-8 decoding, but since the `Write` trait only represents
190    /// Unicode-accepting buffers, we need to actually do decoding here.
191    ///
192    /// # Panics
193    ///
194    /// When `self.fmt` is empty. i.e., Only call this when you know there is
195    /// some remaining bytes to parse.
196    #[cold]
197    #[inline(never)]
198    fn utf8_decode_and_bump(&mut self) -> Result<char, Error> {
199        match utf8::decode(self.fmt).expect("non-empty fmt") {
200            Ok(ch) => {
201                self.fmt = &self.fmt[ch.len_utf8()..];
202                return Ok(ch);
203            }
204            Err(invalid) => Err(err!(
205                "found invalid UTF-8 byte {byte:?} in format \
206                 string (format strings must be valid UTF-8)",
207                byte = escape::Byte(invalid),
208            )),
209        }
210    }
211
212    /// Parses optional extensions before a specifier directive. That is, right
213    /// after the `%`. If any extensions are parsed, the parser is bumped
214    /// to the next byte. (If no next byte exists, then an error is returned.)
215    #[cfg_attr(feature = "perf-inline", inline(always))]
216    fn parse_extension(&mut self) -> Result<Extension, Error> {
217        let flag = self.parse_flag()?;
218        let width = self.parse_width()?;
219        let colons = self.parse_colons();
220        Ok(Extension { flag, width, colons })
221    }
222
223    /// Parses an optional flag. And if one is parsed, the parser is bumped
224    /// to the next byte. (If no next byte exists, then an error is returned.)
225    #[cfg_attr(feature = "perf-inline", inline(always))]
226    fn parse_flag(&mut self) -> Result<Option<Flag>, Error> {
227        let (flag, fmt) = Extension::parse_flag(self.fmt)?;
228        self.fmt = fmt;
229        Ok(flag)
230    }
231
232    /// Parses an optional width that comes after a (possibly absent) flag and
233    /// before the specifier directive itself. And if a width is parsed, the
234    /// parser is bumped to the next byte. (If no next byte exists, then an
235    /// error is returned.)
236    ///
237    /// Note that this is also used to parse precision settings for `%f` and
238    /// `%.f`. In the former case, the width is just re-interpreted as a
239    /// precision setting. In the latter case, something like `%5.9f` is
240    /// technically valid, but the `5` is ignored.
241    #[cfg_attr(feature = "perf-inline", inline(always))]
242    fn parse_width(&mut self) -> Result<Option<u8>, Error> {
243        let (width, fmt) = Extension::parse_width(self.fmt)?;
244        self.fmt = fmt;
245        Ok(width)
246    }
247
248    /// Parses an optional number of colons (up to 3) immediately before a
249    /// conversion specifier.
250    #[cfg_attr(feature = "perf-inline", inline(always))]
251    fn parse_colons(&mut self) -> u8 {
252        let (colons, fmt) = Extension::parse_colons(self.fmt);
253        self.fmt = fmt;
254        colons
255    }
256
257    // These are the formatting functions. They are pretty much responsible
258    // for getting what they need for the broken down time and reporting a
259    // decent failure mode if what they need couldn't be found. And then,
260    // of course, doing the actual formatting.
261
262    /// %P
263    fn fmt_ampm_lower(&mut self, ext: &Extension) -> Result<(), Error> {
264        let hour = self
265            .tm
266            .hour
267            .ok_or_else(|| err!("requires time to format AM/PM"))?
268            .get();
269        ext.write_str(
270            Case::AsIs,
271            if hour < 12 { "am" } else { "pm" },
272            self.wtr,
273        )
274    }
275
276    /// %p
277    fn fmt_ampm_upper(&mut self, ext: &Extension) -> Result<(), Error> {
278        let hour = self
279            .tm
280            .hour
281            .ok_or_else(|| err!("requires time to format AM/PM"))?
282            .get();
283        // Manually specialize this case to avoid hitting `write_str_cold`.
284        let s = if matches!(ext.flag, Some(Flag::Swapcase)) {
285            if hour < 12 {
286                "am"
287            } else {
288                "pm"
289            }
290        } else {
291            if hour < 12 {
292                "AM"
293            } else {
294                "PM"
295            }
296        };
297        self.wtr.write_str(s)
298    }
299
300    /// %D
301    fn fmt_american_date(&mut self, ext: &Extension) -> Result<(), Error> {
302        self.fmt_month(ext)?;
303        self.wtr.write_char('/')?;
304        self.fmt_day_zero(ext)?;
305        self.wtr.write_char('/')?;
306        self.fmt_year2(ext)?;
307        Ok(())
308    }
309
310    /// %R
311    fn fmt_clock_nosecs(&mut self, ext: &Extension) -> Result<(), Error> {
312        self.fmt_hour24_zero(ext)?;
313        self.wtr.write_char(':')?;
314        self.fmt_minute(ext)?;
315        Ok(())
316    }
317
318    /// %T
319    fn fmt_clock_secs(&mut self, ext: &Extension) -> Result<(), Error> {
320        self.fmt_hour24_zero(ext)?;
321        self.wtr.write_char(':')?;
322        self.fmt_minute(ext)?;
323        self.wtr.write_char(':')?;
324        self.fmt_second(ext)?;
325        Ok(())
326    }
327
328    /// %d
329    fn fmt_day_zero(&mut self, ext: &Extension) -> Result<(), Error> {
330        let day = self
331            .tm
332            .day
333            .or_else(|| self.tm.to_date().ok().map(|d| d.day_ranged()))
334            .ok_or_else(|| err!("requires date to format day"))?
335            .get();
336        ext.write_int(b'0', Some(2), day, self.wtr)
337    }
338
339    /// %e
340    fn fmt_day_space(&mut self, ext: &Extension) -> Result<(), Error> {
341        let day = self
342            .tm
343            .day
344            .or_else(|| self.tm.to_date().ok().map(|d| d.day_ranged()))
345            .ok_or_else(|| err!("requires date to format day"))?
346            .get();
347        ext.write_int(b' ', Some(2), day, self.wtr)
348    }
349
350    /// %I
351    fn fmt_hour12_zero(&mut self, ext: &Extension) -> Result<(), Error> {
352        let mut hour = self
353            .tm
354            .hour
355            .ok_or_else(|| err!("requires time to format hour"))?
356            .get();
357        if hour == 0 {
358            hour = 12;
359        } else if hour > 12 {
360            hour -= 12;
361        }
362        ext.write_int(b'0', Some(2), hour, self.wtr)
363    }
364
365    /// %H
366    fn fmt_hour24_zero(&mut self, ext: &Extension) -> Result<(), Error> {
367        let hour = self
368            .tm
369            .hour
370            .ok_or_else(|| err!("requires time to format hour"))?
371            .get();
372        ext.write_int(b'0', Some(2), hour, self.wtr)
373    }
374
375    /// %l
376    fn fmt_hour12_space(&mut self, ext: &Extension) -> Result<(), Error> {
377        let mut hour = self
378            .tm
379            .hour
380            .ok_or_else(|| err!("requires time to format hour"))?
381            .get();
382        if hour == 0 {
383            hour = 12;
384        } else if hour > 12 {
385            hour -= 12;
386        }
387        ext.write_int(b' ', Some(2), hour, self.wtr)
388    }
389
390    /// %k
391    fn fmt_hour24_space(&mut self, ext: &Extension) -> Result<(), Error> {
392        let hour = self
393            .tm
394            .hour
395            .ok_or_else(|| err!("requires time to format hour"))?
396            .get();
397        ext.write_int(b' ', Some(2), hour, self.wtr)
398    }
399
400    /// %F
401    fn fmt_iso_date(&mut self, ext: &Extension) -> Result<(), Error> {
402        self.fmt_year(ext)?;
403        self.wtr.write_char('-')?;
404        self.fmt_month(ext)?;
405        self.wtr.write_char('-')?;
406        self.fmt_day_zero(ext)?;
407        Ok(())
408    }
409
410    /// %M
411    fn fmt_minute(&mut self, ext: &Extension) -> Result<(), Error> {
412        let minute = self
413            .tm
414            .minute
415            .ok_or_else(|| err!("requires time to format minute"))?
416            .get();
417        ext.write_int(b'0', Some(2), minute, self.wtr)
418    }
419
420    /// %m
421    fn fmt_month(&mut self, ext: &Extension) -> Result<(), Error> {
422        let month = self
423            .tm
424            .month
425            .or_else(|| self.tm.to_date().ok().map(|d| d.month_ranged()))
426            .ok_or_else(|| err!("requires date to format month"))?
427            .get();
428        ext.write_int(b'0', Some(2), month, self.wtr)
429    }
430
431    /// %B
432    fn fmt_month_full(&mut self, ext: &Extension) -> Result<(), Error> {
433        let month = self
434            .tm
435            .month
436            .or_else(|| self.tm.to_date().ok().map(|d| d.month_ranged()))
437            .ok_or_else(|| err!("requires date to format month"))?;
438        ext.write_str(Case::AsIs, month_name_full(month), self.wtr)
439    }
440
441    /// %b, %h
442    fn fmt_month_abbrev(&mut self, ext: &Extension) -> Result<(), Error> {
443        let month = self
444            .tm
445            .month
446            .or_else(|| self.tm.to_date().ok().map(|d| d.month_ranged()))
447            .ok_or_else(|| err!("requires date to format month"))?;
448        ext.write_str(Case::AsIs, month_name_abbrev(month), self.wtr)
449    }
450
451    /// %Q
452    fn fmt_iana_nocolon(&mut self) -> Result<(), Error> {
453        let Some(iana) = self.tm.iana_time_zone() else {
454            let offset = self.tm.offset.ok_or_else(|| {
455                err!(
456                    "requires IANA time zone identifier or time \
457                     zone offset, but none were present"
458                )
459            })?;
460            return write_offset(offset, false, true, false, &mut self.wtr);
461        };
462        self.wtr.write_str(iana)?;
463        Ok(())
464    }
465
466    /// %:Q
467    fn fmt_iana_colon(&mut self) -> Result<(), Error> {
468        let Some(iana) = self.tm.iana_time_zone() else {
469            let offset = self.tm.offset.ok_or_else(|| {
470                err!(
471                    "requires IANA time zone identifier or time \
472                     zone offset, but none were present"
473                )
474            })?;
475            return write_offset(offset, true, true, false, &mut self.wtr);
476        };
477        self.wtr.write_str(iana)?;
478        Ok(())
479    }
480
481    /// %z
482    fn fmt_offset_nocolon(&mut self) -> Result<(), Error> {
483        let offset = self.tm.offset.ok_or_else(|| {
484            err!("requires offset to format time zone offset")
485        })?;
486        write_offset(offset, false, true, false, self.wtr)
487    }
488
489    /// %:z
490    fn fmt_offset_colon(&mut self) -> Result<(), Error> {
491        let offset = self.tm.offset.ok_or_else(|| {
492            err!("requires offset to format time zone offset")
493        })?;
494        write_offset(offset, true, true, false, self.wtr)
495    }
496
497    /// %::z
498    fn fmt_offset_colon2(&mut self) -> Result<(), Error> {
499        let offset = self.tm.offset.ok_or_else(|| {
500            err!("requires offset to format time zone offset")
501        })?;
502        write_offset(offset, true, true, true, self.wtr)
503    }
504
505    /// %:::z
506    fn fmt_offset_colon3(&mut self) -> Result<(), Error> {
507        let offset = self.tm.offset.ok_or_else(|| {
508            err!("requires offset to format time zone offset")
509        })?;
510        write_offset(offset, true, false, false, self.wtr)
511    }
512
513    /// %S
514    fn fmt_second(&mut self, ext: &Extension) -> Result<(), Error> {
515        let second = self
516            .tm
517            .second
518            .ok_or_else(|| err!("requires time to format second"))?
519            .get();
520        ext.write_int(b'0', Some(2), second, self.wtr)
521    }
522
523    /// %s
524    fn fmt_timestamp(&mut self, ext: &Extension) -> Result<(), Error> {
525        let timestamp = self.tm.to_timestamp().map_err(|_| {
526            err!(
527                "requires instant (a date, time and offset) \
528                 to format Unix timestamp",
529            )
530        })?;
531        ext.write_int(b' ', None, timestamp.as_second(), self.wtr)
532    }
533
534    /// %f
535    fn fmt_fractional(&mut self, ext: &Extension) -> Result<(), Error> {
536        let subsec = self.tm.subsec.ok_or_else(|| {
537            err!("requires time to format subsecond nanoseconds")
538        })?;
539        // For %f, we always want to emit at least one digit. The only way we
540        // wouldn't is if our fractional component is zero. One exception to
541        // this is when the width is `0` (which looks like `%00f`), in which
542        // case, we emit an error. We could allow it to emit an empty string,
543        // but this seems very odd. And an empty string cannot be parsed by
544        // `%f`.
545        if ext.width == Some(0) {
546            return Err(err!("zero precision with %f is not allowed"));
547        }
548        if subsec == C(0) && ext.width.is_none() {
549            self.wtr.write_str("0")?;
550            return Ok(());
551        }
552        ext.write_fractional_seconds(subsec, self.wtr)?;
553        Ok(())
554    }
555
556    /// %.f
557    fn fmt_dot_fractional(&mut self, ext: &Extension) -> Result<(), Error> {
558        let Some(subsec) = self.tm.subsec else { return Ok(()) };
559        if subsec == C(0) && ext.width.is_none() || ext.width == Some(0) {
560            return Ok(());
561        }
562        ext.write_str(Case::AsIs, ".", self.wtr)?;
563        ext.write_fractional_seconds(subsec, self.wtr)?;
564        Ok(())
565    }
566
567    /// %N
568    fn fmt_nanoseconds(&mut self, ext: &Extension) -> Result<(), Error> {
569        let subsec = self.tm.subsec.ok_or_else(|| {
570            err!("requires time to format subsecond nanoseconds")
571        })?;
572        if ext.width == Some(0) {
573            return Err(err!("zero precision with %N is not allowed"));
574        }
575        // Since `%N` is actually an alias for `%9f`, when the precision
576        // is missing, we default to 9.
577        if ext.width.is_none() {
578            let formatter = FractionalFormatter::new().precision(Some(9));
579            return self.wtr.write_fraction(&formatter, subsec);
580        }
581        ext.write_fractional_seconds(subsec, self.wtr)?;
582        Ok(())
583    }
584
585    /// %Z
586    fn fmt_tzabbrev(&mut self, ext: &Extension) -> Result<(), Error> {
587        let tz =
588            self.tm.tz.as_ref().ok_or_else(|| {
589                err!("requires time zone in broken down time")
590            })?;
591        let ts = self
592            .tm
593            .timestamp
594            .ok_or_else(|| err!("requires timestamp in broken down time"))?;
595        let oinfo = tz.to_offset_info(ts);
596        ext.write_str(Case::Upper, oinfo.abbreviation(), self.wtr)
597    }
598
599    /// %A
600    fn fmt_weekday_full(&mut self, ext: &Extension) -> Result<(), Error> {
601        let weekday = self
602            .tm
603            .weekday
604            .or_else(|| self.tm.to_date().ok().map(|d| d.weekday()))
605            .ok_or_else(|| err!("requires date to format weekday"))?;
606        ext.write_str(Case::AsIs, weekday_name_full(weekday), self.wtr)
607    }
608
609    /// %a
610    fn fmt_weekday_abbrev(&mut self, ext: &Extension) -> Result<(), Error> {
611        let weekday = self
612            .tm
613            .weekday
614            .or_else(|| self.tm.to_date().ok().map(|d| d.weekday()))
615            .ok_or_else(|| err!("requires date to format weekday"))?;
616        ext.write_str(Case::AsIs, weekday_name_abbrev(weekday), self.wtr)
617    }
618
619    /// %u
620    fn fmt_weekday_mon(&mut self, ext: &Extension) -> Result<(), Error> {
621        let weekday = self
622            .tm
623            .weekday
624            .or_else(|| self.tm.to_date().ok().map(|d| d.weekday()))
625            .ok_or_else(|| err!("requires date to format weekday number"))?;
626        ext.write_int(b' ', None, weekday.to_monday_one_offset(), self.wtr)
627    }
628
629    /// %w
630    fn fmt_weekday_sun(&mut self, ext: &Extension) -> Result<(), Error> {
631        let weekday = self
632            .tm
633            .weekday
634            .or_else(|| self.tm.to_date().ok().map(|d| d.weekday()))
635            .ok_or_else(|| err!("requires date to format weekday number"))?;
636        ext.write_int(b' ', None, weekday.to_sunday_zero_offset(), self.wtr)
637    }
638
639    /// %U
640    fn fmt_week_sun(&mut self, ext: &Extension) -> Result<(), Error> {
641        // Short circuit if the week number was explicitly set.
642        if let Some(weeknum) = self.tm.week_sun {
643            return ext.write_int(b'0', Some(2), weeknum, self.wtr);
644        }
645        let day = self
646            .tm
647            .day_of_year
648            .map(|day| day.get())
649            .or_else(|| self.tm.to_date().ok().map(|d| d.day_of_year()))
650            .ok_or_else(|| {
651                err!("requires date to format Sunday-based week number")
652            })?;
653        let weekday = self
654            .tm
655            .weekday
656            .or_else(|| self.tm.to_date().ok().map(|d| d.weekday()))
657            .ok_or_else(|| {
658                err!("requires date to format Sunday-based week number")
659            })?
660            .to_sunday_zero_offset();
661        // Example: 2025-01-05 is the first Sunday in 2025, and thus the start
662        // of week 1. This means that 2025-01-04 (Saturday) is in week 0.
663        //
664        // So for 2025-01-05, day=5 and weekday=0. Thus we get 11/7 = 1.
665        // For 2025-01-04, day=4 and weekday=6. Thus we get 4/7 = 0.
666        let weeknum = (day + 6 - i16::from(weekday)) / 7;
667        ext.write_int(b'0', Some(2), weeknum, self.wtr)
668    }
669
670    /// %V
671    fn fmt_week_iso(&mut self, ext: &Extension) -> Result<(), Error> {
672        let weeknum = self
673            .tm
674            .iso_week
675            .or_else(|| {
676                self.tm.to_date().ok().map(|d| d.iso_week_date().week_ranged())
677            })
678            .ok_or_else(|| {
679                err!("requires date to format ISO 8601 week number")
680            })?;
681        ext.write_int(b'0', Some(2), weeknum, self.wtr)
682    }
683
684    /// %W
685    fn fmt_week_mon(&mut self, ext: &Extension) -> Result<(), Error> {
686        // Short circuit if the week number was explicitly set.
687        if let Some(weeknum) = self.tm.week_mon {
688            return ext.write_int(b'0', Some(2), weeknum, self.wtr);
689        }
690        let day = self
691            .tm
692            .day_of_year
693            .map(|day| day.get())
694            .or_else(|| self.tm.to_date().ok().map(|d| d.day_of_year()))
695            .ok_or_else(|| {
696                err!("requires date to format Monday-based week number")
697            })?;
698        let weekday = self
699            .tm
700            .weekday
701            .or_else(|| self.tm.to_date().ok().map(|d| d.weekday()))
702            .ok_or_else(|| {
703                err!("requires date to format Monday-based week number")
704            })?
705            .to_sunday_zero_offset();
706        // Example: 2025-01-06 is the first Monday in 2025, and thus the start
707        // of week 1. This means that 2025-01-05 (Sunday) is in week 0.
708        //
709        // So for 2025-01-06, day=6 and weekday=1. Thus we get 12/7 = 1.
710        // For 2025-01-05, day=5 and weekday=7. Thus we get 5/7 = 0.
711        let weeknum = (day + 6 - ((i16::from(weekday) + 6) % 7)) / 7;
712        ext.write_int(b'0', Some(2), weeknum, self.wtr)
713    }
714
715    /// %Y
716    fn fmt_year(&mut self, ext: &Extension) -> Result<(), Error> {
717        let year = self
718            .tm
719            .year
720            .or_else(|| self.tm.to_date().ok().map(|d| d.year_ranged()))
721            .ok_or_else(|| err!("requires date to format year"))?
722            .get();
723        ext.write_int(b'0', Some(4), year, self.wtr)
724    }
725
726    /// %y
727    fn fmt_year2(&mut self, ext: &Extension) -> Result<(), Error> {
728        let year = self
729            .tm
730            .year
731            .or_else(|| self.tm.to_date().ok().map(|d| d.year_ranged()))
732            .ok_or_else(|| err!("requires date to format year (2-digit)"))?
733            .get();
734        if !(1969 <= year && year <= 2068) {
735            return Err(err!(
736                "formatting a 2-digit year requires that it be in \
737                 the inclusive range 1969 to 2068, but got {year}",
738            ));
739        }
740        let year = year % 100;
741        ext.write_int(b'0', Some(2), year, self.wtr)
742    }
743
744    /// %C
745    fn fmt_century(&mut self, ext: &Extension) -> Result<(), Error> {
746        let year = self
747            .tm
748            .year
749            .or_else(|| self.tm.to_date().ok().map(|d| d.year_ranged()))
750            .ok_or_else(|| err!("requires date to format century (2-digit)"))?
751            .get();
752        let century = year / 100;
753        ext.write_int(b' ', None, century, self.wtr)
754    }
755
756    /// %G
757    fn fmt_iso_week_year(&mut self, ext: &Extension) -> Result<(), Error> {
758        let year = self
759            .tm
760            .iso_week_year
761            .or_else(|| {
762                self.tm.to_date().ok().map(|d| d.iso_week_date().year_ranged())
763            })
764            .ok_or_else(|| {
765                err!("requires date to format ISO 8601 week-based year")
766            })?
767            .get();
768        ext.write_int(b'0', Some(4), year, self.wtr)
769    }
770
771    /// %g
772    fn fmt_iso_week_year2(&mut self, ext: &Extension) -> Result<(), Error> {
773        let year = self
774            .tm
775            .iso_week_year
776            .or_else(|| {
777                self.tm.to_date().ok().map(|d| d.iso_week_date().year_ranged())
778            })
779            .ok_or_else(|| {
780                err!(
781                    "requires date to format \
782                     ISO 8601 week-based year (2-digit)"
783                )
784            })?
785            .get();
786        if !(1969 <= year && year <= 2068) {
787            return Err(err!(
788                "formatting a 2-digit ISO 8601 week-based year \
789                 requires that it be in \
790                 the inclusive range 1969 to 2068, but got {year}",
791            ));
792        }
793        let year = year % 100;
794        ext.write_int(b'0', Some(2), year, self.wtr)
795    }
796
797    /// %q
798    fn fmt_quarter(&mut self, ext: &Extension) -> Result<(), Error> {
799        let month = self
800            .tm
801            .month
802            .or_else(|| self.tm.to_date().ok().map(|d| d.month_ranged()))
803            .ok_or_else(|| err!("requires date to format quarter"))?
804            .get();
805        let quarter = match month {
806            1..=3 => 1,
807            4..=6 => 2,
808            7..=9 => 3,
809            10..=12 => 4,
810            _ => unreachable!(),
811        };
812        ext.write_int(b'0', None, quarter, self.wtr)
813    }
814
815    /// %j
816    fn fmt_day_of_year(&mut self, ext: &Extension) -> Result<(), Error> {
817        let day = self
818            .tm
819            .day_of_year
820            .map(|day| day.get())
821            .or_else(|| self.tm.to_date().ok().map(|d| d.day_of_year()))
822            .ok_or_else(|| err!("requires date to format day of year"))?;
823        ext.write_int(b'0', Some(3), day, self.wtr)
824    }
825
826    /// %n, %t
827    fn fmt_literal(&mut self, literal: &str) -> Result<(), Error> {
828        self.wtr.write_str(literal)
829    }
830
831    /// %c
832    fn fmt_datetime(&mut self, ext: &Extension) -> Result<(), Error> {
833        self.config.custom.format_datetime(self.config, ext, self.tm, self.wtr)
834    }
835
836    /// %x
837    fn fmt_date(&mut self, ext: &Extension) -> Result<(), Error> {
838        self.config.custom.format_date(self.config, ext, self.tm, self.wtr)
839    }
840
841    /// %X
842    fn fmt_time(&mut self, ext: &Extension) -> Result<(), Error> {
843        self.config.custom.format_time(self.config, ext, self.tm, self.wtr)
844    }
845
846    /// %r
847    fn fmt_12hour_time(&mut self, ext: &Extension) -> Result<(), Error> {
848        self.config.custom.format_12hour_time(
849            self.config,
850            ext,
851            self.tm,
852            self.wtr,
853        )
854    }
855}
856
857/// Writes the given time zone offset to the writer.
858///
859/// When `colon` is true, the hour, minute and optional second components are
860/// delimited by a colon. Otherwise, no delimiter is used.
861///
862/// When `minute` is true, the minute component is always printed. When
863/// false, the minute component is only printed when it is non-zero (or if
864/// the second component is non-zero).
865///
866/// When `second` is true, the second component is always printed. When false,
867/// the second component is only printed when it is non-zero.
868fn write_offset<W: Write>(
869    offset: Offset,
870    colon: bool,
871    minute: bool,
872    second: bool,
873    wtr: &mut W,
874) -> Result<(), Error> {
875    static FMT_TWO: DecimalFormatter = DecimalFormatter::new().padding(2);
876
877    let hours = offset.part_hours_ranged().abs().get();
878    let minutes = offset.part_minutes_ranged().abs().get();
879    let seconds = offset.part_seconds_ranged().abs().get();
880
881    wtr.write_str(if offset.is_negative() { "-" } else { "+" })?;
882    wtr.write_int(&FMT_TWO, hours)?;
883    if minute || minutes != 0 || seconds != 0 {
884        if colon {
885            wtr.write_str(":")?;
886        }
887        wtr.write_int(&FMT_TWO, minutes)?;
888        if second || seconds != 0 {
889            if colon {
890                wtr.write_str(":")?;
891            }
892            wtr.write_int(&FMT_TWO, seconds)?;
893        }
894    }
895    Ok(())
896}
897
898impl Extension {
899    /// Writes the given string using the default case rule provided, unless
900    /// an option in this extension config overrides the default case.
901    #[cfg_attr(feature = "perf-inline", inline(always))]
902    fn write_str<W: Write>(
903        &self,
904        default: Case,
905        string: &str,
906        wtr: &mut W,
907    ) -> Result<(), Error> {
908        if self.flag.is_none() && matches!(default, Case::AsIs) {
909            return wtr.write_str(string);
910        }
911        self.write_str_cold(default, string, wtr)
912    }
913
914    #[cold]
915    #[inline(never)]
916    fn write_str_cold<W: Write>(
917        &self,
918        default: Case,
919        string: &str,
920        wtr: &mut W,
921    ) -> Result<(), Error> {
922        let case = match self.flag {
923            Some(Flag::Uppercase) => Case::Upper,
924            Some(Flag::Swapcase) => default.swap(),
925            _ => default,
926        };
927        match case {
928            Case::AsIs => {
929                wtr.write_str(string)?;
930            }
931            Case::Upper => {
932                for ch in string.chars() {
933                    for ch in ch.to_uppercase() {
934                        wtr.write_char(ch)?;
935                    }
936                }
937            }
938            Case::Lower => {
939                for ch in string.chars() {
940                    for ch in ch.to_lowercase() {
941                        wtr.write_char(ch)?;
942                    }
943                }
944            }
945        }
946        Ok(())
947    }
948
949    /// Writes the given integer using the given padding width and byte, unless
950    /// an option in this extension config overrides a default setting.
951    #[cfg_attr(feature = "perf-inline", inline(always))]
952    fn write_int<W: Write>(
953        &self,
954        pad_byte: u8,
955        pad_width: Option<u8>,
956        number: impl Into<i64>,
957        wtr: &mut W,
958    ) -> Result<(), Error> {
959        let number = number.into();
960        let pad_byte = match self.flag {
961            Some(Flag::PadZero) => b'0',
962            Some(Flag::PadSpace) => b' ',
963            _ => pad_byte,
964        };
965        let pad_width = if matches!(self.flag, Some(Flag::NoPad)) {
966            None
967        } else {
968            self.width.or(pad_width)
969        };
970
971        let mut formatter = DecimalFormatter::new().padding_byte(pad_byte);
972        if let Some(width) = pad_width {
973            formatter = formatter.padding(width);
974        }
975        wtr.write_int(&formatter, number)
976    }
977
978    /// Writes the given number of nanoseconds as a fractional component of
979    /// a second. This does not include the leading `.`.
980    ///
981    /// The `width` setting on `Extension` is treated as a precision setting.
982    fn write_fractional_seconds<W: Write>(
983        &self,
984        number: impl Into<i64>,
985        wtr: &mut W,
986    ) -> Result<(), Error> {
987        let number = number.into();
988
989        let formatter = FractionalFormatter::new().precision(self.width);
990        wtr.write_fraction(&formatter, number)
991    }
992}
993
994/// The case to use when printing a string like weekday or TZ abbreviation.
995#[derive(Clone, Copy, Debug)]
996enum Case {
997    AsIs,
998    Upper,
999    Lower,
1000}
1001
1002impl Case {
1003    /// Swap upper to lowercase, and lower to uppercase.
1004    fn swap(self) -> Case {
1005        match self {
1006            Case::AsIs => Case::AsIs,
1007            Case::Upper => Case::Lower,
1008            Case::Lower => Case::Upper,
1009        }
1010    }
1011}
1012
1013#[cfg(feature = "alloc")]
1014#[cfg(test)]
1015mod tests {
1016    use crate::{
1017        civil::{date, time, Date, DateTime, Time},
1018        fmt::strtime::{format, BrokenDownTime, Config, PosixCustom},
1019        tz::Offset,
1020        Timestamp, Zoned,
1021    };
1022
1023    #[test]
1024    fn ok_format_american_date() {
1025        let f = |fmt: &str, date: Date| format(fmt, date).unwrap();
1026
1027        insta::assert_snapshot!(f("%D", date(2024, 7, 9)), @"07/09/24");
1028        insta::assert_snapshot!(f("%-D", date(2024, 7, 9)), @"7/9/24");
1029        insta::assert_snapshot!(f("%3D", date(2024, 7, 9)), @"007/009/024");
1030        insta::assert_snapshot!(f("%03D", date(2024, 7, 9)), @"007/009/024");
1031    }
1032
1033    #[test]
1034    fn ok_format_ampm() {
1035        let f = |fmt: &str, time: Time| format(fmt, time).unwrap();
1036
1037        insta::assert_snapshot!(f("%H%P", time(9, 0, 0, 0)), @"09am");
1038        insta::assert_snapshot!(f("%H%P", time(11, 0, 0, 0)), @"11am");
1039        insta::assert_snapshot!(f("%H%P", time(23, 0, 0, 0)), @"23pm");
1040        insta::assert_snapshot!(f("%H%P", time(0, 0, 0, 0)), @"00am");
1041
1042        insta::assert_snapshot!(f("%H%p", time(9, 0, 0, 0)), @"09AM");
1043        insta::assert_snapshot!(f("%H%p", time(11, 0, 0, 0)), @"11AM");
1044        insta::assert_snapshot!(f("%H%p", time(23, 0, 0, 0)), @"23PM");
1045        insta::assert_snapshot!(f("%H%p", time(0, 0, 0, 0)), @"00AM");
1046
1047        insta::assert_snapshot!(f("%H%#p", time(9, 0, 0, 0)), @"09am");
1048    }
1049
1050    #[test]
1051    fn ok_format_clock() {
1052        let f = |fmt: &str, time: Time| format(fmt, time).unwrap();
1053
1054        insta::assert_snapshot!(f("%R", time(23, 59, 8, 0)), @"23:59");
1055        insta::assert_snapshot!(f("%T", time(23, 59, 8, 0)), @"23:59:08");
1056    }
1057
1058    #[test]
1059    fn ok_format_day() {
1060        let f = |fmt: &str, date: Date| format(fmt, date).unwrap();
1061
1062        insta::assert_snapshot!(f("%d", date(2024, 7, 9)), @"09");
1063        insta::assert_snapshot!(f("%0d", date(2024, 7, 9)), @"09");
1064        insta::assert_snapshot!(f("%-d", date(2024, 7, 9)), @"9");
1065        insta::assert_snapshot!(f("%_d", date(2024, 7, 9)), @" 9");
1066
1067        insta::assert_snapshot!(f("%e", date(2024, 7, 9)), @" 9");
1068        insta::assert_snapshot!(f("%0e", date(2024, 7, 9)), @"09");
1069        insta::assert_snapshot!(f("%-e", date(2024, 7, 9)), @"9");
1070        insta::assert_snapshot!(f("%_e", date(2024, 7, 9)), @" 9");
1071    }
1072
1073    #[test]
1074    fn ok_format_iso_date() {
1075        let f = |fmt: &str, date: Date| format(fmt, date).unwrap();
1076
1077        insta::assert_snapshot!(f("%F", date(2024, 7, 9)), @"2024-07-09");
1078        insta::assert_snapshot!(f("%-F", date(2024, 7, 9)), @"2024-7-9");
1079        insta::assert_snapshot!(f("%3F", date(2024, 7, 9)), @"2024-007-009");
1080        insta::assert_snapshot!(f("%03F", date(2024, 7, 9)), @"2024-007-009");
1081    }
1082
1083    #[test]
1084    fn ok_format_hour() {
1085        let f = |fmt: &str, time: Time| format(fmt, time).unwrap();
1086
1087        insta::assert_snapshot!(f("%H", time(9, 0, 0, 0)), @"09");
1088        insta::assert_snapshot!(f("%H", time(11, 0, 0, 0)), @"11");
1089        insta::assert_snapshot!(f("%H", time(23, 0, 0, 0)), @"23");
1090        insta::assert_snapshot!(f("%H", time(0, 0, 0, 0)), @"00");
1091
1092        insta::assert_snapshot!(f("%I", time(9, 0, 0, 0)), @"09");
1093        insta::assert_snapshot!(f("%I", time(11, 0, 0, 0)), @"11");
1094        insta::assert_snapshot!(f("%I", time(23, 0, 0, 0)), @"11");
1095        insta::assert_snapshot!(f("%I", time(0, 0, 0, 0)), @"12");
1096
1097        insta::assert_snapshot!(f("%k", time(9, 0, 0, 0)), @" 9");
1098        insta::assert_snapshot!(f("%k", time(11, 0, 0, 0)), @"11");
1099        insta::assert_snapshot!(f("%k", time(23, 0, 0, 0)), @"23");
1100        insta::assert_snapshot!(f("%k", time(0, 0, 0, 0)), @" 0");
1101
1102        insta::assert_snapshot!(f("%l", time(9, 0, 0, 0)), @" 9");
1103        insta::assert_snapshot!(f("%l", time(11, 0, 0, 0)), @"11");
1104        insta::assert_snapshot!(f("%l", time(23, 0, 0, 0)), @"11");
1105        insta::assert_snapshot!(f("%l", time(0, 0, 0, 0)), @"12");
1106    }
1107
1108    #[test]
1109    fn ok_format_minute() {
1110        let f = |fmt: &str, time: Time| format(fmt, time).unwrap();
1111
1112        insta::assert_snapshot!(f("%M", time(0, 9, 0, 0)), @"09");
1113        insta::assert_snapshot!(f("%M", time(0, 11, 0, 0)), @"11");
1114        insta::assert_snapshot!(f("%M", time(0, 23, 0, 0)), @"23");
1115        insta::assert_snapshot!(f("%M", time(0, 0, 0, 0)), @"00");
1116    }
1117
1118    #[test]
1119    fn ok_format_month() {
1120        let f = |fmt: &str, date: Date| format(fmt, date).unwrap();
1121
1122        insta::assert_snapshot!(f("%m", date(2024, 7, 14)), @"07");
1123        insta::assert_snapshot!(f("%m", date(2024, 12, 14)), @"12");
1124        insta::assert_snapshot!(f("%0m", date(2024, 7, 14)), @"07");
1125        insta::assert_snapshot!(f("%0m", date(2024, 12, 14)), @"12");
1126        insta::assert_snapshot!(f("%-m", date(2024, 7, 14)), @"7");
1127        insta::assert_snapshot!(f("%-m", date(2024, 12, 14)), @"12");
1128        insta::assert_snapshot!(f("%_m", date(2024, 7, 14)), @" 7");
1129        insta::assert_snapshot!(f("%_m", date(2024, 12, 14)), @"12");
1130    }
1131
1132    #[test]
1133    fn ok_format_month_name() {
1134        let f = |fmt: &str, date: Date| format(fmt, date).unwrap();
1135
1136        insta::assert_snapshot!(f("%B", date(2024, 7, 14)), @"July");
1137        insta::assert_snapshot!(f("%b", date(2024, 7, 14)), @"Jul");
1138        insta::assert_snapshot!(f("%h", date(2024, 7, 14)), @"Jul");
1139
1140        insta::assert_snapshot!(f("%#B", date(2024, 7, 14)), @"July");
1141        insta::assert_snapshot!(f("%^B", date(2024, 7, 14)), @"JULY");
1142    }
1143
1144    #[test]
1145    fn ok_format_offset_from_zoned() {
1146        if crate::tz::db().is_definitively_empty() {
1147            return;
1148        }
1149
1150        let f = |fmt: &str, zdt: &Zoned| format(fmt, zdt).unwrap();
1151
1152        let zdt = date(2024, 7, 14)
1153            .at(22, 24, 0, 0)
1154            .in_tz("America/New_York")
1155            .unwrap();
1156        insta::assert_snapshot!(f("%z", &zdt), @"-0400");
1157        insta::assert_snapshot!(f("%:z", &zdt), @"-04:00");
1158
1159        let zdt = zdt.checked_add(crate::Span::new().months(5)).unwrap();
1160        insta::assert_snapshot!(f("%z", &zdt), @"-0500");
1161        insta::assert_snapshot!(f("%:z", &zdt), @"-05:00");
1162    }
1163
1164    #[test]
1165    fn ok_format_offset_plain() {
1166        let o = |h: i8, m: i8, s: i8| -> Offset { Offset::hms(h, m, s) };
1167        let f = |fmt: &str, offset: Offset| {
1168            let mut tm = BrokenDownTime::default();
1169            tm.set_offset(Some(offset));
1170            tm.to_string(fmt).unwrap()
1171        };
1172
1173        insta::assert_snapshot!(f("%z", o(0, 0, 0)), @"+0000");
1174        insta::assert_snapshot!(f("%:z", o(0, 0, 0)), @"+00:00");
1175        insta::assert_snapshot!(f("%::z", o(0, 0, 0)), @"+00:00:00");
1176        insta::assert_snapshot!(f("%:::z", o(0, 0, 0)), @"+00");
1177
1178        insta::assert_snapshot!(f("%z", -o(4, 0, 0)), @"-0400");
1179        insta::assert_snapshot!(f("%:z", -o(4, 0, 0)), @"-04:00");
1180        insta::assert_snapshot!(f("%::z", -o(4, 0, 0)), @"-04:00:00");
1181        insta::assert_snapshot!(f("%:::z", -o(4, 0, 0)), @"-04");
1182
1183        insta::assert_snapshot!(f("%z", o(5, 30, 0)), @"+0530");
1184        insta::assert_snapshot!(f("%:z", o(5, 30, 0)), @"+05:30");
1185        insta::assert_snapshot!(f("%::z", o(5, 30, 0)), @"+05:30:00");
1186        insta::assert_snapshot!(f("%:::z", o(5, 30, 0)), @"+05:30");
1187
1188        insta::assert_snapshot!(f("%z", o(5, 30, 15)), @"+053015");
1189        insta::assert_snapshot!(f("%:z", o(5, 30, 15)), @"+05:30:15");
1190        insta::assert_snapshot!(f("%::z", o(5, 30, 15)), @"+05:30:15");
1191        insta::assert_snapshot!(f("%:::z", o(5, 30, 15)), @"+05:30:15");
1192
1193        insta::assert_snapshot!(f("%z", o(5, 0, 15)), @"+050015");
1194        insta::assert_snapshot!(f("%:z", o(5, 0, 15)), @"+05:00:15");
1195        insta::assert_snapshot!(f("%::z", o(5, 0, 15)), @"+05:00:15");
1196        insta::assert_snapshot!(f("%:::z", o(5, 0, 15)), @"+05:00:15");
1197    }
1198
1199    #[test]
1200    fn ok_format_second() {
1201        let f = |fmt: &str, time: Time| format(fmt, time).unwrap();
1202
1203        insta::assert_snapshot!(f("%S", time(0, 0, 9, 0)), @"09");
1204        insta::assert_snapshot!(f("%S", time(0, 0, 11, 0)), @"11");
1205        insta::assert_snapshot!(f("%S", time(0, 0, 23, 0)), @"23");
1206        insta::assert_snapshot!(f("%S", time(0, 0, 0, 0)), @"00");
1207    }
1208
1209    #[test]
1210    fn ok_format_subsec_nanosecond() {
1211        let f = |fmt: &str, time: Time| format(fmt, time).unwrap();
1212        let mk = |subsec| time(0, 0, 0, subsec);
1213
1214        insta::assert_snapshot!(f("%f", mk(123_000_000)), @"123");
1215        insta::assert_snapshot!(f("%f", mk(0)), @"0");
1216        insta::assert_snapshot!(f("%3f", mk(0)), @"000");
1217        insta::assert_snapshot!(f("%3f", mk(123_000_000)), @"123");
1218        insta::assert_snapshot!(f("%6f", mk(123_000_000)), @"123000");
1219        insta::assert_snapshot!(f("%9f", mk(123_000_000)), @"123000000");
1220        insta::assert_snapshot!(f("%255f", mk(123_000_000)), @"123000000");
1221
1222        insta::assert_snapshot!(f("%.f", mk(123_000_000)), @".123");
1223        insta::assert_snapshot!(f("%.f", mk(0)), @"");
1224        insta::assert_snapshot!(f("%3.f", mk(0)), @"");
1225        insta::assert_snapshot!(f("%.3f", mk(0)), @".000");
1226        insta::assert_snapshot!(f("%.3f", mk(123_000_000)), @".123");
1227        insta::assert_snapshot!(f("%.6f", mk(123_000_000)), @".123000");
1228        insta::assert_snapshot!(f("%.9f", mk(123_000_000)), @".123000000");
1229        insta::assert_snapshot!(f("%.255f", mk(123_000_000)), @".123000000");
1230
1231        insta::assert_snapshot!(f("%3f", mk(123_456_789)), @"123");
1232        insta::assert_snapshot!(f("%6f", mk(123_456_789)), @"123456");
1233        insta::assert_snapshot!(f("%9f", mk(123_456_789)), @"123456789");
1234
1235        insta::assert_snapshot!(f("%.0f", mk(123_456_789)), @"");
1236        insta::assert_snapshot!(f("%.3f", mk(123_456_789)), @".123");
1237        insta::assert_snapshot!(f("%.6f", mk(123_456_789)), @".123456");
1238        insta::assert_snapshot!(f("%.9f", mk(123_456_789)), @".123456789");
1239
1240        insta::assert_snapshot!(f("%N", mk(123_000_000)), @"123000000");
1241        insta::assert_snapshot!(f("%N", mk(0)), @"000000000");
1242        insta::assert_snapshot!(f("%N", mk(000_123_000)), @"000123000");
1243        insta::assert_snapshot!(f("%3N", mk(0)), @"000");
1244        insta::assert_snapshot!(f("%3N", mk(123_000_000)), @"123");
1245        insta::assert_snapshot!(f("%6N", mk(123_000_000)), @"123000");
1246        insta::assert_snapshot!(f("%9N", mk(123_000_000)), @"123000000");
1247        insta::assert_snapshot!(f("%255N", mk(123_000_000)), @"123000000");
1248    }
1249
1250    #[test]
1251    fn ok_format_tzabbrev() {
1252        if crate::tz::db().is_definitively_empty() {
1253            return;
1254        }
1255
1256        let f = |fmt: &str, zdt: &Zoned| format(fmt, zdt).unwrap();
1257
1258        let zdt = date(2024, 7, 14)
1259            .at(22, 24, 0, 0)
1260            .in_tz("America/New_York")
1261            .unwrap();
1262        insta::assert_snapshot!(f("%Z", &zdt), @"EDT");
1263        insta::assert_snapshot!(f("%^Z", &zdt), @"EDT");
1264        insta::assert_snapshot!(f("%#Z", &zdt), @"edt");
1265
1266        let zdt = zdt.checked_add(crate::Span::new().months(5)).unwrap();
1267        insta::assert_snapshot!(f("%Z", &zdt), @"EST");
1268    }
1269
1270    #[test]
1271    fn ok_format_iana() {
1272        if crate::tz::db().is_definitively_empty() {
1273            return;
1274        }
1275
1276        let f = |fmt: &str, zdt: &Zoned| format(fmt, zdt).unwrap();
1277
1278        let zdt = date(2024, 7, 14)
1279            .at(22, 24, 0, 0)
1280            .in_tz("America/New_York")
1281            .unwrap();
1282        insta::assert_snapshot!(f("%Q", &zdt), @"America/New_York");
1283        insta::assert_snapshot!(f("%:Q", &zdt), @"America/New_York");
1284
1285        let zdt = date(2024, 7, 14)
1286            .at(22, 24, 0, 0)
1287            .to_zoned(crate::tz::offset(-4).to_time_zone())
1288            .unwrap();
1289        insta::assert_snapshot!(f("%Q", &zdt), @"-0400");
1290        insta::assert_snapshot!(f("%:Q", &zdt), @"-04:00");
1291
1292        let zdt = date(2024, 7, 14)
1293            .at(22, 24, 0, 0)
1294            .to_zoned(crate::tz::TimeZone::UTC)
1295            .unwrap();
1296        insta::assert_snapshot!(f("%Q", &zdt), @"UTC");
1297        insta::assert_snapshot!(f("%:Q", &zdt), @"UTC");
1298    }
1299
1300    #[test]
1301    fn ok_format_weekday_name() {
1302        let f = |fmt: &str, date: Date| format(fmt, date).unwrap();
1303
1304        insta::assert_snapshot!(f("%A", date(2024, 7, 14)), @"Sunday");
1305        insta::assert_snapshot!(f("%a", date(2024, 7, 14)), @"Sun");
1306
1307        insta::assert_snapshot!(f("%#A", date(2024, 7, 14)), @"Sunday");
1308        insta::assert_snapshot!(f("%^A", date(2024, 7, 14)), @"SUNDAY");
1309
1310        insta::assert_snapshot!(f("%u", date(2024, 7, 14)), @"7");
1311        insta::assert_snapshot!(f("%w", date(2024, 7, 14)), @"0");
1312    }
1313
1314    #[test]
1315    fn ok_format_year() {
1316        let f = |fmt: &str, date: Date| format(fmt, date).unwrap();
1317
1318        insta::assert_snapshot!(f("%Y", date(2024, 7, 14)), @"2024");
1319        insta::assert_snapshot!(f("%Y", date(24, 7, 14)), @"0024");
1320        insta::assert_snapshot!(f("%Y", date(-24, 7, 14)), @"-0024");
1321
1322        insta::assert_snapshot!(f("%C", date(2024, 7, 14)), @"20");
1323        insta::assert_snapshot!(f("%C", date(1815, 7, 14)), @"18");
1324        insta::assert_snapshot!(f("%C", date(915, 7, 14)), @"9");
1325        insta::assert_snapshot!(f("%C", date(1, 7, 14)), @"0");
1326        insta::assert_snapshot!(f("%C", date(0, 7, 14)), @"0");
1327        insta::assert_snapshot!(f("%C", date(-1, 7, 14)), @"0");
1328        insta::assert_snapshot!(f("%C", date(-2024, 7, 14)), @"-20");
1329        insta::assert_snapshot!(f("%C", date(-1815, 7, 14)), @"-18");
1330        insta::assert_snapshot!(f("%C", date(-915, 7, 14)), @"-9");
1331    }
1332
1333    #[test]
1334    fn ok_format_default_locale() {
1335        let f = |fmt: &str, date: DateTime| format(fmt, date).unwrap();
1336
1337        insta::assert_snapshot!(
1338            f("%c", date(2024, 7, 14).at(0, 0, 0, 0)),
1339            @"2024 M07 14, Sun 00:00:00",
1340        );
1341        insta::assert_snapshot!(
1342            f("%c", date(24, 7, 14).at(0, 0, 0, 0)),
1343            @"0024 M07 14, Sun 00:00:00",
1344        );
1345        insta::assert_snapshot!(
1346            f("%c", date(-24, 7, 14).at(0, 0, 0, 0)),
1347            @"-0024 M07 14, Wed 00:00:00",
1348        );
1349        insta::assert_snapshot!(
1350            f("%c", date(2024, 7, 14).at(17, 31, 59, 123_456_789)),
1351            @"2024 M07 14, Sun 17:31:59",
1352        );
1353
1354        insta::assert_snapshot!(
1355            f("%r", date(2024, 7, 14).at(8, 30, 0, 0)),
1356            @"8:30:00 AM",
1357        );
1358        insta::assert_snapshot!(
1359            f("%r", date(2024, 7, 14).at(17, 31, 59, 123_456_789)),
1360            @"5:31:59 PM",
1361        );
1362
1363        insta::assert_snapshot!(
1364            f("%x", date(2024, 7, 14).at(0, 0, 0, 0)),
1365            @"2024 M07 14",
1366        );
1367
1368        insta::assert_snapshot!(
1369            f("%X", date(2024, 7, 14).at(8, 30, 0, 0)),
1370            @"08:30:00",
1371        );
1372        insta::assert_snapshot!(
1373            f("%X", date(2024, 7, 14).at(17, 31, 59, 123_456_789)),
1374            @"17:31:59",
1375        );
1376    }
1377
1378    #[test]
1379    fn ok_format_posix_locale() {
1380        let f = |fmt: &str, date: DateTime| {
1381            let config = Config::new().custom(PosixCustom::default());
1382            let tm = BrokenDownTime::from(date);
1383            tm.to_string_with_config(&config, fmt).unwrap()
1384        };
1385
1386        insta::assert_snapshot!(
1387            f("%c", date(2024, 7, 14).at(0, 0, 0, 0)),
1388            @"Sun Jul 14 00:00:00 2024",
1389        );
1390        insta::assert_snapshot!(
1391            f("%c", date(24, 7, 14).at(0, 0, 0, 0)),
1392            @"Sun Jul 14 00:00:00 0024",
1393        );
1394        insta::assert_snapshot!(
1395            f("%c", date(-24, 7, 14).at(0, 0, 0, 0)),
1396            @"Wed Jul 14 00:00:00 -0024",
1397        );
1398        insta::assert_snapshot!(
1399            f("%c", date(2024, 7, 14).at(17, 31, 59, 123_456_789)),
1400            @"Sun Jul 14 17:31:59 2024",
1401        );
1402
1403        insta::assert_snapshot!(
1404            f("%r", date(2024, 7, 14).at(8, 30, 0, 0)),
1405            @"08:30:00 AM",
1406        );
1407        insta::assert_snapshot!(
1408            f("%r", date(2024, 7, 14).at(17, 31, 59, 123_456_789)),
1409            @"05:31:59 PM",
1410        );
1411
1412        insta::assert_snapshot!(
1413            f("%x", date(2024, 7, 14).at(0, 0, 0, 0)),
1414            @"07/14/24",
1415        );
1416
1417        insta::assert_snapshot!(
1418            f("%X", date(2024, 7, 14).at(8, 30, 0, 0)),
1419            @"08:30:00",
1420        );
1421        insta::assert_snapshot!(
1422            f("%X", date(2024, 7, 14).at(17, 31, 59, 123_456_789)),
1423            @"17:31:59",
1424        );
1425    }
1426
1427    #[test]
1428    fn ok_format_year_2digit() {
1429        let f = |fmt: &str, date: Date| format(fmt, date).unwrap();
1430
1431        insta::assert_snapshot!(f("%y", date(2024, 7, 14)), @"24");
1432        insta::assert_snapshot!(f("%y", date(2001, 7, 14)), @"01");
1433        insta::assert_snapshot!(f("%-y", date(2001, 7, 14)), @"1");
1434        insta::assert_snapshot!(f("%5y", date(2001, 7, 14)), @"00001");
1435        insta::assert_snapshot!(f("%-5y", date(2001, 7, 14)), @"1");
1436        insta::assert_snapshot!(f("%05y", date(2001, 7, 14)), @"00001");
1437        insta::assert_snapshot!(f("%_y", date(2001, 7, 14)), @" 1");
1438        insta::assert_snapshot!(f("%_5y", date(2001, 7, 14)), @"    1");
1439    }
1440
1441    #[test]
1442    fn ok_format_iso_week_year() {
1443        let f = |fmt: &str, date: Date| format(fmt, date).unwrap();
1444
1445        insta::assert_snapshot!(f("%G", date(2019, 11, 30)), @"2019");
1446        insta::assert_snapshot!(f("%G", date(19, 11, 30)), @"0019");
1447        insta::assert_snapshot!(f("%G", date(-19, 11, 30)), @"-0019");
1448
1449        // tricksy
1450        insta::assert_snapshot!(f("%G", date(2019, 12, 30)), @"2020");
1451    }
1452
1453    #[test]
1454    fn ok_format_week_num() {
1455        let f = |fmt: &str, date: Date| format(fmt, date).unwrap();
1456
1457        insta::assert_snapshot!(f("%U", date(2025, 1, 4)), @"00");
1458        insta::assert_snapshot!(f("%U", date(2025, 1, 5)), @"01");
1459
1460        insta::assert_snapshot!(f("%W", date(2025, 1, 5)), @"00");
1461        insta::assert_snapshot!(f("%W", date(2025, 1, 6)), @"01");
1462    }
1463
1464    #[test]
1465    fn ok_format_timestamp() {
1466        let f = |fmt: &str, ts: Timestamp| format(fmt, ts).unwrap();
1467
1468        let ts = "1970-01-01T00:00Z".parse().unwrap();
1469        insta::assert_snapshot!(f("%s", ts), @"0");
1470        insta::assert_snapshot!(f("%3s", ts), @"  0");
1471        insta::assert_snapshot!(f("%03s", ts), @"000");
1472
1473        let ts = "2025-01-20T13:09-05[US/Eastern]".parse().unwrap();
1474        insta::assert_snapshot!(f("%s", ts), @"1737396540");
1475    }
1476
1477    #[test]
1478    fn ok_format_quarter() {
1479        let f = |fmt: &str, date: Date| format(fmt, date).unwrap();
1480
1481        insta::assert_snapshot!(f("%q", date(2024, 3, 31)), @"1");
1482        insta::assert_snapshot!(f("%q", date(2024, 4, 1)), @"2");
1483        insta::assert_snapshot!(f("%q", date(2024, 7, 14)), @"3");
1484        insta::assert_snapshot!(f("%q", date(2024, 12, 31)), @"4");
1485
1486        insta::assert_snapshot!(f("%2q", date(2024, 3, 31)), @"01");
1487        insta::assert_snapshot!(f("%02q", date(2024, 3, 31)), @"01");
1488        insta::assert_snapshot!(f("%_2q", date(2024, 3, 31)), @" 1");
1489    }
1490
1491    #[test]
1492    fn err_format_subsec_nanosecond() {
1493        let f = |fmt: &str, time: Time| format(fmt, time).unwrap_err();
1494        let mk = |subsec| time(0, 0, 0, subsec);
1495
1496        insta::assert_snapshot!(
1497            f("%00f", mk(123_456_789)),
1498            @"strftime formatting failed: %f failed: zero precision with %f is not allowed",
1499        );
1500    }
1501
1502    #[test]
1503    fn err_format_timestamp() {
1504        let f = |fmt: &str, dt: DateTime| format(fmt, dt).unwrap_err();
1505
1506        let dt = date(2025, 1, 20).at(13, 9, 0, 0);
1507        insta::assert_snapshot!(
1508            f("%s", dt),
1509            @"strftime formatting failed: %s failed: requires instant (a date, time and offset) to format Unix timestamp",
1510        );
1511    }
1512
1513    #[test]
1514    fn lenient() {
1515        fn f(
1516            fmt: &str,
1517            tm: impl Into<BrokenDownTime>,
1518        ) -> alloc::string::String {
1519            let config = Config::new().lenient(true);
1520            tm.into().to_string_with_config(&config, fmt).unwrap()
1521        }
1522
1523        insta::assert_snapshot!(f("%z", date(2024, 7, 9)), @"%z");
1524        insta::assert_snapshot!(f("%:z", date(2024, 7, 9)), @"%:z");
1525        insta::assert_snapshot!(f("%Q", date(2024, 7, 9)), @"%Q");
1526        insta::assert_snapshot!(f("%+", date(2024, 7, 9)), @"%+");
1527        insta::assert_snapshot!(f("%F", date(2024, 7, 9)), @"2024-07-09");
1528        insta::assert_snapshot!(f("%T", date(2024, 7, 9)), @"%T");
1529    }
1530}