2121//! # use zoneinfo64::{Offset, PossibleOffset, ZoneInfo64, UtcOffset};
2222//!
2323//! // Needs to be u32-aligned
24- //! let resb = resb::include_bytes_as_u32!("./data/zoneinfo64 .res");
24+ //! let resb = resb::include_bytes_as_u32!("./data/2026a .res");
2525//! // Then we parse the data
2626//! let zoneinfo = ZoneInfo64::try_from_u32s(resb)
2727//! .expect("Error processing resource bundle file");
@@ -71,7 +71,7 @@ mod deserialize;
7171
7272/// A bundled zoneinfo64.res that can be used for testing. No guarantee is made
7373/// as to the version in use; though we will try to keep it up to date.
74- pub const ZONEINFO64_RES_FOR_TESTING : & [ u32 ] = resb:: include_bytes_as_u32!( "./data/zoneinfo64 .res" ) ;
74+ pub const ZONEINFO64_RES_FOR_TESTING : & [ u32 ] = resb:: include_bytes_as_u32!( "./data/2026a .res" ) ;
7575
7676const EPOCH : RataDie = calendrical_calculations:: gregorian:: fixed_from_gregorian ( 1970 , 1 , 1 ) ;
7777const SECONDS_IN_UTC_DAY : i64 = 24 * 60 * 60 ;
@@ -181,21 +181,6 @@ impl<'a> ZoneInfo64<'a> {
181181 deserialize:: deserialize ( resb)
182182 }
183183 #[ cfg( test) ]
184- fn is_alias ( & self , iana : & str ) -> bool {
185- let Some ( idx) = self
186- . names
187- . binary_search_by ( |& n| n. chars ( ) . cmp ( iana. chars ( ) ) )
188- . ok ( )
189- else {
190- return false ;
191- } ;
192-
193- #[ expect( clippy:: indexing_slicing) ] // zones and names have the same length
194- let zone = & self . zones [ idx] ;
195-
196- matches ! ( zone, & TzZone :: Int ( _) )
197- }
198- #[ cfg( test) ]
199184 fn iter ( & ' a self ) -> impl Iterator < Item = Zone < ' a > > {
200185 ( 0 ..self . names . len ( ) ) . map ( move |i| Zone :: from_raw_parts ( ( i as u16 , self ) ) )
201186 }
@@ -724,29 +709,41 @@ impl<'a> Zone<'a> {
724709#[ cfg( test) ]
725710mod tests {
726711 use super :: * ;
727- use chrono_tz:: Tz ;
728712 use itertools:: Itertools ;
713+ use jiff:: ToSpan ;
729714 use std:: { str:: FromStr , sync:: LazyLock } ;
730715
716+ // We test on 2025b as that's the latest version that is available in both chrono and jiff.
717+ // The goal here is not to assert up-to-date TZDB data, but to test that zoneinfo64, chrono,
718+ // and jiff agree.
731719 pub ( crate ) static TZDB : LazyLock < ZoneInfo64 > = LazyLock :: new ( || {
732- ZoneInfo64 :: try_from_u32s ( ZONEINFO64_RES_FOR_TESTING )
720+ ZoneInfo64 :: try_from_u32s ( resb :: include_bytes_as_u32! ( "../tests/data/2025b.res" ) )
733721 . expect ( "Error processing resource bundle file" )
734722 } ) ;
735723
724+ const _: ( ) = assert ! ( matches!(
725+ chrono_tz_2025b:: IANA_TZDB_VERSION . as_bytes( ) ,
726+ b"2025b"
727+ ) ) ;
728+ const _: ( ) = assert ! ( matches!(
729+ jiff_tzdb_2025b:: VERSION . unwrap( ) . as_bytes( ) ,
730+ b"2025b"
731+ ) ) ;
732+
736733 /// Tests an invariant we rely on in our code
737734 #[ test]
738735 fn test_monotonic_transition_times ( ) {
739- for chrono in time_zones_to_test ( ) {
740- let iana = chrono. name ( ) ;
741- let zoneinfo64 = TZDB . get ( iana) . unwrap ( ) . simple ( ) ;
736+ for zone in TZDB . iter ( ) {
737+ let zoneinfo64 = zone. simple ( ) ;
742738
743739 for ( prev, curr) in ( -1 ..zoneinfo64. transition_count ( ) )
744740 . map ( |idx| zoneinfo64. transition_offset_at ( idx) )
745741 . tuple_windows :: < ( _ , _ ) > ( )
746742 {
747743 assert ! (
748744 prev. since < curr. since,
749- "{iana}: Transition times should be strictly increasing ({prev:?}, {curr:?})"
745+ "{:?}: Transition times should be strictly increasing ({prev:?}, {curr:?})" ,
746+ zone. name( )
750747 ) ;
751748 }
752749 }
@@ -755,9 +752,8 @@ mod tests {
755752 /// Tests an invariant we rely on in our code
756753 #[ test]
757754 fn test_transition_local_times_do_not_overlap ( ) {
758- for chrono in time_zones_to_test ( ) {
759- let iana = chrono. name ( ) ;
760- let zoneinfo64 = TZDB . get ( iana) . unwrap ( ) . simple ( ) ;
755+ for zone in TZDB . iter ( ) {
756+ let zoneinfo64 = zone. simple ( ) ;
761757
762758 for ( prev, curr) in ( -1 ..zoneinfo64. transition_count ( ) )
763759 . map ( |idx| zoneinfo64. transition_offset_at ( idx) )
@@ -768,26 +764,22 @@ mod tests {
768764
769765 assert ! (
770766 prev_wall < curr_wall,
771- "{iana}: Transitions should not be so close as to create a ambiguity ({prev:?}, {curr:?}"
767+ "{:?}: Transitions should not be so close as to create a ambiguity ({prev:?}, {curr:?}" ,
768+ zone. name( )
772769 ) ;
773770 }
774771 }
775772 }
776773
777- pub ( crate ) fn time_zones_to_test ( ) -> impl Iterator < Item = Tz > {
778- chrono_tz:: TZ_VARIANTS
779- . iter ( )
780- . copied ( )
781- . filter ( |tz| !TZDB . is_alias ( tz. name ( ) ) )
782- }
783-
784774 fn has_rearguard_diff ( iana : & str ) -> bool {
785775 matches ! (
786776 iana,
787777 "Africa/Casablanca"
788778 | "Africa/El_Aaiun"
789779 | "Africa/Windhoek"
780+ | "Eire"
790781 | "Europe/Dublin"
782+ | "Europe/Bratislava"
791783 | "Europe/Prague"
792784 )
793785 }
@@ -796,16 +788,10 @@ mod tests {
796788 fn test_against_chrono ( ) {
797789 use chrono:: Offset ;
798790 use chrono:: TimeZone ;
799- use chrono_tz :: OffsetComponents ;
791+ use chrono_tz_2025b :: OffsetComponents ;
800792
801- for chrono in time_zones_to_test ( ) {
793+ for chrono in chrono_tz_2025b :: TZ_VARIANTS {
802794 let iana = chrono. name ( ) ;
803-
804- if iana == "America/Tijuana" {
805- // 2025c not yet in chrono
806- continue ;
807- }
808-
809795 let zoneinfo64 = TZDB . get ( iana) . unwrap ( ) ;
810796
811797 for seconds_since_epoch in transitions ( iana, false )
@@ -850,67 +836,45 @@ mod tests {
850836 }
851837
852838 fn transitions ( iana : & str , require_offset_change : bool ) -> Vec < Transition > {
853- let tz = jiff:: tz:: TimeZone :: get ( iana) . unwrap ( ) ;
854- let mut transitions = tz
855- // Chrono only evaluates rules until 2100
856- . preceding ( jiff:: Timestamp :: from_str ( "2100-01-01T00:00:00Z" ) . unwrap ( ) )
857- . map ( |t| Transition {
858- since : t. timestamp ( ) . as_second ( ) ,
859- offset : UtcOffset ( t. offset ( ) . seconds ( ) ) ,
860- rule_applies : t. dst ( ) . is_dst ( ) ,
839+ let max = jiff:: Timestamp :: from_str ( "2100-01-01T00:00:00Z" ) . unwrap ( ) ;
840+ let tz = jiff:: tz:: TimeZone :: tzif ( iana, jiff_tzdb_2025b:: get ( iana) . unwrap ( ) . 1 ) . unwrap ( ) ;
841+ tz. following ( jiff:: Timestamp :: MIN )
842+ // Evaluate rules until 2100
843+ . take_while ( |t| t. timestamp ( ) < max)
844+ . filter_map ( |t| {
845+ let before = tz. to_offset_info ( t. timestamp ( ) - 1 . second ( ) ) ;
846+ ( if require_offset_change {
847+ t. offset ( ) != before. offset ( )
848+ } else {
849+ before. offset ( ) != t. offset ( )
850+ || before. dst ( ) != t. dst ( )
851+ // This is a super weird transition in Europe/Paris that would be removed by
852+ // our rule, but we want to keep it because it's in zoneinfo64.
853+ // 1944-04-03T01:00:00Z, (1.0, 1.0)
854+ // 1944-08-24T22:00:00Z, (0.0, 2.0) <- same offset and also DST
855+ // 1944-10-07T23:00:00Z, (0.0, 1.0)
856+ || t. timestamp ( ) . as_second ( ) == -800071200
857+ } )
858+ . then_some ( Transition {
859+ since : t. timestamp ( ) . as_second ( ) ,
860+ offset : UtcOffset ( t. offset ( ) . seconds ( ) ) ,
861+ rule_applies : t. dst ( ) . is_dst ( ) ,
862+ } )
861863 } )
862- . collect :: < Vec < _ > > ( ) ;
863-
864- transitions. reverse ( ) ;
865-
866- // jiff returns transitions also if only the name changes, we don't
867- transitions. retain ( |t| {
868- let before = tz. to_offset_info ( jiff:: Timestamp :: from_second ( t. since - 1 ) . unwrap ( ) ) ;
869- if require_offset_change {
870- before. offset ( ) . seconds ( ) != t. offset . 0
871- } else {
872- before. offset ( ) . seconds ( ) != t. offset . 0
873- || before. dst ( ) . is_dst ( ) != t. rule_applies
874- // This is a super weird transition that would be removed by our rule,
875- // but we want to keep it because it's in zoneinfo64.
876- // 1944-04-03T01:00:00Z, (1.0, 1.0)
877- // 1944-08-24T22:00:00Z, (0.0, 2.0) <- same offset and also DST
878- // 1944-10-07T23:00:00Z, (0.0, 1.0)
879- || ( iana == "Europe/Paris" && t. since == -800071200 )
880- }
881- } ) ;
882-
883- transitions
864+ . collect ( )
884865 }
885866
886867 #[ test]
887868 fn test_transition_against_jiff ( ) {
888- for ( zone , require_offset_change) in
889- time_zones_to_test ( ) . cartesian_product ( [ true , false ] . into_iter ( ) )
869+ for ( iana , require_offset_change) in
870+ jiff_tzdb_2025b :: available ( ) . cartesian_product ( [ true , false ] . into_iter ( ) )
890871 {
891- let iana = zone. name ( ) ;
892872 let transitions = transitions ( iana, require_offset_change) ;
893873
894874 if has_rearguard_diff ( iana) || transitions. is_empty ( ) {
895875 continue ;
896876 }
897877
898- // TODO: investigate why these zones don't work with jiff/tzdb-bundle-always
899- // https://github.com/unicode-org/icu4x/issues/7813
900- if matches ! (
901- iana,
902- "America/Ciudad_Juarez"
903- | "America/Indiana/Petersburg"
904- | "America/Indiana/Vincennes"
905- | "America/Indiana/Winamac"
906- | "America/Metlakatla"
907- | "America/North_Dakota/Beulah"
908- // Broke in the 2025c update
909- | "Europe/Chisinau"
910- ) {
911- continue ;
912- }
913-
914878 let zoneinfo64 = TZDB . get ( iana) . unwrap ( ) ;
915879
916880 assert_eq ! (
0 commit comments