1use crate::{
2 error::{
3 fmt::strtime::{Error as E, FormatError as FE},
4 ErrorContext,
5 },
6 fmt::{
7 buffer::BorrowedWriter,
8 strtime::{
9 month_name_abbrev, month_name_full, weekday_name_abbrev,
10 weekday_name_full, BrokenDownTime, Config, Custom, Extension,
11 Flag, Meridiem,
12 },
13 },
14 tz::Offset,
15 util::utf8,
16 Error,
17};
18
19enum Item {
20 AlreadyFormatted,
21 Integer(ItemInteger),
22 Fraction(ItemFraction),
23 String(ItemString),
24 Offset(ItemOffset),
25}
26
27struct ItemInteger {
28 pad_byte: u8,
29 pad_width: u8,
30 number: i64,
31}
32
33impl ItemInteger {
34 fn new(pad_byte: u8, pad_width: u8, n: impl Into<i64>) -> ItemInteger {
35 ItemInteger { pad_byte, pad_width, number: n.into() }
36 }
37}
38
39struct ItemFraction {
40 width: Option<u8>,
41 dot: bool,
42 subsec: u32,
43}
44
45struct ItemString {
46 case: Case,
47 string: &'static str,
48}
49
50impl ItemString {
51 fn new(case: Case, string: &'static str) -> ItemString {
52 ItemString { case, string }
53 }
54}
55
56struct ItemOffset {
57 offset: Offset,
58 colon: bool,
59 minute: bool,
60 second: bool,
61}
62
63impl ItemOffset {
64 fn new(
65 offset: Offset,
66 colon: bool,
67 minute: bool,
68 second: bool,
69 ) -> ItemOffset {
70 ItemOffset { offset, colon, minute, second }
71 }
72}
73
74pub(super) struct Formatter<
75 'config,
76 'fmt,
77 'tm,
78 'writer,
79 'buffer,
80 'data,
81 'write,
82 L,
83> {
84 pub(super) config: &'config Config<L>,
85 pub(super) fmt: &'fmt [u8],
86 pub(super) tm: &'tm BrokenDownTime,
87 pub(super) wtr: &'writer mut BorrowedWriter<'buffer, 'data, 'write>,
88}
89
90impl<'config, 'fmt, 'tm, 'writer, 'buffer, 'data, 'write, L: Custom>
91 Formatter<'config, 'fmt, 'tm, 'writer, 'buffer, 'data, 'write, L>
92{
93 #[inline(never)]
94 pub(super) fn format(&mut self) -> Result<(), Error> {
95 while !self.fmt.is_empty() {
96 if self.f() != b'%' {
97 if self.f().is_ascii() {
98 self.wtr.write_ascii_char(self.f())?;
99 self.bump_fmt();
100 } else {
101 let ch = self.utf8_decode_and_bump()?;
102 self.wtr.write_char(ch)?;
103 }
104 continue;
105 }
106 if !self.bump_fmt() {
107 if self.config.lenient {
108 self.wtr.write_ascii_char(b'%')?;
109 break;
110 }
111 return Err(E::UnexpectedEndAfterPercent.into());
112 }
113 let orig = self.fmt;
114 if let Err(err) =
115 self.parse_extension().and_then(|ext| self.format_one(&ext))
116 {
117 if !self.config.lenient {
118 return Err(err);
119 }
120 self.wtr.write_ascii_char(b'%')?;
124 self.fmt = orig;
126 }
127 }
128 Ok(())
129 }
130
131 #[cfg_attr(feature = "perf-inline", inline(always))]
132 fn format_one(&mut self, ext: &Extension) -> Result<(), Error> {
133 let i = |item: ItemInteger| Item::Integer(item);
134 let s = |item: ItemString| Item::String(item);
135 let o = |item: ItemOffset| Item::Offset(item);
136 let failc =
137 |directive, colons| E::DirectiveFailure { directive, colons };
138 let fail = |directive| failc(directive, 0);
139
140 let mut directive = self.f();
142 let item = match directive {
143 b'%' => self.fmt_literal("%").map(s).context(fail(b'%')),
144 b'A' => self.fmt_weekday_full().map(s).context(fail(b'A')),
145 b'a' => self.fmt_weekday_abbrev().map(s).context(fail(b'a')),
146 b'B' => self.fmt_month_full().map(s).context(fail(b'B')),
147 b'b' => self.fmt_month_abbrev().map(s).context(fail(b'b')),
148 b'C' => self.fmt_century().map(i).context(fail(b'C')),
149 b'c' => self.fmt_datetime(ext).context(fail(b'c')),
150 b'D' => self.fmt_american_date().context(fail(b'D')),
151 b'd' => self.fmt_day_zero().map(i).context(fail(b'd')),
152 b'e' => self.fmt_day_space().map(i).context(fail(b'e')),
153 b'F' => self.fmt_iso_date().context(fail(b'F')),
154 b'f' => self.fmt_fractional(ext).context(fail(b'f')),
155 b'G' => self.fmt_iso_week_year().map(i).context(fail(b'G')),
156 b'g' => self.fmt_iso_week_year2().map(i).context(fail(b'g')),
157 b'H' => self.fmt_hour24_zero().map(i).context(fail(b'H')),
158 b'h' => self.fmt_month_abbrev().map(s).context(fail(b'b')),
159 b'I' => self.fmt_hour12_zero().map(i).context(fail(b'H')),
160 b'j' => self.fmt_day_of_year().map(i).context(fail(b'j')),
161 b'k' => self.fmt_hour24_space().map(i).context(fail(b'k')),
162 b'l' => self.fmt_hour12_space().map(i).context(fail(b'l')),
163 b'M' => self.fmt_minute().map(i).context(fail(b'M')),
164 b'm' => self.fmt_month().map(i).context(fail(b'm')),
165 b'N' => self.fmt_nanoseconds(ext).context(fail(b'N')),
166 b'n' => self.fmt_literal("\n").map(s).context(fail(b'n')),
167 b'P' => self.fmt_ampm_lower().map(s).context(fail(b'P')),
168 b'p' => self.fmt_ampm_upper(ext).map(s).context(fail(b'p')),
169 b'Q' => match ext.colons {
170 0 => self.fmt_iana_nocolon().context(fail(b'Q')),
171 1 => self.fmt_iana_colon().context(failc(b'Q', 1)),
172 _ => return Err(E::ColonCount { directive: b'Q' }.into()),
173 },
174 b'q' => self.fmt_quarter().map(i).context(fail(b'q')),
175 b'R' => self.fmt_clock_nosecs().context(fail(b'R')),
176 b'r' => self.fmt_12hour_time(ext).context(fail(b'r')),
177 b'S' => self.fmt_second().map(i).context(fail(b'S')),
178 b's' => self.fmt_timestamp().map(i).context(fail(b's')),
179 b'T' => self.fmt_clock_secs().context(fail(b'T')),
180 b't' => self.fmt_literal("\t").map(s).context(fail(b't')),
181 b'U' => self.fmt_week_sun().map(i).context(fail(b'U')),
182 b'u' => self.fmt_weekday_mon().map(i).context(fail(b'u')),
183 b'V' => self.fmt_week_iso().map(i).context(fail(b'V')),
184 b'W' => self.fmt_week_mon().map(i).context(fail(b'W')),
185 b'w' => self.fmt_weekday_sun().map(i).context(fail(b'w')),
186 b'X' => self.fmt_time(ext).context(fail(b'X')),
187 b'x' => self.fmt_date(ext).context(fail(b'x')),
188 b'Y' => self.fmt_year().map(i).context(fail(b'Y')),
189 b'y' => self.fmt_year2().map(i).context(fail(b'y')),
190 b'Z' => self.fmt_tzabbrev(ext).context(fail(b'Z')),
191 b'z' => match ext.colons {
192 0 => self.fmt_offset_nocolon().map(o).context(fail(b'z')),
193 1 => self.fmt_offset_colon().map(o).context(failc(b'z', 1)),
194 2 => self.fmt_offset_colon2().map(o).context(failc(b'z', 2)),
195 3 => self.fmt_offset_colon3().map(o).context(failc(b'z', 3)),
196 _ => return Err(E::ColonCount { directive: b'z' }.into()),
197 },
198 b'.' => {
199 if !self.bump_fmt() {
200 return Err(E::UnexpectedEndAfterDot.into());
201 }
202 let ext =
205 &Extension { width: self.parse_width()?, ..ext.clone() };
206 directive = self.f();
207 match directive {
208 b'f' => self
209 .fmt_dot_fractional(ext)
210 .context(E::DirectiveFailureDot { directive }),
211 _ => {
212 return Err(Error::from(
213 E::UnknownDirectiveAfterDot { directive },
214 ));
215 }
216 }
217 }
218 _ => return Err(Error::from(E::UnknownDirective { directive })),
219 }?;
220 self.write_item(ext, &item).context(fail(directive))?;
221 self.bump_fmt();
222 Ok(())
223 }
224
225 fn f(&self) -> u8 {
231 self.fmt[0]
232 }
233
234 fn bump_fmt(&mut self) -> bool {
239 self.fmt = &self.fmt[1..];
240 !self.fmt.is_empty()
241 }
242
243 #[cold]
265 #[inline(never)]
266 fn utf8_decode_and_bump(&mut self) -> Result<char, FE> {
267 match utf8::decode(self.fmt).expect("non-empty fmt") {
268 Ok(ch) => {
269 self.fmt = &self.fmt[ch.len_utf8()..];
270 return Ok(ch);
271 }
272 Err(err) if self.config.lenient => {
273 self.fmt = &self.fmt[err.len()..];
274 return Ok(char::REPLACEMENT_CHARACTER);
275 }
276 Err(_) => Err(FE::InvalidUtf8),
277 }
278 }
279
280 #[cfg_attr(feature = "perf-inline", inline(always))]
284 fn parse_extension(&mut self) -> Result<Extension, Error> {
285 if self.f().is_ascii_alphabetic() {
286 return Ok(Extension { flag: None, width: None, colons: 0 });
287 }
288 let flag = self.parse_flag()?;
289 let width = self.parse_width()?;
290 let colons = self.parse_colons()?;
291 Ok(Extension { flag, width, colons })
292 }
293
294 #[cfg_attr(feature = "perf-inline", inline(always))]
297 fn parse_flag(&mut self) -> Result<Option<Flag>, Error> {
298 let (flag, fmt) = Extension::parse_flag(self.fmt)?;
299 self.fmt = fmt;
300 Ok(flag)
301 }
302
303 #[cfg_attr(feature = "perf-inline", inline(always))]
313 fn parse_width(&mut self) -> Result<Option<u8>, Error> {
314 let (width, fmt) = Extension::parse_width(self.fmt)?;
315 self.fmt = fmt;
316 Ok(width)
317 }
318
319 #[cfg_attr(feature = "perf-inline", inline(always))]
322 fn parse_colons(&mut self) -> Result<u8, Error> {
323 let (colons, fmt) = Extension::parse_colons(self.fmt)?;
324 self.fmt = fmt;
325 Ok(colons)
326 }
327
328 #[cfg_attr(feature = "perf-inline", inline(always))]
329 fn write_item(
330 &mut self,
331 ext: &Extension,
332 item: &Item,
333 ) -> Result<(), Error> {
334 match *item {
335 Item::AlreadyFormatted => Ok(()),
336 Item::Integer(ref item) => ext.write_int(
337 item.pad_byte,
338 item.pad_width,
339 item.number,
340 self.wtr,
341 ),
342 Item::Fraction(ItemFraction { width, dot, subsec }) => {
343 if dot {
344 self.wtr.write_ascii_char(b'.')?;
345 }
346 self.wtr.write_fraction(width, subsec)
347 }
348 Item::String(ref item) => {
349 ext.write_str(item.case, item.string, self.wtr)
350 }
351 Item::Offset(ref item) => write_offset(
352 item.offset,
353 item.colon,
354 item.minute,
355 item.second,
356 self.wtr,
357 ),
358 }
359 }
360
361 fn fmt_ampm_lower(&self) -> Result<ItemString, Error> {
368 let meridiem = match self.tm.meridiem() {
369 Some(meridiem) => meridiem,
370 None => {
371 let hour = self.tm.hour().ok_or(FE::RequiresTime)?;
372 if hour < 12 {
373 Meridiem::AM
374 } else {
375 Meridiem::PM
376 }
377 }
378 };
379 let s = match meridiem {
380 Meridiem::AM => "am",
381 Meridiem::PM => "pm",
382 };
383 Ok(ItemString::new(Case::AsIs, s))
384 }
385
386 fn fmt_ampm_upper(&self, ext: &Extension) -> Result<ItemString, Error> {
388 let meridiem = match self.tm.meridiem() {
389 Some(meridiem) => meridiem,
390 None => {
391 let hour = self.tm.hour().ok_or(FE::RequiresTime)?;
392 if hour < 12 {
393 Meridiem::AM
394 } else {
395 Meridiem::PM
396 }
397 }
398 };
399 let s = if matches!(ext.flag, Some(Flag::Swapcase)) {
401 match meridiem {
402 Meridiem::AM => "am",
403 Meridiem::PM => "pm",
404 }
405 } else {
406 match meridiem {
407 Meridiem::AM => "AM",
408 Meridiem::PM => "PM",
409 }
410 };
411 Ok(ItemString::new(Case::AsIs, s))
412 }
413
414 fn fmt_american_date(&mut self) -> Result<Item, Error> {
416 let ItemInteger { number, .. } = self.fmt_month()?;
417 self.wtr.write_int_pad2(number.unsigned_abs())?;
418 self.wtr.write_ascii_char(b'/')?;
419 let ItemInteger { number, .. } = self.fmt_day_zero()?;
420 self.wtr.write_int_pad2(number.unsigned_abs())?;
421 self.wtr.write_ascii_char(b'/')?;
422 let ItemInteger { number, .. } = self.fmt_year2()?;
423 if number < 0 {
424 self.wtr.write_ascii_char(b'-')?;
425 }
426 self.wtr.write_int_pad2(number.unsigned_abs())?;
427 Ok(Item::AlreadyFormatted)
428 }
429
430 fn fmt_clock_nosecs(&mut self) -> Result<Item, Error> {
432 let ItemInteger { number, .. } = self.fmt_hour24_zero()?;
433 self.wtr.write_int_pad2(number.unsigned_abs())?;
434 self.wtr.write_ascii_char(b':')?;
435 let ItemInteger { number, .. } = self.fmt_minute()?;
436 self.wtr.write_int_pad2(number.unsigned_abs())?;
437 Ok(Item::AlreadyFormatted)
438 }
439
440 fn fmt_clock_secs(&mut self) -> Result<Item, Error> {
442 let ItemInteger { number, .. } = self.fmt_hour24_zero()?;
443 self.wtr.write_int_pad2(number.unsigned_abs())?;
444 self.wtr.write_ascii_char(b':')?;
445 let ItemInteger { number, .. } = self.fmt_minute()?;
446 self.wtr.write_int_pad2(number.unsigned_abs())?;
447 self.wtr.write_ascii_char(b':')?;
448 let ItemInteger { number, .. } = self.fmt_second()?;
449 self.wtr.write_int_pad2(number.unsigned_abs())?;
450 Ok(Item::AlreadyFormatted)
451 }
452
453 fn fmt_iso_date(&mut self) -> Result<Item, Error> {
455 let ItemInteger { number, .. } = self.fmt_year()?;
456 if number < 0 {
457 self.wtr.write_ascii_char(b'-')?;
458 }
459 self.wtr.write_int_pad4(number.unsigned_abs())?;
460 self.wtr.write_ascii_char(b'-')?;
461 let ItemInteger { number, .. } = self.fmt_month()?;
462 self.wtr.write_int_pad2(number.unsigned_abs())?;
463 self.wtr.write_ascii_char(b'-')?;
464 let ItemInteger { number, .. } = self.fmt_day_zero()?;
465 self.wtr.write_int_pad2(number.unsigned_abs())?;
466 Ok(Item::AlreadyFormatted)
467 }
468
469 fn fmt_day_zero(&self) -> Result<ItemInteger, Error> {
471 let day = self
472 .tm
473 .day()
474 .or_else(|| self.tm.to_date().ok().map(|d| d.day()))
475 .ok_or(FE::RequiresDate)?;
476 Ok(ItemInteger::new(b'0', 2, day))
477 }
478
479 fn fmt_day_space(&self) -> Result<ItemInteger, Error> {
481 let day = self
482 .tm
483 .day()
484 .or_else(|| self.tm.to_date().ok().map(|d| d.day()))
485 .ok_or(FE::RequiresDate)?;
486 Ok(ItemInteger::new(b' ', 2, day))
487 }
488
489 fn fmt_hour12_zero(&self) -> Result<ItemInteger, Error> {
491 let mut hour = self.tm.hour().ok_or(FE::RequiresTime)?;
492 if hour == 0 {
493 hour = 12;
494 } else if hour > 12 {
495 hour -= 12;
496 }
497 Ok(ItemInteger::new(b'0', 2, hour))
498 }
499
500 fn fmt_hour24_zero(&self) -> Result<ItemInteger, Error> {
502 let hour = self.tm.hour().ok_or(FE::RequiresTime)?;
503 Ok(ItemInteger::new(b'0', 2, hour))
504 }
505
506 fn fmt_hour12_space(&self) -> Result<ItemInteger, Error> {
508 let mut hour = self.tm.hour().ok_or(FE::RequiresTime)?;
509 if hour == 0 {
510 hour = 12;
511 } else if hour > 12 {
512 hour -= 12;
513 }
514 Ok(ItemInteger::new(b' ', 2, hour))
515 }
516
517 fn fmt_hour24_space(&self) -> Result<ItemInteger, Error> {
519 let hour = self.tm.hour().ok_or(FE::RequiresTime)?;
520 Ok(ItemInteger::new(b' ', 2, hour))
521 }
522
523 fn fmt_minute(&self) -> Result<ItemInteger, Error> {
525 let minute = self.tm.minute().ok_or(FE::RequiresTime)?;
526 Ok(ItemInteger::new(b'0', 2, minute))
527 }
528
529 fn fmt_month(&self) -> Result<ItemInteger, Error> {
531 let month = self
532 .tm
533 .month()
534 .or_else(|| self.tm.to_date().ok().map(|d| d.month()))
535 .ok_or(FE::RequiresDate)?;
536 Ok(ItemInteger::new(b'0', 2, month))
537 }
538
539 fn fmt_month_full(&self) -> Result<ItemString, Error> {
541 let month = self
542 .tm
543 .month()
544 .or_else(|| self.tm.to_date().ok().map(|d| d.month()))
545 .ok_or(FE::RequiresDate)?;
546 Ok(ItemString::new(Case::AsIs, month_name_full(month)))
547 }
548
549 fn fmt_month_abbrev(&self) -> Result<ItemString, Error> {
551 let month = self
552 .tm
553 .month()
554 .or_else(|| self.tm.to_date().ok().map(|d| d.month()))
555 .ok_or(FE::RequiresDate)?;
556 Ok(ItemString::new(Case::AsIs, month_name_abbrev(month)))
557 }
558
559 fn fmt_iana_nocolon(&mut self) -> Result<Item, Error> {
561 let Some(iana) = self.tm.iana_time_zone() else {
562 let offset = self.tm.offset.ok_or(FE::RequiresTimeZoneOrOffset)?;
563 return Ok(Item::Offset(ItemOffset::new(
564 offset, false, true, false,
565 )));
566 };
567 self.wtr.write_str(iana)?;
568 Ok(Item::AlreadyFormatted)
569 }
570
571 fn fmt_iana_colon(&mut self) -> Result<Item, Error> {
573 let Some(iana) = self.tm.iana_time_zone() else {
574 let offset = self.tm.offset.ok_or(FE::RequiresTimeZoneOrOffset)?;
575 return Ok(Item::Offset(ItemOffset::new(
576 offset, true, true, false,
577 )));
578 };
579 self.wtr.write_str(iana)?;
580 Ok(Item::AlreadyFormatted)
581 }
582
583 fn fmt_offset_nocolon(&self) -> Result<ItemOffset, Error> {
585 let offset = self.tm.offset.ok_or(FE::RequiresOffset)?;
586 Ok(ItemOffset::new(offset, false, true, false))
587 }
588
589 fn fmt_offset_colon(&self) -> Result<ItemOffset, Error> {
591 let offset = self.tm.offset.ok_or(FE::RequiresOffset)?;
592 Ok(ItemOffset::new(offset, true, true, false))
593 }
594
595 fn fmt_offset_colon2(&self) -> Result<ItemOffset, Error> {
597 let offset = self.tm.offset.ok_or(FE::RequiresOffset)?;
598 Ok(ItemOffset::new(offset, true, true, true))
599 }
600
601 fn fmt_offset_colon3(&self) -> Result<ItemOffset, Error> {
603 let offset = self.tm.offset.ok_or(FE::RequiresOffset)?;
604 Ok(ItemOffset::new(offset, true, false, false))
605 }
606
607 fn fmt_second(&self) -> Result<ItemInteger, Error> {
609 let second = self.tm.second().ok_or(FE::RequiresTime)?;
610 Ok(ItemInteger::new(b'0', 2, second))
611 }
612
613 fn fmt_timestamp(&self) -> Result<ItemInteger, Error> {
615 let timestamp =
616 self.tm.to_timestamp().map_err(|_| FE::RequiresInstant)?;
617 Ok(ItemInteger::new(b' ', 0, timestamp.as_second()))
618 }
619
620 fn fmt_fractional(&self, ext: &Extension) -> Result<Item, Error> {
622 let subsec = self.tm.subsec.ok_or(FE::RequiresTime)?;
623 let subsec = i32::from(subsec).unsigned_abs();
624 if ext.width == Some(0) {
631 return Err(Error::from(FE::ZeroPrecisionFloat));
632 }
633 if subsec == 0 && ext.width.is_none() {
634 return Ok(Item::String(ItemString::new(Case::AsIs, "0")));
635 }
636 Ok(Item::Fraction(ItemFraction {
637 width: ext.width,
638 dot: false,
639 subsec,
640 }))
641 }
642
643 fn fmt_dot_fractional(&self, ext: &Extension) -> Result<Item, Error> {
645 let Some(subsec) = self.tm.subsec else {
646 return Ok(Item::AlreadyFormatted);
647 };
648 let subsec = i32::from(subsec).unsigned_abs();
649 if subsec == 0 && ext.width.is_none() || ext.width == Some(0) {
650 return Ok(Item::AlreadyFormatted);
651 }
652 Ok(Item::Fraction(ItemFraction {
653 width: ext.width,
654 dot: true,
655 subsec,
656 }))
657 }
658
659 fn fmt_nanoseconds(&self, ext: &Extension) -> Result<Item, Error> {
661 let subsec = self.tm.subsec.ok_or(FE::RequiresTime)?;
662 if ext.width == Some(0) {
663 return Err(Error::from(FE::ZeroPrecisionNano));
664 }
665 let subsec = i32::from(subsec).unsigned_abs();
666 if ext.width.is_none() {
669 return Ok(Item::Fraction(ItemFraction {
670 width: Some(9),
671 dot: false,
672 subsec,
673 }));
674 }
675 Ok(Item::Fraction(ItemFraction {
676 width: ext.width,
677 dot: false,
678 subsec,
679 }))
680 }
681
682 fn fmt_tzabbrev(&mut self, ext: &Extension) -> Result<Item, Error> {
684 let tz = self.tm.tz.as_ref().ok_or(FE::RequiresTimeZone)?;
685 let ts = self.tm.to_timestamp().map_err(|_| FE::RequiresInstant)?;
686 let oinfo = tz.to_offset_info(ts);
687 ext.write_str(Case::Upper, oinfo.abbreviation(), self.wtr)?;
688 Ok(Item::AlreadyFormatted)
689 }
690
691 fn fmt_weekday_full(&self) -> Result<ItemString, Error> {
693 let weekday = self
694 .tm
695 .weekday
696 .or_else(|| self.tm.to_date().ok().map(|d| d.weekday()))
697 .ok_or(FE::RequiresDate)?;
698 Ok(ItemString::new(Case::AsIs, weekday_name_full(weekday)))
699 }
700
701 fn fmt_weekday_abbrev(&self) -> Result<ItemString, Error> {
703 let weekday = self
704 .tm
705 .weekday
706 .or_else(|| self.tm.to_date().ok().map(|d| d.weekday()))
707 .ok_or(FE::RequiresDate)?;
708 Ok(ItemString::new(Case::AsIs, weekday_name_abbrev(weekday)))
709 }
710
711 fn fmt_weekday_mon(&self) -> Result<ItemInteger, Error> {
713 let weekday = self
714 .tm
715 .weekday
716 .or_else(|| self.tm.to_date().ok().map(|d| d.weekday()))
717 .ok_or(FE::RequiresDate)?;
718 Ok(ItemInteger::new(b' ', 0, weekday.to_monday_one_offset()))
719 }
720
721 fn fmt_weekday_sun(&self) -> Result<ItemInteger, Error> {
723 let weekday = self
724 .tm
725 .weekday
726 .or_else(|| self.tm.to_date().ok().map(|d| d.weekday()))
727 .ok_or(FE::RequiresDate)?;
728 Ok(ItemInteger::new(b' ', 0, weekday.to_sunday_zero_offset()))
729 }
730
731 fn fmt_week_sun(&self) -> Result<ItemInteger, Error> {
733 if let Some(weeknum) = self.tm.week_sun {
735 return Ok(ItemInteger::new(b'0', 2, weeknum));
736 }
737 let day = self
738 .tm
739 .day_of_year()
740 .or_else(|| self.tm.to_date().ok().map(|d| d.day_of_year()))
741 .ok_or(FE::RequiresDate)?;
742 let weekday = self
743 .tm
744 .weekday
745 .or_else(|| self.tm.to_date().ok().map(|d| d.weekday()))
746 .ok_or(FE::RequiresDate)?
747 .to_sunday_zero_offset();
748 let weeknum = (day + 6 - i16::from(weekday)) / 7;
754 Ok(ItemInteger::new(b'0', 2, weeknum))
755 }
756
757 fn fmt_week_iso(&self) -> Result<ItemInteger, Error> {
759 let weeknum = self
760 .tm
761 .iso_week()
762 .or_else(|| {
763 self.tm.to_date().ok().map(|d| d.iso_week_date().week())
764 })
765 .ok_or(FE::RequiresDate)?;
766 Ok(ItemInteger::new(b'0', 2, weeknum))
767 }
768
769 fn fmt_week_mon(&self) -> Result<ItemInteger, Error> {
771 if let Some(weeknum) = self.tm.week_mon {
773 return Ok(ItemInteger::new(b'0', 2, weeknum));
774 }
775 let day = self
776 .tm
777 .day_of_year()
778 .or_else(|| self.tm.to_date().ok().map(|d| d.day_of_year()))
779 .ok_or(FE::RequiresDate)?;
780 let weekday = self
781 .tm
782 .weekday
783 .or_else(|| self.tm.to_date().ok().map(|d| d.weekday()))
784 .ok_or(FE::RequiresDate)?
785 .to_sunday_zero_offset();
786 let weeknum = (day + 6 - ((i16::from(weekday) + 6) % 7)) / 7;
792 Ok(ItemInteger::new(b'0', 2, weeknum))
793 }
794
795 fn fmt_year(&self) -> Result<ItemInteger, Error> {
797 let year = self
798 .tm
799 .year()
800 .or_else(|| self.tm.to_date().ok().map(|d| d.year()))
801 .ok_or(FE::RequiresDate)?;
802 Ok(ItemInteger::new(b'0', 4, year))
803 }
804
805 fn fmt_year2(&self) -> Result<ItemInteger, Error> {
807 let year = self
808 .tm
809 .year()
810 .or_else(|| self.tm.to_date().ok().map(|d| d.year()))
811 .ok_or(FE::RequiresDate)?;
812 let year = year % 100;
813 Ok(ItemInteger::new(b'0', 2, year))
814 }
815
816 fn fmt_century(&self) -> Result<ItemInteger, Error> {
818 let year = self
819 .tm
820 .year()
821 .or_else(|| self.tm.to_date().ok().map(|d| d.year()))
822 .ok_or(FE::RequiresDate)?;
823 let century = year / 100;
824 Ok(ItemInteger::new(b' ', 0, century))
825 }
826
827 fn fmt_iso_week_year(&self) -> Result<ItemInteger, Error> {
829 let year = self
830 .tm
831 .iso_week_year()
832 .or_else(|| {
833 self.tm.to_date().ok().map(|d| d.iso_week_date().year())
834 })
835 .ok_or(FE::RequiresDate)?;
836 Ok(ItemInteger::new(b'0', 4, year))
837 }
838
839 fn fmt_iso_week_year2(&self) -> Result<ItemInteger, Error> {
841 let year = self
842 .tm
843 .iso_week_year()
844 .or_else(|| {
845 self.tm.to_date().ok().map(|d| d.iso_week_date().year())
846 })
847 .ok_or(FE::RequiresDate)?;
848 let year = year % 100;
849 Ok(ItemInteger::new(b'0', 2, year))
850 }
851
852 fn fmt_quarter(&self) -> Result<ItemInteger, Error> {
854 let month = self
855 .tm
856 .month()
857 .or_else(|| self.tm.to_date().ok().map(|d| d.month()))
858 .ok_or(FE::RequiresDate)?;
859 let quarter = match month {
860 1..=3 => 1,
861 4..=6 => 2,
862 7..=9 => 3,
863 10..=12 => 4,
864 _ => unreachable!(),
865 };
866 Ok(ItemInteger::new(b'0', 0, quarter))
867 }
868
869 fn fmt_day_of_year(&self) -> Result<ItemInteger, Error> {
871 let day = self
872 .tm
873 .day_of_year()
874 .or_else(|| self.tm.to_date().ok().map(|d| d.day_of_year()))
875 .ok_or(FE::RequiresDate)?;
876 Ok(ItemInteger::new(b'0', 3, day))
877 }
878
879 fn fmt_literal(&self, literal: &'static str) -> Result<ItemString, Error> {
881 Ok(ItemString::new(Case::AsIs, literal))
882 }
883
884 fn fmt_datetime(&mut self, ext: &Extension) -> Result<Item, Error> {
886 self.config.custom.format_datetime(
887 self.config,
888 ext,
889 self.tm,
890 self.wtr,
891 )?;
892 Ok(Item::AlreadyFormatted)
893 }
894
895 fn fmt_date(&mut self, ext: &Extension) -> Result<Item, Error> {
897 self.config.custom.format_date(self.config, ext, self.tm, self.wtr)?;
898 Ok(Item::AlreadyFormatted)
899 }
900
901 fn fmt_time(&mut self, ext: &Extension) -> Result<Item, Error> {
903 self.config.custom.format_time(self.config, ext, self.tm, self.wtr)?;
904 Ok(Item::AlreadyFormatted)
905 }
906
907 fn fmt_12hour_time(&mut self, ext: &Extension) -> Result<Item, Error> {
909 self.config.custom.format_12hour_time(
910 self.config,
911 ext,
912 self.tm,
913 self.wtr,
914 )?;
915 Ok(Item::AlreadyFormatted)
916 }
917}
918
919#[cfg_attr(feature = "perf-inline", inline(always))]
931fn write_offset(
932 offset: Offset,
933 colon: bool,
934 minute: bool,
935 second: bool,
936 wtr: &mut BorrowedWriter<'_, '_, '_>,
937) -> Result<(), Error> {
938 let total_seconds = offset.seconds().unsigned_abs();
939 let hours = (total_seconds / (60 * 60)) as u8;
940 let minutes = ((total_seconds / 60) % 60) as u8;
941 let seconds = (total_seconds % 60) as u8;
942
943 wtr.write_ascii_char(if offset.is_negative() { b'-' } else { b'+' })?;
944 wtr.write_int_pad2(hours)?;
945 if minute || minutes != 0 || seconds != 0 {
946 if colon {
947 wtr.write_ascii_char(b':')?;
948 }
949 wtr.write_int_pad2(minutes)?;
950 if second || seconds != 0 {
951 if colon {
952 wtr.write_ascii_char(b':')?;
953 }
954 wtr.write_int_pad2(seconds)?;
955 }
956 }
957 Ok(())
958}
959
960impl Extension {
961 #[cfg_attr(feature = "perf-inline", inline(always))]
964 fn write_str(
965 &self,
966 default: Case,
967 string: &str,
968 wtr: &mut BorrowedWriter<'_, '_, '_>,
969 ) -> Result<(), Error> {
970 if self.flag.is_none() && matches!(default, Case::AsIs) {
971 return wtr.write_str(string);
972 }
973 self.write_str_cold(default, string, wtr)
974 }
975
976 #[cold]
977 #[inline(never)]
978 fn write_str_cold(
979 &self,
980 default: Case,
981 string: &str,
982 wtr: &mut BorrowedWriter<'_, '_, '_>,
983 ) -> Result<(), Error> {
984 let case = match self.flag {
985 Some(Flag::Uppercase) => Case::Upper,
986 Some(Flag::Swapcase) => default.swap(),
987 _ => default,
988 };
989 match case {
990 Case::AsIs => {
991 wtr.write_str(string)?;
992 }
993 Case::Upper | Case::Lower => {
994 for ch in string.chars() {
995 wtr.write_char(if matches!(case, Case::Upper) {
996 ch.to_ascii_uppercase()
997 } else {
998 ch.to_ascii_lowercase()
999 })?;
1000 }
1001 }
1002 }
1003 Ok(())
1004 }
1005
1006 #[cfg_attr(feature = "perf-inline", inline(always))]
1009 fn write_int(
1010 &self,
1011 pad_byte: u8,
1012 pad_width: u8,
1013 number: impl Into<i64>,
1014 wtr: &mut BorrowedWriter<'_, '_, '_>,
1015 ) -> Result<(), Error> {
1016 let number = number.into();
1017 let pad_byte = match self.flag {
1018 Some(Flag::PadZero) => b'0',
1019 Some(Flag::PadSpace) => b' ',
1020 _ => pad_byte,
1021 };
1022 let pad_width = if matches!(self.flag, Some(Flag::NoPad)) {
1023 0
1024 } else {
1025 self.width.unwrap_or(pad_width)
1026 };
1027 if number < 0 {
1028 if pad_width > 0 {
1033 return Self::write_negative_int(
1034 pad_byte,
1035 pad_width,
1036 number.unsigned_abs(),
1037 wtr,
1038 );
1039 }
1040 wtr.write_ascii_char(b'-')?;
1041 }
1042 let number = number.unsigned_abs();
1043 match (pad_byte, pad_width) {
1044 (b'0', 2) if number <= 99 => wtr.write_int_pad2(number),
1045 (b' ', 2) if number <= 99 => wtr.write_int_pad2_space(number),
1046 (b'0', 4) if number <= 9999 => wtr.write_int_pad4(number),
1047 _ => wtr.write_int_pad(number, pad_byte, pad_width),
1048 }
1049 }
1050
1051 #[cold]
1054 #[inline(never)]
1055 fn write_negative_int(
1056 pad_byte: u8,
1057 pad_width: u8,
1058 number: u64,
1059 wtr: &mut BorrowedWriter<'_, '_, '_>,
1060 ) -> Result<(), Error> {
1061 let mut pad_width = pad_width.saturating_sub(1);
1064 if pad_byte == b' ' {
1070 let d = 1 + number.checked_ilog10().unwrap_or(0) as u8;
1071 for _ in 0..pad_width.saturating_sub(d) {
1072 wtr.write_ascii_char(b' ')?;
1073 }
1074 pad_width = 0;
1079 }
1080 wtr.write_ascii_char(b'-')?;
1081 wtr.write_int_pad(number, pad_byte, pad_width)
1082 }
1083}
1084
1085#[derive(Clone, Copy, Debug)]
1087enum Case {
1088 AsIs,
1089 Upper,
1090 Lower,
1091}
1092
1093impl Case {
1094 fn swap(self) -> Case {
1096 match self {
1097 Case::AsIs => Case::AsIs,
1098 Case::Upper => Case::Lower,
1099 Case::Lower => Case::Upper,
1100 }
1101 }
1102}
1103
1104#[cfg(feature = "alloc")]
1105#[cfg(test)]
1106mod tests {
1107 use crate::{
1108 civil::{date, time, Date, DateTime, Time},
1109 fmt::strtime::{format, BrokenDownTime, Config, PosixCustom},
1110 tz::Offset,
1111 Timestamp, Zoned,
1112 };
1113
1114 #[test]
1115 fn ok_format_american_date() {
1116 let f = |fmt: &str, date: Date| format(fmt, date).unwrap();
1117
1118 insta::assert_snapshot!(f("%D", date(2024, 7, 9)), @"07/09/24");
1119 insta::assert_snapshot!(f("%-D", date(2024, 7, 9)), @"07/09/24");
1120 insta::assert_snapshot!(f("%3D", date(2024, 7, 9)), @"07/09/24");
1121 insta::assert_snapshot!(f("%03D", date(2024, 7, 9)), @"07/09/24");
1122 }
1123
1124 #[test]
1125 fn ok_format_ampm() {
1126 let f = |fmt: &str, time: Time| format(fmt, time).unwrap();
1127
1128 insta::assert_snapshot!(f("%H%P", time(9, 0, 0, 0)), @"09am");
1129 insta::assert_snapshot!(f("%H%P", time(11, 0, 0, 0)), @"11am");
1130 insta::assert_snapshot!(f("%H%P", time(23, 0, 0, 0)), @"23pm");
1131 insta::assert_snapshot!(f("%H%P", time(0, 0, 0, 0)), @"00am");
1132
1133 insta::assert_snapshot!(f("%H%p", time(9, 0, 0, 0)), @"09AM");
1134 insta::assert_snapshot!(f("%H%p", time(11, 0, 0, 0)), @"11AM");
1135 insta::assert_snapshot!(f("%H%p", time(23, 0, 0, 0)), @"23PM");
1136 insta::assert_snapshot!(f("%H%p", time(0, 0, 0, 0)), @"00AM");
1137
1138 insta::assert_snapshot!(f("%H%#p", time(9, 0, 0, 0)), @"09am");
1139 }
1140
1141 #[test]
1142 fn ok_format_clock() {
1143 let f = |fmt: &str, time: Time| format(fmt, time).unwrap();
1144
1145 insta::assert_snapshot!(f("%R", time(23, 59, 8, 0)), @"23:59");
1146 insta::assert_snapshot!(f("%T", time(23, 59, 8, 0)), @"23:59:08");
1147 }
1148
1149 #[test]
1150 fn ok_format_day() {
1151 let f = |fmt: &str, date: Date| format(fmt, date).unwrap();
1152
1153 insta::assert_snapshot!(f("%d", date(2024, 7, 9)), @"09");
1154 insta::assert_snapshot!(f("%0d", date(2024, 7, 9)), @"09");
1155 insta::assert_snapshot!(f("%-d", date(2024, 7, 9)), @"9");
1156 insta::assert_snapshot!(f("%_d", date(2024, 7, 9)), @" 9");
1157
1158 insta::assert_snapshot!(f("%e", date(2024, 7, 9)), @" 9");
1159 insta::assert_snapshot!(f("%0e", date(2024, 7, 9)), @"09");
1160 insta::assert_snapshot!(f("%-e", date(2024, 7, 9)), @"9");
1161 insta::assert_snapshot!(f("%_e", date(2024, 7, 9)), @" 9");
1162 }
1163
1164 #[test]
1165 fn ok_format_iso_date() {
1166 let f = |fmt: &str, date: Date| format(fmt, date).unwrap();
1167
1168 insta::assert_snapshot!(f("%F", date(2024, 7, 9)), @"2024-07-09");
1169 insta::assert_snapshot!(f("%-F", date(2024, 7, 9)), @"2024-07-09");
1170 insta::assert_snapshot!(f("%3F", date(2024, 7, 9)), @"2024-07-09");
1171 insta::assert_snapshot!(f("%03F", date(2024, 7, 9)), @"2024-07-09");
1172 }
1173
1174 #[test]
1175 fn ok_format_hour() {
1176 let f = |fmt: &str, time: Time| format(fmt, time).unwrap();
1177
1178 insta::assert_snapshot!(f("%H", time(9, 0, 0, 0)), @"09");
1179 insta::assert_snapshot!(f("%H", time(11, 0, 0, 0)), @"11");
1180 insta::assert_snapshot!(f("%H", time(23, 0, 0, 0)), @"23");
1181 insta::assert_snapshot!(f("%H", time(0, 0, 0, 0)), @"00");
1182
1183 insta::assert_snapshot!(f("%I", time(9, 0, 0, 0)), @"09");
1184 insta::assert_snapshot!(f("%I", time(11, 0, 0, 0)), @"11");
1185 insta::assert_snapshot!(f("%I", time(23, 0, 0, 0)), @"11");
1186 insta::assert_snapshot!(f("%I", time(0, 0, 0, 0)), @"12");
1187
1188 insta::assert_snapshot!(f("%k", time(9, 0, 0, 0)), @" 9");
1189 insta::assert_snapshot!(f("%k", time(11, 0, 0, 0)), @"11");
1190 insta::assert_snapshot!(f("%k", time(23, 0, 0, 0)), @"23");
1191 insta::assert_snapshot!(f("%k", time(0, 0, 0, 0)), @" 0");
1192
1193 insta::assert_snapshot!(f("%l", time(9, 0, 0, 0)), @" 9");
1194 insta::assert_snapshot!(f("%l", time(11, 0, 0, 0)), @"11");
1195 insta::assert_snapshot!(f("%l", time(23, 0, 0, 0)), @"11");
1196 insta::assert_snapshot!(f("%l", time(0, 0, 0, 0)), @"12");
1197 }
1198
1199 #[test]
1200 fn ok_format_minute() {
1201 let f = |fmt: &str, time: Time| format(fmt, time).unwrap();
1202
1203 insta::assert_snapshot!(f("%M", time(0, 9, 0, 0)), @"09");
1204 insta::assert_snapshot!(f("%M", time(0, 11, 0, 0)), @"11");
1205 insta::assert_snapshot!(f("%M", time(0, 23, 0, 0)), @"23");
1206 insta::assert_snapshot!(f("%M", time(0, 0, 0, 0)), @"00");
1207 }
1208
1209 #[test]
1210 fn ok_format_month() {
1211 let f = |fmt: &str, date: Date| format(fmt, date).unwrap();
1212
1213 insta::assert_snapshot!(f("%m", date(2024, 7, 14)), @"07");
1214 insta::assert_snapshot!(f("%m", date(2024, 12, 14)), @"12");
1215 insta::assert_snapshot!(f("%0m", date(2024, 7, 14)), @"07");
1216 insta::assert_snapshot!(f("%0m", date(2024, 12, 14)), @"12");
1217 insta::assert_snapshot!(f("%-m", date(2024, 7, 14)), @"7");
1218 insta::assert_snapshot!(f("%-m", date(2024, 12, 14)), @"12");
1219 insta::assert_snapshot!(f("%_m", date(2024, 7, 14)), @" 7");
1220 insta::assert_snapshot!(f("%_m", date(2024, 12, 14)), @"12");
1221 }
1222
1223 #[test]
1224 fn ok_format_month_name() {
1225 let f = |fmt: &str, date: Date| format(fmt, date).unwrap();
1226
1227 insta::assert_snapshot!(f("%B", date(2024, 7, 14)), @"July");
1228 insta::assert_snapshot!(f("%b", date(2024, 7, 14)), @"Jul");
1229 insta::assert_snapshot!(f("%h", date(2024, 7, 14)), @"Jul");
1230
1231 insta::assert_snapshot!(f("%#B", date(2024, 7, 14)), @"July");
1232 insta::assert_snapshot!(f("%^B", date(2024, 7, 14)), @"JULY");
1233 }
1234
1235 #[test]
1236 fn ok_format_offset_from_zoned() {
1237 if crate::tz::db().is_definitively_empty() {
1238 return;
1239 }
1240
1241 let f = |fmt: &str, zdt: &Zoned| format(fmt, zdt).unwrap();
1242
1243 let zdt = date(2024, 7, 14)
1244 .at(22, 24, 0, 0)
1245 .in_tz("America/New_York")
1246 .unwrap();
1247 insta::assert_snapshot!(f("%z", &zdt), @"-0400");
1248 insta::assert_snapshot!(f("%:z", &zdt), @"-04:00");
1249
1250 let zdt = zdt.checked_add(crate::Span::new().months(5)).unwrap();
1251 insta::assert_snapshot!(f("%z", &zdt), @"-0500");
1252 insta::assert_snapshot!(f("%:z", &zdt), @"-05:00");
1253 }
1254
1255 #[test]
1256 fn ok_format_offset_plain() {
1257 let o = |h: i8, m: i8, s: i8| -> Offset { Offset::hms(h, m, s) };
1258 let f = |fmt: &str, offset: Offset| {
1259 let mut tm = BrokenDownTime::default();
1260 tm.set_offset(Some(offset));
1261 tm.to_string(fmt).unwrap()
1262 };
1263
1264 insta::assert_snapshot!(f("%z", o(0, 0, 0)), @"+0000");
1265 insta::assert_snapshot!(f("%:z", o(0, 0, 0)), @"+00:00");
1266 insta::assert_snapshot!(f("%::z", o(0, 0, 0)), @"+00:00:00");
1267 insta::assert_snapshot!(f("%:::z", o(0, 0, 0)), @"+00");
1268
1269 insta::assert_snapshot!(f("%z", -o(4, 0, 0)), @"-0400");
1270 insta::assert_snapshot!(f("%:z", -o(4, 0, 0)), @"-04:00");
1271 insta::assert_snapshot!(f("%::z", -o(4, 0, 0)), @"-04:00:00");
1272 insta::assert_snapshot!(f("%:::z", -o(4, 0, 0)), @"-04");
1273
1274 insta::assert_snapshot!(f("%z", o(5, 30, 0)), @"+0530");
1275 insta::assert_snapshot!(f("%:z", o(5, 30, 0)), @"+05:30");
1276 insta::assert_snapshot!(f("%::z", o(5, 30, 0)), @"+05:30:00");
1277 insta::assert_snapshot!(f("%:::z", o(5, 30, 0)), @"+05:30");
1278
1279 insta::assert_snapshot!(f("%z", o(5, 30, 15)), @"+053015");
1280 insta::assert_snapshot!(f("%:z", o(5, 30, 15)), @"+05:30:15");
1281 insta::assert_snapshot!(f("%::z", o(5, 30, 15)), @"+05:30:15");
1282 insta::assert_snapshot!(f("%:::z", o(5, 30, 15)), @"+05:30:15");
1283
1284 insta::assert_snapshot!(f("%z", o(5, 0, 15)), @"+050015");
1285 insta::assert_snapshot!(f("%:z", o(5, 0, 15)), @"+05:00:15");
1286 insta::assert_snapshot!(f("%::z", o(5, 0, 15)), @"+05:00:15");
1287 insta::assert_snapshot!(f("%:::z", o(5, 0, 15)), @"+05:00:15");
1288 }
1289
1290 #[test]
1291 fn ok_format_second() {
1292 let f = |fmt: &str, time: Time| format(fmt, time).unwrap();
1293
1294 insta::assert_snapshot!(f("%S", time(0, 0, 9, 0)), @"09");
1295 insta::assert_snapshot!(f("%S", time(0, 0, 11, 0)), @"11");
1296 insta::assert_snapshot!(f("%S", time(0, 0, 23, 0)), @"23");
1297 insta::assert_snapshot!(f("%S", time(0, 0, 0, 0)), @"00");
1298 }
1299
1300 #[test]
1301 fn ok_format_subsec_nanosecond() {
1302 let f = |fmt: &str, time: Time| format(fmt, time).unwrap();
1303 let mk = |subsec| time(0, 0, 0, subsec);
1304
1305 insta::assert_snapshot!(f("%f", mk(123_000_000)), @"123");
1306 insta::assert_snapshot!(f("%f", mk(0)), @"0");
1307 insta::assert_snapshot!(f("%3f", mk(0)), @"000");
1308 insta::assert_snapshot!(f("%3f", mk(123_000_000)), @"123");
1309 insta::assert_snapshot!(f("%6f", mk(123_000_000)), @"123000");
1310 insta::assert_snapshot!(f("%9f", mk(123_000_000)), @"123000000");
1311 insta::assert_snapshot!(f("%255f", mk(123_000_000)), @"123000000");
1312
1313 insta::assert_snapshot!(f("%.f", mk(123_000_000)), @".123");
1314 insta::assert_snapshot!(f("%.f", mk(0)), @"");
1315 insta::assert_snapshot!(f("%3.f", mk(0)), @"");
1316 insta::assert_snapshot!(f("%.3f", mk(0)), @".000");
1317 insta::assert_snapshot!(f("%.3f", mk(123_000_000)), @".123");
1318 insta::assert_snapshot!(f("%.6f", mk(123_000_000)), @".123000");
1319 insta::assert_snapshot!(f("%.9f", mk(123_000_000)), @".123000000");
1320 insta::assert_snapshot!(f("%.255f", mk(123_000_000)), @".123000000");
1321
1322 insta::assert_snapshot!(f("%3f", mk(123_456_789)), @"123");
1323 insta::assert_snapshot!(f("%6f", mk(123_456_789)), @"123456");
1324 insta::assert_snapshot!(f("%9f", mk(123_456_789)), @"123456789");
1325
1326 insta::assert_snapshot!(f("%.0f", mk(123_456_789)), @"");
1327 insta::assert_snapshot!(f("%.3f", mk(123_456_789)), @".123");
1328 insta::assert_snapshot!(f("%.6f", mk(123_456_789)), @".123456");
1329 insta::assert_snapshot!(f("%.9f", mk(123_456_789)), @".123456789");
1330
1331 insta::assert_snapshot!(f("%N", mk(123_000_000)), @"123000000");
1332 insta::assert_snapshot!(f("%N", mk(0)), @"000000000");
1333 insta::assert_snapshot!(f("%N", mk(000_123_000)), @"000123000");
1334 insta::assert_snapshot!(f("%3N", mk(0)), @"000");
1335 insta::assert_snapshot!(f("%3N", mk(123_000_000)), @"123");
1336 insta::assert_snapshot!(f("%6N", mk(123_000_000)), @"123000");
1337 insta::assert_snapshot!(f("%9N", mk(123_000_000)), @"123000000");
1338 insta::assert_snapshot!(f("%255N", mk(123_000_000)), @"123000000");
1339 }
1340
1341 #[test]
1342 fn ok_format_tzabbrev() {
1343 if crate::tz::db().is_definitively_empty() {
1344 return;
1345 }
1346
1347 let f = |fmt: &str, zdt: &Zoned| format(fmt, zdt).unwrap();
1348
1349 let zdt = date(2024, 7, 14)
1350 .at(22, 24, 0, 0)
1351 .in_tz("America/New_York")
1352 .unwrap();
1353 insta::assert_snapshot!(f("%Z", &zdt), @"EDT");
1354 insta::assert_snapshot!(f("%^Z", &zdt), @"EDT");
1355 insta::assert_snapshot!(f("%#Z", &zdt), @"edt");
1356
1357 let zdt = zdt.checked_add(crate::Span::new().months(5)).unwrap();
1358 insta::assert_snapshot!(f("%Z", &zdt), @"EST");
1359 }
1360
1361 #[test]
1362 fn ok_format_iana() {
1363 if crate::tz::db().is_definitively_empty() {
1364 return;
1365 }
1366
1367 let f = |fmt: &str, zdt: &Zoned| format(fmt, zdt).unwrap();
1368
1369 let zdt = date(2024, 7, 14)
1370 .at(22, 24, 0, 0)
1371 .in_tz("America/New_York")
1372 .unwrap();
1373 insta::assert_snapshot!(f("%Q", &zdt), @"America/New_York");
1374 insta::assert_snapshot!(f("%:Q", &zdt), @"America/New_York");
1375
1376 let zdt = date(2024, 7, 14)
1377 .at(22, 24, 0, 0)
1378 .to_zoned(crate::tz::offset(-4).to_time_zone())
1379 .unwrap();
1380 insta::assert_snapshot!(f("%Q", &zdt), @"-0400");
1381 insta::assert_snapshot!(f("%:Q", &zdt), @"-04:00");
1382
1383 let zdt = date(2024, 7, 14)
1384 .at(22, 24, 0, 0)
1385 .to_zoned(crate::tz::TimeZone::UTC)
1386 .unwrap();
1387 insta::assert_snapshot!(f("%Q", &zdt), @"UTC");
1388 insta::assert_snapshot!(f("%:Q", &zdt), @"UTC");
1389 }
1390
1391 #[test]
1392 fn ok_format_weekday_name() {
1393 let f = |fmt: &str, date: Date| format(fmt, date).unwrap();
1394
1395 insta::assert_snapshot!(f("%A", date(2024, 7, 14)), @"Sunday");
1396 insta::assert_snapshot!(f("%a", date(2024, 7, 14)), @"Sun");
1397
1398 insta::assert_snapshot!(f("%#A", date(2024, 7, 14)), @"Sunday");
1399 insta::assert_snapshot!(f("%^A", date(2024, 7, 14)), @"SUNDAY");
1400
1401 insta::assert_snapshot!(f("%u", date(2024, 7, 14)), @"7");
1402 insta::assert_snapshot!(f("%w", date(2024, 7, 14)), @"0");
1403 }
1404
1405 #[test]
1406 fn ok_format_year() {
1407 let f = |fmt: &str, date: Date| format(fmt, date).unwrap();
1408
1409 insta::assert_snapshot!(f("%Y", date(2024, 7, 14)), @"2024");
1410 insta::assert_snapshot!(f("%Y", date(24, 7, 14)), @"0024");
1411 insta::assert_snapshot!(f("%Y", date(-24, 7, 14)), @"-024");
1412
1413 insta::assert_snapshot!(f("%C", date(2024, 7, 14)), @"20");
1414 insta::assert_snapshot!(f("%C", date(1815, 7, 14)), @"18");
1415 insta::assert_snapshot!(f("%C", date(915, 7, 14)), @"9");
1416 insta::assert_snapshot!(f("%C", date(1, 7, 14)), @"0");
1417 insta::assert_snapshot!(f("%C", date(0, 7, 14)), @"0");
1418 insta::assert_snapshot!(f("%C", date(-1, 7, 14)), @"0");
1419 insta::assert_snapshot!(f("%C", date(-2024, 7, 14)), @"-20");
1420 insta::assert_snapshot!(f("%C", date(-1815, 7, 14)), @"-18");
1421 insta::assert_snapshot!(f("%C", date(-915, 7, 14)), @"-9");
1422 }
1423
1424 #[test]
1425 fn ok_format_year_negative_padded() {
1426 let f = |fmt: &str, date: Date| format(fmt, date).unwrap();
1427
1428 insta::assert_snapshot!(f("%06Y", date(-2025, 1, 13)), @"-02025");
1429 insta::assert_snapshot!(f("%06Y", date(-25, 1, 13)), @"-00025");
1430 insta::assert_snapshot!(f("%06Y", date(-1, 1, 13)), @"-00001");
1431 insta::assert_snapshot!(f("%08Y", date(-2025, 1, 13)), @"-0002025");
1432 insta::assert_snapshot!(f("%_6Y", date(-2025, 1, 13)), @" -2025");
1433 insta::assert_snapshot!(f("%_6Y", date(-25, 1, 13)), @" -25");
1434 insta::assert_snapshot!(f("%_6Y", date(-1, 1, 13)), @" -1");
1435 insta::assert_snapshot!(f("%_8Y", date(-2025, 1, 13)), @" -2025");
1436 insta::assert_snapshot!(f("%06Y", date(2025, 1, 13)), @"002025");
1437 insta::assert_snapshot!(f("%_6Y", date(2025, 1, 13)), @" 2025");
1438 insta::assert_snapshot!(f("%Y", date(-2025, 1, 13)), @"-2025");
1439 insta::assert_snapshot!(f("%Y", date(-25, 1, 13)), @"-025");
1440 insta::assert_snapshot!(f("%_Y", date(-2025, 1, 13)), @"-2025");
1441 insta::assert_snapshot!(f("%_Y", date(-25, 1, 13)), @" -25");
1442 insta::assert_snapshot!(f("%03Y", date(-2025, 1, 13)), @"-2025");
1443 insta::assert_snapshot!(f("%_3Y", date(-2025, 1, 13)), @"-2025");
1444 insta::assert_snapshot!(f("%-6Y", date(-2025, 1, 13)), @"-2025");
1445 insta::assert_snapshot!(f("%04Y", date(-2025, 1, 13)), @"-2025");
1446 insta::assert_snapshot!(f("%05Y", date(-2025, 1, 13)), @"-2025");
1447 }
1448
1449 #[test]
1450 fn ok_format_default_locale() {
1451 let f = |fmt: &str, date: DateTime| format(fmt, date).unwrap();
1452
1453 insta::assert_snapshot!(
1454 f("%c", date(2024, 7, 14).at(0, 0, 0, 0)),
1455 @"2024 M07 14, Sun 00:00:00",
1456 );
1457 insta::assert_snapshot!(
1458 f("%c", date(24, 7, 14).at(0, 0, 0, 0)),
1459 @"0024 M07 14, Sun 00:00:00",
1460 );
1461 insta::assert_snapshot!(
1462 f("%c", date(-24, 7, 14).at(0, 0, 0, 0)),
1463 @"-024 M07 14, Wed 00:00:00",
1464 );
1465 insta::assert_snapshot!(
1466 f("%c", date(2024, 7, 14).at(17, 31, 59, 123_456_789)),
1467 @"2024 M07 14, Sun 17:31:59",
1468 );
1469
1470 insta::assert_snapshot!(
1471 f("%r", date(2024, 7, 14).at(8, 30, 0, 0)),
1472 @"8:30:00 AM",
1473 );
1474 insta::assert_snapshot!(
1475 f("%r", date(2024, 7, 14).at(17, 31, 59, 123_456_789)),
1476 @"5:31:59 PM",
1477 );
1478
1479 insta::assert_snapshot!(
1480 f("%x", date(2024, 7, 14).at(0, 0, 0, 0)),
1481 @"2024 M07 14",
1482 );
1483
1484 insta::assert_snapshot!(
1485 f("%X", date(2024, 7, 14).at(8, 30, 0, 0)),
1486 @"08:30:00",
1487 );
1488 insta::assert_snapshot!(
1489 f("%X", date(2024, 7, 14).at(17, 31, 59, 123_456_789)),
1490 @"17:31:59",
1491 );
1492 }
1493
1494 #[test]
1495 fn ok_format_compound_uppercase() {
1496 let f = |fmt: &str, date: DateTime| format(fmt, date).unwrap();
1497
1498 insta::assert_snapshot!(
1499 f("%^c", date(2024, 7, 14).at(0, 0, 0, 0)),
1500 @"2024 M07 14, SUN 00:00:00",
1501 );
1502 insta::assert_snapshot!(
1503 f("%^x", date(2024, 7, 14).at(0, 0, 0, 0)),
1504 @"2024 M07 14",
1505 );
1506 insta::assert_snapshot!(
1507 f("%^X", date(2024, 7, 14).at(8, 30, 0, 0)),
1508 @"08:30:00",
1509 );
1510 insta::assert_snapshot!(
1511 f("%^r", date(2024, 7, 14).at(8, 30, 0, 0)),
1512 @"8:30:00 AM",
1513 );
1514 }
1515
1516 #[test]
1517 fn ok_format_posix_locale() {
1518 let f = |fmt: &str, date: DateTime| {
1519 let config = Config::new().custom(PosixCustom::default());
1520 let tm = BrokenDownTime::from(date);
1521 tm.to_string_with_config(&config, fmt).unwrap()
1522 };
1523
1524 insta::assert_snapshot!(
1525 f("%c", date(2024, 7, 14).at(0, 0, 0, 0)),
1526 @"Sun Jul 14 00:00:00 2024",
1527 );
1528 insta::assert_snapshot!(
1529 f("%c", date(24, 7, 14).at(0, 0, 0, 0)),
1530 @"Sun Jul 14 00:00:00 0024",
1531 );
1532 insta::assert_snapshot!(
1533 f("%c", date(-24, 7, 14).at(0, 0, 0, 0)),
1534 @"Wed Jul 14 00:00:00 -024",
1535 );
1536 insta::assert_snapshot!(
1537 f("%c", date(2024, 7, 14).at(17, 31, 59, 123_456_789)),
1538 @"Sun Jul 14 17:31:59 2024",
1539 );
1540
1541 insta::assert_snapshot!(
1542 f("%r", date(2024, 7, 14).at(8, 30, 0, 0)),
1543 @"08:30:00 AM",
1544 );
1545 insta::assert_snapshot!(
1546 f("%r", date(2024, 7, 14).at(17, 31, 59, 123_456_789)),
1547 @"05:31:59 PM",
1548 );
1549
1550 insta::assert_snapshot!(
1551 f("%x", date(2024, 7, 14).at(0, 0, 0, 0)),
1552 @"07/14/24",
1553 );
1554
1555 insta::assert_snapshot!(
1556 f("%X", date(2024, 7, 14).at(8, 30, 0, 0)),
1557 @"08:30:00",
1558 );
1559 insta::assert_snapshot!(
1560 f("%X", date(2024, 7, 14).at(17, 31, 59, 123_456_789)),
1561 @"17:31:59",
1562 );
1563 }
1564
1565 #[test]
1566 fn ok_format_year_2digit() {
1567 let f = |fmt: &str, date: Date| format(fmt, date).unwrap();
1568
1569 insta::assert_snapshot!(f("%y", date(2024, 7, 14)), @"24");
1570 insta::assert_snapshot!(f("%y", date(2001, 7, 14)), @"01");
1571 insta::assert_snapshot!(f("%-y", date(2001, 7, 14)), @"1");
1572 insta::assert_snapshot!(f("%5y", date(2001, 7, 14)), @"00001");
1573 insta::assert_snapshot!(f("%-5y", date(2001, 7, 14)), @"1");
1574 insta::assert_snapshot!(f("%05y", date(2001, 7, 14)), @"00001");
1575 insta::assert_snapshot!(f("%_y", date(2001, 7, 14)), @" 1");
1576 insta::assert_snapshot!(f("%_5y", date(2001, 7, 14)), @" 1");
1577
1578 insta::assert_snapshot!(f("%y", date(1824, 7, 14)), @"24");
1579 insta::assert_snapshot!(f("%g", date(1824, 7, 14)), @"24");
1580 }
1581
1582 #[test]
1583 fn ok_format_iso_week_year() {
1584 let f = |fmt: &str, date: Date| format(fmt, date).unwrap();
1585
1586 insta::assert_snapshot!(f("%G", date(2019, 11, 30)), @"2019");
1587 insta::assert_snapshot!(f("%G", date(19, 11, 30)), @"0019");
1588 insta::assert_snapshot!(f("%G", date(-19, 11, 30)), @"-019");
1589
1590 insta::assert_snapshot!(f("%G", date(2019, 12, 30)), @"2020");
1592 }
1593
1594 #[test]
1595 fn ok_format_week_num() {
1596 let f = |fmt: &str, date: Date| format(fmt, date).unwrap();
1597
1598 insta::assert_snapshot!(f("%U", date(2025, 1, 4)), @"00");
1599 insta::assert_snapshot!(f("%U", date(2025, 1, 5)), @"01");
1600
1601 insta::assert_snapshot!(f("%W", date(2025, 1, 5)), @"00");
1602 insta::assert_snapshot!(f("%W", date(2025, 1, 6)), @"01");
1603 }
1604
1605 #[test]
1606 fn ok_format_timestamp() {
1607 let f = |fmt: &str, ts: Timestamp| format(fmt, ts).unwrap();
1608
1609 let ts = "1970-01-01T00:00Z".parse().unwrap();
1610 insta::assert_snapshot!(f("%s", ts), @"0");
1611 insta::assert_snapshot!(f("%3s", ts), @" 0");
1612 insta::assert_snapshot!(f("%03s", ts), @"000");
1613 insta::assert_snapshot!(f("%2s", ts), @" 0");
1614
1615 let ts = "2025-01-20T13:09-05[US/Eastern]".parse().unwrap();
1616 insta::assert_snapshot!(f("%s", ts), @"1737396540");
1617 }
1618
1619 #[test]
1620 fn ok_format_quarter() {
1621 let f = |fmt: &str, date: Date| format(fmt, date).unwrap();
1622
1623 insta::assert_snapshot!(f("%q", date(2024, 3, 31)), @"1");
1624 insta::assert_snapshot!(f("%q", date(2024, 4, 1)), @"2");
1625 insta::assert_snapshot!(f("%q", date(2024, 7, 14)), @"3");
1626 insta::assert_snapshot!(f("%q", date(2024, 12, 31)), @"4");
1627
1628 insta::assert_snapshot!(f("%2q", date(2024, 3, 31)), @"01");
1629 insta::assert_snapshot!(f("%02q", date(2024, 3, 31)), @"01");
1630 insta::assert_snapshot!(f("%_2q", date(2024, 3, 31)), @" 1");
1631 }
1632
1633 #[test]
1634 fn err_format_subsec_nanosecond() {
1635 let f = |fmt: &str, time: Time| format(fmt, time).unwrap_err();
1636 let mk = |subsec| time(0, 0, 0, subsec);
1637
1638 insta::assert_snapshot!(
1639 f("%00f", mk(123_456_789)),
1640 @"strftime formatting failed: %f failed: zero precision with %f is not allowed",
1641 );
1642 }
1643
1644 #[test]
1645 fn err_format_timestamp() {
1646 let f = |fmt: &str, dt: DateTime| format(fmt, dt).unwrap_err();
1647
1648 let dt = date(2025, 1, 20).at(13, 9, 0, 0);
1649 insta::assert_snapshot!(
1650 f("%s", dt),
1651 @"strftime formatting failed: %s failed: requires instant (a timestamp or a date, time and offset)",
1652 );
1653 }
1654
1655 #[test]
1656 fn err_invalid_utf8() {
1657 let d = date(2025, 1, 20);
1658 insta::assert_snapshot!(
1659 format("abc %F xyz", d).unwrap(),
1660 @"abc 2025-01-20 xyz",
1661 );
1662 insta::assert_snapshot!(
1663 format(b"abc %F \xFFxyz", d).unwrap_err(),
1664 @"strftime formatting failed: invalid format string, it must be valid UTF-8",
1665 );
1666 }
1667
1668 #[test]
1669 fn lenient() {
1670 fn f(
1671 fmt: impl AsRef<[u8]>,
1672 tm: impl Into<BrokenDownTime>,
1673 ) -> alloc::string::String {
1674 let config = Config::new().lenient(true);
1675 tm.into().to_string_with_config(&config, fmt).unwrap()
1676 }
1677
1678 insta::assert_snapshot!(f("%z", date(2024, 7, 9)), @"%z");
1679 insta::assert_snapshot!(f("%:z", date(2024, 7, 9)), @"%:z");
1680 insta::assert_snapshot!(f("%Q", date(2024, 7, 9)), @"%Q");
1681 insta::assert_snapshot!(f("%+", date(2024, 7, 9)), @"%+");
1682 insta::assert_snapshot!(f("%F", date(2024, 7, 9)), @"2024-07-09");
1683 insta::assert_snapshot!(f("%T", date(2024, 7, 9)), @"%T");
1684 insta::assert_snapshot!(f("%F%", date(2024, 7, 9)), @"2024-07-09%");
1685 insta::assert_snapshot!(
1686 f(b"abc %F \xFFxyz", date(2024, 7, 9)),
1687 @"abc 2024-07-09 �xyz",
1688 );
1689 insta::assert_snapshot!(
1695 f(b"%F\xF0\x9F\x92%Y", date(2024, 7, 9)),
1696 @"2024-07-09�2024",
1697 );
1698 insta::assert_snapshot!(
1702 f(b"%F\xFF\xFF\xFF%Y", date(2024, 7, 9)),
1703 @"2024-07-09���2024",
1704 );
1705 }
1706
1707 #[test]
1713 fn regression_timestamp_2s() {
1714 use alloc::string::ToString;
1715
1716 let ts: Timestamp = "2024-07-09T20:24:00Z".parse().unwrap();
1717 assert_eq!(ts.strftime("%2s").to_string(), "1720556640");
1718
1719 let ts: Timestamp = "2024-07-09T20:24:00Z".parse().unwrap();
1720 assert_eq!(ts.strftime("%_2s").to_string(), "1720556640");
1721
1722 let ts: Timestamp = "2024-07-09T20:24:00Z".parse().unwrap();
1723 assert_eq!(ts.strftime("%02s").to_string(), "1720556640");
1724
1725 let ts: Timestamp = "2024-07-09T20:24:00Z".parse().unwrap();
1726 assert_eq!(ts.strftime("%04s").to_string(), "1720556640");
1727 }
1728}