Docs: jiff - Rust
Some other useful links that are likely to answer some common questions:
Docs: jiff - Rust
Some other useful links that are likely to answer some common questions:
I have to admit I was skeptical at first. Another DateTime library, oh no! But the justifications seem reasonable to me.
I've been bitten by daylights savings time bugs enough to appreciate the focus on using a full geographical region (tzinfo) timezone by default.
At first glance, the API looks well thought out. I'll be trying it out for experimental stuff to see how it clicks.
P.S I noticed a typo in your docs while browsing, would a tiny pull request be well received? The last d
should be an s
in the regex under "# Span Format". Threw me for a jiff
And thanks for the detailed docs!
I wish one of these good libs gets into the stdlib.
This looks pretty good! Definitely feels like a step forward, 96-bit nanosecond durations can be used just in memory-constraint situations (big multipliers) and Zoned otherwise...
I think Jiff can probably be used in memory constrained environments too. A jiff::Timestamp
is, for example, just a 96-bit integer of nanoseconds. But, jiff
does require at least alloc
, so I think it would need to be a severely constrained environment. I suppose it just depends on what "memory constrained" means.
I can't overstate how excited I am about this @BurntSushi !!
I've been frustrated by the lack of a decent date/time library in Rust forever. I've gone back and forth between several of the Rust time crates trying to figure out which works best for me. And a few months ago, I started a personal project where I tried to implement some commonly needed time-related tasks, using chrono
, time
, and hifitime
, to highlight the differences in practical usage.
Now I started using jiff
and WOW, it is so much better. Things just work and feel more natural and complete. And great job on the documentation and balanced/fair comparisons you wrote up. Thanks!
Thanks for the feedback! Can you share any specifics about what you specifically found easier/nicer in Jiff for your use case compared to the other datetime libraries?
Seconding this! I'd love to know what improvements can be made to time
.
Here are some pain points I found using the different time libraries:
DateTime::parse_from_rfc3339()
only accepts strings with a Z
suffix, not a numeric offset.Local::now()
) into a DateTime<FixedOffset>
variable in a struct.serde
(2024-10-03 10:59:06.749952421 -07:00:00
, why seconds in the offset?!), you have do special workarounds for every field in your structs to serialize to ISO 8601/RFC 3339 format2024-11-04 0:00:00.0 -04:00:00
:
FromStr
trait is not implemented due to the crate's requirement to specify a format description whenever parsing. The ISO 8601 format is complex with some fancy generic trait stuff for configuration. After some work, you can discover that the way to parse a standard time string is OffsetDateTime::parse(&line, &Iso8601::PARSING)
. Not sure how to parse RFC 3339 strings like 2024-11-03 00:15:00-0400
... won't parse as ISO 8601 since it has a space instead of T
as separator, and the Rfc3339
format seems to parse very few formats.On the other hand, jiff
just works:
FromStr
so you can simply do s.parse()
parsing "2024-11-03T00:15:00-0500[America/New_York]" failed: datetime 2024-11-03T00:15:00 could not resolve to a timestamp since 'reject' conflict resolution was chosen, and because datetime has offset -05, but the time zone America/New_York for the given datetime unambiguously has offset -04
I haven't worked with Jiff enough to give more more detailed comments, but it already looks like it's a clear winner to replace my use of chrono
, time
, and the iso8601-timestamp
crate that is sometimes needed to get timestamps to serialize correctly.
Here is the project where I began implementing some common tasks using different time libraries:
Some locations (namely Liberia) require this for times after 1970, which is the typical delineation for the minimum timeframe to support. I would love to be able to only store the hours and minutes. The only reason I know of Liberia's absurdly late adoption of international time standards is because I've previously looked into it.
If there is a crate that does not support UTC offsets with second-level precision, then they are fundamentally incapable of handling all offsets in real-world use since 1970.
If by "special workarounds" you mean explicitly declaring the format output, then yes. Rust tends to favor things being explicit when there are multiple valid choices. There's not much of an alternative to this approach.
It is intended to be human readable and nothing else. If you need a specific format, it's easy enough to write them in an optimized manner. The format expected is well documented.
What is non-obvious? There are format
methods that accept impl Formattable
; following that link shows the well-known types that implement it.
No traits are involved here. There's const generics, but only in one location. Its use is to make the overhead as close to zero as possible, as all config as done at compile time. The only oddity is that you need to call .encode()
due to compiler limitations, but this is clearly documented. ISO 8601, unlike most (all?) other specifications, provide for options that I pass on to end users for maximum flexibility.
RFC 3339 prima facie requires a T
as separator, which is why parsing fails — it's simply not valid. However, there is an unreleased change (already implemented) that permits any separator, as there is a non-normative part of the specification allowing it. Note that even the RFC authors have stated that a space was not intended to be permitted. Blame the specification, which is incredibly unclear as to what's permitted and what's not; I followed the provided ABNF to a tee.
Ultimately, things like this really should be brought up in a discussion in the time-rs/time repository. Some things can be changed in a non-breaking release, while others inherently need to be breaking. For example, changing the default serialization format is breaking, but the reason it wasn't ISO 8601 from the start is that serde support was implemented first (ISO 8601 requires payment to access). I am happy to discuss any concerns you have in detail (though I very much prefer searching first, as many have already been discussed).
Let's move to a discussion (as linked) if you'd like to continue this, so as to avoid derailing this thread.
I'll limit this to two comments. One clarifying and one opinionated.
Only until 1972. Since Jan 1972, Africa/Monrovia
has an offset of +00:00
.
Jiff supports sub-minute offsets internally, and will also print sub-minute offsets when using its strftime
APIs, but otherwise won't. That's because none of RFC 3339, RFC 9557 or ISO 8601 support it. From RFC 3339:
NOTE: Following ISO 8601, numeric offsets represent only time zones that differ from UTC by an integral number of minutes. However, many historical time zones differ from UTC by a non- integral number of minutes. To represent such historical time stamps exactly, applications must convert them to a representable time zone.
ISO 8601 Part 2 Section 7.4 does have an extension for this called "time shifts," but I don't think it's commonly implemented. And it's not specified in the way you'd expect. e.g., The time shift is represented as Z7H33M14S
.
I think for serialization purposes, the RFC 3339/9557 and ISO 8601 formats (which [Jiff implements as the Temporal ISO 8601 grammar) are ubiquitous enough to be the default. This is also what Temporal does, with its types having default string/JSON representations corresponding to ISO 8601 for civil times, RFC 9557 for ZonedDateTime
and RFC 3339 for Instant
.
From the comparison with hifitime
:
Leap second support, in this context, means that the durations between two points in time take leap seconds into account in hifitime, but Jiff pretends as if they don’t exist.
This makes me wonder about 2 things:
SignedDuration
or Span
? The docs don't mention the term leap year
(I C-f
ed for it).I had the same question. I wish time was simpler to deal with! I'd like to be able to calculate true differences in real time, to compute accurate rate of change of a quantity, for instance, reliably even when a leap second happens.
There's an EXTENSIVE discussion of this for jiff
here: about leap seconds · Issue #7 · BurntSushi/jiff · GitHub - check it out.
Thanks @jhpratt for your clarifications. Obviously I am not an expert in time-rs
and you know a lot more about the intricacies of the standards than I do.
The time
crate obviously is carefully implemented and does a lot of things well, but for me, doing the common tasks I am doing in Rust (similar to what I've used in Java or Python), it does not make it as simple or natural as I'd like.
In particular, today I think it's safe to say that ISO 8601 / RFC 3339 is the universal standard, whenever the user doesn't ask for something different explicitly, the standard format ought to be consistently used.
As for the minutes in the UTC offset, that is interesting that it is required for some time zones. I will have to test some other libraries and see which ones have trouble parsing that.
The docs definitely mention it. Search for leap year
on Span in jiff - Rust
Here's an example. This program passes both assertions:
use jiff::{civil, ToSpan, Unit};
fn main() -> anyhow::Result<()> {
let dt1 = civil::date(2024, 3, 1).at(0, 0, 0, 0);
let dt2 = civil::date(2023, 3, 1).at(0, 0, 0, 0);
assert_eq!(dt1.since((Unit::Year, dt2))?, 1.year());
let dt1 = civil::date(2023, 3, 1).at(0, 0, 0, 0);
let dt2 = civil::date(2022, 3, 1).at(0, 0, 0, 0);
assert_eq!(dt1.since((Unit::Year, dt2))?, 1.year());
Ok(())
}
For SignedDuration
, you can only get calendar units out of it by giving it a reference date.
The same applies to months, since just like years, they are of varying length.
This is unlike humantime
, which incorrectly assumes that all months are 30.44
days and all years are 365.25
days. That may be correct or correct enough for some limited circumstances, but it's definitely inappropriate to do it for all cases. It does this presumably because it doesn't require a reference date to resolve the duration. It's a good example of a library having the appearance of an added capability (i.e., "with humantime
I can convert 1 year
to a std::time::Duration
without providing a reference date!"), but is actually wrong to do in general.
What was the motivation for leaving out leap second support?
That's answered here (and in the issue linked above): jiff::_documentation::design - Rust
And it likely will be in a future breaking change. But keep in mind that ISO 8601 requires payment to view the specification, and not a small amount at that (~$150 I believe). For an individual, that's a significant blocker and why it wasn't implemented at first; I refused to buy it myself as I didn't personally need it. RFC3339 is limited in the range of years it accepts, which is an issue when considering making it the default.