17db96d56Sopenharmony_cifrom __future__ import annotations 27db96d56Sopenharmony_ci 37db96d56Sopenharmony_ciimport base64 47db96d56Sopenharmony_ciimport contextlib 57db96d56Sopenharmony_ciimport dataclasses 67db96d56Sopenharmony_ciimport importlib.metadata 77db96d56Sopenharmony_ciimport io 87db96d56Sopenharmony_ciimport json 97db96d56Sopenharmony_ciimport os 107db96d56Sopenharmony_ciimport pathlib 117db96d56Sopenharmony_ciimport pickle 127db96d56Sopenharmony_ciimport re 137db96d56Sopenharmony_ciimport shutil 147db96d56Sopenharmony_ciimport struct 157db96d56Sopenharmony_ciimport tempfile 167db96d56Sopenharmony_ciimport unittest 177db96d56Sopenharmony_cifrom datetime import date, datetime, time, timedelta, timezone 187db96d56Sopenharmony_cifrom functools import cached_property 197db96d56Sopenharmony_ci 207db96d56Sopenharmony_cifrom test.test_zoneinfo import _support as test_support 217db96d56Sopenharmony_cifrom test.test_zoneinfo._support import OS_ENV_LOCK, TZPATH_TEST_LOCK, ZoneInfoTestBase 227db96d56Sopenharmony_cifrom test.support.import_helper import import_module 237db96d56Sopenharmony_ci 247db96d56Sopenharmony_cilzma = import_module('lzma') 257db96d56Sopenharmony_cipy_zoneinfo, c_zoneinfo = test_support.get_modules() 267db96d56Sopenharmony_ci 277db96d56Sopenharmony_citry: 287db96d56Sopenharmony_ci importlib.metadata.metadata("tzdata") 297db96d56Sopenharmony_ci HAS_TZDATA_PKG = True 307db96d56Sopenharmony_ciexcept importlib.metadata.PackageNotFoundError: 317db96d56Sopenharmony_ci HAS_TZDATA_PKG = False 327db96d56Sopenharmony_ci 337db96d56Sopenharmony_ciZONEINFO_DATA = None 347db96d56Sopenharmony_ciZONEINFO_DATA_V1 = None 357db96d56Sopenharmony_ciTEMP_DIR = None 367db96d56Sopenharmony_ciDATA_DIR = pathlib.Path(__file__).parent / "data" 377db96d56Sopenharmony_ciZONEINFO_JSON = DATA_DIR / "zoneinfo_data.json" 387db96d56Sopenharmony_ci 397db96d56Sopenharmony_ci# Useful constants 407db96d56Sopenharmony_ciZERO = timedelta(0) 417db96d56Sopenharmony_ciONE_H = timedelta(hours=1) 427db96d56Sopenharmony_ci 437db96d56Sopenharmony_ci 447db96d56Sopenharmony_cidef setUpModule(): 457db96d56Sopenharmony_ci global TEMP_DIR 467db96d56Sopenharmony_ci global ZONEINFO_DATA 477db96d56Sopenharmony_ci global ZONEINFO_DATA_V1 487db96d56Sopenharmony_ci 497db96d56Sopenharmony_ci TEMP_DIR = pathlib.Path(tempfile.mkdtemp(prefix="zoneinfo")) 507db96d56Sopenharmony_ci ZONEINFO_DATA = ZoneInfoData(ZONEINFO_JSON, TEMP_DIR / "v2") 517db96d56Sopenharmony_ci ZONEINFO_DATA_V1 = ZoneInfoData(ZONEINFO_JSON, TEMP_DIR / "v1", v1=True) 527db96d56Sopenharmony_ci 537db96d56Sopenharmony_ci 547db96d56Sopenharmony_cidef tearDownModule(): 557db96d56Sopenharmony_ci shutil.rmtree(TEMP_DIR) 567db96d56Sopenharmony_ci 577db96d56Sopenharmony_ci 587db96d56Sopenharmony_ciclass TzPathUserMixin: 597db96d56Sopenharmony_ci """ 607db96d56Sopenharmony_ci Adds a setUp() and tearDown() to make TZPATH manipulations thread-safe. 617db96d56Sopenharmony_ci 627db96d56Sopenharmony_ci Any tests that require manipulation of the TZPATH global are necessarily 637db96d56Sopenharmony_ci thread unsafe, so we will acquire a lock and reset the TZPATH variable 647db96d56Sopenharmony_ci to the default state before each test and release the lock after the test 657db96d56Sopenharmony_ci is through. 667db96d56Sopenharmony_ci """ 677db96d56Sopenharmony_ci 687db96d56Sopenharmony_ci @property 697db96d56Sopenharmony_ci def tzpath(self): # pragma: nocover 707db96d56Sopenharmony_ci return None 717db96d56Sopenharmony_ci 727db96d56Sopenharmony_ci @property 737db96d56Sopenharmony_ci def block_tzdata(self): 747db96d56Sopenharmony_ci return True 757db96d56Sopenharmony_ci 767db96d56Sopenharmony_ci def setUp(self): 777db96d56Sopenharmony_ci with contextlib.ExitStack() as stack: 787db96d56Sopenharmony_ci stack.enter_context( 797db96d56Sopenharmony_ci self.tzpath_context( 807db96d56Sopenharmony_ci self.tzpath, 817db96d56Sopenharmony_ci block_tzdata=self.block_tzdata, 827db96d56Sopenharmony_ci lock=TZPATH_TEST_LOCK, 837db96d56Sopenharmony_ci ) 847db96d56Sopenharmony_ci ) 857db96d56Sopenharmony_ci self.addCleanup(stack.pop_all().close) 867db96d56Sopenharmony_ci 877db96d56Sopenharmony_ci super().setUp() 887db96d56Sopenharmony_ci 897db96d56Sopenharmony_ci 907db96d56Sopenharmony_ciclass DatetimeSubclassMixin: 917db96d56Sopenharmony_ci """ 927db96d56Sopenharmony_ci Replaces all ZoneTransition transition dates with a datetime subclass. 937db96d56Sopenharmony_ci """ 947db96d56Sopenharmony_ci 957db96d56Sopenharmony_ci class DatetimeSubclass(datetime): 967db96d56Sopenharmony_ci @classmethod 977db96d56Sopenharmony_ci def from_datetime(cls, dt): 987db96d56Sopenharmony_ci return cls( 997db96d56Sopenharmony_ci dt.year, 1007db96d56Sopenharmony_ci dt.month, 1017db96d56Sopenharmony_ci dt.day, 1027db96d56Sopenharmony_ci dt.hour, 1037db96d56Sopenharmony_ci dt.minute, 1047db96d56Sopenharmony_ci dt.second, 1057db96d56Sopenharmony_ci dt.microsecond, 1067db96d56Sopenharmony_ci tzinfo=dt.tzinfo, 1077db96d56Sopenharmony_ci fold=dt.fold, 1087db96d56Sopenharmony_ci ) 1097db96d56Sopenharmony_ci 1107db96d56Sopenharmony_ci def load_transition_examples(self, key): 1117db96d56Sopenharmony_ci transition_examples = super().load_transition_examples(key) 1127db96d56Sopenharmony_ci for zt in transition_examples: 1137db96d56Sopenharmony_ci dt = zt.transition 1147db96d56Sopenharmony_ci new_dt = self.DatetimeSubclass.from_datetime(dt) 1157db96d56Sopenharmony_ci new_zt = dataclasses.replace(zt, transition=new_dt) 1167db96d56Sopenharmony_ci yield new_zt 1177db96d56Sopenharmony_ci 1187db96d56Sopenharmony_ci 1197db96d56Sopenharmony_ciclass ZoneInfoTest(TzPathUserMixin, ZoneInfoTestBase): 1207db96d56Sopenharmony_ci module = py_zoneinfo 1217db96d56Sopenharmony_ci class_name = "ZoneInfo" 1227db96d56Sopenharmony_ci 1237db96d56Sopenharmony_ci def setUp(self): 1247db96d56Sopenharmony_ci super().setUp() 1257db96d56Sopenharmony_ci 1267db96d56Sopenharmony_ci # This is necessary because various subclasses pull from different 1277db96d56Sopenharmony_ci # data sources (e.g. tzdata, V1 files, etc). 1287db96d56Sopenharmony_ci self.klass.clear_cache() 1297db96d56Sopenharmony_ci 1307db96d56Sopenharmony_ci @property 1317db96d56Sopenharmony_ci def zoneinfo_data(self): 1327db96d56Sopenharmony_ci return ZONEINFO_DATA 1337db96d56Sopenharmony_ci 1347db96d56Sopenharmony_ci @property 1357db96d56Sopenharmony_ci def tzpath(self): 1367db96d56Sopenharmony_ci return [self.zoneinfo_data.tzpath] 1377db96d56Sopenharmony_ci 1387db96d56Sopenharmony_ci def zone_from_key(self, key): 1397db96d56Sopenharmony_ci return self.klass(key) 1407db96d56Sopenharmony_ci 1417db96d56Sopenharmony_ci def zones(self): 1427db96d56Sopenharmony_ci return ZoneDumpData.transition_keys() 1437db96d56Sopenharmony_ci 1447db96d56Sopenharmony_ci def fixed_offset_zones(self): 1457db96d56Sopenharmony_ci return ZoneDumpData.fixed_offset_zones() 1467db96d56Sopenharmony_ci 1477db96d56Sopenharmony_ci def load_transition_examples(self, key): 1487db96d56Sopenharmony_ci return ZoneDumpData.load_transition_examples(key) 1497db96d56Sopenharmony_ci 1507db96d56Sopenharmony_ci def test_str(self): 1517db96d56Sopenharmony_ci # Zones constructed with a key must have str(zone) == key 1527db96d56Sopenharmony_ci for key in self.zones(): 1537db96d56Sopenharmony_ci with self.subTest(key): 1547db96d56Sopenharmony_ci zi = self.zone_from_key(key) 1557db96d56Sopenharmony_ci 1567db96d56Sopenharmony_ci self.assertEqual(str(zi), key) 1577db96d56Sopenharmony_ci 1587db96d56Sopenharmony_ci # Zones with no key constructed should have str(zone) == repr(zone) 1597db96d56Sopenharmony_ci file_key = self.zoneinfo_data.keys[0] 1607db96d56Sopenharmony_ci file_path = self.zoneinfo_data.path_from_key(file_key) 1617db96d56Sopenharmony_ci 1627db96d56Sopenharmony_ci with open(file_path, "rb") as f: 1637db96d56Sopenharmony_ci with self.subTest(test_name="Repr test", path=file_path): 1647db96d56Sopenharmony_ci zi_ff = self.klass.from_file(f) 1657db96d56Sopenharmony_ci self.assertEqual(str(zi_ff), repr(zi_ff)) 1667db96d56Sopenharmony_ci 1677db96d56Sopenharmony_ci def test_repr(self): 1687db96d56Sopenharmony_ci # The repr is not guaranteed, but I think we can insist that it at 1697db96d56Sopenharmony_ci # least contain the name of the class. 1707db96d56Sopenharmony_ci key = next(iter(self.zones())) 1717db96d56Sopenharmony_ci 1727db96d56Sopenharmony_ci zi = self.klass(key) 1737db96d56Sopenharmony_ci class_name = self.class_name 1747db96d56Sopenharmony_ci with self.subTest(name="from key"): 1757db96d56Sopenharmony_ci self.assertRegex(repr(zi), class_name) 1767db96d56Sopenharmony_ci 1777db96d56Sopenharmony_ci file_key = self.zoneinfo_data.keys[0] 1787db96d56Sopenharmony_ci file_path = self.zoneinfo_data.path_from_key(file_key) 1797db96d56Sopenharmony_ci with open(file_path, "rb") as f: 1807db96d56Sopenharmony_ci zi_ff = self.klass.from_file(f, key=file_key) 1817db96d56Sopenharmony_ci 1827db96d56Sopenharmony_ci with self.subTest(name="from file with key"): 1837db96d56Sopenharmony_ci self.assertRegex(repr(zi_ff), class_name) 1847db96d56Sopenharmony_ci 1857db96d56Sopenharmony_ci with open(file_path, "rb") as f: 1867db96d56Sopenharmony_ci zi_ff_nk = self.klass.from_file(f) 1877db96d56Sopenharmony_ci 1887db96d56Sopenharmony_ci with self.subTest(name="from file without key"): 1897db96d56Sopenharmony_ci self.assertRegex(repr(zi_ff_nk), class_name) 1907db96d56Sopenharmony_ci 1917db96d56Sopenharmony_ci def test_key_attribute(self): 1927db96d56Sopenharmony_ci key = next(iter(self.zones())) 1937db96d56Sopenharmony_ci 1947db96d56Sopenharmony_ci def from_file_nokey(key): 1957db96d56Sopenharmony_ci with open(self.zoneinfo_data.path_from_key(key), "rb") as f: 1967db96d56Sopenharmony_ci return self.klass.from_file(f) 1977db96d56Sopenharmony_ci 1987db96d56Sopenharmony_ci constructors = ( 1997db96d56Sopenharmony_ci ("Primary constructor", self.klass, key), 2007db96d56Sopenharmony_ci ("no_cache", self.klass.no_cache, key), 2017db96d56Sopenharmony_ci ("from_file", from_file_nokey, None), 2027db96d56Sopenharmony_ci ) 2037db96d56Sopenharmony_ci 2047db96d56Sopenharmony_ci for msg, constructor, expected in constructors: 2057db96d56Sopenharmony_ci zi = constructor(key) 2067db96d56Sopenharmony_ci 2077db96d56Sopenharmony_ci # Ensure that the key attribute is set to the input to ``key`` 2087db96d56Sopenharmony_ci with self.subTest(msg): 2097db96d56Sopenharmony_ci self.assertEqual(zi.key, expected) 2107db96d56Sopenharmony_ci 2117db96d56Sopenharmony_ci # Ensure that the key attribute is read-only 2127db96d56Sopenharmony_ci with self.subTest(f"{msg}: readonly"): 2137db96d56Sopenharmony_ci with self.assertRaises(AttributeError): 2147db96d56Sopenharmony_ci zi.key = "Some/Value" 2157db96d56Sopenharmony_ci 2167db96d56Sopenharmony_ci def test_bad_keys(self): 2177db96d56Sopenharmony_ci bad_keys = [ 2187db96d56Sopenharmony_ci "Eurasia/Badzone", # Plausible but does not exist 2197db96d56Sopenharmony_ci "BZQ", 2207db96d56Sopenharmony_ci "America.Los_Angeles", 2217db96d56Sopenharmony_ci "", # Non-ascii 2227db96d56Sopenharmony_ci "America/New\ud800York", # Contains surrogate character 2237db96d56Sopenharmony_ci ] 2247db96d56Sopenharmony_ci 2257db96d56Sopenharmony_ci for bad_key in bad_keys: 2267db96d56Sopenharmony_ci with self.assertRaises(self.module.ZoneInfoNotFoundError): 2277db96d56Sopenharmony_ci self.klass(bad_key) 2287db96d56Sopenharmony_ci 2297db96d56Sopenharmony_ci def test_bad_keys_paths(self): 2307db96d56Sopenharmony_ci bad_keys = [ 2317db96d56Sopenharmony_ci "/America/Los_Angeles", # Absolute path 2327db96d56Sopenharmony_ci "America/Los_Angeles/", # Trailing slash - not normalized 2337db96d56Sopenharmony_ci "../zoneinfo/America/Los_Angeles", # Traverses above TZPATH 2347db96d56Sopenharmony_ci "America/../America/Los_Angeles", # Not normalized 2357db96d56Sopenharmony_ci "America/./Los_Angeles", 2367db96d56Sopenharmony_ci ] 2377db96d56Sopenharmony_ci 2387db96d56Sopenharmony_ci for bad_key in bad_keys: 2397db96d56Sopenharmony_ci with self.assertRaises(ValueError): 2407db96d56Sopenharmony_ci self.klass(bad_key) 2417db96d56Sopenharmony_ci 2427db96d56Sopenharmony_ci def test_bad_zones(self): 2437db96d56Sopenharmony_ci bad_zones = [ 2447db96d56Sopenharmony_ci b"", # Empty file 2457db96d56Sopenharmony_ci b"AAAA3" + b" " * 15, # Bad magic 2467db96d56Sopenharmony_ci ] 2477db96d56Sopenharmony_ci 2487db96d56Sopenharmony_ci for bad_zone in bad_zones: 2497db96d56Sopenharmony_ci fobj = io.BytesIO(bad_zone) 2507db96d56Sopenharmony_ci with self.assertRaises(ValueError): 2517db96d56Sopenharmony_ci self.klass.from_file(fobj) 2527db96d56Sopenharmony_ci 2537db96d56Sopenharmony_ci def test_fromutc_errors(self): 2547db96d56Sopenharmony_ci key = next(iter(self.zones())) 2557db96d56Sopenharmony_ci zone = self.zone_from_key(key) 2567db96d56Sopenharmony_ci 2577db96d56Sopenharmony_ci bad_values = [ 2587db96d56Sopenharmony_ci (datetime(2019, 1, 1, tzinfo=timezone.utc), ValueError), 2597db96d56Sopenharmony_ci (datetime(2019, 1, 1), ValueError), 2607db96d56Sopenharmony_ci (date(2019, 1, 1), TypeError), 2617db96d56Sopenharmony_ci (time(0), TypeError), 2627db96d56Sopenharmony_ci (0, TypeError), 2637db96d56Sopenharmony_ci ("2019-01-01", TypeError), 2647db96d56Sopenharmony_ci ] 2657db96d56Sopenharmony_ci 2667db96d56Sopenharmony_ci for val, exc_type in bad_values: 2677db96d56Sopenharmony_ci with self.subTest(val=val): 2687db96d56Sopenharmony_ci with self.assertRaises(exc_type): 2697db96d56Sopenharmony_ci zone.fromutc(val) 2707db96d56Sopenharmony_ci 2717db96d56Sopenharmony_ci def test_utc(self): 2727db96d56Sopenharmony_ci zi = self.klass("UTC") 2737db96d56Sopenharmony_ci dt = datetime(2020, 1, 1, tzinfo=zi) 2747db96d56Sopenharmony_ci 2757db96d56Sopenharmony_ci self.assertEqual(dt.utcoffset(), ZERO) 2767db96d56Sopenharmony_ci self.assertEqual(dt.dst(), ZERO) 2777db96d56Sopenharmony_ci self.assertEqual(dt.tzname(), "UTC") 2787db96d56Sopenharmony_ci 2797db96d56Sopenharmony_ci def test_unambiguous(self): 2807db96d56Sopenharmony_ci test_cases = [] 2817db96d56Sopenharmony_ci for key in self.zones(): 2827db96d56Sopenharmony_ci for zone_transition in self.load_transition_examples(key): 2837db96d56Sopenharmony_ci test_cases.append( 2847db96d56Sopenharmony_ci ( 2857db96d56Sopenharmony_ci key, 2867db96d56Sopenharmony_ci zone_transition.transition - timedelta(days=2), 2877db96d56Sopenharmony_ci zone_transition.offset_before, 2887db96d56Sopenharmony_ci ) 2897db96d56Sopenharmony_ci ) 2907db96d56Sopenharmony_ci 2917db96d56Sopenharmony_ci test_cases.append( 2927db96d56Sopenharmony_ci ( 2937db96d56Sopenharmony_ci key, 2947db96d56Sopenharmony_ci zone_transition.transition + timedelta(days=2), 2957db96d56Sopenharmony_ci zone_transition.offset_after, 2967db96d56Sopenharmony_ci ) 2977db96d56Sopenharmony_ci ) 2987db96d56Sopenharmony_ci 2997db96d56Sopenharmony_ci for key, dt, offset in test_cases: 3007db96d56Sopenharmony_ci with self.subTest(key=key, dt=dt, offset=offset): 3017db96d56Sopenharmony_ci tzi = self.zone_from_key(key) 3027db96d56Sopenharmony_ci dt = dt.replace(tzinfo=tzi) 3037db96d56Sopenharmony_ci 3047db96d56Sopenharmony_ci self.assertEqual(dt.tzname(), offset.tzname, dt) 3057db96d56Sopenharmony_ci self.assertEqual(dt.utcoffset(), offset.utcoffset, dt) 3067db96d56Sopenharmony_ci self.assertEqual(dt.dst(), offset.dst, dt) 3077db96d56Sopenharmony_ci 3087db96d56Sopenharmony_ci def test_folds_and_gaps(self): 3097db96d56Sopenharmony_ci test_cases = [] 3107db96d56Sopenharmony_ci for key in self.zones(): 3117db96d56Sopenharmony_ci tests = {"folds": [], "gaps": []} 3127db96d56Sopenharmony_ci for zt in self.load_transition_examples(key): 3137db96d56Sopenharmony_ci if zt.fold: 3147db96d56Sopenharmony_ci test_group = tests["folds"] 3157db96d56Sopenharmony_ci elif zt.gap: 3167db96d56Sopenharmony_ci test_group = tests["gaps"] 3177db96d56Sopenharmony_ci else: 3187db96d56Sopenharmony_ci # Assign a random variable here to disable the peephole 3197db96d56Sopenharmony_ci # optimizer so that coverage can see this line. 3207db96d56Sopenharmony_ci # See bpo-2506 for more information. 3217db96d56Sopenharmony_ci no_peephole_opt = None 3227db96d56Sopenharmony_ci continue 3237db96d56Sopenharmony_ci 3247db96d56Sopenharmony_ci # Cases are of the form key, dt, fold, offset 3257db96d56Sopenharmony_ci dt = zt.anomaly_start - timedelta(seconds=1) 3267db96d56Sopenharmony_ci test_group.append((dt, 0, zt.offset_before)) 3277db96d56Sopenharmony_ci test_group.append((dt, 1, zt.offset_before)) 3287db96d56Sopenharmony_ci 3297db96d56Sopenharmony_ci dt = zt.anomaly_start 3307db96d56Sopenharmony_ci test_group.append((dt, 0, zt.offset_before)) 3317db96d56Sopenharmony_ci test_group.append((dt, 1, zt.offset_after)) 3327db96d56Sopenharmony_ci 3337db96d56Sopenharmony_ci dt = zt.anomaly_start + timedelta(seconds=1) 3347db96d56Sopenharmony_ci test_group.append((dt, 0, zt.offset_before)) 3357db96d56Sopenharmony_ci test_group.append((dt, 1, zt.offset_after)) 3367db96d56Sopenharmony_ci 3377db96d56Sopenharmony_ci dt = zt.anomaly_end - timedelta(seconds=1) 3387db96d56Sopenharmony_ci test_group.append((dt, 0, zt.offset_before)) 3397db96d56Sopenharmony_ci test_group.append((dt, 1, zt.offset_after)) 3407db96d56Sopenharmony_ci 3417db96d56Sopenharmony_ci dt = zt.anomaly_end 3427db96d56Sopenharmony_ci test_group.append((dt, 0, zt.offset_after)) 3437db96d56Sopenharmony_ci test_group.append((dt, 1, zt.offset_after)) 3447db96d56Sopenharmony_ci 3457db96d56Sopenharmony_ci dt = zt.anomaly_end + timedelta(seconds=1) 3467db96d56Sopenharmony_ci test_group.append((dt, 0, zt.offset_after)) 3477db96d56Sopenharmony_ci test_group.append((dt, 1, zt.offset_after)) 3487db96d56Sopenharmony_ci 3497db96d56Sopenharmony_ci for grp, test_group in tests.items(): 3507db96d56Sopenharmony_ci test_cases.append(((key, grp), test_group)) 3517db96d56Sopenharmony_ci 3527db96d56Sopenharmony_ci for (key, grp), tests in test_cases: 3537db96d56Sopenharmony_ci with self.subTest(key=key, grp=grp): 3547db96d56Sopenharmony_ci tzi = self.zone_from_key(key) 3557db96d56Sopenharmony_ci 3567db96d56Sopenharmony_ci for dt, fold, offset in tests: 3577db96d56Sopenharmony_ci dt = dt.replace(fold=fold, tzinfo=tzi) 3587db96d56Sopenharmony_ci 3597db96d56Sopenharmony_ci self.assertEqual(dt.tzname(), offset.tzname, dt) 3607db96d56Sopenharmony_ci self.assertEqual(dt.utcoffset(), offset.utcoffset, dt) 3617db96d56Sopenharmony_ci self.assertEqual(dt.dst(), offset.dst, dt) 3627db96d56Sopenharmony_ci 3637db96d56Sopenharmony_ci def test_folds_from_utc(self): 3647db96d56Sopenharmony_ci for key in self.zones(): 3657db96d56Sopenharmony_ci zi = self.zone_from_key(key) 3667db96d56Sopenharmony_ci with self.subTest(key=key): 3677db96d56Sopenharmony_ci for zt in self.load_transition_examples(key): 3687db96d56Sopenharmony_ci if not zt.fold: 3697db96d56Sopenharmony_ci continue 3707db96d56Sopenharmony_ci 3717db96d56Sopenharmony_ci dt_utc = zt.transition_utc 3727db96d56Sopenharmony_ci dt_before_utc = dt_utc - timedelta(seconds=1) 3737db96d56Sopenharmony_ci dt_after_utc = dt_utc + timedelta(seconds=1) 3747db96d56Sopenharmony_ci 3757db96d56Sopenharmony_ci dt_before = dt_before_utc.astimezone(zi) 3767db96d56Sopenharmony_ci self.assertEqual(dt_before.fold, 0, (dt_before, dt_utc)) 3777db96d56Sopenharmony_ci 3787db96d56Sopenharmony_ci dt_after = dt_after_utc.astimezone(zi) 3797db96d56Sopenharmony_ci self.assertEqual(dt_after.fold, 1, (dt_after, dt_utc)) 3807db96d56Sopenharmony_ci 3817db96d56Sopenharmony_ci def test_time_variable_offset(self): 3827db96d56Sopenharmony_ci # self.zones() only ever returns variable-offset zones 3837db96d56Sopenharmony_ci for key in self.zones(): 3847db96d56Sopenharmony_ci zi = self.zone_from_key(key) 3857db96d56Sopenharmony_ci t = time(11, 15, 1, 34471, tzinfo=zi) 3867db96d56Sopenharmony_ci 3877db96d56Sopenharmony_ci with self.subTest(key=key): 3887db96d56Sopenharmony_ci self.assertIs(t.tzname(), None) 3897db96d56Sopenharmony_ci self.assertIs(t.utcoffset(), None) 3907db96d56Sopenharmony_ci self.assertIs(t.dst(), None) 3917db96d56Sopenharmony_ci 3927db96d56Sopenharmony_ci def test_time_fixed_offset(self): 3937db96d56Sopenharmony_ci for key, offset in self.fixed_offset_zones(): 3947db96d56Sopenharmony_ci zi = self.zone_from_key(key) 3957db96d56Sopenharmony_ci 3967db96d56Sopenharmony_ci t = time(11, 15, 1, 34471, tzinfo=zi) 3977db96d56Sopenharmony_ci 3987db96d56Sopenharmony_ci with self.subTest(key=key): 3997db96d56Sopenharmony_ci self.assertEqual(t.tzname(), offset.tzname) 4007db96d56Sopenharmony_ci self.assertEqual(t.utcoffset(), offset.utcoffset) 4017db96d56Sopenharmony_ci self.assertEqual(t.dst(), offset.dst) 4027db96d56Sopenharmony_ci 4037db96d56Sopenharmony_ci 4047db96d56Sopenharmony_ciclass CZoneInfoTest(ZoneInfoTest): 4057db96d56Sopenharmony_ci module = c_zoneinfo 4067db96d56Sopenharmony_ci 4077db96d56Sopenharmony_ci def test_fold_mutate(self): 4087db96d56Sopenharmony_ci """Test that fold isn't mutated when no change is necessary. 4097db96d56Sopenharmony_ci 4107db96d56Sopenharmony_ci The underlying C API is capable of mutating datetime objects, and 4117db96d56Sopenharmony_ci may rely on the fact that addition of a datetime object returns a 4127db96d56Sopenharmony_ci new datetime; this test ensures that the input datetime to fromutc 4137db96d56Sopenharmony_ci is not mutated. 4147db96d56Sopenharmony_ci """ 4157db96d56Sopenharmony_ci 4167db96d56Sopenharmony_ci def to_subclass(dt): 4177db96d56Sopenharmony_ci class SameAddSubclass(type(dt)): 4187db96d56Sopenharmony_ci def __add__(self, other): 4197db96d56Sopenharmony_ci if other == timedelta(0): 4207db96d56Sopenharmony_ci return self 4217db96d56Sopenharmony_ci 4227db96d56Sopenharmony_ci return super().__add__(other) # pragma: nocover 4237db96d56Sopenharmony_ci 4247db96d56Sopenharmony_ci return SameAddSubclass( 4257db96d56Sopenharmony_ci dt.year, 4267db96d56Sopenharmony_ci dt.month, 4277db96d56Sopenharmony_ci dt.day, 4287db96d56Sopenharmony_ci dt.hour, 4297db96d56Sopenharmony_ci dt.minute, 4307db96d56Sopenharmony_ci dt.second, 4317db96d56Sopenharmony_ci dt.microsecond, 4327db96d56Sopenharmony_ci fold=dt.fold, 4337db96d56Sopenharmony_ci tzinfo=dt.tzinfo, 4347db96d56Sopenharmony_ci ) 4357db96d56Sopenharmony_ci 4367db96d56Sopenharmony_ci subclass = [False, True] 4377db96d56Sopenharmony_ci 4387db96d56Sopenharmony_ci key = "Europe/London" 4397db96d56Sopenharmony_ci zi = self.zone_from_key(key) 4407db96d56Sopenharmony_ci for zt in self.load_transition_examples(key): 4417db96d56Sopenharmony_ci if zt.fold and zt.offset_after.utcoffset == ZERO: 4427db96d56Sopenharmony_ci example = zt.transition_utc.replace(tzinfo=zi) 4437db96d56Sopenharmony_ci break 4447db96d56Sopenharmony_ci 4457db96d56Sopenharmony_ci for subclass in [False, True]: 4467db96d56Sopenharmony_ci if subclass: 4477db96d56Sopenharmony_ci dt = to_subclass(example) 4487db96d56Sopenharmony_ci else: 4497db96d56Sopenharmony_ci dt = example 4507db96d56Sopenharmony_ci 4517db96d56Sopenharmony_ci with self.subTest(subclass=subclass): 4527db96d56Sopenharmony_ci dt_fromutc = zi.fromutc(dt) 4537db96d56Sopenharmony_ci 4547db96d56Sopenharmony_ci self.assertEqual(dt_fromutc.fold, 1) 4557db96d56Sopenharmony_ci self.assertEqual(dt.fold, 0) 4567db96d56Sopenharmony_ci 4577db96d56Sopenharmony_ci 4587db96d56Sopenharmony_ciclass ZoneInfoDatetimeSubclassTest(DatetimeSubclassMixin, ZoneInfoTest): 4597db96d56Sopenharmony_ci pass 4607db96d56Sopenharmony_ci 4617db96d56Sopenharmony_ci 4627db96d56Sopenharmony_ciclass CZoneInfoDatetimeSubclassTest(DatetimeSubclassMixin, CZoneInfoTest): 4637db96d56Sopenharmony_ci pass 4647db96d56Sopenharmony_ci 4657db96d56Sopenharmony_ci 4667db96d56Sopenharmony_ciclass ZoneInfoSubclassTest(ZoneInfoTest): 4677db96d56Sopenharmony_ci @classmethod 4687db96d56Sopenharmony_ci def setUpClass(cls): 4697db96d56Sopenharmony_ci super().setUpClass() 4707db96d56Sopenharmony_ci 4717db96d56Sopenharmony_ci class ZISubclass(cls.klass): 4727db96d56Sopenharmony_ci pass 4737db96d56Sopenharmony_ci 4747db96d56Sopenharmony_ci cls.class_name = "ZISubclass" 4757db96d56Sopenharmony_ci cls.parent_klass = cls.klass 4767db96d56Sopenharmony_ci cls.klass = ZISubclass 4777db96d56Sopenharmony_ci 4787db96d56Sopenharmony_ci def test_subclass_own_cache(self): 4797db96d56Sopenharmony_ci base_obj = self.parent_klass("Europe/London") 4807db96d56Sopenharmony_ci sub_obj = self.klass("Europe/London") 4817db96d56Sopenharmony_ci 4827db96d56Sopenharmony_ci self.assertIsNot(base_obj, sub_obj) 4837db96d56Sopenharmony_ci self.assertIsInstance(base_obj, self.parent_klass) 4847db96d56Sopenharmony_ci self.assertIsInstance(sub_obj, self.klass) 4857db96d56Sopenharmony_ci 4867db96d56Sopenharmony_ci 4877db96d56Sopenharmony_ciclass CZoneInfoSubclassTest(ZoneInfoSubclassTest): 4887db96d56Sopenharmony_ci module = c_zoneinfo 4897db96d56Sopenharmony_ci 4907db96d56Sopenharmony_ci 4917db96d56Sopenharmony_ciclass ZoneInfoV1Test(ZoneInfoTest): 4927db96d56Sopenharmony_ci @property 4937db96d56Sopenharmony_ci def zoneinfo_data(self): 4947db96d56Sopenharmony_ci return ZONEINFO_DATA_V1 4957db96d56Sopenharmony_ci 4967db96d56Sopenharmony_ci def load_transition_examples(self, key): 4977db96d56Sopenharmony_ci # We will discard zdump examples outside the range epoch +/- 2**31, 4987db96d56Sopenharmony_ci # because they are not well-supported in Version 1 files. 4997db96d56Sopenharmony_ci epoch = datetime(1970, 1, 1) 5007db96d56Sopenharmony_ci max_offset_32 = timedelta(seconds=2 ** 31) 5017db96d56Sopenharmony_ci min_dt = epoch - max_offset_32 5027db96d56Sopenharmony_ci max_dt = epoch + max_offset_32 5037db96d56Sopenharmony_ci 5047db96d56Sopenharmony_ci for zt in ZoneDumpData.load_transition_examples(key): 5057db96d56Sopenharmony_ci if min_dt <= zt.transition <= max_dt: 5067db96d56Sopenharmony_ci yield zt 5077db96d56Sopenharmony_ci 5087db96d56Sopenharmony_ci 5097db96d56Sopenharmony_ciclass CZoneInfoV1Test(ZoneInfoV1Test): 5107db96d56Sopenharmony_ci module = c_zoneinfo 5117db96d56Sopenharmony_ci 5127db96d56Sopenharmony_ci 5137db96d56Sopenharmony_ci@unittest.skipIf( 5147db96d56Sopenharmony_ci not HAS_TZDATA_PKG, "Skipping tzdata-specific tests: tzdata not installed" 5157db96d56Sopenharmony_ci) 5167db96d56Sopenharmony_ciclass TZDataTests(ZoneInfoTest): 5177db96d56Sopenharmony_ci """ 5187db96d56Sopenharmony_ci Runs all the ZoneInfoTest tests, but against the tzdata package 5197db96d56Sopenharmony_ci 5207db96d56Sopenharmony_ci NOTE: The ZoneDumpData has frozen test data, but tzdata will update, so 5217db96d56Sopenharmony_ci some of the tests (particularly those related to the far future) may break 5227db96d56Sopenharmony_ci in the event that the time zone policies in the relevant time zones change. 5237db96d56Sopenharmony_ci """ 5247db96d56Sopenharmony_ci 5257db96d56Sopenharmony_ci @property 5267db96d56Sopenharmony_ci def tzpath(self): 5277db96d56Sopenharmony_ci return [] 5287db96d56Sopenharmony_ci 5297db96d56Sopenharmony_ci @property 5307db96d56Sopenharmony_ci def block_tzdata(self): 5317db96d56Sopenharmony_ci return False 5327db96d56Sopenharmony_ci 5337db96d56Sopenharmony_ci def zone_from_key(self, key): 5347db96d56Sopenharmony_ci return self.klass(key=key) 5357db96d56Sopenharmony_ci 5367db96d56Sopenharmony_ci 5377db96d56Sopenharmony_ci@unittest.skipIf( 5387db96d56Sopenharmony_ci not HAS_TZDATA_PKG, "Skipping tzdata-specific tests: tzdata not installed" 5397db96d56Sopenharmony_ci) 5407db96d56Sopenharmony_ciclass CTZDataTests(TZDataTests): 5417db96d56Sopenharmony_ci module = c_zoneinfo 5427db96d56Sopenharmony_ci 5437db96d56Sopenharmony_ci 5447db96d56Sopenharmony_ciclass WeirdZoneTest(ZoneInfoTestBase): 5457db96d56Sopenharmony_ci module = py_zoneinfo 5467db96d56Sopenharmony_ci 5477db96d56Sopenharmony_ci def test_one_transition(self): 5487db96d56Sopenharmony_ci LMT = ZoneOffset("LMT", -timedelta(hours=6, minutes=31, seconds=2)) 5497db96d56Sopenharmony_ci STD = ZoneOffset("STD", -timedelta(hours=6)) 5507db96d56Sopenharmony_ci 5517db96d56Sopenharmony_ci transitions = [ 5527db96d56Sopenharmony_ci ZoneTransition(datetime(1883, 6, 9, 14), LMT, STD), 5537db96d56Sopenharmony_ci ] 5547db96d56Sopenharmony_ci 5557db96d56Sopenharmony_ci after = "STD6" 5567db96d56Sopenharmony_ci 5577db96d56Sopenharmony_ci zf = self.construct_zone(transitions, after) 5587db96d56Sopenharmony_ci zi = self.klass.from_file(zf) 5597db96d56Sopenharmony_ci 5607db96d56Sopenharmony_ci dt0 = datetime(1883, 6, 9, 1, tzinfo=zi) 5617db96d56Sopenharmony_ci dt1 = datetime(1883, 6, 10, 1, tzinfo=zi) 5627db96d56Sopenharmony_ci 5637db96d56Sopenharmony_ci for dt, offset in [(dt0, LMT), (dt1, STD)]: 5647db96d56Sopenharmony_ci with self.subTest(name="local", dt=dt): 5657db96d56Sopenharmony_ci self.assertEqual(dt.tzname(), offset.tzname) 5667db96d56Sopenharmony_ci self.assertEqual(dt.utcoffset(), offset.utcoffset) 5677db96d56Sopenharmony_ci self.assertEqual(dt.dst(), offset.dst) 5687db96d56Sopenharmony_ci 5697db96d56Sopenharmony_ci dts = [ 5707db96d56Sopenharmony_ci ( 5717db96d56Sopenharmony_ci datetime(1883, 6, 9, 1, tzinfo=zi), 5727db96d56Sopenharmony_ci datetime(1883, 6, 9, 7, 31, 2, tzinfo=timezone.utc), 5737db96d56Sopenharmony_ci ), 5747db96d56Sopenharmony_ci ( 5757db96d56Sopenharmony_ci datetime(2010, 4, 1, 12, tzinfo=zi), 5767db96d56Sopenharmony_ci datetime(2010, 4, 1, 18, tzinfo=timezone.utc), 5777db96d56Sopenharmony_ci ), 5787db96d56Sopenharmony_ci ] 5797db96d56Sopenharmony_ci 5807db96d56Sopenharmony_ci for dt_local, dt_utc in dts: 5817db96d56Sopenharmony_ci with self.subTest(name="fromutc", dt=dt_local): 5827db96d56Sopenharmony_ci dt_actual = dt_utc.astimezone(zi) 5837db96d56Sopenharmony_ci self.assertEqual(dt_actual, dt_local) 5847db96d56Sopenharmony_ci 5857db96d56Sopenharmony_ci dt_utc_actual = dt_local.astimezone(timezone.utc) 5867db96d56Sopenharmony_ci self.assertEqual(dt_utc_actual, dt_utc) 5877db96d56Sopenharmony_ci 5887db96d56Sopenharmony_ci def test_one_zone_dst(self): 5897db96d56Sopenharmony_ci DST = ZoneOffset("DST", ONE_H, ONE_H) 5907db96d56Sopenharmony_ci transitions = [ 5917db96d56Sopenharmony_ci ZoneTransition(datetime(1970, 1, 1), DST, DST), 5927db96d56Sopenharmony_ci ] 5937db96d56Sopenharmony_ci 5947db96d56Sopenharmony_ci after = "STD0DST-1,0/0,J365/25" 5957db96d56Sopenharmony_ci 5967db96d56Sopenharmony_ci zf = self.construct_zone(transitions, after) 5977db96d56Sopenharmony_ci zi = self.klass.from_file(zf) 5987db96d56Sopenharmony_ci 5997db96d56Sopenharmony_ci dts = [ 6007db96d56Sopenharmony_ci datetime(1900, 3, 1), 6017db96d56Sopenharmony_ci datetime(1965, 9, 12), 6027db96d56Sopenharmony_ci datetime(1970, 1, 1), 6037db96d56Sopenharmony_ci datetime(2010, 11, 3), 6047db96d56Sopenharmony_ci datetime(2040, 1, 1), 6057db96d56Sopenharmony_ci ] 6067db96d56Sopenharmony_ci 6077db96d56Sopenharmony_ci for dt in dts: 6087db96d56Sopenharmony_ci dt = dt.replace(tzinfo=zi) 6097db96d56Sopenharmony_ci with self.subTest(dt=dt): 6107db96d56Sopenharmony_ci self.assertEqual(dt.tzname(), DST.tzname) 6117db96d56Sopenharmony_ci self.assertEqual(dt.utcoffset(), DST.utcoffset) 6127db96d56Sopenharmony_ci self.assertEqual(dt.dst(), DST.dst) 6137db96d56Sopenharmony_ci 6147db96d56Sopenharmony_ci def test_no_tz_str(self): 6157db96d56Sopenharmony_ci STD = ZoneOffset("STD", ONE_H, ZERO) 6167db96d56Sopenharmony_ci DST = ZoneOffset("DST", 2 * ONE_H, ONE_H) 6177db96d56Sopenharmony_ci 6187db96d56Sopenharmony_ci transitions = [] 6197db96d56Sopenharmony_ci for year in range(1996, 2000): 6207db96d56Sopenharmony_ci transitions.append( 6217db96d56Sopenharmony_ci ZoneTransition(datetime(year, 3, 1, 2), STD, DST) 6227db96d56Sopenharmony_ci ) 6237db96d56Sopenharmony_ci transitions.append( 6247db96d56Sopenharmony_ci ZoneTransition(datetime(year, 11, 1, 2), DST, STD) 6257db96d56Sopenharmony_ci ) 6267db96d56Sopenharmony_ci 6277db96d56Sopenharmony_ci after = "" 6287db96d56Sopenharmony_ci 6297db96d56Sopenharmony_ci zf = self.construct_zone(transitions, after) 6307db96d56Sopenharmony_ci 6317db96d56Sopenharmony_ci # According to RFC 8536, local times after the last transition time 6327db96d56Sopenharmony_ci # with an empty TZ string are unspecified. We will go with "hold the 6337db96d56Sopenharmony_ci # last transition", but the most we should promise is "doesn't crash." 6347db96d56Sopenharmony_ci zi = self.klass.from_file(zf) 6357db96d56Sopenharmony_ci 6367db96d56Sopenharmony_ci cases = [ 6377db96d56Sopenharmony_ci (datetime(1995, 1, 1), STD), 6387db96d56Sopenharmony_ci (datetime(1996, 4, 1), DST), 6397db96d56Sopenharmony_ci (datetime(1996, 11, 2), STD), 6407db96d56Sopenharmony_ci (datetime(2001, 1, 1), STD), 6417db96d56Sopenharmony_ci ] 6427db96d56Sopenharmony_ci 6437db96d56Sopenharmony_ci for dt, offset in cases: 6447db96d56Sopenharmony_ci dt = dt.replace(tzinfo=zi) 6457db96d56Sopenharmony_ci with self.subTest(dt=dt): 6467db96d56Sopenharmony_ci self.assertEqual(dt.tzname(), offset.tzname) 6477db96d56Sopenharmony_ci self.assertEqual(dt.utcoffset(), offset.utcoffset) 6487db96d56Sopenharmony_ci self.assertEqual(dt.dst(), offset.dst) 6497db96d56Sopenharmony_ci 6507db96d56Sopenharmony_ci # Test that offsets return None when using a datetime.time 6517db96d56Sopenharmony_ci t = time(0, tzinfo=zi) 6527db96d56Sopenharmony_ci with self.subTest("Testing datetime.time"): 6537db96d56Sopenharmony_ci self.assertIs(t.tzname(), None) 6547db96d56Sopenharmony_ci self.assertIs(t.utcoffset(), None) 6557db96d56Sopenharmony_ci self.assertIs(t.dst(), None) 6567db96d56Sopenharmony_ci 6577db96d56Sopenharmony_ci def test_tz_before_only(self): 6587db96d56Sopenharmony_ci # From RFC 8536 Section 3.2: 6597db96d56Sopenharmony_ci # 6607db96d56Sopenharmony_ci # If there are no transitions, local time for all timestamps is 6617db96d56Sopenharmony_ci # specified by the TZ string in the footer if present and nonempty; 6627db96d56Sopenharmony_ci # otherwise, it is specified by time type 0. 6637db96d56Sopenharmony_ci 6647db96d56Sopenharmony_ci offsets = [ 6657db96d56Sopenharmony_ci ZoneOffset("STD", ZERO, ZERO), 6667db96d56Sopenharmony_ci ZoneOffset("DST", ONE_H, ONE_H), 6677db96d56Sopenharmony_ci ] 6687db96d56Sopenharmony_ci 6697db96d56Sopenharmony_ci for offset in offsets: 6707db96d56Sopenharmony_ci # Phantom transition to set time type 0. 6717db96d56Sopenharmony_ci transitions = [ 6727db96d56Sopenharmony_ci ZoneTransition(None, offset, offset), 6737db96d56Sopenharmony_ci ] 6747db96d56Sopenharmony_ci 6757db96d56Sopenharmony_ci after = "" 6767db96d56Sopenharmony_ci 6777db96d56Sopenharmony_ci zf = self.construct_zone(transitions, after) 6787db96d56Sopenharmony_ci zi = self.klass.from_file(zf) 6797db96d56Sopenharmony_ci 6807db96d56Sopenharmony_ci dts = [ 6817db96d56Sopenharmony_ci datetime(1900, 1, 1), 6827db96d56Sopenharmony_ci datetime(1970, 1, 1), 6837db96d56Sopenharmony_ci datetime(2000, 1, 1), 6847db96d56Sopenharmony_ci ] 6857db96d56Sopenharmony_ci 6867db96d56Sopenharmony_ci for dt in dts: 6877db96d56Sopenharmony_ci dt = dt.replace(tzinfo=zi) 6887db96d56Sopenharmony_ci with self.subTest(offset=offset, dt=dt): 6897db96d56Sopenharmony_ci self.assertEqual(dt.tzname(), offset.tzname) 6907db96d56Sopenharmony_ci self.assertEqual(dt.utcoffset(), offset.utcoffset) 6917db96d56Sopenharmony_ci self.assertEqual(dt.dst(), offset.dst) 6927db96d56Sopenharmony_ci 6937db96d56Sopenharmony_ci def test_empty_zone(self): 6947db96d56Sopenharmony_ci zf = self.construct_zone([], "") 6957db96d56Sopenharmony_ci 6967db96d56Sopenharmony_ci with self.assertRaises(ValueError): 6977db96d56Sopenharmony_ci self.klass.from_file(zf) 6987db96d56Sopenharmony_ci 6997db96d56Sopenharmony_ci def test_zone_very_large_timestamp(self): 7007db96d56Sopenharmony_ci """Test when a transition is in the far past or future. 7017db96d56Sopenharmony_ci 7027db96d56Sopenharmony_ci Particularly, this is a concern if something: 7037db96d56Sopenharmony_ci 7047db96d56Sopenharmony_ci 1. Attempts to call ``datetime.timestamp`` for a datetime outside 7057db96d56Sopenharmony_ci of ``[datetime.min, datetime.max]``. 7067db96d56Sopenharmony_ci 2. Attempts to construct a timedelta outside of 7077db96d56Sopenharmony_ci ``[timedelta.min, timedelta.max]``. 7087db96d56Sopenharmony_ci 7097db96d56Sopenharmony_ci This actually occurs "in the wild", as some time zones on Ubuntu (at 7107db96d56Sopenharmony_ci least as of 2020) have an initial transition added at ``-2**58``. 7117db96d56Sopenharmony_ci """ 7127db96d56Sopenharmony_ci 7137db96d56Sopenharmony_ci LMT = ZoneOffset("LMT", timedelta(seconds=-968)) 7147db96d56Sopenharmony_ci GMT = ZoneOffset("GMT", ZERO) 7157db96d56Sopenharmony_ci 7167db96d56Sopenharmony_ci transitions = [ 7177db96d56Sopenharmony_ci (-(1 << 62), LMT, LMT), 7187db96d56Sopenharmony_ci ZoneTransition(datetime(1912, 1, 1), LMT, GMT), 7197db96d56Sopenharmony_ci ((1 << 62), GMT, GMT), 7207db96d56Sopenharmony_ci ] 7217db96d56Sopenharmony_ci 7227db96d56Sopenharmony_ci after = "GMT0" 7237db96d56Sopenharmony_ci 7247db96d56Sopenharmony_ci zf = self.construct_zone(transitions, after) 7257db96d56Sopenharmony_ci zi = self.klass.from_file(zf, key="Africa/Abidjan") 7267db96d56Sopenharmony_ci 7277db96d56Sopenharmony_ci offset_cases = [ 7287db96d56Sopenharmony_ci (datetime.min, LMT), 7297db96d56Sopenharmony_ci (datetime.max, GMT), 7307db96d56Sopenharmony_ci (datetime(1911, 12, 31), LMT), 7317db96d56Sopenharmony_ci (datetime(1912, 1, 2), GMT), 7327db96d56Sopenharmony_ci ] 7337db96d56Sopenharmony_ci 7347db96d56Sopenharmony_ci for dt_naive, offset in offset_cases: 7357db96d56Sopenharmony_ci dt = dt_naive.replace(tzinfo=zi) 7367db96d56Sopenharmony_ci with self.subTest(name="offset", dt=dt, offset=offset): 7377db96d56Sopenharmony_ci self.assertEqual(dt.tzname(), offset.tzname) 7387db96d56Sopenharmony_ci self.assertEqual(dt.utcoffset(), offset.utcoffset) 7397db96d56Sopenharmony_ci self.assertEqual(dt.dst(), offset.dst) 7407db96d56Sopenharmony_ci 7417db96d56Sopenharmony_ci utc_cases = [ 7427db96d56Sopenharmony_ci (datetime.min, datetime.min + timedelta(seconds=968)), 7437db96d56Sopenharmony_ci (datetime(1898, 12, 31, 23, 43, 52), datetime(1899, 1, 1)), 7447db96d56Sopenharmony_ci ( 7457db96d56Sopenharmony_ci datetime(1911, 12, 31, 23, 59, 59, 999999), 7467db96d56Sopenharmony_ci datetime(1912, 1, 1, 0, 16, 7, 999999), 7477db96d56Sopenharmony_ci ), 7487db96d56Sopenharmony_ci (datetime(1912, 1, 1, 0, 16, 8), datetime(1912, 1, 1, 0, 16, 8)), 7497db96d56Sopenharmony_ci (datetime(1970, 1, 1), datetime(1970, 1, 1)), 7507db96d56Sopenharmony_ci (datetime.max, datetime.max), 7517db96d56Sopenharmony_ci ] 7527db96d56Sopenharmony_ci 7537db96d56Sopenharmony_ci for naive_dt, naive_dt_utc in utc_cases: 7547db96d56Sopenharmony_ci dt = naive_dt.replace(tzinfo=zi) 7557db96d56Sopenharmony_ci dt_utc = naive_dt_utc.replace(tzinfo=timezone.utc) 7567db96d56Sopenharmony_ci 7577db96d56Sopenharmony_ci self.assertEqual(dt_utc.astimezone(zi), dt) 7587db96d56Sopenharmony_ci self.assertEqual(dt, dt_utc) 7597db96d56Sopenharmony_ci 7607db96d56Sopenharmony_ci def test_fixed_offset_phantom_transition(self): 7617db96d56Sopenharmony_ci UTC = ZoneOffset("UTC", ZERO, ZERO) 7627db96d56Sopenharmony_ci 7637db96d56Sopenharmony_ci transitions = [ZoneTransition(datetime(1970, 1, 1), UTC, UTC)] 7647db96d56Sopenharmony_ci 7657db96d56Sopenharmony_ci after = "UTC0" 7667db96d56Sopenharmony_ci zf = self.construct_zone(transitions, after) 7677db96d56Sopenharmony_ci zi = self.klass.from_file(zf, key="UTC") 7687db96d56Sopenharmony_ci 7697db96d56Sopenharmony_ci dt = datetime(2020, 1, 1, tzinfo=zi) 7707db96d56Sopenharmony_ci with self.subTest("datetime.datetime"): 7717db96d56Sopenharmony_ci self.assertEqual(dt.tzname(), UTC.tzname) 7727db96d56Sopenharmony_ci self.assertEqual(dt.utcoffset(), UTC.utcoffset) 7737db96d56Sopenharmony_ci self.assertEqual(dt.dst(), UTC.dst) 7747db96d56Sopenharmony_ci 7757db96d56Sopenharmony_ci t = time(0, tzinfo=zi) 7767db96d56Sopenharmony_ci with self.subTest("datetime.time"): 7777db96d56Sopenharmony_ci self.assertEqual(t.tzname(), UTC.tzname) 7787db96d56Sopenharmony_ci self.assertEqual(t.utcoffset(), UTC.utcoffset) 7797db96d56Sopenharmony_ci self.assertEqual(t.dst(), UTC.dst) 7807db96d56Sopenharmony_ci 7817db96d56Sopenharmony_ci def construct_zone(self, transitions, after=None, version=3): 7827db96d56Sopenharmony_ci # These are not used for anything, so we're not going to include 7837db96d56Sopenharmony_ci # them for now. 7847db96d56Sopenharmony_ci isutc = [] 7857db96d56Sopenharmony_ci isstd = [] 7867db96d56Sopenharmony_ci leap_seconds = [] 7877db96d56Sopenharmony_ci 7887db96d56Sopenharmony_ci offset_lists = [[], []] 7897db96d56Sopenharmony_ci trans_times_lists = [[], []] 7907db96d56Sopenharmony_ci trans_idx_lists = [[], []] 7917db96d56Sopenharmony_ci 7927db96d56Sopenharmony_ci v1_range = (-(2 ** 31), 2 ** 31) 7937db96d56Sopenharmony_ci v2_range = (-(2 ** 63), 2 ** 63) 7947db96d56Sopenharmony_ci ranges = [v1_range, v2_range] 7957db96d56Sopenharmony_ci 7967db96d56Sopenharmony_ci def zt_as_tuple(zt): 7977db96d56Sopenharmony_ci # zt may be a tuple (timestamp, offset_before, offset_after) or 7987db96d56Sopenharmony_ci # a ZoneTransition object — this is to allow the timestamp to be 7997db96d56Sopenharmony_ci # values that are outside the valid range for datetimes but still 8007db96d56Sopenharmony_ci # valid 64-bit timestamps. 8017db96d56Sopenharmony_ci if isinstance(zt, tuple): 8027db96d56Sopenharmony_ci return zt 8037db96d56Sopenharmony_ci 8047db96d56Sopenharmony_ci if zt.transition: 8057db96d56Sopenharmony_ci trans_time = int(zt.transition_utc.timestamp()) 8067db96d56Sopenharmony_ci else: 8077db96d56Sopenharmony_ci trans_time = None 8087db96d56Sopenharmony_ci 8097db96d56Sopenharmony_ci return (trans_time, zt.offset_before, zt.offset_after) 8107db96d56Sopenharmony_ci 8117db96d56Sopenharmony_ci transitions = sorted(map(zt_as_tuple, transitions), key=lambda x: x[0]) 8127db96d56Sopenharmony_ci 8137db96d56Sopenharmony_ci for zt in transitions: 8147db96d56Sopenharmony_ci trans_time, offset_before, offset_after = zt 8157db96d56Sopenharmony_ci 8167db96d56Sopenharmony_ci for v, (dt_min, dt_max) in enumerate(ranges): 8177db96d56Sopenharmony_ci offsets = offset_lists[v] 8187db96d56Sopenharmony_ci trans_times = trans_times_lists[v] 8197db96d56Sopenharmony_ci trans_idx = trans_idx_lists[v] 8207db96d56Sopenharmony_ci 8217db96d56Sopenharmony_ci if trans_time is not None and not ( 8227db96d56Sopenharmony_ci dt_min <= trans_time <= dt_max 8237db96d56Sopenharmony_ci ): 8247db96d56Sopenharmony_ci continue 8257db96d56Sopenharmony_ci 8267db96d56Sopenharmony_ci if offset_before not in offsets: 8277db96d56Sopenharmony_ci offsets.append(offset_before) 8287db96d56Sopenharmony_ci 8297db96d56Sopenharmony_ci if offset_after not in offsets: 8307db96d56Sopenharmony_ci offsets.append(offset_after) 8317db96d56Sopenharmony_ci 8327db96d56Sopenharmony_ci if trans_time is not None: 8337db96d56Sopenharmony_ci trans_times.append(trans_time) 8347db96d56Sopenharmony_ci trans_idx.append(offsets.index(offset_after)) 8357db96d56Sopenharmony_ci 8367db96d56Sopenharmony_ci isutcnt = len(isutc) 8377db96d56Sopenharmony_ci isstdcnt = len(isstd) 8387db96d56Sopenharmony_ci leapcnt = len(leap_seconds) 8397db96d56Sopenharmony_ci 8407db96d56Sopenharmony_ci zonefile = io.BytesIO() 8417db96d56Sopenharmony_ci 8427db96d56Sopenharmony_ci time_types = ("l", "q") 8437db96d56Sopenharmony_ci for v in range(min((version, 2))): 8447db96d56Sopenharmony_ci offsets = offset_lists[v] 8457db96d56Sopenharmony_ci trans_times = trans_times_lists[v] 8467db96d56Sopenharmony_ci trans_idx = trans_idx_lists[v] 8477db96d56Sopenharmony_ci time_type = time_types[v] 8487db96d56Sopenharmony_ci 8497db96d56Sopenharmony_ci # Translate the offsets into something closer to the C values 8507db96d56Sopenharmony_ci abbrstr = bytearray() 8517db96d56Sopenharmony_ci ttinfos = [] 8527db96d56Sopenharmony_ci 8537db96d56Sopenharmony_ci for offset in offsets: 8547db96d56Sopenharmony_ci utcoff = int(offset.utcoffset.total_seconds()) 8557db96d56Sopenharmony_ci isdst = bool(offset.dst) 8567db96d56Sopenharmony_ci abbrind = len(abbrstr) 8577db96d56Sopenharmony_ci 8587db96d56Sopenharmony_ci ttinfos.append((utcoff, isdst, abbrind)) 8597db96d56Sopenharmony_ci abbrstr += offset.tzname.encode("ascii") + b"\x00" 8607db96d56Sopenharmony_ci abbrstr = bytes(abbrstr) 8617db96d56Sopenharmony_ci 8627db96d56Sopenharmony_ci typecnt = len(offsets) 8637db96d56Sopenharmony_ci timecnt = len(trans_times) 8647db96d56Sopenharmony_ci charcnt = len(abbrstr) 8657db96d56Sopenharmony_ci 8667db96d56Sopenharmony_ci # Write the header 8677db96d56Sopenharmony_ci zonefile.write(b"TZif") 8687db96d56Sopenharmony_ci zonefile.write(b"%d" % version) 8697db96d56Sopenharmony_ci zonefile.write(b" " * 15) 8707db96d56Sopenharmony_ci zonefile.write( 8717db96d56Sopenharmony_ci struct.pack( 8727db96d56Sopenharmony_ci ">6l", isutcnt, isstdcnt, leapcnt, timecnt, typecnt, charcnt 8737db96d56Sopenharmony_ci ) 8747db96d56Sopenharmony_ci ) 8757db96d56Sopenharmony_ci 8767db96d56Sopenharmony_ci # Now the transition data 8777db96d56Sopenharmony_ci zonefile.write(struct.pack(f">{timecnt}{time_type}", *trans_times)) 8787db96d56Sopenharmony_ci zonefile.write(struct.pack(f">{timecnt}B", *trans_idx)) 8797db96d56Sopenharmony_ci 8807db96d56Sopenharmony_ci for ttinfo in ttinfos: 8817db96d56Sopenharmony_ci zonefile.write(struct.pack(">lbb", *ttinfo)) 8827db96d56Sopenharmony_ci 8837db96d56Sopenharmony_ci zonefile.write(bytes(abbrstr)) 8847db96d56Sopenharmony_ci 8857db96d56Sopenharmony_ci # Now the metadata and leap seconds 8867db96d56Sopenharmony_ci zonefile.write(struct.pack(f"{isutcnt}b", *isutc)) 8877db96d56Sopenharmony_ci zonefile.write(struct.pack(f"{isstdcnt}b", *isstd)) 8887db96d56Sopenharmony_ci zonefile.write(struct.pack(f">{leapcnt}l", *leap_seconds)) 8897db96d56Sopenharmony_ci 8907db96d56Sopenharmony_ci # Finally we write the TZ string if we're writing a Version 2+ file 8917db96d56Sopenharmony_ci if v > 0: 8927db96d56Sopenharmony_ci zonefile.write(b"\x0A") 8937db96d56Sopenharmony_ci zonefile.write(after.encode("ascii")) 8947db96d56Sopenharmony_ci zonefile.write(b"\x0A") 8957db96d56Sopenharmony_ci 8967db96d56Sopenharmony_ci zonefile.seek(0) 8977db96d56Sopenharmony_ci return zonefile 8987db96d56Sopenharmony_ci 8997db96d56Sopenharmony_ci 9007db96d56Sopenharmony_ciclass CWeirdZoneTest(WeirdZoneTest): 9017db96d56Sopenharmony_ci module = c_zoneinfo 9027db96d56Sopenharmony_ci 9037db96d56Sopenharmony_ci 9047db96d56Sopenharmony_ciclass TZStrTest(ZoneInfoTestBase): 9057db96d56Sopenharmony_ci module = py_zoneinfo 9067db96d56Sopenharmony_ci 9077db96d56Sopenharmony_ci NORMAL = 0 9087db96d56Sopenharmony_ci FOLD = 1 9097db96d56Sopenharmony_ci GAP = 2 9107db96d56Sopenharmony_ci 9117db96d56Sopenharmony_ci @classmethod 9127db96d56Sopenharmony_ci def setUpClass(cls): 9137db96d56Sopenharmony_ci super().setUpClass() 9147db96d56Sopenharmony_ci 9157db96d56Sopenharmony_ci cls._populate_test_cases() 9167db96d56Sopenharmony_ci cls.populate_tzstr_header() 9177db96d56Sopenharmony_ci 9187db96d56Sopenharmony_ci @classmethod 9197db96d56Sopenharmony_ci def populate_tzstr_header(cls): 9207db96d56Sopenharmony_ci out = bytearray() 9217db96d56Sopenharmony_ci # The TZif format always starts with a Version 1 file followed by 9227db96d56Sopenharmony_ci # the Version 2+ file. In this case, we have no transitions, just 9237db96d56Sopenharmony_ci # the tzstr in the footer, so up to the footer, the files are 9247db96d56Sopenharmony_ci # identical and we can just write the same file twice in a row. 9257db96d56Sopenharmony_ci for _ in range(2): 9267db96d56Sopenharmony_ci out += b"TZif" # Magic value 9277db96d56Sopenharmony_ci out += b"3" # Version 9287db96d56Sopenharmony_ci out += b" " * 15 # Reserved 9297db96d56Sopenharmony_ci 9307db96d56Sopenharmony_ci # We will not write any of the manual transition parts 9317db96d56Sopenharmony_ci out += struct.pack(">6l", 0, 0, 0, 0, 0, 0) 9327db96d56Sopenharmony_ci 9337db96d56Sopenharmony_ci cls._tzif_header = bytes(out) 9347db96d56Sopenharmony_ci 9357db96d56Sopenharmony_ci def zone_from_tzstr(self, tzstr): 9367db96d56Sopenharmony_ci """Creates a zoneinfo file following a POSIX rule.""" 9377db96d56Sopenharmony_ci zonefile = io.BytesIO(self._tzif_header) 9387db96d56Sopenharmony_ci zonefile.seek(0, 2) 9397db96d56Sopenharmony_ci 9407db96d56Sopenharmony_ci # Write the footer 9417db96d56Sopenharmony_ci zonefile.write(b"\x0A") 9427db96d56Sopenharmony_ci zonefile.write(tzstr.encode("ascii")) 9437db96d56Sopenharmony_ci zonefile.write(b"\x0A") 9447db96d56Sopenharmony_ci 9457db96d56Sopenharmony_ci zonefile.seek(0) 9467db96d56Sopenharmony_ci 9477db96d56Sopenharmony_ci return self.klass.from_file(zonefile, key=tzstr) 9487db96d56Sopenharmony_ci 9497db96d56Sopenharmony_ci def test_tzstr_localized(self): 9507db96d56Sopenharmony_ci for tzstr, cases in self.test_cases.items(): 9517db96d56Sopenharmony_ci with self.subTest(tzstr=tzstr): 9527db96d56Sopenharmony_ci zi = self.zone_from_tzstr(tzstr) 9537db96d56Sopenharmony_ci 9547db96d56Sopenharmony_ci for dt_naive, offset, _ in cases: 9557db96d56Sopenharmony_ci dt = dt_naive.replace(tzinfo=zi) 9567db96d56Sopenharmony_ci 9577db96d56Sopenharmony_ci with self.subTest(tzstr=tzstr, dt=dt, offset=offset): 9587db96d56Sopenharmony_ci self.assertEqual(dt.tzname(), offset.tzname) 9597db96d56Sopenharmony_ci self.assertEqual(dt.utcoffset(), offset.utcoffset) 9607db96d56Sopenharmony_ci self.assertEqual(dt.dst(), offset.dst) 9617db96d56Sopenharmony_ci 9627db96d56Sopenharmony_ci def test_tzstr_from_utc(self): 9637db96d56Sopenharmony_ci for tzstr, cases in self.test_cases.items(): 9647db96d56Sopenharmony_ci with self.subTest(tzstr=tzstr): 9657db96d56Sopenharmony_ci zi = self.zone_from_tzstr(tzstr) 9667db96d56Sopenharmony_ci 9677db96d56Sopenharmony_ci for dt_naive, offset, dt_type in cases: 9687db96d56Sopenharmony_ci if dt_type == self.GAP: 9697db96d56Sopenharmony_ci continue # Cannot create a gap from UTC 9707db96d56Sopenharmony_ci 9717db96d56Sopenharmony_ci dt_utc = (dt_naive - offset.utcoffset).replace( 9727db96d56Sopenharmony_ci tzinfo=timezone.utc 9737db96d56Sopenharmony_ci ) 9747db96d56Sopenharmony_ci 9757db96d56Sopenharmony_ci # Check that we can go UTC -> Our zone 9767db96d56Sopenharmony_ci dt_act = dt_utc.astimezone(zi) 9777db96d56Sopenharmony_ci dt_exp = dt_naive.replace(tzinfo=zi) 9787db96d56Sopenharmony_ci 9797db96d56Sopenharmony_ci self.assertEqual(dt_act, dt_exp) 9807db96d56Sopenharmony_ci 9817db96d56Sopenharmony_ci if dt_type == self.FOLD: 9827db96d56Sopenharmony_ci self.assertEqual(dt_act.fold, dt_naive.fold, dt_naive) 9837db96d56Sopenharmony_ci else: 9847db96d56Sopenharmony_ci self.assertEqual(dt_act.fold, 0) 9857db96d56Sopenharmony_ci 9867db96d56Sopenharmony_ci # Now check that we can go our zone -> UTC 9877db96d56Sopenharmony_ci dt_act = dt_exp.astimezone(timezone.utc) 9887db96d56Sopenharmony_ci 9897db96d56Sopenharmony_ci self.assertEqual(dt_act, dt_utc) 9907db96d56Sopenharmony_ci 9917db96d56Sopenharmony_ci def test_invalid_tzstr(self): 9927db96d56Sopenharmony_ci invalid_tzstrs = [ 9937db96d56Sopenharmony_ci "PST8PDT", # DST but no transition specified 9947db96d56Sopenharmony_ci "+11", # Unquoted alphanumeric 9957db96d56Sopenharmony_ci "GMT,M3.2.0/2,M11.1.0/3", # Transition rule but no DST 9967db96d56Sopenharmony_ci "GMT0+11,M3.2.0/2,M11.1.0/3", # Unquoted alphanumeric in DST 9977db96d56Sopenharmony_ci "PST8PDT,M3.2.0/2", # Only one transition rule 9987db96d56Sopenharmony_ci # Invalid offsets 9997db96d56Sopenharmony_ci "STD+25", 10007db96d56Sopenharmony_ci "STD-25", 10017db96d56Sopenharmony_ci "STD+374", 10027db96d56Sopenharmony_ci "STD+374DST,M3.2.0/2,M11.1.0/3", 10037db96d56Sopenharmony_ci "STD+23DST+25,M3.2.0/2,M11.1.0/3", 10047db96d56Sopenharmony_ci "STD-23DST-25,M3.2.0/2,M11.1.0/3", 10057db96d56Sopenharmony_ci # Completely invalid dates 10067db96d56Sopenharmony_ci "AAA4BBB,M1443339,M11.1.0/3", 10077db96d56Sopenharmony_ci "AAA4BBB,M3.2.0/2,0349309483959c", 10087db96d56Sopenharmony_ci # Invalid months 10097db96d56Sopenharmony_ci "AAA4BBB,M13.1.1/2,M1.1.1/2", 10107db96d56Sopenharmony_ci "AAA4BBB,M1.1.1/2,M13.1.1/2", 10117db96d56Sopenharmony_ci "AAA4BBB,M0.1.1/2,M1.1.1/2", 10127db96d56Sopenharmony_ci "AAA4BBB,M1.1.1/2,M0.1.1/2", 10137db96d56Sopenharmony_ci # Invalid weeks 10147db96d56Sopenharmony_ci "AAA4BBB,M1.6.1/2,M1.1.1/2", 10157db96d56Sopenharmony_ci "AAA4BBB,M1.1.1/2,M1.6.1/2", 10167db96d56Sopenharmony_ci # Invalid weekday 10177db96d56Sopenharmony_ci "AAA4BBB,M1.1.7/2,M2.1.1/2", 10187db96d56Sopenharmony_ci "AAA4BBB,M1.1.1/2,M2.1.7/2", 10197db96d56Sopenharmony_ci # Invalid numeric offset 10207db96d56Sopenharmony_ci "AAA4BBB,-1/2,20/2", 10217db96d56Sopenharmony_ci "AAA4BBB,1/2,-1/2", 10227db96d56Sopenharmony_ci "AAA4BBB,367,20/2", 10237db96d56Sopenharmony_ci "AAA4BBB,1/2,367/2", 10247db96d56Sopenharmony_ci # Invalid julian offset 10257db96d56Sopenharmony_ci "AAA4BBB,J0/2,J20/2", 10267db96d56Sopenharmony_ci "AAA4BBB,J20/2,J366/2", 10277db96d56Sopenharmony_ci ] 10287db96d56Sopenharmony_ci 10297db96d56Sopenharmony_ci for invalid_tzstr in invalid_tzstrs: 10307db96d56Sopenharmony_ci with self.subTest(tzstr=invalid_tzstr): 10317db96d56Sopenharmony_ci # Not necessarily a guaranteed property, but we should show 10327db96d56Sopenharmony_ci # the problematic TZ string if that's the cause of failure. 10337db96d56Sopenharmony_ci tzstr_regex = re.escape(invalid_tzstr) 10347db96d56Sopenharmony_ci with self.assertRaisesRegex(ValueError, tzstr_regex): 10357db96d56Sopenharmony_ci self.zone_from_tzstr(invalid_tzstr) 10367db96d56Sopenharmony_ci 10377db96d56Sopenharmony_ci @classmethod 10387db96d56Sopenharmony_ci def _populate_test_cases(cls): 10397db96d56Sopenharmony_ci # This method uses a somewhat unusual style in that it populates the 10407db96d56Sopenharmony_ci # test cases for each tzstr by using a decorator to automatically call 10417db96d56Sopenharmony_ci # a function that mutates the current dictionary of test cases. 10427db96d56Sopenharmony_ci # 10437db96d56Sopenharmony_ci # The population of the test cases is done in individual functions to 10447db96d56Sopenharmony_ci # give each set of test cases its own namespace in which to define 10457db96d56Sopenharmony_ci # its offsets (this way we don't have to worry about variable reuse 10467db96d56Sopenharmony_ci # causing problems if someone makes a typo). 10477db96d56Sopenharmony_ci # 10487db96d56Sopenharmony_ci # The decorator for calling is used to make it more obvious that each 10497db96d56Sopenharmony_ci # function is actually called (if it's not decorated, it's not called). 10507db96d56Sopenharmony_ci def call(f): 10517db96d56Sopenharmony_ci """Decorator to call the addition methods. 10527db96d56Sopenharmony_ci 10537db96d56Sopenharmony_ci This will call a function which adds at least one new entry into 10547db96d56Sopenharmony_ci the `cases` dictionary. The decorator will also assert that 10557db96d56Sopenharmony_ci something was added to the dictionary. 10567db96d56Sopenharmony_ci """ 10577db96d56Sopenharmony_ci prev_len = len(cases) 10587db96d56Sopenharmony_ci f() 10597db96d56Sopenharmony_ci assert len(cases) > prev_len, "Function did not add a test case!" 10607db96d56Sopenharmony_ci 10617db96d56Sopenharmony_ci NORMAL = cls.NORMAL 10627db96d56Sopenharmony_ci FOLD = cls.FOLD 10637db96d56Sopenharmony_ci GAP = cls.GAP 10647db96d56Sopenharmony_ci 10657db96d56Sopenharmony_ci cases = {} 10667db96d56Sopenharmony_ci 10677db96d56Sopenharmony_ci @call 10687db96d56Sopenharmony_ci def _add(): 10697db96d56Sopenharmony_ci # Transition to EDT on the 2nd Sunday in March at 4 AM, and 10707db96d56Sopenharmony_ci # transition back on the first Sunday in November at 3AM 10717db96d56Sopenharmony_ci tzstr = "EST5EDT,M3.2.0/4:00,M11.1.0/3:00" 10727db96d56Sopenharmony_ci 10737db96d56Sopenharmony_ci EST = ZoneOffset("EST", timedelta(hours=-5), ZERO) 10747db96d56Sopenharmony_ci EDT = ZoneOffset("EDT", timedelta(hours=-4), ONE_H) 10757db96d56Sopenharmony_ci 10767db96d56Sopenharmony_ci cases[tzstr] = ( 10777db96d56Sopenharmony_ci (datetime(2019, 3, 9), EST, NORMAL), 10787db96d56Sopenharmony_ci (datetime(2019, 3, 10, 3, 59), EST, NORMAL), 10797db96d56Sopenharmony_ci (datetime(2019, 3, 10, 4, 0, fold=0), EST, GAP), 10807db96d56Sopenharmony_ci (datetime(2019, 3, 10, 4, 0, fold=1), EDT, GAP), 10817db96d56Sopenharmony_ci (datetime(2019, 3, 10, 4, 1, fold=0), EST, GAP), 10827db96d56Sopenharmony_ci (datetime(2019, 3, 10, 4, 1, fold=1), EDT, GAP), 10837db96d56Sopenharmony_ci (datetime(2019, 11, 2), EDT, NORMAL), 10847db96d56Sopenharmony_ci (datetime(2019, 11, 3, 1, 59, fold=1), EDT, NORMAL), 10857db96d56Sopenharmony_ci (datetime(2019, 11, 3, 2, 0, fold=0), EDT, FOLD), 10867db96d56Sopenharmony_ci (datetime(2019, 11, 3, 2, 0, fold=1), EST, FOLD), 10877db96d56Sopenharmony_ci (datetime(2020, 3, 8, 3, 59), EST, NORMAL), 10887db96d56Sopenharmony_ci (datetime(2020, 3, 8, 4, 0, fold=0), EST, GAP), 10897db96d56Sopenharmony_ci (datetime(2020, 3, 8, 4, 0, fold=1), EDT, GAP), 10907db96d56Sopenharmony_ci (datetime(2020, 11, 1, 1, 59, fold=1), EDT, NORMAL), 10917db96d56Sopenharmony_ci (datetime(2020, 11, 1, 2, 0, fold=0), EDT, FOLD), 10927db96d56Sopenharmony_ci (datetime(2020, 11, 1, 2, 0, fold=1), EST, FOLD), 10937db96d56Sopenharmony_ci ) 10947db96d56Sopenharmony_ci 10957db96d56Sopenharmony_ci @call 10967db96d56Sopenharmony_ci def _add(): 10977db96d56Sopenharmony_ci # Transition to BST happens on the last Sunday in March at 1 AM GMT 10987db96d56Sopenharmony_ci # and the transition back happens the last Sunday in October at 2AM BST 10997db96d56Sopenharmony_ci tzstr = "GMT0BST-1,M3.5.0/1:00,M10.5.0/2:00" 11007db96d56Sopenharmony_ci 11017db96d56Sopenharmony_ci GMT = ZoneOffset("GMT", ZERO, ZERO) 11027db96d56Sopenharmony_ci BST = ZoneOffset("BST", ONE_H, ONE_H) 11037db96d56Sopenharmony_ci 11047db96d56Sopenharmony_ci cases[tzstr] = ( 11057db96d56Sopenharmony_ci (datetime(2019, 3, 30), GMT, NORMAL), 11067db96d56Sopenharmony_ci (datetime(2019, 3, 31, 0, 59), GMT, NORMAL), 11077db96d56Sopenharmony_ci (datetime(2019, 3, 31, 2, 0), BST, NORMAL), 11087db96d56Sopenharmony_ci (datetime(2019, 10, 26), BST, NORMAL), 11097db96d56Sopenharmony_ci (datetime(2019, 10, 27, 0, 59, fold=1), BST, NORMAL), 11107db96d56Sopenharmony_ci (datetime(2019, 10, 27, 1, 0, fold=0), BST, GAP), 11117db96d56Sopenharmony_ci (datetime(2019, 10, 27, 2, 0, fold=1), GMT, GAP), 11127db96d56Sopenharmony_ci (datetime(2020, 3, 29, 0, 59), GMT, NORMAL), 11137db96d56Sopenharmony_ci (datetime(2020, 3, 29, 2, 0), BST, NORMAL), 11147db96d56Sopenharmony_ci (datetime(2020, 10, 25, 0, 59, fold=1), BST, NORMAL), 11157db96d56Sopenharmony_ci (datetime(2020, 10, 25, 1, 0, fold=0), BST, FOLD), 11167db96d56Sopenharmony_ci (datetime(2020, 10, 25, 2, 0, fold=1), GMT, NORMAL), 11177db96d56Sopenharmony_ci ) 11187db96d56Sopenharmony_ci 11197db96d56Sopenharmony_ci @call 11207db96d56Sopenharmony_ci def _add(): 11217db96d56Sopenharmony_ci # Austrialian time zone - DST start is chronologically first 11227db96d56Sopenharmony_ci tzstr = "AEST-10AEDT,M10.1.0/2,M4.1.0/3" 11237db96d56Sopenharmony_ci 11247db96d56Sopenharmony_ci AEST = ZoneOffset("AEST", timedelta(hours=10), ZERO) 11257db96d56Sopenharmony_ci AEDT = ZoneOffset("AEDT", timedelta(hours=11), ONE_H) 11267db96d56Sopenharmony_ci 11277db96d56Sopenharmony_ci cases[tzstr] = ( 11287db96d56Sopenharmony_ci (datetime(2019, 4, 6), AEDT, NORMAL), 11297db96d56Sopenharmony_ci (datetime(2019, 4, 7, 1, 59), AEDT, NORMAL), 11307db96d56Sopenharmony_ci (datetime(2019, 4, 7, 1, 59, fold=1), AEDT, NORMAL), 11317db96d56Sopenharmony_ci (datetime(2019, 4, 7, 2, 0, fold=0), AEDT, FOLD), 11327db96d56Sopenharmony_ci (datetime(2019, 4, 7, 2, 1, fold=0), AEDT, FOLD), 11337db96d56Sopenharmony_ci (datetime(2019, 4, 7, 2, 0, fold=1), AEST, FOLD), 11347db96d56Sopenharmony_ci (datetime(2019, 4, 7, 2, 1, fold=1), AEST, FOLD), 11357db96d56Sopenharmony_ci (datetime(2019, 4, 7, 3, 0, fold=0), AEST, NORMAL), 11367db96d56Sopenharmony_ci (datetime(2019, 4, 7, 3, 0, fold=1), AEST, NORMAL), 11377db96d56Sopenharmony_ci (datetime(2019, 10, 5, 0), AEST, NORMAL), 11387db96d56Sopenharmony_ci (datetime(2019, 10, 6, 1, 59), AEST, NORMAL), 11397db96d56Sopenharmony_ci (datetime(2019, 10, 6, 2, 0, fold=0), AEST, GAP), 11407db96d56Sopenharmony_ci (datetime(2019, 10, 6, 2, 0, fold=1), AEDT, GAP), 11417db96d56Sopenharmony_ci (datetime(2019, 10, 6, 3, 0), AEDT, NORMAL), 11427db96d56Sopenharmony_ci ) 11437db96d56Sopenharmony_ci 11447db96d56Sopenharmony_ci @call 11457db96d56Sopenharmony_ci def _add(): 11467db96d56Sopenharmony_ci # Irish time zone - negative DST 11477db96d56Sopenharmony_ci tzstr = "IST-1GMT0,M10.5.0,M3.5.0/1" 11487db96d56Sopenharmony_ci 11497db96d56Sopenharmony_ci GMT = ZoneOffset("GMT", ZERO, -ONE_H) 11507db96d56Sopenharmony_ci IST = ZoneOffset("IST", ONE_H, ZERO) 11517db96d56Sopenharmony_ci 11527db96d56Sopenharmony_ci cases[tzstr] = ( 11537db96d56Sopenharmony_ci (datetime(2019, 3, 30), GMT, NORMAL), 11547db96d56Sopenharmony_ci (datetime(2019, 3, 31, 0, 59), GMT, NORMAL), 11557db96d56Sopenharmony_ci (datetime(2019, 3, 31, 2, 0), IST, NORMAL), 11567db96d56Sopenharmony_ci (datetime(2019, 10, 26), IST, NORMAL), 11577db96d56Sopenharmony_ci (datetime(2019, 10, 27, 0, 59, fold=1), IST, NORMAL), 11587db96d56Sopenharmony_ci (datetime(2019, 10, 27, 1, 0, fold=0), IST, FOLD), 11597db96d56Sopenharmony_ci (datetime(2019, 10, 27, 1, 0, fold=1), GMT, FOLD), 11607db96d56Sopenharmony_ci (datetime(2019, 10, 27, 2, 0, fold=1), GMT, NORMAL), 11617db96d56Sopenharmony_ci (datetime(2020, 3, 29, 0, 59), GMT, NORMAL), 11627db96d56Sopenharmony_ci (datetime(2020, 3, 29, 2, 0), IST, NORMAL), 11637db96d56Sopenharmony_ci (datetime(2020, 10, 25, 0, 59, fold=1), IST, NORMAL), 11647db96d56Sopenharmony_ci (datetime(2020, 10, 25, 1, 0, fold=0), IST, FOLD), 11657db96d56Sopenharmony_ci (datetime(2020, 10, 25, 2, 0, fold=1), GMT, NORMAL), 11667db96d56Sopenharmony_ci ) 11677db96d56Sopenharmony_ci 11687db96d56Sopenharmony_ci @call 11697db96d56Sopenharmony_ci def _add(): 11707db96d56Sopenharmony_ci # Pacific/Kosrae: Fixed offset zone with a quoted numerical tzname 11717db96d56Sopenharmony_ci tzstr = "<+11>-11" 11727db96d56Sopenharmony_ci 11737db96d56Sopenharmony_ci cases[tzstr] = ( 11747db96d56Sopenharmony_ci ( 11757db96d56Sopenharmony_ci datetime(2020, 1, 1), 11767db96d56Sopenharmony_ci ZoneOffset("+11", timedelta(hours=11)), 11777db96d56Sopenharmony_ci NORMAL, 11787db96d56Sopenharmony_ci ), 11797db96d56Sopenharmony_ci ) 11807db96d56Sopenharmony_ci 11817db96d56Sopenharmony_ci @call 11827db96d56Sopenharmony_ci def _add(): 11837db96d56Sopenharmony_ci # Quoted STD and DST, transitions at 24:00 11847db96d56Sopenharmony_ci tzstr = "<-04>4<-03>,M9.1.6/24,M4.1.6/24" 11857db96d56Sopenharmony_ci 11867db96d56Sopenharmony_ci M04 = ZoneOffset("-04", timedelta(hours=-4)) 11877db96d56Sopenharmony_ci M03 = ZoneOffset("-03", timedelta(hours=-3), ONE_H) 11887db96d56Sopenharmony_ci 11897db96d56Sopenharmony_ci cases[tzstr] = ( 11907db96d56Sopenharmony_ci (datetime(2020, 5, 1), M04, NORMAL), 11917db96d56Sopenharmony_ci (datetime(2020, 11, 1), M03, NORMAL), 11927db96d56Sopenharmony_ci ) 11937db96d56Sopenharmony_ci 11947db96d56Sopenharmony_ci @call 11957db96d56Sopenharmony_ci def _add(): 11967db96d56Sopenharmony_ci # Permanent daylight saving time is modeled with transitions at 0/0 11977db96d56Sopenharmony_ci # and J365/25, as mentioned in RFC 8536 Section 3.3.1 11987db96d56Sopenharmony_ci tzstr = "EST5EDT,0/0,J365/25" 11997db96d56Sopenharmony_ci 12007db96d56Sopenharmony_ci EDT = ZoneOffset("EDT", timedelta(hours=-4), ONE_H) 12017db96d56Sopenharmony_ci 12027db96d56Sopenharmony_ci cases[tzstr] = ( 12037db96d56Sopenharmony_ci (datetime(2019, 1, 1), EDT, NORMAL), 12047db96d56Sopenharmony_ci (datetime(2019, 6, 1), EDT, NORMAL), 12057db96d56Sopenharmony_ci (datetime(2019, 12, 31, 23, 59, 59, 999999), EDT, NORMAL), 12067db96d56Sopenharmony_ci (datetime(2020, 1, 1), EDT, NORMAL), 12077db96d56Sopenharmony_ci (datetime(2020, 3, 1), EDT, NORMAL), 12087db96d56Sopenharmony_ci (datetime(2020, 6, 1), EDT, NORMAL), 12097db96d56Sopenharmony_ci (datetime(2020, 12, 31, 23, 59, 59, 999999), EDT, NORMAL), 12107db96d56Sopenharmony_ci (datetime(2400, 1, 1), EDT, NORMAL), 12117db96d56Sopenharmony_ci (datetime(2400, 3, 1), EDT, NORMAL), 12127db96d56Sopenharmony_ci (datetime(2400, 12, 31, 23, 59, 59, 999999), EDT, NORMAL), 12137db96d56Sopenharmony_ci ) 12147db96d56Sopenharmony_ci 12157db96d56Sopenharmony_ci @call 12167db96d56Sopenharmony_ci def _add(): 12177db96d56Sopenharmony_ci # Transitions on March 1st and November 1st of each year 12187db96d56Sopenharmony_ci tzstr = "AAA3BBB,J60/12,J305/12" 12197db96d56Sopenharmony_ci 12207db96d56Sopenharmony_ci AAA = ZoneOffset("AAA", timedelta(hours=-3)) 12217db96d56Sopenharmony_ci BBB = ZoneOffset("BBB", timedelta(hours=-2), ONE_H) 12227db96d56Sopenharmony_ci 12237db96d56Sopenharmony_ci cases[tzstr] = ( 12247db96d56Sopenharmony_ci (datetime(2019, 1, 1), AAA, NORMAL), 12257db96d56Sopenharmony_ci (datetime(2019, 2, 28), AAA, NORMAL), 12267db96d56Sopenharmony_ci (datetime(2019, 3, 1, 11, 59), AAA, NORMAL), 12277db96d56Sopenharmony_ci (datetime(2019, 3, 1, 12, fold=0), AAA, GAP), 12287db96d56Sopenharmony_ci (datetime(2019, 3, 1, 12, fold=1), BBB, GAP), 12297db96d56Sopenharmony_ci (datetime(2019, 3, 1, 13), BBB, NORMAL), 12307db96d56Sopenharmony_ci (datetime(2019, 11, 1, 10, 59), BBB, NORMAL), 12317db96d56Sopenharmony_ci (datetime(2019, 11, 1, 11, fold=0), BBB, FOLD), 12327db96d56Sopenharmony_ci (datetime(2019, 11, 1, 11, fold=1), AAA, FOLD), 12337db96d56Sopenharmony_ci (datetime(2019, 11, 1, 12), AAA, NORMAL), 12347db96d56Sopenharmony_ci (datetime(2019, 12, 31, 23, 59, 59, 999999), AAA, NORMAL), 12357db96d56Sopenharmony_ci (datetime(2020, 1, 1), AAA, NORMAL), 12367db96d56Sopenharmony_ci (datetime(2020, 2, 29), AAA, NORMAL), 12377db96d56Sopenharmony_ci (datetime(2020, 3, 1, 11, 59), AAA, NORMAL), 12387db96d56Sopenharmony_ci (datetime(2020, 3, 1, 12, fold=0), AAA, GAP), 12397db96d56Sopenharmony_ci (datetime(2020, 3, 1, 12, fold=1), BBB, GAP), 12407db96d56Sopenharmony_ci (datetime(2020, 3, 1, 13), BBB, NORMAL), 12417db96d56Sopenharmony_ci (datetime(2020, 11, 1, 10, 59), BBB, NORMAL), 12427db96d56Sopenharmony_ci (datetime(2020, 11, 1, 11, fold=0), BBB, FOLD), 12437db96d56Sopenharmony_ci (datetime(2020, 11, 1, 11, fold=1), AAA, FOLD), 12447db96d56Sopenharmony_ci (datetime(2020, 11, 1, 12), AAA, NORMAL), 12457db96d56Sopenharmony_ci (datetime(2020, 12, 31, 23, 59, 59, 999999), AAA, NORMAL), 12467db96d56Sopenharmony_ci ) 12477db96d56Sopenharmony_ci 12487db96d56Sopenharmony_ci @call 12497db96d56Sopenharmony_ci def _add(): 12507db96d56Sopenharmony_ci # Taken from America/Godthab, this rule has a transition on the 12517db96d56Sopenharmony_ci # Saturday before the last Sunday of March and October, at 22:00 12527db96d56Sopenharmony_ci # and 23:00, respectively. This is encoded with negative start 12537db96d56Sopenharmony_ci # and end transition times. 12547db96d56Sopenharmony_ci tzstr = "<-03>3<-02>,M3.5.0/-2,M10.5.0/-1" 12557db96d56Sopenharmony_ci 12567db96d56Sopenharmony_ci N03 = ZoneOffset("-03", timedelta(hours=-3)) 12577db96d56Sopenharmony_ci N02 = ZoneOffset("-02", timedelta(hours=-2), ONE_H) 12587db96d56Sopenharmony_ci 12597db96d56Sopenharmony_ci cases[tzstr] = ( 12607db96d56Sopenharmony_ci (datetime(2020, 3, 27), N03, NORMAL), 12617db96d56Sopenharmony_ci (datetime(2020, 3, 28, 21, 59, 59), N03, NORMAL), 12627db96d56Sopenharmony_ci (datetime(2020, 3, 28, 22, fold=0), N03, GAP), 12637db96d56Sopenharmony_ci (datetime(2020, 3, 28, 22, fold=1), N02, GAP), 12647db96d56Sopenharmony_ci (datetime(2020, 3, 28, 23), N02, NORMAL), 12657db96d56Sopenharmony_ci (datetime(2020, 10, 24, 21), N02, NORMAL), 12667db96d56Sopenharmony_ci (datetime(2020, 10, 24, 22, fold=0), N02, FOLD), 12677db96d56Sopenharmony_ci (datetime(2020, 10, 24, 22, fold=1), N03, FOLD), 12687db96d56Sopenharmony_ci (datetime(2020, 10, 24, 23), N03, NORMAL), 12697db96d56Sopenharmony_ci ) 12707db96d56Sopenharmony_ci 12717db96d56Sopenharmony_ci @call 12727db96d56Sopenharmony_ci def _add(): 12737db96d56Sopenharmony_ci # Transition times with minutes and seconds 12747db96d56Sopenharmony_ci tzstr = "AAA3BBB,M3.2.0/01:30,M11.1.0/02:15:45" 12757db96d56Sopenharmony_ci 12767db96d56Sopenharmony_ci AAA = ZoneOffset("AAA", timedelta(hours=-3)) 12777db96d56Sopenharmony_ci BBB = ZoneOffset("BBB", timedelta(hours=-2), ONE_H) 12787db96d56Sopenharmony_ci 12797db96d56Sopenharmony_ci cases[tzstr] = ( 12807db96d56Sopenharmony_ci (datetime(2012, 3, 11, 1, 0), AAA, NORMAL), 12817db96d56Sopenharmony_ci (datetime(2012, 3, 11, 1, 30, fold=0), AAA, GAP), 12827db96d56Sopenharmony_ci (datetime(2012, 3, 11, 1, 30, fold=1), BBB, GAP), 12837db96d56Sopenharmony_ci (datetime(2012, 3, 11, 2, 30), BBB, NORMAL), 12847db96d56Sopenharmony_ci (datetime(2012, 11, 4, 1, 15, 44, 999999), BBB, NORMAL), 12857db96d56Sopenharmony_ci (datetime(2012, 11, 4, 1, 15, 45, fold=0), BBB, FOLD), 12867db96d56Sopenharmony_ci (datetime(2012, 11, 4, 1, 15, 45, fold=1), AAA, FOLD), 12877db96d56Sopenharmony_ci (datetime(2012, 11, 4, 2, 15, 45), AAA, NORMAL), 12887db96d56Sopenharmony_ci ) 12897db96d56Sopenharmony_ci 12907db96d56Sopenharmony_ci cls.test_cases = cases 12917db96d56Sopenharmony_ci 12927db96d56Sopenharmony_ci 12937db96d56Sopenharmony_ciclass CTZStrTest(TZStrTest): 12947db96d56Sopenharmony_ci module = c_zoneinfo 12957db96d56Sopenharmony_ci 12967db96d56Sopenharmony_ci 12977db96d56Sopenharmony_ciclass ZoneInfoCacheTest(TzPathUserMixin, ZoneInfoTestBase): 12987db96d56Sopenharmony_ci module = py_zoneinfo 12997db96d56Sopenharmony_ci 13007db96d56Sopenharmony_ci def setUp(self): 13017db96d56Sopenharmony_ci self.klass.clear_cache() 13027db96d56Sopenharmony_ci super().setUp() 13037db96d56Sopenharmony_ci 13047db96d56Sopenharmony_ci @property 13057db96d56Sopenharmony_ci def zoneinfo_data(self): 13067db96d56Sopenharmony_ci return ZONEINFO_DATA 13077db96d56Sopenharmony_ci 13087db96d56Sopenharmony_ci @property 13097db96d56Sopenharmony_ci def tzpath(self): 13107db96d56Sopenharmony_ci return [self.zoneinfo_data.tzpath] 13117db96d56Sopenharmony_ci 13127db96d56Sopenharmony_ci def test_ephemeral_zones(self): 13137db96d56Sopenharmony_ci self.assertIs( 13147db96d56Sopenharmony_ci self.klass("America/Los_Angeles"), self.klass("America/Los_Angeles") 13157db96d56Sopenharmony_ci ) 13167db96d56Sopenharmony_ci 13177db96d56Sopenharmony_ci def test_strong_refs(self): 13187db96d56Sopenharmony_ci tz0 = self.klass("Australia/Sydney") 13197db96d56Sopenharmony_ci tz1 = self.klass("Australia/Sydney") 13207db96d56Sopenharmony_ci 13217db96d56Sopenharmony_ci self.assertIs(tz0, tz1) 13227db96d56Sopenharmony_ci 13237db96d56Sopenharmony_ci def test_no_cache(self): 13247db96d56Sopenharmony_ci 13257db96d56Sopenharmony_ci tz0 = self.klass("Europe/Lisbon") 13267db96d56Sopenharmony_ci tz1 = self.klass.no_cache("Europe/Lisbon") 13277db96d56Sopenharmony_ci 13287db96d56Sopenharmony_ci self.assertIsNot(tz0, tz1) 13297db96d56Sopenharmony_ci 13307db96d56Sopenharmony_ci def test_cache_reset_tzpath(self): 13317db96d56Sopenharmony_ci """Test that the cache persists when tzpath has been changed. 13327db96d56Sopenharmony_ci 13337db96d56Sopenharmony_ci The PEP specifies that as long as a reference exists to one zone 13347db96d56Sopenharmony_ci with a given key, the primary constructor must continue to return 13357db96d56Sopenharmony_ci the same object. 13367db96d56Sopenharmony_ci """ 13377db96d56Sopenharmony_ci zi0 = self.klass("America/Los_Angeles") 13387db96d56Sopenharmony_ci with self.tzpath_context([]): 13397db96d56Sopenharmony_ci zi1 = self.klass("America/Los_Angeles") 13407db96d56Sopenharmony_ci 13417db96d56Sopenharmony_ci self.assertIs(zi0, zi1) 13427db96d56Sopenharmony_ci 13437db96d56Sopenharmony_ci def test_clear_cache_explicit_none(self): 13447db96d56Sopenharmony_ci la0 = self.klass("America/Los_Angeles") 13457db96d56Sopenharmony_ci self.klass.clear_cache(only_keys=None) 13467db96d56Sopenharmony_ci la1 = self.klass("America/Los_Angeles") 13477db96d56Sopenharmony_ci 13487db96d56Sopenharmony_ci self.assertIsNot(la0, la1) 13497db96d56Sopenharmony_ci 13507db96d56Sopenharmony_ci def test_clear_cache_one_key(self): 13517db96d56Sopenharmony_ci """Tests that you can clear a single key from the cache.""" 13527db96d56Sopenharmony_ci la0 = self.klass("America/Los_Angeles") 13537db96d56Sopenharmony_ci dub0 = self.klass("Europe/Dublin") 13547db96d56Sopenharmony_ci 13557db96d56Sopenharmony_ci self.klass.clear_cache(only_keys=["America/Los_Angeles"]) 13567db96d56Sopenharmony_ci 13577db96d56Sopenharmony_ci la1 = self.klass("America/Los_Angeles") 13587db96d56Sopenharmony_ci dub1 = self.klass("Europe/Dublin") 13597db96d56Sopenharmony_ci 13607db96d56Sopenharmony_ci self.assertIsNot(la0, la1) 13617db96d56Sopenharmony_ci self.assertIs(dub0, dub1) 13627db96d56Sopenharmony_ci 13637db96d56Sopenharmony_ci def test_clear_cache_two_keys(self): 13647db96d56Sopenharmony_ci la0 = self.klass("America/Los_Angeles") 13657db96d56Sopenharmony_ci dub0 = self.klass("Europe/Dublin") 13667db96d56Sopenharmony_ci tok0 = self.klass("Asia/Tokyo") 13677db96d56Sopenharmony_ci 13687db96d56Sopenharmony_ci self.klass.clear_cache( 13697db96d56Sopenharmony_ci only_keys=["America/Los_Angeles", "Europe/Dublin"] 13707db96d56Sopenharmony_ci ) 13717db96d56Sopenharmony_ci 13727db96d56Sopenharmony_ci la1 = self.klass("America/Los_Angeles") 13737db96d56Sopenharmony_ci dub1 = self.klass("Europe/Dublin") 13747db96d56Sopenharmony_ci tok1 = self.klass("Asia/Tokyo") 13757db96d56Sopenharmony_ci 13767db96d56Sopenharmony_ci self.assertIsNot(la0, la1) 13777db96d56Sopenharmony_ci self.assertIsNot(dub0, dub1) 13787db96d56Sopenharmony_ci self.assertIs(tok0, tok1) 13797db96d56Sopenharmony_ci 13807db96d56Sopenharmony_ci 13817db96d56Sopenharmony_ciclass CZoneInfoCacheTest(ZoneInfoCacheTest): 13827db96d56Sopenharmony_ci module = c_zoneinfo 13837db96d56Sopenharmony_ci 13847db96d56Sopenharmony_ci 13857db96d56Sopenharmony_ciclass ZoneInfoPickleTest(TzPathUserMixin, ZoneInfoTestBase): 13867db96d56Sopenharmony_ci module = py_zoneinfo 13877db96d56Sopenharmony_ci 13887db96d56Sopenharmony_ci def setUp(self): 13897db96d56Sopenharmony_ci self.klass.clear_cache() 13907db96d56Sopenharmony_ci 13917db96d56Sopenharmony_ci with contextlib.ExitStack() as stack: 13927db96d56Sopenharmony_ci stack.enter_context(test_support.set_zoneinfo_module(self.module)) 13937db96d56Sopenharmony_ci self.addCleanup(stack.pop_all().close) 13947db96d56Sopenharmony_ci 13957db96d56Sopenharmony_ci super().setUp() 13967db96d56Sopenharmony_ci 13977db96d56Sopenharmony_ci @property 13987db96d56Sopenharmony_ci def zoneinfo_data(self): 13997db96d56Sopenharmony_ci return ZONEINFO_DATA 14007db96d56Sopenharmony_ci 14017db96d56Sopenharmony_ci @property 14027db96d56Sopenharmony_ci def tzpath(self): 14037db96d56Sopenharmony_ci return [self.zoneinfo_data.tzpath] 14047db96d56Sopenharmony_ci 14057db96d56Sopenharmony_ci def test_cache_hit(self): 14067db96d56Sopenharmony_ci for proto in range(pickle.HIGHEST_PROTOCOL + 1): 14077db96d56Sopenharmony_ci with self.subTest(proto=proto): 14087db96d56Sopenharmony_ci zi_in = self.klass("Europe/Dublin") 14097db96d56Sopenharmony_ci pkl = pickle.dumps(zi_in, protocol=proto) 14107db96d56Sopenharmony_ci zi_rt = pickle.loads(pkl) 14117db96d56Sopenharmony_ci 14127db96d56Sopenharmony_ci with self.subTest(test="Is non-pickled ZoneInfo"): 14137db96d56Sopenharmony_ci self.assertIs(zi_in, zi_rt) 14147db96d56Sopenharmony_ci 14157db96d56Sopenharmony_ci zi_rt2 = pickle.loads(pkl) 14167db96d56Sopenharmony_ci with self.subTest(test="Is unpickled ZoneInfo"): 14177db96d56Sopenharmony_ci self.assertIs(zi_rt, zi_rt2) 14187db96d56Sopenharmony_ci 14197db96d56Sopenharmony_ci def test_cache_miss(self): 14207db96d56Sopenharmony_ci for proto in range(pickle.HIGHEST_PROTOCOL + 1): 14217db96d56Sopenharmony_ci with self.subTest(proto=proto): 14227db96d56Sopenharmony_ci zi_in = self.klass("Europe/Dublin") 14237db96d56Sopenharmony_ci pkl = pickle.dumps(zi_in, protocol=proto) 14247db96d56Sopenharmony_ci 14257db96d56Sopenharmony_ci del zi_in 14267db96d56Sopenharmony_ci self.klass.clear_cache() # Induce a cache miss 14277db96d56Sopenharmony_ci zi_rt = pickle.loads(pkl) 14287db96d56Sopenharmony_ci zi_rt2 = pickle.loads(pkl) 14297db96d56Sopenharmony_ci 14307db96d56Sopenharmony_ci self.assertIs(zi_rt, zi_rt2) 14317db96d56Sopenharmony_ci 14327db96d56Sopenharmony_ci def test_no_cache(self): 14337db96d56Sopenharmony_ci for proto in range(pickle.HIGHEST_PROTOCOL + 1): 14347db96d56Sopenharmony_ci with self.subTest(proto=proto): 14357db96d56Sopenharmony_ci zi_no_cache = self.klass.no_cache("Europe/Dublin") 14367db96d56Sopenharmony_ci 14377db96d56Sopenharmony_ci pkl = pickle.dumps(zi_no_cache, protocol=proto) 14387db96d56Sopenharmony_ci zi_rt = pickle.loads(pkl) 14397db96d56Sopenharmony_ci 14407db96d56Sopenharmony_ci with self.subTest(test="Not the pickled object"): 14417db96d56Sopenharmony_ci self.assertIsNot(zi_rt, zi_no_cache) 14427db96d56Sopenharmony_ci 14437db96d56Sopenharmony_ci zi_rt2 = pickle.loads(pkl) 14447db96d56Sopenharmony_ci with self.subTest(test="Not a second unpickled object"): 14457db96d56Sopenharmony_ci self.assertIsNot(zi_rt, zi_rt2) 14467db96d56Sopenharmony_ci 14477db96d56Sopenharmony_ci zi_cache = self.klass("Europe/Dublin") 14487db96d56Sopenharmony_ci with self.subTest(test="Not a cached object"): 14497db96d56Sopenharmony_ci self.assertIsNot(zi_rt, zi_cache) 14507db96d56Sopenharmony_ci 14517db96d56Sopenharmony_ci def test_from_file(self): 14527db96d56Sopenharmony_ci key = "Europe/Dublin" 14537db96d56Sopenharmony_ci with open(self.zoneinfo_data.path_from_key(key), "rb") as f: 14547db96d56Sopenharmony_ci zi_nokey = self.klass.from_file(f) 14557db96d56Sopenharmony_ci 14567db96d56Sopenharmony_ci f.seek(0) 14577db96d56Sopenharmony_ci zi_key = self.klass.from_file(f, key=key) 14587db96d56Sopenharmony_ci 14597db96d56Sopenharmony_ci test_cases = [ 14607db96d56Sopenharmony_ci (zi_key, "ZoneInfo with key"), 14617db96d56Sopenharmony_ci (zi_nokey, "ZoneInfo without key"), 14627db96d56Sopenharmony_ci ] 14637db96d56Sopenharmony_ci 14647db96d56Sopenharmony_ci for zi, test_name in test_cases: 14657db96d56Sopenharmony_ci for proto in range(pickle.HIGHEST_PROTOCOL + 1): 14667db96d56Sopenharmony_ci with self.subTest(test_name=test_name, proto=proto): 14677db96d56Sopenharmony_ci with self.assertRaises(pickle.PicklingError): 14687db96d56Sopenharmony_ci pickle.dumps(zi, protocol=proto) 14697db96d56Sopenharmony_ci 14707db96d56Sopenharmony_ci def test_pickle_after_from_file(self): 14717db96d56Sopenharmony_ci # This may be a bit of paranoia, but this test is to ensure that no 14727db96d56Sopenharmony_ci # global state is maintained in order to handle the pickle cache and 14737db96d56Sopenharmony_ci # from_file behavior, and that it is possible to interweave the 14747db96d56Sopenharmony_ci # constructors of each of these and pickling/unpickling without issues. 14757db96d56Sopenharmony_ci for proto in range(pickle.HIGHEST_PROTOCOL + 1): 14767db96d56Sopenharmony_ci with self.subTest(proto=proto): 14777db96d56Sopenharmony_ci key = "Europe/Dublin" 14787db96d56Sopenharmony_ci zi = self.klass(key) 14797db96d56Sopenharmony_ci 14807db96d56Sopenharmony_ci pkl_0 = pickle.dumps(zi, protocol=proto) 14817db96d56Sopenharmony_ci zi_rt_0 = pickle.loads(pkl_0) 14827db96d56Sopenharmony_ci self.assertIs(zi, zi_rt_0) 14837db96d56Sopenharmony_ci 14847db96d56Sopenharmony_ci with open(self.zoneinfo_data.path_from_key(key), "rb") as f: 14857db96d56Sopenharmony_ci zi_ff = self.klass.from_file(f, key=key) 14867db96d56Sopenharmony_ci 14877db96d56Sopenharmony_ci pkl_1 = pickle.dumps(zi, protocol=proto) 14887db96d56Sopenharmony_ci zi_rt_1 = pickle.loads(pkl_1) 14897db96d56Sopenharmony_ci self.assertIs(zi, zi_rt_1) 14907db96d56Sopenharmony_ci 14917db96d56Sopenharmony_ci with self.assertRaises(pickle.PicklingError): 14927db96d56Sopenharmony_ci pickle.dumps(zi_ff, protocol=proto) 14937db96d56Sopenharmony_ci 14947db96d56Sopenharmony_ci pkl_2 = pickle.dumps(zi, protocol=proto) 14957db96d56Sopenharmony_ci zi_rt_2 = pickle.loads(pkl_2) 14967db96d56Sopenharmony_ci self.assertIs(zi, zi_rt_2) 14977db96d56Sopenharmony_ci 14987db96d56Sopenharmony_ci 14997db96d56Sopenharmony_ciclass CZoneInfoPickleTest(ZoneInfoPickleTest): 15007db96d56Sopenharmony_ci module = c_zoneinfo 15017db96d56Sopenharmony_ci 15027db96d56Sopenharmony_ci 15037db96d56Sopenharmony_ciclass CallingConventionTest(ZoneInfoTestBase): 15047db96d56Sopenharmony_ci """Tests for functions with restricted calling conventions.""" 15057db96d56Sopenharmony_ci 15067db96d56Sopenharmony_ci module = py_zoneinfo 15077db96d56Sopenharmony_ci 15087db96d56Sopenharmony_ci @property 15097db96d56Sopenharmony_ci def zoneinfo_data(self): 15107db96d56Sopenharmony_ci return ZONEINFO_DATA 15117db96d56Sopenharmony_ci 15127db96d56Sopenharmony_ci def test_from_file(self): 15137db96d56Sopenharmony_ci with open(self.zoneinfo_data.path_from_key("UTC"), "rb") as f: 15147db96d56Sopenharmony_ci with self.assertRaises(TypeError): 15157db96d56Sopenharmony_ci self.klass.from_file(fobj=f) 15167db96d56Sopenharmony_ci 15177db96d56Sopenharmony_ci def test_clear_cache(self): 15187db96d56Sopenharmony_ci with self.assertRaises(TypeError): 15197db96d56Sopenharmony_ci self.klass.clear_cache(["UTC"]) 15207db96d56Sopenharmony_ci 15217db96d56Sopenharmony_ci 15227db96d56Sopenharmony_ciclass CCallingConventionTest(CallingConventionTest): 15237db96d56Sopenharmony_ci module = c_zoneinfo 15247db96d56Sopenharmony_ci 15257db96d56Sopenharmony_ci 15267db96d56Sopenharmony_ciclass TzPathTest(TzPathUserMixin, ZoneInfoTestBase): 15277db96d56Sopenharmony_ci module = py_zoneinfo 15287db96d56Sopenharmony_ci 15297db96d56Sopenharmony_ci @staticmethod 15307db96d56Sopenharmony_ci @contextlib.contextmanager 15317db96d56Sopenharmony_ci def python_tzpath_context(value): 15327db96d56Sopenharmony_ci path_var = "PYTHONTZPATH" 15337db96d56Sopenharmony_ci unset_env_sentinel = object() 15347db96d56Sopenharmony_ci old_env = unset_env_sentinel 15357db96d56Sopenharmony_ci try: 15367db96d56Sopenharmony_ci with OS_ENV_LOCK: 15377db96d56Sopenharmony_ci old_env = os.environ.get(path_var, None) 15387db96d56Sopenharmony_ci os.environ[path_var] = value 15397db96d56Sopenharmony_ci yield 15407db96d56Sopenharmony_ci finally: 15417db96d56Sopenharmony_ci if old_env is unset_env_sentinel: 15427db96d56Sopenharmony_ci # In this case, `old_env` was never retrieved from the 15437db96d56Sopenharmony_ci # environment for whatever reason, so there's no need to 15447db96d56Sopenharmony_ci # reset the environment TZPATH. 15457db96d56Sopenharmony_ci pass 15467db96d56Sopenharmony_ci elif old_env is None: 15477db96d56Sopenharmony_ci del os.environ[path_var] 15487db96d56Sopenharmony_ci else: 15497db96d56Sopenharmony_ci os.environ[path_var] = old_env # pragma: nocover 15507db96d56Sopenharmony_ci 15517db96d56Sopenharmony_ci def test_env_variable(self): 15527db96d56Sopenharmony_ci """Tests that the environment variable works with reset_tzpath.""" 15537db96d56Sopenharmony_ci new_paths = [ 15547db96d56Sopenharmony_ci ("", []), 15557db96d56Sopenharmony_ci ("/etc/zoneinfo", ["/etc/zoneinfo"]), 15567db96d56Sopenharmony_ci (f"/a/b/c{os.pathsep}/d/e/f", ["/a/b/c", "/d/e/f"]), 15577db96d56Sopenharmony_ci ] 15587db96d56Sopenharmony_ci 15597db96d56Sopenharmony_ci for new_path_var, expected_result in new_paths: 15607db96d56Sopenharmony_ci with self.python_tzpath_context(new_path_var): 15617db96d56Sopenharmony_ci with self.subTest(tzpath=new_path_var): 15627db96d56Sopenharmony_ci self.module.reset_tzpath() 15637db96d56Sopenharmony_ci tzpath = self.module.TZPATH 15647db96d56Sopenharmony_ci self.assertSequenceEqual(tzpath, expected_result) 15657db96d56Sopenharmony_ci 15667db96d56Sopenharmony_ci def test_env_variable_relative_paths(self): 15677db96d56Sopenharmony_ci test_cases = [ 15687db96d56Sopenharmony_ci [("path/to/somewhere",), ()], 15697db96d56Sopenharmony_ci [ 15707db96d56Sopenharmony_ci ("/usr/share/zoneinfo", "path/to/somewhere",), 15717db96d56Sopenharmony_ci ("/usr/share/zoneinfo",), 15727db96d56Sopenharmony_ci ], 15737db96d56Sopenharmony_ci [("../relative/path",), ()], 15747db96d56Sopenharmony_ci [ 15757db96d56Sopenharmony_ci ("/usr/share/zoneinfo", "../relative/path",), 15767db96d56Sopenharmony_ci ("/usr/share/zoneinfo",), 15777db96d56Sopenharmony_ci ], 15787db96d56Sopenharmony_ci [("path/to/somewhere", "../relative/path",), ()], 15797db96d56Sopenharmony_ci [ 15807db96d56Sopenharmony_ci ( 15817db96d56Sopenharmony_ci "/usr/share/zoneinfo", 15827db96d56Sopenharmony_ci "path/to/somewhere", 15837db96d56Sopenharmony_ci "../relative/path", 15847db96d56Sopenharmony_ci ), 15857db96d56Sopenharmony_ci ("/usr/share/zoneinfo",), 15867db96d56Sopenharmony_ci ], 15877db96d56Sopenharmony_ci ] 15887db96d56Sopenharmony_ci 15897db96d56Sopenharmony_ci for input_paths, expected_paths in test_cases: 15907db96d56Sopenharmony_ci path_var = os.pathsep.join(input_paths) 15917db96d56Sopenharmony_ci with self.python_tzpath_context(path_var): 15927db96d56Sopenharmony_ci with self.subTest("warning", path_var=path_var): 15937db96d56Sopenharmony_ci # Note: Per PEP 615 the warning is implementation-defined 15947db96d56Sopenharmony_ci # behavior, other implementations need not warn. 15957db96d56Sopenharmony_ci with self.assertWarns(self.module.InvalidTZPathWarning): 15967db96d56Sopenharmony_ci self.module.reset_tzpath() 15977db96d56Sopenharmony_ci 15987db96d56Sopenharmony_ci tzpath = self.module.TZPATH 15997db96d56Sopenharmony_ci with self.subTest("filtered", path_var=path_var): 16007db96d56Sopenharmony_ci self.assertSequenceEqual(tzpath, expected_paths) 16017db96d56Sopenharmony_ci 16027db96d56Sopenharmony_ci def test_reset_tzpath_kwarg(self): 16037db96d56Sopenharmony_ci self.module.reset_tzpath(to=["/a/b/c"]) 16047db96d56Sopenharmony_ci 16057db96d56Sopenharmony_ci self.assertSequenceEqual(self.module.TZPATH, ("/a/b/c",)) 16067db96d56Sopenharmony_ci 16077db96d56Sopenharmony_ci def test_reset_tzpath_relative_paths(self): 16087db96d56Sopenharmony_ci bad_values = [ 16097db96d56Sopenharmony_ci ("path/to/somewhere",), 16107db96d56Sopenharmony_ci ("/usr/share/zoneinfo", "path/to/somewhere",), 16117db96d56Sopenharmony_ci ("../relative/path",), 16127db96d56Sopenharmony_ci ("/usr/share/zoneinfo", "../relative/path",), 16137db96d56Sopenharmony_ci ("path/to/somewhere", "../relative/path",), 16147db96d56Sopenharmony_ci ("/usr/share/zoneinfo", "path/to/somewhere", "../relative/path",), 16157db96d56Sopenharmony_ci ] 16167db96d56Sopenharmony_ci for input_paths in bad_values: 16177db96d56Sopenharmony_ci with self.subTest(input_paths=input_paths): 16187db96d56Sopenharmony_ci with self.assertRaises(ValueError): 16197db96d56Sopenharmony_ci self.module.reset_tzpath(to=input_paths) 16207db96d56Sopenharmony_ci 16217db96d56Sopenharmony_ci def test_tzpath_type_error(self): 16227db96d56Sopenharmony_ci bad_values = [ 16237db96d56Sopenharmony_ci "/etc/zoneinfo:/usr/share/zoneinfo", 16247db96d56Sopenharmony_ci b"/etc/zoneinfo:/usr/share/zoneinfo", 16257db96d56Sopenharmony_ci 0, 16267db96d56Sopenharmony_ci ] 16277db96d56Sopenharmony_ci 16287db96d56Sopenharmony_ci for bad_value in bad_values: 16297db96d56Sopenharmony_ci with self.subTest(value=bad_value): 16307db96d56Sopenharmony_ci with self.assertRaises(TypeError): 16317db96d56Sopenharmony_ci self.module.reset_tzpath(bad_value) 16327db96d56Sopenharmony_ci 16337db96d56Sopenharmony_ci def test_tzpath_attribute(self): 16347db96d56Sopenharmony_ci tzpath_0 = ["/one", "/two"] 16357db96d56Sopenharmony_ci tzpath_1 = ["/three"] 16367db96d56Sopenharmony_ci 16377db96d56Sopenharmony_ci with self.tzpath_context(tzpath_0): 16387db96d56Sopenharmony_ci query_0 = self.module.TZPATH 16397db96d56Sopenharmony_ci 16407db96d56Sopenharmony_ci with self.tzpath_context(tzpath_1): 16417db96d56Sopenharmony_ci query_1 = self.module.TZPATH 16427db96d56Sopenharmony_ci 16437db96d56Sopenharmony_ci self.assertSequenceEqual(tzpath_0, query_0) 16447db96d56Sopenharmony_ci self.assertSequenceEqual(tzpath_1, query_1) 16457db96d56Sopenharmony_ci 16467db96d56Sopenharmony_ci 16477db96d56Sopenharmony_ciclass CTzPathTest(TzPathTest): 16487db96d56Sopenharmony_ci module = c_zoneinfo 16497db96d56Sopenharmony_ci 16507db96d56Sopenharmony_ci 16517db96d56Sopenharmony_ciclass TestModule(ZoneInfoTestBase): 16527db96d56Sopenharmony_ci module = py_zoneinfo 16537db96d56Sopenharmony_ci 16547db96d56Sopenharmony_ci @property 16557db96d56Sopenharmony_ci def zoneinfo_data(self): 16567db96d56Sopenharmony_ci return ZONEINFO_DATA 16577db96d56Sopenharmony_ci 16587db96d56Sopenharmony_ci @cached_property 16597db96d56Sopenharmony_ci def _UTC_bytes(self): 16607db96d56Sopenharmony_ci zone_file = self.zoneinfo_data.path_from_key("UTC") 16617db96d56Sopenharmony_ci with open(zone_file, "rb") as f: 16627db96d56Sopenharmony_ci return f.read() 16637db96d56Sopenharmony_ci 16647db96d56Sopenharmony_ci def touch_zone(self, key, tz_root): 16657db96d56Sopenharmony_ci """Creates a valid TZif file at key under the zoneinfo root tz_root. 16667db96d56Sopenharmony_ci 16677db96d56Sopenharmony_ci tz_root must exist, but all folders below that will be created. 16687db96d56Sopenharmony_ci """ 16697db96d56Sopenharmony_ci if not os.path.exists(tz_root): 16707db96d56Sopenharmony_ci raise FileNotFoundError(f"{tz_root} does not exist.") 16717db96d56Sopenharmony_ci 16727db96d56Sopenharmony_ci root_dir, *tail = key.rsplit("/", 1) 16737db96d56Sopenharmony_ci if tail: # If there's no tail, then the first component isn't a dir 16747db96d56Sopenharmony_ci os.makedirs(os.path.join(tz_root, root_dir), exist_ok=True) 16757db96d56Sopenharmony_ci 16767db96d56Sopenharmony_ci zonefile_path = os.path.join(tz_root, key) 16777db96d56Sopenharmony_ci with open(zonefile_path, "wb") as f: 16787db96d56Sopenharmony_ci f.write(self._UTC_bytes) 16797db96d56Sopenharmony_ci 16807db96d56Sopenharmony_ci def test_getattr_error(self): 16817db96d56Sopenharmony_ci with self.assertRaises(AttributeError): 16827db96d56Sopenharmony_ci self.module.NOATTRIBUTE 16837db96d56Sopenharmony_ci 16847db96d56Sopenharmony_ci def test_dir_contains_all(self): 16857db96d56Sopenharmony_ci """dir(self.module) should at least contain everything in __all__.""" 16867db96d56Sopenharmony_ci module_all_set = set(self.module.__all__) 16877db96d56Sopenharmony_ci module_dir_set = set(dir(self.module)) 16887db96d56Sopenharmony_ci 16897db96d56Sopenharmony_ci difference = module_all_set - module_dir_set 16907db96d56Sopenharmony_ci 16917db96d56Sopenharmony_ci self.assertFalse(difference) 16927db96d56Sopenharmony_ci 16937db96d56Sopenharmony_ci def test_dir_unique(self): 16947db96d56Sopenharmony_ci """Test that there are no duplicates in dir(self.module)""" 16957db96d56Sopenharmony_ci module_dir = dir(self.module) 16967db96d56Sopenharmony_ci module_unique = set(module_dir) 16977db96d56Sopenharmony_ci 16987db96d56Sopenharmony_ci self.assertCountEqual(module_dir, module_unique) 16997db96d56Sopenharmony_ci 17007db96d56Sopenharmony_ci def test_available_timezones(self): 17017db96d56Sopenharmony_ci with self.tzpath_context([self.zoneinfo_data.tzpath]): 17027db96d56Sopenharmony_ci self.assertTrue(self.zoneinfo_data.keys) # Sanity check 17037db96d56Sopenharmony_ci 17047db96d56Sopenharmony_ci available_keys = self.module.available_timezones() 17057db96d56Sopenharmony_ci zoneinfo_keys = set(self.zoneinfo_data.keys) 17067db96d56Sopenharmony_ci 17077db96d56Sopenharmony_ci # If tzdata is not present, zoneinfo_keys == available_keys, 17087db96d56Sopenharmony_ci # otherwise it should be a subset. 17097db96d56Sopenharmony_ci union = zoneinfo_keys & available_keys 17107db96d56Sopenharmony_ci self.assertEqual(zoneinfo_keys, union) 17117db96d56Sopenharmony_ci 17127db96d56Sopenharmony_ci def test_available_timezones_weirdzone(self): 17137db96d56Sopenharmony_ci with tempfile.TemporaryDirectory() as td: 17147db96d56Sopenharmony_ci # Make a fictional zone at "Mars/Olympus_Mons" 17157db96d56Sopenharmony_ci self.touch_zone("Mars/Olympus_Mons", td) 17167db96d56Sopenharmony_ci 17177db96d56Sopenharmony_ci with self.tzpath_context([td]): 17187db96d56Sopenharmony_ci available_keys = self.module.available_timezones() 17197db96d56Sopenharmony_ci self.assertIn("Mars/Olympus_Mons", available_keys) 17207db96d56Sopenharmony_ci 17217db96d56Sopenharmony_ci def test_folder_exclusions(self): 17227db96d56Sopenharmony_ci expected = { 17237db96d56Sopenharmony_ci "America/Los_Angeles", 17247db96d56Sopenharmony_ci "America/Santiago", 17257db96d56Sopenharmony_ci "America/Indiana/Indianapolis", 17267db96d56Sopenharmony_ci "UTC", 17277db96d56Sopenharmony_ci "Europe/Paris", 17287db96d56Sopenharmony_ci "Europe/London", 17297db96d56Sopenharmony_ci "Asia/Tokyo", 17307db96d56Sopenharmony_ci "Australia/Sydney", 17317db96d56Sopenharmony_ci } 17327db96d56Sopenharmony_ci 17337db96d56Sopenharmony_ci base_tree = list(expected) 17347db96d56Sopenharmony_ci posix_tree = [f"posix/{x}" for x in base_tree] 17357db96d56Sopenharmony_ci right_tree = [f"right/{x}" for x in base_tree] 17367db96d56Sopenharmony_ci 17377db96d56Sopenharmony_ci cases = [ 17387db96d56Sopenharmony_ci ("base_tree", base_tree), 17397db96d56Sopenharmony_ci ("base_and_posix", base_tree + posix_tree), 17407db96d56Sopenharmony_ci ("base_and_right", base_tree + right_tree), 17417db96d56Sopenharmony_ci ("all_trees", base_tree + right_tree + posix_tree), 17427db96d56Sopenharmony_ci ] 17437db96d56Sopenharmony_ci 17447db96d56Sopenharmony_ci with tempfile.TemporaryDirectory() as td: 17457db96d56Sopenharmony_ci for case_name, tree in cases: 17467db96d56Sopenharmony_ci tz_root = os.path.join(td, case_name) 17477db96d56Sopenharmony_ci os.mkdir(tz_root) 17487db96d56Sopenharmony_ci 17497db96d56Sopenharmony_ci for key in tree: 17507db96d56Sopenharmony_ci self.touch_zone(key, tz_root) 17517db96d56Sopenharmony_ci 17527db96d56Sopenharmony_ci with self.tzpath_context([tz_root]): 17537db96d56Sopenharmony_ci with self.subTest(case_name): 17547db96d56Sopenharmony_ci actual = self.module.available_timezones() 17557db96d56Sopenharmony_ci self.assertEqual(actual, expected) 17567db96d56Sopenharmony_ci 17577db96d56Sopenharmony_ci def test_exclude_posixrules(self): 17587db96d56Sopenharmony_ci expected = { 17597db96d56Sopenharmony_ci "America/New_York", 17607db96d56Sopenharmony_ci "Europe/London", 17617db96d56Sopenharmony_ci } 17627db96d56Sopenharmony_ci 17637db96d56Sopenharmony_ci tree = list(expected) + ["posixrules"] 17647db96d56Sopenharmony_ci 17657db96d56Sopenharmony_ci with tempfile.TemporaryDirectory() as td: 17667db96d56Sopenharmony_ci for key in tree: 17677db96d56Sopenharmony_ci self.touch_zone(key, td) 17687db96d56Sopenharmony_ci 17697db96d56Sopenharmony_ci with self.tzpath_context([td]): 17707db96d56Sopenharmony_ci actual = self.module.available_timezones() 17717db96d56Sopenharmony_ci self.assertEqual(actual, expected) 17727db96d56Sopenharmony_ci 17737db96d56Sopenharmony_ci 17747db96d56Sopenharmony_ciclass CTestModule(TestModule): 17757db96d56Sopenharmony_ci module = c_zoneinfo 17767db96d56Sopenharmony_ci 17777db96d56Sopenharmony_ci 17787db96d56Sopenharmony_ciclass ExtensionBuiltTest(unittest.TestCase): 17797db96d56Sopenharmony_ci """Smoke test to ensure that the C and Python extensions are both tested. 17807db96d56Sopenharmony_ci 17817db96d56Sopenharmony_ci Because the intention is for the Python and C versions of ZoneInfo to 17827db96d56Sopenharmony_ci behave identically, these tests necessarily rely on implementation details, 17837db96d56Sopenharmony_ci so the tests may need to be adjusted if the implementations change. Do not 17847db96d56Sopenharmony_ci rely on these tests as an indication of stable properties of these classes. 17857db96d56Sopenharmony_ci """ 17867db96d56Sopenharmony_ci 17877db96d56Sopenharmony_ci def test_cache_location(self): 17887db96d56Sopenharmony_ci # The pure Python version stores caches on attributes, but the C 17897db96d56Sopenharmony_ci # extension stores them in C globals (at least for now) 17907db96d56Sopenharmony_ci self.assertFalse(hasattr(c_zoneinfo.ZoneInfo, "_weak_cache")) 17917db96d56Sopenharmony_ci self.assertTrue(hasattr(py_zoneinfo.ZoneInfo, "_weak_cache")) 17927db96d56Sopenharmony_ci 17937db96d56Sopenharmony_ci def test_gc_tracked(self): 17947db96d56Sopenharmony_ci # The pure Python version is tracked by the GC but (for now) the C 17957db96d56Sopenharmony_ci # version is not. 17967db96d56Sopenharmony_ci import gc 17977db96d56Sopenharmony_ci 17987db96d56Sopenharmony_ci self.assertTrue(gc.is_tracked(py_zoneinfo.ZoneInfo)) 17997db96d56Sopenharmony_ci self.assertFalse(gc.is_tracked(c_zoneinfo.ZoneInfo)) 18007db96d56Sopenharmony_ci 18017db96d56Sopenharmony_ci 18027db96d56Sopenharmony_ci@dataclasses.dataclass(frozen=True) 18037db96d56Sopenharmony_ciclass ZoneOffset: 18047db96d56Sopenharmony_ci tzname: str 18057db96d56Sopenharmony_ci utcoffset: timedelta 18067db96d56Sopenharmony_ci dst: timedelta = ZERO 18077db96d56Sopenharmony_ci 18087db96d56Sopenharmony_ci 18097db96d56Sopenharmony_ci@dataclasses.dataclass(frozen=True) 18107db96d56Sopenharmony_ciclass ZoneTransition: 18117db96d56Sopenharmony_ci transition: datetime 18127db96d56Sopenharmony_ci offset_before: ZoneOffset 18137db96d56Sopenharmony_ci offset_after: ZoneOffset 18147db96d56Sopenharmony_ci 18157db96d56Sopenharmony_ci @property 18167db96d56Sopenharmony_ci def transition_utc(self): 18177db96d56Sopenharmony_ci return (self.transition - self.offset_before.utcoffset).replace( 18187db96d56Sopenharmony_ci tzinfo=timezone.utc 18197db96d56Sopenharmony_ci ) 18207db96d56Sopenharmony_ci 18217db96d56Sopenharmony_ci @property 18227db96d56Sopenharmony_ci def fold(self): 18237db96d56Sopenharmony_ci """Whether this introduces a fold""" 18247db96d56Sopenharmony_ci return self.offset_before.utcoffset > self.offset_after.utcoffset 18257db96d56Sopenharmony_ci 18267db96d56Sopenharmony_ci @property 18277db96d56Sopenharmony_ci def gap(self): 18287db96d56Sopenharmony_ci """Whether this introduces a gap""" 18297db96d56Sopenharmony_ci return self.offset_before.utcoffset < self.offset_after.utcoffset 18307db96d56Sopenharmony_ci 18317db96d56Sopenharmony_ci @property 18327db96d56Sopenharmony_ci def delta(self): 18337db96d56Sopenharmony_ci return self.offset_after.utcoffset - self.offset_before.utcoffset 18347db96d56Sopenharmony_ci 18357db96d56Sopenharmony_ci @property 18367db96d56Sopenharmony_ci def anomaly_start(self): 18377db96d56Sopenharmony_ci if self.fold: 18387db96d56Sopenharmony_ci return self.transition + self.delta 18397db96d56Sopenharmony_ci else: 18407db96d56Sopenharmony_ci return self.transition 18417db96d56Sopenharmony_ci 18427db96d56Sopenharmony_ci @property 18437db96d56Sopenharmony_ci def anomaly_end(self): 18447db96d56Sopenharmony_ci if not self.fold: 18457db96d56Sopenharmony_ci return self.transition + self.delta 18467db96d56Sopenharmony_ci else: 18477db96d56Sopenharmony_ci return self.transition 18487db96d56Sopenharmony_ci 18497db96d56Sopenharmony_ci 18507db96d56Sopenharmony_ciclass ZoneInfoData: 18517db96d56Sopenharmony_ci def __init__(self, source_json, tzpath, v1=False): 18527db96d56Sopenharmony_ci self.tzpath = pathlib.Path(tzpath) 18537db96d56Sopenharmony_ci self.keys = [] 18547db96d56Sopenharmony_ci self.v1 = v1 18557db96d56Sopenharmony_ci self._populate_tzpath(source_json) 18567db96d56Sopenharmony_ci 18577db96d56Sopenharmony_ci def path_from_key(self, key): 18587db96d56Sopenharmony_ci return self.tzpath / key 18597db96d56Sopenharmony_ci 18607db96d56Sopenharmony_ci def _populate_tzpath(self, source_json): 18617db96d56Sopenharmony_ci with open(source_json, "rb") as f: 18627db96d56Sopenharmony_ci zoneinfo_dict = json.load(f) 18637db96d56Sopenharmony_ci 18647db96d56Sopenharmony_ci zoneinfo_data = zoneinfo_dict["data"] 18657db96d56Sopenharmony_ci 18667db96d56Sopenharmony_ci for key, value in zoneinfo_data.items(): 18677db96d56Sopenharmony_ci self.keys.append(key) 18687db96d56Sopenharmony_ci raw_data = self._decode_text(value) 18697db96d56Sopenharmony_ci 18707db96d56Sopenharmony_ci if self.v1: 18717db96d56Sopenharmony_ci data = self._convert_to_v1(raw_data) 18727db96d56Sopenharmony_ci else: 18737db96d56Sopenharmony_ci data = raw_data 18747db96d56Sopenharmony_ci 18757db96d56Sopenharmony_ci destination = self.path_from_key(key) 18767db96d56Sopenharmony_ci destination.parent.mkdir(exist_ok=True, parents=True) 18777db96d56Sopenharmony_ci with open(destination, "wb") as f: 18787db96d56Sopenharmony_ci f.write(data) 18797db96d56Sopenharmony_ci 18807db96d56Sopenharmony_ci def _decode_text(self, contents): 18817db96d56Sopenharmony_ci raw_data = b"".join(map(str.encode, contents)) 18827db96d56Sopenharmony_ci decoded = base64.b85decode(raw_data) 18837db96d56Sopenharmony_ci 18847db96d56Sopenharmony_ci return lzma.decompress(decoded) 18857db96d56Sopenharmony_ci 18867db96d56Sopenharmony_ci def _convert_to_v1(self, contents): 18877db96d56Sopenharmony_ci assert contents[0:4] == b"TZif", "Invalid TZif data found!" 18887db96d56Sopenharmony_ci version = int(contents[4:5]) 18897db96d56Sopenharmony_ci 18907db96d56Sopenharmony_ci header_start = 4 + 16 18917db96d56Sopenharmony_ci header_end = header_start + 24 # 6l == 24 bytes 18927db96d56Sopenharmony_ci assert version >= 2, "Version 1 file found: no conversion necessary" 18937db96d56Sopenharmony_ci isutcnt, isstdcnt, leapcnt, timecnt, typecnt, charcnt = struct.unpack( 18947db96d56Sopenharmony_ci ">6l", contents[header_start:header_end] 18957db96d56Sopenharmony_ci ) 18967db96d56Sopenharmony_ci 18977db96d56Sopenharmony_ci file_size = ( 18987db96d56Sopenharmony_ci timecnt * 5 18997db96d56Sopenharmony_ci + typecnt * 6 19007db96d56Sopenharmony_ci + charcnt 19017db96d56Sopenharmony_ci + leapcnt * 8 19027db96d56Sopenharmony_ci + isstdcnt 19037db96d56Sopenharmony_ci + isutcnt 19047db96d56Sopenharmony_ci ) 19057db96d56Sopenharmony_ci file_size += header_end 19067db96d56Sopenharmony_ci out = b"TZif" + b"\x00" + contents[5:file_size] 19077db96d56Sopenharmony_ci 19087db96d56Sopenharmony_ci assert ( 19097db96d56Sopenharmony_ci contents[file_size : (file_size + 4)] == b"TZif" 19107db96d56Sopenharmony_ci ), "Version 2 file not truncated at Version 2 header" 19117db96d56Sopenharmony_ci 19127db96d56Sopenharmony_ci return out 19137db96d56Sopenharmony_ci 19147db96d56Sopenharmony_ci 19157db96d56Sopenharmony_ciclass ZoneDumpData: 19167db96d56Sopenharmony_ci @classmethod 19177db96d56Sopenharmony_ci def transition_keys(cls): 19187db96d56Sopenharmony_ci return cls._get_zonedump().keys() 19197db96d56Sopenharmony_ci 19207db96d56Sopenharmony_ci @classmethod 19217db96d56Sopenharmony_ci def load_transition_examples(cls, key): 19227db96d56Sopenharmony_ci return cls._get_zonedump()[key] 19237db96d56Sopenharmony_ci 19247db96d56Sopenharmony_ci @classmethod 19257db96d56Sopenharmony_ci def fixed_offset_zones(cls): 19267db96d56Sopenharmony_ci if not cls._FIXED_OFFSET_ZONES: 19277db96d56Sopenharmony_ci cls._populate_fixed_offsets() 19287db96d56Sopenharmony_ci 19297db96d56Sopenharmony_ci return cls._FIXED_OFFSET_ZONES.items() 19307db96d56Sopenharmony_ci 19317db96d56Sopenharmony_ci @classmethod 19327db96d56Sopenharmony_ci def _get_zonedump(cls): 19337db96d56Sopenharmony_ci if not cls._ZONEDUMP_DATA: 19347db96d56Sopenharmony_ci cls._populate_zonedump_data() 19357db96d56Sopenharmony_ci return cls._ZONEDUMP_DATA 19367db96d56Sopenharmony_ci 19377db96d56Sopenharmony_ci @classmethod 19387db96d56Sopenharmony_ci def _populate_fixed_offsets(cls): 19397db96d56Sopenharmony_ci cls._FIXED_OFFSET_ZONES = { 19407db96d56Sopenharmony_ci "UTC": ZoneOffset("UTC", ZERO, ZERO), 19417db96d56Sopenharmony_ci } 19427db96d56Sopenharmony_ci 19437db96d56Sopenharmony_ci @classmethod 19447db96d56Sopenharmony_ci def _populate_zonedump_data(cls): 19457db96d56Sopenharmony_ci def _Africa_Abidjan(): 19467db96d56Sopenharmony_ci LMT = ZoneOffset("LMT", timedelta(seconds=-968)) 19477db96d56Sopenharmony_ci GMT = ZoneOffset("GMT", ZERO) 19487db96d56Sopenharmony_ci 19497db96d56Sopenharmony_ci return [ 19507db96d56Sopenharmony_ci ZoneTransition(datetime(1912, 1, 1), LMT, GMT), 19517db96d56Sopenharmony_ci ] 19527db96d56Sopenharmony_ci 19537db96d56Sopenharmony_ci def _Africa_Casablanca(): 19547db96d56Sopenharmony_ci P00_s = ZoneOffset("+00", ZERO, ZERO) 19557db96d56Sopenharmony_ci P01_d = ZoneOffset("+01", ONE_H, ONE_H) 19567db96d56Sopenharmony_ci P00_d = ZoneOffset("+00", ZERO, -ONE_H) 19577db96d56Sopenharmony_ci P01_s = ZoneOffset("+01", ONE_H, ZERO) 19587db96d56Sopenharmony_ci 19597db96d56Sopenharmony_ci return [ 19607db96d56Sopenharmony_ci # Morocco sometimes pauses DST during Ramadan 19617db96d56Sopenharmony_ci ZoneTransition(datetime(2018, 3, 25, 2), P00_s, P01_d), 19627db96d56Sopenharmony_ci ZoneTransition(datetime(2018, 5, 13, 3), P01_d, P00_s), 19637db96d56Sopenharmony_ci ZoneTransition(datetime(2018, 6, 17, 2), P00_s, P01_d), 19647db96d56Sopenharmony_ci # On October 28th Morocco set standard time to +01, 19657db96d56Sopenharmony_ci # with negative DST only during Ramadan 19667db96d56Sopenharmony_ci ZoneTransition(datetime(2018, 10, 28, 3), P01_d, P01_s), 19677db96d56Sopenharmony_ci ZoneTransition(datetime(2019, 5, 5, 3), P01_s, P00_d), 19687db96d56Sopenharmony_ci ZoneTransition(datetime(2019, 6, 9, 2), P00_d, P01_s), 19697db96d56Sopenharmony_ci ] 19707db96d56Sopenharmony_ci 19717db96d56Sopenharmony_ci def _America_Los_Angeles(): 19727db96d56Sopenharmony_ci LMT = ZoneOffset("LMT", timedelta(seconds=-28378), ZERO) 19737db96d56Sopenharmony_ci PST = ZoneOffset("PST", timedelta(hours=-8), ZERO) 19747db96d56Sopenharmony_ci PDT = ZoneOffset("PDT", timedelta(hours=-7), ONE_H) 19757db96d56Sopenharmony_ci PWT = ZoneOffset("PWT", timedelta(hours=-7), ONE_H) 19767db96d56Sopenharmony_ci PPT = ZoneOffset("PPT", timedelta(hours=-7), ONE_H) 19777db96d56Sopenharmony_ci 19787db96d56Sopenharmony_ci return [ 19797db96d56Sopenharmony_ci ZoneTransition(datetime(1883, 11, 18, 12, 7, 2), LMT, PST), 19807db96d56Sopenharmony_ci ZoneTransition(datetime(1918, 3, 31, 2), PST, PDT), 19817db96d56Sopenharmony_ci ZoneTransition(datetime(1918, 3, 31, 2), PST, PDT), 19827db96d56Sopenharmony_ci ZoneTransition(datetime(1918, 10, 27, 2), PDT, PST), 19837db96d56Sopenharmony_ci # Transition to Pacific War Time 19847db96d56Sopenharmony_ci ZoneTransition(datetime(1942, 2, 9, 2), PST, PWT), 19857db96d56Sopenharmony_ci # Transition from Pacific War Time to Pacific Peace Time 19867db96d56Sopenharmony_ci ZoneTransition(datetime(1945, 8, 14, 16), PWT, PPT), 19877db96d56Sopenharmony_ci ZoneTransition(datetime(1945, 9, 30, 2), PPT, PST), 19887db96d56Sopenharmony_ci ZoneTransition(datetime(2015, 3, 8, 2), PST, PDT), 19897db96d56Sopenharmony_ci ZoneTransition(datetime(2015, 11, 1, 2), PDT, PST), 19907db96d56Sopenharmony_ci # After 2038: Rules continue indefinitely 19917db96d56Sopenharmony_ci ZoneTransition(datetime(2450, 3, 13, 2), PST, PDT), 19927db96d56Sopenharmony_ci ZoneTransition(datetime(2450, 11, 6, 2), PDT, PST), 19937db96d56Sopenharmony_ci ] 19947db96d56Sopenharmony_ci 19957db96d56Sopenharmony_ci def _America_Santiago(): 19967db96d56Sopenharmony_ci LMT = ZoneOffset("LMT", timedelta(seconds=-16966), ZERO) 19977db96d56Sopenharmony_ci SMT = ZoneOffset("SMT", timedelta(seconds=-16966), ZERO) 19987db96d56Sopenharmony_ci N05 = ZoneOffset("-05", timedelta(seconds=-18000), ZERO) 19997db96d56Sopenharmony_ci N04 = ZoneOffset("-04", timedelta(seconds=-14400), ZERO) 20007db96d56Sopenharmony_ci N03 = ZoneOffset("-03", timedelta(seconds=-10800), ONE_H) 20017db96d56Sopenharmony_ci 20027db96d56Sopenharmony_ci return [ 20037db96d56Sopenharmony_ci ZoneTransition(datetime(1890, 1, 1), LMT, SMT), 20047db96d56Sopenharmony_ci ZoneTransition(datetime(1910, 1, 10), SMT, N05), 20057db96d56Sopenharmony_ci ZoneTransition(datetime(1916, 7, 1), N05, SMT), 20067db96d56Sopenharmony_ci ZoneTransition(datetime(2008, 3, 30), N03, N04), 20077db96d56Sopenharmony_ci ZoneTransition(datetime(2008, 10, 12), N04, N03), 20087db96d56Sopenharmony_ci ZoneTransition(datetime(2040, 4, 8), N03, N04), 20097db96d56Sopenharmony_ci ZoneTransition(datetime(2040, 9, 2), N04, N03), 20107db96d56Sopenharmony_ci ] 20117db96d56Sopenharmony_ci 20127db96d56Sopenharmony_ci def _Asia_Tokyo(): 20137db96d56Sopenharmony_ci JST = ZoneOffset("JST", timedelta(seconds=32400), ZERO) 20147db96d56Sopenharmony_ci JDT = ZoneOffset("JDT", timedelta(seconds=36000), ONE_H) 20157db96d56Sopenharmony_ci 20167db96d56Sopenharmony_ci # Japan had DST from 1948 to 1951, and it was unusual in that 20177db96d56Sopenharmony_ci # the transition from DST to STD occurred at 25:00, and is 20187db96d56Sopenharmony_ci # denominated as such in the time zone database 20197db96d56Sopenharmony_ci return [ 20207db96d56Sopenharmony_ci ZoneTransition(datetime(1948, 5, 2), JST, JDT), 20217db96d56Sopenharmony_ci ZoneTransition(datetime(1948, 9, 12, 1), JDT, JST), 20227db96d56Sopenharmony_ci ZoneTransition(datetime(1951, 9, 9, 1), JDT, JST), 20237db96d56Sopenharmony_ci ] 20247db96d56Sopenharmony_ci 20257db96d56Sopenharmony_ci def _Australia_Sydney(): 20267db96d56Sopenharmony_ci LMT = ZoneOffset("LMT", timedelta(seconds=36292), ZERO) 20277db96d56Sopenharmony_ci AEST = ZoneOffset("AEST", timedelta(seconds=36000), ZERO) 20287db96d56Sopenharmony_ci AEDT = ZoneOffset("AEDT", timedelta(seconds=39600), ONE_H) 20297db96d56Sopenharmony_ci 20307db96d56Sopenharmony_ci return [ 20317db96d56Sopenharmony_ci ZoneTransition(datetime(1895, 2, 1), LMT, AEST), 20327db96d56Sopenharmony_ci ZoneTransition(datetime(1917, 1, 1, 0, 1), AEST, AEDT), 20337db96d56Sopenharmony_ci ZoneTransition(datetime(1917, 3, 25, 2), AEDT, AEST), 20347db96d56Sopenharmony_ci ZoneTransition(datetime(2012, 4, 1, 3), AEDT, AEST), 20357db96d56Sopenharmony_ci ZoneTransition(datetime(2012, 10, 7, 2), AEST, AEDT), 20367db96d56Sopenharmony_ci ZoneTransition(datetime(2040, 4, 1, 3), AEDT, AEST), 20377db96d56Sopenharmony_ci ZoneTransition(datetime(2040, 10, 7, 2), AEST, AEDT), 20387db96d56Sopenharmony_ci ] 20397db96d56Sopenharmony_ci 20407db96d56Sopenharmony_ci def _Europe_Dublin(): 20417db96d56Sopenharmony_ci LMT = ZoneOffset("LMT", timedelta(seconds=-1500), ZERO) 20427db96d56Sopenharmony_ci DMT = ZoneOffset("DMT", timedelta(seconds=-1521), ZERO) 20437db96d56Sopenharmony_ci IST_0 = ZoneOffset("IST", timedelta(seconds=2079), ONE_H) 20447db96d56Sopenharmony_ci GMT_0 = ZoneOffset("GMT", ZERO, ZERO) 20457db96d56Sopenharmony_ci BST = ZoneOffset("BST", ONE_H, ONE_H) 20467db96d56Sopenharmony_ci GMT_1 = ZoneOffset("GMT", ZERO, -ONE_H) 20477db96d56Sopenharmony_ci IST_1 = ZoneOffset("IST", ONE_H, ZERO) 20487db96d56Sopenharmony_ci 20497db96d56Sopenharmony_ci return [ 20507db96d56Sopenharmony_ci ZoneTransition(datetime(1880, 8, 2, 0), LMT, DMT), 20517db96d56Sopenharmony_ci ZoneTransition(datetime(1916, 5, 21, 2), DMT, IST_0), 20527db96d56Sopenharmony_ci ZoneTransition(datetime(1916, 10, 1, 3), IST_0, GMT_0), 20537db96d56Sopenharmony_ci ZoneTransition(datetime(1917, 4, 8, 2), GMT_0, BST), 20547db96d56Sopenharmony_ci ZoneTransition(datetime(2016, 3, 27, 1), GMT_1, IST_1), 20557db96d56Sopenharmony_ci ZoneTransition(datetime(2016, 10, 30, 2), IST_1, GMT_1), 20567db96d56Sopenharmony_ci ZoneTransition(datetime(2487, 3, 30, 1), GMT_1, IST_1), 20577db96d56Sopenharmony_ci ZoneTransition(datetime(2487, 10, 26, 2), IST_1, GMT_1), 20587db96d56Sopenharmony_ci ] 20597db96d56Sopenharmony_ci 20607db96d56Sopenharmony_ci def _Europe_Lisbon(): 20617db96d56Sopenharmony_ci WET = ZoneOffset("WET", ZERO, ZERO) 20627db96d56Sopenharmony_ci WEST = ZoneOffset("WEST", ONE_H, ONE_H) 20637db96d56Sopenharmony_ci CET = ZoneOffset("CET", ONE_H, ZERO) 20647db96d56Sopenharmony_ci CEST = ZoneOffset("CEST", timedelta(seconds=7200), ONE_H) 20657db96d56Sopenharmony_ci 20667db96d56Sopenharmony_ci return [ 20677db96d56Sopenharmony_ci ZoneTransition(datetime(1992, 3, 29, 1), WET, WEST), 20687db96d56Sopenharmony_ci ZoneTransition(datetime(1992, 9, 27, 2), WEST, CET), 20697db96d56Sopenharmony_ci ZoneTransition(datetime(1993, 3, 28, 2), CET, CEST), 20707db96d56Sopenharmony_ci ZoneTransition(datetime(1993, 9, 26, 3), CEST, CET), 20717db96d56Sopenharmony_ci ZoneTransition(datetime(1996, 3, 31, 2), CET, WEST), 20727db96d56Sopenharmony_ci ZoneTransition(datetime(1996, 10, 27, 2), WEST, WET), 20737db96d56Sopenharmony_ci ] 20747db96d56Sopenharmony_ci 20757db96d56Sopenharmony_ci def _Europe_London(): 20767db96d56Sopenharmony_ci LMT = ZoneOffset("LMT", timedelta(seconds=-75), ZERO) 20777db96d56Sopenharmony_ci GMT = ZoneOffset("GMT", ZERO, ZERO) 20787db96d56Sopenharmony_ci BST = ZoneOffset("BST", ONE_H, ONE_H) 20797db96d56Sopenharmony_ci 20807db96d56Sopenharmony_ci return [ 20817db96d56Sopenharmony_ci ZoneTransition(datetime(1847, 12, 1), LMT, GMT), 20827db96d56Sopenharmony_ci ZoneTransition(datetime(2005, 3, 27, 1), GMT, BST), 20837db96d56Sopenharmony_ci ZoneTransition(datetime(2005, 10, 30, 2), BST, GMT), 20847db96d56Sopenharmony_ci ZoneTransition(datetime(2043, 3, 29, 1), GMT, BST), 20857db96d56Sopenharmony_ci ZoneTransition(datetime(2043, 10, 25, 2), BST, GMT), 20867db96d56Sopenharmony_ci ] 20877db96d56Sopenharmony_ci 20887db96d56Sopenharmony_ci def _Pacific_Kiritimati(): 20897db96d56Sopenharmony_ci LMT = ZoneOffset("LMT", timedelta(seconds=-37760), ZERO) 20907db96d56Sopenharmony_ci N1040 = ZoneOffset("-1040", timedelta(seconds=-38400), ZERO) 20917db96d56Sopenharmony_ci N10 = ZoneOffset("-10", timedelta(seconds=-36000), ZERO) 20927db96d56Sopenharmony_ci P14 = ZoneOffset("+14", timedelta(seconds=50400), ZERO) 20937db96d56Sopenharmony_ci 20947db96d56Sopenharmony_ci # This is literally every transition in Christmas Island history 20957db96d56Sopenharmony_ci return [ 20967db96d56Sopenharmony_ci ZoneTransition(datetime(1901, 1, 1), LMT, N1040), 20977db96d56Sopenharmony_ci ZoneTransition(datetime(1979, 10, 1), N1040, N10), 20987db96d56Sopenharmony_ci # They skipped December 31, 1994 20997db96d56Sopenharmony_ci ZoneTransition(datetime(1994, 12, 31), N10, P14), 21007db96d56Sopenharmony_ci ] 21017db96d56Sopenharmony_ci 21027db96d56Sopenharmony_ci cls._ZONEDUMP_DATA = { 21037db96d56Sopenharmony_ci "Africa/Abidjan": _Africa_Abidjan(), 21047db96d56Sopenharmony_ci "Africa/Casablanca": _Africa_Casablanca(), 21057db96d56Sopenharmony_ci "America/Los_Angeles": _America_Los_Angeles(), 21067db96d56Sopenharmony_ci "America/Santiago": _America_Santiago(), 21077db96d56Sopenharmony_ci "Australia/Sydney": _Australia_Sydney(), 21087db96d56Sopenharmony_ci "Asia/Tokyo": _Asia_Tokyo(), 21097db96d56Sopenharmony_ci "Europe/Dublin": _Europe_Dublin(), 21107db96d56Sopenharmony_ci "Europe/Lisbon": _Europe_Lisbon(), 21117db96d56Sopenharmony_ci "Europe/London": _Europe_London(), 21127db96d56Sopenharmony_ci "Pacific/Kiritimati": _Pacific_Kiritimati(), 21137db96d56Sopenharmony_ci } 21147db96d56Sopenharmony_ci 21157db96d56Sopenharmony_ci _ZONEDUMP_DATA = None 21167db96d56Sopenharmony_ci _FIXED_OFFSET_ZONES = None 21177db96d56Sopenharmony_ci 21187db96d56Sopenharmony_ci 21197db96d56Sopenharmony_ciif __name__ == '__main__': 21207db96d56Sopenharmony_ci unittest.main() 2121