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