Skip to content

Commit deb9ede

Browse files
committed
Remove dependency on tzlocal
1 parent 4fe4f2c commit deb9ede

File tree

6 files changed

+222
-4
lines changed

6 files changed

+222
-4
lines changed

clock

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
from __future__ import unicode_literals
33

44
import glob
5+
import json
56
import os
67

8+
from babel.core import get_global
79
from babel.plural import (
810
PluralRule, compile_zero,
911
_GettextCompiler, _binary_compiler, _unary_compiler
@@ -236,9 +238,34 @@ class LocaleRecreate(Command):
236238
self.call('locale:create', [('locales', locales)])
237239

238240

241+
class WindowsTzDump(Command):
242+
"""
243+
Dumps the mapping of Windows timezones to IANA timezones.
244+
245+
windows:tz:dump
246+
"""
247+
248+
MAPPING_DIR = os.path.join('pendulum', 'tz', 'data')
249+
250+
def handle(self):
251+
raw_tznames = get_global('windows_zone_mapping')
252+
sorted_names = sorted(list(raw_tznames.keys()))
253+
254+
tznames = {}
255+
for name in sorted_names:
256+
tznames[name] = raw_tznames[name]
257+
258+
mapping = json.dumps(tznames, indent=4)
259+
mapping = 'windows_timezones = ' + mapping.replace('"', "'") + '\n'
260+
261+
with open(os.path.join(self.MAPPING_DIR, 'windows.py'), 'w') as f:
262+
f.write(mapping)
263+
264+
239265
app = Application('Clock', __version__)
240266
app.add(LocaleCreate())
241267
app.add(LocaleRecreate())
268+
app.add(WindowsTzDump())
242269

243270

244271
if __name__ == '__main__':

pendulum/tz/data/__init__.py

Whitespace-only changes.

pendulum/tz/data/windows.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
windows_timezones = {
2+
'AUS Central Standard Time': 'Australia/Darwin',
3+
'AUS Eastern Standard Time': 'Australia/Sydney',
4+
'Afghanistan Standard Time': 'Asia/Kabul',
5+
'Alaskan Standard Time': 'America/Anchorage',
6+
'Arab Standard Time': 'Asia/Riyadh',
7+
'Arabian Standard Time': 'Asia/Dubai',
8+
'Arabic Standard Time': 'Asia/Baghdad',
9+
'Argentina Standard Time': 'America/Buenos_Aires',
10+
'Atlantic Standard Time': 'America/Halifax',
11+
'Azerbaijan Standard Time': 'Asia/Baku',
12+
'Azores Standard Time': 'Atlantic/Azores',
13+
'Bahia Standard Time': 'America/Bahia',
14+
'Bangladesh Standard Time': 'Asia/Dhaka',
15+
'Belarus Standard Time': 'Europe/Minsk',
16+
'Canada Central Standard Time': 'America/Regina',
17+
'Cape Verde Standard Time': 'Atlantic/Cape_Verde',
18+
'Caucasus Standard Time': 'Asia/Yerevan',
19+
'Cen. Australia Standard Time': 'Australia/Adelaide',
20+
'Central America Standard Time': 'America/Guatemala',
21+
'Central Asia Standard Time': 'Asia/Almaty',
22+
'Central Brazilian Standard Time': 'America/Cuiaba',
23+
'Central Europe Standard Time': 'Europe/Budapest',
24+
'Central European Standard Time': 'Europe/Warsaw',
25+
'Central Pacific Standard Time': 'Pacific/Guadalcanal',
26+
'Central Standard Time': 'America/Chicago',
27+
'Central Standard Time (Mexico)': 'America/Mexico_City',
28+
'China Standard Time': 'Asia/Shanghai',
29+
'Dateline Standard Time': 'Etc/GMT+12',
30+
'E. Africa Standard Time': 'Africa/Nairobi',
31+
'E. Australia Standard Time': 'Australia/Brisbane',
32+
'E. Europe Standard Time': 'Europe/Chisinau',
33+
'E. South America Standard Time': 'America/Sao_Paulo',
34+
'Eastern Standard Time': 'America/New_York',
35+
'Eastern Standard Time (Mexico)': 'America/Cancun',
36+
'Egypt Standard Time': 'Africa/Cairo',
37+
'Ekaterinburg Standard Time': 'Asia/Yekaterinburg',
38+
'FLE Standard Time': 'Europe/Kiev',
39+
'Fiji Standard Time': 'Pacific/Fiji',
40+
'GMT Standard Time': 'Europe/London',
41+
'GTB Standard Time': 'Europe/Bucharest',
42+
'Georgian Standard Time': 'Asia/Tbilisi',
43+
'Greenland Standard Time': 'America/Godthab',
44+
'Greenwich Standard Time': 'Atlantic/Reykjavik',
45+
'Hawaiian Standard Time': 'Pacific/Honolulu',
46+
'India Standard Time': 'Asia/Calcutta',
47+
'Iran Standard Time': 'Asia/Tehran',
48+
'Israel Standard Time': 'Asia/Jerusalem',
49+
'Jordan Standard Time': 'Asia/Amman',
50+
'Kaliningrad Standard Time': 'Europe/Kaliningrad',
51+
'Korea Standard Time': 'Asia/Seoul',
52+
'Libya Standard Time': 'Africa/Tripoli',
53+
'Line Islands Standard Time': 'Pacific/Kiritimati',
54+
'Magadan Standard Time': 'Asia/Magadan',
55+
'Mauritius Standard Time': 'Indian/Mauritius',
56+
'Middle East Standard Time': 'Asia/Beirut',
57+
'Montevideo Standard Time': 'America/Montevideo',
58+
'Morocco Standard Time': 'Africa/Casablanca',
59+
'Mountain Standard Time': 'America/Denver',
60+
'Mountain Standard Time (Mexico)': 'America/Chihuahua',
61+
'Myanmar Standard Time': 'Asia/Rangoon',
62+
'N. Central Asia Standard Time': 'Asia/Novosibirsk',
63+
'Namibia Standard Time': 'Africa/Windhoek',
64+
'Nepal Standard Time': 'Asia/Katmandu',
65+
'New Zealand Standard Time': 'Pacific/Auckland',
66+
'Newfoundland Standard Time': 'America/St_Johns',
67+
'North Asia East Standard Time': 'Asia/Irkutsk',
68+
'North Asia Standard Time': 'Asia/Krasnoyarsk',
69+
'North Korea Standard Time': 'Asia/Pyongyang',
70+
'Pacific SA Standard Time': 'America/Santiago',
71+
'Pacific Standard Time': 'America/Los_Angeles',
72+
'Pakistan Standard Time': 'Asia/Karachi',
73+
'Paraguay Standard Time': 'America/Asuncion',
74+
'Romance Standard Time': 'Europe/Paris',
75+
'Russia Time Zone 10': 'Asia/Srednekolymsk',
76+
'Russia Time Zone 11': 'Asia/Kamchatka',
77+
'Russia Time Zone 3': 'Europe/Samara',
78+
'Russian Standard Time': 'Europe/Moscow',
79+
'SA Eastern Standard Time': 'America/Cayenne',
80+
'SA Pacific Standard Time': 'America/Bogota',
81+
'SA Western Standard Time': 'America/La_Paz',
82+
'SE Asia Standard Time': 'Asia/Bangkok',
83+
'Samoa Standard Time': 'Pacific/Apia',
84+
'Singapore Standard Time': 'Asia/Singapore',
85+
'South Africa Standard Time': 'Africa/Johannesburg',
86+
'Sri Lanka Standard Time': 'Asia/Colombo',
87+
'Syria Standard Time': 'Asia/Damascus',
88+
'Taipei Standard Time': 'Asia/Taipei',
89+
'Tasmania Standard Time': 'Australia/Hobart',
90+
'Tokyo Standard Time': 'Asia/Tokyo',
91+
'Tonga Standard Time': 'Pacific/Tongatapu',
92+
'Turkey Standard Time': 'Europe/Istanbul',
93+
'US Eastern Standard Time': 'America/Indianapolis',
94+
'US Mountain Standard Time': 'America/Phoenix',
95+
'UTC': 'Etc/GMT',
96+
'UTC+12': 'Etc/GMT-12',
97+
'UTC-02': 'Etc/GMT+2',
98+
'UTC-11': 'Etc/GMT+11',
99+
'Ulaanbaatar Standard Time': 'Asia/Ulaanbaatar',
100+
'Venezuela Standard Time': 'America/Caracas',
101+
'Vladivostok Standard Time': 'Asia/Vladivostok',
102+
'W. Australia Standard Time': 'Australia/Perth',
103+
'W. Central Africa Standard Time': 'Africa/Lagos',
104+
'W. Europe Standard Time': 'Europe/Berlin',
105+
'West Asia Standard Time': 'Asia/Tashkent',
106+
'West Pacific Standard Time': 'Pacific/Port_Moresby',
107+
'Yakutsk Standard Time': 'Asia/Yakutsk'
108+
}

pendulum/tz/local_timezone.py

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,84 @@ def _get_system_timezone(): # type: () -> Timezone
5151

5252

5353
def _get_windows_timezone(): # type: () -> Timezone
54-
from tzlocal.win32 import get_localzone_name
54+
import winreg
55+
56+
from .data.windows import windows_timezones
57+
58+
# Windows is special. It has unique time zone names (in several
59+
# meanings of the word) available, but unfortunately, they can be
60+
# translated to the language of the operating system, so we need to
61+
# do a backwards lookup, by going through all time zones and see which
62+
# one matches.
63+
handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
64+
65+
tz_local_key_name = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
66+
localtz = winreg.OpenKey(handle, tz_local_key_name)
67+
68+
timezone_info = {}
69+
size = winreg.QueryInfoKey(localtz)[1]
70+
for i in range(size):
71+
data = winreg.EnumValue(localtz, i)
72+
timezone_info[data[0]] = data[1]
73+
74+
localtz.Close()
75+
76+
if 'TimeZoneKeyName' in timezone_info:
77+
# Windows 7 (and Vista?)
78+
79+
# For some reason this returns a string with loads of NUL bytes at
80+
# least on some systems. I don't know if this is a bug somewhere, I
81+
# just work around it.
82+
tzkeyname = timezone_info['TimeZoneKeyName'].split('\x00', 1)[0]
83+
else:
84+
# Windows 2000 or XP
85+
86+
# This is the localized name:
87+
tzwin = timezone_info['StandardName']
88+
89+
# Open the list of timezones to look up the real name:
90+
tz_key_name = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"
91+
tzkey = winreg.OpenKey(handle, tz_key_name)
92+
93+
# Now, match this value to Time Zone information
94+
tzkeyname = None
95+
for i in range(winreg.QueryInfoKey(tzkey)[0]):
96+
subkey = winreg.EnumKey(tzkey, i)
97+
sub = winreg.OpenKey(tzkey, subkey)
98+
99+
info = {}
100+
size = winreg.QueryInfoKey(sub)[1]
101+
for i in range(size):
102+
data = winreg.EnumValue(sub, i)
103+
info[data[0]] = data[1]
104+
105+
sub.Close()
106+
try:
107+
if info['Std'] == tzwin:
108+
tzkeyname = subkey
109+
break
110+
except KeyError:
111+
# This timezone didn't have proper configuration.
112+
# Ignore it.
113+
pass
114+
115+
tzkey.Close()
116+
handle.Close()
117+
118+
if tzkeyname is None:
119+
raise LookupError('Can not find Windows timezone configuration')
120+
121+
timezone = windows_timezones.get(tzkeyname)
122+
if timezone is None:
123+
# Nope, that didn't work. Try adding "Standard Time",
124+
# it seems to work a lot of times:
125+
timezone = windows_timezones.get(tzkeyname + " Standard Time")
126+
127+
# Return what we have.
128+
if timezone is None:
129+
raise LookupError('Unable to find timezone ' + tzkeyname)
55130

56-
return Timezone(get_localzone_name())
131+
return Timezone(timezone)
57132

58133

59134
def _get_darwin_timezone(): # type: () -> Timezone

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ build = "build.py"
1818

1919
[tool.poetry.dependencies]
2020
python = "~2.7 || ^3.4"
21-
tzlocal = "^1.3"
2221
python-dateutil = "^2.6"
2322
pytzdata = ">=2018.3"
2423

tests/tz/test_local_timezone.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
import pytest
55

66
from pendulum.tz.local_timezone import _get_unix_timezone
7+
from pendulum.tz.local_timezone import _get_windows_timezone
78

89

910
@pytest.mark.skipif(sys.platform == 'win32',
10-
reason='Tests only available for UNIX systems')
11+
reason='Test only available for UNIX systems')
1112
def test_unix_symlink():
1213
# A ZONE setting in the target path of a symbolic linked localtime,
1314
# f ex systemd distributions
@@ -17,3 +18,11 @@ def test_unix_symlink():
1718
)
1819

1920
assert tz.name == 'Europe/Paris'
21+
22+
23+
@pytest.mark.skipif(sys.platform != 'win32',
24+
reason='Test only available for Windows')
25+
def test_unix_symlink():
26+
timezone = _get_windows_timezone()
27+
28+
assert timezone is not None

0 commit comments

Comments
 (0)