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