1616import tempfile
1717import unittest
1818from datetime import date , datetime , time , timedelta , timezone
19+ from functools import cached_property
1920
2021from . import _support as test_support
2122from ._support import (
@@ -72,10 +73,18 @@ class TzPathUserMixin:
7273 def tzpath (self ): # pragma: nocover
7374 return None
7475
76+ @property
77+ def block_tzdata (self ):
78+ return True
79+
7580 def setUp (self ):
7681 with contextlib .ExitStack () as stack :
7782 stack .enter_context (
78- self .tzpath_context (self .tzpath , lock = TZPATH_TEST_LOCK )
83+ self .tzpath_context (
84+ self .tzpath ,
85+ block_tzdata = self .block_tzdata ,
86+ lock = TZPATH_TEST_LOCK ,
87+ )
7988 )
8089 self .addCleanup (stack .pop_all ().close )
8190
@@ -522,6 +531,10 @@ class TZDataTests(ZoneInfoTest):
522531 def tzpath (self ):
523532 return []
524533
534+ @property
535+ def block_tzdata (self ):
536+ return False
537+
525538 def zone_from_key (self , key ):
526539 return self .klass (key = key )
527540
@@ -1628,6 +1641,32 @@ class CTzPathTest(TzPathTest):
16281641class TestModule (ZoneInfoTestBase ):
16291642 module = py_zoneinfo
16301643
1644+ @property
1645+ def zoneinfo_data (self ):
1646+ return ZONEINFO_DATA
1647+
1648+ @cached_property
1649+ def _UTC_bytes (self ):
1650+ zone_file = self .zoneinfo_data .path_from_key ("UTC" )
1651+ with open (zone_file , "rb" ) as f :
1652+ return f .read ()
1653+
1654+ def touch_zone (self , key , tz_root ):
1655+ """Creates a valid TZif file at key under the zoneinfo root tz_root.
1656+
1657+ tz_root must exist, but all folders below that will be created.
1658+ """
1659+ if not os .path .exists (tz_root ):
1660+ raise FileNotFoundError (f"{ tz_root } does not exist." )
1661+
1662+ root_dir , * tail = key .rsplit ("/" , 1 )
1663+ if tail : # If there's no tail, then the first component isn't a dir
1664+ os .makedirs (os .path .join (tz_root , root_dir ), exist_ok = True )
1665+
1666+ zonefile_path = os .path .join (tz_root , key )
1667+ with open (zonefile_path , "wb" ) as f :
1668+ f .write (self ._UTC_bytes )
1669+
16311670 def test_getattr_error (self ):
16321671 with self .assertRaises (AttributeError ):
16331672 self .module .NOATTRIBUTE
@@ -1648,6 +1687,79 @@ def test_dir_unique(self):
16481687
16491688 self .assertCountEqual (module_dir , module_unique )
16501689
1690+ def test_available_timezones (self ):
1691+ with self .tzpath_context ([self .zoneinfo_data .tzpath ]):
1692+ self .assertTrue (self .zoneinfo_data .keys ) # Sanity check
1693+
1694+ available_keys = self .module .available_timezones ()
1695+ zoneinfo_keys = set (self .zoneinfo_data .keys )
1696+
1697+ # If tzdata is not present, zoneinfo_keys == available_keys,
1698+ # otherwise it should be a subset.
1699+ union = zoneinfo_keys & available_keys
1700+ self .assertEqual (zoneinfo_keys , union )
1701+
1702+ def test_available_timezones_weirdzone (self ):
1703+ with tempfile .TemporaryDirectory () as td :
1704+ # Make a fictional zone at "Mars/Olympus_Mons"
1705+ self .touch_zone ("Mars/Olympus_Mons" , td )
1706+
1707+ with self .tzpath_context ([td ]):
1708+ available_keys = self .module .available_timezones ()
1709+ self .assertIn ("Mars/Olympus_Mons" , available_keys )
1710+
1711+ def test_folder_exclusions (self ):
1712+ expected = {
1713+ "America/Los_Angeles" ,
1714+ "America/Santiago" ,
1715+ "America/Indiana/Indianapolis" ,
1716+ "UTC" ,
1717+ "Europe/Paris" ,
1718+ "Europe/London" ,
1719+ "Asia/Tokyo" ,
1720+ "Australia/Sydney" ,
1721+ }
1722+
1723+ base_tree = list (expected )
1724+ posix_tree = [f"posix/{ x } " for x in base_tree ]
1725+ right_tree = [f"right/{ x } " for x in base_tree ]
1726+
1727+ cases = [
1728+ ("base_tree" , base_tree ),
1729+ ("base_and_posix" , base_tree + posix_tree ),
1730+ ("base_and_right" , base_tree + right_tree ),
1731+ ("all_trees" , base_tree + right_tree + posix_tree ),
1732+ ]
1733+
1734+ with tempfile .TemporaryDirectory () as td :
1735+ for case_name , tree in cases :
1736+ tz_root = os .path .join (td , case_name )
1737+ os .mkdir (tz_root )
1738+
1739+ for key in tree :
1740+ self .touch_zone (key , tz_root )
1741+
1742+ with self .tzpath_context ([tz_root ]):
1743+ with self .subTest (case_name ):
1744+ actual = self .module .available_timezones ()
1745+ self .assertEqual (actual , expected )
1746+
1747+ def test_exclude_posixrules (self ):
1748+ expected = {
1749+ "America/New_York" ,
1750+ "Europe/London" ,
1751+ }
1752+
1753+ tree = list (expected ) + ["posixrules" ]
1754+
1755+ with tempfile .TemporaryDirectory () as td :
1756+ for key in tree :
1757+ self .touch_zone (key , td )
1758+
1759+ with self .tzpath_context ([td ]):
1760+ actual = self .module .available_timezones ()
1761+ self .assertEqual (actual , expected )
1762+
16511763
16521764class CTestModule (TestModule ):
16531765 module = c_zoneinfo
0 commit comments