struct ZonedInner {
timestamp: Timestamp,
datetime: DateTime,
offset: Offset,
time_zone: TimeZone,
}
Expand description
The representation of a Zoned
.
This uses 4 different things: a timestamp, a datetime, an offset and a
time zone. This in turn makes Zoned
a bit beefy (40 bytes on x86-64),
but I think this is probably the right trade off. (At time of writing,
2024-07-04.)
Technically speaking, the only essential fields here are timestamp and time zone. The datetime and offset can both be unambiguously computed from the combination of a timestamp and a time zone. Indeed, just the timestamp and the time zone was my initial representation. But as I developed the API of this type, it became clearer that we should probably store the datetime and offset as well.
The main issue here is that in order to compute the datetime from a timestamp and a time zone, you need to do two things:
- First, compute the offset. This means doing a binary search on the TZif data for the transition (or closest transition) matching the timestamp.
- Second, use the offset (from UTC) to convert the timestamp into a civil datetime. This involves a “Unix time to Unix epoch days” conversion that requires some heavy arithmetic.
So if we don’t store the datetime or offset, then we need to compute them any time we need them. And the Temporal design really pushes heavily in favor of treating the “instant in time” and “civil datetime” as two sides to the same coin. That means users are very encouraged to just use whatever they need. So if we are always computing the offset and datetime whenever we need them, we’re potentially punishing users for working with civil datetimes. It just doesn’t feel like the right trade-off.
Instead, my idea here is that, ultimately, Zoned
is meant to provide
a one-stop shop for “doing the right thing.” Presenting that unified
abstraction comes with costs. And that if we want to expose cheaper ways
of performing at least some of the operations on Zoned
by making fewer
assumptions, then we should probably endeavor to do that by exposing a
lower level API. I’m not sure what that would look like, so I think it
should be driven by use cases.
Some other things I considered:
- Use
Zoned(Arc<ZonedInner>)
to makeZoned
pointer-sized. But I didn’t like this because it implies creating any newZoned
value requires an allocation. Since aTimeZone
internally uses anArc
, all it requires today is a chunky memcpy and an atomic ref count increment. - Use
OnceLock
shenanigans for the datetime and offset fields. This would makeZoned
even beefier and I wasn’t totally clear how much this would save us. And it would impose some (probably small) cost on every datetime or offset access. - Use a radically different design that permits a
Zoned
to beCopy
. I personally find it deeply annoying thatZoned
is both the “main” datetime type in Jiff and also the only one that doesn’t implementCopy
. I explored some designs, but I couldn’t figure out how to make it work in a satisfying way. The main issue here isTimeZone
. ATimeZone
is a huge chunk of data and the ergonomics of theZoned
API require being able to access aTimeZone
without the caller providing it explicitly. So to me, the only real alternative here is to use some kind of integer handle into a global time zone database. But now you all of a sudden need to worry about synchronization for every time zone access and plausibly also garbage collection. And this also complicates matters for using custom time zone databases. So I ultimately came down on “Zoned is not Copy” as the least awful choice. heavy sigh
Fields§
§timestamp: Timestamp
§datetime: DateTime
§offset: Offset
§time_zone: TimeZone
Trait Implementations§
Source§impl Clone for ZonedInner
impl Clone for ZonedInner
Source§fn clone(&self) -> ZonedInner
fn clone(&self) -> ZonedInner
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source
. Read more