summaryrefslogtreecommitdiffstats
path: root/venv/lib/python3.9/site-packages/tzlocal/win32.py
blob: 4a6cc83d3a91e717ff259acee7620f05aecdaebf (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
from datetime import datetime
import pytz_deprecation_shim as pds

try:
    import _winreg as winreg
except ImportError:
    import winreg

from tzlocal.windows_tz import win_tz
from tzlocal import utils

_cache_tz = None
_cache_tz_name = None


def valuestodict(key):
    """Convert a registry key's values to a dictionary."""
    result = {}
    size = winreg.QueryInfoKey(key)[1]
    for i in range(size):
        data = winreg.EnumValue(key, i)
        result[data[0]] = data[1]
    return result


def _get_dst_info(tz):
    # Find the offset for when it doesn't have DST:
    dst_offset = std_offset = None
    has_dst = False
    year = datetime.now().year
    for dt in (datetime(year, 1, 1), datetime(year, 6, 1)):
        if tz.dst(dt).total_seconds() == 0.0:
            # OK, no DST during winter, get this offset
            std_offset = tz.utcoffset(dt).total_seconds()
        else:
            has_dst = True

    return has_dst, std_offset, dst_offset


def _get_localzone_name():
    # Windows is special. It has unique time zone names (in several
    # meanings of the word) available, but unfortunately, they can be
    # translated to the language of the operating system, so we need to
    # do a backwards lookup, by going through all time zones and see which
    # one matches.
    tzenv = utils._tz_name_from_env()
    if tzenv:
        return tzenv

    handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)

    TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
    localtz = winreg.OpenKey(handle, TZLOCALKEYNAME)
    keyvalues = valuestodict(localtz)
    localtz.Close()

    if "TimeZoneKeyName" in keyvalues:
        # Windows 7 and later

        # For some reason this returns a string with loads of NUL bytes at
        # least on some systems. I don't know if this is a bug somewhere, I
        # just work around it.
        tzkeyname = keyvalues["TimeZoneKeyName"].split("\x00", 1)[0]
    else:
        # Don't support XP any longer
        raise LookupError("Can not find Windows timezone configuration")

    timezone = win_tz.get(tzkeyname)
    if timezone is None:
        # Nope, that didn't work. Try adding "Standard Time",
        # it seems to work a lot of times:
        timezone = win_tz.get(tzkeyname + " Standard Time")

    # Return what we have.
    if timezone is None:
        raise utils.ZoneInfoNotFoundError(tzkeyname)

    if keyvalues.get("DynamicDaylightTimeDisabled", 0) == 1:
        # DST is disabled, so don't return the timezone name,
        # instead return Etc/GMT+offset

        tz = pds.timezone(timezone)
        has_dst, std_offset, dst_offset = _get_dst_info(tz)
        if not has_dst:
            # The DST is turned off in the windows configuration,
            # but this timezone doesn't have DST so it doesn't matter
            return timezone

        if std_offset is None:
            raise utils.ZoneInfoNotFoundError(
                f"{tzkeyname} claims to not have a non-DST time!?"
            )

        if std_offset % 3600:
            # I can't convert this to an hourly offset
            raise utils.ZoneInfoNotFoundError(
                f"tzlocal can't support disabling DST in the {timezone} zone."
            )

        # This has whole hours as offset, return it as Etc/GMT
        return f"Etc/GMT{-std_offset//3600:+.0f}"

    return timezone


def get_localzone_name():
    """Get the zoneinfo timezone name that matches the Windows-configured timezone."""
    global _cache_tz_name
    if _cache_tz_name is None:
        _cache_tz_name = _get_localzone_name()

    return _cache_tz_name


def get_localzone():
    """Returns the zoneinfo-based tzinfo object that matches the Windows-configured timezone."""

    global _cache_tz
    if _cache_tz is None:
        _cache_tz = pds.timezone(get_localzone_name())

    if not utils._tz_name_from_env():
        # If the timezone does NOT come from a TZ environment variable,
        # verify that it's correct. If it's from the environment,
        # we accept it, this is so you can run tests with different timezones.
        utils.assert_tz_offset(_cache_tz)

    return _cache_tz


def reload_localzone():
    """Reload the cached localzone. You need to call this if the timezone has changed."""
    global _cache_tz
    global _cache_tz_name
    _cache_tz_name = _get_localzone_name()
    _cache_tz = pds.timezone(_cache_tz_name)
    utils.assert_tz_offset(_cache_tz)
    return _cache_tz