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