Commit graph

19 commits

Author SHA1 Message Date
Andrew Gallant
5a6f268e82 cargo: add perf-inline crate feature
This is copied over from `regex`. Basically, when this is enabled (which
is the default), then a bunch of `inline(always)` annotations are
scattered in places (as motivated by micro-benchmarking). Otherwise,
these annotations are removed.
2025-04-11 15:58:33 -04:00
Andrew Gallant
7bbe21a6cb civil: fix Date::nth_weekday_of_month
This also removes the error prone `transpose` functions I had added to
the bridge between the internal non-ranged datetime types and the ranged
datetime types.

Fixes #312
2025-04-06 14:51:57 -04:00
Andrew Gallant
f41d586445
shared: remove pointless as_ref
I believe the signature of the constructor was originally generic over
`AsRef<[u8]>`, but it got changed to be concrete. However, the
implementation continued calling `as_ref()` unnecessarily. This looks
harmless, but exposes us to inference failures.

Ref https://github.com/rust-lang/rust/pull/139441
2025-04-06 09:31:23 -04:00
Andrew Gallant
460a0bc039 tz: fix comment and remove superfluous repr(align(..))
When I originally wrote the comment on `Repr`, I got the alignment
wrong. But I caught that mistake and fixed it before merging anything
to `master`. I just hadn't updated the comment.

The `repr(align(..))` was leftovers from experimenting with a
`TzifDateTime` that *wasn't* packed. I was trying to get `rustc` to
optimize comparisons automatically to a single integer, but couldn't get
ti to work. So I resorted to bit-packing. Since the representation is
now just an `i64`, an explicit alignment is not needed. (And it didn't
help anyway.)
2025-03-07 16:07:01 -05:00
Andrew Gallant
4dbc59f354 shared: fix implementation of IDate::yesterday
Basically, it was wrong when the date was the first day of the month.
This only impacts `jiff 0.2.2`. The bug wasn't present in previous
releases.

This was a transcription error during one of my refactors and it looks
like there unfortunately wasn't test coverage for it. Test coverage has
been added in this PR.

Fixes #290
2025-03-07 08:42:41 -05:00
Andrew Gallant
98164d6263 tz: pack TZif civil datetime into i64
This leads to amazing speed-ups for TZ lookups on civil datetimes:

    tz/posix_datetime_to_offset/jiff               1.00     32.6±0.70ns        ? ?/sec    1.01     32.9±0.28ns        ? ?/sec
    tz/posix_timestamp_to_offset/jiff              1.00     21.8±0.17ns        ? ?/sec    1.04     22.6±0.15ns        ? ?/sec
    tz/tzif_bundled_datetime_to_offset/jiff        2.57     23.4±0.19ns        ? ?/sec    1.00      9.1±0.06ns        ? ?/sec
    tz/tzif_bundled_timestamp_to_offset/jiff       1.00      6.0±0.05ns        ? ?/sec    1.04      6.2±0.05ns        ? ?/sec
    tz/tzif_future_datetime_to_offset/jiff         1.35     50.5±0.60ns        ? ?/sec    1.00     37.4±0.67ns        ? ?/sec
    tz/tzif_future_timestamp_to_offset/jiff        1.00     21.2±0.15ns        ? ?/sec    1.00     21.2±0.17ns        ? ?/sec
    tz/tzif_historical_datetime_to_offset/jiff     2.68     23.4±0.17ns        ? ?/sec    1.00      8.7±0.08ns        ? ?/sec
    tz/tzif_historical_timestamp_to_offset/jiff    1.00      6.0±0.05ns        ? ?/sec    1.00      6.0±0.05ns        ? ?/sec

It turns out that comparing civil datetimes is actually quite
expensive. Getting them down to a single integer comparison makes
the binary search much quicker.
2025-03-05 18:48:44 -05:00
Andrew Gallant
f6a5cc6a22 tz: refactor TZif representation to use column storage
This makes binary search for TZ lookups substantially faster.

This is yet another brutal refactor. Changing anything in POSIX time
zones or TZif handling is now a monster pain in the ass because all
of that code is shared in a very awkward way with `jiff-static`.

Ref #271
2025-03-05 18:48:44 -05:00
Andrew Gallant
5af655e5ea tz: add new enabled-by-default tz-fat feature
When enabled, this feature will "fatten" TZif data by adding more time
zone transitions. This corresponds to what tzdb's `zic` program does
when `-b fat` is given, except Jiff does it at runtime. If the TZif data
has already been fattened, then this has no effect.

The reason for this is that it smooths out performance differences in
time zone runtime lookups between pre-fattened TZif data and "slim"
TZif data. It is unpredictable whether `/usr/share/zoneinfo` is
actually fat or not, so this helps makes performance more predictable
regardless of what the source TZif data looks like.

This uses about 25% more heap memory in my experiments. For a single
time zone, this is, in an absolute sense, likely insignificant. But if
you have thousands of time zones loaded into memory, it can add up. But
that's a somewhat niche use case. However, this can make binary sizes
bigger when the `jiff-static` proc macro is used.

So while unlikely to matter too much, the `tz-fat` feature can be
disabled if you want to prioritize memory usage and binary size.

Fixes #271
2025-03-05 18:48:44 -05:00
Andrew Gallant
092f05ff9f tz/posix: move most code to shared
This was yet another absolutely brutal refactor. But in order to
"fatten" up TZif data after parsing, we need to be able to actually use
POSIX time zones in order to compute missing transitions. And in order
to do that, basically the entire POSIX time zone implementation needs to
be in `shared`. And that means no ranged integers. Which in turn means
implementing several datetime algorithms on just primitives.

This was just overall brutal, and I am getting very close to ripping
out ranged integers.
2025-03-05 18:48:44 -05:00
Andrew Gallant
fc993ca79a shared: move more date algorithms to itime
This isn't all of them, but I specifically prioritized the ones I
think I'll need for handling POSIX time zones.
2025-03-05 18:48:44 -05:00
Andrew Gallant
843adbea82 shared: define one Error type in shared
I think we're going to use this more. So we might as well put it in
one place.

And also, make it core-only compatible via degraded error messages.
2025-03-05 18:48:44 -05:00
Andrew Gallant
41cd2b8643 shared: move core datetime algorithms
Instead of just free functions operating on tuples, we actually give
them names.

Ranged integers keep pissing me off. The fact that I have "primitive"
datetime types and "ranged" datetime types is just absolutely
infuriating and creating a lot of dissonance.

But at least the new composite stuff makes moving back-and-forth a
little easier now. Of course, the composite stuff is also write-once
and read-never. *heavy sigh*

In the next commit, we're going to start moving some more of our
datetime algorithms to `shared`.

The ultimate goal here is to have enough in `shared` that we can handle
POSIX time zones.
2025-03-05 18:48:44 -05:00
Andrew Gallant
da67afe6f4 tz: remove use of String when parsing POSIX time zones
This was just really bugging me. And if we're going to move more of the
POSIX time zone implementation into `shared`, we might as well just bite
the bullet and do this too.

Now I believe the only parts of POSIX time zones that require `alloc`
are parsing the `:blah` implementation defined strings for the `TZ`
environment variable and error messages. Not that it really matters
much I think.
2025-03-05 18:48:44 -05:00
Andrew Gallant
7b724ba380 shared: move ArrayStr into shared module
So that we can use it in `jiff` and `jiff-static`.

This will avoid using a `String` at all when creating or using POSIX
time zones.
2025-03-05 18:48:44 -05:00
Andrew Gallant
cb75cb7a57 shared: move some things around
We're likely going to be moving more `crate::util` code into
`crate::shared::util` (unfortunately), so split `shared/util` apart
to make room for it.
2025-03-05 18:48:44 -05:00
Andrew Gallant
bf01f266ea tz: simplify POSIX time zone types
Now that we eagerly reject unreasonable POSIX time zones, we can
simplify our type definitions. There's no more split between
`PosixTimeZone` and `ReasonablePosixTimeZone`. Everything is
just reasonable.
2025-03-05 18:48:44 -05:00
Andrew Gallant
8c1517b308 tz: reject "unreasonable" POSIX time zones outright
This makes the POSIX time zone parser reject strings like `EST5EDT`.
That is, a time zone with daylight saving time, but without an explicit
rule stating when daylight saving time becomes active/inactive.

We were already doing this, but more explicitly by calling
`PosixTimeZone::reasonable`, so there is no public API breakage here.
The only difference is that `EST5EDT` will be treated as invalid and
will instead be attempted to be used as an IANA time zone identifier.
(Which, incidentally enough, actually exists. Odd, but I suppose
technically more correct than the current behavior of just rejecting it
outright.)

I did this because it makes the type definitions simpler. There was a
lot of cognitive energy on my part being devoted to parsing unreasonable
POSIX time zones successfully and only later asserting that they are
reasonable through a fallible API. But I don't think this was really
buying us anything, and we should just reject them outright.

Interestingly, PostgreSQL does support these "unreasonable" POSIX time
zones[1]:

> If a daylight-savings abbreviation is given but the transition
> rule field is omitted, the fallback behavior is to use the rule
> M3.2.0,M11.1.0, which corresponds to USA practice as of 2020 (that
> is, spring forward on the second Sunday of March, fall back on
> the first Sunday of November, both transitions occurring at 2AM
> prevailing time). Note that this rule does not give correct USA
> transition dates for years before 2007.

But POSIX has literally nothing to say about it[2], despite providing a
grammar that clearly makes the DST transition rule optional even when a
DST abbreviation is provided. Like it doesn't even mention that it's
unspecified, despite bloviating about how certain abbreviation lengths
lead to unspecified behavior. Why does POSIX suck so bad?

Anyway, it seems like there are really only two choices here. We could
either reject unreasonable time zones as invalid POSIX time zone
strings, or we could just "helpfully" assume a particular DST transition
rule. Jiff isn't legacy software (yet), so maybe don't try to be so
helpful that we assume one country's DST transition rules silently for
everyone in the world.

This commit does the bare minimum to reject these time zones.
The next commit will be the payoff.

[1]: https://www.postgresql.org/docs/current/datetime-posix-timezone-specs.html
[2]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html
2025-03-05 18:48:44 -05:00
Andrew Gallant
04d895345a jiff-tz-static: add initial version of new proc macro
This isn't quite done, but it does parse TZif and emits the correct
Jiff code to construct a `TimeZone` in a const context.

The main thing missing here is a fair bit of polish and a change
to the TimeZone internals to actually support this method of
construction in core-only environments without increasing the size
of `TimeZone` (i.e., pointer tagging).
2025-02-26 16:55:17 -05:00
Andrew Gallant
68af809598 shared: add new exposed-but-internal jiff::shared module
See the module comments in `shared` for a bit more of an explanation
for why I ended up with this design. The summary is that this new
module will be copied to the proc macro, which will enable jiff to
depend on and re-export the proc macro.

This was a pretty gnarly refactor, because this required separating
the TZif and POSIX time zone parsers out from their internal data
types.
2025-02-26 16:55:17 -05:00