Replies: 2 comments 7 replies
-
Thank you very much for this feedback! It is very appreciated.
Hmmm it's not just performance. It's the fact that in order for it to work for all possible inputs, you must provide a relative datetime. Otherwise the duration of years, months, weeks and days is not known. Days could be safely assumed to be 24 hours in some contexts (hence why Since some extra data is required to do a correct calculation in all cases, it would I suppose be feasible to implement
I prefer working with complete examples that we can both compile, so I took the liberty here: use jiff::{Span, SpanRelativeTo, Timestamp};
fn main() -> anyhow::Result<()> {
let created: Timestamp = "2025-07-05T05:30-04".parse()?;
if matches!(
(Timestamp::now() - created)
.compare((
Span::new().days(7),
SpanRelativeTo::days_are_24_hours()
))
.unwrap(),
core::cmp::Ordering::Greater
) {
return Err(anyhow::anyhow!("failure"));
}
Ok(())
} I can see how you got here, but there are some simplifications you can make. The first is that you can use use jiff::{Span, SpanRelativeTo, Timestamp};
fn main() -> anyhow::Result<()> {
let created: Timestamp = "2025-07-05T05:30-04".parse()?;
if (Timestamp::now() - created)
.compare((Span::new().days(7), SpanRelativeTo::days_are_24_hours()))
.unwrap()
.is_gt()
{
return Err(anyhow::anyhow!("failure"));
}
Ok(())
} The second approach is to just use use jiff::{ToSpan, Zoned};
fn main() -> anyhow::Result<()> {
let created: Zoned = "2025-07-05T05:30-04[UTC]".parse()?;
let now = Zoned::now();
if (&now - &created).compare((7.days(), &now)).unwrap().is_gt() {
return Err(anyhow::anyhow!("failure"));
}
Ok(())
} (I also imported the The Now, if you really just want to do classic Unix timestamp arithmetic, then I think it's acceptable to use use jiff::Timestamp;
fn main() -> anyhow::Result<()> {
let created: Timestamp = "2025-07-05T05:30-04".parse()?;
if Timestamp::now().duration_since(created).as_hours() > (7 * 24) {
return Err(anyhow::anyhow!("failure"));
}
Ok(())
} This gets you down to one import, no unwraps and no faffing about with the stricter but harder to misuse It's true that the library pushes you toward There is a gradient here that is perhaps hard to discover. It makes sense that people will want to use the library in the "most correct" way possible. And so they might "feel bad" about the most succinct solution I posted above. But I think this is an education problem that #24 touches on. I plan to try to address this in a Jiff book.
My guess is that you didn't know about the Apologies, but this is all I had time to answer tonight. I'll address your other points tomorrow. :-) |
Beta Was this translation helpful? Give feedback.
-
The terminology comes from phrases like, "3 months relative to 2025-02-01" or "1 year relative to 2023-04-01." The span of time "3 months" or "1 year" has no defined meaning unless it is interpreted relative to some specific datetime. I'm not really sure where the "CS perspective" comes in here. I don't really get why "civil/Zoned" would be better here. It's awkward to use. It's more like "relative civil date," "relative civil datetime" or "relative zoned datetime." Or, more generically, "relative datetime." The reason why
This makes sense to me. I expect people to chafe against this a bit since it's a somewhat novel approach to datetime library design. The only other library that has a type like Now, the "old school" approach isn't always wrong. If you don't care about time zones and just want to work with Unix timestamps and unambiguous duration units, then Jiff's
Yeah I think for something like this where you specifically don't care about time zones, I wouldn't use The original design of the library tried to get away with just a
I don't think it's unorthodox? The tuples are just targets for the
This is kinda interesting but feels a little too cute. IMO, the better answer is for you to just use
Where did you get the impression that the missing
This is an interesting suggestion. Particularly since I've suggested using |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
So I've read through the discussions on why
Span
doesn't implementPartialOrd
(performance when doing so in a semantically correct manner), but I'm wondering if it's possible to make it more expressive.This is the approach the design of the library has guided me towards using in order to determine if the difference between two timestamps exceeds a certain maximum (let me know if I'm wrong!):
This requires importing/qualifying four distinct types (
Timestamp
,Span
,SpanRelativeTo
, andGreater
), aside from figuring out and using the tuple trick.The lowest hanging fruit that requires no changes to the api would be to add
is_gt()
/is_eq()
/is_lt()
toSpan
(you could remove theis_
prefix, but then people might think it implementsPartialOrd
, not that that's the worst thing in the world), allowing the following syntax instead, getting rid of thematches!()
macro call and theGreater
type:At this point, the discussion diverges into a more aggressive change to the api. I don't mean to disparage the work, time, and effort you've already put into the library or to imply that you might not have already considered and rejected these design proposals in the past, but in the name of airing out the dirty laundry before 1.0 ships I feel it would be remiss not to point out a few things that have felt very awkward to me (sort of the opposite of 'the pit of success') when learning to use this library instead of
chrono
(which I've contributed to) and coming from years of experience with other libraries that have tried to tackle the very hard problem of designing a strictly correct but not impossible to use date/time library (such as Joda Time/Noda Time, the .NET DateOnly/DateTime/DateTimeOffset misadventure, etc).First, some observations (in no particular order) that have contributed to a mixed experience using the library:
The documentation about fallible
Span
operations uses the term "relative datetime" but the definition of this was hard to find (it's in the docs forSpanRelativeTo
). From a CS perspective, "relative" would make one think about the difference between two absolute values (vs the absolute value itself), but more concretely, sinceTimestamp
can be infallibly round-tripped to/fromZoned
, it's kind of weird to say that one is "relative" and one isn't, making it hard to logically infer what "relative" means in this context (I believe the intention is that the length of a day is relative? But with civil dates, the length of a day is assumed, making it absolute rather than relative?). I would humbly suggest using "civil/Zoned
" in place of "relative", assuming that is indeed the intended meaning.Most of the issues I encountered with the library had to do with the panicking/erroring behavior that occurs when using calendar units. I imagine I'm not the only one that's going to have this issue. I've read what I could find in the repo issues and PRs about the current behavior and have some reflections on the details of the current implementation (being careful not to revisit whether the approach to larger units itself is the right one or not),
While I personally would have been fine with the original jiff behavior with silently assuming 24 hour days by default unless a zoned/civil date(time) was provided, and would have resolved the ambiguities by extending
Timestamp
to assume 24-hour days by default as well; anecdotally when I was using calendar units to perform arithmetic onTimestamp
values and being personally aware that without specifying a date/timezone operations such as adding or subtracting n days would be a rough estimate at best, there was one instance where I admit that the current design (after running into panics at runtime because I assumedstd::ops::Sub
would haveOutput=Result<..>
if it could panic on routine math) led me to revisit the approach and have a more precise result (though it was admittedly outnumbered by the number of places I did indeed just want a 24-hour approximation of a day).I personally found the concept of using tuples to create a "tagged version" of the original value that has the
SpanRelativeTo::days_are_24_hours()
marker to be unorthodox. I understand how it simplifies/optimizes the design of the library types, but it's neigh undiscoverable without referencing the docs (and not at all via editor completions). Providing an api that allows opting into certain behavior is difficult without an explosion of types; generics would make this a lot easier but I'm assuming you've intentionally made a decision to forgo their use. The obvious suggestions that occur to me:FixedLength
counterpart toUnit
that can be silently assumed to have larger calendar units built on a 24-hour day that can be used everywhereUnit
can be used;Unit
that convert larger calendar units into hours, e.g.Unit::from_days()
orUnit::hours_from_days()
, etc. bypassing the matter altogether (which would require minimal changes to the rest of the library, as the result would beUnit::Hour
but created from a convenience function) - subjectively, quite a lot of the times when grepping codebases for "days" or "weeks" they are simply out of convenience for not having to specify 86400 or 604800 seconds;Unit
denoting that it's safe to assume 24-hour days (can be done with bitwise operators without taking more space via an struct-enum hack), then adding afn assume_24_hour_days(self) -> Self
member to set that bit, removing the need to use a tuple to associate aUnit
with a marker;Error
to internally include the would-be result if 24-hour days were assumed, then add a helper method toResult<_, Error>
to assert 24-hour days post-facto, converting a would-be-error into success; this is possibly best illustrated by example:Zoned
contexts for cases where fixed-length days should not be assumed.I've mentioned the question of making
std::ops::Sub/Add
return a result instead of panicking elsewhere, esp. given how easy it is to run into the relative calendar units problem;It's hinted at with the first part of this issue, but I'm uneasy with the lack of
PartialOrd
andEq
forSpan
values; it really does drive one towardsSignedDuration
which is rather unfortunate since it's not the intended way to hold the library. I wonder if documenting repeatedly the possible performance gotchas of using theEq
andPartialOrd
impls would be sufficient to avoid the current song and dance needed to compare spans? Also (and only from an abundance of precaution), I am unsure if thefieldwise()
method might lead unwary devs to incorrectly using that to perform equivalence checks? I really don't see much use for the fieldwise operation in general date/time-programming aside from unit testing the library itself; I wonder if it's possible to perform a semanticeq()
(if not alsocmp()
) operation fast enough with some clever tricks to make it acceptable. Otherwise, maybe lean intoSignedDuration
more and have diffing twoTimestamp
values returnSignedDuration
instead ofSpan
, as that's probably the most common/likely reason to compare?Beta Was this translation helpful? Give feedback.
All reactions