Jiff: Datetime library for Rust
github.com
github.com
let span = 5.days().hours(8).minutes(1);
It feels sort of weird how the first number appears in front, and then all the other ones are function arguments. I suppose if you don't like that you can just write: let span = Span::new().days(5).hours(8).minutes(1);
at the expense of a couple characters, which is not too bad. Span::new(days=5, hours=8, minutes=1)It would have to look something like:
f({ hours: 2, seconds: 53, ..Default::default() })
The defaults could come from some value / function with a name shorter than Default::default(), but it would be less clear.- leaving some mandatory fields
- reduce the need for the builder pattern
- enable the above to be written as f(S { hours: 2, seconds: 53, .. })
If that feature ever lands, coupled with structural/anonymous structs or struct literal inference, you're getting everything you'd want from named arguments without any of the foot guns.
It got postponed because it wasn't going to make it into Rust 2018: https://github.com/rust-lang/rfcs/pull/1806#issuecomment-327...
1: https://internals.rust-lang.org/t/pre-pre-rfc-syntactic-suga...
I didn't even think about the builder thing, but you're right there too.
https://internals.rust-lang.org/t/pre-rfc-named-arguments/16...
It doesn't think about optional arguments (but somehow does include overloading). And a bit in a related fashion, it doesn't permit for reordering calling arguments, which I consider a downside:
> Reordering named arguments when calling
> No it is not possible. Just like unnamed arguments and generics, named arguments are also position-based and cannot be reordered when calling: register(name:surname:) cannot be called as register(surname:name:).
> Reordering them at the definition site is an API break, just like reordering unnamed arguments or generics is an API break already.
The rationale for this expressed in the comments says it's incompatible with overloading, but to me I don't see why named arguments and overloading should go hand-in-hand—or, indeed, how desirable overloading is in the first place. Or why should overloading be able to overload that kind of scenario. The other reasons for this don't seem really problems at all.
> fn func2(pub name: u32, name hidden: u32) { /\* ... */ }
> fn func3(name hidden1: u32, name hidden2: u32) { /* ... */ }
> func2 and func3 could work in theory: named arguments as proposed in this RFC are position-based and their internal names are different: just like two arguments can have the same type without ambiguity, those functions could be allowed.Maybe there are technical reasons that are simpler when considering type compatibility between function types that have or don't have labeled arguments? Seems the proposal has misunderstood something about OCaml labeled arguments when placing it under https://internals.rust-lang.org/t/pre-rfc-named-arguments/16... , though.
In addition the proposal doesn't seem to have a neat syntax for forwarding named parameters, like in constructing records you can just fill in a field called foo by mentioning its name by itself—or, like in OCaml you can have
let foo ~bar = bar + 1
let baz ~bar = foo ~bar
let main () = baz ~bar:42
If it used the .-prefix as mentioned as an idea elsewhere, then this too could be naturally expressed.Maybe there are other ideas how to go about the labeled arguments, though that one seems pretty well thought-out.
One thing I've enjoyed with Python (and Mypy) is the ability to require the caller to use named arguments with the asterisk marker in the parameter list. This idea is mentioned in the proposal.
However, many were opposing overloading the same pre-rfc was suggesting.
I also feel at this point it's basically a wontfix, but that's more because it's both controversial and huge, and arguably not as high of a priority as other things even if it weren't those two things. The reason it gets huge is that you really want to consider all three of {optional, named, variadric} at roughly the same time.
I do think that people are generally more supportive of variadric than optional/named.
Currently using vs code with C++, I like how it handles the missing language feature by adding a grayed out parameter name before the value for function calls and initializers. Maybe there is something like that for rust.
use jiff::{Timestamp, ToSpan};
fn main() -> Result<(), jiff::Error> {
let time: Timestamp = "2024-07-11T01:14:00Z".parse()?;
I seem to remember Rust does that thing with interfaces instead of classes, is it that? How come I import a library and all of a sudden strings have a `parse()` method that despite its generic name results in a `Timestamp` object? or is it the left-hand side that determines which meaning `str.parse()` should have? What if I have two libraries, one for dates and one for say, Lisp expressions that both augment strings with a `parse()` method? Why use this syntax at all, why not, say, `Timestamp.parse( str )`? I have so many questions.Its return type is generic, and here it's inferred from the left hand side. It's implemented using the `FromStr` trait, and you can equivalently write `Timestamp::from_str(t)`.
You're thinking of the "extension trait" pattern for using traits to add methods to existing types when the trait in scope, but that's not what's going on here. Jiff's `ToSpan` mentioned above is an example of that pattern, though: https://docs.rs/jiff/latest/jiff/trait.ToSpan.html
There will never be two libraries that clash because of Rust’s orphan rule: you can only implement either a trait which you define on any type, or define a foreign trait on a type which you define, so there’s no way for some random library to also ship an implementation of FromStr for Timestamp
> let time: Timestamp = "2024-07-11T01:14:00Z".parse::<Timestamp>()?;
- let time = Timestamp::parse("2024-07-11T01:14:00Z")?;
- let time: Timestamp = "2024-07-11T01:14:00Z".parse()?;
- let time = "2024-07-11T01:14:00Z".parse::<Timestamp>()?;
You’re free to choose whatever you prefer, although the compiler needs to be able to infer the type of time. If it can’t, it’ll let you know.
So a fourth option is allowed, as long as the subsequent lines make the type of time unambiguous.
- let time = "2024-07-11T01:14:00Z".parse()?;
This is a direct consequence of Timestamp implementing the FromStr trait.
let time = Timestamp::from_str("2024-07-11T01:14:00Z")?;
I think you meant :)Not being snarky, but I suggest starting by reading at least a little about traits? None of your questions are really about this library - it's just FromStr and an orphan rule.
> `strongman.open( door )` is just that much clearer, flexible and explicit.
Where the strongman came from? Entirely different semantics.
let span = 5.days() + 8.hours() + 1.minutes();It would be plausible to make `+` for spans do _only_ component wise addition, but this would be an extremely subtle distinction between `+` and `Span::checked_add`. To the point where sometimes `+` and `checked_add` would agree on the results and sometimes they wouldn't. I think that would also be bad.
So I started conservative for the time being: no `+` for adding spans together.
I'm thinking something along the lines of how ActiveSupport::Duration works:
>> 4.years + 5.months + 3.weeks + 2.days + 1.hour + 4.minutes + 10.seconds
=> 4 years, 5 months, 3 weeks, 2 days, 1 hour, 4 minutes, and 10 seconds
Of course the downside being that it would need to be sized large enough to contain each component, even though they may be rarely used. use jiff::{ToSpan, Unit, Zoned};
fn main() -> anyhow::Result<()> {
let span1 = 1.year().months(3);
let span2 = 11.months();
let now = Zoned::now().round(Unit::Minute)?;
let added = span1.checked_add((span2, &now))?;
println!("{added}");
Ok(())
}
Has this output: $ cargo -q r
P2y2m
Notice how the months overflow automatically into years. Days will do the same into months. You need a reference point to do this correctly. For example, just `span1.checked_add(span2)?` would produce an error.In contrast, component wise addition would lead to a span of `1 year 14 months`. Which is a valid `Span`. Jiff is fine with it. But it's different than what `checked_add` does. Having both operations seems too subtle.
Also, I don't really think using the `+` operator just to construct spans is that much of a win over what Jiff already has. So that needs to be taken into account as well.
Span::add_with_reference(&self, other: &Span, reference: impl Into<SpanRelativeTo>) use jiff::ToSpan::days;But it's much less simple then it seems.
Because `use Trait::method` would not be one (potential generic) method but a group of them so it would be it's own kind of thing working differently to free functions etc. Furthermore as generics might be on the trait you might not be able to fill them in with `::<>` and even if you fill them in you also wouldn't be able to get a function pointer without having a way to also specify the type the trait is implemented on.
All of this (and probably more issues) are solvable AFIK but in context of this being a minor UX benefit its IMHO not worth it, 1. due to additional compiler complexity but also due to 2. additional language complexity. Through maybe it will happen if someone really cares about it.
Anyway until then you can always define a free function which just calls the method, e.g. `fn default<T: Default>() -> T { T::default() }`. (Which is probably roughly how `use` on a trait method would work if it where a thing.)
The original looks like something Ruby would do.
> let span = 5.days().hours(8).minutes(1); # "original"
Actual ruby (with Rails extensions; stock ruby doesn't do this)
irb(main):001> now = Time.now
=> 2024-07-23 08:31:53.656455305 -0400
irb(main):002> now + 4.days + 8.minutes + 2.hours
=> 2024-07-27 10:39:53.656455305 -0400
So the version of his that you like for "consistency" is far more closely aligned to what ruby/rails _actually_ does. let span = Span::days(5).hours(8).minutes(1);For anyone having that thought, try reading through the design document of Jiff (https://github.com/BurntSushi/jiff/blob/master/DESIGN.md), which, as all things burntsushi do, is excellent and extensive. Another good read is the comparison with (mainly) chrono, the de facto standard datetime library in Rust: https://docs.rs/jiff/latest/jiff/_documentation/comparison/i...
Stuffs like DST arithmetic (that works across ser/de!), roundable duration, timezone aware calendar arithmetic, retrospective timezone conflict detection (!), etc. all contribute to a making the library correct, capable, and pleasant to use. In my experience, chrono is a very comprehensive and "correct" library, but it is also rigid and not very easy to use.
I was also using JodaTime back when some people still though Eclipse was better than IntelliJ IDEA.
But there's nothing in that document that contradicts: "just represent duration as nanoseconds".
Users needs to see timezones and correct hour depending on DST, sure. Programs typically do not. Unless you're working on stuff specifically dealing with different timezones, it's usually a very safe bet to: "represent duration as milliseconds/nanoseconds".
That humans have invented timezones and DST won't change the physics of a CPU's internal clock ticking x billion times per second.
Just look at, say, the kernel of an OS that didn't crash on half the planet a few days ago: there are plenty of timeouts in code expressed as milliseconds.
Reading your comment could be misinterpreted as: "We'll allow a 30 seconds cooldown, so let's take the current timezone, add 30 seconds to that, save that time as a string with the time 30 seconds from now, complete with its timezone, DST, 12/24 hours representation and while we're at it maybe add exta code logic to check if there's going to be a leap second or not to make sure we don't wait 29 or 31 seconds, then let the cooldown happen at the 'correct' time". Or you could, you know, just use a freakin' 30 seconds timeout/cooldown expressed in milliseconds (without caring about whether a leap second happened or not btw because we don't care if it actually happens after 29 seconds as seen by the user).
Increasingly we are programming in distributed systems. One milli or nano on one node is not a milli or nano on another node, and that is physics that is more inviolable.
It may depend in some cases, but as soon as you add network latency there will be variance regardless of the tool you use to correct for variance.
Try adding one year to a timestamp because you're tracking someone's birthday. Or add one week because of running a backup schedule.
But there's a whole world above and beyond timestamps.
Even something as simple as schedulling a periodic batch process.
uhg, I can't believe it took me this long to realize why Serde crate is named that!
So yeah keeping things in Unix time is great if all your doing is reading back timestamps for when an event occurred, but the moment you have to schedule things for humans, everything is on fire.
Anyone thinking datetimes are easy, should not be allowed near any schedulling or date processing code!
Where? Maybe people downplay storing dates but not making a library.
Does anyone know why burntsushi is making this new library? I haven't messed around with times in rust much, but do the existing libraries have performance problems? Or are the existing API's awkward to use? Or is he just doing it for fun, or some other reason?
https://github.com/BurntSushi/jiff/blob/master/DESIGN.md
https://github.com/BurntSushi/jiff/blob/master/COMPARE.md
TLDR: API and IANA timezone support primarily.
https://www.joda.org/joda-time/ https://www.baeldung.com/java-8-date-time-intro
Temporal benefits from the JodaTime/Java 8+ date work, but also includes more recent IETF and IANA standards as other influences.
I hope somebody will rectify this at some point. Jiff seems like a step in a right direction but the syntax is sometimes weird. I guess I’d wellcome something more predictable
> Jiff seems like a step in a right direction but the syntax is sometimes weird. I guess I’d wellcome something more predictable
Can you say more? How can Jiff be better?
On a serious note, any rustaceans in here know the reason the crate doesn't use something like `tracing`? A bit too heavyweight maybe -- it is about half the size on crates?
`log` is of course fine, and I don't know that tracing down to the calls for tz operations is a normal use case, but always interested to know if there was a specific why here.
But no, I'm sure `tracing` would have worked fine too.
Here is the relevant Jiff issue with more details: https://github.com/BurntSushi/jiff/issues/7
UNIX timestamps don't use the SI second definition (instead it's 1/86000th of the current day, all UNIX seconds don't have the same duration) which breaks all correct duration computations. I understand that the tradeoff was to inherit compat with older time tracking methods and enable faster calendar formatting, I disagree that it was the right trade-off. UNIX timestamps mix representation concerns with data. In my opinion, leap seconds should be treated exactly like the 29th of February or time zones.
Whenever people talk about leap seconds, it always seems to be in some abstract notion. But it's very rare to see folks connect them to real world use cases. I get the scientific use case, and I feel like that's well served by specialized libraries. Do we need anything else? I'm not sure that we do.
I want to be able to compute durations between timestamps stored in the DB, received from API calls or retrieved from the system and get the right duration "out of the box". Computing these durations lets me apply business logic relying on it. A message can be editable for x amount of time, a token is valid for y amount of time, a sanction expires after z amount of time, etc.
For example, I want to issue some token valid for 60s. What should I set the expiry time to? `now + 60s`. Except if `now` is 2016-12-31T23:59:30Z, then most libs will return a time 61s in the future.
1 second may not be a big error, but it's still an error and depending on context it may be relevant. This is a systematic error unrelated to time sync / precision concerns, so it's pretty frustrating to see it being so common. It seems though that we won't have any new leap seconds in the near future so eventually it will just become a curiosity from the past and we'll be stuck with a constant offset between UNIX and TAI.
> I feel like that's well served by specialized libraries.
Agreed that you need a specialized lib for this, but my point is that _you shouldn't have to_ and the current situation is a failure of software engineering. Computing `t2 - t1` in a model assuming global synchronized time should not be hard. I don't mean it as a personal critique, this is not an easy problem solve since UNIX timestamps are baked almost everywhere. It's just disappointing that we still have to deal with this.
A few months ago, Jiff did have leap second support. It worked. I know how to do it, but none of the arguments in its favor seem to justify its complexity. Especially when specialized libraries exist for it. You can't look at this in a vacuum. By make a general purpose datetime library more complex, you risk the introduction of new and different types of errors by users of the library that could be much worse than the errors introduced by missing leap second support.
Agreed, I can easily imagine that it could cause situation where some numeric value is not interpreted correctly and it causes a constant offset of 37 seconds. UNIX timestamps are entrenched, so deviating from it introduces misuse risks.
Regarding my use-cases, I agree that these ones should still work fine. I could also come up with issues where a 1s error is more meaningful, but they would be artificial. The main problem I can see is using some absolute timestamp instead of a more precise timer in a higher frequency context.
Overall, it's the general discussion about correctness VS "good enough". I consider that the extra complexity in a lib is warranted if it means less edge cases.
Yeah I just tend to have a very expansive view of this notion. I live by "all models are wrong, but some are useful." A Jiff timestamp is _wrong_. Dead wrong. And it's a total lie. Because it is _not_ a precise instant in time. It is actually a reference to a _range_ of time covered by 1,000 picoseconds. So when someone tells me, "but it's not correct,"[1] this doesn't actually have a compelling effect on me. Because from where I'm standing, everything is incorrect. Most of the time, it's not about a binary correct-or-incorrect, but a tolerance of thresholds. And that is a much more nuanced thing!
[1]: I try hard not to be a pedant. Context is everything and sometimes it's very clear what message is being communicated. But in this context, the actual ramifications of incorrectness really matters here, because it gets to the heart of whether they should be supported or not.
One of the most common and high-impact failure mode is crashing when parsing a leap second that is represented as "23:59:60".
Jiff is able to parse leap seconds, and beyond that, I doubt that there are many scenarios where you care about leap seconds but not enough to use a library that supports them.
I can embed a big list into my application, but then I have to remember to periodically update it until 2035, or maybe longer if they decide to keep leap seconds after all. So I can try to download a leap-seconds.list from somewhere, except that only a few sources set "Access-Control-Allow-Origin: *", and none promise to keep it set indefinitely.
The annoying part is, applications on both Windows (since 10) and Unix-like systems generally have access to up-to-date leap-second information; at worst, browsers can be expected to be updated regularly. But since the JS standards devs want to stick strictly to POSIX time, they provide no means to obtain this information from the inside, through Date, Intl.Locale, or the proposed Temporal.
This is all to say, I would really love to always have some way available to get "all leap-second info that the current system knows about, if it does have any such info". For its part, hifitime either downloads from a fixed URL (which doesn't have that Access-Control-Allow-Origin header IIRC), consults a fixed list, or gets the user to locate the info, which isn't nearly as general or foolproof as it could be.
(Native speaker here - but of Dutch, the language known for its proper hard Gs. Unless you're from the South.)
Over my dead body!
† pronounced "ch" as in "chase" of course, i.e. "trono"
> Jiff is pronounced like "gif" with a soft "g," as in "gem."
Heh, I see what they did there…
Now that I'm back at my desk I had to check ISO8601, that suffix is not included. However it does look like an extension - RFC 9557 - which looks like is still in proposed state.
I would personally caution using these suffixes until wider adoption, because AFAIK the Olson database names themselves are not standardised on non-POSIX systems (i.e you might have a hard time on Windows).
The database itself may not be available but there are various implementations including Windows https://data.iana.org/time-zones/tz-link.html#software
https://learn.microsoft.com/en-gb/archive/blogs/bclteam/expl...
I believe java.time also uses this syntax. I'm addition to Temporal.
It enables lossless round-trips of zoned datetimes. See: https://docs.rs/jiff/latest/jiff/_documentation/comparison/i...
This is interesting, in that Rust with Chrono is the best datetime experience I've had in any language.
I think the author is actually looking for the GPL but doesn't realise it yet. Unlicense can't make something free forever, no matter how hard the author wishes it. GPL can. In other words, Unlicense/MIT is idealistic, GPL is pragmatic. You can't turn off copyright, but you can make it work for the people instead of against them.
And almost the whole Rust ecosystem is MIT.
Ironic for a "free" software license.
It would be great if there was a license somewhere inbetween GPL and MIT: you'd be required to upstream (or make available) any changes you made to the parts of other people's code you're making use of, but not required to open your entire codebase.
And thereby severely guarantees the freedom of said end-users.
The freedom to deny the freedom of another person is not a freedom worth discussing.
The author expressly dislikes copyright. GPL is still the only real cure to copyright. "Permissive" licences are corporate friendly. They allow corporations to take what they want and give back nothing. In this day and age it's more important than ever to empower individuals and limit the growth of corporations/oligopolies.
If that was true, you wouldn't be doing just that.
The person who was explicitly defending non-GPL licences was rahkiin. I don't know how they'd respond to your challenge, but here is how I would:
"The freedom to deny the freedom of other people" is impossibly vague, because "the freedom of other people" can mean zillions of things. It's also confusing to talk about since we have two separate freedoms here, so let's talk about the freedom(1) to deny the freedom(2) of other people.
Suppose we put "the freedom to kill other people" in the freedom(2) slot. Most of us think that isn't a freedom people are entitled to, so the freedom(1) to deny that particular freedom(2) would be a good thing.
Suppose we put "the freedom to breathe the air" in the freedom(2) slot. Most of us think that is a freedom people are entitled to, so the freedom(1) to deny that particular freedom(2) would be a bad thing.
In the present case, what goes in the freedom(2) slot is something more complicated and less clear-cut -- it isn't a Super-Obvious Fundamental Human Right like the right to go on breathing, but it also isn't a Right To Do Very Evil Things like the right to murder.
It's something like "the freedom to read and modify the source code of a particular piece of software". We demonstrably don't presently have that freedom as regards many widely-used pieces of software; the world's legal systems pretty much unanimously agree that if you put this in the freedom(2) slot then the freedom(1) to deny it is worth having.
Why? Well, the usual arguments would be (1) that creating something gives you some rights to limit what other people do with it, and (2) that giving creators some such rights is a good thing overall because it increases the incentives for people to create nice things.
Of course you might disagree! (And, also of course, even if you agree with #1 and #2 in the abstract you might think that "intellectual property" law as currently implemented across the world is a very bad way to get #1 and #2.) But I hope your reasons are a matter of thinking carefully about the tradeoffs involved, not just of saying "yay freedom" and therefore denying every instance of "it's good for X to have the freedom(1) to deny Y's freedom(2) to do Z".
Not least because you literally can't consistently do so in every case -- if you say no one should ever have the freedom(1) to deny freedom(2) to others, whatever specific freedom(2) may be, then what you are calling for is precisely to deny that freedom(1) to others.
Nobody denies anyone anything. If $corp never makes a piece of software, the users never had rights to begin with. If $corp makes it but doesn't open source the software, the users still never had rights to begin with. It is not "denying people freedom" to exercise one's right to make software which is not open source.
> They allow corporations to take what they want and give back nothing.
This is, and has always been, a very weak argument. When a developer makes a public gift of their software, it is very churlish indeed to complain when someone makes use of that gift. It's also pretty hypocritical, unless you're going to make the same complaint about individuals using open source software and not giving back.
Almost all of the Rust ecosystem is dual licensed under MIT/Apache 2.0, so this combination is a bit unusual. But the presence of MIT means that it hasn’t been a problem in practice.
There’s a stackoverflow post that discusses some of the issues https://softwareengineering.stackexchange.com/questions/1471...
(I collected that mostly because I didn’t find all the relevant information in one place, or explanation of the reasonable alternatives.)
You may not agree with me, which is fine, but you should now understand.
It’s so painful to come from something like the JVM where time operations and formed in near natural language. From a quick glance, this looks even better than java.time.
negative duration:
duration in the reverse direction to the proceeding time scale
Matching "physical reality" is a non-goal. What's important is modeling the problem domain in a way that makes sense to programmers and helps them avoid mistakes. A negative span doesn't give you any extra expressivity that "subtract a span from a datetime" doesn't already give you. Both are a means to go backwards in time. And they line up with human concepts that talk about the past. So for example, if I say, "I went camping 1 year ago." I am expressing a concept that is modeled by a negative span.And there are also just practical benefits to allowing a span to be signed. For example:
use jiff::{ToSpan, Unit, Zoned};
fn main() -> anyhow::Result<()> {
let now = Zoned::now().intz("Europe/Kyiv")?;
let next_year = now.checked_add(1.year())?;
let span = now.since((Unit::Month, &next_year))?;
println!("{span}");
Ok(())
}
Has this output: $ cargo -q r
-P12m
If negative spans weren't supported, what would the behavior of this routine be? It could return an error. Or maybe an out-of-band sign. I'm not sure. But this seems like the most sensible thing.And of course, Temporal has negative durations too. This design was copied from them.
Your watch example is actually an important use case that specifically isn't served by Jiff. You should be using monotonic time (in Rust, that's `std::time::Instant`) for things like that. In that case, you really do get a guarantee that the duration between two instants (one in the past and one in the future) is non-negative. And if it weren't, then that would be worthy of an assert. But you get no such guarantees with system time.
Expressing a before/after relationship requires that we establish what is the anchor point. Is it the present time experienced in my reference frame? Is it unix epoch? Using a negative number to denote a disembodied "duration before" leaves me guessing about the anchor. In the case when we want to express the timestamp "3 days before/after time t0" I think it's probably best to say something like:
t0.before(3.days()) // -> Timestamp
t0.after(3.days())
This isn't a criticism of your library, it looks awesome and I'll definitely use it. I am fairly surprised that negative duration definition made it into the standard though.There are also negative years, even though that doesn't really correspond to anything "real" either.
Here's the thread about signed durations for Temporal: https://github.com/tc39/proposal-temporal/issues/558
Apparently signed durations are a common feature in virtually every datetime library.
That makes it mostly uninteresting to me; nice api is nice to have, but I'd personally appreciate correct results more.
From the wider ecosystem C++ std::chrono seems like the only one that shows some promise on this front. Last I checked the implementations were not there quite yet though, and the API definitely didn't seem all that pleasant. Maybe in couple of years we'll see how it'll work out.
Hifitime 4.0 seems like almost the only option at this point, and it is in early alpha still.
I recall using astropy at one point just for time calculations, but it is quite overkill solution.
The quest for perfect datetime lib (for any language) continues.
Leap seconds are just to be replaced by a yet to be defined adjustment, likely leap minutes.
If you don't like leap seconds and don't care about a small (but increasing) deviation from Earth's angle you can do so today: Just use TAI.
> I appreciate that it was at least informed decision, and seems to have been tough call to make
but you are right, having that link here helps others.
Can you also say why you need the precision? Like, what's the use case? What happens if your program computes durations that are off because of leap seconds?
Finding a trajectory that matches several candidate detections is called “linking” and it is very sensitive to time. Being off by even one second will result in a predicted position which is far off course, and so a candidate asteroid detection will not be linked.
Linking is not quite sensitive enough to demand a relativistic time scale, but definitely sensitive enough to require correct leap seconds.
Like yes, scientific applications are a very valid use case for this. But scientific applications usually want other things not afforded by general purpose datetime libraries, like large time ranges and high precision. What I ask in that issue, and what I don't understand, is why folks who want leap second support aren't happy with using specialized libraries for that task, and instead request that leap second support be added to general purpose datetime libraries.
Since Jiff hopes to be ubiquitous (I think? Seems that way) it would be nice if this sort of thing could be avoided. Time is such a fundamental in many APIs that having one common library is very important.
IDK, maybe there is a way to unify everything in a satisfying way, but they seem way too different to me.
One random use-case (reminded by other thread on the front-page) is that I occasionally have needed to analyze some logs, and get some stats from the data. For example having logs like "2024-07-24T14:46:53.123456Z Foo id=42 started" and "2024-07-24T14:46:54.654321Z Foo id=42 finished", and wanting to get some histogram on how long "Foo" took.
Sure, ideally you'd have some explicit metrics/tracing system or some other measurements for getting that data, but unfortunately in practice that is not always the case and I have to make do with what I have.
Or even more simple, I just want to sort bunch of events to establish a timeline. UNIX style time handling makes that difficult.
NTP adjustments can also cause problems in these sort of cases, but at least the systems I work with are usually kept in relatively tight sync so the "window of uncertainty" is much less than 1s.
I found Hifitime to be very opinionated and give a false sense of security due to its automatic computation of leap seconds based on historical tables. Yes, leap seconds are announced some ~6 month in advance, but what if you don't update regularly the library? Or if you can't because it is deployed on an embedded system?
In the end I wrote my own minimalistic TAI timestamp library [1] and made the conscious decision to let the user take the responsibility to deal with leap seconds in UTC conversion.
The original tag line is "Jiff is a datetime library for Rust that encourages you to jump into the pit of success."
+1
I don't understand why people are downvoting your comment. This title is absolutely a violation of HN guidelines. And a very blatant one no less!
Do people not read https://news.ycombinator.com/newsguidelines.html anymore?
And "Please don't comment about the voting on comments. It never does any good, and it makes boring reading."; your comment is commenting about voting, and this guideline sets a precedent that boring reading is not desirable.
and "Please don't comment on whether someone read an article. "Did you even read the article?"" which you are doing about the guidelines.
and "Please don't complain about tangential annoyances—e.g. article or website formats, name collisions, or back-button breakage. They're too common to be interesting." which reinforces the idea that uninteresting comments are not deisable. "The title is bad" is not a very interesting comment.
Pet annoyance recently is seeing people comment "why am I being downvoted? What I said was true" when what they said was arguably true - but also low effort, flamebait, tangent, off-topic, or similar; "it is correct" alone isn't enough. Since this has been annoying me I want to write more, here are examples:
Searching Algolia for 'downvoted': https://news.ycombinator.com/item?id=41039279 which says "Microsoft is like Disney, they steal from others and trounce others for stealing from them. Absurd people." followed by "Sad to see this extremely historically accurate and relevant comment downvoted.". It's arguably true - but also arguably false - but definitely a single line of low effort flamebait. Actually justifying the claim would be better.
And https://news.ycombinator.com/item?id=41027729 - about Kamala Harris "I hate to say it yet I don't think she's polling well because even in 2024 I don't think America is ready to allow a woman of colour be POTUS." reply "You’re downvoted, but you’re correct.". Maybe it is correct, but they could at least argue the case for their claim? Something more substantial than a one line which is basically "America racist"? ("Eschew flamebait. Avoid generic tangents. Omit internet tropes." - guidelines).
and https://news.ycombinator.com/item?id=41008957 CrowdStrike thread -> substantive comment on centralised control and security -> risks of splaying personal valuables out in front of everyone at security -> security tried to screw me because they had to do their job -> I got downvoted "by some swede who doesn't think this stuff can happen"; assuming the downvote is because people don't believe it happened, rather than because it's marginally - if at all - relevant, substantive, thoughtful, interesting.
impl Decimal {
...
/// Returns the ASCII representation of this decimal as a string slice.
pub(crate) fn as_str(&self) -> &str {
// SAFETY: This is safe because all bytes written to `self.buf` are
// guaranteed to be ASCII (including in its initial state), and thus,
// any subsequence is guaranteed to be valid UTF-8.
unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
}
}
https://github.com/BurntSushi/jiff/blob/08dfdde204c739e38147...How much performance does `from_utf8_unchecked` buy us over `from_utf8`? It's saving iterating over a 20-byte array. Because the array fits in a single cache line, the iteration will run at the CPU's internal clock rate. I expect that a benchmark would not be able to detect the CPU time saved. Even a program that was only serializing these Decimal types at GB/s, the difference would be barely measurable.
As software engineers, about 80% of our time is spent coding. And about 80% of coding time is spent learning how the thing works. If we prioritize code clarity, we speed up 64% of our future work.
Performance optimizations always make code less clear. Using `from_utf8_unchecked` is a performance optimization. It makes the code less clear so it needs a three-line comment.
Engineering is all about trade-offs. Performance optimizations can be worth their clarity cost when they bring tangible benefits. Therefore, we need to measure. The comment doesn't say "Using `from_utf8_unchecked` because it takes 1ns while `from_utf8` takes 5ns." To make sure we're making a good tradeoff between clarity and performance, we need to measure the impact of the optimization and document it in the code.
Using `unsafe` also has a cost. Safety depends on invariants. Future changes to the code can break these invariants. The compiler is the most reliable method for detecting invariant-breakage. Let's take advantage of it. This means using `unsafe` only when its use brings tangible benefits that outweigh the risks.
I started my professional life as a Windows sysadmin. Dealing with worms and viruses was a large and unpleasant part of my job. During university, I learned about formal verification of software. It has great potential for making our software secure. Unfortunately, the tools are very hard to use. To verify a program, one must translate it into the language of the verification tool. This is a lot of effort so few projects do it. Now with Rust, we get verification FOR FREE! It's amazing!!!! So let's use it and move our industry forward. Let's make worms, viruses, ransomware, and data theft, into things from the barbaric early days.
We've had discussions before about the use of `unsafe` in Rust and we have a pretty clear disagreement about how to balance the trade-offs surrounding it. I have zero interest in re-litigating that discussion with you. I also personally find your conversational style condescending, which makes it difficult for me to talk to you.
The very fact they are there, still used, and so on, contradicts "unnessary [sic]". Sure, it might be outdated now, or technically better alternatives might be there.
But, in the end, software that deals with "The Real World" is going to be a complex, illogical mess. Because the real world is a complex, illogical mess. We could make a time that is global, counts resonant frequency of atoms. While technically superior, I will continue saying "The job took me 3h25 minutes" and not "The job took me 113,066,170,771,000,000 cycles", or even "The job took me 113066 Tera-cycles." or such. Messy, illogical and complex is often simply more practical. If only because "everyone does it that way".
Or conversely, is a specified instant considered "tomorrow"?
I once built a calendar and reservation system for a theatre that did movies, theatre, concerts and parties. A party that starts Friday at 22:00 and ends at 1:30 doesn't end "tomorrow" in people's minds, it ends Friday night.
(And a party, concert or movie that technically starts Sunday, 00:15 still has to show up on Saturday night 00:15, because that's how everyone looks for it)
Time handling is one of the many cases where there is actually complexity in the domain, and if you refuse to model the complexity, that doesn't make the complexity go away; it just means you've got bugs.
How could be erased those panic! methods that are used in most of Rust's libraries is something that may be is beyond the possible? beside is promoted from all the Rust's tutorials and reference code.
So much correctness in the Rust language just for to promote to all the community to crash the program from libraries without handling the error is something I can not understand.
I hope this philosophy do not reach the Linux kernel.
Build in release a hello world, Build in release the example of this library (replacing the assert_eq by a println!("{}", zoned) or other ), or even a different library if it have a large dependence tree. Open on a text editor both files. The presence of the string messages for "unwrap" increased, right?
One can observe the code in this library take care of making reach the user the errors in most the parts of the library, so:
how was increased the string messages for "unwrap"? How can one know from were they came from? One ends up having to read all the dependencies^5, from were almost always a unwrap() reach the runtime of the release build.
It's arguably quite possible, though not as straightforwards as one may hope. For example, there's no_panic, which results in a linker error if the compiler cannot prove a function cannot panic [0], albeit with some caveats.
> So much correctness in the Rust language just for to promote to all the community to crash the program from libraries without handling the error is something I can not understand.
Is there that much "promoting" of unchecked unwrap()/expect()/etc. going on? How do you distinguish that from "genuine" cases of violations of the programmer's assumptions?
I ask because Result/? along with libraries like thiserror/anyhow/etc. are right there and arguably easier/more concise, so unwarranted unwrap()/etc. would seem "harder" to write/justify than the alternative. The main exception I can think of are more one-off cases where the author is intentionally sacrificing robust error handling for the sake of speed/convenience, but that's a more language-agnostic thing that pretty much "doesn't count" by definition.
> I hope this philosophy do not reach the Linux kernel.
IIRC this is being worked on, especially given Linus's position on panics in the kernel.
More like promoted indirectly, I think by being used widely on reference code and tutorials the programmers absorbs as a familiar and quick to write method without planning much. And at same time by not being actively promoted that such methods should not be used within a library runtime or similar at least, because many people do not see it as wrong, what convert it in philosophy I guess.
When the dependency chain of library loading is fired, almost always I checked some unwrap ends within the program's runtime, so distinguishing whether those are genuine cases of violations (IMHO they can't be genuine if a lib can panic the program), or if it was just a unfinished prototyping part or etc, I think is not exactly important as individual until it reach terms of generalized behavior along the language libraries, and even seen in some programs of the community.
> IIRC this is being worked on, especially given Linus's position on panics in the kernel.
These are good news
For references/documentation/tutorials I think the use of unwrap() and friends is a tradeoff. It (arguably) allows for more focused/self-contained examples that better showcase a particular aspect, though with the risk that a reader uses those examples as-is without taking other factors into consideration. There's also the fact that documentation examples can be used as tests, in which case use of unwrap() in docs/examples/etc. is arguably a good thing.
> And at same time by not being actively promoted that such methods should not be used within a library runtime or similar at least, because many people do not see it as wrong, what convert it in philosophy I guess.
I think it might depend on where you're looking. For example, the Rust book has a section titled "To panic! or Not to panic!" [0] which outlines some things to consider when deciding whether to panic/call unwrap()/etc. Not sure if that counts as active promotion, but the fact it's in official docs should count for something at least.
> IMHO they can't be genuine if a lib can panic the program
I feel this is a rather strong position to take. Given how panics are intended to be used (handling unexpected state/precondition violations/etc.), it seems it seems akin to saying "just don't write bugs", which would certainly be nice but isn't really realistic for the vast majority of development. I suppose one could hypothetically bubble up every possible error but that comes with its own maintainability/readability/etc. costs.
In addition, that stance seems similar to stating that there's no "genuine" assertion failures or similar in libraries, which seems... bold? What would the alternative be?
> or if it was just a unfinished prototyping part or etc
At least in Rust there's todo!() and unimplemented!() which more directly convey meaning.
[0]: https://doc.rust-lang.org/book/ch09-03-to-panic-or-not-to-pa...
That is indeed exactly what is being said as far as I can tell. And yes, it's exactly as ridiculous as you think it is.
I'll link my blog on the topic again because I think it might help here as well: https://blog.burntsushi.net/unwrap/
`unwrap()` contains an assertion. Just like `slice[i]` or even `Box::new(whatever)`. The way to avoid these in C is to commit UB instead of panicking. I've seen arguments that seem understandable for why this is maybe appropriate in the Linux kernel ("I'd rather continue executing with garbage than shut down the user's system"), but I don't think it applies much beyond that. And to be clear, I'm not saying I agree with that either.
It is more about always making the error reach the function that called the method/library (lost if one of the own dependencies of this library rise the panic [UB included] crashing the program), what allows the programmer to take the decision (and not to a deep dependency^5 ) about if to continue running the program, by taking alternative route, or not.
I mean, as the crash is not exposed in the public API, given the things, I think it might not matter if this signal is exposed or not.
Otherwise you have to catch panics via unwinding.
Empathy tone changed in such last message which you are ridiculing, it is the point within all my comments, the behavior of a --release.
I should not have had dialogue as the things are not going to change. What I take with me is to think on alternative for panic/unwrap is something only come from ignorance, the best is to crash a program without let the programmer nor the final user to blink; to think in a different behavior in Rust is ridiculous. This is what is really quite something.
This is also "viral", in that the use of any possibly-falliable operation in the implementation would require the corresponding public API to return a Result/Option/etc., even if the intent is that a correct implementation could never fail. This makes the public API clunkier, especially if/when all possible bugs are shaken out or their absence proved one way or another.
I'm guessing this kind of tradeoff is why (most?) indexing operations in Rust return the value instead of an Option/Result/etc - perhaps the team judged that most of the time users have some other way of guaranteeing success (e.g., checking indices) so panicking on failure better fits/streamlines the needs/use cases of most users rather than forcing them to constantly deal with an Option/Result/etc., especially since get()/etc. exists for those users who do want an infalliable option.
Presumably the author(s) of the library/libraries you're using made a similar judgement - while it's possible to write code in the way you desire, perhaps it just wasn't worth the bother for them and/or most of their users.
But I've already opined on this topic: https://blog.burntsushi.net/unwrap/
Well, I hope it does. Albeit it almost certainly will not, because Linus is opposed to it. But ever since I read Joe Duffy's blog posts on the Midori research project at MS, I have been convinced that using panics leads to increased reliability, not decreased. From his blog[1]:
"Given that bugs are inherently not recoverable, we made no attempt to try. All bugs detected at runtime caused something called abandonment, which was Midori’s term for something otherwise known as “fail-fast”."
And:
"Abandonment, and the degree to which we used it, was in my opinion our biggest and most successful bet with the Error Model. We found bugs early and often, where they are easiest to diagnose and fix."
I think that the Midori team's work shows that a practice of "there's a bug, stop everything" leads to more reliable software. Sure, there's an initial period of pain where you're fixing a ton of bugs as they cause the software to panic. But you reap the rewards of that effort. I don't think Linux will ever move towards a model like this, but I think it would be beneficial in the end if they did.
1: https://joeduffyblog.com/2016/02/07/the-error-model/#bugs-ar...