├── .github └── workflows │ └── test.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CHANGES.txt ├── LICENSE.txt ├── MANIFEST.in ├── Makefile ├── README.rst ├── pyproject.toml ├── tests ├── test_data │ ├── Africa │ │ └── Harare │ ├── UTC │ ├── conflicting │ │ ├── etc │ │ │ ├── conf.d │ │ │ │ └── clock │ │ │ ├── localtime │ │ │ ├── sysconfig │ │ │ │ └── clock │ │ │ └── timezone │ │ ├── usr │ │ │ └── share │ │ │ │ └── zoneinfo │ │ │ │ └── Africa │ │ │ │ └── Harare │ │ └── var │ │ │ └── db │ │ │ └── zoneinfo │ ├── localtime │ │ └── etc │ │ │ └── localtime │ ├── noconflict │ │ ├── etc │ │ │ ├── conf.d │ │ │ │ └── clock │ │ │ ├── localtime │ │ │ ├── sysconfig │ │ │ │ └── clock │ │ │ └── timezone │ │ └── usr │ │ │ └── share │ │ │ └── zoneinfo │ │ │ ├── Etc │ │ │ ├── UCT │ │ │ └── UTC │ │ │ ├── UTC │ │ │ └── Zulu │ ├── symlink_localtime │ │ ├── etc │ │ │ └── localtime │ │ └── usr │ │ │ └── share │ │ │ └── zoneinfo │ │ │ └── Africa │ │ │ └── Harare │ ├── termux │ │ └── system │ │ │ └── bin │ │ │ └── getprop │ ├── timezone │ │ └── etc │ │ │ └── timezone │ ├── timezone_deprecated │ │ └── etc │ │ │ ├── conf.d │ │ │ └── clock │ │ │ └── timezone │ ├── timezone_setting │ │ └── etc │ │ │ └── conf.d │ │ │ └── clock │ ├── top_line_comment │ │ └── etc │ │ │ └── timezone │ ├── ubuntu_docker_bug │ │ └── etc │ │ │ └── timezone │ ├── vardbzoneinfo │ │ ├── etc │ │ │ └── timezone │ │ └── var │ │ │ └── db │ │ │ └── zoneinfo │ └── zone_setting │ │ └── etc │ │ └── sysconfig │ │ └── clock └── test_tzlocal.py ├── tzlocal ├── __init__.py ├── py.typed ├── unix.py ├── utils.py ├── win32.py └── windows_tz.py └── update_windows_mappings.py /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run the test suite 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | 8 | jobs: 9 | test: 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | os: [ubuntu-latest, macos-latest, windows-latest] 14 | python-version: ["3.9", "3.13", "3.14-dev", "pypy3.10"] 15 | exclude: 16 | - os: macos-latest 17 | python-version: 3.9 18 | - os: macos-latest 19 | python-version: 3.13 20 | - os: macos-latest 21 | python-version: pypy3 22 | - os: windows-latest 23 | python-version: 3.9 24 | - os: windows-latest 25 | python-version: 3.13 26 | - os: windows-latest 27 | python-version: pypy3 28 | runs-on: ${{ matrix.os }} 29 | steps: 30 | - uses: actions/checkout@v4 31 | - name: Set up Python ${{ matrix.python-version }} 32 | uses: actions/setup-python@v4 33 | with: 34 | python-version: ${{ matrix.python-version }} 35 | allow-prereleases: true 36 | - uses: actions/cache@v3 37 | with: 38 | path: ~/.cache/pip 39 | key: pip-test-${{ matrix.python-version }}-${{ matrix.os }} 40 | - name: Install dependencies 41 | run: pip install .[devenv] 42 | - name: Test with pytest 43 | run: pytest 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.wpr 3 | *.wpu 4 | *.egg-info 5 | *.egg 6 | .tox 7 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.5.0 4 | hooks: 5 | - id: check-yaml 6 | - id: end-of-file-fixer 7 | - id: trailing-whitespace 8 | - repo: https://github.com/astral-sh/ruff-pre-commit 9 | rev: v0.0.292 10 | hooks: 11 | - id: ruff 12 | args: [--fix, --show-fixes] 13 | - repo: https://github.com/psf/black-pre-commit-mirror 14 | rev: 23.9.1 15 | hooks: 16 | - id: black 17 | - repo: https://github.com/regebro/pyroma 18 | rev: '4.2' 19 | hooks: 20 | - id: pyroma 21 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | Changes 2 | ======= 3 | 4 | 5.3.2 (unreleased) 5 | ------------------ 6 | 7 | - Nothing changed yet. 8 | 9 | 10 | 5.3.1 (2025-03-05) 11 | ------------------ 12 | 13 | - Now only warns bout /etc/timezone if it is actually conflicting with 14 | other timezone configurations. 15 | 16 | 17 | 5.3 (2025-02-13) 18 | ---------------- 19 | 20 | - Now supports Python 3.9 to 3.13, and no longer requires backports.zoneinfo. 21 | 22 | - Some distributions are for some reason removing support for /etc/timezone, 23 | which is bad, because that's the only place where the timezone is stated 24 | in plain text, and what's worse, they don't delete it. So we can't trust 25 | it now, so when we have multiple configs, we are forced to just ignore it. 26 | 27 | - Attempts to return a ZoneInfo object also for UTC. 28 | 29 | 30 | 5.2 (2023-10-22) 31 | ---------------- 32 | 33 | - Added a pre-commit config 34 | 35 | - Updated python versions [hugovk] 36 | 37 | - Added type hints for the public functions 38 | 39 | - Moved to using pyproject.toml for project config. 40 | 41 | 42 | 5.1 (2023-10-04) 43 | ---------------- 44 | 45 | - The Unicode data doesn't change tz names when IANA does, so what is current 46 | in IANA is treated as an alias in Unicode data. This version handles that. 47 | 48 | 49 | 5.0.1 (2023-05-15) 50 | ------------------ 51 | 52 | - The logging info under windows made it look like it looked up the registry 53 | info even when you had a TZ environment, but it doesn't actually do that. 54 | 55 | - Improved the handling of loggers. 56 | 57 | 58 | 5.0 (2023-05-14) 59 | ---------------- 60 | 61 | - Fixed a bug in the new assert_tz_offset method. 62 | 63 | 64 | 5.0b2 (2023-04-11) 65 | ------------------ 66 | 67 | - Change how the system offset is calculated to deal with non-DST 68 | temporary changes, such as Ramadan time in Morocco. 69 | 70 | - Change the default to only warn when the timezone offset and system 71 | offset disagree (but still not even warn if TZ is set) 72 | 73 | - Add the assert_tz_offset() method to the top level for those who want 74 | to explicitly check and fail. 75 | 76 | 77 | 5.0b1 (2023-04-07) 78 | ------------------ 79 | 80 | - Removed the deprecation shim. 81 | 82 | 83 | 4.4b1 (2023-03-20) 84 | ------------------ 85 | 86 | - Added debug logging 87 | 88 | 89 | 4.3 (2023-03-18) 90 | ---------------- 91 | 92 | - Improved the error message when the ZoneInfo cannot be found 93 | 94 | - Don't error out because we find multiple possible timezones for 95 | a symlink. 96 | 97 | - More stable on Android/Termux with proot 98 | 99 | 100 | 4.2 (2022-04-02) 101 | ---------------- 102 | 103 | - If TZ environment variable is set to /etc/localhost, and that's a link to 104 | a zoneinfo file, then tzlocal will now find the timezone name, and not 105 | just return a localtime TZ object. 106 | 107 | 108 | 4.1 (2021-10-29) 109 | ---------------- 110 | 111 | - No changes from 4.1b1. 112 | 113 | 114 | 4.1b1 (2021-10-28) 115 | ------------------ 116 | 117 | - It turns out a lot of Linux distributions make the links between zoneinfo 118 | aliases backwards, so instead of linking GB to Europe/London it actually 119 | links the other way. When /etc/localtime then links to Europe/London, and you 120 | also have a config file saying Europe/London, the code that checks if 121 | /etc/localtime is a symlink ends up at GB instead of Europe/London and 122 | we get an error, as it thinks GB and Europe/London are different zones. 123 | 124 | So now we check the symlink of all timezones in the uniqueness test. We still 125 | return the name in the config file, though, so you would only get GB or Zulu 126 | returned as the time zone instead of Europe/London or UTC if your only 127 | configuration is the /etc/localtime symlink, as that's checked last, and 128 | tzlocal will return the first configuration found. 129 | 130 | - The above change also means that GMT and UTC are no longer seen as synonyms, 131 | as zoneinfo does not see them as synonyms. This might be controversial, 132 | but you just have to live with it. Pick one and stay with it. ;-) 133 | 134 | 135 | 4.0.2 (2021-10-26) 136 | ------------------ 137 | 138 | - Improved the error message when you had a conflict including a 139 | /etc/localtime symlink. 140 | 141 | 142 | 4.0.1 (2021-10-19) 143 | ------------------ 144 | 145 | - A long time bug in Ubuntu docker images seem to not get fixed, 146 | so I added a workaround. 147 | 148 | 149 | 4.0.1b1 (2021-10-18) 150 | -------------------- 151 | 152 | - Handle UCT and Zulu as synonyms for UTC, while treating GMT and 153 | UTC as different. 154 | 155 | 156 | 4.0 (2021-10-18) 157 | ---------------- 158 | 159 | - No changes. 160 | 161 | 162 | 4.0b5 (2021-10-18) 163 | ------------------ 164 | 165 | - Fixed a bug in the Windows DST support. 166 | 167 | 168 | 4.0b4 (2021-10-18) 169 | ------------------ 170 | 171 | - Added support for turning off DST in Windows. That only works in 172 | whole hour timezones, and honestly, if you need to turn off DST, 173 | you should just use UTC as a timezone. 174 | 175 | 176 | 4.0b3 (2021-10-08) 177 | ------------------ 178 | 179 | - Returning pytz_deprecation_shim zones to lower the surprise for pytz users. 180 | 181 | - The Windows OS environment variable 'TZ' will allow an override for 182 | setting the timezone. The override timezone will be asserted for 183 | timezone validity bit not compared against the systems timezone offset. 184 | This allows for overriding the timezone when running tests. 185 | 186 | - Dropped support for Windows 2000, XP and Vista, supports Windows 7, 8 and 10. 187 | 188 | 189 | 4.0b2 (2021-09-26) 190 | ------------------ 191 | 192 | - Big refactor; Implemented get_localzone_name() functions. 193 | 194 | - Adding a Windows OS environment variable 'TZ' will allow an override for 195 | setting the timezone (also see 4.0b3). 196 | 197 | 198 | 4.0b1 (2021-08-21) 199 | ------------------ 200 | 201 | - Now finds and compares all the configs (under Unix-like systems) and 202 | tells you what files it found and how they conflict. This should make 203 | it a lot easier to figure out what goes wrong. 204 | 205 | 206 | 3.0 (2021-08-13) 207 | ---------------- 208 | 209 | - Modernized the packaging, moving to setup.cfg etc. 210 | 211 | - Handles if the text config files incorrectly is a TZ file. (revanSZ) 212 | 213 | 214 | 3.0b1 (2020-09-21) 215 | ------------------ 216 | 217 | - Dropped Python 2 support 218 | - Switched timezone provider from pytz to zoneinfo (PEP 615) 219 | 220 | 221 | 2.1 (2020-05-08) 222 | ---------------- 223 | 224 | - No changes. 225 | 226 | 227 | 2.1b1 (2020-02-08) 228 | ------------------ 229 | 230 | - The is_dst flag is wrong for Europe/Dublin on some Unix releases. 231 | I changed to another way of determining if DST is in effect or not. 232 | 233 | - Added support for Python 3.7 and 3.8. Dropped 3.5 although it still works. 234 | 235 | 236 | 2.0.0 (2019-07-23) 237 | ------------------ 238 | 239 | - No differences since 2.0.0b3 240 | 241 | Major differences since 1.5.1 242 | ............................. 243 | 244 | - When no time zone configuration can be find, tzlocal now return UTC. 245 | This is a major difference from 1.x, where an exception would be raised. 246 | This change is because Docker images often have no configuration at all, 247 | and the unix utilities will then default to UTC, so we follow that. 248 | 249 | - If tzlocal on Unix finds a timezone name in a /etc config file, then 250 | tzlocal now verifies that the timezone it fouds has the same offset as 251 | the local computer is configured with. If it doesn't, something is 252 | configured incorrectly. (Victor Torres, regebro) 253 | 254 | - Get timezone via Termux `getprop` wrapper on Android. It's not officially 255 | supported because we can't test it, but at least we make an effort. 256 | (Jean Jordaan) 257 | 258 | Minor differences and bug fixes 259 | ............................... 260 | 261 | - Skip comment lines when parsing /etc/timezone. (Edward Betts) 262 | 263 | - Don't load timezone from current directory. (Gabriel Corona) 264 | 265 | - Now verifies that the config files actually contain something before 266 | reading them. (Zackary Welch, regebro) 267 | 268 | - Got rid of a BytesWarning (Mickaël Schoentgen) 269 | 270 | - Now handles if config file paths exists, but are directories. 271 | 272 | - Moved tests out from distributions 273 | 274 | - Support wheels 275 | 276 | 277 | 1.5.1 (2017-12-01) 278 | ------------------ 279 | 280 | - 1.5 had a bug that slipped through testing, fixed that, 281 | increased test coverage. 282 | 283 | 284 | 1.5 (2017-11-30) 285 | ---------------- 286 | 287 | - No longer treats macOS as special, but as a unix. 288 | 289 | - get_windows_info.py is renamed to update_windows_mappings.py 290 | 291 | - Windows mappings now also contain mappings from deprecated zoneinfo names. 292 | (Preston-Landers, regebro) 293 | 294 | 295 | 1.4 (2017-04-18) 296 | ---------------- 297 | 298 | - I use MIT on my other projects, so relicensing. 299 | 300 | 301 | 1.4b1 (2017-04-14) 302 | ------------------ 303 | 304 | - Dropping support for Python versions nobody uses (2.5, 3.1, 3.2), adding 3.6 305 | Python 3.1 and 3.2 still works, 2.5 has been broken for some time. 306 | 307 | - Ayalash's OS X fix didn't work on Python 2.7, fixed that. 308 | 309 | 310 | 1.3.2 (2017-04-12) 311 | ------------------ 312 | 313 | - Ensure closing of subprocess on OS X (ayalash) 314 | 315 | - Removed unused imports (jwilk) 316 | 317 | - Closes stdout and stderr to get rid of ResourceWarnings (johnwquarles) 318 | 319 | - Updated Windows timezones (axil) 320 | 321 | 322 | 1.3 (2016-10-15) 323 | ---------------- 324 | 325 | - #34: Added support for /var/db/zoneinfo 326 | 327 | 328 | 1.2.2 (2016-03-02) 329 | ------------------ 330 | 331 | - #30: Fixed a bug on OS X. 332 | 333 | 334 | 1.2.1 (2016-02-28) 335 | ------------------ 336 | 337 | - Tests failed if TZ was set in the environment. (EdwardBetts) 338 | 339 | - Replaces os.popen() with subprocess.Popen() for OS X to 340 | handle when systemsetup doesn't exist. (mckabi, cewing) 341 | 342 | 343 | 1.2 (2015-06-14) 344 | ---------------- 345 | 346 | - Systemd stores no time zone name, forcing us to look at the name of the file 347 | that localtime symlinks to. (cameris) 348 | 349 | 350 | 1.1.2 (2014-10-18) 351 | ------------------ 352 | 353 | - Timezones that has 3 items did not work on Mac OS X. 354 | (Marc Van Olmen) 355 | 356 | - Now doesn't fail if the TZ environment variable isn't an Olsen time zone. 357 | 358 | - Some timezones on Windows can apparently be empty (perhaps the are deleted). 359 | Now these are ignored. 360 | (Xiaokun Zhu) 361 | 362 | 363 | 1.1.1 (2014-01-29) 364 | ------------------ 365 | 366 | - I forgot to add Etc/UTC as an alias for Etc/GMT. 367 | 368 | 369 | 1.1 (2014-01-28) 370 | ---------------- 371 | 372 | - Adding better support for OS X. 373 | 374 | - Added support to map from tzdata/Olsen names to Windows names. 375 | (Thanks to Benjamen Meyer). 376 | 377 | 378 | 1.0 (2013-05-29) 379 | ---------------- 380 | 381 | - Fixed some more cases where spaces needs replacing with underscores. 382 | 383 | - Better handling of misconfigured /etc/timezone. 384 | 385 | - Better error message on Windows if we can't find a timezone at all. 386 | 387 | 388 | 0.3 (2012-09-13) 389 | ---------------- 390 | 391 | - Windows 7 support. 392 | 393 | - Python 2.5 supported; because it only needed a __future__ import. 394 | 395 | - Python 3.3 tested, it worked. 396 | 397 | - Got rid of relative imports, because I don't actually like them, 398 | so I don't know why I used them in the first place. 399 | 400 | - For each Windows zone, use the default zoneinfo zone, not the last one. 401 | 402 | 403 | 0.2 (2012-09-12) 404 | ---------------- 405 | 406 | - Python 3 support. 407 | 408 | 409 | 0.1 (2012-09-11) 410 | ---------------- 411 | 412 | - Initial release. 413 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2011-2017 Lennart Regebro 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.py 2 | include *.rst 3 | include *.txt 4 | include tzlocal/py.typed 5 | recursive-include tests/test_data * 6 | include tests/*.py 7 | exclude Makefile 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | root_dir := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) 2 | bin_dir := $(root_dir)/ve/bin 3 | 4 | all: devenv test 5 | 6 | # The fullrelease script is a part of zest.releaser, which is the last 7 | # package installed, so if it exists, the devenv is installed. 8 | devenv: ve/bin/fullrelease 9 | 10 | ve/bin/fullrelease: 11 | virtualenv ve 12 | $(bin_dir)/pip install -e .[devenv] 13 | 14 | update_mapping: 15 | $(bin_dir)/python update_windows_mappings.py 16 | 17 | check: 18 | $(bin_dir)/black tzlocal tests 19 | $(bin_dir)/flake8 tzlocal tests 20 | $(bin_dir)/pyroma -d . 21 | 22 | coverage: 23 | $(bin_dir)/coverage run $(bin_dir)/pytest 24 | $(bin_dir)/coverage html 25 | $(bin_dir)/coverage report 26 | 27 | test: 28 | $(bin_dir)/pytest 29 | 30 | release: update_mapping check 31 | $(bin_dir)/fullrelease 32 | 33 | clean: 34 | rm -rf ve .coverage htmlcov build .pytest_cache 35 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | tzlocal 2 | ======= 3 | 4 | API CHANGE! 5 | ----------- 6 | 7 | With version 3.0 of tzlocal, tzlocal no longer returned `pytz` objects, but 8 | `zoneinfo` objects, which has a different API. Since 4.0, it now restored 9 | partial compatibility for `pytz` users through Paul Ganssle's 10 | `pytz_deprecation_shim`. 11 | 12 | tzlocal 4.0 also adds an official function `get_localzone_name()` to get only 13 | the timezone name, instead of a timezone object. On unix, it can raise an 14 | error if you don't have a timezone name configured, where `get_localzone()` 15 | will succeed, so only use that if you need the timezone name. 16 | 17 | 4.0 also adds way more information on what is going wrong in your 18 | configuration when the configuration files are unclear or contradictory. 19 | 20 | Version 5.0 removes the `pytz_deprecation_shim`, and now only returns 21 | `zoneinfo` objects, like verion 3.0 did. If you need `pytz` objects, you have 22 | to stay on version 4.0. If there are bugs in version 4.0, I will release 23 | updates, but there will be no further functional changes on the 4.x branch. 24 | 25 | 26 | Info 27 | ---- 28 | 29 | This Python module returns the `IANA time zone name 30 | `_ for your local time zone or a ``tzinfo`` 31 | object with the local timezone information, under Unix and Windows. 32 | 33 | This module attempts to fix a glaring hole in the ``pytz`` and ``zoneinfo`` 34 | modules, that there is no way to get the local timezone information, unless 35 | you know the zoneinfo name, and under several Linux distros that's hard or 36 | impossible to figure out. 37 | 38 | With ``tzlocal`` you only need to call ``get_localzone()`` and you will get a 39 | ``tzinfo`` object with the local time zone info. On some Unices you will 40 | still not get to know what the timezone name is, but you don't need that when 41 | you have the tzinfo file. However, if the timezone name is readily available 42 | it will be used. 43 | 44 | What it's not for 45 | ----------------- 46 | 47 | It's not for converting the current time between UTC and your local time. There are 48 | other, simpler ways of doing this. This is if you need to know things like the name 49 | of the time zone, or if you need to be able to convert between your time zone and 50 | another time zone for times that are in the future or in the past. 51 | 52 | For current time conversions to and from UTC, look in the Python ``time`` module. 53 | 54 | 55 | Supported systems 56 | ----------------- 57 | 58 | These are the systems that are in theory supported: 59 | 60 | * Windows 2000 and later 61 | 62 | * Any unix-like system with a ``/etc/localtime`` or ``/usr/local/etc/localtime`` 63 | 64 | If you have one of the above systems and it does not work, it's a bug. 65 | Please report it. 66 | 67 | Please note that if you are getting a time zone called ``local``, this is not 68 | a bug, it's actually the main feature of ``tzlocal``, that even if your 69 | system does NOT have a configuration file with the zoneinfo name of your time 70 | zone, it will still work. 71 | 72 | You can also use ``tzlocal`` to get the name of your local timezone, but only 73 | if your system is configured to make that possible. ``tzlocal`` looks for the 74 | timezone name in ``/etc/timezone``, ``/var/db/zoneinfo``, 75 | ``/etc/sysconfig/clock`` and ``/etc/conf.d/clock``. If your 76 | ``/etc/localtime`` is a symlink it can also extract the name from that 77 | symlink. 78 | 79 | If you need the name of your local time zone, then please make sure your 80 | system is properly configured to allow that. 81 | 82 | If your unix system doesn't have a timezone configured, tzlocal will default 83 | to UTC. 84 | 85 | Notes on Docker 86 | --------------- 87 | 88 | It turns out that Docker images frequently have broken timezone setups. 89 | This usually results in a warning that the configuration is wrong, or that 90 | the timezone offset doesn't match the found timezone. 91 | 92 | The easiest way to fix that is to set a TZ variable in your docker setup 93 | to whatever timezone you want, which is usually the timezone your host 94 | computer has. 95 | 96 | Usage 97 | ----- 98 | 99 | Load the local timezone: 100 | 101 | >>> from tzlocal import get_localzone 102 | >>> tz = get_localzone() 103 | >>> tz 104 | zoneinfo.ZoneInfo(key='Europe/Warsaw') 105 | 106 | Create a local datetime: 107 | 108 | >>> from datetime import datetime 109 | >>> dt = datetime(2015, 4, 10, 7, 22, tzinfo=tz) 110 | >>> dt 111 | datetime.datetime(2015, 4, 10, 7, 22, tzinfo=zoneinfo.ZoneInfo(key='Europe/Warsaw')) 112 | 113 | Lookup another timezone with ``zoneinfo``: 114 | 115 | >>> from zoneinfo import ZoneInfo 116 | >>> eastern = ZoneInfo('US/Eastern') 117 | 118 | Convert the datetime: 119 | 120 | >>> dt.astimezone(eastern) 121 | datetime.datetime(2015, 4, 10, 1, 22, tzinfo=zoneinfo.ZoneInfo(key='US/Eastern')) 122 | 123 | If you just want the name of the local timezone, use `get_localzone_name()`: 124 | 125 | >>> from tzlocal import get_localzone_name 126 | >>> get_localzone_name() 127 | "Europe/Warsaw" 128 | 129 | Please note that under Unix, `get_localzone_name()` may fail if there is no zone 130 | configured, where `get_localzone()` would generally succeed. 131 | 132 | Troubleshooting 133 | --------------- 134 | 135 | If you don't get the result you expect, try running it with debugging turned on. 136 | Start a python interpreter that has tzlocal installed, and run the following code:: 137 | 138 | import logging 139 | logging.basicConfig(level="DEBUG") 140 | import tzlocal 141 | tzlocal.get_localzone() 142 | 143 | The output should look something like this, and this will tell you what 144 | configurations were found:: 145 | 146 | DEBUG:root:/etc/timezone found, contents: 147 | Europe/Warsaw 148 | 149 | DEBUG:root:/etc/localtime found 150 | DEBUG:root:2 found: 151 | {'/etc/timezone': 'Europe/Warsaw', '/etc/localtime is a symlink to': 'Europe/Warsaw'} 152 | zoneinfo.ZoneInfo(key='Europe/Warsaw') 153 | 154 | 155 | Development 156 | ----------- 157 | 158 | For ease of development, there is a Makefile that will help you with basic tasks, 159 | like creating a development environment with all the necessary tools (although 160 | you need a supported Python version installed first):: 161 | 162 | $ make devenv 163 | 164 | To run tests:: 165 | 166 | $ make test 167 | 168 | Check the syntax:: 169 | 170 | $ make check 171 | 172 | 173 | Maintainer 174 | ---------- 175 | 176 | * Lennart Regebro, regebro@gmail.com 177 | 178 | Contributors 179 | ------------ 180 | 181 | * Marc Van Olmen 182 | * Benjamen Meyer 183 | * Manuel Ebert 184 | * Xiaokun Zhu 185 | * Cameris 186 | * Edward Betts 187 | * McK KIM 188 | * Cris Ewing 189 | * Ayala Shachar 190 | * Lev Maximov 191 | * Jakub Wilk 192 | * John Quarles 193 | * Preston Landers 194 | * Victor Torres 195 | * Jean Jordaan 196 | * Zackary Welch 197 | * Mickaël Schoentgen 198 | * Gabriel Corona 199 | * Alex Grönholm 200 | * Julin S 201 | * Miroslav Šedivý 202 | * revansSZ 203 | * Sam Treweek 204 | * Peter Di Pasquale 205 | * Rongrong 206 | 207 | (Sorry if I forgot someone) 208 | 209 | License 210 | ------- 211 | 212 | * MIT https://opensource.org/licenses/MIT 213 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools >= 64"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "tzlocal" 7 | description = "tzinfo object for the local timezone" 8 | readme = "README.rst" 9 | version = "5.3.2.dev0" 10 | authors = [{name = "Lennart Regebro", email = "regebro@gmail.com"}] 11 | license = {text = "MIT"} 12 | keywords = ["timezone"] 13 | classifiers = [ 14 | "Development Status :: 5 - Production/Stable", 15 | "License :: OSI Approved :: MIT License", 16 | "Operating System :: Microsoft :: Windows", 17 | "Operating System :: Unix", 18 | "Operating System :: MacOS :: MacOS X", 19 | "Typing :: Typed", 20 | "Programming Language :: Python :: 3.9", 21 | "Programming Language :: Python :: 3.10", 22 | "Programming Language :: Python :: 3.11", 23 | "Programming Language :: Python :: 3.12", 24 | "Programming Language :: Python :: 3.13", 25 | ] 26 | requires-python = ">= 3.9" 27 | dependencies = [ 28 | "tzdata; platform_system == 'Windows'", 29 | ] 30 | 31 | [project.urls] 32 | "Source code" = "https://github.com/regebro/tzlocal" 33 | Changelog = "https://github.com/regebro/tzlocal/blob/master/CHANGES.txt" 34 | "Issue tracker" = "https://github.com/regebro/tzlocal/issues" 35 | 36 | [project.optional-dependencies] 37 | devenv = [ 38 | "pytest >= 4.3", 39 | "pytest-mock >= 3.3", 40 | "pytest-cov", 41 | "check_manifest", 42 | "zest.releaser", 43 | ] 44 | 45 | [tool.ruff] 46 | line-length = 120 47 | select = [ 48 | "E", "F", "W", # default flake-8 49 | "I", # isort 50 | "ISC", # flake8-implicit-str-concat 51 | "PGH", # pygrep-hooks 52 | "RUF100", # unused noqa (yesqa) 53 | "UP", # pyupgrade 54 | ] 55 | ignore = ["UP015"] 56 | 57 | [tool.zest.releaser] 58 | create-wheel = true 59 | 60 | [tool.pytest.ini_options] 61 | testpaths = "tests" 62 | -------------------------------------------------------------------------------- /tests/test_data/Africa/Harare: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regebro/tzlocal/83e715fe5ee08be70f99130c121f54f55a5e7cb2/tests/test_data/Africa/Harare -------------------------------------------------------------------------------- /tests/test_data/UTC: -------------------------------------------------------------------------------- 1 | TZif2UTCTZif2UTC 2 | UTC0 3 | -------------------------------------------------------------------------------- /tests/test_data/conflicting/etc/conf.d/clock: -------------------------------------------------------------------------------- 1 | TIMEZONE = "Africa/Johannesburg" 2 | -------------------------------------------------------------------------------- /tests/test_data/conflicting/etc/localtime: -------------------------------------------------------------------------------- 1 | ../usr/share/zoneinfo/Africa/Harare -------------------------------------------------------------------------------- /tests/test_data/conflicting/etc/sysconfig/clock: -------------------------------------------------------------------------------- 1 | ZONE="Europe/Warsaw" 2 | -------------------------------------------------------------------------------- /tests/test_data/conflicting/etc/timezone: -------------------------------------------------------------------------------- 1 | Europe/Paris# We allow comments. It's unusual, but has happened 2 | -------------------------------------------------------------------------------- /tests/test_data/conflicting/usr/share/zoneinfo/Africa/Harare: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regebro/tzlocal/83e715fe5ee08be70f99130c121f54f55a5e7cb2/tests/test_data/conflicting/usr/share/zoneinfo/Africa/Harare -------------------------------------------------------------------------------- /tests/test_data/conflicting/var/db/zoneinfo: -------------------------------------------------------------------------------- 1 | America/New_York localhost # The host is not a part of the format, but is allowed. 2 | -------------------------------------------------------------------------------- /tests/test_data/localtime/etc/localtime: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regebro/tzlocal/83e715fe5ee08be70f99130c121f54f55a5e7cb2/tests/test_data/localtime/etc/localtime -------------------------------------------------------------------------------- /tests/test_data/noconflict/etc/conf.d/clock: -------------------------------------------------------------------------------- 1 | TIMEZONE = "Etc/UCT" 2 | -------------------------------------------------------------------------------- /tests/test_data/noconflict/etc/localtime: -------------------------------------------------------------------------------- 1 | ../usr/share/zoneinfo/Zulu -------------------------------------------------------------------------------- /tests/test_data/noconflict/etc/sysconfig/clock: -------------------------------------------------------------------------------- 1 | ZONE="Zulu" 2 | -------------------------------------------------------------------------------- /tests/test_data/noconflict/etc/timezone: -------------------------------------------------------------------------------- 1 | Etc/UTC# We allow comments. It's unusual, but has happened 2 | -------------------------------------------------------------------------------- /tests/test_data/noconflict/usr/share/zoneinfo/Etc/UCT: -------------------------------------------------------------------------------- 1 | UTC -------------------------------------------------------------------------------- /tests/test_data/noconflict/usr/share/zoneinfo/Etc/UTC: -------------------------------------------------------------------------------- 1 | TZif2UTCTZif2UTC 2 | UTC0 3 | -------------------------------------------------------------------------------- /tests/test_data/noconflict/usr/share/zoneinfo/UTC: -------------------------------------------------------------------------------- 1 | Etc/UCT -------------------------------------------------------------------------------- /tests/test_data/noconflict/usr/share/zoneinfo/Zulu: -------------------------------------------------------------------------------- 1 | UTC -------------------------------------------------------------------------------- /tests/test_data/symlink_localtime/etc/localtime: -------------------------------------------------------------------------------- 1 | ../usr/share/zoneinfo/Africa/Harare -------------------------------------------------------------------------------- /tests/test_data/symlink_localtime/usr/share/zoneinfo/Africa/Harare: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regebro/tzlocal/83e715fe5ee08be70f99130c121f54f55a5e7cb2/tests/test_data/symlink_localtime/usr/share/zoneinfo/Africa/Harare -------------------------------------------------------------------------------- /tests/test_data/termux/system/bin/getprop: -------------------------------------------------------------------------------- 1 | This is just a file to make tzlocal think it's termux under Android 2 | -------------------------------------------------------------------------------- /tests/test_data/timezone/etc/timezone: -------------------------------------------------------------------------------- 1 | Africa/Harare# We allow comments. It's unusual, but has happened 2 | -------------------------------------------------------------------------------- /tests/test_data/timezone_deprecated/etc/conf.d/clock: -------------------------------------------------------------------------------- 1 | TIMEZONE = "Africa/Johannesburg" 2 | -------------------------------------------------------------------------------- /tests/test_data/timezone_deprecated/etc/timezone: -------------------------------------------------------------------------------- 1 | Europe/Paris# We allow comments. It's unusual, but has happened 2 | -------------------------------------------------------------------------------- /tests/test_data/timezone_setting/etc/conf.d/clock: -------------------------------------------------------------------------------- 1 | TIMEZONE = "Africa/Harare" 2 | -------------------------------------------------------------------------------- /tests/test_data/top_line_comment/etc/timezone: -------------------------------------------------------------------------------- 1 | # Comment on first line 2 | Africa/Harare 3 | -------------------------------------------------------------------------------- /tests/test_data/ubuntu_docker_bug/etc/timezone: -------------------------------------------------------------------------------- 1 | /UTC 2 | -------------------------------------------------------------------------------- /tests/test_data/vardbzoneinfo/etc/timezone: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regebro/tzlocal/83e715fe5ee08be70f99130c121f54f55a5e7cb2/tests/test_data/vardbzoneinfo/etc/timezone -------------------------------------------------------------------------------- /tests/test_data/vardbzoneinfo/var/db/zoneinfo: -------------------------------------------------------------------------------- 1 | Africa/Harare localhost # The host is not a part of the format, but is allowed. 2 | -------------------------------------------------------------------------------- /tests/test_data/zone_setting/etc/sysconfig/clock: -------------------------------------------------------------------------------- 1 | ZONE="Africa/Harare" 2 | -------------------------------------------------------------------------------- /tests/test_tzlocal.py: -------------------------------------------------------------------------------- 1 | import os 2 | import platform 3 | import sys 4 | from datetime import datetime, timezone 5 | from pathlib import Path 6 | from unittest.mock import MagicMock, Mock 7 | 8 | import pytest 9 | 10 | import tzlocal.unix 11 | import tzlocal.utils 12 | 13 | from zoneinfo import ZoneInfo, ZoneInfoNotFoundError 14 | 15 | import logging 16 | 17 | logging.basicConfig(level=logging.DEBUG) 18 | 19 | 20 | @pytest.fixture(scope="session", autouse=True) 21 | def clear_tz_env_variable(): 22 | os.environ.pop("TZ", None) 23 | 24 | 25 | def tz_path(zonefile: str = None) -> str: 26 | path = Path(__file__).parent.joinpath("test_data") 27 | if zonefile: 28 | return str(path / zonefile) 29 | else: 30 | return str(path) 31 | 32 | 33 | def test_env(monkeypatch): 34 | tz_harare = tzlocal.utils._tz_from_env(":Africa/Harare") 35 | assert str(tz_harare) == "Africa/Harare" 36 | 37 | # Some Unices allow this as well, so we must allow it: 38 | tz_harare = tzlocal.utils._tz_from_env("Africa/Harare") 39 | assert str(tz_harare) == "Africa/Harare" 40 | 41 | path = tz_path(os.path.join("Africa", "Harare")) 42 | tz_local = tzlocal.utils._tz_from_env(":" + path) 43 | assert str(tz_local) == "Africa/Harare" 44 | # Make sure the local timezone is the same as the Harare one above. 45 | # We test this with a past date, so that we don't run into future changes 46 | # of the Harare timezone. 47 | dt = datetime(2012, 1, 1, 5) 48 | assert dt.replace(tzinfo=tz_harare) == dt.replace(tzinfo=tz_local) 49 | 50 | tz_local = tzlocal.utils._tz_from_env(tz_path("UTC")) 51 | assert str(tz_local) == "UTC" 52 | 53 | path = tz_path(os.path.join("localtime", "etc", "localtime")) 54 | tz_local = tzlocal.utils._tz_from_env(path) 55 | assert str(tz_local) == "localtime" 56 | 57 | # Non-zoneinfo timezones are not supported in the TZ environment. 58 | pytest.raises(ZoneInfoNotFoundError, tzlocal.utils._tz_from_env, "GMT+03:00") 59 | 60 | # With a zone that doesn't exist, raises error 61 | pytest.raises(ZoneInfoNotFoundError, tzlocal.utils._tz_from_env, "Just Nonsense") 62 | 63 | 64 | def test_timezone(): 65 | # Most versions of Ubuntu 66 | 67 | tz = tzlocal.unix._get_localzone(_root=tz_path("timezone")) 68 | assert str(tz) == "Africa/Harare" 69 | 70 | 71 | def test_timezone_top_line_comment(): 72 | tz = tzlocal.unix._get_localzone(_root=tz_path("top_line_comment")) 73 | assert str(tz) == "Africa/Harare" 74 | 75 | 76 | def test_zone_setting(): 77 | # A ZONE setting in /etc/sysconfig/clock, f ex CentOS 78 | 79 | tz = tzlocal.unix._get_localzone(_root=tz_path("zone_setting")) 80 | assert str(tz) == "Africa/Harare" 81 | 82 | 83 | def test_timezone_setting(): 84 | # A ZONE setting in /etc/conf.d/clock, f ex Gentoo 85 | 86 | tz = tzlocal.unix._get_localzone(_root=tz_path("timezone_setting")) 87 | assert str(tz) == "Africa/Harare" 88 | 89 | 90 | @pytest.mark.skipif( 91 | platform.system() == "Windows", reason="Symbolic links are not available on Windows" 92 | ) 93 | def test_symlink_localtime(): 94 | # A ZONE setting in the target path of a symbolic linked localtime, f ex systemd distributions 95 | 96 | tz = tzlocal.unix._get_localzone(_root=tz_path("symlink_localtime")) 97 | assert str(tz) == "Africa/Harare" 98 | 99 | 100 | def test_vardbzoneinfo_setting(): 101 | # A ZONE setting in /etc/conf.d/clock, f ex Gentoo 102 | 103 | tz = tzlocal.unix._get_localzone(_root=tz_path("vardbzoneinfo")) 104 | assert str(tz) == "Africa/Harare" 105 | 106 | 107 | def test_only_localtime(): 108 | tz = tzlocal.unix._get_localzone(_root=tz_path("localtime")) 109 | assert str(tz) == "local" 110 | dt = datetime(2012, 1, 1, 5) 111 | assert dt.replace(tzinfo=ZoneInfo("Africa/Harare")) == dt.replace(tzinfo=tz) 112 | 113 | 114 | def test_timezone_deprecated(): 115 | tz = tzlocal.unix._get_localzone(_root=tz_path("timezone_deprecated")) 116 | assert str(tz) == "Africa/Johannesburg" 117 | dt = datetime(2012, 1, 1, 5) 118 | assert dt.replace(tzinfo=ZoneInfo("Africa/Harare")) == dt.replace(tzinfo=tz) 119 | 120 | 121 | def test_get_reload(mocker, monkeypatch): 122 | mocker.patch("tzlocal.utils.assert_tz_offset") 123 | # Clear any cached zone 124 | monkeypatch.setattr(tzlocal.unix, "_cache_tz", None) 125 | monkeypatch.setenv("TZ", "Africa/Harare") 126 | 127 | tz_harare = tzlocal.unix.get_localzone() 128 | assert str(tz_harare) == "Africa/Harare" 129 | # Changing the TZ makes no difference, because it's cached 130 | monkeypatch.setenv("TZ", "Africa/Johannesburg") 131 | tz_harare = tzlocal.unix.get_localzone() 132 | assert str(tz_harare) == "Africa/Harare" 133 | # So we reload it 134 | tz_harare = tzlocal.unix.reload_localzone() 135 | assert str(tz_harare) == "Africa/Johannesburg" 136 | 137 | 138 | def test_fail(recwarn): 139 | with pytest.warns(UserWarning, match="Can not find any timezone configuration"): 140 | tz = tzlocal.unix._get_localzone(_root=tz_path()) 141 | assert "UTC" in tz.key 142 | 143 | 144 | def test_assert_tz_offset(): 145 | # The local zone should be the local zone: 146 | local = tzlocal.get_localzone() 147 | tzlocal.utils.assert_tz_offset(local) 148 | 149 | # Get a non local zone. Let's use Chatham, population 600. 150 | other = ZoneInfo("Pacific/Chatham") 151 | pytest.raises(ValueError, tzlocal.utils.assert_tz_offset, other) 152 | 153 | # But you can change it do it only warns 154 | other = ZoneInfo("Pacific/Chatham") 155 | with pytest.warns(UserWarning): 156 | tzlocal.utils.assert_tz_offset(other, error=False) 157 | 158 | 159 | def test_win32(mocker): 160 | if sys.platform == "win32": 161 | # Ironically, these tests don't work on Windows. 162 | import tzlocal.win32 163 | 164 | # Just check on Windows that the code works, and that we get 165 | # something reasonable back. 166 | tz = tzlocal.win32.get_localzone() 167 | # It should be a timezone with a slash in it, at least: 168 | assert "/" in str(tz) 169 | return 170 | 171 | # Yes, winreg is all mocked out, but this test means we at least 172 | # catch syntax errors, etc. 173 | mocker.patch("tzlocal.utils.assert_tz_offset") 174 | winreg = MagicMock() 175 | winreg.EnumValue.configure_mock( 176 | return_value=("TimeZoneKeyName", "Belarus Standard Time") 177 | ) 178 | sys.modules["winreg"] = winreg 179 | 180 | import tzlocal.win32 181 | 182 | tz = tzlocal.win32.get_localzone() 183 | assert str(tz) == "Europe/Minsk" 184 | 185 | tz = tzlocal.win32.reload_localzone() 186 | assert str(tz) == "Europe/Minsk" 187 | 188 | winreg.EnumValue.configure_mock( 189 | return_value=("TimeZoneKeyName", "Not a real timezone") 190 | ) 191 | pytest.raises(ZoneInfoNotFoundError, tzlocal.win32._get_localzone_name) 192 | 193 | # Old XP style reginfo should fail 194 | winreg.EnumValue.configure_mock( 195 | return_value=("TimeZoneKeyName", "Belarus Standard Time") 196 | ) 197 | tzlocal.win32.valuestodict = Mock( 198 | return_value={ 199 | "StandardName": "Mocked Standard Time", 200 | "Std": "Mocked Standard Time", 201 | } 202 | ) 203 | pytest.raises(LookupError, tzlocal.win32._get_localzone_name) 204 | 205 | 206 | def test_win32_env(mocker, monkeypatch): 207 | sys.modules["winreg"] = MagicMock() 208 | import tzlocal.win32 209 | 210 | mocker.patch("tzlocal.utils.assert_tz_offset") 211 | monkeypatch.setattr(tzlocal.win32, "_cache_tz", None) 212 | monkeypatch.setenv("TZ", "Europe/Berlin") 213 | 214 | tzlocal.win32._cache_tz_name = None 215 | tzname = tzlocal.win32.get_localzone_name() 216 | assert tzname == "Europe/Berlin" 217 | tz = tzlocal.win32.get_localzone() 218 | assert str(tz) == "Europe/Berlin" 219 | 220 | 221 | def test_win32_no_dst(mocker): 222 | mocker.patch("tzlocal.utils.assert_tz_offset") 223 | valuesmock = mocker.patch("tzlocal.win32.valuestodict") 224 | 225 | # If you turn off the DST, tzlocal returns "Etc/GMT+zomething": 226 | valuesmock.configure_mock( 227 | return_value={ 228 | "TimeZoneKeyName": "Romance Standard Time", 229 | "DynamicDaylightTimeDisabled": 1, 230 | } 231 | ) 232 | tzlocal.win32._cache_tz_name = None 233 | tzlocal.win32._cache_tz = None 234 | assert str(tzlocal.win32.get_localzone()) == "Etc/GMT-1" 235 | 236 | # Except if the timezone doesn't have daylight savings at all, 237 | # then just return the timezone in question, because why not? 238 | valuesmock.configure_mock( 239 | return_value={ 240 | "TimeZoneKeyName": "Belarus Standard Time", 241 | "DynamicDaylightTimeDisabled": 1, 242 | } 243 | ) 244 | tz = tzlocal.win32._get_localzone_name() 245 | assert tz == "Europe/Minsk" 246 | 247 | # Now, if you disable this in a timezone with DST, that has a 248 | # non-whole hour offset, then there's nothing we can return. 249 | valuesmock.configure_mock( 250 | return_value={ 251 | "TimeZoneKeyName": "Cen. Australia Standard Time", 252 | "DynamicDaylightTimeDisabled": 1, 253 | } 254 | ) 255 | pytest.raises(ZoneInfoNotFoundError, tzlocal.win32._get_localzone_name) 256 | 257 | # But again, if there is no DST, that works fine: 258 | valuesmock.configure_mock( 259 | return_value={ 260 | "TimeZoneKeyName": "Aus Central W. Standard Time", 261 | "DynamicDaylightTimeDisabled": 1, 262 | } 263 | ) 264 | tz = tzlocal.win32._get_localzone_name() 265 | assert tz == "Australia/Eucla" 266 | 267 | 268 | def test_termux(mocker): 269 | subprocess = MagicMock() 270 | subprocess.check_output.configure_mock(return_value=b"Africa/Johannesburg") 271 | sys.modules["subprocess"] = subprocess 272 | 273 | tz = tzlocal.unix._get_localzone(_root=tz_path("termux")) 274 | assert str(tz) == "Africa/Johannesburg" 275 | 276 | 277 | @pytest.mark.skipif( 278 | platform.system() == "Windows", reason="Symbolic links are not available on Windows" 279 | ) 280 | def test_conflicting(): 281 | with pytest.raises(ZoneInfoNotFoundError) as excinfo: 282 | tzlocal.unix._get_localzone(_root=tz_path("conflicting")) 283 | message = excinfo.value.args[0] 284 | assert "Multiple conflicting time zone configurations found:\n" in message 285 | assert "America/New_York" in message 286 | assert "Europe/Warsaw" in message 287 | assert "Africa/Johannesburg" in message 288 | assert "localtime is a symlink to: Africa/Harare" in message 289 | 290 | 291 | @pytest.mark.skipif( 292 | platform.system() == "Windows", reason="Symbolic links are not available on Windows" 293 | ) 294 | def test_noconflict(): 295 | tz = tzlocal.unix._get_localzone(_root=tz_path("noconflict")) 296 | assert str(tz) == "Etc/UTC" 297 | 298 | 299 | def test_zoneinfo_compatibility(): 300 | os.environ["TZ"] = "Africa/Harare" 301 | tzlocal.unix.reload_localzone() 302 | tz_harare = tzlocal.unix.get_localzone() 303 | assert str(tz_harare) == "Africa/Harare" 304 | 305 | os.environ["TZ"] = "America/New_York" 306 | tzlocal.unix.reload_localzone() 307 | tz_newyork = tzlocal.unix.get_localzone() 308 | assert str(tz_newyork) == "America/New_York" 309 | 310 | dt = datetime(2021, 10, 1, 12, 00) 311 | dt = dt.replace(tzinfo=tz_harare) 312 | assert dt.utcoffset().total_seconds() == 7200 313 | dt = dt.replace(tzinfo=tz_newyork) 314 | assert dt.utcoffset().total_seconds() == -14400 315 | del os.environ["TZ"] 316 | 317 | 318 | def test_get_localzone_name(): 319 | tzlocal.unix._cache_tz_name = None 320 | os.environ["TZ"] = "America/New_York" 321 | assert tzlocal.unix.get_localzone_name() == "America/New_York" 322 | del os.environ["TZ"] 323 | 324 | 325 | def test_ubuntu_docker_bug(): 326 | tz = tzlocal.unix._get_localzone(_root=tz_path("ubuntu_docker_bug")) 327 | assert str(tz) == "UTC" 328 | -------------------------------------------------------------------------------- /tzlocal/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | if sys.platform == "win32": 4 | from tzlocal.win32 import ( 5 | get_localzone, 6 | get_localzone_name, 7 | reload_localzone, 8 | ) 9 | else: 10 | from tzlocal.unix import get_localzone, get_localzone_name, reload_localzone 11 | 12 | from tzlocal.utils import assert_tz_offset 13 | 14 | __all__ = [ 15 | "get_localzone", 16 | "get_localzone_name", 17 | "reload_localzone", 18 | "assert_tz_offset", 19 | ] 20 | -------------------------------------------------------------------------------- /tzlocal/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regebro/tzlocal/83e715fe5ee08be70f99130c121f54f55a5e7cb2/tzlocal/py.typed -------------------------------------------------------------------------------- /tzlocal/unix.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import re 4 | import sys 5 | import warnings 6 | from datetime import timezone 7 | 8 | from tzlocal import utils 9 | 10 | import zoneinfo 11 | 12 | _cache_tz = None 13 | _cache_tz_name = None 14 | 15 | log = logging.getLogger("tzlocal") 16 | 17 | 18 | def _get_localzone_name(_root="/"): 19 | """Tries to find the local timezone configuration. 20 | 21 | This method finds the timezone name, if it can, or it returns None. 22 | 23 | The parameter _root makes the function look for files like /etc/localtime 24 | beneath the _root directory. This is primarily used by the tests. 25 | In normal usage you call the function without parameters.""" 26 | 27 | # First try the ENV setting. 28 | tzenv = utils._tz_name_from_env() 29 | if tzenv: 30 | return tzenv 31 | 32 | # Are we under Termux on Android? 33 | if os.path.exists(os.path.join(_root, "system/bin/getprop")): 34 | log.debug("This looks like Termux") 35 | 36 | import subprocess 37 | 38 | try: 39 | androidtz = ( 40 | subprocess.check_output(["getprop", "persist.sys.timezone"]) 41 | .strip() 42 | .decode() 43 | ) 44 | return androidtz 45 | except (OSError, subprocess.CalledProcessError): 46 | # proot environment or failed to getprop 47 | log.debug("It's not termux?") 48 | pass 49 | 50 | # Now look for distribution specific configuration files 51 | # that contain the timezone name. 52 | 53 | # Stick all of them in a dict, to compare later. 54 | found_configs = {} 55 | 56 | for configfile in ("etc/timezone", "var/db/zoneinfo"): 57 | tzpath = os.path.join(_root, configfile) 58 | try: 59 | with open(tzpath) as tzfile: 60 | data = tzfile.read() 61 | log.debug(f"{tzpath} found, contents:\n {data}") 62 | 63 | etctz = data.strip("/ \t\r\n") 64 | if not etctz: 65 | # Empty file, skip 66 | continue 67 | for etctz in etctz.splitlines(): 68 | # Get rid of host definitions and comments: 69 | if " " in etctz: 70 | etctz, dummy = etctz.split(" ", 1) 71 | if "#" in etctz: 72 | etctz, dummy = etctz.split("#", 1) 73 | if not etctz: 74 | continue 75 | 76 | found_configs[tzpath] = etctz.replace(" ", "_") 77 | 78 | except (OSError, UnicodeDecodeError): 79 | # File doesn't exist or is a directory, or it's a binary file. 80 | continue 81 | 82 | # CentOS has a ZONE setting in /etc/sysconfig/clock, 83 | # OpenSUSE has a TIMEZONE setting in /etc/sysconfig/clock and 84 | # Gentoo has a TIMEZONE setting in /etc/conf.d/clock 85 | # We look through these files for a timezone: 86 | 87 | zone_re = re.compile(r"\s*ZONE\s*=\s*\"") 88 | timezone_re = re.compile(r"\s*TIMEZONE\s*=\s*\"") 89 | end_re = re.compile('"') 90 | 91 | for filename in ("etc/sysconfig/clock", "etc/conf.d/clock"): 92 | tzpath = os.path.join(_root, filename) 93 | try: 94 | with open(tzpath, "rt") as tzfile: 95 | data = tzfile.readlines() 96 | log.debug(f"{tzpath} found, contents:\n {data}") 97 | 98 | for line in data: 99 | # Look for the ZONE= setting. 100 | match = zone_re.match(line) 101 | if match is None: 102 | # No ZONE= setting. Look for the TIMEZONE= setting. 103 | match = timezone_re.match(line) 104 | if match is not None: 105 | # Some setting existed 106 | line = line[match.end() :] 107 | etctz = line[: end_re.search(line).start()] 108 | 109 | # We found a timezone 110 | found_configs[tzpath] = etctz.replace(" ", "_") 111 | 112 | except (OSError, UnicodeDecodeError): 113 | # UnicodeDecode handles when clock is symlink to /etc/localtime 114 | continue 115 | 116 | # systemd distributions use symlinks that include the zone name, 117 | # see manpage of localtime(5) and timedatectl(1) 118 | tzpath = os.path.join(_root, "etc/localtime") 119 | if os.path.exists(tzpath) and os.path.islink(tzpath): 120 | log.debug(f"{tzpath} found") 121 | etctz = os.path.realpath(tzpath) 122 | start = etctz.find("/") + 1 123 | while start != 0: 124 | etctz = etctz[start:] 125 | try: 126 | zoneinfo.ZoneInfo(etctz) 127 | tzinfo = f"{tzpath} is a symlink to" 128 | found_configs[tzinfo] = etctz.replace(" ", "_") 129 | # Only need first valid relative path in simlink. 130 | break 131 | except zoneinfo.ZoneInfoNotFoundError: 132 | pass 133 | start = etctz.find("/") + 1 134 | 135 | if len(found_configs) > 0: 136 | log.debug(f"{len(found_configs)} found:\n {found_configs}") 137 | 138 | # We found some explicit config of some sort! 139 | if len(found_configs) > 1: 140 | # Uh-oh, multiple configs. See if they match: 141 | unique_tzs = _get_unique_tzs(found_configs, _root) 142 | 143 | if len(unique_tzs) != 1 and "etc/timezone" in str(found_configs.keys()): 144 | # For some reason some distros are removing support for /etc/timezone, 145 | # which is bad, because that's the only place where the timezone is stated 146 | # in plain text, and what's worse, they don't delete it. So we can't trust 147 | # it now, so when we have conflicting configs, we just ignore it, with a warning. 148 | log.warning("/etc/timezone is deprecated in some distros, and no longer reliable. " 149 | "tzlocal is ignoring it, and you can likely delete it.") 150 | found_configs = {k: v for k, v in found_configs.items() if "etc/timezone" not in k} 151 | unique_tzs = _get_unique_tzs(found_configs, _root) 152 | 153 | if len(unique_tzs) != 1: 154 | message = "Multiple conflicting time zone configurations found:\n" 155 | for key, value in found_configs.items(): 156 | message += f"{key}: {value}\n" 157 | message += "Fix the configuration, or set the time zone in a TZ environment variable.\n" 158 | raise zoneinfo.ZoneInfoNotFoundError(message) 159 | 160 | # We found exactly one config! Use it. 161 | return list(found_configs.values())[0] 162 | 163 | 164 | def _get_unique_tzs(found_configs, _root): 165 | unique_tzs = set() 166 | zoneinfopath = os.path.join(_root, "usr", "share", "zoneinfo") 167 | directory_depth = len(zoneinfopath.split(os.path.sep)) 168 | 169 | for tzname in found_configs.values(): 170 | # Look them up in /usr/share/zoneinfo, and find what they 171 | # really point to: 172 | path = os.path.realpath(os.path.join(zoneinfopath, *tzname.split("/"))) 173 | real_zone_name = "/".join(path.split(os.path.sep)[directory_depth:]) 174 | unique_tzs.add(real_zone_name) 175 | 176 | return unique_tzs 177 | 178 | 179 | def _get_localzone(_root="/"): 180 | """Creates a timezone object from the timezone name. 181 | 182 | If there is no timezone config, it will try to create a file from the 183 | localtime timezone, and if there isn't one, it will default to UTC. 184 | 185 | The parameter _root makes the function look for files like /etc/localtime 186 | beneath the _root directory. This is primarily used by the tests. 187 | In normal usage you call the function without parameters.""" 188 | 189 | # First try the ENV setting. 190 | tzenv = utils._tz_from_env() 191 | if tzenv: 192 | return tzenv 193 | 194 | tzname = _get_localzone_name(_root) 195 | if tzname is None: 196 | # No explicit setting existed. Use localtime 197 | log.debug("No explicit setting existed. Use localtime") 198 | for filename in ("etc/localtime", "usr/local/etc/localtime"): 199 | tzpath = os.path.join(_root, filename) 200 | 201 | if not os.path.exists(tzpath): 202 | continue 203 | with open(tzpath, "rb") as tzfile: 204 | tz = zoneinfo.ZoneInfo.from_file(tzfile, key="local") 205 | break 206 | else: 207 | warnings.warn("Can not find any timezone configuration, defaulting to UTC.") 208 | utcname = [x for x in zoneinfo.available_timezones() if "UTC" in x] 209 | if utcname: 210 | tz = zoneinfo.ZoneInfo(utcname[0]) 211 | else: 212 | tz = timezone.utc 213 | else: 214 | tz = zoneinfo.ZoneInfo(tzname) 215 | 216 | if _root == "/": 217 | # We are using a file in etc to name the timezone. 218 | # Verify that the timezone specified there is actually used: 219 | utils.assert_tz_offset(tz, error=False) 220 | return tz 221 | 222 | 223 | def get_localzone_name() -> str: 224 | """Get the computers configured local timezone name, if any.""" 225 | global _cache_tz_name 226 | if _cache_tz_name is None: 227 | _cache_tz_name = _get_localzone_name() 228 | 229 | return _cache_tz_name 230 | 231 | 232 | def get_localzone() -> zoneinfo.ZoneInfo: 233 | """Get the computers configured local timezone, if any.""" 234 | 235 | global _cache_tz 236 | if _cache_tz is None: 237 | _cache_tz = _get_localzone() 238 | 239 | return _cache_tz 240 | 241 | 242 | def reload_localzone() -> zoneinfo.ZoneInfo: 243 | """Reload the cached localzone. You need to call this if the timezone has changed.""" 244 | global _cache_tz_name 245 | global _cache_tz 246 | _cache_tz_name = _get_localzone_name() 247 | _cache_tz = _get_localzone() 248 | 249 | return _cache_tz 250 | -------------------------------------------------------------------------------- /tzlocal/utils.py: -------------------------------------------------------------------------------- 1 | import calendar 2 | import datetime 3 | import logging 4 | import os 5 | import time 6 | import warnings 7 | import zoneinfo 8 | 9 | from tzlocal import windows_tz 10 | 11 | log = logging.getLogger("tzlocal") 12 | 13 | 14 | def get_tz_offset(tz): 15 | """Get timezone's offset using built-in function datetime.utcoffset().""" 16 | return int(datetime.datetime.now(tz).utcoffset().total_seconds()) 17 | 18 | 19 | def assert_tz_offset(tz, error=True): 20 | """Assert that system's timezone offset equals to the timezone offset found. 21 | 22 | If they don't match, we probably have a misconfiguration, for example, an 23 | incorrect timezone set in /etc/timezone file in systemd distributions. 24 | 25 | If error is True, this method will raise a ValueError, otherwise it will 26 | emit a warning. 27 | """ 28 | 29 | tz_offset = get_tz_offset(tz) 30 | system_offset = calendar.timegm(time.localtime()) - calendar.timegm(time.gmtime()) 31 | # No one has timezone offsets less than a minute, so this should be close enough: 32 | if abs(tz_offset - system_offset) > 60: 33 | msg = ( 34 | f"Timezone offset does not match system offset: {tz_offset} != {system_offset}. " 35 | "Please, check your config files." 36 | ) 37 | if error: 38 | raise ValueError(msg) 39 | warnings.warn(msg) 40 | 41 | 42 | def _tz_name_from_env(tzenv=None): 43 | if tzenv is None: 44 | tzenv = os.environ.get("TZ") 45 | 46 | if not tzenv: 47 | return None 48 | 49 | log.debug(f"Found a TZ environment: {tzenv}") 50 | 51 | if tzenv[0] == ":": 52 | tzenv = tzenv[1:] 53 | 54 | if tzenv in windows_tz.tz_win: 55 | # Yup, it's a timezone 56 | return tzenv 57 | 58 | if os.path.isabs(tzenv) and os.path.exists(tzenv): 59 | # It's a file specification, expand it, if possible 60 | parts = os.path.realpath(tzenv).split(os.sep) 61 | 62 | # Is it a zone info zone? 63 | possible_tz = "/".join(parts[-2:]) 64 | if possible_tz in windows_tz.tz_win: 65 | # Yup, it is 66 | return possible_tz 67 | 68 | # Maybe it's a short one, like UTC? 69 | if parts[-1] in windows_tz.tz_win: 70 | # Indeed 71 | return parts[-1] 72 | 73 | log.debug("TZ does not contain a time zone name") 74 | return None 75 | 76 | 77 | def _tz_from_env(tzenv=None): 78 | if tzenv is None: 79 | tzenv = os.environ.get("TZ") 80 | 81 | if not tzenv: 82 | return None 83 | 84 | # Some weird format that exists: 85 | if tzenv[0] == ":": 86 | tzenv = tzenv[1:] 87 | 88 | # TZ specifies a file 89 | if os.path.isabs(tzenv) and os.path.exists(tzenv): 90 | # Try to see if we can figure out the name 91 | tzname = _tz_name_from_env(tzenv) 92 | if not tzname: 93 | # Nope, not a standard timezone name, just take the filename 94 | tzname = tzenv.split(os.sep)[-1] 95 | with open(tzenv, "rb") as tzfile: 96 | return zoneinfo.ZoneInfo.from_file(tzfile, key=tzname) 97 | 98 | # TZ must specify a zoneinfo zone. 99 | try: 100 | tz = zoneinfo.ZoneInfo(tzenv) 101 | # That worked, so we return this: 102 | return tz 103 | except zoneinfo.ZoneInfoNotFoundError: 104 | # Nope, it's something like "PST4DST" etc, we can't handle that. 105 | raise zoneinfo.ZoneInfoNotFoundError( 106 | f"tzlocal() does not support non-zoneinfo timezones like {tzenv}. \n" 107 | "Please use a timezone in the form of Continent/City" 108 | ) from None 109 | -------------------------------------------------------------------------------- /tzlocal/win32.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from datetime import datetime 3 | 4 | try: 5 | import _winreg as winreg 6 | except ImportError: 7 | import winreg 8 | 9 | import zoneinfo 10 | 11 | from tzlocal import utils 12 | from tzlocal.windows_tz import win_tz 13 | 14 | _cache_tz = None 15 | _cache_tz_name = None 16 | 17 | log = logging.getLogger("tzlocal") 18 | 19 | 20 | def valuestodict(key): 21 | """Convert a registry key's values to a dictionary.""" 22 | result = {} 23 | size = winreg.QueryInfoKey(key)[1] 24 | for i in range(size): 25 | data = winreg.EnumValue(key, i) 26 | result[data[0]] = data[1] 27 | return result 28 | 29 | 30 | def _get_dst_info(tz): 31 | # Find the offset for when it doesn't have DST: 32 | dst_offset = std_offset = None 33 | has_dst = False 34 | year = datetime.now().year 35 | for dt in (datetime(year, 1, 1), datetime(year, 6, 1)): 36 | if tz.dst(dt).total_seconds() == 0.0: 37 | # OK, no DST during winter, get this offset 38 | std_offset = tz.utcoffset(dt).total_seconds() 39 | else: 40 | has_dst = True 41 | 42 | return has_dst, std_offset, dst_offset 43 | 44 | 45 | def _get_localzone_name(): 46 | # Windows is special. It has unique time zone names (in several 47 | # meanings of the word) available, but unfortunately, they can be 48 | # translated to the language of the operating system, so we need to 49 | # do a backwards lookup, by going through all time zones and see which 50 | # one matches. 51 | tzenv = utils._tz_name_from_env() 52 | if tzenv: 53 | return tzenv 54 | 55 | log.debug("Looking up time zone info from registry") 56 | handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) 57 | 58 | TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation" 59 | localtz = winreg.OpenKey(handle, TZLOCALKEYNAME) 60 | keyvalues = valuestodict(localtz) 61 | localtz.Close() 62 | 63 | if "TimeZoneKeyName" in keyvalues: 64 | # Windows 7 and later 65 | 66 | # For some reason this returns a string with loads of NUL bytes at 67 | # least on some systems. I don't know if this is a bug somewhere, I 68 | # just work around it. 69 | tzkeyname = keyvalues["TimeZoneKeyName"].split("\x00", 1)[0] 70 | else: 71 | # Don't support XP any longer 72 | raise LookupError("Can not find Windows timezone configuration") 73 | 74 | timezone = win_tz.get(tzkeyname) 75 | if timezone is None: 76 | # Nope, that didn't work. Try adding "Standard Time", 77 | # it seems to work a lot of times: 78 | timezone = win_tz.get(tzkeyname + " Standard Time") 79 | 80 | # Return what we have. 81 | if timezone is None: 82 | raise zoneinfo.ZoneInfoNotFoundError(tzkeyname) 83 | 84 | if keyvalues.get("DynamicDaylightTimeDisabled", 0) == 1: 85 | # DST is disabled, so don't return the timezone name, 86 | # instead return Etc/GMT+offset 87 | 88 | tz = zoneinfo.ZoneInfo(timezone) 89 | has_dst, std_offset, dst_offset = _get_dst_info(tz) 90 | if not has_dst: 91 | # The DST is turned off in the windows configuration, 92 | # but this timezone doesn't have DST so it doesn't matter 93 | return timezone 94 | 95 | if std_offset is None: 96 | raise zoneinfo.ZoneInfoNotFoundError( 97 | f"{tzkeyname} claims to not have a non-DST time!?" 98 | ) 99 | 100 | if std_offset % 3600: 101 | # I can't convert this to an hourly offset 102 | raise zoneinfo.ZoneInfoNotFoundError( 103 | f"tzlocal can't support disabling DST in the {timezone} zone." 104 | ) 105 | 106 | # This has whole hours as offset, return it as Etc/GMT 107 | return f"Etc/GMT{-std_offset//3600:+.0f}" 108 | 109 | return timezone 110 | 111 | 112 | def get_localzone_name() -> str: 113 | """Get the zoneinfo timezone name that matches the Windows-configured timezone.""" 114 | global _cache_tz_name 115 | if _cache_tz_name is None: 116 | _cache_tz_name = _get_localzone_name() 117 | 118 | return _cache_tz_name 119 | 120 | 121 | def get_localzone() -> zoneinfo.ZoneInfo: 122 | """Returns the zoneinfo-based tzinfo object that matches the Windows-configured timezone.""" 123 | 124 | global _cache_tz 125 | if _cache_tz is None: 126 | _cache_tz = zoneinfo.ZoneInfo(get_localzone_name()) 127 | 128 | if not utils._tz_name_from_env(): 129 | # If the timezone does NOT come from a TZ environment variable, 130 | # verify that it's correct. If it's from the environment, 131 | # we accept it, this is so you can run tests with different timezones. 132 | utils.assert_tz_offset(_cache_tz, error=False) 133 | 134 | return _cache_tz 135 | 136 | 137 | def reload_localzone() -> zoneinfo.ZoneInfo: 138 | """Reload the cached localzone. You need to call this if the timezone has changed.""" 139 | global _cache_tz 140 | global _cache_tz_name 141 | _cache_tz_name = _get_localzone_name() 142 | _cache_tz = zoneinfo.ZoneInfo(_cache_tz_name) 143 | utils.assert_tz_offset(_cache_tz, error=False) 144 | return _cache_tz 145 | -------------------------------------------------------------------------------- /tzlocal/windows_tz.py: -------------------------------------------------------------------------------- 1 | # This file is autogenerated by the update_windows_mapping.py script 2 | # Do not edit. 3 | win_tz = { 4 | "AUS Central Standard Time": "Australia/Darwin", 5 | "AUS Eastern Standard Time": "Australia/Sydney", 6 | "Afghanistan Standard Time": "Asia/Kabul", 7 | "Alaskan Standard Time": "America/Anchorage", 8 | "Aleutian Standard Time": "America/Adak", 9 | "Altai Standard Time": "Asia/Barnaul", 10 | "Arab Standard Time": "Asia/Riyadh", 11 | "Arabian Standard Time": "Asia/Dubai", 12 | "Arabic Standard Time": "Asia/Baghdad", 13 | "Argentina Standard Time": "America/Buenos_Aires", 14 | "Astrakhan Standard Time": "Europe/Astrakhan", 15 | "Atlantic Standard Time": "America/Halifax", 16 | "Aus Central W. Standard Time": "Australia/Eucla", 17 | "Azerbaijan Standard Time": "Asia/Baku", 18 | "Azores Standard Time": "Atlantic/Azores", 19 | "Bahia Standard Time": "America/Bahia", 20 | "Bangladesh Standard Time": "Asia/Dhaka", 21 | "Belarus Standard Time": "Europe/Minsk", 22 | "Bougainville Standard Time": "Pacific/Bougainville", 23 | "Canada Central Standard Time": "America/Regina", 24 | "Cape Verde Standard Time": "Atlantic/Cape_Verde", 25 | "Caucasus Standard Time": "Asia/Yerevan", 26 | "Cen. Australia Standard Time": "Australia/Adelaide", 27 | "Central America Standard Time": "America/Guatemala", 28 | "Central Asia Standard Time": "Asia/Almaty", 29 | "Central Brazilian Standard Time": "America/Cuiaba", 30 | "Central Europe Standard Time": "Europe/Budapest", 31 | "Central European Standard Time": "Europe/Warsaw", 32 | "Central Pacific Standard Time": "Pacific/Guadalcanal", 33 | "Central Standard Time": "America/Chicago", 34 | "Central Standard Time (Mexico)": "America/Mexico_City", 35 | "Chatham Islands Standard Time": "Pacific/Chatham", 36 | "China Standard Time": "Asia/Shanghai", 37 | "Cuba Standard Time": "America/Havana", 38 | "Dateline Standard Time": "Etc/GMT+12", 39 | "E. Africa Standard Time": "Africa/Nairobi", 40 | "E. Australia Standard Time": "Australia/Brisbane", 41 | "E. Europe Standard Time": "Europe/Chisinau", 42 | "E. South America Standard Time": "America/Sao_Paulo", 43 | "Easter Island Standard Time": "Pacific/Easter", 44 | "Eastern Standard Time": "America/New_York", 45 | "Eastern Standard Time (Mexico)": "America/Cancun", 46 | "Egypt Standard Time": "Africa/Cairo", 47 | "Ekaterinburg Standard Time": "Asia/Yekaterinburg", 48 | "FLE Standard Time": "Europe/Kiev", 49 | "Fiji Standard Time": "Pacific/Fiji", 50 | "GMT Standard Time": "Europe/London", 51 | "GTB Standard Time": "Europe/Bucharest", 52 | "Georgian Standard Time": "Asia/Tbilisi", 53 | "Greenland Standard Time": "America/Godthab", 54 | "Greenwich Standard Time": "Atlantic/Reykjavik", 55 | "Haiti Standard Time": "America/Port-au-Prince", 56 | "Hawaiian Standard Time": "Pacific/Honolulu", 57 | "India Standard Time": "Asia/Calcutta", 58 | "Iran Standard Time": "Asia/Tehran", 59 | "Israel Standard Time": "Asia/Jerusalem", 60 | "Jordan Standard Time": "Asia/Amman", 61 | "Kaliningrad Standard Time": "Europe/Kaliningrad", 62 | "Korea Standard Time": "Asia/Seoul", 63 | "Libya Standard Time": "Africa/Tripoli", 64 | "Line Islands Standard Time": "Pacific/Kiritimati", 65 | "Lord Howe Standard Time": "Australia/Lord_Howe", 66 | "Magadan Standard Time": "Asia/Magadan", 67 | "Magallanes Standard Time": "America/Punta_Arenas", 68 | "Marquesas Standard Time": "Pacific/Marquesas", 69 | "Mauritius Standard Time": "Indian/Mauritius", 70 | "Middle East Standard Time": "Asia/Beirut", 71 | "Montevideo Standard Time": "America/Montevideo", 72 | "Morocco Standard Time": "Africa/Casablanca", 73 | "Mountain Standard Time": "America/Denver", 74 | "Mountain Standard Time (Mexico)": "America/Mazatlan", 75 | "Myanmar Standard Time": "Asia/Rangoon", 76 | "N. Central Asia Standard Time": "Asia/Novosibirsk", 77 | "Namibia Standard Time": "Africa/Windhoek", 78 | "Nepal Standard Time": "Asia/Katmandu", 79 | "New Zealand Standard Time": "Pacific/Auckland", 80 | "Newfoundland Standard Time": "America/St_Johns", 81 | "Norfolk Standard Time": "Pacific/Norfolk", 82 | "North Asia East Standard Time": "Asia/Irkutsk", 83 | "North Asia Standard Time": "Asia/Krasnoyarsk", 84 | "North Korea Standard Time": "Asia/Pyongyang", 85 | "Omsk Standard Time": "Asia/Omsk", 86 | "Pacific SA Standard Time": "America/Santiago", 87 | "Pacific Standard Time": "America/Los_Angeles", 88 | "Pacific Standard Time (Mexico)": "America/Tijuana", 89 | "Pakistan Standard Time": "Asia/Karachi", 90 | "Paraguay Standard Time": "America/Asuncion", 91 | "Qyzylorda Standard Time": "Asia/Qyzylorda", 92 | "Romance Standard Time": "Europe/Paris", 93 | "Russia Time Zone 10": "Asia/Srednekolymsk", 94 | "Russia Time Zone 11": "Asia/Kamchatka", 95 | "Russia Time Zone 3": "Europe/Samara", 96 | "Russian Standard Time": "Europe/Moscow", 97 | "SA Eastern Standard Time": "America/Cayenne", 98 | "SA Pacific Standard Time": "America/Bogota", 99 | "SA Western Standard Time": "America/La_Paz", 100 | "SE Asia Standard Time": "Asia/Bangkok", 101 | "Saint Pierre Standard Time": "America/Miquelon", 102 | "Sakhalin Standard Time": "Asia/Sakhalin", 103 | "Samoa Standard Time": "Pacific/Apia", 104 | "Sao Tome Standard Time": "Africa/Sao_Tome", 105 | "Saratov Standard Time": "Europe/Saratov", 106 | "Singapore Standard Time": "Asia/Singapore", 107 | "South Africa Standard Time": "Africa/Johannesburg", 108 | "South Sudan Standard Time": "Africa/Juba", 109 | "Sri Lanka Standard Time": "Asia/Colombo", 110 | "Sudan Standard Time": "Africa/Khartoum", 111 | "Syria Standard Time": "Asia/Damascus", 112 | "Taipei Standard Time": "Asia/Taipei", 113 | "Tasmania Standard Time": "Australia/Hobart", 114 | "Tocantins Standard Time": "America/Araguaina", 115 | "Tokyo Standard Time": "Asia/Tokyo", 116 | "Tomsk Standard Time": "Asia/Tomsk", 117 | "Tonga Standard Time": "Pacific/Tongatapu", 118 | "Transbaikal Standard Time": "Asia/Chita", 119 | "Turkey Standard Time": "Europe/Istanbul", 120 | "Turks And Caicos Standard Time": "America/Grand_Turk", 121 | "US Eastern Standard Time": "America/Indianapolis", 122 | "US Mountain Standard Time": "America/Phoenix", 123 | "UTC": "Etc/UTC", 124 | "UTC+12": "Etc/GMT-12", 125 | "UTC+13": "Etc/GMT-13", 126 | "UTC-02": "Etc/GMT+2", 127 | "UTC-08": "Etc/GMT+8", 128 | "UTC-09": "Etc/GMT+9", 129 | "UTC-11": "Etc/GMT+11", 130 | "Ulaanbaatar Standard Time": "Asia/Ulaanbaatar", 131 | "Venezuela Standard Time": "America/Caracas", 132 | "Vladivostok Standard Time": "Asia/Vladivostok", 133 | "Volgograd Standard Time": "Europe/Volgograd", 134 | "W. Australia Standard Time": "Australia/Perth", 135 | "W. Central Africa Standard Time": "Africa/Lagos", 136 | "W. Europe Standard Time": "Europe/Berlin", 137 | "W. Mongolia Standard Time": "Asia/Hovd", 138 | "West Asia Standard Time": "Asia/Tashkent", 139 | "West Bank Standard Time": "Asia/Hebron", 140 | "West Pacific Standard Time": "Pacific/Port_Moresby", 141 | "Yakutsk Standard Time": "Asia/Yakutsk", 142 | "Yukon Standard Time": "America/Whitehorse", 143 | } 144 | 145 | # Old name for the win_tz variable: 146 | tz_names = win_tz 147 | 148 | tz_win = { 149 | "": "Central Standard Time (Mexico)", 150 | "Africa/Abidjan": "Greenwich Standard Time", 151 | "Africa/Accra": "Greenwich Standard Time", 152 | "Africa/Addis_Ababa": "E. Africa Standard Time", 153 | "Africa/Algiers": "W. Central Africa Standard Time", 154 | "Africa/Asmara": "E. Africa Standard Time", 155 | "Africa/Asmera": "E. Africa Standard Time", 156 | "Africa/Bamako": "Greenwich Standard Time", 157 | "Africa/Bangui": "W. Central Africa Standard Time", 158 | "Africa/Banjul": "Greenwich Standard Time", 159 | "Africa/Bissau": "Greenwich Standard Time", 160 | "Africa/Blantyre": "South Africa Standard Time", 161 | "Africa/Brazzaville": "W. Central Africa Standard Time", 162 | "Africa/Bujumbura": "South Africa Standard Time", 163 | "Africa/Cairo": "Egypt Standard Time", 164 | "Africa/Casablanca": "Morocco Standard Time", 165 | "Africa/Ceuta": "Romance Standard Time", 166 | "Africa/Conakry": "Greenwich Standard Time", 167 | "Africa/Dakar": "Greenwich Standard Time", 168 | "Africa/Dar_es_Salaam": "E. Africa Standard Time", 169 | "Africa/Djibouti": "E. Africa Standard Time", 170 | "Africa/Douala": "W. Central Africa Standard Time", 171 | "Africa/El_Aaiun": "Morocco Standard Time", 172 | "Africa/Freetown": "Greenwich Standard Time", 173 | "Africa/Gaborone": "South Africa Standard Time", 174 | "Africa/Harare": "South Africa Standard Time", 175 | "Africa/Johannesburg": "South Africa Standard Time", 176 | "Africa/Juba": "South Sudan Standard Time", 177 | "Africa/Kampala": "E. Africa Standard Time", 178 | "Africa/Khartoum": "Sudan Standard Time", 179 | "Africa/Kigali": "South Africa Standard Time", 180 | "Africa/Kinshasa": "W. Central Africa Standard Time", 181 | "Africa/Lagos": "W. Central Africa Standard Time", 182 | "Africa/Libreville": "W. Central Africa Standard Time", 183 | "Africa/Lome": "Greenwich Standard Time", 184 | "Africa/Luanda": "W. Central Africa Standard Time", 185 | "Africa/Lubumbashi": "South Africa Standard Time", 186 | "Africa/Lusaka": "South Africa Standard Time", 187 | "Africa/Malabo": "W. Central Africa Standard Time", 188 | "Africa/Maputo": "South Africa Standard Time", 189 | "Africa/Maseru": "South Africa Standard Time", 190 | "Africa/Mbabane": "South Africa Standard Time", 191 | "Africa/Mogadishu": "E. Africa Standard Time", 192 | "Africa/Monrovia": "Greenwich Standard Time", 193 | "Africa/Nairobi": "E. Africa Standard Time", 194 | "Africa/Ndjamena": "W. Central Africa Standard Time", 195 | "Africa/Niamey": "W. Central Africa Standard Time", 196 | "Africa/Nouakchott": "Greenwich Standard Time", 197 | "Africa/Ouagadougou": "Greenwich Standard Time", 198 | "Africa/Porto-Novo": "W. Central Africa Standard Time", 199 | "Africa/Sao_Tome": "Sao Tome Standard Time", 200 | "Africa/Timbuktu": "Greenwich Standard Time", 201 | "Africa/Tripoli": "Libya Standard Time", 202 | "Africa/Tunis": "W. Central Africa Standard Time", 203 | "Africa/Windhoek": "Namibia Standard Time", 204 | "America/Adak": "Aleutian Standard Time", 205 | "America/Anchorage": "Alaskan Standard Time", 206 | "America/Anguilla": "SA Western Standard Time", 207 | "America/Antigua": "SA Western Standard Time", 208 | "America/Araguaina": "Tocantins Standard Time", 209 | "America/Argentina/Buenos_Aires": "Argentina Standard Time", 210 | "America/Argentina/Catamarca": "Argentina Standard Time", 211 | "America/Argentina/ComodRivadavia": "Argentina Standard Time", 212 | "America/Argentina/Cordoba": "Argentina Standard Time", 213 | "America/Argentina/Jujuy": "Argentina Standard Time", 214 | "America/Argentina/La_Rioja": "Argentina Standard Time", 215 | "America/Argentina/Mendoza": "Argentina Standard Time", 216 | "America/Argentina/Rio_Gallegos": "Argentina Standard Time", 217 | "America/Argentina/Salta": "Argentina Standard Time", 218 | "America/Argentina/San_Juan": "Argentina Standard Time", 219 | "America/Argentina/San_Luis": "Argentina Standard Time", 220 | "America/Argentina/Tucuman": "Argentina Standard Time", 221 | "America/Argentina/Ushuaia": "Argentina Standard Time", 222 | "America/Aruba": "SA Western Standard Time", 223 | "America/Asuncion": "Paraguay Standard Time", 224 | "America/Atikokan": "SA Pacific Standard Time", 225 | "America/Atka": "Aleutian Standard Time", 226 | "America/Bahia": "Bahia Standard Time", 227 | "America/Bahia_Banderas": "Central Standard Time (Mexico)", 228 | "America/Barbados": "SA Western Standard Time", 229 | "America/Belem": "SA Eastern Standard Time", 230 | "America/Belize": "Central America Standard Time", 231 | "America/Blanc-Sablon": "SA Western Standard Time", 232 | "America/Boa_Vista": "SA Western Standard Time", 233 | "America/Bogota": "SA Pacific Standard Time", 234 | "America/Boise": "Mountain Standard Time", 235 | "America/Buenos_Aires": "Argentina Standard Time", 236 | "America/Cambridge_Bay": "Mountain Standard Time", 237 | "America/Campo_Grande": "Central Brazilian Standard Time", 238 | "America/Cancun": "Eastern Standard Time (Mexico)", 239 | "America/Caracas": "Venezuela Standard Time", 240 | "America/Catamarca": "Argentina Standard Time", 241 | "America/Cayenne": "SA Eastern Standard Time", 242 | "America/Cayman": "SA Pacific Standard Time", 243 | "America/Chicago": "Central Standard Time", 244 | "America/Chihuahua": "Central Standard Time (Mexico)", 245 | "America/Ciudad_Juarez": "Mountain Standard Time", 246 | "America/Coral_Harbour": "SA Pacific Standard Time", 247 | "America/Cordoba": "Argentina Standard Time", 248 | "America/Costa_Rica": "Central America Standard Time", 249 | "America/Creston": "US Mountain Standard Time", 250 | "America/Cuiaba": "Central Brazilian Standard Time", 251 | "America/Curacao": "SA Western Standard Time", 252 | "America/Danmarkshavn": "Greenwich Standard Time", 253 | "America/Dawson": "Yukon Standard Time", 254 | "America/Dawson_Creek": "US Mountain Standard Time", 255 | "America/Denver": "Mountain Standard Time", 256 | "America/Detroit": "Eastern Standard Time", 257 | "America/Dominica": "SA Western Standard Time", 258 | "America/Edmonton": "Mountain Standard Time", 259 | "America/Eirunepe": "SA Pacific Standard Time", 260 | "America/El_Salvador": "Central America Standard Time", 261 | "America/Ensenada": "Pacific Standard Time (Mexico)", 262 | "America/Fort_Nelson": "US Mountain Standard Time", 263 | "America/Fort_Wayne": "US Eastern Standard Time", 264 | "America/Fortaleza": "SA Eastern Standard Time", 265 | "America/Glace_Bay": "Atlantic Standard Time", 266 | "America/Godthab": "Greenland Standard Time", 267 | "America/Goose_Bay": "Atlantic Standard Time", 268 | "America/Grand_Turk": "Turks And Caicos Standard Time", 269 | "America/Grenada": "SA Western Standard Time", 270 | "America/Guadeloupe": "SA Western Standard Time", 271 | "America/Guatemala": "Central America Standard Time", 272 | "America/Guayaquil": "SA Pacific Standard Time", 273 | "America/Guyana": "SA Western Standard Time", 274 | "America/Halifax": "Atlantic Standard Time", 275 | "America/Havana": "Cuba Standard Time", 276 | "America/Hermosillo": "US Mountain Standard Time", 277 | "America/Indiana/Indianapolis": "US Eastern Standard Time", 278 | "America/Indiana/Knox": "Central Standard Time", 279 | "America/Indiana/Marengo": "US Eastern Standard Time", 280 | "America/Indiana/Petersburg": "Eastern Standard Time", 281 | "America/Indiana/Tell_City": "Central Standard Time", 282 | "America/Indiana/Vevay": "US Eastern Standard Time", 283 | "America/Indiana/Vincennes": "Eastern Standard Time", 284 | "America/Indiana/Winamac": "Eastern Standard Time", 285 | "America/Indianapolis": "US Eastern Standard Time", 286 | "America/Inuvik": "Mountain Standard Time", 287 | "America/Iqaluit": "Eastern Standard Time", 288 | "America/Jamaica": "SA Pacific Standard Time", 289 | "America/Jujuy": "Argentina Standard Time", 290 | "America/Juneau": "Alaskan Standard Time", 291 | "America/Kentucky/Louisville": "Eastern Standard Time", 292 | "America/Kentucky/Monticello": "Eastern Standard Time", 293 | "America/Knox_IN": "Central Standard Time", 294 | "America/Kralendijk": "SA Western Standard Time", 295 | "America/La_Paz": "SA Western Standard Time", 296 | "America/Lima": "SA Pacific Standard Time", 297 | "America/Los_Angeles": "Pacific Standard Time", 298 | "America/Louisville": "Eastern Standard Time", 299 | "America/Lower_Princes": "SA Western Standard Time", 300 | "America/Maceio": "SA Eastern Standard Time", 301 | "America/Managua": "Central America Standard Time", 302 | "America/Manaus": "SA Western Standard Time", 303 | "America/Marigot": "SA Western Standard Time", 304 | "America/Martinique": "SA Western Standard Time", 305 | "America/Matamoros": "Central Standard Time", 306 | "America/Mazatlan": "Mountain Standard Time (Mexico)", 307 | "America/Mendoza": "Argentina Standard Time", 308 | "America/Menominee": "Central Standard Time", 309 | "America/Merida": "Central Standard Time (Mexico)", 310 | "America/Metlakatla": "Alaskan Standard Time", 311 | "America/Mexico_City": "Central Standard Time (Mexico)", 312 | "America/Miquelon": "Saint Pierre Standard Time", 313 | "America/Moncton": "Atlantic Standard Time", 314 | "America/Monterrey": "Central Standard Time (Mexico)", 315 | "America/Montevideo": "Montevideo Standard Time", 316 | "America/Montreal": "Eastern Standard Time", 317 | "America/Montserrat": "SA Western Standard Time", 318 | "America/Nassau": "Eastern Standard Time", 319 | "America/New_York": "Eastern Standard Time", 320 | "America/Nipigon": "Eastern Standard Time", 321 | "America/Nome": "Alaskan Standard Time", 322 | "America/Noronha": "UTC-02", 323 | "America/North_Dakota/Beulah": "Central Standard Time", 324 | "America/North_Dakota/Center": "Central Standard Time", 325 | "America/North_Dakota/New_Salem": "Central Standard Time", 326 | "America/Nuuk": "Greenland Standard Time", 327 | "America/Ojinaga": "Central Standard Time", 328 | "America/Panama": "SA Pacific Standard Time", 329 | "America/Pangnirtung": "Eastern Standard Time", 330 | "America/Paramaribo": "SA Eastern Standard Time", 331 | "America/Phoenix": "US Mountain Standard Time", 332 | "America/Port-au-Prince": "Haiti Standard Time", 333 | "America/Port_of_Spain": "SA Western Standard Time", 334 | "America/Porto_Acre": "SA Pacific Standard Time", 335 | "America/Porto_Velho": "SA Western Standard Time", 336 | "America/Puerto_Rico": "SA Western Standard Time", 337 | "America/Punta_Arenas": "Magallanes Standard Time", 338 | "America/Rainy_River": "Central Standard Time", 339 | "America/Rankin_Inlet": "Central Standard Time", 340 | "America/Recife": "SA Eastern Standard Time", 341 | "America/Regina": "Canada Central Standard Time", 342 | "America/Resolute": "Central Standard Time", 343 | "America/Rio_Branco": "SA Pacific Standard Time", 344 | "America/Rosario": "Argentina Standard Time", 345 | "America/Santa_Isabel": "Pacific Standard Time (Mexico)", 346 | "America/Santarem": "SA Eastern Standard Time", 347 | "America/Santiago": "Pacific SA Standard Time", 348 | "America/Santo_Domingo": "SA Western Standard Time", 349 | "America/Sao_Paulo": "E. South America Standard Time", 350 | "America/Scoresbysund": "Azores Standard Time", 351 | "America/Shiprock": "Mountain Standard Time", 352 | "America/Sitka": "Alaskan Standard Time", 353 | "America/St_Barthelemy": "SA Western Standard Time", 354 | "America/St_Johns": "Newfoundland Standard Time", 355 | "America/St_Kitts": "SA Western Standard Time", 356 | "America/St_Lucia": "SA Western Standard Time", 357 | "America/St_Thomas": "SA Western Standard Time", 358 | "America/St_Vincent": "SA Western Standard Time", 359 | "America/Swift_Current": "Canada Central Standard Time", 360 | "America/Tegucigalpa": "Central America Standard Time", 361 | "America/Thule": "Atlantic Standard Time", 362 | "America/Thunder_Bay": "Eastern Standard Time", 363 | "America/Tijuana": "Pacific Standard Time (Mexico)", 364 | "America/Toronto": "Eastern Standard Time", 365 | "America/Tortola": "SA Western Standard Time", 366 | "America/Vancouver": "Pacific Standard Time", 367 | "America/Virgin": "SA Western Standard Time", 368 | "America/Whitehorse": "Yukon Standard Time", 369 | "America/Winnipeg": "Central Standard Time", 370 | "America/Yakutat": "Alaskan Standard Time", 371 | "America/Yellowknife": "Mountain Standard Time", 372 | "Antarctica/Casey": "Central Pacific Standard Time", 373 | "Antarctica/Davis": "SE Asia Standard Time", 374 | "Antarctica/DumontDUrville": "West Pacific Standard Time", 375 | "Antarctica/Macquarie": "Tasmania Standard Time", 376 | "Antarctica/Mawson": "West Asia Standard Time", 377 | "Antarctica/McMurdo": "New Zealand Standard Time", 378 | "Antarctica/Palmer": "SA Eastern Standard Time", 379 | "Antarctica/Rothera": "SA Eastern Standard Time", 380 | "Antarctica/South_Pole": "New Zealand Standard Time", 381 | "Antarctica/Syowa": "E. Africa Standard Time", 382 | "Antarctica/Vostok": "Central Asia Standard Time", 383 | "Arctic/Longyearbyen": "W. Europe Standard Time", 384 | "Asia/Aden": "Arab Standard Time", 385 | "Asia/Almaty": "Central Asia Standard Time", 386 | "Asia/Amman": "Jordan Standard Time", 387 | "Asia/Anadyr": "Russia Time Zone 11", 388 | "Asia/Aqtau": "West Asia Standard Time", 389 | "Asia/Aqtobe": "West Asia Standard Time", 390 | "Asia/Ashgabat": "West Asia Standard Time", 391 | "Asia/Ashkhabad": "West Asia Standard Time", 392 | "Asia/Atyrau": "West Asia Standard Time", 393 | "Asia/Baghdad": "Arabic Standard Time", 394 | "Asia/Bahrain": "Arab Standard Time", 395 | "Asia/Baku": "Azerbaijan Standard Time", 396 | "Asia/Bangkok": "SE Asia Standard Time", 397 | "Asia/Barnaul": "Altai Standard Time", 398 | "Asia/Beirut": "Middle East Standard Time", 399 | "Asia/Bishkek": "Central Asia Standard Time", 400 | "Asia/Brunei": "Singapore Standard Time", 401 | "Asia/Calcutta": "India Standard Time", 402 | "Asia/Chita": "Transbaikal Standard Time", 403 | "Asia/Choibalsan": "Ulaanbaatar Standard Time", 404 | "Asia/Chongqing": "China Standard Time", 405 | "Asia/Chungking": "China Standard Time", 406 | "Asia/Colombo": "Sri Lanka Standard Time", 407 | "Asia/Dacca": "Bangladesh Standard Time", 408 | "Asia/Damascus": "Syria Standard Time", 409 | "Asia/Dhaka": "Bangladesh Standard Time", 410 | "Asia/Dili": "Tokyo Standard Time", 411 | "Asia/Dubai": "Arabian Standard Time", 412 | "Asia/Dushanbe": "West Asia Standard Time", 413 | "Asia/Famagusta": "GTB Standard Time", 414 | "Asia/Gaza": "West Bank Standard Time", 415 | "Asia/Harbin": "China Standard Time", 416 | "Asia/Hebron": "West Bank Standard Time", 417 | "Asia/Ho_Chi_Minh": "SE Asia Standard Time", 418 | "Asia/Hong_Kong": "China Standard Time", 419 | "Asia/Hovd": "W. Mongolia Standard Time", 420 | "Asia/Irkutsk": "North Asia East Standard Time", 421 | "Asia/Istanbul": "Turkey Standard Time", 422 | "Asia/Jakarta": "SE Asia Standard Time", 423 | "Asia/Jayapura": "Tokyo Standard Time", 424 | "Asia/Jerusalem": "Israel Standard Time", 425 | "Asia/Kabul": "Afghanistan Standard Time", 426 | "Asia/Kamchatka": "Russia Time Zone 11", 427 | "Asia/Karachi": "Pakistan Standard Time", 428 | "Asia/Kashgar": "Central Asia Standard Time", 429 | "Asia/Kathmandu": "Nepal Standard Time", 430 | "Asia/Katmandu": "Nepal Standard Time", 431 | "Asia/Khandyga": "Yakutsk Standard Time", 432 | "Asia/Kolkata": "India Standard Time", 433 | "Asia/Krasnoyarsk": "North Asia Standard Time", 434 | "Asia/Kuala_Lumpur": "Singapore Standard Time", 435 | "Asia/Kuching": "Singapore Standard Time", 436 | "Asia/Kuwait": "Arab Standard Time", 437 | "Asia/Macao": "China Standard Time", 438 | "Asia/Macau": "China Standard Time", 439 | "Asia/Magadan": "Magadan Standard Time", 440 | "Asia/Makassar": "Singapore Standard Time", 441 | "Asia/Manila": "Singapore Standard Time", 442 | "Asia/Muscat": "Arabian Standard Time", 443 | "Asia/Nicosia": "GTB Standard Time", 444 | "Asia/Novokuznetsk": "North Asia Standard Time", 445 | "Asia/Novosibirsk": "N. Central Asia Standard Time", 446 | "Asia/Omsk": "Omsk Standard Time", 447 | "Asia/Oral": "West Asia Standard Time", 448 | "Asia/Phnom_Penh": "SE Asia Standard Time", 449 | "Asia/Pontianak": "SE Asia Standard Time", 450 | "Asia/Pyongyang": "North Korea Standard Time", 451 | "Asia/Qatar": "Arab Standard Time", 452 | "Asia/Qostanay": "Central Asia Standard Time", 453 | "Asia/Qyzylorda": "Qyzylorda Standard Time", 454 | "Asia/Rangoon": "Myanmar Standard Time", 455 | "Asia/Riyadh": "Arab Standard Time", 456 | "Asia/Saigon": "SE Asia Standard Time", 457 | "Asia/Sakhalin": "Sakhalin Standard Time", 458 | "Asia/Samarkand": "West Asia Standard Time", 459 | "Asia/Seoul": "Korea Standard Time", 460 | "Asia/Shanghai": "China Standard Time", 461 | "Asia/Singapore": "Singapore Standard Time", 462 | "Asia/Srednekolymsk": "Russia Time Zone 10", 463 | "Asia/Taipei": "Taipei Standard Time", 464 | "Asia/Tashkent": "West Asia Standard Time", 465 | "Asia/Tbilisi": "Georgian Standard Time", 466 | "Asia/Tehran": "Iran Standard Time", 467 | "Asia/Tel_Aviv": "Israel Standard Time", 468 | "Asia/Thimbu": "Bangladesh Standard Time", 469 | "Asia/Thimphu": "Bangladesh Standard Time", 470 | "Asia/Tokyo": "Tokyo Standard Time", 471 | "Asia/Tomsk": "Tomsk Standard Time", 472 | "Asia/Ujung_Pandang": "Singapore Standard Time", 473 | "Asia/Ulaanbaatar": "Ulaanbaatar Standard Time", 474 | "Asia/Ulan_Bator": "Ulaanbaatar Standard Time", 475 | "Asia/Urumqi": "Central Asia Standard Time", 476 | "Asia/Ust-Nera": "Vladivostok Standard Time", 477 | "Asia/Vientiane": "SE Asia Standard Time", 478 | "Asia/Vladivostok": "Vladivostok Standard Time", 479 | "Asia/Yakutsk": "Yakutsk Standard Time", 480 | "Asia/Yangon": "Myanmar Standard Time", 481 | "Asia/Yekaterinburg": "Ekaterinburg Standard Time", 482 | "Asia/Yerevan": "Caucasus Standard Time", 483 | "Atlantic/Azores": "Azores Standard Time", 484 | "Atlantic/Bermuda": "Atlantic Standard Time", 485 | "Atlantic/Canary": "GMT Standard Time", 486 | "Atlantic/Cape_Verde": "Cape Verde Standard Time", 487 | "Atlantic/Faeroe": "GMT Standard Time", 488 | "Atlantic/Faroe": "GMT Standard Time", 489 | "Atlantic/Jan_Mayen": "W. Europe Standard Time", 490 | "Atlantic/Madeira": "GMT Standard Time", 491 | "Atlantic/Reykjavik": "Greenwich Standard Time", 492 | "Atlantic/South_Georgia": "UTC-02", 493 | "Atlantic/St_Helena": "Greenwich Standard Time", 494 | "Atlantic/Stanley": "SA Eastern Standard Time", 495 | "Australia/ACT": "AUS Eastern Standard Time", 496 | "Australia/Adelaide": "Cen. Australia Standard Time", 497 | "Australia/Brisbane": "E. Australia Standard Time", 498 | "Australia/Broken_Hill": "Cen. Australia Standard Time", 499 | "Australia/Canberra": "AUS Eastern Standard Time", 500 | "Australia/Currie": "Tasmania Standard Time", 501 | "Australia/Darwin": "AUS Central Standard Time", 502 | "Australia/Eucla": "Aus Central W. Standard Time", 503 | "Australia/Hobart": "Tasmania Standard Time", 504 | "Australia/LHI": "Lord Howe Standard Time", 505 | "Australia/Lindeman": "E. Australia Standard Time", 506 | "Australia/Lord_Howe": "Lord Howe Standard Time", 507 | "Australia/Melbourne": "AUS Eastern Standard Time", 508 | "Australia/NSW": "AUS Eastern Standard Time", 509 | "Australia/North": "AUS Central Standard Time", 510 | "Australia/Perth": "W. Australia Standard Time", 511 | "Australia/Queensland": "E. Australia Standard Time", 512 | "Australia/South": "Cen. Australia Standard Time", 513 | "Australia/Sydney": "AUS Eastern Standard Time", 514 | "Australia/Tasmania": "Tasmania Standard Time", 515 | "Australia/Victoria": "AUS Eastern Standard Time", 516 | "Australia/West": "W. Australia Standard Time", 517 | "Australia/Yancowinna": "Cen. Australia Standard Time", 518 | "Brazil/Acre": "SA Pacific Standard Time", 519 | "Brazil/DeNoronha": "UTC-02", 520 | "Brazil/East": "E. South America Standard Time", 521 | "Brazil/West": "SA Western Standard Time", 522 | "CST6CDT": "Central Standard Time", 523 | "Canada/Atlantic": "Atlantic Standard Time", 524 | "Canada/Central": "Central Standard Time", 525 | "Canada/Eastern": "Eastern Standard Time", 526 | "Canada/Mountain": "Mountain Standard Time", 527 | "Canada/Newfoundland": "Newfoundland Standard Time", 528 | "Canada/Pacific": "Pacific Standard Time", 529 | "Canada/Saskatchewan": "Canada Central Standard Time", 530 | "Canada/Yukon": "Yukon Standard Time", 531 | "Chile/Continental": "Pacific SA Standard Time", 532 | "Chile/EasterIsland": "Easter Island Standard Time", 533 | "Cuba": "Cuba Standard Time", 534 | "EST5EDT": "Eastern Standard Time", 535 | "Egypt": "Egypt Standard Time", 536 | "Eire": "GMT Standard Time", 537 | "Etc/GMT": "UTC", 538 | "Etc/GMT+0": "UTC", 539 | "Etc/GMT+1": "Cape Verde Standard Time", 540 | "Etc/GMT+10": "Hawaiian Standard Time", 541 | "Etc/GMT+11": "UTC-11", 542 | "Etc/GMT+12": "Dateline Standard Time", 543 | "Etc/GMT+2": "UTC-02", 544 | "Etc/GMT+3": "SA Eastern Standard Time", 545 | "Etc/GMT+4": "SA Western Standard Time", 546 | "Etc/GMT+5": "SA Pacific Standard Time", 547 | "Etc/GMT+6": "Central America Standard Time", 548 | "Etc/GMT+7": "US Mountain Standard Time", 549 | "Etc/GMT+8": "UTC-08", 550 | "Etc/GMT+9": "UTC-09", 551 | "Etc/GMT-0": "UTC", 552 | "Etc/GMT-1": "W. Central Africa Standard Time", 553 | "Etc/GMT-10": "West Pacific Standard Time", 554 | "Etc/GMT-11": "Central Pacific Standard Time", 555 | "Etc/GMT-12": "UTC+12", 556 | "Etc/GMT-13": "UTC+13", 557 | "Etc/GMT-14": "Line Islands Standard Time", 558 | "Etc/GMT-2": "South Africa Standard Time", 559 | "Etc/GMT-3": "E. Africa Standard Time", 560 | "Etc/GMT-4": "Arabian Standard Time", 561 | "Etc/GMT-5": "West Asia Standard Time", 562 | "Etc/GMT-6": "Central Asia Standard Time", 563 | "Etc/GMT-7": "SE Asia Standard Time", 564 | "Etc/GMT-8": "Singapore Standard Time", 565 | "Etc/GMT-9": "Tokyo Standard Time", 566 | "Etc/GMT0": "UTC", 567 | "Etc/Greenwich": "UTC", 568 | "Etc/UCT": "UTC", 569 | "Etc/UTC": "UTC", 570 | "Etc/Universal": "UTC", 571 | "Etc/Zulu": "UTC", 572 | "Europe/Amsterdam": "W. Europe Standard Time", 573 | "Europe/Andorra": "W. Europe Standard Time", 574 | "Europe/Astrakhan": "Astrakhan Standard Time", 575 | "Europe/Athens": "GTB Standard Time", 576 | "Europe/Belfast": "GMT Standard Time", 577 | "Europe/Belgrade": "Central Europe Standard Time", 578 | "Europe/Berlin": "W. Europe Standard Time", 579 | "Europe/Bratislava": "Central Europe Standard Time", 580 | "Europe/Brussels": "Romance Standard Time", 581 | "Europe/Bucharest": "GTB Standard Time", 582 | "Europe/Budapest": "Central Europe Standard Time", 583 | "Europe/Busingen": "W. Europe Standard Time", 584 | "Europe/Chisinau": "E. Europe Standard Time", 585 | "Europe/Copenhagen": "Romance Standard Time", 586 | "Europe/Dublin": "GMT Standard Time", 587 | "Europe/Gibraltar": "W. Europe Standard Time", 588 | "Europe/Guernsey": "GMT Standard Time", 589 | "Europe/Helsinki": "FLE Standard Time", 590 | "Europe/Isle_of_Man": "GMT Standard Time", 591 | "Europe/Istanbul": "Turkey Standard Time", 592 | "Europe/Jersey": "GMT Standard Time", 593 | "Europe/Kaliningrad": "Kaliningrad Standard Time", 594 | "Europe/Kiev": "FLE Standard Time", 595 | "Europe/Kirov": "Russian Standard Time", 596 | "Europe/Kyiv": "FLE Standard Time", 597 | "Europe/Lisbon": "GMT Standard Time", 598 | "Europe/Ljubljana": "Central Europe Standard Time", 599 | "Europe/London": "GMT Standard Time", 600 | "Europe/Luxembourg": "W. Europe Standard Time", 601 | "Europe/Madrid": "Romance Standard Time", 602 | "Europe/Malta": "W. Europe Standard Time", 603 | "Europe/Mariehamn": "FLE Standard Time", 604 | "Europe/Minsk": "Belarus Standard Time", 605 | "Europe/Monaco": "W. Europe Standard Time", 606 | "Europe/Moscow": "Russian Standard Time", 607 | "Europe/Nicosia": "GTB Standard Time", 608 | "Europe/Oslo": "W. Europe Standard Time", 609 | "Europe/Paris": "Romance Standard Time", 610 | "Europe/Podgorica": "Central Europe Standard Time", 611 | "Europe/Prague": "Central Europe Standard Time", 612 | "Europe/Riga": "FLE Standard Time", 613 | "Europe/Rome": "W. Europe Standard Time", 614 | "Europe/Samara": "Russia Time Zone 3", 615 | "Europe/San_Marino": "W. Europe Standard Time", 616 | "Europe/Sarajevo": "Central European Standard Time", 617 | "Europe/Saratov": "Saratov Standard Time", 618 | "Europe/Simferopol": "Russian Standard Time", 619 | "Europe/Skopje": "Central European Standard Time", 620 | "Europe/Sofia": "FLE Standard Time", 621 | "Europe/Stockholm": "W. Europe Standard Time", 622 | "Europe/Tallinn": "FLE Standard Time", 623 | "Europe/Tirane": "Central Europe Standard Time", 624 | "Europe/Tiraspol": "E. Europe Standard Time", 625 | "Europe/Ulyanovsk": "Astrakhan Standard Time", 626 | "Europe/Uzhgorod": "FLE Standard Time", 627 | "Europe/Vaduz": "W. Europe Standard Time", 628 | "Europe/Vatican": "W. Europe Standard Time", 629 | "Europe/Vienna": "W. Europe Standard Time", 630 | "Europe/Vilnius": "FLE Standard Time", 631 | "Europe/Volgograd": "Volgograd Standard Time", 632 | "Europe/Warsaw": "Central European Standard Time", 633 | "Europe/Zagreb": "Central European Standard Time", 634 | "Europe/Zaporozhye": "FLE Standard Time", 635 | "Europe/Zurich": "W. Europe Standard Time", 636 | "GB": "GMT Standard Time", 637 | "GB-Eire": "GMT Standard Time", 638 | "GMT+0": "UTC", 639 | "GMT-0": "UTC", 640 | "GMT0": "UTC", 641 | "Greenwich": "UTC", 642 | "Hongkong": "China Standard Time", 643 | "Iceland": "Greenwich Standard Time", 644 | "Indian/Antananarivo": "E. Africa Standard Time", 645 | "Indian/Chagos": "Central Asia Standard Time", 646 | "Indian/Christmas": "SE Asia Standard Time", 647 | "Indian/Cocos": "Myanmar Standard Time", 648 | "Indian/Comoro": "E. Africa Standard Time", 649 | "Indian/Kerguelen": "West Asia Standard Time", 650 | "Indian/Mahe": "Mauritius Standard Time", 651 | "Indian/Maldives": "West Asia Standard Time", 652 | "Indian/Mauritius": "Mauritius Standard Time", 653 | "Indian/Mayotte": "E. Africa Standard Time", 654 | "Indian/Reunion": "Mauritius Standard Time", 655 | "Iran": "Iran Standard Time", 656 | "Israel": "Israel Standard Time", 657 | "Jamaica": "SA Pacific Standard Time", 658 | "Japan": "Tokyo Standard Time", 659 | "Kwajalein": "UTC+12", 660 | "Libya": "Libya Standard Time", 661 | "MST7MDT": "Mountain Standard Time", 662 | "Mexico/BajaNorte": "Pacific Standard Time (Mexico)", 663 | "Mexico/BajaSur": "Mountain Standard Time (Mexico)", 664 | "Mexico/General": "Central Standard Time (Mexico)", 665 | "NZ": "New Zealand Standard Time", 666 | "NZ-CHAT": "Chatham Islands Standard Time", 667 | "Navajo": "Mountain Standard Time", 668 | "PRC": "China Standard Time", 669 | "PST8PDT": "Pacific Standard Time", 670 | "Pacific/Apia": "Samoa Standard Time", 671 | "Pacific/Auckland": "New Zealand Standard Time", 672 | "Pacific/Bougainville": "Bougainville Standard Time", 673 | "Pacific/Chatham": "Chatham Islands Standard Time", 674 | "Pacific/Chuuk": "West Pacific Standard Time", 675 | "Pacific/Easter": "Easter Island Standard Time", 676 | "Pacific/Efate": "Central Pacific Standard Time", 677 | "Pacific/Enderbury": "UTC+13", 678 | "Pacific/Fakaofo": "UTC+13", 679 | "Pacific/Fiji": "Fiji Standard Time", 680 | "Pacific/Funafuti": "UTC+12", 681 | "Pacific/Galapagos": "Central America Standard Time", 682 | "Pacific/Gambier": "UTC-09", 683 | "Pacific/Guadalcanal": "Central Pacific Standard Time", 684 | "Pacific/Guam": "West Pacific Standard Time", 685 | "Pacific/Honolulu": "Hawaiian Standard Time", 686 | "Pacific/Johnston": "Hawaiian Standard Time", 687 | "Pacific/Kanton": "UTC+13", 688 | "Pacific/Kiritimati": "Line Islands Standard Time", 689 | "Pacific/Kosrae": "Central Pacific Standard Time", 690 | "Pacific/Kwajalein": "UTC+12", 691 | "Pacific/Majuro": "UTC+12", 692 | "Pacific/Marquesas": "Marquesas Standard Time", 693 | "Pacific/Midway": "UTC-11", 694 | "Pacific/Nauru": "UTC+12", 695 | "Pacific/Niue": "UTC-11", 696 | "Pacific/Norfolk": "Norfolk Standard Time", 697 | "Pacific/Noumea": "Central Pacific Standard Time", 698 | "Pacific/Pago_Pago": "UTC-11", 699 | "Pacific/Palau": "Tokyo Standard Time", 700 | "Pacific/Pitcairn": "UTC-08", 701 | "Pacific/Pohnpei": "Central Pacific Standard Time", 702 | "Pacific/Ponape": "Central Pacific Standard Time", 703 | "Pacific/Port_Moresby": "West Pacific Standard Time", 704 | "Pacific/Rarotonga": "Hawaiian Standard Time", 705 | "Pacific/Saipan": "West Pacific Standard Time", 706 | "Pacific/Samoa": "UTC-11", 707 | "Pacific/Tahiti": "Hawaiian Standard Time", 708 | "Pacific/Tarawa": "UTC+12", 709 | "Pacific/Tongatapu": "Tonga Standard Time", 710 | "Pacific/Truk": "West Pacific Standard Time", 711 | "Pacific/Wake": "UTC+12", 712 | "Pacific/Wallis": "UTC+12", 713 | "Pacific/Yap": "West Pacific Standard Time", 714 | "Poland": "Central European Standard Time", 715 | "Portugal": "GMT Standard Time", 716 | "ROC": "Taipei Standard Time", 717 | "ROK": "Korea Standard Time", 718 | "Singapore": "Singapore Standard Time", 719 | "Turkey": "Turkey Standard Time", 720 | "UCT": "UTC", 721 | "US/Alaska": "Alaskan Standard Time", 722 | "US/Aleutian": "Aleutian Standard Time", 723 | "US/Arizona": "US Mountain Standard Time", 724 | "US/Central": "Central Standard Time", 725 | "US/Eastern": "Eastern Standard Time", 726 | "US/Hawaii": "Hawaiian Standard Time", 727 | "US/Indiana-Starke": "Central Standard Time", 728 | "US/Michigan": "Eastern Standard Time", 729 | "US/Mountain": "Mountain Standard Time", 730 | "US/Pacific": "Pacific Standard Time", 731 | "US/Samoa": "UTC-11", 732 | "UTC": "UTC", 733 | "Universal": "UTC", 734 | "W-SU": "Russian Standard Time", 735 | "Zulu": "UTC", 736 | } 737 | -------------------------------------------------------------------------------- /update_windows_mappings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # This script generates the mapping between MS Windows timezone names and 4 | # tzdata/Olsen timezone names, by retrieving a file: 5 | # http://unicode.org/cldr/data/common/supplemental/supplementalData.xml 6 | # and parsing it, and from this generating the file windows_tz.py. 7 | # 8 | # It must be run with Python 3. 9 | 10 | import ftplib 11 | import logging 12 | import tarfile 13 | from io import BytesIO 14 | from pprint import pprint 15 | from urllib.parse import urlparse 16 | from urllib.request import urlopen 17 | from xml.dom import minidom 18 | 19 | WIN_ZONES_URL = "https://raw.githubusercontent.com/unicode-org/cldr/master/common/supplemental/windowsZones.xml" 20 | ZONEINFO_URL = "ftp://ftp.iana.org/tz/tzdata-latest.tar.gz" 21 | 22 | logging.basicConfig(level=logging.INFO) 23 | log = logging.getLogger("tzlocal") 24 | 25 | 26 | def update_old_names(): 27 | """Fetches the list of old tz names and returns a mapping""" 28 | 29 | url = urlparse(ZONEINFO_URL) 30 | log.info("Connecting to %s" % url.netloc) 31 | ftp = ftplib.FTP(url.netloc) 32 | ftp.login() 33 | gzfile = BytesIO() 34 | 35 | log.info("Fetching zoneinfo database") 36 | ftp.retrbinary("RETR " + url.path, gzfile.write) 37 | gzfile.seek(0) 38 | 39 | log.info("Extracting backwards data") 40 | archive = tarfile.open(mode="r:gz", fileobj=gzfile) 41 | backward = {} 42 | for line in archive.extractfile("backward").readlines(): 43 | if line[0] == "#": 44 | continue 45 | if len(line.strip()) == 0: 46 | continue 47 | parts = line.split() 48 | if parts[0] != b"Link": 49 | continue 50 | 51 | backward[parts[2].decode("ascii")] = parts[1].decode("ascii") 52 | 53 | return backward 54 | 55 | 56 | def update_windows_zones(): 57 | backward = update_old_names() 58 | 59 | log.info("Fetching Windows mapping info from unicode.org") 60 | source = urlopen(WIN_ZONES_URL).read() 61 | dom = minidom.parseString(source) 62 | 63 | for element in dom.getElementsByTagName("mapTimezones"): 64 | if element.getAttribute("type") == "windows": 65 | break 66 | 67 | log.info("Making windows mapping") 68 | win_tz = {} 69 | tz_win = {} 70 | for mapping in element.getElementsByTagName("mapZone"): 71 | if mapping.getAttribute("territory") == "001": 72 | win_tz[mapping.getAttribute("other")] = mapping.getAttribute("type").split( 73 | " " 74 | )[0] 75 | if win_tz[mapping.getAttribute("other")].startswith("Etc"): 76 | print( 77 | win_tz[mapping.getAttribute("other")], 78 | mapping.getAttribute("type").split(" ")[0], 79 | ) 80 | 81 | for tz_name in mapping.getAttribute("type").split(" "): 82 | tz_win[tz_name] = mapping.getAttribute("other") 83 | 84 | log.info("Adding backwards and forwards data") 85 | # Map in the backwards (or forwards) compatible zone names 86 | for backward_compat_name, standard_name in backward.items(): 87 | if backward_compat_name not in tz_win: 88 | win_zone = tz_win.get(standard_name, None) 89 | if win_zone: 90 | tz_win[backward_compat_name] = win_zone 91 | if standard_name not in tz_win: 92 | win_zone = tz_win.get(backward_compat_name, None) 93 | if win_zone: 94 | tz_win[standard_name] = win_zone 95 | 96 | # Etc/UTC is a common but non-standard alias for Etc/GMT: 97 | tz_win["Etc/UTC"] = "UTC" 98 | 99 | log.info("Writing mapping") 100 | with open("tzlocal/windows_tz.py", "w") as out: 101 | out.write( 102 | "# This file is autogenerated by the update_windows_mapping.py script\n" 103 | "# Do not edit.\nwin_tz = " 104 | ) 105 | pprint(win_tz, out) 106 | out.write( 107 | "\n# Old name for the win_tz variable:\ntz_names = win_tz\n\ntz_win = " 108 | ) 109 | pprint(tz_win, out) 110 | 111 | log.info("Done") 112 | 113 | 114 | if __name__ == "__main__": 115 | update_windows_zones() 116 | --------------------------------------------------------------------------------