From b3f9c1effec783502dcd7d5346b477210b65235f Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Mon, 5 May 2025 16:52:27 -0400 Subject: [PATCH 1/4] doc: some minor clean-ups This just fixes some errors I found in the prose when re-reading some of it. This also adds some `Zoned::start_of_day` test cases from https://github.com/tc39/proposal-temporal/issues/3110 --- PLATFORM.md | 46 ++++++++++++++++++++++++---------------------- src/tz/mod.rs | 10 +++++----- src/zoned.rs | 30 ++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 27 deletions(-) diff --git a/PLATFORM.md b/PLATFORM.md index 20e80f30..a078e202 100644 --- a/PLATFORM.md +++ b/PLATFORM.md @@ -68,7 +68,7 @@ specifically want Jiff to prefer using a non-system copy of the database. need to use `TimeZoneDatabase::from_dir` and use the resulting handle explicitly.) -If a IANA Time Zone Database could not be found a `TZDIR`, then Jiff will +If a IANA Time Zone Database could not be found at `TZDIR`, then Jiff will still attempt to look for a database at the standard locations (like `/usr/share/zoneinfo`). @@ -90,7 +90,7 @@ Summarizing POSIX (and common extensions supported by GNU libc and musl), the to a TZif formatted file. * `EST5EDT,M3.2.0,M11.1.0` sets the time zone using a POSIX daylight saving time rule. The rule shown here is for `US/Eastern` at time of writing (2024). -This is useful for specifying a custom time zone with generating TZif data, +This is useful for specifying a custom time zone without generating TZif data, but is rarely used in practice. When `TZ` isn't set, then Jiff uses heuristics to detect the system's @@ -108,12 +108,12 @@ defined, then Jiff uses `/data/misc` as its default value. Note that these environment variables are not necessarily only read on Android, although they likely only make sense in the context of an Android -environment. This is because Jiff's supported for the Concatenated Time +environment. This is because Jiff's support for the Concatenated Time Zone Database is platform independent. For example, Jiff will let users create a database from a Concatenated Time Zone Database file via the `TimeZoneDatabase::from_concatenated_path` API on _any_ platform. This is -intended to enable maximum flexibility, and because there is no specific -reason to make the Concatenated Time Zone Database format Android-specific. +intended to enable maximum flexibility, and because there is no specific reason +to make the Concatenated Time Zone Database format Android-specific. ## Platforms @@ -235,19 +235,21 @@ API to create a `TimeZoneDatabase` from a concatenated `tzdata` file on any platform. If users of Jiff are uncomfortable relying on Android's "unstable" time zone -database format, then there are three options available to them after disabling +database format, then there are a few options available to them after disabling the `tzdb-concatenated` crate feature: * They can own the responsibility of putting a standard `zoneinfo` database installation into their environment. Then set the `TZDIR` environment variable to point at it, and Jiff will automatically use it. -* Enable the `tzdb-bundle-always` crate feature. This will cause all time zone -database to be compiled into your binary. Nothing else needs to be done. Jiff -will automatically use the bundled copy. +* Enable the `tzdb-bundle-always` crate feature. This will cause the entire +time zone database to be compiled into your binary. Nothing else needs to be +done. Jiff will automatically use the bundled copy. * Manually create `TimeZone` values via `TimeZone::tzif` from TZif formatted data. With this approach, you may need to change how you use Jiff in some cases. For example, any `in_tz` method will need to be changed to use the `to_zoned` equivalent. +* Embed specific time zones into your binary with `jiff::tz::get` or +`jiff::tz::include`. This requires enabling Jiff's `static` feature. #### System time zone @@ -259,15 +261,15 @@ Database] format as unstable, they also discourage the discovery of the system time zone through properties as well. (See [chrono#1018] and [chrono#1148] for some discussion on this topic.) For Jiff at least, there is no feasible alternative. Apparently, the blessed API is to use their Java libraries, but -that doesn't seem feasible to Jiff since I (Jiff's author) is unaware of a -mechanism for easily calling Java code from Rust. The only option left is to -use their `libc` APIs, which they did at least improve to make them thread -safe, but this isn't enough for Jiff. For Jiff, we really want the actual IANA -time zone identifier, and it isn't clear how to discover this from their `libc` -APIs. Moreover, Jiff supports far more sophisticated operations on a time zone -(like dealing with discontinuities in civil time) that cannot be implemented on -top of `libc`-style APIs. Using Android's `libc` APIs for time handling would -be a huge regression compared to all other platforms. +that doesn't seem feasible to me inside of Jiff since I (Jiff's author) is +unaware of a mechanism for easily calling Java code from Rust. The only option +left is to use their `libc` APIs, which they did at least improve to make them +thread safe, but this isn't enough for Jiff. For Jiff, we really want the +actual IANA time zone identifier, and it isn't clear how to discover this from +their `libc` APIs. Moreover, Jiff supports far more sophisticated operations on +a time zone (like dealing with discontinuities in civil time) that cannot be +implemented on top of `libc`-style APIs. Using Android's `libc` APIs for time +handling would be a huge regression compared to all other platforms. It's worth noting that all other popular Unix systems provide at least some reliable means of both querying the time zone database _and_ discovering the @@ -276,8 +278,8 @@ the existing conventions for Unix systems is unclear. If users of Jiff are uncomfortable relying on Android's `persist.sys.timezone` property, then they should avoid APIs like `Zoned::now` and `TimeZone::system`. -Instead, they can use `TimeZone::UTC`, which is what the fallback time zone -would be when the system time zone cannot be discovered. +Instead, they can use `TimeZone::unknown()`, which is what the fallback time +zone would be when the system time zone cannot be discovered. ### Windows @@ -361,7 +363,7 @@ feature will cause Jiff to assume a web context and use JavaScript's #### IANA Time Zone Database None of the WASM targets have a canonical installation of the IANA Time Zone -Database. Because of this, and because of the important of time zone support +Database. Because of this, and because of the importance of time zone support to Jiff's design, Jiff will automatically embed an entire copy of the IANA Time Zone Database into your binary on all WASM targets. @@ -407,7 +409,7 @@ the time zone in Jiff's configured IANA Time Zone Database. [RFC 9557]: https://www.rfc-editor.org/rfc/rfc9557.html [ISO 8601]: https://www.iso.org/iso-8601-date-and-time-format.html [Temporal]: https://tc39.es/proposal-temporal -[issue-platform]: https://github.com/BurntSushi/jiff/labels/platform +[issue-platform]: https://github.com/BurntSushi/jiff/issues?q=label%3Aplatform [issue-new]: https://github.com/BurntSushi/jiff/issues/new [`GetDynamicTimeZoneInformation`]: https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-getdynamictimezoneinformation [`DYNAMIC_TIME_ZONE_INFORMATION`]: https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/ns-timezoneapi-dynamic_time_zone_information diff --git a/src/tz/mod.rs b/src/tz/mod.rs index e9b1c922..42d0b970 100644 --- a/src/tz/mod.rs +++ b/src/tz/mod.rs @@ -168,7 +168,7 @@ mod zic; /// # Return type /// /// This macro returns a value with type `TimeZone`. To get a `&'static -/// TimeZone`, simply use `&include("...")`. +/// TimeZone`, simply use `&get!("...")`. /// /// # Usage /// @@ -227,9 +227,9 @@ pub use jiff_static::get; /// /// Unlike [`jiff::tz::get`](get), this reads TZif data from a file. /// `jiff::tz::get`, in contrast, reads TZif data from the [`jiff-tzdb`] crate. -/// `jiff::tz::get` is more convenient and doesn't require using managing TZif -/// files, but it comes at the cost of a dependency on `jiff-tzdb` and being -/// forced to use whatever data is in `jiff-tzdb`. +/// `jiff::tz::get` is more convenient and doesn't require managing your own +/// TZif files, but it comes at the cost of a compile-time dependency on +/// `jiff-tzdb` and being forced to use whatever data is in `jiff-tzdb`. /// /// # Input /// @@ -247,7 +247,7 @@ pub use jiff_static::get; /// # Return type /// /// This macro returns a value with type `TimeZone`. To get a `&'static -/// TimeZone`, simply use `&include("...")`. +/// TimeZone`, simply use `&include!("...")`. /// /// # Usage /// diff --git a/src/zoned.rs b/src/zoned.rs index 719007b7..698aac48 100644 --- a/src/zoned.rs +++ b/src/zoned.rs @@ -5797,4 +5797,34 @@ mod tests { @r#"parsing "1970-06-01T00:00:00-00:45:00[Africa/Monrovia]" failed: datetime 1970-06-01T00:00:00 could not resolve to a timestamp since 'reject' conflict resolution was chosen, and because datetime has offset -00:45, but the time zone Africa/Monrovia for the given datetime unambiguously has offset -00:44:30"#, ); } + + // These are some interesting tests because the time zones have transitions + // that are very close to one another (within 14 days!). I picked these up + // from a bug report to Temporal. Their reference implementation uses + // different logic to examine time zone transitions than Jiff. In contrast, + // Jiff uses the IANA time zone database directly. So it was unaffected. + // + // [1]: https://github.com/tc39/proposal-temporal/issues/3110 + #[test] + fn weird_time_zone_transitions() { + if crate::tz::db().is_definitively_empty() { + return; + } + + let zdt: Zoned = + "2000-10-08T01:00:00-01:00[America/Noronha]".parse().unwrap(); + let sod = zdt.start_of_day().unwrap(); + assert_eq!( + sod.to_string(), + "2000-10-08T01:00:00-01:00[America/Noronha]" + ); + + let zdt: Zoned = + "2000-10-08T03:00:00-03:00[America/Boa_Vista]".parse().unwrap(); + let sod = zdt.start_of_day().unwrap(); + assert_eq!( + sod.to_string(), + "2000-10-08T01:00:00-03:00[America/Boa_Vista]", + ); + } } From 5336ffb81165de6250ca7c26569004c47d30eed0 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Mon, 5 May 2025 20:44:11 -0400 Subject: [PATCH 2/4] doc: fix outdated reference to `Eq` implementation on `Span` This was removed in `jiff 0.2` in favor of an `Eq` implementation on the new `SpanFieldwise` type. --- src/span.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/span.rs b/src/span.rs index 6a379e68..446227eb 100644 --- a/src/span.rs +++ b/src/span.rs @@ -1889,11 +1889,11 @@ impl Span { /// /// Two spans compare equal when they correspond to the same duration /// of time, even if their individual fields are different. This is in - /// contrast to the `Eq` trait implementation of `Span`, which performs - /// exact field-wise comparisons. This split exists because the comparison - /// provided by this routine is "heavy" in that it may need to do - /// datetime arithmetic to return an answer. In contrast, the `Eq` trait - /// implementation is "cheap." + /// contrast to the `Eq` trait implementation of `SpanFieldwise` (created + /// by [`Span::fieldwise`]), which performs exact field-wise comparisons. + /// This split exists because the comparison provided by this routine is + /// "heavy" in that it may need to do datetime arithmetic to return an + /// answer. In contrast, the `Eq` trait implementation is "cheap." /// /// This routine accepts anything that implements `Into`. /// There are some trait implementations that make using this routine From 677072eb0fa9dbe12c10bbab079b8dc7f79b8ba7 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Mon, 5 May 2025 21:54:09 -0400 Subject: [PATCH 3/4] tz: fix case analysis in `TimeZone::following` This could happen in time zones that had DST but ended it at some point in the past. In this case, `TimeZone::following` (but no other Jiff APIs) would skip over the last historical time zone transition. This happened because we were erroneously deferring to the POSIX time zone, which has no DST (because it reflects the current state of no DST). But we should only do this when the timestamp given comes *at or after* all historical time zone transitions. Fixes #362 --- CHANGELOG.md | 14 +++++ ...z__tzif__tests__America__Boa_Vista_v1.snap | Bin 0 -> 5222 bytes ...__tzif__tests__America__Boa_Vista_v2+.snap | 49 ++++++++++++++++++ src/tz/testdata.rs | 6 +++ src/tz/testdata/america-boa-vista.tzif | Bin 0 -> 618 bytes src/tz/timezone.rs | 31 +++++++++++ src/tz/tzif.rs | 12 ++++- 7 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 src/tz/snapshots/jiff__tz__tzif__tests__America__Boa_Vista_v1.snap create mode 100644 src/tz/snapshots/jiff__tz__tzif__tests__America__Boa_Vista_v2+.snap create mode 100644 src/tz/testdata/america-boa-vista.tzif diff --git a/CHANGELOG.md b/CHANGELOG.md index ddab17dc..4535a5de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # CHANGELOG +0.2.13 (2025-05-05) +=================== +This release fixes a bug in a corner case where `TimeZone::following` could +sometimes omit the last time zone transition. This could only happen in time +zones that once had daylight saving time, but ended it. This did not have an +impact on other Jiff APIs that handle daylight saving time. + +Bug fixes: + +* [#362](https://github.com/BurntSushi/jiff/issues/362): +Fix a bug where `TimeZone::following` could omit the last historical time zone +transition. + + 0.2.12 (2025-05-03) =================== This release fixes a couple bugs in Jiff's parser. In particular, a regression diff --git a/src/tz/snapshots/jiff__tz__tzif__tests__America__Boa_Vista_v1.snap b/src/tz/snapshots/jiff__tz__tzif__tests__America__Boa_Vista_v1.snap new file mode 100644 index 0000000000000000000000000000000000000000..b249b1067ba946d61d64218602f4cfbc824190e1 GIT binary patch literal 5222 zcmb7|OK;;g6otF?ui&ak7Mk!OlA-}G(jtohc@z_2fX>Fy#7b(wu>(tPr~UQ4e2A1} zQI?DV87Mq=jy~RdC^<=zMRhnGU&|$`j;}ZMfAF(?`*N%n< zb^0jw>tk7LiXXf3>Q{Tctm-eH#jz^af0L{0>qP=Z^ZUm;^!@qi4n5sI-YpQi{V0#y z*W%{4L$Ut0t?Ht+}R{*!2eSpj;r0_wb!DAt*>rK_Qn!X+iPvWBYzOoT|oL_jmp8XSo7+Eslnr<*XSIMN;F0=rhEL+HKuOiufHySwTw(HnS;kTFAz8C^_&z!yT+jAl&~BMHH< zGdbb5bjJ+ELh%$jL~9M1wt~dWgrd|gY0Lm$(*?Sm`zJJn^~0@dcD|0Ypn!|N+P_+GYIwvgY`H6N;)|eTAgUvRbnQ@A?na+%Z8MmZoW~_6R z^#jot9V@O#MwM&iX0tdbE_9AwVv9PtX)^Sti{antRP;@FB8_1v=fx{G`dagQc>=p6O@k?gFv4PA@f%&3x;RHn#PFOMnjtyi1?GJYwIjypksD3AF1;9}w za6-|kaBLt8uAeL5ARIicI5&CR>71a5!|_FDz_Ed>tD6KFJjyx7qJKa*1s;Rri%x}O z0~t!k$BNJ_Q#y3oV-*x*a9q(DaBLt8G1fWo1PBLK&krarj8!N)5e~J13_1c?KHNF* zOMHEh0 zG6Rkc92>})84@f9 z;po@A=1nj7u?jH;#}}OlhuJ`eFDu+T;(6yNmMQ&RMR%uyA_^xgnGT2PK-M#C5@Zk# zJkAT+J0QFw4#zKnqEq4MKsGuJCmR|oC9#sSJL7Fd1WtR&3^+QF8|iT7C`q_%zOZne Q;}!AD@k<~(F2Py+59}3{LjV8( literal 0 HcmV?d00001 diff --git a/src/tz/snapshots/jiff__tz__tzif__tests__America__Boa_Vista_v2+.snap b/src/tz/snapshots/jiff__tz__tzif__tests__America__Boa_Vista_v2+.snap new file mode 100644 index 00000000..6d7aaf9d --- /dev/null +++ b/src/tz/snapshots/jiff__tz__tzif__tests__America__Boa_Vista_v2+.snap @@ -0,0 +1,49 @@ +--- +source: src/tz/tzif.rs +expression: tzif_to_human_readable(&tzif_test.parse()) +--- +TIME ZONE NAME + America/Boa_Vista +TIME ZONE VERSION + 2 +LOCAL TIME TYPES + 000: offset=-04:02:40 designation=LMT indicator=local/wall + 001: offset=-03 designation=-03 dst indicator=local/wall + 002: offset=-04 designation=-04 indicator=local/wall +TRANSITIONS + 0000: -009999-01-02T01:59:59Z unix=-377705023201 wall=-009999-01-01T21:57:19 unambiguous type=0 -04:02:40 LMT + 0001: 1914-01-01T04:02:40Z unix=-1767211040 wall=1914-01-01T00:00:00 gap-until(1914-01-01T00:02:40) type=2 -04 -04 + 0002: 1931-10-03T15:00:00Z unix=-1206954000 wall=1931-10-03T11:00:00 gap-until(1931-10-03T12:00:00) type=1 -03 -03 dst + 0003: 1932-04-01T03:00:00Z unix=-1191358800 wall=1932-03-31T23:00:00 fold-until(1932-04-01T00:00:00) type=2 -04 -04 + 0004: 1932-10-03T04:00:00Z unix=-1175371200 wall=1932-10-03T00:00:00 gap-until(1932-10-03T01:00:00) type=1 -03 -03 dst + 0005: 1933-04-01T03:00:00Z unix=-1159822800 wall=1933-03-31T23:00:00 fold-until(1933-04-01T00:00:00) type=2 -04 -04 + 0006: 1949-12-01T04:00:00Z unix=-633816000 wall=1949-12-01T00:00:00 gap-until(1949-12-01T01:00:00) type=1 -03 -03 dst + 0007: 1950-04-16T04:00:00Z unix=-622065600 wall=1950-04-16T00:00:00 fold-until(1950-04-16T01:00:00) type=2 -04 -04 + 0008: 1950-12-01T04:00:00Z unix=-602280000 wall=1950-12-01T00:00:00 gap-until(1950-12-01T01:00:00) type=1 -03 -03 dst + 0009: 1951-04-01T03:00:00Z unix=-591829200 wall=1951-03-31T23:00:00 fold-until(1951-04-01T00:00:00) type=2 -04 -04 + 0010: 1951-12-01T04:00:00Z unix=-570744000 wall=1951-12-01T00:00:00 gap-until(1951-12-01T01:00:00) type=1 -03 -03 dst + 0011: 1952-04-01T03:00:00Z unix=-560206800 wall=1952-03-31T23:00:00 fold-until(1952-04-01T00:00:00) type=2 -04 -04 + 0012: 1952-12-01T04:00:00Z unix=-539121600 wall=1952-12-01T00:00:00 gap-until(1952-12-01T01:00:00) type=1 -03 -03 dst + 0013: 1953-03-01T03:00:00Z unix=-531349200 wall=1953-02-28T23:00:00 fold-until(1953-03-01T00:00:00) type=2 -04 -04 + 0014: 1963-12-09T04:00:00Z unix=-191361600 wall=1963-12-09T00:00:00 gap-until(1963-12-09T01:00:00) type=1 -03 -03 dst + 0015: 1964-03-01T03:00:00Z unix=-184194000 wall=1964-02-29T23:00:00 fold-until(1964-03-01T00:00:00) type=2 -04 -04 + 0016: 1965-01-31T04:00:00Z unix=-155160000 wall=1965-01-31T00:00:00 gap-until(1965-01-31T01:00:00) type=1 -03 -03 dst + 0017: 1965-03-31T03:00:00Z unix=-150066000 wall=1965-03-30T23:00:00 fold-until(1965-03-31T00:00:00) type=2 -04 -04 + 0018: 1965-12-01T04:00:00Z unix=-128894400 wall=1965-12-01T00:00:00 gap-until(1965-12-01T01:00:00) type=1 -03 -03 dst + 0019: 1966-03-01T03:00:00Z unix=-121122000 wall=1966-02-28T23:00:00 fold-until(1966-03-01T00:00:00) type=2 -04 -04 + 0020: 1966-11-01T04:00:00Z unix=-99950400 wall=1966-11-01T00:00:00 gap-until(1966-11-01T01:00:00) type=1 -03 -03 dst + 0021: 1967-03-01T03:00:00Z unix=-89586000 wall=1967-02-28T23:00:00 fold-until(1967-03-01T00:00:00) type=2 -04 -04 + 0022: 1967-11-01T04:00:00Z unix=-68414400 wall=1967-11-01T00:00:00 gap-until(1967-11-01T01:00:00) type=1 -03 -03 dst + 0023: 1968-03-01T03:00:00Z unix=-57963600 wall=1968-02-29T23:00:00 fold-until(1968-03-01T00:00:00) type=2 -04 -04 + 0024: 1985-11-02T04:00:00Z unix=499752000 wall=1985-11-02T00:00:00 gap-until(1985-11-02T01:00:00) type=1 -03 -03 dst + 0025: 1986-03-15T03:00:00Z unix=511239600 wall=1986-03-14T23:00:00 fold-until(1986-03-15T00:00:00) type=2 -04 -04 + 0026: 1986-10-25T04:00:00Z unix=530596800 wall=1986-10-25T00:00:00 gap-until(1986-10-25T01:00:00) type=1 -03 -03 dst + 0027: 1987-02-14T03:00:00Z unix=540270000 wall=1987-02-13T23:00:00 fold-until(1987-02-14T00:00:00) type=2 -04 -04 + 0028: 1987-10-25T04:00:00Z unix=562132800 wall=1987-10-25T00:00:00 gap-until(1987-10-25T01:00:00) type=1 -03 -03 dst + 0029: 1988-02-07T03:00:00Z unix=571201200 wall=1988-02-06T23:00:00 fold-until(1988-02-07T00:00:00) type=2 -04 -04 + 0030: 1999-10-03T04:00:00Z unix=938923200 wall=1999-10-03T00:00:00 gap-until(1999-10-03T01:00:00) type=1 -03 -03 dst + 0031: 2000-02-27T03:00:00Z unix=951620400 wall=2000-02-26T23:00:00 fold-until(2000-02-27T00:00:00) type=2 -04 -04 + 0032: 2000-10-08T04:00:00Z unix=970977600 wall=2000-10-08T00:00:00 gap-until(2000-10-08T01:00:00) type=1 -03 -03 dst + 0033: 2000-10-15T03:00:00Z unix=971578800 wall=2000-10-14T23:00:00 fold-until(2000-10-15T00:00:00) type=2 -04 -04 +POSIX TIME ZONE STRING + <-04>4 diff --git a/src/tz/testdata.rs b/src/tz/testdata.rs index cdd87c04..ac5d68a2 100644 --- a/src/tz/testdata.rs +++ b/src/tz/testdata.rs @@ -71,6 +71,12 @@ pub(crate) static TZIF_TEST_FILES: &[TzifTestFile] = &[ name: "America/Sao_Paulo", data: include_bytes!("testdata/america-sao-paulo.tzif"), }, + // Another test file I added for a region that eliminated DST and thus + // has a "final" time zone transition. + TzifTestFile { + name: "America/Boa_Vista", + data: include_bytes!("testdata/america-boa-vista.tzif"), + }, TzifTestFile { name: "UTC", data: include_bytes!("testdata/utc.tzif") }, ]; diff --git a/src/tz/testdata/america-boa-vista.tzif b/src/tz/testdata/america-boa-vista.tzif new file mode 100644 index 0000000000000000000000000000000000000000..08d518b151425f85458727fbf69f6e601f12be86 GIT binary patch literal 618 zcmbu+Jxjwt0Eh9^H%fe4TpX&ct_p`O*ow%hs|rFu@B=71?fBU;_akgzjtqohTf4M0~4ybIwPB< zJJGUz-MZ*e?O;fL=JP6Xb0$ajp4901qE4ngl{(m!W4(`Rx^b_w?vcpV>N5A7P#v*f zo-iyWEh`KkZev~MU0d;`WyhKnX>&7{^M|>iIZQ@oN1Ex#d?W#qA=0EkazvUWNERdw zk_SnIWI|FQxsYT?wn&o>$rou7A{mjC{7iBpNs+8bS|l%$7|D#JMsi2~Ke;_?!p~*w EZ|_nGD*ylh literal 0 HcmV?d00001 diff --git a/src/tz/timezone.rs b/src/tz/timezone.rs index df148681..44cbd9fb 100644 --- a/src/tz/timezone.rs +++ b/src/tz/timezone.rs @@ -3829,4 +3829,35 @@ mod tests { assert!(tz.to_fixed_offset().is_err()); } } + + /// This tests that `TimeZone::following` correctly returns a final time + /// zone transition. + #[cfg(feature = "alloc")] + #[test] + fn time_zone_following_boa_vista() { + use alloc::{vec, vec::Vec}; + + let test_file = TzifTestFile::get("America/Boa_Vista"); + let tz = TimeZone::tzif(test_file.name, test_file.data).unwrap(); + let last4: Vec = vec![ + "1999-10-03T04Z".parse().unwrap(), + "2000-02-27T03Z".parse().unwrap(), + "2000-10-08T04Z".parse().unwrap(), + "2000-10-15T03Z".parse().unwrap(), + ]; + + let start: Timestamp = "2001-01-01T00Z".parse().unwrap(); + let mut transitions: Vec = + tz.preceding(start).take(4).map(|t| t.timestamp()).collect(); + transitions.reverse(); + assert_eq!(transitions, last4); + + let start: Timestamp = "1990-01-01T00Z".parse().unwrap(); + let transitions: Vec = + tz.following(start).map(|t| t.timestamp()).collect(); + // The regression here was that the 2000-10-15 transition wasn't + // being found here, despite the fact that it existed and was found + // by `preceding`. + assert_eq!(transitions, last4); + } } diff --git a/src/tz/tzif.rs b/src/tz/tzif.rs index e554466c..1459321a 100644 --- a/src/tz/tzif.rs +++ b/src/tz/tzif.rs @@ -453,7 +453,7 @@ impl< // The first transition is a dummy that we insert, so if we land on // it here, treat it as if it doesn't exist. return None; - } else if index >= self.timestamps().len() - 1 { + } else if index >= self.timestamps().len() { if let Some(posix_tz) = self.posix_tz() { // Since the POSIX TZ must be consistent with the last // transition, it must be the case that next.timestamp <= @@ -470,6 +470,16 @@ impl< // But unlike the previous case, if we get `None` here, then // that is the real answer because there are no other known // future time zone transitions. + // + // 2025-05-05: OK, this could return `None` and this is fine. + // It happens for time zones that had DST but then stopped + // it at some point in the past. The POSIX time zone has no + // DST and thus returns `None`. That's fine. But there was a + // problem: we were using the POSIX time zone even when there + // was a historical time zone transition after the timestamp + // given. That was fixed by changing the condition when we get + // here: it can only happen when the timestamp given comes at + // or after all historical time zone transitions. return posix_tz.next_transition(ts); } self.timestamps().len() - 1 From 5d60f33c588eae254a4a0be1c5f2bb6501c8da84 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Mon, 5 May 2025 22:06:38 -0400 Subject: [PATCH 4/4] 0.2.13 --- Cargo.toml | 4 ++-- crates/jiff-static/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1a842cc0..188c056b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jiff" -version = "0.2.12" #:version +version = "0.2.13" #:version authors = ["Andrew Gallant "] license = "Unlicense OR MIT" repository = "https://github.com/BurntSushi/jiff" @@ -196,7 +196,7 @@ serde = { version = "1.0.203", optional = true, default-features = false } # # See: https://github.com/matklad/macro-dep-test [target.'cfg(any())'.dependencies] -jiff-static = { version = "=0.2.12", path = "crates/jiff-static" } +jiff-static = { version = "=0.2.13", path = "crates/jiff-static" } # Note that the `cfg` gate for the `tzdb-bundle-platform` must repeat the # target gate on this dependency. The intent is that `tzdb-bundle-platform` diff --git a/crates/jiff-static/Cargo.toml b/crates/jiff-static/Cargo.toml index d5a51ab5..b23c4d50 100644 --- a/crates/jiff-static/Cargo.toml +++ b/crates/jiff-static/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jiff-static" -version = "0.2.12" #:version +version = "0.2.13" #:version authors = ["Andrew Gallant "] license = "Unlicense OR MIT" homepage = "https://github.com/BurntSushi/jiff/tree/master/crates/jiff-static"