devela::_dep::jiff::fmt

Module strtime

Available on crate features dep_jiff and alloc only.
Expand description

Support for “printf”-style parsing and formatting.

While the routines exposed in this module very closely resemble the corresponding strptime and strftime POSIX functions, it is not a goal for the formatting machinery to precisely match POSIX semantics.

If there is a conversion specifier you need that Jiff doesn’t support, please create a new issue.

The formatting and parsing in this module does not currently support any form of localization. Please see this issue about the topic of localization in Jiff.

§Example

This shows how to parse a civil date and its weekday:

use jiff::civil::Date;

let date = Date::strptime("%Y-%m-%d is a %A", "2024-07-15 is a Monday")?;
assert_eq!(date.to_string(), "2024-07-15");
// Leading zeros are optional for numbers in all cases:
let date = Date::strptime("%Y-%m-%d is a %A", "2024-07-15 is a Monday")?;
assert_eq!(date.to_string(), "2024-07-15");
// Parsing does error checking! 2024-07-15 was not a Tuesday.
assert!(Date::strptime("%Y-%m-%d is a %A", "2024-07-15 is a Tuesday").is_err());

And this shows how to format a zoned datetime with a time zone abbreviation:

use jiff::civil::date;

let zdt = date(2024, 7, 15).at(17, 30, 59, 0).intz("Australia/Tasmania")?;
// %-I instead of %I means no padding.
let string = zdt.strftime("%A, %B %d, %Y at %-I:%M%P %Z").to_string();
assert_eq!(string, "Monday, July 15, 2024 at 5:30pm AEST");

Or parse a zoned datetime with an IANA time zone identifier:

use jiff::{civil::date, Zoned};

let zdt = Zoned::strptime(
    "%A, %B %d, %Y at %-I:%M%P %:V",
    "Monday, July 15, 2024 at 5:30pm Australia/Tasmania",
)?;
assert_eq!(
    zdt,
    date(2024, 7, 15).at(17, 30, 0, 0).intz("Australia/Tasmania")?,
);

§Usage

For most cases, you can use the strptime and strftime methods on the corresponding datetime type. For example, Zoned::strptime and Zoned::strftime. However, the BrokenDownTime type in this module provides a little more control.

For example, assuming t is a civil::Time, then t.strftime("%Y").to_string() will actually panic because a civil::Time does not have a year. While the underlying formatting machinery actually returns an error, this error gets turned into a panic by virtue of going through the std::fmt::Display and std::string::ToString APIs.

In contrast, BrokenDownTime::format (or just format) can report the error to you without any panicking:

use jiff::{civil::time, fmt::strtime};

let t = time(23, 59, 59, 0);
assert_eq!(
    strtime::format("%Y", t).unwrap_err().to_string(),
    "strftime formatting failed: %Y failed: requires date to format year",
);

§Advice

The formatting machinery supported by this module is not especially expressive. The pattern language is a simple sequence of conversion specifiers interspersed by literals and arbitrary whitespace. This means that you sometimes need delimiters or spaces between components. For example, this is fine:

use jiff::fmt::strtime;

let date = strtime::parse("%Y%m%d", "20240715")?.to_date()?;
assert_eq!(date.to_string(), "2024-07-15");

But this is ambiguous (is the year 999 or 9990?):

use jiff::fmt::strtime;

assert!(strtime::parse("%Y%m%d", "9990715").is_err());

In this case, since years greedily consume up to 4 digits by default, 9990 is parsed as the year. And since months greedily consume up to 2 digits by default, 71 is parsed as the month, which results in an invalid day. If you expect your datetimes to always use 4 digits for the year, then it might be okay to skip on the delimiters. For example, the year 999 could be written with a leading zero:

use jiff::fmt::strtime;

let date = strtime::parse("%Y%m%d", "09990715")?.to_date()?;
assert_eq!(date.to_string(), "0999-07-15");
// Indeed, the leading zero is written by default when
// formatting, since years are padded out to 4 digits
// by default:
assert_eq!(date.strftime("%Y%m%d").to_string(), "09990715");

The main advice here is that these APIs can come in handy for ad hoc tasks that would otherwise be annoying to deal with. For example, I once wrote a tool to extract data from an XML dump of my SMS messages, and one of the date formats used was Apr 1, 2022 20:46:15. That doesn’t correspond to any standard, and while parsing it with a regex isn’t that difficult, it’s pretty annoying, especially because of the English abbreviated month name. That’s exactly the kind of use case where this module shines.

If the formatting machinery in this module isn’t flexible enough for your use case and you don’t control the format, it is recommended to write a bespoke parser (possibly with regex). It is unlikely that the expressiveness of this formatting machinery will be improved much. (Although it is plausible to add new conversion specifiers.)

§Conversion specifications

This table lists the complete set of conversion specifiers supported in the format. While most conversion specifiers are supported as is in both parsing and formatting, there are some differences. Where differences occur, they are noted in the table below.

When parsing, and whenever a conversion specifier matches an enumeration of strings, the strings are matched without regard to ASCII case.

SpecifierExampleDescription
%%%%A literal %.
%A, %aSunday, SunThe full and abbreviated weekday, respectively.
%B, %b, %hJune, Jun, JunThe full and abbreviated month name, respectively.
%D7/14/24Equivalent to %m/%d/%y.
%d, %e25, 5The day of the month. %d is zero-padded, %e is space padded.
%F2024-07-14Equivalent to %Y-%m-%d.
%f000456Fractional seconds, up to nanosecond precision.
%.f.000456Optional fractional seconds, with dot, up to nanosecond precision.
%H23The hour in a 24 hour clock. Zero padded.
%I11The hour in a 12 hour clock. Zero padded.
%M04The minute. Zero padded.
%m01The month. Zero padded.
%PamWhether the time is in the AM or PM, lowercase.
%pPMWhether the time is in the AM or PM, uppercase.
%S59The second. Zero padded.
%T23:30:59Equivalent to %H:%M:%S.
%VAmerica/New_York, +0530An IANA time zone identifier, or %z if one doesn’t exist.
%:VAmerica/New_York, +05:30An IANA time zone identifier, or %:z if one doesn’t exist.
%Y2024A full year, including century. Zero padded to 4 digits.
%y24A two-digit year. Represents only 1969-2068. Zero padded.
%ZEDTA time zone abbreviation. Supported when formatting only.
%z+0530A time zone offset in the format [+-]HHMM[SS].
%:z+05:30A time zone offset in the format [+-]HH:MM[:SS].

When formatting, the following flags can be inserted immediately after the % and before the directive:

  • _ - Pad a numeric result to the left with spaces.
  • - - Do not pad a numeric result.
  • 0 - Pad a numeric result to the left with zeros.
  • ^ - Use alphabetic uppercase for all relevant strings.
  • # - Swap the case of the result string. This is typically only useful with %p or %Z, since they are the only conversion specifiers that emit strings entirely in uppercase by default.

The above flags override the “default” settings of a specifier. For example, %_d pads with spaces instead of zeros, and %0e pads with zeros instead of spaces. The exceptions are the %z and %:z specifiers. They are unaffected by any flags.

Moreover, any number of decimal digits can be inserted after the (possibly absent) flag and before the directive, so long as the parsed number is less than 256. The number formed by these digits will correspond to the minimum amount of padding (to the left).

The flags and padding amount above may be used when parsing as well. Most settings are ignoring during parsing except for padding. For example, if one wanted to parse 003 as the day 3, then one should use %03d. Otherwise, by default, %d will only try to consume at most 2 digits.

The %f and %.f flags also support specifying the precision, up to nanoseconds. For example, %3f and %.3f will both always print a fractional second component to exactly 3 decimal places. When no precision is specified, then %f will always emit at least one digit, even if it’s zero. But %.f will emit the empty string when the fractional component is zero. Otherwise, it will include the leading .. For parsing, %f does not include the leading dot, but %.f does. Note that all of the options above are still parsed for %f and %.f, but they are all no-ops (except for the padding for %f, which is instead interpreted as a precision setting). When using a precision setting, truncation is used. If you need a different rounding mode, you should use higher level APIs like Timestamp::round or Zoned::round.

§Conditionally unsupported

Jiff does not support %V or %:V (IANA time zone identifier) when the alloc crate feature is not enabled. This is because a time zone identifier is variable width data. If you have a use case for this, please detail it in a new issue.

§Unsupported

The following things are currently unsupported:

  • Parsing or formatting fractional seconds in the time time zone offset.
  • Conversion specifiers related to week numbers.
  • Conversion specifiers related to day-of-year numbers, like the Julian day.
  • The %s conversion specifier, for Unix timestamps in seconds.

Structs§

  • The “broken down time” used by parsing and formatting.
  • A “lazy” implementation of std::fmt::Display for strftime.

Enums§

  • A label to disambiguate hours on a 12-hour clock.

Functions§

  • Format the given broken down time using the format string given.
  • Parse the given input according to the given format string.