Brought up with the #show and tell > roc-realworld initial exploration discussion:
What's the best backing type for storing time epochs? From the chrono
docs for timestamp_nanos_opt:
An
i64
with nanosecond precision can span a range of ~584 years. This function returnsNone
on an out of rangeDateTime
.
The dates that can be represented as nanoseconds are between 1677-09-21T00:12:43.145224192 and 2262-04-11T23:47:16.854775807.
So except for where you're trying to use a timestamp to represent dates in history, anything in the recent past/future is totally covered with i64
. I personally think you could use u64
because now and forward is what we care about for app dev, but I can see usage of either.
That being said, if i64
covers almost 600 years, then using i128
would cover any year in the past, present, or future that would ever need to be represented for anything. You'd _probably_ be safe using it to represent all datetime types and crashing on invalid years like 1 trillion BC (though even that would still be in range!).
So I'd say:
I expect Roc should just use I128
everywhere to be safe?
@Luke Boswell does this answer your question?
I think I64/I128
is useful so you can do a - b
without worrying about accidentally producing a negative
yeah I jut wonder about whether "negative duration" is a good concept to have or not
I assume it's fine, but maybe it's desirable to know it's nonnegative for some reason?
I feel like max efficiency isn't needed here so I128
seams fine. Then it can be used datetimes and time measurements instead of splitting the ecosystem
I think we're already comfortable with "negative duration" because we don't measure from now, we measure from 54 years ago
yeah I jut wonder about whether "negative duration" is a good concept to have or not
That's totally fair.
If we want to use this type for datetimes, negative is needed anyway for before 1970.
If it is only for time measurements, I agree that unsigned makes more sense
I'm not sure I agree that you'd want unsigned unless you wanted to save space.
Signed adds more safety (but not perfect safety, of course!) around math on the underlying epoch
And I can't think of a situation where I'd want to limit the cardinality of my times to "1970 and later" at the type level instead of just having a proper constraint in the DB or something
well Instant
and Duration
can be different in that way
Instant
should certainly be signed
Duration
I can see arguments both ways for whether it should be signed
(semantically, not thinking about performance or anything like that)
Oh, good point! I was thinking of instant for both of those words
Duration
s probably should not be negative!
So yes, I128
for Instant
and U64
or U128
(probably the latter) for Duration
I guess eventually Instant
will be a nominal type. Then instant.elapsed(other)
will return a Duration
which is a U128
(maybe also nominal for methods like .to_ms()
.
In a world where Instant
is just structural Duration
will ultimately be the same type cause users will just do instant - other
oh I definitely think both of them should be nominal
in general for things where units matter, nominal is great for avoiding mixing up your units
like accidentally adding together two Instant
s, which is almost certainly never what you want :sweat_smile:
Ah yes, Dec 3rd, 1994 + Jan 1st, 1970 == Dec 3rd, 1994
If durations can't be negative, wouldduration_a - duration_b
would always return a positive number?
If so, it creates a situation where the order of +
and -
operations with durations matters, as below.
a = Duration.from_ms 2
b = Duration.from_ms 8
c = Duration.from_ms 5
expect
// Fails. LHS: 5, RHS: 11
a + b - c == a - c + b
I'd expect it to do whatever 3 - 5
does which is presumably crash
math binops exist because doing left.minus(right)
every time sucks, and left.minus(right)?
every time isn't worth it either, so we just expect it to crash
But we have minus_checked(...)
for those safety-aware cases
yeah this is true in general of unsigned integers, and it's a tradeoff that comes with the upside of guaranteeing that they're never negative
I think durations should definitely be signed. It would be a very bad user experience to compare two durations and get a crash. It's more acceptable when subtracting raw unsigned integers because you know what type you're working with and that crashing is part of the game. With a custom type, you would have no reason to expect that today.minus(tomorrow)
would explode without reading the docs.
To me a negative duration seems very intuitive and useful. It's just representing the distance and direction between two points in time.
today
and tomorrow
would be Instant
s, though, not Duration
s :thinking:
Isaac Van Doren said:
It would be a very bad user experience to compare two durations and get a crash. It's more acceptable when subtracting raw unsigned integers because you know what type you're working with and that crashing is part of the game.
this is a good point
Right, I was imagining that today.minus(tomorrow)
would be a duration though
the downside of it being signed is that you can't guarantee it's nonnegative, so there might be some scenarios you have more difficulty ruling out
but the downside of it being unsigned is that it might crash if you use it wrong
which is a more serious downside
I think it'd be better to expose a Instant.absolute_delta(Instant) -> Duration
and maintain unsigned Duration
s
the downside of it being signed is that you can't guarantee it's nonnegative, so there might be some scenarios you have more difficulty ruling out
It seems fine to just have to check if the duration is positive if you need to
I think it'd be better to expose a
Instant.absolute_delta(Instant) -> Duration
and maintain unsignedDuration
s
The trouble with this is that it will make adjusting dates really annoying if you actually do want a negative quantity
Why do you need a negative quantity? You can just do Instant.move_forwards(Duration) -> Instant
and Instant.move_backwards(Duration) -> Instant
I'm approaching this from a "cardinality first, API shape second" and negative durations don't fit into that first box for me
Elm is big on descriptive APIs, it seems totally prudent to have a descriptive API for this stuff
Right I could do that, but that is a lot clunkier than myInstant.adjust(myDuration)
which handles either direction
Sure
I don't have strong feelings on it. Not the way I'd do it, but that approach works, so it's okay with me
Warning, it is very important to know when durations are negative. It is a sign that the underlying clock changed. So you need to know to throw out that data.
Yeah that's a good point
Cause there is no guarantee to that end comes after start when doing:
start = Instant.now!()
end = Instant.now!()
oh that's a great point
that's enough to convince me duration should be signed :+1:
I have a concrete use case for negative durations:
In my work, I regularly deal with comparing multiple times, and seeing how close they are. (Specifically, "Did the train come on time?", comparing the scheduled time to the predicted arrival time to the time the train actually showed up.)
The train could either be early or late. This would be way easier to do with signed subtraction and negative durations, rather than having unsigned durations and having to maintain a separate "early or late" enum.
That's a great example
That reminds me that stage monitors (displays that only the people on stage can see) will often display the amount of time remaining for a particular segment and then switch negative once a segment starts going too long.
cool, ok it seems like Rust got this one wrong
so then the question is I64
or I128
If your Instant
is I128
, seems like the same for Duration
only makes sense
If you don't know what to use for instant, then I128
is 99.9999999% safe for any usage and I64
is 99.9% safe
yeah there's always a performance argument for I64
, which I think would mainly come up if you're storing a bunch of these in memory, like a time series or something
but maybe for use cases like that you're storing a plain integer anyway and just converting to Instant
or Duration
one at a time, briefly
A Instant.to_i64() : Instant -> Result I64 [Overflow]
method should be sufficient for those that want small storage, and Instant.from_i64() : I64 -> Instant
is infallible
We should default to safe unless there's a big cost
for high performance time series (trading use case) my experience is you store offsets relative to some known time, and your TS is usually fixed an intervals, so you often elide explicit intervals anyway. I would think I128 is fine in general
Re negative Durations: maybe the name "Duration" isn't quite right? I think what we're talking about is an "InstantDifference"? For me, a "Duration" is never negative. I can't think of a better name for this concept, but the folks here are very good in proposing alternatives...
Interesting
Last updated: Jul 06 2025 at 12:14 UTC