Both `chrono` and `time` use more elaborate representations for their
canonical "timestamp" data types. In contrast, Jiff just uses an integer
number of nanoseconds (like `std::time::Duration`). This makes, among
other things, construction from an integer virtually free:
$ cargo bench -- timestamp/from_seconds
timestamp/from_seconds/span/jiff
time: [385.45 ps 385.53 ps 385.62 ps]
Found 7 outliers among 100 measurements (7.00%)
1 (1.00%) high mild
6 (6.00%) high severe
timestamp/from_seconds/duration/chrono
time: [4.6363 ns 4.6375 ns 4.6387 ns]
Found 9 outliers among 100 measurements (9.00%)
5 (5.00%) high mild
4 (4.00%) high severe
timestamp/from_seconds/duration/time
time: [9.6360 ns 9.6387 ns 9.6418 ns]
Found 8 outliers among 100 measurements (8.00%)
5 (5.00%) high mild
3 (3.00%) high severe
Chrono doesn't really have a "timestamp" type, but `DateTime<Utc>` is
the de facto choice. `time` just recently added a `UtcDateTime` type,
which also feels a lot like a timestamp type, but its representation is
more elaborate than Jiff's.
I originally didn't do this because it seemed kinda bush
league, but this turns out to be a pretty common case via
POSIX time zones. So let's just use `yesterday()` and
`tomorrow()`, which will avoid a round-trip through Unix
epoch days.
It's still not quite as fast as I would hope, but both of the
primary TZ operations are now about 2x as fast.
We also simplify the data structure internals. This helped
make it faster since more stuff is now pre-computed. But now
we can't roundtrip a parsed POSIX TZ exactly. But I think this
is not a big deal and there is no specific requirement for
needing a POSIX TZ to be formatted exactly how it was given.
(Of course, semantically, the POSIX TZ will round-trip.) For
example, a POSIX time zone that uses `+` explicitly is allowed,
but Jiff will never format a POSIX TZ with a `+` anywhere.
This was partially motivated by optimization, but I was initially
moved to do this because I plan to expose some of these internals
for use with proc macros. And I wanted the exposed type definitions
to be simpler. A `ReasonablePosixTz` was pretty unwieldy before.
But now it's much tighter.
Ref #256
I wasn't sure how I wanted to organize benchmarks when I first started
writing them, so I kind just put them all in one file in a very ad hoc
way.
This commit adds new benchmarks and systematizes things a bit. Namely,
benchmarks are now split into modules according to which data type is
being operated upon. Moreover, we define some half-assed conversion
traits to make `chrono` and `time` values from `jiff` values. We don't
include those conversions in benchmarks, but I found it made benchmarks
a little nicer to write. Namely, we can just define all of our inputs
using Jiff types and then convert them to what we need for measuring
other crates. This also ensures we get the conversions correct.
To quickly demonstrate this, you can now support friendly and correct
durations with Jiff easily. Here's a simple CLI program using Clap:
```rust
use clap::Parser;
use jiff::{Span, Zoned};
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
duration: Span,
}
fn main() {
let args = Args::parse();
println!("adding duration to now: {}", &Zoned::now() + args.duration);
}
```
And running the program:
```
$ cargo run -q -- '1 year, 2 months, 5 hours'
adding duration to now: 2026-02-26T18:58:22-05:00[America/New_York]
$ cargo run -q -- 'P1Y2MT5H' # ISO 8601 durations are supported too!
adding duration to now: 2026-02-26T19:00:57-05:00[America/New_York]
```
With Jiff, you should no longer need to pull in crates like
[`humantime`](https://docs.rs/humantime) and
[`humantime-serde`](https://docs.rs/humantime-serde)
to accomplished a similar task.
-----
This ended up being a lot more work than I anticipated. But this PR adds
a second kind of duration format to Jiff. To make this PR description
more accessible, I'm just going to quote from the `jiff::fmt::friendly`
docs as to the motivation for this:
> This format was devised, in part, because the standard duration interchange
> format specified by [Temporal's ISO 8601 definition](https://docs.rs/jiff/latest/jiff/fmt/temporal/index.html) is
> sub-optimal in two important respects:
>
> 1. It doesn't support individual sub-second components.
> 2. It is difficult to read.
>
> In the first case, ISO 8601 durations do support sub-second components, but are
> only expressible as fractional seconds. For example:
>
> ```text
> PT1.100S
> ```
>
> This is problematic in some cases because it doesn't permit distinguishing
> between some spans. For example, `1.second().milliseconds(100)` and
> `1100.milliseconds()` both serialize to the same ISO 8601 duration as shown
> above. At deserialization time, it's impossible to know what the span originally
> looked like. Thus, using the ISO 8601 format means the serialization and
> deserialization of [`Span`](crate::Span) values is lossy.
>
> In the second case, ISO 8601 durations appear somewhat difficult to quickly
> read. For example:
>
> ```text
> P1Y2M3DT4H59M1.1S
> P1y2m3dT4h59m1.1S
> ```
>
> When all of the unit designators are capital letters in particular, everything
> runs together and it's hard for the eye to distinguish where digits stop and
> letters begin. Using lowercase letters for unit designators helps somewhat,
> but this is an extension to ISO 8601 that isn't broadly supported.
>
> The "friendly" format resolves both of these problems by permitting sub-second
> components and allowing the use of whitespace and longer unit designator labels
> to improve readability. For example, all of the following are equivalent and
> will parse to the same `Span`:
>
> ```text
> 1y 2mo 3d 4h 59m 1100ms
> 1 year 2 months 3 days 4h59m1100ms
> 1 year, 2 months, 3 days, 4h59m1100ms
> 1 year, 2 months, 3 days, 4 hours 59 minutes 1100 milliseconds
> ```
>
> At the same time, the friendly format continues to support fractional
> time components since they may be desirable in some cases. For example, all
> of the following are equivalent:
>
> ```text
> 1h 1m 1.5s
> 1h 1m 1,5s
> 01:01:01.5
> 01:01:01,5
> ```
>
> The idea with the friendly format is that end users who know how to write
> English durations are happy to both read and write durations in this format.
> And moreover, the format is flexible enough that end users generally don't need
> to stare at a grammar to figure out how to write a valid duration. Most of the
> intuitive things you'd expect to work will work.
While this doesn't support any kind of internationalization, the
prevalence of the `humantime` crate suggests there's a desire for
something like this. The "friendly" format is meant to service all the
same use cases as `humantime` does for durations, but in a way that
doesn't let you shoot yourself in the foot.
The new "friendly" format is now the default for the `Debug`
implementations of both `Span` and `SignedDuration`. It's also
available via the "alternate" `Display` implementations for `Span` and
`SignedDuration` as well. Moreover, both `Span` and `SignedDuration`
will parse _both_ the ISO 8601 duration and this new "friendly" format.
Closes#60, Closes#111
This is useful because Asia/Shanghai doesn't have DST, so this helps
measure the flow through the code when DST doesn't need to be handled.
That is, there are some shortcuts we can take.
Closes#103
This makes some cases of adding durations to Timestamp/Zoned faster.
It does seem like there is some room left here to improved. But making
Zoned truly faster is difficult because of its higher level abstraction
facilities.
There were a few benchmarks where I was doing `timestamp()` comparisons
unnecessarily. It was probably a transcription error. There is one
benchmark though where it is intentional.
Also, we used the `well_known` ISO 8601 format for `time` when parsing
civil datetimes. There is a significant speed-up that comes with it.
It's a hair faster than Jiff now in my benchmarks.
Fixes#38