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