| 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 | 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 | // fast path |
| 136 | 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(()), // for future expansion |
| 199 | _ => Err(fmt::Error), // insufficient arguments for given format |
| 200 | } |
| 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), // insufficient arguments for given format |
| 316 | } |
| 317 | } |
| 318 | } |
| 319 | |
| 320 | #[cfg (feature = "alloc" )] |
| 321 | impl<'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 = String::new(); |
| 324 | self.format(&mut result)?; |
| 325 | f.pad(&result) |
| 326 | } |
| 327 | } |
| 328 | |
| 329 | /// Tries to format given arguments with given formatting items. |
| 330 | /// Internally used by `DelayedFormat`. |
| 331 | #[cfg (feature = "alloc" )] |
| 332 | #[deprecated (since = "0.4.32" , note = "Use DelayedFormat::fmt instead" )] |
| 333 | pub 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 |
| 340 | where |
| 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 | /// Formats single formatting item |
| 355 | #[cfg (feature = "alloc" )] |
| 356 | #[deprecated (since = "0.4.32" , note = "Use DelayedFormat::fmt instead" )] |
| 357 | pub 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" ))] |
| 375 | impl OffsetFormat { |
| 376 | /// Writes an offset from UTC with the format defined by `self`. |
| 377 | 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 | // Minutes and seconds are simply truncated |
| 391 | hours = (off / 3600) as u8; |
| 392 | OffsetPrecision::Hours |
| 393 | } |
| 394 | OffsetPrecision::Minutes | OffsetPrecision::OptionalMinutes => { |
| 395 | // Round seconds to the nearest minute. |
| 396 | 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 | /// Specific formatting options for seconds. This may be extended in the |
| 455 | /// future, so exhaustive matching in external code is not recommended. |
| 456 | /// |
| 457 | /// See the `TimeZone::to_rfc3339_opts` function for usage. |
| 458 | #[derive (Clone, Copy, Debug, Eq, PartialEq, Hash)] |
| 459 | #[allow (clippy::manual_non_exhaustive)] |
| 460 | pub enum SecondsFormat { |
| 461 | /// Format whole seconds only, with no decimal point nor subseconds. |
| 462 | Secs, |
| 463 | |
| 464 | /// Use fixed 3 subsecond digits. This corresponds to [Fixed::Nanosecond3]. |
| 465 | Millis, |
| 466 | |
| 467 | /// Use fixed 6 subsecond digits. This corresponds to [Fixed::Nanosecond6]. |
| 468 | Micros, |
| 469 | |
| 470 | /// Use fixed 9 subsecond digits. This corresponds to [Fixed::Nanosecond9]. |
| 471 | Nanos, |
| 472 | |
| 473 | /// Automatically select one of `Secs`, `Millis`, `Micros`, or `Nanos` to display all available |
| 474 | /// non-zero sub-second digits. This corresponds to [Fixed::Nanosecond]. |
| 475 | AutoSi, |
| 476 | |
| 477 | // Do not match against this. |
| 478 | #[doc (hidden)] |
| 479 | __NonExhaustive, |
| 480 | } |
| 481 | |
| 482 | /// Writes the date, time and offset to the string. same as `%Y-%m-%dT%H:%M:%S%.f%:z` |
| 483 | #[inline ] |
| 484 | #[cfg (any(feature = "alloc" , feature = "serde" ))] |
| 485 | pub(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 | // ISO 8601 requires the explicit sign for out-of-range years |
| 498 | 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" )] |
| 548 | /// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` |
| 549 | pub(crate) fn write_rfc2822( |
| 550 | w: &mut impl Write, |
| 551 | dt: NaiveDateTime, |
| 552 | off: FixedOffset, |
| 553 | ) -> fmt::Result { |
| 554 | let year = dt.year(); |
| 555 | // RFC2822 is only defined on years 0 through 9999 |
| 556 | 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 | |
| 594 | /// Equivalent to `{:02}` formatting for n < 100. |
| 595 | pub(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: u8 = b'0' + n / 10; |
| 601 | let ones: u8 = 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" )] |
| 608 | mod 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" ); // since 2012 is a leap year |
| 624 | 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 | // non-four-digit years |
| 630 | 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 | // corner cases |
| 649 | 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 | // corner cases |
| 685 | 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 | // a horror of leap second: coming near to you. |
| 705 | 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" ); // not 1341100800, it's intentional. |
| 711 | } |
| 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 | // Item::Literal, odd number of padding bytes. |
| 723 | let percent = datetime.format("%%" ); |
| 724 | assert_eq!(" %" , format!("{:>4}" , percent)); |
| 725 | assert_eq!("% " , format!("{:<4}" , percent)); |
| 726 | assert_eq!(" % " , format!("{:^4}" , percent)); |
| 727 | |
| 728 | // Item::Numeric, custom non-ASCII padding character |
| 729 | 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 | // Item::Fixed |
| 735 | 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 | // [Item::Numeric, Item::Space, Item::Literal, Item::Space, Item::Numeric] |
| 741 | 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 | // Truncated |
| 747 | 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 | // +03:45, -03:30, +11:00, -11:00:22, +02:34:26, -12:34:30, +00:00 |
| 770 | 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 | // `Colons::Maybe` should format the same as `Colons::None` |
| 792 | 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 | } |
| 904 | |