mirror of
https://github.com/BurntSushi/jiff.git
synced 2025-12-23 08:47:45 +00:00
fmt/strtime: add support for %::z and %:::z
These are also motivated by their presence in GNU date. They are basically just different ways of printing offsets from UTC. We do some light refactoring to DRY up the code a bit, and to make it a little nicer to handle a variable number (up to a fixed size limit) of `:` characters preceding a directive. This is honestly just a giant clusterfuck (who thinks having `%z`, `%:z`, `%::z` and `%:::z` is a nice user experience!?!?), but so is `strptime`/`strftime` I guess. Oh well. Closes #342
This commit is contained in:
parent
8860d97b0b
commit
9f3d5ebd2f
5 changed files with 283 additions and 63 deletions
|
|
@ -13,6 +13,8 @@ Enhancements:
|
|||
Add support for the `%c`, `%r`, `%X` and `%x` conversion specifiers.
|
||||
* [#341](https://github.com/BurntSushi/jiff/issues/341):
|
||||
Add support for `%q` in `jiff::fmt::strtime` (prints quarter of year).
|
||||
* [#342](https://github.com/BurntSushi/jiff/issues/342):
|
||||
Add support for `%::z` and `%:::z` in `jiff::fmt::strtime`.
|
||||
* [#344](https://github.com/BurntSushi/jiff/issues/344):
|
||||
Add support for `%N` in `jiff::fmt::strtime` (alias for `%9f`).
|
||||
|
||||
|
|
|
|||
|
|
@ -290,6 +290,7 @@ impl core::fmt::Debug for Numeric {
|
|||
pub(crate) struct Parser {
|
||||
zulu: bool,
|
||||
require_minute: bool,
|
||||
require_second: bool,
|
||||
subminute: bool,
|
||||
subsecond: bool,
|
||||
colon: Colon,
|
||||
|
|
@ -301,6 +302,7 @@ impl Parser {
|
|||
Parser {
|
||||
zulu: true,
|
||||
require_minute: false,
|
||||
require_second: false,
|
||||
subminute: true,
|
||||
subsecond: true,
|
||||
colon: Colon::Optional,
|
||||
|
|
@ -326,6 +328,16 @@ impl Parser {
|
|||
Parser { require_minute: yes, ..self }
|
||||
}
|
||||
|
||||
/// When enabled, the second component of a time zone offset is required.
|
||||
/// If no seconds (or minutes) are found, then an error is returned.
|
||||
///
|
||||
/// When `subminute` is disabled, this setting has no effect.
|
||||
///
|
||||
/// This is disabled by default.
|
||||
pub(crate) const fn require_second(self, yes: bool) -> Parser {
|
||||
Parser { require_second: yes, ..self }
|
||||
}
|
||||
|
||||
/// When enabled, offsets with precision greater than integral minutes
|
||||
/// are supported. Specifically, when enabled, nanosecond precision is
|
||||
/// supported.
|
||||
|
|
@ -462,7 +474,7 @@ impl Parser {
|
|||
return Err(err!(
|
||||
"parsed hour component of time zone offset from \
|
||||
{original:?}, but could not find required colon \
|
||||
component",
|
||||
separator",
|
||||
));
|
||||
}
|
||||
true
|
||||
|
|
@ -497,7 +509,7 @@ impl Parser {
|
|||
)
|
||||
})?;
|
||||
if !has_minutes {
|
||||
if self.require_minute {
|
||||
if self.require_minute || (self.subminute && self.require_second) {
|
||||
return Err(err!(
|
||||
"parsed hour component of time zone offset from \
|
||||
{original:?}, but could not find required minute \
|
||||
|
|
@ -544,6 +556,13 @@ impl Parser {
|
|||
)
|
||||
})?;
|
||||
if !has_seconds {
|
||||
if self.require_second {
|
||||
return Err(err!(
|
||||
"parsed hour and minute components of time zone offset \
|
||||
from {original:?}, but could not find required second \
|
||||
component",
|
||||
));
|
||||
}
|
||||
return Ok(Parsed { value: numeric, input });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -69,7 +69,15 @@ impl<'c, 'f, 't, 'w, W: Write, L: Custom> Formatter<'c, 'f, 't, 'w, W, L> {
|
|||
b'n' => self.fmt_literal("\n").context("%n failed")?,
|
||||
b'P' => self.fmt_ampm_lower(&ext).context("%P failed")?,
|
||||
b'p' => self.fmt_ampm_upper(&ext).context("%p failed")?,
|
||||
b'Q' => self.fmt_iana_nocolon().context("%Q failed")?,
|
||||
b'Q' => match ext.colons {
|
||||
0 => self.fmt_iana_nocolon().context("%Q failed")?,
|
||||
1 => self.fmt_iana_colon().context("%:Q failed")?,
|
||||
_ => {
|
||||
return Err(err!(
|
||||
"invalid number of `:` in `%Q` directive"
|
||||
))
|
||||
}
|
||||
},
|
||||
b'q' => self.fmt_quarter(&ext).context("%q failed")?,
|
||||
b'R' => self.fmt_clock_nosecs(&ext).context("%R failed")?,
|
||||
b'r' => self.fmt_12hour_time(&ext).context("%r failed")?,
|
||||
|
|
@ -87,28 +95,17 @@ impl<'c, 'f, 't, 'w, W: Write, L: Custom> Formatter<'c, 'f, 't, 'w, W, L> {
|
|||
b'Y' => self.fmt_year(&ext).context("%Y failed")?,
|
||||
b'y' => self.fmt_year2(&ext).context("%y failed")?,
|
||||
b'Z' => self.fmt_tzabbrev(&ext).context("%Z failed")?,
|
||||
b'z' => self.fmt_offset_nocolon().context("%z failed")?,
|
||||
b':' => {
|
||||
if !self.bump_fmt() {
|
||||
b'z' => match ext.colons {
|
||||
0 => self.fmt_offset_nocolon().context("%z failed")?,
|
||||
1 => self.fmt_offset_colon().context("%:z failed")?,
|
||||
2 => self.fmt_offset_colon2().context("%::z failed")?,
|
||||
3 => self.fmt_offset_colon3().context("%:::z failed")?,
|
||||
_ => {
|
||||
return Err(err!(
|
||||
"invalid format string, expected directive \
|
||||
after '%:'",
|
||||
));
|
||||
"invalid number of `:` in `%z` directive"
|
||||
))
|
||||
}
|
||||
match self.f() {
|
||||
b'Q' => self.fmt_iana_colon().context("%:Q failed")?,
|
||||
b'z' => {
|
||||
self.fmt_offset_colon().context("%:z failed")?
|
||||
}
|
||||
unk => {
|
||||
return Err(err!(
|
||||
"found unrecognized directive %{unk} \
|
||||
following %:",
|
||||
unk = escape::Byte(unk),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
b'.' => {
|
||||
if !self.bump_fmt() {
|
||||
return Err(err!(
|
||||
|
|
@ -199,7 +196,8 @@ impl<'c, 'f, 't, 'w, W: Write, L: Custom> Formatter<'c, 'f, 't, 'w, W, L> {
|
|||
fn parse_extension(&mut self) -> Result<Extension, Error> {
|
||||
let flag = self.parse_flag()?;
|
||||
let width = self.parse_width()?;
|
||||
Ok(Extension { flag, width })
|
||||
let colons = self.parse_colons();
|
||||
Ok(Extension { flag, width, colons })
|
||||
}
|
||||
|
||||
/// Parses an optional flag. And if one is parsed, the parser is bumped
|
||||
|
|
@ -227,6 +225,15 @@ impl<'c, 'f, 't, 'w, W: Write, L: Custom> Formatter<'c, 'f, 't, 'w, W, L> {
|
|||
Ok(width)
|
||||
}
|
||||
|
||||
/// Parses an optional number of colons (up to 3) immediately before a
|
||||
/// conversion specifier.
|
||||
#[cfg_attr(feature = "perf-inline", inline(always))]
|
||||
fn parse_colons(&mut self) -> u8 {
|
||||
let (colons, fmt) = Extension::parse_colons(self.fmt);
|
||||
self.fmt = fmt;
|
||||
colons
|
||||
}
|
||||
|
||||
// These are the formatting functions. They are pretty much responsible
|
||||
// for getting what they need for the broken down time and reporting a
|
||||
// decent failure mode if what they need couldn't be found. And then,
|
||||
|
|
@ -430,7 +437,7 @@ impl<'c, 'f, 't, 'w, W: Write, L: Custom> Formatter<'c, 'f, 't, 'w, W, L> {
|
|||
zone offset, but none were present"
|
||||
)
|
||||
})?;
|
||||
return write_offset(offset, false, &mut self.wtr);
|
||||
return write_offset(offset, false, true, false, &mut self.wtr);
|
||||
};
|
||||
self.wtr.write_str(iana)?;
|
||||
Ok(())
|
||||
|
|
@ -445,7 +452,7 @@ impl<'c, 'f, 't, 'w, W: Write, L: Custom> Formatter<'c, 'f, 't, 'w, W, L> {
|
|||
zone offset, but none were present"
|
||||
)
|
||||
})?;
|
||||
return write_offset(offset, true, &mut self.wtr);
|
||||
return write_offset(offset, true, true, false, &mut self.wtr);
|
||||
};
|
||||
self.wtr.write_str(iana)?;
|
||||
Ok(())
|
||||
|
|
@ -456,7 +463,7 @@ impl<'c, 'f, 't, 'w, W: Write, L: Custom> Formatter<'c, 'f, 't, 'w, W, L> {
|
|||
let offset = self.tm.offset.ok_or_else(|| {
|
||||
err!("requires offset to format time zone offset")
|
||||
})?;
|
||||
write_offset(offset, false, self.wtr)
|
||||
write_offset(offset, false, true, false, self.wtr)
|
||||
}
|
||||
|
||||
/// %:z
|
||||
|
|
@ -464,7 +471,23 @@ impl<'c, 'f, 't, 'w, W: Write, L: Custom> Formatter<'c, 'f, 't, 'w, W, L> {
|
|||
let offset = self.tm.offset.ok_or_else(|| {
|
||||
err!("requires offset to format time zone offset")
|
||||
})?;
|
||||
write_offset(offset, true, self.wtr)
|
||||
write_offset(offset, true, true, false, self.wtr)
|
||||
}
|
||||
|
||||
/// %::z
|
||||
fn fmt_offset_colon2(&mut self) -> Result<(), Error> {
|
||||
let offset = self.tm.offset.ok_or_else(|| {
|
||||
err!("requires offset to format time zone offset")
|
||||
})?;
|
||||
write_offset(offset, true, true, true, self.wtr)
|
||||
}
|
||||
|
||||
/// %:::z
|
||||
fn fmt_offset_colon3(&mut self) -> Result<(), Error> {
|
||||
let offset = self.tm.offset.ok_or_else(|| {
|
||||
err!("requires offset to format time zone offset")
|
||||
})?;
|
||||
write_offset(offset, true, false, false, self.wtr)
|
||||
}
|
||||
|
||||
/// %S
|
||||
|
|
@ -815,9 +838,18 @@ impl<'c, 'f, 't, 'w, W: Write, L: Custom> Formatter<'c, 'f, 't, 'w, W, L> {
|
|||
///
|
||||
/// When `colon` is true, the hour, minute and optional second components are
|
||||
/// delimited by a colon. Otherwise, no delimiter is used.
|
||||
///
|
||||
/// When `minute` is true, the minute component is always printed. When
|
||||
/// false, the minute component is only printed when it is non-zero (or if
|
||||
/// the second component is non-zero).
|
||||
///
|
||||
/// When `second` is true, the second component is always printed. When false,
|
||||
/// the second component is only printed when it is non-zero.
|
||||
fn write_offset<W: Write>(
|
||||
offset: Offset,
|
||||
colon: bool,
|
||||
minute: bool,
|
||||
second: bool,
|
||||
wtr: &mut W,
|
||||
) -> Result<(), Error> {
|
||||
static FMT_TWO: DecimalFormatter = DecimalFormatter::new().padding(2);
|
||||
|
|
@ -828,15 +860,17 @@ fn write_offset<W: Write>(
|
|||
|
||||
wtr.write_str(if offset.is_negative() { "-" } else { "+" })?;
|
||||
wtr.write_int(&FMT_TWO, hours)?;
|
||||
if colon {
|
||||
wtr.write_str(":")?;
|
||||
}
|
||||
wtr.write_int(&FMT_TWO, minutes)?;
|
||||
if seconds != 0 {
|
||||
if minute || minutes != 0 || seconds != 0 {
|
||||
if colon {
|
||||
wtr.write_str(":")?;
|
||||
}
|
||||
wtr.write_int(&FMT_TWO, seconds)?;
|
||||
wtr.write_int(&FMT_TWO, minutes)?;
|
||||
if second || seconds != 0 {
|
||||
if colon {
|
||||
wtr.write_str(":")?;
|
||||
}
|
||||
wtr.write_int(&FMT_TWO, seconds)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1118,12 +1152,28 @@ mod tests {
|
|||
|
||||
insta::assert_snapshot!(f("%z", o(0, 0, 0)), @"+0000");
|
||||
insta::assert_snapshot!(f("%:z", o(0, 0, 0)), @"+00:00");
|
||||
insta::assert_snapshot!(f("%::z", o(0, 0, 0)), @"+00:00:00");
|
||||
insta::assert_snapshot!(f("%:::z", o(0, 0, 0)), @"+00");
|
||||
|
||||
insta::assert_snapshot!(f("%z", -o(4, 0, 0)), @"-0400");
|
||||
insta::assert_snapshot!(f("%:z", -o(4, 0, 0)), @"-04:00");
|
||||
insta::assert_snapshot!(f("%::z", -o(4, 0, 0)), @"-04:00:00");
|
||||
insta::assert_snapshot!(f("%:::z", -o(4, 0, 0)), @"-04");
|
||||
|
||||
insta::assert_snapshot!(f("%z", o(5, 30, 0)), @"+0530");
|
||||
insta::assert_snapshot!(f("%:z", o(5, 30, 0)), @"+05:30");
|
||||
insta::assert_snapshot!(f("%::z", o(5, 30, 0)), @"+05:30:00");
|
||||
insta::assert_snapshot!(f("%:::z", o(5, 30, 0)), @"+05:30");
|
||||
|
||||
insta::assert_snapshot!(f("%z", o(5, 30, 15)), @"+053015");
|
||||
insta::assert_snapshot!(f("%:z", o(5, 30, 15)), @"+05:30:15");
|
||||
insta::assert_snapshot!(f("%::z", o(5, 30, 15)), @"+05:30:15");
|
||||
insta::assert_snapshot!(f("%:::z", o(5, 30, 15)), @"+05:30:15");
|
||||
|
||||
insta::assert_snapshot!(f("%z", o(5, 0, 15)), @"+050015");
|
||||
insta::assert_snapshot!(f("%:z", o(5, 0, 15)), @"+05:00:15");
|
||||
insta::assert_snapshot!(f("%::z", o(5, 0, 15)), @"+05:00:15");
|
||||
insta::assert_snapshot!(f("%:::z", o(5, 0, 15)), @"+05:00:15");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -202,6 +202,8 @@ strings, the strings are matched without regard to ASCII case.
|
|||
| `%Z` | `EDT` | A time zone abbreviation. Supported when formatting only. |
|
||||
| `%z` | `+0530` | A time zone offset in the format `[+-]HHMM[SS]`. |
|
||||
| `%:z` | `+05:30` | A time zone offset in the format `[+-]HH:MM[:SS]`. |
|
||||
| `%::z` | `+05:30:00` | A time zone offset in the format `[+-]HH:MM:SS`. |
|
||||
| `%:::z` | `-04`, `+05:30` | A time zone offset in the format `[+-]HH:[MM[:SS]]`. |
|
||||
|
||||
When formatting, the following flags can be inserted immediately after the `%`
|
||||
and before the directive:
|
||||
|
|
@ -253,7 +255,6 @@ is variable width data. If you have a use case for this, please
|
|||
The following things are currently unsupported:
|
||||
|
||||
* Parsing or formatting fractional seconds in the time time zone offset.
|
||||
* The `%::z` and `%:::z` specifiers found in GNU date.
|
||||
* The `%+` conversion specifier is not supported since there doesn't seem to
|
||||
be any consistent definition for it.
|
||||
* With only Jiff, the `%c`, `%r`, `%X` and `%x` locale oriented specifiers
|
||||
|
|
@ -3084,6 +3085,7 @@ impl From<Time> for Meridiem {
|
|||
pub struct Extension {
|
||||
flag: Option<Flag>,
|
||||
width: Option<u8>,
|
||||
colons: u8,
|
||||
}
|
||||
|
||||
impl Extension {
|
||||
|
|
@ -3148,6 +3150,22 @@ impl Extension {
|
|||
}
|
||||
Ok((Some(width), fmt))
|
||||
}
|
||||
|
||||
/// Parses an optional number of colons.
|
||||
///
|
||||
/// This is meant to be used immediately before the conversion specifier
|
||||
/// (after the flag and width has been parsed).
|
||||
///
|
||||
/// This supports parsing up to 3 colons. The colons are used in some cases
|
||||
/// for alternate specifiers. e.g., `%:Q` or `%:::z`.
|
||||
#[cfg_attr(feature = "perf-inline", inline(always))]
|
||||
fn parse_colons<'i>(fmt: &'i [u8]) -> (u8, &'i [u8]) {
|
||||
let mut colons = 0;
|
||||
while colons < 3 && colons < fmt.len() && fmt[colons] == b':' {
|
||||
colons += 1;
|
||||
}
|
||||
(u8::try_from(colons).unwrap(), &fmt[usize::from(colons)..])
|
||||
}
|
||||
}
|
||||
|
||||
/// The different flags one can set. They are mutually exclusive.
|
||||
|
|
|
|||
|
|
@ -73,7 +73,15 @@ impl<'f, 'i, 't> Parser<'f, 'i, 't> {
|
|||
b'n' => self.parse_whitespace().context("%n failed")?,
|
||||
b'P' => self.parse_ampm().context("%P failed")?,
|
||||
b'p' => self.parse_ampm().context("%p failed")?,
|
||||
b'Q' => self.parse_iana_nocolon().context("%Q failed")?,
|
||||
b'Q' => match ext.colons {
|
||||
0 => self.parse_iana_nocolon().context("%Q failed")?,
|
||||
1 => self.parse_iana_colon().context("%:Q failed")?,
|
||||
_ => {
|
||||
return Err(err!(
|
||||
"invalid number of `:` in `%Q` directive"
|
||||
))
|
||||
}
|
||||
},
|
||||
b'R' => self.parse_clock_nosecs().context("%R failed")?,
|
||||
b'S' => self.parse_second(ext).context("%S failed")?,
|
||||
b's' => self.parse_timestamp(ext).context("%s failed")?,
|
||||
|
|
@ -86,30 +94,17 @@ impl<'f, 'i, 't> Parser<'f, 'i, 't> {
|
|||
b'w' => self.parse_weekday_sun(ext).context("%w failed")?,
|
||||
b'Y' => self.parse_year(ext).context("%Y failed")?,
|
||||
b'y' => self.parse_year2(ext).context("%y failed")?,
|
||||
b'z' => self.parse_offset_nocolon().context("%z failed")?,
|
||||
b':' => {
|
||||
if !self.bump_fmt() {
|
||||
b'z' => match ext.colons {
|
||||
0 => self.parse_offset_nocolon().context("%z failed")?,
|
||||
1 => self.parse_offset_colon().context("%:z failed")?,
|
||||
2 => self.parse_offset_colon2().context("%::z failed")?,
|
||||
3 => self.parse_offset_colon3().context("%:::z failed")?,
|
||||
_ => {
|
||||
return Err(err!(
|
||||
"invalid format string, expected directive \
|
||||
after '%:'",
|
||||
));
|
||||
"invalid number of `:` in `%z` directive"
|
||||
))
|
||||
}
|
||||
match self.f() {
|
||||
b'Q' => {
|
||||
self.parse_iana_colon().context("%:Q failed")?
|
||||
}
|
||||
b'z' => {
|
||||
self.parse_offset_colon().context("%:z failed")?
|
||||
}
|
||||
unk => {
|
||||
return Err(err!(
|
||||
"found unrecognized directive %{unk} \
|
||||
following %:",
|
||||
unk = escape::Byte(unk),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
b'c' => {
|
||||
return Err(err!("cannot parse locale date and time"));
|
||||
}
|
||||
|
|
@ -205,8 +200,9 @@ impl<'f, 'i, 't> Parser<'f, 'i, 't> {
|
|||
fn parse_extension(&mut self) -> Result<Extension, Error> {
|
||||
let (flag, fmt) = Extension::parse_flag(self.fmt)?;
|
||||
let (width, fmt) = Extension::parse_width(fmt)?;
|
||||
let (colons, fmt) = Extension::parse_colons(fmt);
|
||||
self.fmt = fmt;
|
||||
Ok(Extension { flag, width })
|
||||
Ok(Extension { flag, width, colons })
|
||||
}
|
||||
|
||||
// We write out a parsing routine for each directive below. Each parsing
|
||||
|
|
@ -449,7 +445,8 @@ impl<'f, 'i, 't> Parser<'f, 'i, 't> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Parse `%z`, which is a time zone offset without colons.
|
||||
/// Parse `%z`, which is a time zone offset without colons that requires
|
||||
/// a minutes component but will parse a second component if it's there.
|
||||
fn parse_offset_nocolon(&mut self) -> Result<(), Error> {
|
||||
static PARSER: offset::Parser = offset::Parser::new()
|
||||
.zulu(false)
|
||||
|
|
@ -466,7 +463,8 @@ impl<'f, 'i, 't> Parser<'f, 'i, 't> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Parse `%:z`, which is a time zone offset with colons.
|
||||
/// Parse `%:z`, which is a time zone offset with colons that requires
|
||||
/// a minutes component but will parse a second component if it's there.
|
||||
fn parse_offset_colon(&mut self) -> Result<(), Error> {
|
||||
static PARSER: offset::Parser = offset::Parser::new()
|
||||
.zulu(false)
|
||||
|
|
@ -483,6 +481,45 @@ impl<'f, 'i, 't> Parser<'f, 'i, 't> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Parse `%::z`, which is a time zone offset with colons that requires
|
||||
/// a seconds component.
|
||||
fn parse_offset_colon2(&mut self) -> Result<(), Error> {
|
||||
static PARSER: offset::Parser = offset::Parser::new()
|
||||
.zulu(false)
|
||||
.require_minute(true)
|
||||
.require_second(true)
|
||||
.subminute(true)
|
||||
.subsecond(false)
|
||||
.colon(offset::Colon::Required);
|
||||
|
||||
let Parsed { value, input } = PARSER.parse(self.inp)?;
|
||||
self.tm.offset = Some(value.to_offset()?);
|
||||
self.inp = input;
|
||||
self.bump_fmt();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parse `%:::z`, which is a time zone offset with colons that only
|
||||
/// requires an hour component, but will parse minute/second components
|
||||
/// if they are there.
|
||||
fn parse_offset_colon3(&mut self) -> Result<(), Error> {
|
||||
static PARSER: offset::Parser = offset::Parser::new()
|
||||
.zulu(false)
|
||||
.require_minute(false)
|
||||
.require_second(false)
|
||||
.subminute(true)
|
||||
.subsecond(false)
|
||||
.colon(offset::Colon::Required);
|
||||
|
||||
let Parsed { value, input } = PARSER.parse(self.inp)?;
|
||||
self.tm.offset = Some(value.to_offset()?);
|
||||
self.inp = input;
|
||||
self.bump_fmt();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parses `%S`, which is equivalent to the second.
|
||||
fn parse_second(&mut self, ext: Extension) -> Result<(), Error> {
|
||||
let (mut second, inp) = ext
|
||||
|
|
@ -1693,6 +1730,10 @@ mod tests {
|
|||
p("%z", "+053015"),
|
||||
@"05:30:15",
|
||||
);
|
||||
insta::assert_debug_snapshot!(
|
||||
p("%z", "+050015"),
|
||||
@"05:00:15",
|
||||
);
|
||||
|
||||
insta::assert_debug_snapshot!(
|
||||
p("%:z", "+05:30"),
|
||||
|
|
@ -1710,6 +1751,68 @@ mod tests {
|
|||
p("%:z", "+05:30:15"),
|
||||
@"05:30:15",
|
||||
);
|
||||
insta::assert_debug_snapshot!(
|
||||
p("%:z", "-05:00:15"),
|
||||
@"-05:00:15",
|
||||
);
|
||||
|
||||
insta::assert_debug_snapshot!(
|
||||
p("%::z", "+05:30:15"),
|
||||
@"05:30:15",
|
||||
);
|
||||
insta::assert_debug_snapshot!(
|
||||
p("%::z", "-05:30:15"),
|
||||
@"-05:30:15",
|
||||
);
|
||||
insta::assert_debug_snapshot!(
|
||||
p("%::z", "-05:00:00"),
|
||||
@"-05:00:00",
|
||||
);
|
||||
insta::assert_debug_snapshot!(
|
||||
p("%::z", "-05:00:15"),
|
||||
@"-05:00:15",
|
||||
);
|
||||
|
||||
insta::assert_debug_snapshot!(
|
||||
p("%:::z", "+05"),
|
||||
@"05:00:00",
|
||||
);
|
||||
insta::assert_debug_snapshot!(
|
||||
p("%:::z", "-05"),
|
||||
@"-05:00:00",
|
||||
);
|
||||
insta::assert_debug_snapshot!(
|
||||
p("%:::z", "+00"),
|
||||
@"00:00:00",
|
||||
);
|
||||
insta::assert_debug_snapshot!(
|
||||
p("%:::z", "-00"),
|
||||
@"00:00:00",
|
||||
);
|
||||
insta::assert_debug_snapshot!(
|
||||
p("%:::z", "+05:30"),
|
||||
@"05:30:00",
|
||||
);
|
||||
insta::assert_debug_snapshot!(
|
||||
p("%:::z", "-05:30"),
|
||||
@"-05:30:00",
|
||||
);
|
||||
insta::assert_debug_snapshot!(
|
||||
p("%:::z", "+05:30:15"),
|
||||
@"05:30:15",
|
||||
);
|
||||
insta::assert_debug_snapshot!(
|
||||
p("%:::z", "-05:30:15"),
|
||||
@"-05:30:15",
|
||||
);
|
||||
insta::assert_debug_snapshot!(
|
||||
p("%:::z", "-05:00:00"),
|
||||
@"-05:00:00",
|
||||
);
|
||||
insta::assert_debug_snapshot!(
|
||||
p("%:::z", "-05:00:15"),
|
||||
@"-05:00:15",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1866,7 +1969,7 @@ mod tests {
|
|||
);
|
||||
insta::assert_snapshot!(
|
||||
p("%:Q", "+0400"),
|
||||
@r#"strptime parsing failed: %:Q failed: parsed hour component of time zone offset from "+0400", but could not find required colon component"#,
|
||||
@r#"strptime parsing failed: %:Q failed: parsed hour component of time zone offset from "+0400", but could not find required colon separator"#,
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
p("%Q", "+04:00"),
|
||||
|
|
@ -2020,7 +2123,15 @@ mod tests {
|
|||
);
|
||||
insta::assert_snapshot!(
|
||||
p("%:z", "+0530"),
|
||||
@r#"strptime parsing failed: %:z failed: parsed hour component of time zone offset from "+0530", but could not find required colon component"#,
|
||||
@r#"strptime parsing failed: %:z failed: parsed hour component of time zone offset from "+0530", but could not find required colon separator"#,
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
p("%::z", "+0530"),
|
||||
@r#"strptime parsing failed: %::z failed: parsed hour component of time zone offset from "+0530", but could not find required colon separator"#,
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
p("%:::z", "+0530"),
|
||||
@r#"strptime parsing failed: %:::z failed: parsed hour component of time zone offset from "+0530", but could not find required colon separator"#,
|
||||
);
|
||||
|
||||
insta::assert_snapshot!(
|
||||
|
|
@ -2031,6 +2142,18 @@ mod tests {
|
|||
p("%:z", "+05"),
|
||||
@r#"strptime parsing failed: %:z failed: parsed hour component of time zone offset from "+05", but could not find required minute component"#,
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
p("%::z", "+05"),
|
||||
@r#"strptime parsing failed: %::z failed: parsed hour component of time zone offset from "+05", but could not find required minute component"#,
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
p("%::z", "+05:30"),
|
||||
@r#"strptime parsing failed: %::z failed: parsed hour and minute components of time zone offset from "+05:30", but could not find required second component"#,
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
p("%:::z", "+5"),
|
||||
@r#"strptime parsing failed: %:::z failed: failed to parse hours in UTC numeric offset "+5": expected two digit hour after sign, but found end of input"#,
|
||||
);
|
||||
|
||||
insta::assert_snapshot!(
|
||||
p("%z", "+0530:15"),
|
||||
|
|
@ -2040,5 +2163,13 @@ mod tests {
|
|||
p("%:z", "+05:3015"),
|
||||
@r#"strptime expects to consume the entire input, but "15" remains unparsed"#,
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
p("%::z", "+05:3015"),
|
||||
@r#"strptime parsing failed: %::z failed: parsed hour and minute components of time zone offset from "+05:3015", but could not find required second component"#,
|
||||
);
|
||||
insta::assert_snapshot!(
|
||||
p("%:::z", "+05:3015"),
|
||||
@r#"strptime expects to consume the entire input, but "15" remains unparsed"#,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue