From f3fcb47427beb9ba9e4a8ee022c14b2d9d1788ff Mon Sep 17 00:00:00 2001 From: Labrys of Knossos Date: Sat, 15 Dec 2018 01:12:12 -0500 Subject: [PATCH] Update subliminal to 2.0.5 Also updates: - appdirs-1.4.3 - babelfish-0.5.5 - beautifulsoup4-4.6.3 - certifi-2018.11.29 - chardet-3.0.4 - click-7.0 - decorator-4.3.0 - dogpile.cache-0.7.1 - enzyme-0.4.1 - guessit-3.0.3 - idna-2.8 - pbr-5.1.1 - pysrt-1.1.1 - python-dateutil-2.7.5 - pytz-2018.7 - rarfile-3.0 - rebulk-1.0.0 - requests-2.21.0 - six-1.12.0 - stevedore-1.30.0 - urllib3-1.24.1 --- libs/appdirs.py | 608 +++ libs/bin/guessit.exe | Bin 93036 -> 93036 bytes libs/bin/pbr.exe | Bin 0 -> 93032 bytes libs/bin/srt.exe | Bin 0 -> 93034 bytes libs/bin/subliminal.exe | Bin 0 -> 93046 bytes libs/bs4/__init__.py | 123 +- libs/bs4/builder/__init__.py | 23 +- libs/bs4/builder/_html5lib.py | 116 +- libs/bs4/builder/_htmlparser.py | 112 +- libs/bs4/builder/_lxml.py | 40 +- libs/bs4/dammit.py | 22 +- libs/bs4/diagnose.py | 72 +- libs/bs4/element.py | 322 +- libs/bs4/testing.py | 129 +- libs/bs4/tests/test_html5lib.py | 27 +- libs/bs4/tests/test_htmlparser.py | 17 + libs/bs4/tests/test_lxml.py | 10 +- libs/bs4/tests/test_soup.py | 64 +- libs/bs4/tests/test_tree.py | 164 +- libs/click/__init__.py | 97 + libs/click/_bashcomplete.py | 293 ++ libs/click/_compat.py | 703 +++ libs/click/_termui_impl.py | 621 +++ libs/click/_textwrap.py | 38 + libs/click/_unicodefun.py | 125 + libs/click/_winconsole.py | 307 ++ libs/click/core.py | 1856 ++++++++ libs/click/decorators.py | 311 ++ libs/click/exceptions.py | 235 + libs/click/formatting.py | 256 + libs/click/globals.py | 48 + libs/click/parser.py | 427 ++ libs/click/termui.py | 606 +++ libs/click/testing.py | 374 ++ libs/click/types.py | 668 +++ libs/click/utils.py | 440 ++ libs/decorator.py | 432 ++ libs/dogpile/__init__.py | 10 +- libs/dogpile/cache/__init__.py | 5 +- libs/dogpile/cache/api.py | 54 +- libs/dogpile/cache/backends/__init__.py | 28 +- libs/dogpile/cache/backends/file.py | 52 +- libs/dogpile/cache/backends/memcached.py | 76 +- libs/dogpile/cache/backends/memory.py | 16 +- libs/dogpile/cache/backends/null.py | 6 +- libs/dogpile/cache/backends/redis.py | 18 +- libs/dogpile/cache/exception.py | 8 + libs/dogpile/cache/plugins/mako_cache.py | 15 +- libs/dogpile/cache/proxy.py | 10 +- libs/dogpile/cache/region.py | 712 ++- libs/dogpile/cache/util.py | 208 +- libs/dogpile/core.py | 17 + libs/dogpile/core/__init__.py | 11 - libs/dogpile/core/legacy.py | 154 - libs/dogpile/core/util.py | 8 - libs/dogpile/{core/dogpile.py => lock.py} | 123 +- libs/dogpile/util/__init__.py | 4 + libs/dogpile/{cache => util}/compat.py | 41 +- libs/dogpile/util/langhelpers.py | 123 + libs/dogpile/{core => util}/nameregistry.py | 11 +- libs/dogpile/{core => util}/readwrite_lock.py | 38 +- libs/enzyme/__init__.py | 2 +- libs/enzyme/tests/test_mkv.py | 28 +- libs/pbr/__init__.py | 0 libs/pbr/builddoc.py | 292 ++ libs/pbr/cmd/__init__.py | 0 libs/pbr/cmd/main.py | 112 + libs/pbr/core.py | 145 + libs/pbr/extra_files.py | 35 + libs/pbr/find_package.py | 29 + libs/pbr/git.py | 331 ++ libs/pbr/hooks/__init__.py | 28 + libs/pbr/hooks/backwards.py | 33 + libs/pbr/hooks/base.py | 34 + libs/pbr/hooks/commands.py | 66 + libs/pbr/hooks/files.py | 103 + libs/pbr/hooks/metadata.py | 32 + libs/pbr/options.py | 53 + libs/pbr/packaging.py | 855 ++++ libs/pbr/pbr_json.py | 34 + libs/pbr/sphinxext.py | 99 + libs/pbr/testr_command.py | 167 + libs/pbr/tests/__init__.py | 26 + libs/pbr/tests/base.py | 221 + libs/pbr/tests/test_commands.py | 84 + libs/pbr/tests/test_core.py | 151 + libs/pbr/tests/test_files.py | 78 + libs/pbr/tests/test_hooks.py | 75 + libs/pbr/tests/test_integration.py | 269 ++ libs/pbr/tests/test_packaging.py | 923 ++++ libs/pbr/tests/test_pbr_json.py | 30 + libs/pbr/tests/test_setup.py | 445 ++ libs/pbr/tests/test_util.py | 91 + libs/pbr/tests/test_version.py | 311 ++ libs/pbr/tests/test_wsgi.py | 163 + libs/pbr/tests/testpackage/CHANGES.txt | 86 + libs/pbr/tests/testpackage/LICENSE.txt | 29 + libs/pbr/tests/testpackage/MANIFEST.in | 2 + libs/pbr/tests/testpackage/README.txt | 148 + libs/pbr/tests/testpackage/data_files/a.txt | 0 libs/pbr/tests/testpackage/data_files/b.txt | 0 libs/pbr/tests/testpackage/data_files/c.rst | 0 libs/pbr/tests/testpackage/doc/source/conf.py | 74 + .../tests/testpackage/doc/source/index.rst | 23 + .../testpackage/doc/source/installation.rst | 12 + .../tests/testpackage/doc/source/usage.rst | 7 + libs/pbr/tests/testpackage/extra-file.txt | 0 libs/pbr/tests/testpackage/git-extra-file.txt | 0 .../testpackage/pbr_testpackage/__init__.py | 3 + .../pbr_testpackage/_setup_hooks.py | 65 + .../tests/testpackage/pbr_testpackage/cmd.py | 26 + .../testpackage/pbr_testpackage/extra.py | 0 .../pbr_testpackage/package_data/1.txt | 0 .../pbr_testpackage/package_data/2.txt | 0 .../tests/testpackage/pbr_testpackage/wsgi.py | 40 + libs/pbr/tests/testpackage/setup.py | 21 + libs/pbr/tests/testpackage/src/testext.c | 29 + .../tests/testpackage/test-requirements.txt | 2 + libs/pbr/tests/util.py | 78 + libs/pbr/util.py | 611 +++ libs/pbr/version.py | 483 ++ libs/pytz/__init__.py | 1527 ++++++ libs/pytz/exceptions.py | 48 + libs/pytz/lazy.py | 172 + libs/pytz/reference.py | 140 + libs/pytz/tzfile.py | 134 + libs/pytz/tzinfo.py | 577 +++ libs/pytz/zoneinfo/Africa/Abidjan | Bin 0 -> 156 bytes libs/pytz/zoneinfo/Africa/Accra | Bin 0 -> 828 bytes libs/pytz/zoneinfo/Africa/Addis_Ababa | Bin 0 -> 271 bytes libs/pytz/zoneinfo/Africa/Algiers | Bin 0 -> 751 bytes libs/pytz/zoneinfo/Africa/Asmara | Bin 0 -> 271 bytes libs/pytz/zoneinfo/Africa/Asmera | Bin 0 -> 271 bytes libs/pytz/zoneinfo/Africa/Bamako | Bin 0 -> 156 bytes libs/pytz/zoneinfo/Africa/Bangui | Bin 0 -> 157 bytes libs/pytz/zoneinfo/Africa/Banjul | Bin 0 -> 156 bytes libs/pytz/zoneinfo/Africa/Bissau | Bin 0 -> 194 bytes libs/pytz/zoneinfo/Africa/Blantyre | Bin 0 -> 157 bytes libs/pytz/zoneinfo/Africa/Brazzaville | Bin 0 -> 157 bytes libs/pytz/zoneinfo/Africa/Bujumbura | Bin 0 -> 157 bytes libs/pytz/zoneinfo/Africa/Cairo | Bin 0 -> 1963 bytes libs/pytz/zoneinfo/Africa/Casablanca | Bin 0 -> 969 bytes libs/pytz/zoneinfo/Africa/Ceuta | Bin 0 -> 2050 bytes libs/pytz/zoneinfo/Africa/Conakry | Bin 0 -> 156 bytes libs/pytz/zoneinfo/Africa/Dakar | Bin 0 -> 156 bytes libs/pytz/zoneinfo/Africa/Dar_es_Salaam | Bin 0 -> 271 bytes libs/pytz/zoneinfo/Africa/Djibouti | Bin 0 -> 271 bytes libs/pytz/zoneinfo/Africa/Douala | Bin 0 -> 157 bytes libs/pytz/zoneinfo/Africa/El_Aaiun | Bin 0 -> 839 bytes libs/pytz/zoneinfo/Africa/Freetown | Bin 0 -> 156 bytes libs/pytz/zoneinfo/Africa/Gaborone | Bin 0 -> 157 bytes libs/pytz/zoneinfo/Africa/Harare | Bin 0 -> 157 bytes libs/pytz/zoneinfo/Africa/Johannesburg | Bin 0 -> 262 bytes libs/pytz/zoneinfo/Africa/Juba | Bin 0 -> 669 bytes libs/pytz/zoneinfo/Africa/Kampala | Bin 0 -> 271 bytes libs/pytz/zoneinfo/Africa/Khartoum | Bin 0 -> 699 bytes libs/pytz/zoneinfo/Africa/Kigali | Bin 0 -> 157 bytes libs/pytz/zoneinfo/Africa/Kinshasa | Bin 0 -> 157 bytes libs/pytz/zoneinfo/Africa/Lagos | Bin 0 -> 157 bytes libs/pytz/zoneinfo/Africa/Libreville | Bin 0 -> 157 bytes libs/pytz/zoneinfo/Africa/Lome | Bin 0 -> 156 bytes libs/pytz/zoneinfo/Africa/Luanda | Bin 0 -> 157 bytes libs/pytz/zoneinfo/Africa/Lubumbashi | Bin 0 -> 157 bytes libs/pytz/zoneinfo/Africa/Lusaka | Bin 0 -> 157 bytes libs/pytz/zoneinfo/Africa/Malabo | Bin 0 -> 157 bytes libs/pytz/zoneinfo/Africa/Maputo | Bin 0 -> 157 bytes libs/pytz/zoneinfo/Africa/Maseru | Bin 0 -> 262 bytes libs/pytz/zoneinfo/Africa/Mbabane | Bin 0 -> 262 bytes libs/pytz/zoneinfo/Africa/Mogadishu | Bin 0 -> 271 bytes libs/pytz/zoneinfo/Africa/Monrovia | Bin 0 -> 224 bytes libs/pytz/zoneinfo/Africa/Nairobi | Bin 0 -> 271 bytes libs/pytz/zoneinfo/Africa/Ndjamena | Bin 0 -> 211 bytes libs/pytz/zoneinfo/Africa/Niamey | Bin 0 -> 157 bytes libs/pytz/zoneinfo/Africa/Nouakchott | Bin 0 -> 156 bytes libs/pytz/zoneinfo/Africa/Ouagadougou | Bin 0 -> 156 bytes libs/pytz/zoneinfo/Africa/Porto-Novo | Bin 0 -> 157 bytes libs/pytz/zoneinfo/Africa/Sao_Tome | Bin 0 -> 225 bytes libs/pytz/zoneinfo/Africa/Timbuktu | Bin 0 -> 156 bytes libs/pytz/zoneinfo/Africa/Tripoli | Bin 0 -> 641 bytes libs/pytz/zoneinfo/Africa/Tunis | Bin 0 -> 701 bytes libs/pytz/zoneinfo/Africa/Windhoek | Bin 0 -> 979 bytes libs/pytz/zoneinfo/America/Adak | Bin 0 -> 2356 bytes libs/pytz/zoneinfo/America/Anchorage | Bin 0 -> 2371 bytes libs/pytz/zoneinfo/America/Anguilla | Bin 0 -> 156 bytes libs/pytz/zoneinfo/America/Antigua | Bin 0 -> 156 bytes libs/pytz/zoneinfo/America/Araguaina | Bin 0 -> 896 bytes .../zoneinfo/America/Argentina/Buenos_Aires | Bin 0 -> 1100 bytes .../pytz/zoneinfo/America/Argentina/Catamarca | Bin 0 -> 1100 bytes .../zoneinfo/America/Argentina/ComodRivadavia | Bin 0 -> 1100 bytes libs/pytz/zoneinfo/America/Argentina/Cordoba | Bin 0 -> 1100 bytes libs/pytz/zoneinfo/America/Argentina/Jujuy | Bin 0 -> 1072 bytes libs/pytz/zoneinfo/America/Argentina/La_Rioja | Bin 0 -> 1114 bytes libs/pytz/zoneinfo/America/Argentina/Mendoza | Bin 0 -> 1100 bytes .../zoneinfo/America/Argentina/Rio_Gallegos | Bin 0 -> 1100 bytes libs/pytz/zoneinfo/America/Argentina/Salta | Bin 0 -> 1072 bytes libs/pytz/zoneinfo/America/Argentina/San_Juan | Bin 0 -> 1114 bytes libs/pytz/zoneinfo/America/Argentina/San_Luis | Bin 0 -> 1130 bytes libs/pytz/zoneinfo/America/Argentina/Tucuman | Bin 0 -> 1128 bytes libs/pytz/zoneinfo/America/Argentina/Ushuaia | Bin 0 -> 1100 bytes libs/pytz/zoneinfo/America/Aruba | Bin 0 -> 198 bytes libs/pytz/zoneinfo/America/Asuncion | Bin 0 -> 2068 bytes libs/pytz/zoneinfo/America/Atikokan | Bin 0 -> 336 bytes libs/pytz/zoneinfo/America/Atka | Bin 0 -> 2356 bytes libs/pytz/zoneinfo/America/Bahia | Bin 0 -> 1036 bytes libs/pytz/zoneinfo/America/Bahia_Banderas | Bin 0 -> 1574 bytes libs/pytz/zoneinfo/America/Barbados | Bin 0 -> 330 bytes libs/pytz/zoneinfo/America/Belem | Bin 0 -> 588 bytes libs/pytz/zoneinfo/America/Belize | Bin 0 -> 964 bytes libs/pytz/zoneinfo/America/Blanc-Sablon | Bin 0 -> 298 bytes libs/pytz/zoneinfo/America/Boa_Vista | Bin 0 -> 644 bytes libs/pytz/zoneinfo/America/Bogota | Bin 0 -> 262 bytes libs/pytz/zoneinfo/America/Boise | Bin 0 -> 2394 bytes libs/pytz/zoneinfo/America/Buenos_Aires | Bin 0 -> 1100 bytes libs/pytz/zoneinfo/America/Cambridge_Bay | Bin 0 -> 2084 bytes libs/pytz/zoneinfo/America/Campo_Grande | Bin 0 -> 2002 bytes libs/pytz/zoneinfo/America/Cancun | Bin 0 -> 802 bytes libs/pytz/zoneinfo/America/Caracas | Bin 0 -> 280 bytes libs/pytz/zoneinfo/America/Catamarca | Bin 0 -> 1100 bytes libs/pytz/zoneinfo/America/Cayenne | Bin 0 -> 210 bytes libs/pytz/zoneinfo/America/Cayman | Bin 0 -> 194 bytes libs/pytz/zoneinfo/America/Chicago | Bin 0 -> 3576 bytes libs/pytz/zoneinfo/America/Chihuahua | Bin 0 -> 1508 bytes libs/pytz/zoneinfo/America/Coral_Harbour | Bin 0 -> 336 bytes libs/pytz/zoneinfo/America/Cordoba | Bin 0 -> 1100 bytes libs/pytz/zoneinfo/America/Costa_Rica | Bin 0 -> 332 bytes libs/pytz/zoneinfo/America/Creston | Bin 0 -> 224 bytes libs/pytz/zoneinfo/America/Cuiaba | Bin 0 -> 1974 bytes libs/pytz/zoneinfo/America/Curacao | Bin 0 -> 198 bytes libs/pytz/zoneinfo/America/Danmarkshavn | Bin 0 -> 698 bytes libs/pytz/zoneinfo/America/Dawson | Bin 0 -> 2084 bytes libs/pytz/zoneinfo/America/Dawson_Creek | Bin 0 -> 1050 bytes libs/pytz/zoneinfo/America/Denver | Bin 0 -> 2444 bytes libs/pytz/zoneinfo/America/Detroit | Bin 0 -> 2174 bytes libs/pytz/zoneinfo/America/Dominica | Bin 0 -> 156 bytes libs/pytz/zoneinfo/America/Edmonton | Bin 0 -> 2388 bytes libs/pytz/zoneinfo/America/Eirunepe | Bin 0 -> 676 bytes libs/pytz/zoneinfo/America/El_Salvador | Bin 0 -> 236 bytes libs/pytz/zoneinfo/America/Ensenada | Bin 0 -> 2342 bytes libs/pytz/zoneinfo/America/Fort_Nelson | Bin 0 -> 2240 bytes libs/pytz/zoneinfo/America/Fort_Wayne | Bin 0 -> 1666 bytes libs/pytz/zoneinfo/America/Fortaleza | Bin 0 -> 728 bytes libs/pytz/zoneinfo/America/Glace_Bay | Bin 0 -> 2192 bytes libs/pytz/zoneinfo/America/Godthab | Bin 0 -> 1878 bytes libs/pytz/zoneinfo/America/Goose_Bay | Bin 0 -> 3210 bytes libs/pytz/zoneinfo/America/Grand_Turk | Bin 0 -> 1872 bytes libs/pytz/zoneinfo/America/Grenada | Bin 0 -> 156 bytes libs/pytz/zoneinfo/America/Guadeloupe | Bin 0 -> 156 bytes libs/pytz/zoneinfo/America/Guatemala | Bin 0 -> 292 bytes libs/pytz/zoneinfo/America/Guayaquil | Bin 0 -> 262 bytes libs/pytz/zoneinfo/America/Guyana | Bin 0 -> 252 bytes libs/pytz/zoneinfo/America/Halifax | Bin 0 -> 3424 bytes libs/pytz/zoneinfo/America/Havana | Bin 0 -> 2428 bytes libs/pytz/zoneinfo/America/Hermosillo | Bin 0 -> 440 bytes .../zoneinfo/America/Indiana/Indianapolis | Bin 0 -> 1666 bytes libs/pytz/zoneinfo/America/Indiana/Knox | Bin 0 -> 2428 bytes libs/pytz/zoneinfo/America/Indiana/Marengo | Bin 0 -> 1722 bytes libs/pytz/zoneinfo/America/Indiana/Petersburg | Bin 0 -> 1904 bytes libs/pytz/zoneinfo/America/Indiana/Tell_City | Bin 0 -> 1726 bytes libs/pytz/zoneinfo/America/Indiana/Vevay | Bin 0 -> 1414 bytes libs/pytz/zoneinfo/America/Indiana/Vincennes | Bin 0 -> 1694 bytes libs/pytz/zoneinfo/America/Indiana/Winamac | Bin 0 -> 1778 bytes libs/pytz/zoneinfo/America/Indianapolis | Bin 0 -> 1666 bytes libs/pytz/zoneinfo/America/Inuvik | Bin 0 -> 1914 bytes libs/pytz/zoneinfo/America/Iqaluit | Bin 0 -> 2032 bytes libs/pytz/zoneinfo/America/Jamaica | Bin 0 -> 498 bytes libs/pytz/zoneinfo/America/Jujuy | Bin 0 -> 1072 bytes libs/pytz/zoneinfo/America/Juneau | Bin 0 -> 2353 bytes .../pytz/zoneinfo/America/Kentucky/Louisville | Bin 0 -> 2772 bytes .../pytz/zoneinfo/America/Kentucky/Monticello | Bin 0 -> 2352 bytes libs/pytz/zoneinfo/America/Knox_IN | Bin 0 -> 2428 bytes libs/pytz/zoneinfo/America/Kralendijk | Bin 0 -> 198 bytes libs/pytz/zoneinfo/America/La_Paz | Bin 0 -> 248 bytes libs/pytz/zoneinfo/America/Lima | Bin 0 -> 422 bytes libs/pytz/zoneinfo/America/Los_Angeles | Bin 0 -> 2836 bytes libs/pytz/zoneinfo/America/Louisville | Bin 0 -> 2772 bytes libs/pytz/zoneinfo/America/Lower_Princes | Bin 0 -> 198 bytes libs/pytz/zoneinfo/America/Maceio | Bin 0 -> 756 bytes libs/pytz/zoneinfo/America/Managua | Bin 0 -> 454 bytes libs/pytz/zoneinfo/America/Manaus | Bin 0 -> 616 bytes libs/pytz/zoneinfo/America/Marigot | Bin 0 -> 156 bytes libs/pytz/zoneinfo/America/Martinique | Bin 0 -> 248 bytes libs/pytz/zoneinfo/America/Matamoros | Bin 0 -> 1402 bytes libs/pytz/zoneinfo/America/Mazatlan | Bin 0 -> 1550 bytes libs/pytz/zoneinfo/America/Mendoza | Bin 0 -> 1100 bytes libs/pytz/zoneinfo/America/Menominee | Bin 0 -> 2274 bytes libs/pytz/zoneinfo/America/Merida | Bin 0 -> 1442 bytes libs/pytz/zoneinfo/America/Metlakatla | Bin 0 -> 1409 bytes libs/pytz/zoneinfo/America/Mexico_City | Bin 0 -> 1604 bytes libs/pytz/zoneinfo/America/Miquelon | Bin 0 -> 1682 bytes libs/pytz/zoneinfo/America/Moncton | Bin 0 -> 3154 bytes libs/pytz/zoneinfo/America/Monterrey | Bin 0 -> 1402 bytes libs/pytz/zoneinfo/America/Montevideo | Bin 0 -> 1550 bytes libs/pytz/zoneinfo/America/Montreal | Bin 0 -> 3494 bytes libs/pytz/zoneinfo/America/Montserrat | Bin 0 -> 156 bytes libs/pytz/zoneinfo/America/Nassau | Bin 0 -> 2270 bytes libs/pytz/zoneinfo/America/New_York | Bin 0 -> 3536 bytes libs/pytz/zoneinfo/America/Nipigon | Bin 0 -> 2122 bytes libs/pytz/zoneinfo/America/Nome | Bin 0 -> 2367 bytes libs/pytz/zoneinfo/America/Noronha | Bin 0 -> 728 bytes .../pytz/zoneinfo/America/North_Dakota/Beulah | Bin 0 -> 2380 bytes .../pytz/zoneinfo/America/North_Dakota/Center | Bin 0 -> 2380 bytes .../zoneinfo/America/North_Dakota/New_Salem | Bin 0 -> 2380 bytes libs/pytz/zoneinfo/America/Ojinaga | Bin 0 -> 1508 bytes libs/pytz/zoneinfo/America/Panama | Bin 0 -> 194 bytes libs/pytz/zoneinfo/America/Pangnirtung | Bin 0 -> 2094 bytes libs/pytz/zoneinfo/America/Paramaribo | Bin 0 -> 282 bytes libs/pytz/zoneinfo/America/Phoenix | Bin 0 -> 344 bytes libs/pytz/zoneinfo/America/Port-au-Prince | Bin 0 -> 1446 bytes libs/pytz/zoneinfo/America/Port_of_Spain | Bin 0 -> 156 bytes libs/pytz/zoneinfo/America/Porto_Acre | Bin 0 -> 648 bytes libs/pytz/zoneinfo/America/Porto_Velho | Bin 0 -> 588 bytes libs/pytz/zoneinfo/America/Puerto_Rico | Bin 0 -> 246 bytes libs/pytz/zoneinfo/America/Punta_Arenas | Bin 0 -> 1902 bytes libs/pytz/zoneinfo/America/Rainy_River | Bin 0 -> 2122 bytes libs/pytz/zoneinfo/America/Rankin_Inlet | Bin 0 -> 1916 bytes libs/pytz/zoneinfo/America/Recife | Bin 0 -> 728 bytes libs/pytz/zoneinfo/America/Regina | Bin 0 -> 980 bytes libs/pytz/zoneinfo/America/Resolute | Bin 0 -> 1916 bytes libs/pytz/zoneinfo/America/Rio_Branco | Bin 0 -> 648 bytes libs/pytz/zoneinfo/America/Rosario | Bin 0 -> 1100 bytes libs/pytz/zoneinfo/America/Santa_Isabel | Bin 0 -> 2342 bytes libs/pytz/zoneinfo/America/Santarem | Bin 0 -> 618 bytes libs/pytz/zoneinfo/America/Santiago | Bin 0 -> 2529 bytes libs/pytz/zoneinfo/America/Santo_Domingo | Bin 0 -> 482 bytes libs/pytz/zoneinfo/America/Sao_Paulo | Bin 0 -> 2002 bytes libs/pytz/zoneinfo/America/Scoresbysund | Bin 0 -> 1916 bytes libs/pytz/zoneinfo/America/Shiprock | Bin 0 -> 2444 bytes libs/pytz/zoneinfo/America/Sitka | Bin 0 -> 2329 bytes libs/pytz/zoneinfo/America/St_Barthelemy | Bin 0 -> 156 bytes libs/pytz/zoneinfo/America/St_Johns | Bin 0 -> 3655 bytes libs/pytz/zoneinfo/America/St_Kitts | Bin 0 -> 156 bytes libs/pytz/zoneinfo/America/St_Lucia | Bin 0 -> 156 bytes libs/pytz/zoneinfo/America/St_Thomas | Bin 0 -> 156 bytes libs/pytz/zoneinfo/America/St_Vincent | Bin 0 -> 156 bytes libs/pytz/zoneinfo/America/Swift_Current | Bin 0 -> 560 bytes libs/pytz/zoneinfo/America/Tegucigalpa | Bin 0 -> 264 bytes libs/pytz/zoneinfo/America/Thule | Bin 0 -> 1514 bytes libs/pytz/zoneinfo/America/Thunder_Bay | Bin 0 -> 2202 bytes libs/pytz/zoneinfo/America/Tijuana | Bin 0 -> 2342 bytes libs/pytz/zoneinfo/America/Toronto | Bin 0 -> 3494 bytes libs/pytz/zoneinfo/America/Tortola | Bin 0 -> 156 bytes libs/pytz/zoneinfo/America/Vancouver | Bin 0 -> 2892 bytes libs/pytz/zoneinfo/America/Virgin | Bin 0 -> 156 bytes libs/pytz/zoneinfo/America/Whitehorse | Bin 0 -> 2084 bytes libs/pytz/zoneinfo/America/Winnipeg | Bin 0 -> 2882 bytes libs/pytz/zoneinfo/America/Yakutat | Bin 0 -> 2305 bytes libs/pytz/zoneinfo/America/Yellowknife | Bin 0 -> 1966 bytes libs/pytz/zoneinfo/Antarctica/Casey | Bin 0 -> 297 bytes libs/pytz/zoneinfo/Antarctica/Davis | Bin 0 -> 297 bytes libs/pytz/zoneinfo/Antarctica/DumontDUrville | Bin 0 -> 202 bytes libs/pytz/zoneinfo/Antarctica/Macquarie | Bin 0 -> 1534 bytes libs/pytz/zoneinfo/Antarctica/Mawson | Bin 0 -> 211 bytes libs/pytz/zoneinfo/Antarctica/McMurdo | Bin 0 -> 2451 bytes libs/pytz/zoneinfo/Antarctica/Palmer | Bin 0 -> 1418 bytes libs/pytz/zoneinfo/Antarctica/Rothera | Bin 0 -> 172 bytes libs/pytz/zoneinfo/Antarctica/South_Pole | Bin 0 -> 2451 bytes libs/pytz/zoneinfo/Antarctica/Syowa | Bin 0 -> 173 bytes libs/pytz/zoneinfo/Antarctica/Troll | Bin 0 -> 1162 bytes libs/pytz/zoneinfo/Antarctica/Vostok | Bin 0 -> 173 bytes libs/pytz/zoneinfo/Arctic/Longyearbyen | Bin 0 -> 2242 bytes libs/pytz/zoneinfo/Asia/Aden | Bin 0 -> 173 bytes libs/pytz/zoneinfo/Asia/Almaty | Bin 0 -> 1017 bytes libs/pytz/zoneinfo/Asia/Amman | Bin 0 -> 1863 bytes libs/pytz/zoneinfo/Asia/Anadyr | Bin 0 -> 1208 bytes libs/pytz/zoneinfo/Asia/Aqtau | Bin 0 -> 1003 bytes libs/pytz/zoneinfo/Asia/Aqtobe | Bin 0 -> 1033 bytes libs/pytz/zoneinfo/Asia/Ashgabat | Bin 0 -> 637 bytes libs/pytz/zoneinfo/Asia/Ashkhabad | Bin 0 -> 637 bytes libs/pytz/zoneinfo/Asia/Atyrau | Bin 0 -> 1011 bytes libs/pytz/zoneinfo/Asia/Baghdad | Bin 0 -> 995 bytes libs/pytz/zoneinfo/Asia/Bahrain | Bin 0 -> 211 bytes libs/pytz/zoneinfo/Asia/Baku | Bin 0 -> 1255 bytes libs/pytz/zoneinfo/Asia/Bangkok | Bin 0 -> 211 bytes libs/pytz/zoneinfo/Asia/Barnaul | Bin 0 -> 1241 bytes libs/pytz/zoneinfo/Asia/Beirut | Bin 0 -> 2166 bytes libs/pytz/zoneinfo/Asia/Bishkek | Bin 0 -> 999 bytes libs/pytz/zoneinfo/Asia/Brunei | Bin 0 -> 215 bytes libs/pytz/zoneinfo/Asia/Calcutta | Bin 0 -> 303 bytes libs/pytz/zoneinfo/Asia/Chita | Bin 0 -> 1243 bytes libs/pytz/zoneinfo/Asia/Choibalsan | Bin 0 -> 977 bytes libs/pytz/zoneinfo/Asia/Chongqing | Bin 0 -> 545 bytes libs/pytz/zoneinfo/Asia/Chungking | Bin 0 -> 545 bytes libs/pytz/zoneinfo/Asia/Colombo | Bin 0 -> 404 bytes libs/pytz/zoneinfo/Asia/Dacca | Bin 0 -> 361 bytes libs/pytz/zoneinfo/Asia/Damascus | Bin 0 -> 2306 bytes libs/pytz/zoneinfo/Asia/Dhaka | Bin 0 -> 361 bytes libs/pytz/zoneinfo/Asia/Dili | Bin 0 -> 239 bytes libs/pytz/zoneinfo/Asia/Dubai | Bin 0 -> 173 bytes libs/pytz/zoneinfo/Asia/Dushanbe | Bin 0 -> 607 bytes libs/pytz/zoneinfo/Asia/Famagusta | Bin 0 -> 2028 bytes libs/pytz/zoneinfo/Asia/Gaza | Bin 0 -> 2286 bytes libs/pytz/zoneinfo/Asia/Harbin | Bin 0 -> 545 bytes libs/pytz/zoneinfo/Asia/Hebron | Bin 0 -> 2314 bytes libs/pytz/zoneinfo/Asia/Ho_Chi_Minh | Bin 0 -> 375 bytes libs/pytz/zoneinfo/Asia/Hong_Kong | Bin 0 -> 1175 bytes libs/pytz/zoneinfo/Asia/Hovd | Bin 0 -> 907 bytes libs/pytz/zoneinfo/Asia/Irkutsk | Bin 0 -> 1267 bytes libs/pytz/zoneinfo/Asia/Istanbul | Bin 0 -> 2157 bytes libs/pytz/zoneinfo/Asia/Jakarta | Bin 0 -> 383 bytes libs/pytz/zoneinfo/Asia/Jayapura | Bin 0 -> 237 bytes libs/pytz/zoneinfo/Asia/Jerusalem | Bin 0 -> 2256 bytes libs/pytz/zoneinfo/Asia/Kabul | Bin 0 -> 220 bytes libs/pytz/zoneinfo/Asia/Kamchatka | Bin 0 -> 1184 bytes libs/pytz/zoneinfo/Asia/Karachi | Bin 0 -> 403 bytes libs/pytz/zoneinfo/Asia/Kashgar | Bin 0 -> 173 bytes libs/pytz/zoneinfo/Asia/Kathmandu | Bin 0 -> 224 bytes libs/pytz/zoneinfo/Asia/Katmandu | Bin 0 -> 224 bytes libs/pytz/zoneinfo/Asia/Khandyga | Bin 0 -> 1297 bytes libs/pytz/zoneinfo/Asia/Kolkata | Bin 0 -> 303 bytes libs/pytz/zoneinfo/Asia/Krasnoyarsk | Bin 0 -> 1229 bytes libs/pytz/zoneinfo/Asia/Kuala_Lumpur | Bin 0 -> 415 bytes libs/pytz/zoneinfo/Asia/Kuching | Bin 0 -> 507 bytes libs/pytz/zoneinfo/Asia/Kuwait | Bin 0 -> 173 bytes libs/pytz/zoneinfo/Asia/Macao | Bin 0 -> 1241 bytes libs/pytz/zoneinfo/Asia/Macau | Bin 0 -> 1241 bytes libs/pytz/zoneinfo/Asia/Magadan | Bin 0 -> 1244 bytes libs/pytz/zoneinfo/Asia/Makassar | Bin 0 -> 274 bytes libs/pytz/zoneinfo/Asia/Manila | Bin 0 -> 350 bytes libs/pytz/zoneinfo/Asia/Muscat | Bin 0 -> 173 bytes libs/pytz/zoneinfo/Asia/Nicosia | Bin 0 -> 2002 bytes libs/pytz/zoneinfo/Asia/Novokuznetsk | Bin 0 -> 1183 bytes libs/pytz/zoneinfo/Asia/Novosibirsk | Bin 0 -> 1241 bytes libs/pytz/zoneinfo/Asia/Omsk | Bin 0 -> 1229 bytes libs/pytz/zoneinfo/Asia/Oral | Bin 0 -> 1025 bytes libs/pytz/zoneinfo/Asia/Phnom_Penh | Bin 0 -> 211 bytes libs/pytz/zoneinfo/Asia/Pontianak | Bin 0 -> 381 bytes libs/pytz/zoneinfo/Asia/Pyongyang | Bin 0 -> 253 bytes libs/pytz/zoneinfo/Asia/Qatar | Bin 0 -> 211 bytes libs/pytz/zoneinfo/Asia/Qyzylorda | Bin 0 -> 1017 bytes libs/pytz/zoneinfo/Asia/Rangoon | Bin 0 -> 288 bytes libs/pytz/zoneinfo/Asia/Riyadh | Bin 0 -> 173 bytes libs/pytz/zoneinfo/Asia/Saigon | Bin 0 -> 375 bytes libs/pytz/zoneinfo/Asia/Sakhalin | Bin 0 -> 1220 bytes libs/pytz/zoneinfo/Asia/Samarkand | Bin 0 -> 605 bytes libs/pytz/zoneinfo/Asia/Seoul | Bin 0 -> 517 bytes libs/pytz/zoneinfo/Asia/Shanghai | Bin 0 -> 545 bytes libs/pytz/zoneinfo/Asia/Singapore | Bin 0 -> 415 bytes libs/pytz/zoneinfo/Asia/Srednekolymsk | Bin 0 -> 1230 bytes libs/pytz/zoneinfo/Asia/Taipei | Bin 0 -> 781 bytes libs/pytz/zoneinfo/Asia/Tashkent | Bin 0 -> 621 bytes libs/pytz/zoneinfo/Asia/Tbilisi | Bin 0 -> 1071 bytes libs/pytz/zoneinfo/Asia/Tehran | Bin 0 -> 1704 bytes libs/pytz/zoneinfo/Asia/Tel_Aviv | Bin 0 -> 2256 bytes libs/pytz/zoneinfo/Asia/Thimbu | Bin 0 -> 215 bytes libs/pytz/zoneinfo/Asia/Thimphu | Bin 0 -> 215 bytes libs/pytz/zoneinfo/Asia/Tokyo | Bin 0 -> 309 bytes libs/pytz/zoneinfo/Asia/Tomsk | Bin 0 -> 1241 bytes libs/pytz/zoneinfo/Asia/Ujung_Pandang | Bin 0 -> 274 bytes libs/pytz/zoneinfo/Asia/Ulaanbaatar | Bin 0 -> 907 bytes libs/pytz/zoneinfo/Asia/Ulan_Bator | Bin 0 -> 907 bytes libs/pytz/zoneinfo/Asia/Urumqi | Bin 0 -> 173 bytes libs/pytz/zoneinfo/Asia/Ust-Nera | Bin 0 -> 1276 bytes libs/pytz/zoneinfo/Asia/Vientiane | Bin 0 -> 211 bytes libs/pytz/zoneinfo/Asia/Vladivostok | Bin 0 -> 1230 bytes libs/pytz/zoneinfo/Asia/Yakutsk | Bin 0 -> 1229 bytes libs/pytz/zoneinfo/Asia/Yangon | Bin 0 -> 288 bytes libs/pytz/zoneinfo/Asia/Yekaterinburg | Bin 0 -> 1267 bytes libs/pytz/zoneinfo/Asia/Yerevan | Bin 0 -> 1199 bytes libs/pytz/zoneinfo/Atlantic/Azores | Bin 0 -> 3484 bytes libs/pytz/zoneinfo/Atlantic/Bermuda | Bin 0 -> 1990 bytes libs/pytz/zoneinfo/Atlantic/Canary | Bin 0 -> 1897 bytes libs/pytz/zoneinfo/Atlantic/Cape_Verde | Bin 0 -> 270 bytes libs/pytz/zoneinfo/Atlantic/Faeroe | Bin 0 -> 1815 bytes libs/pytz/zoneinfo/Atlantic/Faroe | Bin 0 -> 1815 bytes libs/pytz/zoneinfo/Atlantic/Jan_Mayen | Bin 0 -> 2242 bytes libs/pytz/zoneinfo/Atlantic/Madeira | Bin 0 -> 3475 bytes libs/pytz/zoneinfo/Atlantic/Reykjavik | Bin 0 -> 1174 bytes libs/pytz/zoneinfo/Atlantic/South_Georgia | Bin 0 -> 172 bytes libs/pytz/zoneinfo/Atlantic/St_Helena | Bin 0 -> 156 bytes libs/pytz/zoneinfo/Atlantic/Stanley | Bin 0 -> 1242 bytes libs/pytz/zoneinfo/Australia/ACT | Bin 0 -> 2214 bytes libs/pytz/zoneinfo/Australia/Adelaide | Bin 0 -> 2233 bytes libs/pytz/zoneinfo/Australia/Brisbane | Bin 0 -> 443 bytes libs/pytz/zoneinfo/Australia/Broken_Hill | Bin 0 -> 2269 bytes libs/pytz/zoneinfo/Australia/Canberra | Bin 0 -> 2214 bytes libs/pytz/zoneinfo/Australia/Currie | Bin 0 -> 2214 bytes libs/pytz/zoneinfo/Australia/Darwin | Bin 0 -> 318 bytes libs/pytz/zoneinfo/Australia/Eucla | Bin 0 -> 494 bytes libs/pytz/zoneinfo/Australia/Hobart | Bin 0 -> 2326 bytes libs/pytz/zoneinfo/Australia/LHI | Bin 0 -> 1880 bytes libs/pytz/zoneinfo/Australia/Lindeman | Bin 0 -> 513 bytes libs/pytz/zoneinfo/Australia/Lord_Howe | Bin 0 -> 1880 bytes libs/pytz/zoneinfo/Australia/Melbourne | Bin 0 -> 2214 bytes libs/pytz/zoneinfo/Australia/NSW | Bin 0 -> 2214 bytes libs/pytz/zoneinfo/Australia/North | Bin 0 -> 318 bytes libs/pytz/zoneinfo/Australia/Perth | Bin 0 -> 470 bytes libs/pytz/zoneinfo/Australia/Queensland | Bin 0 -> 443 bytes libs/pytz/zoneinfo/Australia/South | Bin 0 -> 2233 bytes libs/pytz/zoneinfo/Australia/Sydney | Bin 0 -> 2214 bytes libs/pytz/zoneinfo/Australia/Tasmania | Bin 0 -> 2326 bytes libs/pytz/zoneinfo/Australia/Victoria | Bin 0 -> 2214 bytes libs/pytz/zoneinfo/Australia/West | Bin 0 -> 470 bytes libs/pytz/zoneinfo/Australia/Yancowinna | Bin 0 -> 2269 bytes libs/pytz/zoneinfo/Brazil/Acre | Bin 0 -> 648 bytes libs/pytz/zoneinfo/Brazil/DeNoronha | Bin 0 -> 728 bytes libs/pytz/zoneinfo/Brazil/East | Bin 0 -> 2002 bytes libs/pytz/zoneinfo/Brazil/West | Bin 0 -> 616 bytes libs/pytz/zoneinfo/CET | Bin 0 -> 2102 bytes libs/pytz/zoneinfo/CST6CDT | Bin 0 -> 2294 bytes libs/pytz/zoneinfo/Canada/Atlantic | Bin 0 -> 3424 bytes libs/pytz/zoneinfo/Canada/Central | Bin 0 -> 2882 bytes libs/pytz/zoneinfo/Canada/Eastern | Bin 0 -> 3494 bytes libs/pytz/zoneinfo/Canada/Mountain | Bin 0 -> 2388 bytes libs/pytz/zoneinfo/Canada/Newfoundland | Bin 0 -> 3655 bytes libs/pytz/zoneinfo/Canada/Pacific | Bin 0 -> 2892 bytes libs/pytz/zoneinfo/Canada/Saskatchewan | Bin 0 -> 980 bytes libs/pytz/zoneinfo/Canada/Yukon | Bin 0 -> 2084 bytes libs/pytz/zoneinfo/Chile/Continental | Bin 0 -> 2529 bytes libs/pytz/zoneinfo/Chile/EasterIsland | Bin 0 -> 2233 bytes libs/pytz/zoneinfo/Cuba | Bin 0 -> 2428 bytes libs/pytz/zoneinfo/EET | Bin 0 -> 1876 bytes libs/pytz/zoneinfo/EST | Bin 0 -> 118 bytes libs/pytz/zoneinfo/EST5EDT | Bin 0 -> 2294 bytes libs/pytz/zoneinfo/Egypt | Bin 0 -> 1963 bytes libs/pytz/zoneinfo/Eire | Bin 0 -> 3522 bytes libs/pytz/zoneinfo/Etc/GMT | Bin 0 -> 118 bytes libs/pytz/zoneinfo/Etc/GMT+0 | Bin 0 -> 118 bytes libs/pytz/zoneinfo/Etc/GMT+1 | Bin 0 -> 120 bytes libs/pytz/zoneinfo/Etc/GMT+10 | Bin 0 -> 121 bytes libs/pytz/zoneinfo/Etc/GMT+11 | Bin 0 -> 121 bytes libs/pytz/zoneinfo/Etc/GMT+12 | Bin 0 -> 121 bytes libs/pytz/zoneinfo/Etc/GMT+2 | Bin 0 -> 120 bytes libs/pytz/zoneinfo/Etc/GMT+3 | Bin 0 -> 120 bytes libs/pytz/zoneinfo/Etc/GMT+4 | Bin 0 -> 120 bytes libs/pytz/zoneinfo/Etc/GMT+5 | Bin 0 -> 120 bytes libs/pytz/zoneinfo/Etc/GMT+6 | Bin 0 -> 120 bytes libs/pytz/zoneinfo/Etc/GMT+7 | Bin 0 -> 120 bytes libs/pytz/zoneinfo/Etc/GMT+8 | Bin 0 -> 120 bytes libs/pytz/zoneinfo/Etc/GMT+9 | Bin 0 -> 120 bytes libs/pytz/zoneinfo/Etc/GMT-0 | Bin 0 -> 118 bytes libs/pytz/zoneinfo/Etc/GMT-1 | Bin 0 -> 121 bytes libs/pytz/zoneinfo/Etc/GMT-10 | Bin 0 -> 122 bytes libs/pytz/zoneinfo/Etc/GMT-11 | Bin 0 -> 122 bytes libs/pytz/zoneinfo/Etc/GMT-12 | Bin 0 -> 122 bytes libs/pytz/zoneinfo/Etc/GMT-13 | Bin 0 -> 122 bytes libs/pytz/zoneinfo/Etc/GMT-14 | Bin 0 -> 122 bytes libs/pytz/zoneinfo/Etc/GMT-2 | Bin 0 -> 121 bytes libs/pytz/zoneinfo/Etc/GMT-3 | Bin 0 -> 121 bytes libs/pytz/zoneinfo/Etc/GMT-4 | Bin 0 -> 121 bytes libs/pytz/zoneinfo/Etc/GMT-5 | Bin 0 -> 121 bytes libs/pytz/zoneinfo/Etc/GMT-6 | Bin 0 -> 121 bytes libs/pytz/zoneinfo/Etc/GMT-7 | Bin 0 -> 121 bytes libs/pytz/zoneinfo/Etc/GMT-8 | Bin 0 -> 121 bytes libs/pytz/zoneinfo/Etc/GMT-9 | Bin 0 -> 121 bytes libs/pytz/zoneinfo/Etc/GMT0 | Bin 0 -> 118 bytes libs/pytz/zoneinfo/Etc/Greenwich | Bin 0 -> 118 bytes libs/pytz/zoneinfo/Etc/UCT | Bin 0 -> 118 bytes libs/pytz/zoneinfo/Etc/UTC | Bin 0 -> 118 bytes libs/pytz/zoneinfo/Etc/Universal | Bin 0 -> 118 bytes libs/pytz/zoneinfo/Etc/Zulu | Bin 0 -> 118 bytes libs/pytz/zoneinfo/Europe/Amsterdam | Bin 0 -> 2940 bytes libs/pytz/zoneinfo/Europe/Andorra | Bin 0 -> 1742 bytes libs/pytz/zoneinfo/Europe/Astrakhan | Bin 0 -> 1183 bytes libs/pytz/zoneinfo/Europe/Athens | Bin 0 -> 2262 bytes libs/pytz/zoneinfo/Europe/Belfast | Bin 0 -> 3678 bytes libs/pytz/zoneinfo/Europe/Belgrade | Bin 0 -> 1948 bytes libs/pytz/zoneinfo/Europe/Berlin | Bin 0 -> 2326 bytes libs/pytz/zoneinfo/Europe/Bratislava | Bin 0 -> 2329 bytes libs/pytz/zoneinfo/Europe/Brussels | Bin 0 -> 2961 bytes libs/pytz/zoneinfo/Europe/Bucharest | Bin 0 -> 2212 bytes libs/pytz/zoneinfo/Europe/Budapest | Bin 0 -> 2396 bytes libs/pytz/zoneinfo/Europe/Busingen | Bin 0 -> 1909 bytes libs/pytz/zoneinfo/Europe/Chisinau | Bin 0 -> 2436 bytes libs/pytz/zoneinfo/Europe/Copenhagen | Bin 0 -> 2151 bytes libs/pytz/zoneinfo/Europe/Dublin | Bin 0 -> 3522 bytes libs/pytz/zoneinfo/Europe/Gibraltar | Bin 0 -> 3052 bytes libs/pytz/zoneinfo/Europe/Guernsey | Bin 0 -> 3678 bytes libs/pytz/zoneinfo/Europe/Helsinki | Bin 0 -> 1900 bytes libs/pytz/zoneinfo/Europe/Isle_of_Man | Bin 0 -> 3678 bytes libs/pytz/zoneinfo/Europe/Istanbul | Bin 0 -> 2157 bytes libs/pytz/zoneinfo/Europe/Jersey | Bin 0 -> 3678 bytes libs/pytz/zoneinfo/Europe/Kaliningrad | Bin 0 -> 1509 bytes libs/pytz/zoneinfo/Europe/Kiev | Bin 0 -> 2088 bytes libs/pytz/zoneinfo/Europe/Kirov | Bin 0 -> 1153 bytes libs/pytz/zoneinfo/Europe/Lisbon | Bin 0 -> 3469 bytes libs/pytz/zoneinfo/Europe/Ljubljana | Bin 0 -> 1948 bytes libs/pytz/zoneinfo/Europe/London | Bin 0 -> 3678 bytes libs/pytz/zoneinfo/Europe/Luxembourg | Bin 0 -> 2960 bytes libs/pytz/zoneinfo/Europe/Madrid | Bin 0 -> 2628 bytes libs/pytz/zoneinfo/Europe/Malta | Bin 0 -> 2620 bytes libs/pytz/zoneinfo/Europe/Mariehamn | Bin 0 -> 1900 bytes libs/pytz/zoneinfo/Europe/Minsk | Bin 0 -> 1361 bytes libs/pytz/zoneinfo/Europe/Monaco | Bin 0 -> 2944 bytes libs/pytz/zoneinfo/Europe/Moscow | Bin 0 -> 1535 bytes libs/pytz/zoneinfo/Europe/Nicosia | Bin 0 -> 2002 bytes libs/pytz/zoneinfo/Europe/Oslo | Bin 0 -> 2242 bytes libs/pytz/zoneinfo/Europe/Paris | Bin 0 -> 2962 bytes libs/pytz/zoneinfo/Europe/Podgorica | Bin 0 -> 1948 bytes libs/pytz/zoneinfo/Europe/Prague | Bin 0 -> 2329 bytes libs/pytz/zoneinfo/Europe/Riga | Bin 0 -> 2226 bytes libs/pytz/zoneinfo/Europe/Rome | Bin 0 -> 2683 bytes libs/pytz/zoneinfo/Europe/Samara | Bin 0 -> 1215 bytes libs/pytz/zoneinfo/Europe/San_Marino | Bin 0 -> 2683 bytes libs/pytz/zoneinfo/Europe/Sarajevo | Bin 0 -> 1948 bytes libs/pytz/zoneinfo/Europe/Saratov | Bin 0 -> 1183 bytes libs/pytz/zoneinfo/Europe/Simferopol | Bin 0 -> 1481 bytes libs/pytz/zoneinfo/Europe/Skopje | Bin 0 -> 1948 bytes libs/pytz/zoneinfo/Europe/Sofia | Bin 0 -> 2121 bytes libs/pytz/zoneinfo/Europe/Stockholm | Bin 0 -> 1909 bytes libs/pytz/zoneinfo/Europe/Tallinn | Bin 0 -> 2178 bytes libs/pytz/zoneinfo/Europe/Tirane | Bin 0 -> 2084 bytes libs/pytz/zoneinfo/Europe/Tiraspol | Bin 0 -> 2436 bytes libs/pytz/zoneinfo/Europe/Ulyanovsk | Bin 0 -> 1267 bytes libs/pytz/zoneinfo/Europe/Uzhgorod | Bin 0 -> 2094 bytes libs/pytz/zoneinfo/Europe/Vaduz | Bin 0 -> 1909 bytes libs/pytz/zoneinfo/Europe/Vatican | Bin 0 -> 2683 bytes libs/pytz/zoneinfo/Europe/Vienna | Bin 0 -> 2228 bytes libs/pytz/zoneinfo/Europe/Vilnius | Bin 0 -> 2190 bytes libs/pytz/zoneinfo/Europe/Volgograd | Bin 0 -> 1183 bytes libs/pytz/zoneinfo/Europe/Warsaw | Bin 0 -> 2696 bytes libs/pytz/zoneinfo/Europe/Zagreb | Bin 0 -> 1948 bytes libs/pytz/zoneinfo/Europe/Zaporozhye | Bin 0 -> 2106 bytes libs/pytz/zoneinfo/Europe/Zurich | Bin 0 -> 1909 bytes libs/pytz/zoneinfo/Factory | Bin 0 -> 120 bytes libs/pytz/zoneinfo/GB | Bin 0 -> 3678 bytes libs/pytz/zoneinfo/GB-Eire | Bin 0 -> 3678 bytes libs/pytz/zoneinfo/GMT | Bin 0 -> 118 bytes libs/pytz/zoneinfo/GMT+0 | Bin 0 -> 118 bytes libs/pytz/zoneinfo/GMT-0 | Bin 0 -> 118 bytes libs/pytz/zoneinfo/GMT0 | Bin 0 -> 118 bytes libs/pytz/zoneinfo/Greenwich | Bin 0 -> 118 bytes libs/pytz/zoneinfo/HST | Bin 0 -> 119 bytes libs/pytz/zoneinfo/Hongkong | Bin 0 -> 1175 bytes libs/pytz/zoneinfo/Iceland | Bin 0 -> 1174 bytes libs/pytz/zoneinfo/Indian/Antananarivo | Bin 0 -> 271 bytes libs/pytz/zoneinfo/Indian/Chagos | Bin 0 -> 211 bytes libs/pytz/zoneinfo/Indian/Christmas | Bin 0 -> 173 bytes libs/pytz/zoneinfo/Indian/Cocos | Bin 0 -> 182 bytes libs/pytz/zoneinfo/Indian/Comoro | Bin 0 -> 271 bytes libs/pytz/zoneinfo/Indian/Kerguelen | Bin 0 -> 173 bytes libs/pytz/zoneinfo/Indian/Mahe | Bin 0 -> 173 bytes libs/pytz/zoneinfo/Indian/Maldives | Bin 0 -> 211 bytes libs/pytz/zoneinfo/Indian/Mauritius | Bin 0 -> 253 bytes libs/pytz/zoneinfo/Indian/Mayotte | Bin 0 -> 271 bytes libs/pytz/zoneinfo/Indian/Reunion | Bin 0 -> 173 bytes libs/pytz/zoneinfo/Iran | Bin 0 -> 1704 bytes libs/pytz/zoneinfo/Israel | Bin 0 -> 2256 bytes libs/pytz/zoneinfo/Jamaica | Bin 0 -> 498 bytes libs/pytz/zoneinfo/Japan | Bin 0 -> 309 bytes libs/pytz/zoneinfo/Kwajalein | Bin 0 -> 250 bytes libs/pytz/zoneinfo/Libya | Bin 0 -> 641 bytes libs/pytz/zoneinfo/MET | Bin 0 -> 2102 bytes libs/pytz/zoneinfo/MST | Bin 0 -> 118 bytes libs/pytz/zoneinfo/MST7MDT | Bin 0 -> 2294 bytes libs/pytz/zoneinfo/Mexico/BajaNorte | Bin 0 -> 2342 bytes libs/pytz/zoneinfo/Mexico/BajaSur | Bin 0 -> 1550 bytes libs/pytz/zoneinfo/Mexico/General | Bin 0 -> 1604 bytes libs/pytz/zoneinfo/NZ | Bin 0 -> 2451 bytes libs/pytz/zoneinfo/NZ-CHAT | Bin 0 -> 2078 bytes libs/pytz/zoneinfo/Navajo | Bin 0 -> 2444 bytes libs/pytz/zoneinfo/PRC | Bin 0 -> 545 bytes libs/pytz/zoneinfo/PST8PDT | Bin 0 -> 2294 bytes libs/pytz/zoneinfo/Pacific/Apia | Bin 0 -> 1125 bytes libs/pytz/zoneinfo/Pacific/Auckland | Bin 0 -> 2451 bytes libs/pytz/zoneinfo/Pacific/Bougainville | Bin 0 -> 286 bytes libs/pytz/zoneinfo/Pacific/Chatham | Bin 0 -> 2078 bytes libs/pytz/zoneinfo/Pacific/Chuuk | Bin 0 -> 174 bytes libs/pytz/zoneinfo/Pacific/Easter | Bin 0 -> 2233 bytes libs/pytz/zoneinfo/Pacific/Efate | Bin 0 -> 478 bytes libs/pytz/zoneinfo/Pacific/Enderbury | Bin 0 -> 250 bytes libs/pytz/zoneinfo/Pacific/Fakaofo | Bin 0 -> 212 bytes libs/pytz/zoneinfo/Pacific/Fiji | Bin 0 -> 1090 bytes libs/pytz/zoneinfo/Pacific/Funafuti | Bin 0 -> 174 bytes libs/pytz/zoneinfo/Pacific/Galapagos | Bin 0 -> 254 bytes libs/pytz/zoneinfo/Pacific/Gambier | Bin 0 -> 172 bytes libs/pytz/zoneinfo/Pacific/Guadalcanal | Bin 0 -> 174 bytes libs/pytz/zoneinfo/Pacific/Guam | Bin 0 -> 216 bytes libs/pytz/zoneinfo/Pacific/Honolulu | Bin 0 -> 329 bytes libs/pytz/zoneinfo/Pacific/Johnston | Bin 0 -> 329 bytes libs/pytz/zoneinfo/Pacific/Kiritimati | Bin 0 -> 254 bytes libs/pytz/zoneinfo/Pacific/Kosrae | Bin 0 -> 242 bytes libs/pytz/zoneinfo/Pacific/Kwajalein | Bin 0 -> 250 bytes libs/pytz/zoneinfo/Pacific/Majuro | Bin 0 -> 212 bytes libs/pytz/zoneinfo/Pacific/Marquesas | Bin 0 -> 181 bytes libs/pytz/zoneinfo/Pacific/Midway | Bin 0 -> 187 bytes libs/pytz/zoneinfo/Pacific/Nauru | Bin 0 -> 268 bytes libs/pytz/zoneinfo/Pacific/Niue | Bin 0 -> 257 bytes libs/pytz/zoneinfo/Pacific/Norfolk | Bin 0 -> 314 bytes libs/pytz/zoneinfo/Pacific/Noumea | Bin 0 -> 314 bytes libs/pytz/zoneinfo/Pacific/Pago_Pago | Bin 0 -> 187 bytes libs/pytz/zoneinfo/Pacific/Palau | Bin 0 -> 173 bytes libs/pytz/zoneinfo/Pacific/Pitcairn | Bin 0 -> 214 bytes libs/pytz/zoneinfo/Pacific/Pohnpei | Bin 0 -> 174 bytes libs/pytz/zoneinfo/Pacific/Ponape | Bin 0 -> 174 bytes libs/pytz/zoneinfo/Pacific/Port_Moresby | Bin 0 -> 196 bytes libs/pytz/zoneinfo/Pacific/Rarotonga | Bin 0 -> 593 bytes libs/pytz/zoneinfo/Pacific/Saipan | Bin 0 -> 216 bytes libs/pytz/zoneinfo/Pacific/Samoa | Bin 0 -> 187 bytes libs/pytz/zoneinfo/Pacific/Tahiti | Bin 0 -> 173 bytes libs/pytz/zoneinfo/Pacific/Tarawa | Bin 0 -> 174 bytes libs/pytz/zoneinfo/Pacific/Tongatapu | Bin 0 -> 384 bytes libs/pytz/zoneinfo/Pacific/Truk | Bin 0 -> 174 bytes libs/pytz/zoneinfo/Pacific/Wake | Bin 0 -> 174 bytes libs/pytz/zoneinfo/Pacific/Wallis | Bin 0 -> 174 bytes libs/pytz/zoneinfo/Pacific/Yap | Bin 0 -> 174 bytes libs/pytz/zoneinfo/Poland | Bin 0 -> 2696 bytes libs/pytz/zoneinfo/Portugal | Bin 0 -> 3469 bytes libs/pytz/zoneinfo/ROC | Bin 0 -> 781 bytes libs/pytz/zoneinfo/ROK | Bin 0 -> 517 bytes libs/pytz/zoneinfo/Singapore | Bin 0 -> 415 bytes libs/pytz/zoneinfo/Turkey | Bin 0 -> 2157 bytes libs/pytz/zoneinfo/UCT | Bin 0 -> 118 bytes libs/pytz/zoneinfo/US/Alaska | Bin 0 -> 2371 bytes libs/pytz/zoneinfo/US/Aleutian | Bin 0 -> 2356 bytes libs/pytz/zoneinfo/US/Arizona | Bin 0 -> 344 bytes libs/pytz/zoneinfo/US/Central | Bin 0 -> 3576 bytes libs/pytz/zoneinfo/US/East-Indiana | Bin 0 -> 1666 bytes libs/pytz/zoneinfo/US/Eastern | Bin 0 -> 3536 bytes libs/pytz/zoneinfo/US/Hawaii | Bin 0 -> 329 bytes libs/pytz/zoneinfo/US/Indiana-Starke | Bin 0 -> 2428 bytes libs/pytz/zoneinfo/US/Michigan | Bin 0 -> 2174 bytes libs/pytz/zoneinfo/US/Mountain | Bin 0 -> 2444 bytes libs/pytz/zoneinfo/US/Pacific | Bin 0 -> 2836 bytes libs/pytz/zoneinfo/US/Samoa | Bin 0 -> 187 bytes libs/pytz/zoneinfo/UTC | Bin 0 -> 118 bytes libs/pytz/zoneinfo/Universal | Bin 0 -> 118 bytes libs/pytz/zoneinfo/W-SU | Bin 0 -> 1535 bytes libs/pytz/zoneinfo/WET | Bin 0 -> 1873 bytes libs/pytz/zoneinfo/Zulu | Bin 0 -> 118 bytes libs/pytz/zoneinfo/iso3166.tab | 274 ++ libs/pytz/zoneinfo/leapseconds | 66 + libs/pytz/zoneinfo/posixrules | Bin 0 -> 3536 bytes libs/pytz/zoneinfo/tzdata.zi | 4177 +++++++++++++++++ libs/pytz/zoneinfo/zone.tab | 448 ++ libs/pytz/zoneinfo/zone1970.tab | 382 ++ libs/rarfile.py | 2445 +++++++--- libs/stevedore/__init__.py | 14 +- libs/stevedore/dispatch.py | 15 +- libs/stevedore/driver.py | 32 +- libs/stevedore/enabled.py | 15 +- libs/stevedore/example/__init__.py | 0 libs/stevedore/example/base.py | 22 + libs/stevedore/example/load_as_driver.py | 37 + libs/stevedore/example/load_as_extension.py | 39 + libs/stevedore/example/setup.py | 43 + libs/stevedore/example/simple.py | 20 + libs/stevedore/example2/__init__.py | 0 libs/stevedore/example2/fields.py | 36 + libs/stevedore/example2/setup.py | 42 + libs/stevedore/exception.py | 23 + libs/stevedore/extension.py | 88 +- libs/stevedore/hook.py | 29 +- libs/stevedore/named.py | 39 +- libs/stevedore/sphinxext.py | 115 + libs/stevedore/tests/__init__.py | 0 .../stevedore/tests/extension_unimportable.py | 0 libs/stevedore/tests/manager.py | 67 + libs/stevedore/tests/test_callback.py | 55 + libs/stevedore/tests/test_dispatch.py | 103 + libs/stevedore/tests/test_driver.py | 89 + libs/stevedore/tests/test_enabled.py | 42 + libs/stevedore/tests/test_example_fields.py | 41 + libs/stevedore/tests/test_example_simple.py | 29 + libs/stevedore/tests/test_extension.py | 244 + libs/stevedore/tests/test_hook.py | 55 + libs/stevedore/tests/test_named.py | 93 + libs/stevedore/tests/test_sphinxext.py | 120 + libs/stevedore/tests/test_test_manager.py | 216 + libs/stevedore/tests/utils.py | 17 + libs/subliminal/subtitles/__init__.py | 88 + libs/subliminal/subtitles/subrip.py | 82 + 761 files changed, 29015 insertions(+), 1843 deletions(-) create mode 100644 libs/appdirs.py create mode 100644 libs/bin/pbr.exe create mode 100644 libs/bin/srt.exe create mode 100644 libs/bin/subliminal.exe create mode 100644 libs/click/__init__.py create mode 100644 libs/click/_bashcomplete.py create mode 100644 libs/click/_compat.py create mode 100644 libs/click/_termui_impl.py create mode 100644 libs/click/_textwrap.py create mode 100644 libs/click/_unicodefun.py create mode 100644 libs/click/_winconsole.py create mode 100644 libs/click/core.py create mode 100644 libs/click/decorators.py create mode 100644 libs/click/exceptions.py create mode 100644 libs/click/formatting.py create mode 100644 libs/click/globals.py create mode 100644 libs/click/parser.py create mode 100644 libs/click/termui.py create mode 100644 libs/click/testing.py create mode 100644 libs/click/types.py create mode 100644 libs/click/utils.py create mode 100644 libs/decorator.py create mode 100644 libs/dogpile/core.py delete mode 100644 libs/dogpile/core/__init__.py delete mode 100644 libs/dogpile/core/legacy.py delete mode 100644 libs/dogpile/core/util.py rename libs/dogpile/{core/dogpile.py => lock.py} (54%) create mode 100644 libs/dogpile/util/__init__.py rename libs/dogpile/{cache => util}/compat.py (50%) create mode 100644 libs/dogpile/util/langhelpers.py rename libs/dogpile/{core => util}/nameregistry.py (93%) rename libs/dogpile/{core => util}/readwrite_lock.py (89%) create mode 100644 libs/pbr/__init__.py create mode 100644 libs/pbr/builddoc.py create mode 100644 libs/pbr/cmd/__init__.py create mode 100644 libs/pbr/cmd/main.py create mode 100644 libs/pbr/core.py create mode 100644 libs/pbr/extra_files.py create mode 100644 libs/pbr/find_package.py create mode 100644 libs/pbr/git.py create mode 100644 libs/pbr/hooks/__init__.py create mode 100644 libs/pbr/hooks/backwards.py create mode 100644 libs/pbr/hooks/base.py create mode 100644 libs/pbr/hooks/commands.py create mode 100644 libs/pbr/hooks/files.py create mode 100644 libs/pbr/hooks/metadata.py create mode 100644 libs/pbr/options.py create mode 100644 libs/pbr/packaging.py create mode 100644 libs/pbr/pbr_json.py create mode 100644 libs/pbr/sphinxext.py create mode 100644 libs/pbr/testr_command.py create mode 100644 libs/pbr/tests/__init__.py create mode 100644 libs/pbr/tests/base.py create mode 100644 libs/pbr/tests/test_commands.py create mode 100644 libs/pbr/tests/test_core.py create mode 100644 libs/pbr/tests/test_files.py create mode 100644 libs/pbr/tests/test_hooks.py create mode 100644 libs/pbr/tests/test_integration.py create mode 100644 libs/pbr/tests/test_packaging.py create mode 100644 libs/pbr/tests/test_pbr_json.py create mode 100644 libs/pbr/tests/test_setup.py create mode 100644 libs/pbr/tests/test_util.py create mode 100644 libs/pbr/tests/test_version.py create mode 100644 libs/pbr/tests/test_wsgi.py create mode 100644 libs/pbr/tests/testpackage/CHANGES.txt create mode 100644 libs/pbr/tests/testpackage/LICENSE.txt create mode 100644 libs/pbr/tests/testpackage/MANIFEST.in create mode 100644 libs/pbr/tests/testpackage/README.txt create mode 100644 libs/pbr/tests/testpackage/data_files/a.txt create mode 100644 libs/pbr/tests/testpackage/data_files/b.txt create mode 100644 libs/pbr/tests/testpackage/data_files/c.rst create mode 100644 libs/pbr/tests/testpackage/doc/source/conf.py create mode 100644 libs/pbr/tests/testpackage/doc/source/index.rst create mode 100644 libs/pbr/tests/testpackage/doc/source/installation.rst create mode 100644 libs/pbr/tests/testpackage/doc/source/usage.rst create mode 100644 libs/pbr/tests/testpackage/extra-file.txt create mode 100644 libs/pbr/tests/testpackage/git-extra-file.txt create mode 100644 libs/pbr/tests/testpackage/pbr_testpackage/__init__.py create mode 100644 libs/pbr/tests/testpackage/pbr_testpackage/_setup_hooks.py create mode 100644 libs/pbr/tests/testpackage/pbr_testpackage/cmd.py create mode 100644 libs/pbr/tests/testpackage/pbr_testpackage/extra.py create mode 100644 libs/pbr/tests/testpackage/pbr_testpackage/package_data/1.txt create mode 100644 libs/pbr/tests/testpackage/pbr_testpackage/package_data/2.txt create mode 100644 libs/pbr/tests/testpackage/pbr_testpackage/wsgi.py create mode 100644 libs/pbr/tests/testpackage/setup.py create mode 100644 libs/pbr/tests/testpackage/src/testext.c create mode 100644 libs/pbr/tests/testpackage/test-requirements.txt create mode 100644 libs/pbr/tests/util.py create mode 100644 libs/pbr/util.py create mode 100644 libs/pbr/version.py create mode 100644 libs/pytz/__init__.py create mode 100644 libs/pytz/exceptions.py create mode 100644 libs/pytz/lazy.py create mode 100644 libs/pytz/reference.py create mode 100644 libs/pytz/tzfile.py create mode 100644 libs/pytz/tzinfo.py create mode 100644 libs/pytz/zoneinfo/Africa/Abidjan create mode 100644 libs/pytz/zoneinfo/Africa/Accra create mode 100644 libs/pytz/zoneinfo/Africa/Addis_Ababa create mode 100644 libs/pytz/zoneinfo/Africa/Algiers create mode 100644 libs/pytz/zoneinfo/Africa/Asmara create mode 100644 libs/pytz/zoneinfo/Africa/Asmera create mode 100644 libs/pytz/zoneinfo/Africa/Bamako create mode 100644 libs/pytz/zoneinfo/Africa/Bangui create mode 100644 libs/pytz/zoneinfo/Africa/Banjul create mode 100644 libs/pytz/zoneinfo/Africa/Bissau create mode 100644 libs/pytz/zoneinfo/Africa/Blantyre create mode 100644 libs/pytz/zoneinfo/Africa/Brazzaville create mode 100644 libs/pytz/zoneinfo/Africa/Bujumbura create mode 100644 libs/pytz/zoneinfo/Africa/Cairo create mode 100644 libs/pytz/zoneinfo/Africa/Casablanca create mode 100644 libs/pytz/zoneinfo/Africa/Ceuta create mode 100644 libs/pytz/zoneinfo/Africa/Conakry create mode 100644 libs/pytz/zoneinfo/Africa/Dakar create mode 100644 libs/pytz/zoneinfo/Africa/Dar_es_Salaam create mode 100644 libs/pytz/zoneinfo/Africa/Djibouti create mode 100644 libs/pytz/zoneinfo/Africa/Douala create mode 100644 libs/pytz/zoneinfo/Africa/El_Aaiun create mode 100644 libs/pytz/zoneinfo/Africa/Freetown create mode 100644 libs/pytz/zoneinfo/Africa/Gaborone create mode 100644 libs/pytz/zoneinfo/Africa/Harare create mode 100644 libs/pytz/zoneinfo/Africa/Johannesburg create mode 100644 libs/pytz/zoneinfo/Africa/Juba create mode 100644 libs/pytz/zoneinfo/Africa/Kampala create mode 100644 libs/pytz/zoneinfo/Africa/Khartoum create mode 100644 libs/pytz/zoneinfo/Africa/Kigali create mode 100644 libs/pytz/zoneinfo/Africa/Kinshasa create mode 100644 libs/pytz/zoneinfo/Africa/Lagos create mode 100644 libs/pytz/zoneinfo/Africa/Libreville create mode 100644 libs/pytz/zoneinfo/Africa/Lome create mode 100644 libs/pytz/zoneinfo/Africa/Luanda create mode 100644 libs/pytz/zoneinfo/Africa/Lubumbashi create mode 100644 libs/pytz/zoneinfo/Africa/Lusaka create mode 100644 libs/pytz/zoneinfo/Africa/Malabo create mode 100644 libs/pytz/zoneinfo/Africa/Maputo create mode 100644 libs/pytz/zoneinfo/Africa/Maseru create mode 100644 libs/pytz/zoneinfo/Africa/Mbabane create mode 100644 libs/pytz/zoneinfo/Africa/Mogadishu create mode 100644 libs/pytz/zoneinfo/Africa/Monrovia create mode 100644 libs/pytz/zoneinfo/Africa/Nairobi create mode 100644 libs/pytz/zoneinfo/Africa/Ndjamena create mode 100644 libs/pytz/zoneinfo/Africa/Niamey create mode 100644 libs/pytz/zoneinfo/Africa/Nouakchott create mode 100644 libs/pytz/zoneinfo/Africa/Ouagadougou create mode 100644 libs/pytz/zoneinfo/Africa/Porto-Novo create mode 100644 libs/pytz/zoneinfo/Africa/Sao_Tome create mode 100644 libs/pytz/zoneinfo/Africa/Timbuktu create mode 100644 libs/pytz/zoneinfo/Africa/Tripoli create mode 100644 libs/pytz/zoneinfo/Africa/Tunis create mode 100644 libs/pytz/zoneinfo/Africa/Windhoek create mode 100644 libs/pytz/zoneinfo/America/Adak create mode 100644 libs/pytz/zoneinfo/America/Anchorage create mode 100644 libs/pytz/zoneinfo/America/Anguilla create mode 100644 libs/pytz/zoneinfo/America/Antigua create mode 100644 libs/pytz/zoneinfo/America/Araguaina create mode 100644 libs/pytz/zoneinfo/America/Argentina/Buenos_Aires create mode 100644 libs/pytz/zoneinfo/America/Argentina/Catamarca create mode 100644 libs/pytz/zoneinfo/America/Argentina/ComodRivadavia create mode 100644 libs/pytz/zoneinfo/America/Argentina/Cordoba create mode 100644 libs/pytz/zoneinfo/America/Argentina/Jujuy create mode 100644 libs/pytz/zoneinfo/America/Argentina/La_Rioja create mode 100644 libs/pytz/zoneinfo/America/Argentina/Mendoza create mode 100644 libs/pytz/zoneinfo/America/Argentina/Rio_Gallegos create mode 100644 libs/pytz/zoneinfo/America/Argentina/Salta create mode 100644 libs/pytz/zoneinfo/America/Argentina/San_Juan create mode 100644 libs/pytz/zoneinfo/America/Argentina/San_Luis create mode 100644 libs/pytz/zoneinfo/America/Argentina/Tucuman create mode 100644 libs/pytz/zoneinfo/America/Argentina/Ushuaia create mode 100644 libs/pytz/zoneinfo/America/Aruba create mode 100644 libs/pytz/zoneinfo/America/Asuncion create mode 100644 libs/pytz/zoneinfo/America/Atikokan create mode 100644 libs/pytz/zoneinfo/America/Atka create mode 100644 libs/pytz/zoneinfo/America/Bahia create mode 100644 libs/pytz/zoneinfo/America/Bahia_Banderas create mode 100644 libs/pytz/zoneinfo/America/Barbados create mode 100644 libs/pytz/zoneinfo/America/Belem create mode 100644 libs/pytz/zoneinfo/America/Belize create mode 100644 libs/pytz/zoneinfo/America/Blanc-Sablon create mode 100644 libs/pytz/zoneinfo/America/Boa_Vista create mode 100644 libs/pytz/zoneinfo/America/Bogota create mode 100644 libs/pytz/zoneinfo/America/Boise create mode 100644 libs/pytz/zoneinfo/America/Buenos_Aires create mode 100644 libs/pytz/zoneinfo/America/Cambridge_Bay create mode 100644 libs/pytz/zoneinfo/America/Campo_Grande create mode 100644 libs/pytz/zoneinfo/America/Cancun create mode 100644 libs/pytz/zoneinfo/America/Caracas create mode 100644 libs/pytz/zoneinfo/America/Catamarca create mode 100644 libs/pytz/zoneinfo/America/Cayenne create mode 100644 libs/pytz/zoneinfo/America/Cayman create mode 100644 libs/pytz/zoneinfo/America/Chicago create mode 100644 libs/pytz/zoneinfo/America/Chihuahua create mode 100644 libs/pytz/zoneinfo/America/Coral_Harbour create mode 100644 libs/pytz/zoneinfo/America/Cordoba create mode 100644 libs/pytz/zoneinfo/America/Costa_Rica create mode 100644 libs/pytz/zoneinfo/America/Creston create mode 100644 libs/pytz/zoneinfo/America/Cuiaba create mode 100644 libs/pytz/zoneinfo/America/Curacao create mode 100644 libs/pytz/zoneinfo/America/Danmarkshavn create mode 100644 libs/pytz/zoneinfo/America/Dawson create mode 100644 libs/pytz/zoneinfo/America/Dawson_Creek create mode 100644 libs/pytz/zoneinfo/America/Denver create mode 100644 libs/pytz/zoneinfo/America/Detroit create mode 100644 libs/pytz/zoneinfo/America/Dominica create mode 100644 libs/pytz/zoneinfo/America/Edmonton create mode 100644 libs/pytz/zoneinfo/America/Eirunepe create mode 100644 libs/pytz/zoneinfo/America/El_Salvador create mode 100644 libs/pytz/zoneinfo/America/Ensenada create mode 100644 libs/pytz/zoneinfo/America/Fort_Nelson create mode 100644 libs/pytz/zoneinfo/America/Fort_Wayne create mode 100644 libs/pytz/zoneinfo/America/Fortaleza create mode 100644 libs/pytz/zoneinfo/America/Glace_Bay create mode 100644 libs/pytz/zoneinfo/America/Godthab create mode 100644 libs/pytz/zoneinfo/America/Goose_Bay create mode 100644 libs/pytz/zoneinfo/America/Grand_Turk create mode 100644 libs/pytz/zoneinfo/America/Grenada create mode 100644 libs/pytz/zoneinfo/America/Guadeloupe create mode 100644 libs/pytz/zoneinfo/America/Guatemala create mode 100644 libs/pytz/zoneinfo/America/Guayaquil create mode 100644 libs/pytz/zoneinfo/America/Guyana create mode 100644 libs/pytz/zoneinfo/America/Halifax create mode 100644 libs/pytz/zoneinfo/America/Havana create mode 100644 libs/pytz/zoneinfo/America/Hermosillo create mode 100644 libs/pytz/zoneinfo/America/Indiana/Indianapolis create mode 100644 libs/pytz/zoneinfo/America/Indiana/Knox create mode 100644 libs/pytz/zoneinfo/America/Indiana/Marengo create mode 100644 libs/pytz/zoneinfo/America/Indiana/Petersburg create mode 100644 libs/pytz/zoneinfo/America/Indiana/Tell_City create mode 100644 libs/pytz/zoneinfo/America/Indiana/Vevay create mode 100644 libs/pytz/zoneinfo/America/Indiana/Vincennes create mode 100644 libs/pytz/zoneinfo/America/Indiana/Winamac create mode 100644 libs/pytz/zoneinfo/America/Indianapolis create mode 100644 libs/pytz/zoneinfo/America/Inuvik create mode 100644 libs/pytz/zoneinfo/America/Iqaluit create mode 100644 libs/pytz/zoneinfo/America/Jamaica create mode 100644 libs/pytz/zoneinfo/America/Jujuy create mode 100644 libs/pytz/zoneinfo/America/Juneau create mode 100644 libs/pytz/zoneinfo/America/Kentucky/Louisville create mode 100644 libs/pytz/zoneinfo/America/Kentucky/Monticello create mode 100644 libs/pytz/zoneinfo/America/Knox_IN create mode 100644 libs/pytz/zoneinfo/America/Kralendijk create mode 100644 libs/pytz/zoneinfo/America/La_Paz create mode 100644 libs/pytz/zoneinfo/America/Lima create mode 100644 libs/pytz/zoneinfo/America/Los_Angeles create mode 100644 libs/pytz/zoneinfo/America/Louisville create mode 100644 libs/pytz/zoneinfo/America/Lower_Princes create mode 100644 libs/pytz/zoneinfo/America/Maceio create mode 100644 libs/pytz/zoneinfo/America/Managua create mode 100644 libs/pytz/zoneinfo/America/Manaus create mode 100644 libs/pytz/zoneinfo/America/Marigot create mode 100644 libs/pytz/zoneinfo/America/Martinique create mode 100644 libs/pytz/zoneinfo/America/Matamoros create mode 100644 libs/pytz/zoneinfo/America/Mazatlan create mode 100644 libs/pytz/zoneinfo/America/Mendoza create mode 100644 libs/pytz/zoneinfo/America/Menominee create mode 100644 libs/pytz/zoneinfo/America/Merida create mode 100644 libs/pytz/zoneinfo/America/Metlakatla create mode 100644 libs/pytz/zoneinfo/America/Mexico_City create mode 100644 libs/pytz/zoneinfo/America/Miquelon create mode 100644 libs/pytz/zoneinfo/America/Moncton create mode 100644 libs/pytz/zoneinfo/America/Monterrey create mode 100644 libs/pytz/zoneinfo/America/Montevideo create mode 100644 libs/pytz/zoneinfo/America/Montreal create mode 100644 libs/pytz/zoneinfo/America/Montserrat create mode 100644 libs/pytz/zoneinfo/America/Nassau create mode 100644 libs/pytz/zoneinfo/America/New_York create mode 100644 libs/pytz/zoneinfo/America/Nipigon create mode 100644 libs/pytz/zoneinfo/America/Nome create mode 100644 libs/pytz/zoneinfo/America/Noronha create mode 100644 libs/pytz/zoneinfo/America/North_Dakota/Beulah create mode 100644 libs/pytz/zoneinfo/America/North_Dakota/Center create mode 100644 libs/pytz/zoneinfo/America/North_Dakota/New_Salem create mode 100644 libs/pytz/zoneinfo/America/Ojinaga create mode 100644 libs/pytz/zoneinfo/America/Panama create mode 100644 libs/pytz/zoneinfo/America/Pangnirtung create mode 100644 libs/pytz/zoneinfo/America/Paramaribo create mode 100644 libs/pytz/zoneinfo/America/Phoenix create mode 100644 libs/pytz/zoneinfo/America/Port-au-Prince create mode 100644 libs/pytz/zoneinfo/America/Port_of_Spain create mode 100644 libs/pytz/zoneinfo/America/Porto_Acre create mode 100644 libs/pytz/zoneinfo/America/Porto_Velho create mode 100644 libs/pytz/zoneinfo/America/Puerto_Rico create mode 100644 libs/pytz/zoneinfo/America/Punta_Arenas create mode 100644 libs/pytz/zoneinfo/America/Rainy_River create mode 100644 libs/pytz/zoneinfo/America/Rankin_Inlet create mode 100644 libs/pytz/zoneinfo/America/Recife create mode 100644 libs/pytz/zoneinfo/America/Regina create mode 100644 libs/pytz/zoneinfo/America/Resolute create mode 100644 libs/pytz/zoneinfo/America/Rio_Branco create mode 100644 libs/pytz/zoneinfo/America/Rosario create mode 100644 libs/pytz/zoneinfo/America/Santa_Isabel create mode 100644 libs/pytz/zoneinfo/America/Santarem create mode 100644 libs/pytz/zoneinfo/America/Santiago create mode 100644 libs/pytz/zoneinfo/America/Santo_Domingo create mode 100644 libs/pytz/zoneinfo/America/Sao_Paulo create mode 100644 libs/pytz/zoneinfo/America/Scoresbysund create mode 100644 libs/pytz/zoneinfo/America/Shiprock create mode 100644 libs/pytz/zoneinfo/America/Sitka create mode 100644 libs/pytz/zoneinfo/America/St_Barthelemy create mode 100644 libs/pytz/zoneinfo/America/St_Johns create mode 100644 libs/pytz/zoneinfo/America/St_Kitts create mode 100644 libs/pytz/zoneinfo/America/St_Lucia create mode 100644 libs/pytz/zoneinfo/America/St_Thomas create mode 100644 libs/pytz/zoneinfo/America/St_Vincent create mode 100644 libs/pytz/zoneinfo/America/Swift_Current create mode 100644 libs/pytz/zoneinfo/America/Tegucigalpa create mode 100644 libs/pytz/zoneinfo/America/Thule create mode 100644 libs/pytz/zoneinfo/America/Thunder_Bay create mode 100644 libs/pytz/zoneinfo/America/Tijuana create mode 100644 libs/pytz/zoneinfo/America/Toronto create mode 100644 libs/pytz/zoneinfo/America/Tortola create mode 100644 libs/pytz/zoneinfo/America/Vancouver create mode 100644 libs/pytz/zoneinfo/America/Virgin create mode 100644 libs/pytz/zoneinfo/America/Whitehorse create mode 100644 libs/pytz/zoneinfo/America/Winnipeg create mode 100644 libs/pytz/zoneinfo/America/Yakutat create mode 100644 libs/pytz/zoneinfo/America/Yellowknife create mode 100644 libs/pytz/zoneinfo/Antarctica/Casey create mode 100644 libs/pytz/zoneinfo/Antarctica/Davis create mode 100644 libs/pytz/zoneinfo/Antarctica/DumontDUrville create mode 100644 libs/pytz/zoneinfo/Antarctica/Macquarie create mode 100644 libs/pytz/zoneinfo/Antarctica/Mawson create mode 100644 libs/pytz/zoneinfo/Antarctica/McMurdo create mode 100644 libs/pytz/zoneinfo/Antarctica/Palmer create mode 100644 libs/pytz/zoneinfo/Antarctica/Rothera create mode 100644 libs/pytz/zoneinfo/Antarctica/South_Pole create mode 100644 libs/pytz/zoneinfo/Antarctica/Syowa create mode 100644 libs/pytz/zoneinfo/Antarctica/Troll create mode 100644 libs/pytz/zoneinfo/Antarctica/Vostok create mode 100644 libs/pytz/zoneinfo/Arctic/Longyearbyen create mode 100644 libs/pytz/zoneinfo/Asia/Aden create mode 100644 libs/pytz/zoneinfo/Asia/Almaty create mode 100644 libs/pytz/zoneinfo/Asia/Amman create mode 100644 libs/pytz/zoneinfo/Asia/Anadyr create mode 100644 libs/pytz/zoneinfo/Asia/Aqtau create mode 100644 libs/pytz/zoneinfo/Asia/Aqtobe create mode 100644 libs/pytz/zoneinfo/Asia/Ashgabat create mode 100644 libs/pytz/zoneinfo/Asia/Ashkhabad create mode 100644 libs/pytz/zoneinfo/Asia/Atyrau create mode 100644 libs/pytz/zoneinfo/Asia/Baghdad create mode 100644 libs/pytz/zoneinfo/Asia/Bahrain create mode 100644 libs/pytz/zoneinfo/Asia/Baku create mode 100644 libs/pytz/zoneinfo/Asia/Bangkok create mode 100644 libs/pytz/zoneinfo/Asia/Barnaul create mode 100644 libs/pytz/zoneinfo/Asia/Beirut create mode 100644 libs/pytz/zoneinfo/Asia/Bishkek create mode 100644 libs/pytz/zoneinfo/Asia/Brunei create mode 100644 libs/pytz/zoneinfo/Asia/Calcutta create mode 100644 libs/pytz/zoneinfo/Asia/Chita create mode 100644 libs/pytz/zoneinfo/Asia/Choibalsan create mode 100644 libs/pytz/zoneinfo/Asia/Chongqing create mode 100644 libs/pytz/zoneinfo/Asia/Chungking create mode 100644 libs/pytz/zoneinfo/Asia/Colombo create mode 100644 libs/pytz/zoneinfo/Asia/Dacca create mode 100644 libs/pytz/zoneinfo/Asia/Damascus create mode 100644 libs/pytz/zoneinfo/Asia/Dhaka create mode 100644 libs/pytz/zoneinfo/Asia/Dili create mode 100644 libs/pytz/zoneinfo/Asia/Dubai create mode 100644 libs/pytz/zoneinfo/Asia/Dushanbe create mode 100644 libs/pytz/zoneinfo/Asia/Famagusta create mode 100644 libs/pytz/zoneinfo/Asia/Gaza create mode 100644 libs/pytz/zoneinfo/Asia/Harbin create mode 100644 libs/pytz/zoneinfo/Asia/Hebron create mode 100644 libs/pytz/zoneinfo/Asia/Ho_Chi_Minh create mode 100644 libs/pytz/zoneinfo/Asia/Hong_Kong create mode 100644 libs/pytz/zoneinfo/Asia/Hovd create mode 100644 libs/pytz/zoneinfo/Asia/Irkutsk create mode 100644 libs/pytz/zoneinfo/Asia/Istanbul create mode 100644 libs/pytz/zoneinfo/Asia/Jakarta create mode 100644 libs/pytz/zoneinfo/Asia/Jayapura create mode 100644 libs/pytz/zoneinfo/Asia/Jerusalem create mode 100644 libs/pytz/zoneinfo/Asia/Kabul create mode 100644 libs/pytz/zoneinfo/Asia/Kamchatka create mode 100644 libs/pytz/zoneinfo/Asia/Karachi create mode 100644 libs/pytz/zoneinfo/Asia/Kashgar create mode 100644 libs/pytz/zoneinfo/Asia/Kathmandu create mode 100644 libs/pytz/zoneinfo/Asia/Katmandu create mode 100644 libs/pytz/zoneinfo/Asia/Khandyga create mode 100644 libs/pytz/zoneinfo/Asia/Kolkata create mode 100644 libs/pytz/zoneinfo/Asia/Krasnoyarsk create mode 100644 libs/pytz/zoneinfo/Asia/Kuala_Lumpur create mode 100644 libs/pytz/zoneinfo/Asia/Kuching create mode 100644 libs/pytz/zoneinfo/Asia/Kuwait create mode 100644 libs/pytz/zoneinfo/Asia/Macao create mode 100644 libs/pytz/zoneinfo/Asia/Macau create mode 100644 libs/pytz/zoneinfo/Asia/Magadan create mode 100644 libs/pytz/zoneinfo/Asia/Makassar create mode 100644 libs/pytz/zoneinfo/Asia/Manila create mode 100644 libs/pytz/zoneinfo/Asia/Muscat create mode 100644 libs/pytz/zoneinfo/Asia/Nicosia create mode 100644 libs/pytz/zoneinfo/Asia/Novokuznetsk create mode 100644 libs/pytz/zoneinfo/Asia/Novosibirsk create mode 100644 libs/pytz/zoneinfo/Asia/Omsk create mode 100644 libs/pytz/zoneinfo/Asia/Oral create mode 100644 libs/pytz/zoneinfo/Asia/Phnom_Penh create mode 100644 libs/pytz/zoneinfo/Asia/Pontianak create mode 100644 libs/pytz/zoneinfo/Asia/Pyongyang create mode 100644 libs/pytz/zoneinfo/Asia/Qatar create mode 100644 libs/pytz/zoneinfo/Asia/Qyzylorda create mode 100644 libs/pytz/zoneinfo/Asia/Rangoon create mode 100644 libs/pytz/zoneinfo/Asia/Riyadh create mode 100644 libs/pytz/zoneinfo/Asia/Saigon create mode 100644 libs/pytz/zoneinfo/Asia/Sakhalin create mode 100644 libs/pytz/zoneinfo/Asia/Samarkand create mode 100644 libs/pytz/zoneinfo/Asia/Seoul create mode 100644 libs/pytz/zoneinfo/Asia/Shanghai create mode 100644 libs/pytz/zoneinfo/Asia/Singapore create mode 100644 libs/pytz/zoneinfo/Asia/Srednekolymsk create mode 100644 libs/pytz/zoneinfo/Asia/Taipei create mode 100644 libs/pytz/zoneinfo/Asia/Tashkent create mode 100644 libs/pytz/zoneinfo/Asia/Tbilisi create mode 100644 libs/pytz/zoneinfo/Asia/Tehran create mode 100644 libs/pytz/zoneinfo/Asia/Tel_Aviv create mode 100644 libs/pytz/zoneinfo/Asia/Thimbu create mode 100644 libs/pytz/zoneinfo/Asia/Thimphu create mode 100644 libs/pytz/zoneinfo/Asia/Tokyo create mode 100644 libs/pytz/zoneinfo/Asia/Tomsk create mode 100644 libs/pytz/zoneinfo/Asia/Ujung_Pandang create mode 100644 libs/pytz/zoneinfo/Asia/Ulaanbaatar create mode 100644 libs/pytz/zoneinfo/Asia/Ulan_Bator create mode 100644 libs/pytz/zoneinfo/Asia/Urumqi create mode 100644 libs/pytz/zoneinfo/Asia/Ust-Nera create mode 100644 libs/pytz/zoneinfo/Asia/Vientiane create mode 100644 libs/pytz/zoneinfo/Asia/Vladivostok create mode 100644 libs/pytz/zoneinfo/Asia/Yakutsk create mode 100644 libs/pytz/zoneinfo/Asia/Yangon create mode 100644 libs/pytz/zoneinfo/Asia/Yekaterinburg create mode 100644 libs/pytz/zoneinfo/Asia/Yerevan create mode 100644 libs/pytz/zoneinfo/Atlantic/Azores create mode 100644 libs/pytz/zoneinfo/Atlantic/Bermuda create mode 100644 libs/pytz/zoneinfo/Atlantic/Canary create mode 100644 libs/pytz/zoneinfo/Atlantic/Cape_Verde create mode 100644 libs/pytz/zoneinfo/Atlantic/Faeroe create mode 100644 libs/pytz/zoneinfo/Atlantic/Faroe create mode 100644 libs/pytz/zoneinfo/Atlantic/Jan_Mayen create mode 100644 libs/pytz/zoneinfo/Atlantic/Madeira create mode 100644 libs/pytz/zoneinfo/Atlantic/Reykjavik create mode 100644 libs/pytz/zoneinfo/Atlantic/South_Georgia create mode 100644 libs/pytz/zoneinfo/Atlantic/St_Helena create mode 100644 libs/pytz/zoneinfo/Atlantic/Stanley create mode 100644 libs/pytz/zoneinfo/Australia/ACT create mode 100644 libs/pytz/zoneinfo/Australia/Adelaide create mode 100644 libs/pytz/zoneinfo/Australia/Brisbane create mode 100644 libs/pytz/zoneinfo/Australia/Broken_Hill create mode 100644 libs/pytz/zoneinfo/Australia/Canberra create mode 100644 libs/pytz/zoneinfo/Australia/Currie create mode 100644 libs/pytz/zoneinfo/Australia/Darwin create mode 100644 libs/pytz/zoneinfo/Australia/Eucla create mode 100644 libs/pytz/zoneinfo/Australia/Hobart create mode 100644 libs/pytz/zoneinfo/Australia/LHI create mode 100644 libs/pytz/zoneinfo/Australia/Lindeman create mode 100644 libs/pytz/zoneinfo/Australia/Lord_Howe create mode 100644 libs/pytz/zoneinfo/Australia/Melbourne create mode 100644 libs/pytz/zoneinfo/Australia/NSW create mode 100644 libs/pytz/zoneinfo/Australia/North create mode 100644 libs/pytz/zoneinfo/Australia/Perth create mode 100644 libs/pytz/zoneinfo/Australia/Queensland create mode 100644 libs/pytz/zoneinfo/Australia/South create mode 100644 libs/pytz/zoneinfo/Australia/Sydney create mode 100644 libs/pytz/zoneinfo/Australia/Tasmania create mode 100644 libs/pytz/zoneinfo/Australia/Victoria create mode 100644 libs/pytz/zoneinfo/Australia/West create mode 100644 libs/pytz/zoneinfo/Australia/Yancowinna create mode 100644 libs/pytz/zoneinfo/Brazil/Acre create mode 100644 libs/pytz/zoneinfo/Brazil/DeNoronha create mode 100644 libs/pytz/zoneinfo/Brazil/East create mode 100644 libs/pytz/zoneinfo/Brazil/West create mode 100644 libs/pytz/zoneinfo/CET create mode 100644 libs/pytz/zoneinfo/CST6CDT create mode 100644 libs/pytz/zoneinfo/Canada/Atlantic create mode 100644 libs/pytz/zoneinfo/Canada/Central create mode 100644 libs/pytz/zoneinfo/Canada/Eastern create mode 100644 libs/pytz/zoneinfo/Canada/Mountain create mode 100644 libs/pytz/zoneinfo/Canada/Newfoundland create mode 100644 libs/pytz/zoneinfo/Canada/Pacific create mode 100644 libs/pytz/zoneinfo/Canada/Saskatchewan create mode 100644 libs/pytz/zoneinfo/Canada/Yukon create mode 100644 libs/pytz/zoneinfo/Chile/Continental create mode 100644 libs/pytz/zoneinfo/Chile/EasterIsland create mode 100644 libs/pytz/zoneinfo/Cuba create mode 100644 libs/pytz/zoneinfo/EET create mode 100644 libs/pytz/zoneinfo/EST create mode 100644 libs/pytz/zoneinfo/EST5EDT create mode 100644 libs/pytz/zoneinfo/Egypt create mode 100644 libs/pytz/zoneinfo/Eire create mode 100644 libs/pytz/zoneinfo/Etc/GMT create mode 100644 libs/pytz/zoneinfo/Etc/GMT+0 create mode 100644 libs/pytz/zoneinfo/Etc/GMT+1 create mode 100644 libs/pytz/zoneinfo/Etc/GMT+10 create mode 100644 libs/pytz/zoneinfo/Etc/GMT+11 create mode 100644 libs/pytz/zoneinfo/Etc/GMT+12 create mode 100644 libs/pytz/zoneinfo/Etc/GMT+2 create mode 100644 libs/pytz/zoneinfo/Etc/GMT+3 create mode 100644 libs/pytz/zoneinfo/Etc/GMT+4 create mode 100644 libs/pytz/zoneinfo/Etc/GMT+5 create mode 100644 libs/pytz/zoneinfo/Etc/GMT+6 create mode 100644 libs/pytz/zoneinfo/Etc/GMT+7 create mode 100644 libs/pytz/zoneinfo/Etc/GMT+8 create mode 100644 libs/pytz/zoneinfo/Etc/GMT+9 create mode 100644 libs/pytz/zoneinfo/Etc/GMT-0 create mode 100644 libs/pytz/zoneinfo/Etc/GMT-1 create mode 100644 libs/pytz/zoneinfo/Etc/GMT-10 create mode 100644 libs/pytz/zoneinfo/Etc/GMT-11 create mode 100644 libs/pytz/zoneinfo/Etc/GMT-12 create mode 100644 libs/pytz/zoneinfo/Etc/GMT-13 create mode 100644 libs/pytz/zoneinfo/Etc/GMT-14 create mode 100644 libs/pytz/zoneinfo/Etc/GMT-2 create mode 100644 libs/pytz/zoneinfo/Etc/GMT-3 create mode 100644 libs/pytz/zoneinfo/Etc/GMT-4 create mode 100644 libs/pytz/zoneinfo/Etc/GMT-5 create mode 100644 libs/pytz/zoneinfo/Etc/GMT-6 create mode 100644 libs/pytz/zoneinfo/Etc/GMT-7 create mode 100644 libs/pytz/zoneinfo/Etc/GMT-8 create mode 100644 libs/pytz/zoneinfo/Etc/GMT-9 create mode 100644 libs/pytz/zoneinfo/Etc/GMT0 create mode 100644 libs/pytz/zoneinfo/Etc/Greenwich create mode 100644 libs/pytz/zoneinfo/Etc/UCT create mode 100644 libs/pytz/zoneinfo/Etc/UTC create mode 100644 libs/pytz/zoneinfo/Etc/Universal create mode 100644 libs/pytz/zoneinfo/Etc/Zulu create mode 100644 libs/pytz/zoneinfo/Europe/Amsterdam create mode 100644 libs/pytz/zoneinfo/Europe/Andorra create mode 100644 libs/pytz/zoneinfo/Europe/Astrakhan create mode 100644 libs/pytz/zoneinfo/Europe/Athens create mode 100644 libs/pytz/zoneinfo/Europe/Belfast create mode 100644 libs/pytz/zoneinfo/Europe/Belgrade create mode 100644 libs/pytz/zoneinfo/Europe/Berlin create mode 100644 libs/pytz/zoneinfo/Europe/Bratislava create mode 100644 libs/pytz/zoneinfo/Europe/Brussels create mode 100644 libs/pytz/zoneinfo/Europe/Bucharest create mode 100644 libs/pytz/zoneinfo/Europe/Budapest create mode 100644 libs/pytz/zoneinfo/Europe/Busingen create mode 100644 libs/pytz/zoneinfo/Europe/Chisinau create mode 100644 libs/pytz/zoneinfo/Europe/Copenhagen create mode 100644 libs/pytz/zoneinfo/Europe/Dublin create mode 100644 libs/pytz/zoneinfo/Europe/Gibraltar create mode 100644 libs/pytz/zoneinfo/Europe/Guernsey create mode 100644 libs/pytz/zoneinfo/Europe/Helsinki create mode 100644 libs/pytz/zoneinfo/Europe/Isle_of_Man create mode 100644 libs/pytz/zoneinfo/Europe/Istanbul create mode 100644 libs/pytz/zoneinfo/Europe/Jersey create mode 100644 libs/pytz/zoneinfo/Europe/Kaliningrad create mode 100644 libs/pytz/zoneinfo/Europe/Kiev create mode 100644 libs/pytz/zoneinfo/Europe/Kirov create mode 100644 libs/pytz/zoneinfo/Europe/Lisbon create mode 100644 libs/pytz/zoneinfo/Europe/Ljubljana create mode 100644 libs/pytz/zoneinfo/Europe/London create mode 100644 libs/pytz/zoneinfo/Europe/Luxembourg create mode 100644 libs/pytz/zoneinfo/Europe/Madrid create mode 100644 libs/pytz/zoneinfo/Europe/Malta create mode 100644 libs/pytz/zoneinfo/Europe/Mariehamn create mode 100644 libs/pytz/zoneinfo/Europe/Minsk create mode 100644 libs/pytz/zoneinfo/Europe/Monaco create mode 100644 libs/pytz/zoneinfo/Europe/Moscow create mode 100644 libs/pytz/zoneinfo/Europe/Nicosia create mode 100644 libs/pytz/zoneinfo/Europe/Oslo create mode 100644 libs/pytz/zoneinfo/Europe/Paris create mode 100644 libs/pytz/zoneinfo/Europe/Podgorica create mode 100644 libs/pytz/zoneinfo/Europe/Prague create mode 100644 libs/pytz/zoneinfo/Europe/Riga create mode 100644 libs/pytz/zoneinfo/Europe/Rome create mode 100644 libs/pytz/zoneinfo/Europe/Samara create mode 100644 libs/pytz/zoneinfo/Europe/San_Marino create mode 100644 libs/pytz/zoneinfo/Europe/Sarajevo create mode 100644 libs/pytz/zoneinfo/Europe/Saratov create mode 100644 libs/pytz/zoneinfo/Europe/Simferopol create mode 100644 libs/pytz/zoneinfo/Europe/Skopje create mode 100644 libs/pytz/zoneinfo/Europe/Sofia create mode 100644 libs/pytz/zoneinfo/Europe/Stockholm create mode 100644 libs/pytz/zoneinfo/Europe/Tallinn create mode 100644 libs/pytz/zoneinfo/Europe/Tirane create mode 100644 libs/pytz/zoneinfo/Europe/Tiraspol create mode 100644 libs/pytz/zoneinfo/Europe/Ulyanovsk create mode 100644 libs/pytz/zoneinfo/Europe/Uzhgorod create mode 100644 libs/pytz/zoneinfo/Europe/Vaduz create mode 100644 libs/pytz/zoneinfo/Europe/Vatican create mode 100644 libs/pytz/zoneinfo/Europe/Vienna create mode 100644 libs/pytz/zoneinfo/Europe/Vilnius create mode 100644 libs/pytz/zoneinfo/Europe/Volgograd create mode 100644 libs/pytz/zoneinfo/Europe/Warsaw create mode 100644 libs/pytz/zoneinfo/Europe/Zagreb create mode 100644 libs/pytz/zoneinfo/Europe/Zaporozhye create mode 100644 libs/pytz/zoneinfo/Europe/Zurich create mode 100644 libs/pytz/zoneinfo/Factory create mode 100644 libs/pytz/zoneinfo/GB create mode 100644 libs/pytz/zoneinfo/GB-Eire create mode 100644 libs/pytz/zoneinfo/GMT create mode 100644 libs/pytz/zoneinfo/GMT+0 create mode 100644 libs/pytz/zoneinfo/GMT-0 create mode 100644 libs/pytz/zoneinfo/GMT0 create mode 100644 libs/pytz/zoneinfo/Greenwich create mode 100644 libs/pytz/zoneinfo/HST create mode 100644 libs/pytz/zoneinfo/Hongkong create mode 100644 libs/pytz/zoneinfo/Iceland create mode 100644 libs/pytz/zoneinfo/Indian/Antananarivo create mode 100644 libs/pytz/zoneinfo/Indian/Chagos create mode 100644 libs/pytz/zoneinfo/Indian/Christmas create mode 100644 libs/pytz/zoneinfo/Indian/Cocos create mode 100644 libs/pytz/zoneinfo/Indian/Comoro create mode 100644 libs/pytz/zoneinfo/Indian/Kerguelen create mode 100644 libs/pytz/zoneinfo/Indian/Mahe create mode 100644 libs/pytz/zoneinfo/Indian/Maldives create mode 100644 libs/pytz/zoneinfo/Indian/Mauritius create mode 100644 libs/pytz/zoneinfo/Indian/Mayotte create mode 100644 libs/pytz/zoneinfo/Indian/Reunion create mode 100644 libs/pytz/zoneinfo/Iran create mode 100644 libs/pytz/zoneinfo/Israel create mode 100644 libs/pytz/zoneinfo/Jamaica create mode 100644 libs/pytz/zoneinfo/Japan create mode 100644 libs/pytz/zoneinfo/Kwajalein create mode 100644 libs/pytz/zoneinfo/Libya create mode 100644 libs/pytz/zoneinfo/MET create mode 100644 libs/pytz/zoneinfo/MST create mode 100644 libs/pytz/zoneinfo/MST7MDT create mode 100644 libs/pytz/zoneinfo/Mexico/BajaNorte create mode 100644 libs/pytz/zoneinfo/Mexico/BajaSur create mode 100644 libs/pytz/zoneinfo/Mexico/General create mode 100644 libs/pytz/zoneinfo/NZ create mode 100644 libs/pytz/zoneinfo/NZ-CHAT create mode 100644 libs/pytz/zoneinfo/Navajo create mode 100644 libs/pytz/zoneinfo/PRC create mode 100644 libs/pytz/zoneinfo/PST8PDT create mode 100644 libs/pytz/zoneinfo/Pacific/Apia create mode 100644 libs/pytz/zoneinfo/Pacific/Auckland create mode 100644 libs/pytz/zoneinfo/Pacific/Bougainville create mode 100644 libs/pytz/zoneinfo/Pacific/Chatham create mode 100644 libs/pytz/zoneinfo/Pacific/Chuuk create mode 100644 libs/pytz/zoneinfo/Pacific/Easter create mode 100644 libs/pytz/zoneinfo/Pacific/Efate create mode 100644 libs/pytz/zoneinfo/Pacific/Enderbury create mode 100644 libs/pytz/zoneinfo/Pacific/Fakaofo create mode 100644 libs/pytz/zoneinfo/Pacific/Fiji create mode 100644 libs/pytz/zoneinfo/Pacific/Funafuti create mode 100644 libs/pytz/zoneinfo/Pacific/Galapagos create mode 100644 libs/pytz/zoneinfo/Pacific/Gambier create mode 100644 libs/pytz/zoneinfo/Pacific/Guadalcanal create mode 100644 libs/pytz/zoneinfo/Pacific/Guam create mode 100644 libs/pytz/zoneinfo/Pacific/Honolulu create mode 100644 libs/pytz/zoneinfo/Pacific/Johnston create mode 100644 libs/pytz/zoneinfo/Pacific/Kiritimati create mode 100644 libs/pytz/zoneinfo/Pacific/Kosrae create mode 100644 libs/pytz/zoneinfo/Pacific/Kwajalein create mode 100644 libs/pytz/zoneinfo/Pacific/Majuro create mode 100644 libs/pytz/zoneinfo/Pacific/Marquesas create mode 100644 libs/pytz/zoneinfo/Pacific/Midway create mode 100644 libs/pytz/zoneinfo/Pacific/Nauru create mode 100644 libs/pytz/zoneinfo/Pacific/Niue create mode 100644 libs/pytz/zoneinfo/Pacific/Norfolk create mode 100644 libs/pytz/zoneinfo/Pacific/Noumea create mode 100644 libs/pytz/zoneinfo/Pacific/Pago_Pago create mode 100644 libs/pytz/zoneinfo/Pacific/Palau create mode 100644 libs/pytz/zoneinfo/Pacific/Pitcairn create mode 100644 libs/pytz/zoneinfo/Pacific/Pohnpei create mode 100644 libs/pytz/zoneinfo/Pacific/Ponape create mode 100644 libs/pytz/zoneinfo/Pacific/Port_Moresby create mode 100644 libs/pytz/zoneinfo/Pacific/Rarotonga create mode 100644 libs/pytz/zoneinfo/Pacific/Saipan create mode 100644 libs/pytz/zoneinfo/Pacific/Samoa create mode 100644 libs/pytz/zoneinfo/Pacific/Tahiti create mode 100644 libs/pytz/zoneinfo/Pacific/Tarawa create mode 100644 libs/pytz/zoneinfo/Pacific/Tongatapu create mode 100644 libs/pytz/zoneinfo/Pacific/Truk create mode 100644 libs/pytz/zoneinfo/Pacific/Wake create mode 100644 libs/pytz/zoneinfo/Pacific/Wallis create mode 100644 libs/pytz/zoneinfo/Pacific/Yap create mode 100644 libs/pytz/zoneinfo/Poland create mode 100644 libs/pytz/zoneinfo/Portugal create mode 100644 libs/pytz/zoneinfo/ROC create mode 100644 libs/pytz/zoneinfo/ROK create mode 100644 libs/pytz/zoneinfo/Singapore create mode 100644 libs/pytz/zoneinfo/Turkey create mode 100644 libs/pytz/zoneinfo/UCT create mode 100644 libs/pytz/zoneinfo/US/Alaska create mode 100644 libs/pytz/zoneinfo/US/Aleutian create mode 100644 libs/pytz/zoneinfo/US/Arizona create mode 100644 libs/pytz/zoneinfo/US/Central create mode 100644 libs/pytz/zoneinfo/US/East-Indiana create mode 100644 libs/pytz/zoneinfo/US/Eastern create mode 100644 libs/pytz/zoneinfo/US/Hawaii create mode 100644 libs/pytz/zoneinfo/US/Indiana-Starke create mode 100644 libs/pytz/zoneinfo/US/Michigan create mode 100644 libs/pytz/zoneinfo/US/Mountain create mode 100644 libs/pytz/zoneinfo/US/Pacific create mode 100644 libs/pytz/zoneinfo/US/Samoa create mode 100644 libs/pytz/zoneinfo/UTC create mode 100644 libs/pytz/zoneinfo/Universal create mode 100644 libs/pytz/zoneinfo/W-SU create mode 100644 libs/pytz/zoneinfo/WET create mode 100644 libs/pytz/zoneinfo/Zulu create mode 100644 libs/pytz/zoneinfo/iso3166.tab create mode 100644 libs/pytz/zoneinfo/leapseconds create mode 100644 libs/pytz/zoneinfo/posixrules create mode 100644 libs/pytz/zoneinfo/tzdata.zi create mode 100644 libs/pytz/zoneinfo/zone.tab create mode 100644 libs/pytz/zoneinfo/zone1970.tab create mode 100644 libs/stevedore/example/__init__.py create mode 100644 libs/stevedore/example/base.py create mode 100644 libs/stevedore/example/load_as_driver.py create mode 100644 libs/stevedore/example/load_as_extension.py create mode 100644 libs/stevedore/example/setup.py create mode 100644 libs/stevedore/example/simple.py create mode 100644 libs/stevedore/example2/__init__.py create mode 100644 libs/stevedore/example2/fields.py create mode 100644 libs/stevedore/example2/setup.py create mode 100644 libs/stevedore/exception.py create mode 100644 libs/stevedore/sphinxext.py create mode 100644 libs/stevedore/tests/__init__.py create mode 100644 libs/stevedore/tests/extension_unimportable.py create mode 100644 libs/stevedore/tests/manager.py create mode 100644 libs/stevedore/tests/test_callback.py create mode 100644 libs/stevedore/tests/test_dispatch.py create mode 100644 libs/stevedore/tests/test_driver.py create mode 100644 libs/stevedore/tests/test_enabled.py create mode 100644 libs/stevedore/tests/test_example_fields.py create mode 100644 libs/stevedore/tests/test_example_simple.py create mode 100644 libs/stevedore/tests/test_extension.py create mode 100644 libs/stevedore/tests/test_hook.py create mode 100644 libs/stevedore/tests/test_named.py create mode 100644 libs/stevedore/tests/test_sphinxext.py create mode 100644 libs/stevedore/tests/test_test_manager.py create mode 100644 libs/stevedore/tests/utils.py create mode 100644 libs/subliminal/subtitles/__init__.py create mode 100644 libs/subliminal/subtitles/subrip.py diff --git a/libs/appdirs.py b/libs/appdirs.py new file mode 100644 index 00000000..ae67001a --- /dev/null +++ b/libs/appdirs.py @@ -0,0 +1,608 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (c) 2005-2010 ActiveState Software Inc. +# Copyright (c) 2013 Eddy Petrișor + +"""Utilities for determining application-specific dirs. + +See for details and usage. +""" +# Dev Notes: +# - MSDN on where to store app data files: +# http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120 +# - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html +# - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html + +__version_info__ = (1, 4, 3) +__version__ = '.'.join(map(str, __version_info__)) + + +import sys +import os + +PY3 = sys.version_info[0] == 3 + +if PY3: + unicode = str + +if sys.platform.startswith('java'): + import platform + os_name = platform.java_ver()[3][0] + if os_name.startswith('Windows'): # "Windows XP", "Windows 7", etc. + system = 'win32' + elif os_name.startswith('Mac'): # "Mac OS X", etc. + system = 'darwin' + else: # "Linux", "SunOS", "FreeBSD", etc. + # Setting this to "linux2" is not ideal, but only Windows or Mac + # are actually checked for and the rest of the module expects + # *sys.platform* style strings. + system = 'linux2' +else: + system = sys.platform + + + +def user_data_dir(appname=None, appauthor=None, version=None, roaming=False): + r"""Return full path to the user-specific data dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "roaming" (boolean, default False) can be set True to use the Windows + roaming appdata directory. That means that for users on a Windows + network setup for roaming profiles, this user data will be + sync'd on login. See + + for a discussion of issues. + + Typical user data directories are: + Mac OS X: ~/Library/Application Support/ + Unix: ~/.local/share/ # or in $XDG_DATA_HOME, if defined + Win XP (not roaming): C:\Documents and Settings\\Application Data\\ + Win XP (roaming): C:\Documents and Settings\\Local Settings\Application Data\\ + Win 7 (not roaming): C:\Users\\AppData\Local\\ + Win 7 (roaming): C:\Users\\AppData\Roaming\\ + + For Unix, we follow the XDG spec and support $XDG_DATA_HOME. + That means, by default "~/.local/share/". + """ + if system == "win32": + if appauthor is None: + appauthor = appname + const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA" + path = os.path.normpath(_get_win_folder(const)) + if appname: + if appauthor is not False: + path = os.path.join(path, appauthor, appname) + else: + path = os.path.join(path, appname) + elif system == 'darwin': + path = os.path.expanduser('~/Library/Application Support/') + if appname: + path = os.path.join(path, appname) + else: + path = os.getenv('XDG_DATA_HOME', os.path.expanduser("~/.local/share")) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def site_data_dir(appname=None, appauthor=None, version=None, multipath=False): + r"""Return full path to the user-shared data dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "multipath" is an optional parameter only applicable to *nix + which indicates that the entire list of data dirs should be + returned. By default, the first item from XDG_DATA_DIRS is + returned, or '/usr/local/share/', + if XDG_DATA_DIRS is not set + + Typical site data directories are: + Mac OS X: /Library/Application Support/ + Unix: /usr/local/share/ or /usr/share/ + Win XP: C:\Documents and Settings\All Users\Application Data\\ + Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) + Win 7: C:\ProgramData\\ # Hidden, but writeable on Win 7. + + For Unix, this is using the $XDG_DATA_DIRS[0] default. + + WARNING: Do not use this on Windows. See the Vista-Fail note above for why. + """ + if system == "win32": + if appauthor is None: + appauthor = appname + path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA")) + if appname: + if appauthor is not False: + path = os.path.join(path, appauthor, appname) + else: + path = os.path.join(path, appname) + elif system == 'darwin': + path = os.path.expanduser('/Library/Application Support') + if appname: + path = os.path.join(path, appname) + else: + # XDG default for $XDG_DATA_DIRS + # only first, if multipath is False + path = os.getenv('XDG_DATA_DIRS', + os.pathsep.join(['/usr/local/share', '/usr/share'])) + pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)] + if appname: + if version: + appname = os.path.join(appname, version) + pathlist = [os.sep.join([x, appname]) for x in pathlist] + + if multipath: + path = os.pathsep.join(pathlist) + else: + path = pathlist[0] + return path + + if appname and version: + path = os.path.join(path, version) + return path + + +def user_config_dir(appname=None, appauthor=None, version=None, roaming=False): + r"""Return full path to the user-specific config dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "roaming" (boolean, default False) can be set True to use the Windows + roaming appdata directory. That means that for users on a Windows + network setup for roaming profiles, this user data will be + sync'd on login. See + + for a discussion of issues. + + Typical user config directories are: + Mac OS X: same as user_data_dir + Unix: ~/.config/ # or in $XDG_CONFIG_HOME, if defined + Win *: same as user_data_dir + + For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME. + That means, by default "~/.config/". + """ + if system in ["win32", "darwin"]: + path = user_data_dir(appname, appauthor, None, roaming) + else: + path = os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config")) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def site_config_dir(appname=None, appauthor=None, version=None, multipath=False): + r"""Return full path to the user-shared data dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "multipath" is an optional parameter only applicable to *nix + which indicates that the entire list of config dirs should be + returned. By default, the first item from XDG_CONFIG_DIRS is + returned, or '/etc/xdg/', if XDG_CONFIG_DIRS is not set + + Typical site config directories are: + Mac OS X: same as site_data_dir + Unix: /etc/xdg/ or $XDG_CONFIG_DIRS[i]/ for each value in + $XDG_CONFIG_DIRS + Win *: same as site_data_dir + Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) + + For Unix, this is using the $XDG_CONFIG_DIRS[0] default, if multipath=False + + WARNING: Do not use this on Windows. See the Vista-Fail note above for why. + """ + if system in ["win32", "darwin"]: + path = site_data_dir(appname, appauthor) + if appname and version: + path = os.path.join(path, version) + else: + # XDG default for $XDG_CONFIG_DIRS + # only first, if multipath is False + path = os.getenv('XDG_CONFIG_DIRS', '/etc/xdg') + pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)] + if appname: + if version: + appname = os.path.join(appname, version) + pathlist = [os.sep.join([x, appname]) for x in pathlist] + + if multipath: + path = os.pathsep.join(pathlist) + else: + path = pathlist[0] + return path + + +def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True): + r"""Return full path to the user-specific cache dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "opinion" (boolean) can be False to disable the appending of + "Cache" to the base app data dir for Windows. See + discussion below. + + Typical user cache directories are: + Mac OS X: ~/Library/Caches/ + Unix: ~/.cache/ (XDG default) + Win XP: C:\Documents and Settings\\Local Settings\Application Data\\\Cache + Vista: C:\Users\\AppData\Local\\\Cache + + On Windows the only suggestion in the MSDN docs is that local settings go in + the `CSIDL_LOCAL_APPDATA` directory. This is identical to the non-roaming + app data dir (the default returned by `user_data_dir` above). Apps typically + put cache data somewhere *under* the given dir here. Some examples: + ...\Mozilla\Firefox\Profiles\\Cache + ...\Acme\SuperApp\Cache\1.0 + OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value. + This can be disabled with the `opinion=False` option. + """ + if system == "win32": + if appauthor is None: + appauthor = appname + path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA")) + if appname: + if appauthor is not False: + path = os.path.join(path, appauthor, appname) + else: + path = os.path.join(path, appname) + if opinion: + path = os.path.join(path, "Cache") + elif system == 'darwin': + path = os.path.expanduser('~/Library/Caches') + if appname: + path = os.path.join(path, appname) + else: + path = os.getenv('XDG_CACHE_HOME', os.path.expanduser('~/.cache')) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def user_state_dir(appname=None, appauthor=None, version=None, roaming=False): + r"""Return full path to the user-specific state dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "roaming" (boolean, default False) can be set True to use the Windows + roaming appdata directory. That means that for users on a Windows + network setup for roaming profiles, this user data will be + sync'd on login. See + + for a discussion of issues. + + Typical user state directories are: + Mac OS X: same as user_data_dir + Unix: ~/.local/state/ # or in $XDG_STATE_HOME, if defined + Win *: same as user_data_dir + + For Unix, we follow this Debian proposal + to extend the XDG spec and support $XDG_STATE_HOME. + + That means, by default "~/.local/state/". + """ + if system in ["win32", "darwin"]: + path = user_data_dir(appname, appauthor, None, roaming) + else: + path = os.getenv('XDG_STATE_HOME', os.path.expanduser("~/.local/state")) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def user_log_dir(appname=None, appauthor=None, version=None, opinion=True): + r"""Return full path to the user-specific log dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "opinion" (boolean) can be False to disable the appending of + "Logs" to the base app data dir for Windows, and "log" to the + base cache dir for Unix. See discussion below. + + Typical user log directories are: + Mac OS X: ~/Library/Logs/ + Unix: ~/.cache//log # or under $XDG_CACHE_HOME if defined + Win XP: C:\Documents and Settings\\Local Settings\Application Data\\\Logs + Vista: C:\Users\\AppData\Local\\\Logs + + On Windows the only suggestion in the MSDN docs is that local settings + go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in + examples of what some windows apps use for a logs dir.) + + OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA` + value for Windows and appends "log" to the user cache dir for Unix. + This can be disabled with the `opinion=False` option. + """ + if system == "darwin": + path = os.path.join( + os.path.expanduser('~/Library/Logs'), + appname) + elif system == "win32": + path = user_data_dir(appname, appauthor, version) + version = False + if opinion: + path = os.path.join(path, "Logs") + else: + path = user_cache_dir(appname, appauthor, version) + version = False + if opinion: + path = os.path.join(path, "log") + if appname and version: + path = os.path.join(path, version) + return path + + +class AppDirs(object): + """Convenience wrapper for getting application dirs.""" + def __init__(self, appname=None, appauthor=None, version=None, + roaming=False, multipath=False): + self.appname = appname + self.appauthor = appauthor + self.version = version + self.roaming = roaming + self.multipath = multipath + + @property + def user_data_dir(self): + return user_data_dir(self.appname, self.appauthor, + version=self.version, roaming=self.roaming) + + @property + def site_data_dir(self): + return site_data_dir(self.appname, self.appauthor, + version=self.version, multipath=self.multipath) + + @property + def user_config_dir(self): + return user_config_dir(self.appname, self.appauthor, + version=self.version, roaming=self.roaming) + + @property + def site_config_dir(self): + return site_config_dir(self.appname, self.appauthor, + version=self.version, multipath=self.multipath) + + @property + def user_cache_dir(self): + return user_cache_dir(self.appname, self.appauthor, + version=self.version) + + @property + def user_state_dir(self): + return user_state_dir(self.appname, self.appauthor, + version=self.version) + + @property + def user_log_dir(self): + return user_log_dir(self.appname, self.appauthor, + version=self.version) + + +#---- internal support stuff + +def _get_win_folder_from_registry(csidl_name): + """This is a fallback technique at best. I'm not sure if using the + registry for this guarantees us the correct answer for all CSIDL_* + names. + """ + if PY3: + import winreg as _winreg + else: + import _winreg + + shell_folder_name = { + "CSIDL_APPDATA": "AppData", + "CSIDL_COMMON_APPDATA": "Common AppData", + "CSIDL_LOCAL_APPDATA": "Local AppData", + }[csidl_name] + + key = _winreg.OpenKey( + _winreg.HKEY_CURRENT_USER, + r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" + ) + dir, type = _winreg.QueryValueEx(key, shell_folder_name) + return dir + + +def _get_win_folder_with_pywin32(csidl_name): + from win32com.shell import shellcon, shell + dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0) + # Try to make this a unicode path because SHGetFolderPath does + # not return unicode strings when there is unicode data in the + # path. + try: + dir = unicode(dir) + + # Downgrade to short path name if have highbit chars. See + # . + has_high_char = False + for c in dir: + if ord(c) > 255: + has_high_char = True + break + if has_high_char: + try: + import win32api + dir = win32api.GetShortPathName(dir) + except ImportError: + pass + except UnicodeError: + pass + return dir + + +def _get_win_folder_with_ctypes(csidl_name): + import ctypes + + csidl_const = { + "CSIDL_APPDATA": 26, + "CSIDL_COMMON_APPDATA": 35, + "CSIDL_LOCAL_APPDATA": 28, + }[csidl_name] + + buf = ctypes.create_unicode_buffer(1024) + ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf) + + # Downgrade to short path name if have highbit chars. See + # . + has_high_char = False + for c in buf: + if ord(c) > 255: + has_high_char = True + break + if has_high_char: + buf2 = ctypes.create_unicode_buffer(1024) + if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024): + buf = buf2 + + return buf.value + +def _get_win_folder_with_jna(csidl_name): + import array + from com.sun import jna + from com.sun.jna.platform import win32 + + buf_size = win32.WinDef.MAX_PATH * 2 + buf = array.zeros('c', buf_size) + shell = win32.Shell32.INSTANCE + shell.SHGetFolderPath(None, getattr(win32.ShlObj, csidl_name), None, win32.ShlObj.SHGFP_TYPE_CURRENT, buf) + dir = jna.Native.toString(buf.tostring()).rstrip("\0") + + # Downgrade to short path name if have highbit chars. See + # . + has_high_char = False + for c in dir: + if ord(c) > 255: + has_high_char = True + break + if has_high_char: + buf = array.zeros('c', buf_size) + kernel = win32.Kernel32.INSTANCE + if kernel.GetShortPathName(dir, buf, buf_size): + dir = jna.Native.toString(buf.tostring()).rstrip("\0") + + return dir + +if system == "win32": + try: + import win32com.shell + _get_win_folder = _get_win_folder_with_pywin32 + except ImportError: + try: + from ctypes import windll + _get_win_folder = _get_win_folder_with_ctypes + except ImportError: + try: + import com.sun.jna + _get_win_folder = _get_win_folder_with_jna + except ImportError: + _get_win_folder = _get_win_folder_from_registry + + +#---- self test code + +if __name__ == "__main__": + appname = "MyApp" + appauthor = "MyCompany" + + props = ("user_data_dir", + "user_config_dir", + "user_cache_dir", + "user_state_dir", + "user_log_dir", + "site_data_dir", + "site_config_dir") + + print("-- app dirs %s --" % __version__) + + print("-- app dirs (with optional 'version')") + dirs = AppDirs(appname, appauthor, version="1.0") + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) + + print("\n-- app dirs (without optional 'version')") + dirs = AppDirs(appname, appauthor) + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) + + print("\n-- app dirs (without optional 'appauthor')") + dirs = AppDirs(appname) + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) + + print("\n-- app dirs (with disabled 'appauthor')") + dirs = AppDirs(appname, appauthor=False) + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) diff --git a/libs/bin/guessit.exe b/libs/bin/guessit.exe index b50c3e4d518c685d57919d1ae8a71cb4320d7361..0645fb74d39f3161cd6c95184707ac7ffe6faad1 100644 GIT binary patch delta 27 gcmaEJjrGkn)`l&N=2=YdIi_1?F@6Bi+S!bb0Ip{W@c;k- delta 27 gcmaEJjrGkn)`l&N=2=Xt?9(l?7(ak$?QBLz0G#&;TL1t6 diff --git a/libs/bin/pbr.exe b/libs/bin/pbr.exe new file mode 100644 index 0000000000000000000000000000000000000000..9021803af2bacbf23dfcc3faca245f68e33bb2c5 GIT binary patch literal 93032 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 F^DmMNBbWdH literal 0 HcmV?d00001 diff --git a/libs/bin/srt.exe b/libs/bin/srt.exe new file mode 100644 index 0000000000000000000000000000000000000000..57a09da55ca5bb49730ee86512730b110cefb87c GIT binary patch literal 93034 zcmeFae|!{0wm01KBgrHTnE?_A5MachXi%deNF0I#WI|jC4hCk362KMWILj(RH{ePj zu``%XGb_8R_v$|4mCL$UukKy$uKZHLgkS~~70^XiSdF_`t+BHjmuwgyrl0Sro=Jjw z?{oin-_P^UgJ!zA>QvRKQ>RXyI(4eL;;wCiMGyol{&Zas_TfqYJpA{+|A`|xbHb~c z!Yk?TT(QqI@0}|a2Jc_%TD|7M`_|m^W7oa+Jn+DSqU(n%U2CKVT=zfVD!rr9_2UOu zth|2c(2Tr9(NA5t&2BDL`2=vh9AO<+De^2=$}gv zmS4YS#XaIZf{>Aqgm(N*!QV0b4f^Ln)z=$f!r^I1aH3)=lNe*rKaU_ZU%zJUntKt) z+ln>|cjCo%Iii5`T)$@Jss{o1@0myk4S0EXeFttfQvct-{|_jzNbRiew1NS4Gz_05 z6uzl=d*xc2AbBHRr%#vck#O%NT@UJz5kcY;ANvDFj(j-FNbm)xT=WR+p`nOt_W0P8 zEK0P8OnSD^?h(|A-okg706sq2ikj34TcA*nl=b=?2UD8I&k}qKn1+r28~3R^yR!lj^nQw?s+{dbRh|=(1`mLGGLq2+l*55pQpy9$cP}GL+h0rM8RRhgu4c zx}%OKT7nA!v4FXBT@RT9y41`3IS_AnE*m8XPb*%Q(%Yx&^5HyXQK#aKyQ8%hr8Zva z2W*_ct~S75vx4y|(HP0bibhZgHnoctqFDK`%N-TRsa>Izsz~hz=bl$+9aw}7MCRoLu4 z?|8B~xEgIzq)s2ZjiSAs`QGkO3TmtZ@Y4nkR5g3YCJ4YrK0GB~>d2Sc^UpnOF6;>j zerni!qbjs1!0tswy!f`U&F4=CpFsIO*7*&mOQdwBzVvP_vqp99--U!4_b@T7+#Ox} zrDjpQT~yT4(a7%Ys#?aoR_?U>L)U{qg*}QCXIB7;sw#BqIDasB-7JH5fPu}gXWPIS zND<4lhXTP@P;jFzcwOF6oJwM);=0wVHNLdYC4fjm@{PtPtTw(Sb{ zNOnDY1_8uVB~uyl8T?0MWB86>(JX30dPqQyTtF2zdyMpsczx$tbiOg14l50Lr|||( z26Gkafq+t)m#b$_rAkgmO7on)&}uw3_(JKGdiE4VqgcDVG0(YLNp;tK=<;JJV<0x3P)i8KVWg3Eac>rsLVDD)X(b9NGWK@OJz1$vbe z-a66{&N0e`bmFghcnvo4VhT7Sh;|y%=NJUW0?=J8DgD$Vy!JAHD$&XMht$8~%t)CH z($2A0r~%C<$nlBdn2^oKB+OvMx{@8hy#}!KJ~9kdt8H?dO}!L*hq|=d7P1HTQJKsG z-YPsAZieWo44y{R0`{wmx*mBX$FVm}KAb}pjG(edC(0I+eOnpK?Ir3<07vWPs2Mp3 zJd?n`z!2c5d|o5pDyZkh(T=^TlyD-M0EEmn#i`QgiG+QL1kqO5T%)8SHNcjFAu2Jz z7ow)IdPrDY|2Yjw$P^#@<^t90tdZRlrK^xdo;k77@kDd5kz@4_Jl(tYXOd|cLd=3%B8 zn2SgxXIs(5HS+X{qBZ2wQbH5uW^2^~A3Fd@qobnXcC_&b*k8+wtTt=I2#4QbV&Nia zaCORVf;8m%L7F}MA+YLXUO@@HPZVv+ZUz`_Xf#aEA0kp_X7x#WDLh)E*k?z=T?qTy zj46z*MElivVRKjqNim*W-%yY4jAJ}S9-|qgu%}9W&mCWz-88K3;!x3EcQHduo8>;T z<}1ytevOPhB;Tj=Y^x|+Rb?dH4MFT{OBM3Z`vW0cF!l|NsRAHMBD?U6`yAz2!ShT< z9-?!DM476pBD?8XQ@ouX{XDZBb2O)i!87Bf&v{Q?8Qg|K(C0qZb)Jg=^D?8qRwXlJ zSk6;-xmzX1vs@8uPG&j4vl#F*z6U-M?j%zAmF@IoKf;d^?!a$hbMbb12D_;!V#PHm zied>c=;}+vEYoO4ep_&UrFY3t+DH%BSCbm)}c6+j0Jn>N^M7BGX#qJ z6Hvk(m9p4}V+0{8jD(zFKS8jtS$hN!lAWsp&^$gyM-!*M^)!*>;{Y z2RXH)(2Qz|-I9wn_7@lGi+HX-NZON{r zLN-{@jx=_OpajgPyckT4HR>X}W~*_(B@UOHAsK8n;iFPlO|esiut|WCQYu~t6fj) zZ7A7er9@~QhpYleL+*4IHdh9Uy-r61t;4`BVB0b5H|XjFr}z-u2Xb$Yy+i=D_OLE~ z0;MY}QqjcgX7)p$?yu}|=h3B{Nykj=3dWTl)bl=FyV zFaB@KZ>g*86_$!=YDHYWXZ1JBApDI+mXxDw1;6w#BmuRwo*KgWY!qt+mnT|UgCK9I zcCT7t4<8l(oc}dil=-a|9Y>3fJNBBs)1nsMBH(qB@H#HGa=Z@Zw`e24Uz~A?Q)CPR zG$zSOm81Y%YG41LKOmP74+>Han|}kie>{8YIxLWMV9QNsrDIu$mJ%1x%wDVWfNNJVEhpc|3 zh|<{B%MwyTV-_!MEj+oO%GFYK5WHeH%PlVXkhT6o9Yn^)FG77w0pSEhKt0qFPf@Mm zI%sR^MfvjyEuW{VR{e{)Yu<_kxh0RM_+2pB$P*)-n{lpa3 z4IK0$s*8<)BpoDNc>CO4YbMtBEl1t!$Efe-A8EOeBDXjfu$m%4sGn~a>d-VTLvC|n zVX*|%P4*SUiX6|X9Vs_EeXJP3P&Dex4S0wYuN}M%-JP-w2qNBccgvayCA`9%`sH?g zv##g2prO2=Q9!+_y4A?Ld{EvB8x?sWt9C>p4@Z&}eiytn&t3^pbEmp6&sKP*X-S^_ z{2?eZ5D-ln@*&erZ;NYWW)g2QVx=!+W?eHppk8YEi_P*0J)D+Lw6V*e1Bsc*93JG5 z{(g5W!TwdvD17@3y{~VR<%0aRUicn$-lu}eR4=xxKj=mISKg$Fqg!H51nmf#wIjaR4j51QwJY`hM-i$-ET{y*gvDnsDP0O zCPz>eV*i0~afNN|FkUHJhuF}>ST&@g`|VA0LhXeo7oY!Hj+@uq94Sq=m5{At{Rnn| z3O?*^6?3D)F^FAl7}O+MW*{m(DiA&7W*fwqdK%JrD4W3Rr6HvoK4KV%Gulgj7C0j3g6Rf+uR=wmty#|IOcWtlZvDXk0(5KM?4%Ubt-YN*!Y_ghWnrh?u zpFpBtQ`@W7cE!Sga#we+St8eV3*vHQrt=&(FRjj;Gi=Wps}? z5$vLS#u2^>wX5E&*y}Xu)M6owZnjhR*w`rGk8WcvAVO4_2&`j| z6V!aWOO573WS^Iuu?8c?sdYlR+@?dhYzH`*V>*f@r+7oLlqFtUEagbo@zNbAoeVPU zRWyJKU%?B<6eF-S%Gk{QiU+j59AmgEM9ZAZxaC7AwlD<_QW#T^9SWnyvpr8z!VnVu z*|3U7op*6Q%&Kk$s=El)BC7F>QcZert<8OjG}~6x{2tbf3GP~hAlN1LCaQpTP;KWh z;#sBE7GO~fg(@&-&s@7ldN9C#fbQTVA1lZEpnDx}xtIb0@#%z?Pg5=SCuz#kQuc3v z*48sCZ?kj__0DJl%~JUk(>|f4J=J237=ZgYpeL_R%wi=27`2n>vZ6yTuI`Yo3@{CK zs?da-K8$aBfPD8rHvz%He`x;ZTQu*S70{6jBB}qOd9l8VZX8^G5!~*UMJGBSRF7< zkn>6esRF3+P=sOJsIXx?k5lP)6blRhUc|BvGWVw-yJPRL0O?HEJNC{*wi<|n;VM>R zhr~f^>@FA)1VpqzlOG0X=?^t>v7l7+iZdV)9ebxk+ozn_j=eWh<~G0{0<4+r0myud zAW>$@1oIuYW0>%cCO|rRd-Ge)pB~$MrMGt(EO`md*j@?ogxS=62`uvr@J+PwRs@M< zR)U6DmKC|FgQ{SkEM8`X#dn!CWUBPD-`~au0Bk|-R>#&$#K8ef%CtEl+4ARFW0Me4 z)6_d`>goJHD%IURhb(BzDPpNC&PwuU6Iwn??J2#qHQN=7x?|7NYjs?e;`uF> zLoJt5P*Ws#J8>n}d#Z)kT7X&~h7l8@BF;W5=Z%4Yl3eOs%uF`R5iPxLdWK}ty*3Y& zn{(&q+65OTC=cb}^6@{7OyTB-Q$Q|lI#(mXbL*Yz9rm6Un`k@VLKC8BQRhM;qvD>@ z0;^S|BB5wO%&FdPi???vDe@T7$7x9a5bYx^-iC3Cp3P>K{syyO!zNBOO(tP51WW2F zTBOm-wUA;kk$-0eT7}GftoR7p=y+Ozs%7>UWXZ`(G^k1C-Y2(zCD%GlN|{~C^s_%e zPMM&et#k@iel~tGh+1Z^YG{7gCb#zjMjQEpNgV!yP0W0enkl74%W_DQHs(b?>z&SJ zeA8UC=qO|*q=n5qz=ln;8%-QK&2+Bp{);KX?uNf(Go<6 z_p!bo2*OT=y%m;&5PCVCHG=2SDYqM$fYU6#z;+Wp3y@Z&#P!P>Uy@r7A zBjMc!iS%W9QcL_fLYS*GQMnm%0%F0e6o8TB1}7%r8mN4E2p0 zJib7#R@kfq0rrB8w;&f>Gl=g3@_RanoW-u=Rq<)_I3R~awbGt4yDU!kv)z-ZTjFfm z?Rc`i&;op{20Z`;gb%g%bZxj=mJ1bTh>wl@3QefV#jI6h7iitbS*w6(n1d>4o*@em zOfJds^m|m7U@$*|#P>r{wMQJvi-6fCk6Php|Ni$RgRvPzz(I^f^R@N?iuJSe1eIi| zPH>AEtFzS*6vPwz$0wJ!M`5w5g6<#63i=4SM^JTPPjS(6U_xn#ADdWMiLJt9w6EeW znz>Me2kSiQ*=ajwAY8wXVrc(e`eOeOh}N3o#vH^*XXSk&o|)_3FFabjiy??Xrc`vW zyTJ9}Fk2{>k-lEVbQn5#gp0cCg(e?0kk+moLx9 zDCnS3@Oec7%Eq=66kCoC;@Q&KR*DFj*uB(DFd-H@4^z|*8cREubnNU1(%0yLY9AMJW<(y2BzU8y*Wea_$AhEhP^l}z=XRlMzTZHGYcpTh{p z(g2@eLDk#NR$)J(m3<6^V^2aJ@>#CFb265RJL3}|`iFMYZ*~{`j_ah~B1XR@9r&%; zn(cJaW2lus#__W>TyJf30$i0Tz~_Tp9bT6YR~heol}PVwAG8ciuj znhF2ypv0ZMpkOqm3%}`Bp*fn;jSxD~u-Pl&(^$jrXvA{eu)yls8>s_4C;~+NH?*h< zvrhH~Lw~f%|d%2@=TXV)@nI^k60kb*N9ij@%7>;wgr5c7%bNy2!-Yzvmm@?0!_7{g=gf7 zUXzyoS~^;SpxM}fuzw}|+lHWEDiK6|nI>gGgaX}LM%XMiF$ZVl_ zm&`InZ#n1yq_Sm}>IjcUiRW8|W)Ryui4zoFv@pQU9;ZI|F^cn)QST+57pDV{0DLl%GV z6?8glUI>(F&)*Sl1d!a8Isk+oERiJYN}eSp_&Rd<*`G8%&M@ksYGwcpOw`&eY>XV? z$p;4~J1N;LXcI$e!LvO1U;2~B%59mHY!U|XOCdH(W{ShvJ(hkZu_CDD2J1i&T5Wr2 zGY}KsXO)C`7DP79vo5UH^ptjt0J0gE+hL1THdvME$_AUVAy+AP^0jct8C)$uR4hP| zg=e_6AAJ7&MDRIQEHo*$ySY8i5qS&L;C8o&bysnYcsH3vNWUq6k;pF1ij;jL$DQkk zN6KK;+HnO+01X?SNaoU~?((y5Ad#x7cqyuNSC0pCk=^HK3;#yZW!lfwIOaR;-q3Vb zPJ&Gx%I$pC|Aa+je(*UgNs?J*ZXv6~;0rhNIB5hbU_WLkh`%ejyR@;W!vG{xnvr$J zF4Ukbv%4>eBkS+uHaFzq^mq?}20Zt=alyoIfJu8d0-#`w{*KALfteoB886 zujBE|hS&fV;pzZwQ2%)bXmL3sK@X7(lx#lu+Tb5Dna zAYEz@S1%&c>e-FFT+vdkw|{$e|65G0#|oQ$^p8dH0>{!DrP;Bf`1gqc`^E#eN0o0>o^e^Zt@(3$**w(;FrFl+eRh~0~ zzx;M=9dl;65uQSC`jnLn%Ogn71na>I2X?a+J1JkQTG6#a!CDdYTt+6hzg90WNCDjqtmoUYw`08Pf5E#K z8$H$P@#(#+r{C0 zKQW-buO4ClWJJTpMFR0#SoNSk2V?aay`!1sHZ<^BOqDP8iB|XD*Igf(x-PQh_fB;PFqR*&3evHliCQto#t!)eVL!tBOpoBRH`T^QSWY`e)dh1(8C+ox#sQmIZA7vw{Fj$vtURp6$*B@Q=x2yA9D$eaI$+;GBiY zoYb;y5C+_j<;j+vw7;dcB*r`0hQzT6Be~maU+Z8+kXgyisOnb7Z!7HBCB=%!R94t5 z_qDGd;Sbr8JGHd!g%N*~TtYiuf|%=P%d#-o5O~TKAFDV(Y%){MU*_Nb9~~6jotwSG#xzlB;1Zb_Y&hLlnXm zpW32qvMQTw$|ifur_LcQkxkB*UV3T2kVSlL2XOwoZ&1%SWtkeCo;#%TkuBr!dJys( zaW=%wm(DLsNYMJuTrk3*`6v(xGgv%*`Z}wg{REoKcPD6q?nO%qn;RRr*P+K9UDMqZ z{t}>VVVVYA4b5UfWcyc$aO^qa*kf@YSwAwr#p8=SF_h9nt~*&angA4==9sXv+R!YW zLU*kr=S*ZmeLmDpps)mn1U6>@sykDOc*J6|3G^oikg1aO@S$Cr06;$u00g<&gMdzO zpgf}6Rxef4(_#`c>*l47b2e>Fp<=aRJuPN2o1$D4g@PKlrV_!lw8m$6fZFV!!$`?nkx6`XDvY@@u zsafE)Jj?ywnzrP$_x#5+?ZMcvjWn#UU`J(7r(?9nckrF~xvRx-^5#{7I7(d~1asO# zF81%3Yp}b*(ol74Xei4icL6d#0R*d5cM;#Np9Y)A7|fi{7_954?;|b|(_qZ~g!CT* zQsxF#4vlO8eF~sS#fC(L_ES~rKm~usW_5C5-RZ1E&(P-0b0|g`my1ybfh3KOrce-M zz%cw33YuQsD|!>#q;hmxZqh_GXC6w1a6oN|r^KVl+Y=7S>_4GJ0$HzSIV(8!!z z*kq=|Rig0ZZ1A`8h*eo@FJ8nPTWHMG)qaU0-$y7SebtoNfTb50Kyd6S!$>(AdlBJ5 z#e5BMuU2%Rm>(T2fKna#PY-nx3=jEDWhM-=YaDxKI`%Zf=;Cc}s+)pDTd8{-N;A!M z$Jc#9PP1+1x|xD>937`)iQZ4G}P%7!5eN>wUt@Un%jVaO~)R6RnXO8d9sBH|NAcp(ag#fQehQm+4<;R7KnxQhnD zXE2h=7416PiiwF7{(BP*u8^o4O>wSWr*BQ zD>DoU_0qZL6Cu(C8*sg}^l z&_C=cTa88R7s%F=LZj2<2>%H$7$Hw*Cx_r1>&_`?AEw@&1^j8>ITg>sX4tIccuK9a zMx8gu2`4T6jRZF4>`4Q|rW`NC-@2yU~!X}~U4*;J+ zMWQ0EDR8Bi(4ZYx83}|MNy7hYXhA8b6961Bvi#W8Ew2MF@-=7`A1tw92`&cJEkrRy zEQO!IUFsGh8Qw_`mRaN>PDvxa(h<^w{ z%GhjVEJev4b<1JAT}MON$9w=#w~&$NjXM0~M}4e>M;%YR-M|ZL#v98+5T;;t3(>!1 zGWFKj;-?5FLigZpkhXg$iCsEPwMI7e_w8n*Z-=RAzp=7y z6fH-2S4aJ97rkEA$K)jD#^MBAG1adYxX+7|1Ilz3qM?pCa4fd35yX~Wm4r!f+ZbaK zTuUshMwgO*I{F0@@Ntqm55R`ZaxhfXE@J{NTMf-^6DHtXW}@iTs}i$t9yB(Zh3k<6 z+1Wpl^x>O8MdV8-x2^KCDs&i$n||v&N)WVzfPUObxuuR)(pnq9n5}yD%Xn~SIlo@C z8b#>YyAZ=&`N!%-GaxRE)vnsr5AX^Bv@LDjv5Kn17Vt0ni2Cg9Oz?v@URPAs{UvQ^NWZ99li2S zt%7|98>Ykuw}5Dz7Db*x^a0c4;OGR46Fb1#ewb)8->So_C*9BHoI-424{B;gJe|ED z?VN2!MZ6wc$jNdctiT6LTS3Mg6Udm4tsLNtZH|UG+M$-^p%Uza+y_boMh$FeKZd!%Ba18hjG|eh^3HK4rs@M4#vcsWYN(-=S2Y1|f zAdZwv2oO$+Fwye>W)CTE2aT+q zl(K_HLo|gl9+~aIJ_JGWyvBgsnHV{ah8DEV7>1Z-ND1V!^?49VFQV*f5shR0lmU}K zRyWEskTr(pP6Jt92m1^Rimtp@Eg?HrP$@+Tyfpno{rJx0s4h+N^D_`S34SiPoSy-X za>f!bPl2LzIWN;WoHVY_!GCd?F$wJ>Hx0Qni(E4t4UeI5m9%{uspw>F?-K`is`Inp zk?^*Z4dEIof1^geFnYbU2DVb{9B8+5zmAZJdv=Vc9k#wdp<2)dP99a_6!oVxhdB0F zO`0pRsP|6zc`UNQ*1M^}KP7Yt)GCXPN7zLjsgE^mp7F-gcVc9_& zULm}QE%2U#8ujCe`IKruLZX%;`LVrYAsb7<@*5Jv#;yd7Y5C%3kAsgPJ=qgjXZzXW zFLcCxbO(jsluc3VKKwJ&Sz< zkl;cFFd}gPPAE><2yS&WoJRlb+<;({*ZHp^p75%IUj7`S^`b_UqZScQLUlW>R3C>s za8NI5Kr|wtkAI+4!*S`f{FN19_oX$rvzso!@RcV14KFkGn<*QcfG8zRf8QvNqLM`v zSD%$qioK`BOe&}PxZ*v{OI53nYcEB;9jifu`r3|-c&r@;e=LaFi2p*&~>%$L7@wx4FBc;T5U<$x7+ z!u70S6#zpPHX3FW_>jRXC(VekQ3RL{!jPPyk?&F$4VcIU`+C@D(OJ*Wken% zwBQ9L@OYpkJ+JSkCL^vB3Nc4h`dQHFG6})u$Pi%nSMX?UX(j!OJq%KXy7lboz*y~a zpA*aAATQ1;Y;Lm8ZQPn-Ls>P&xpPIEr=%P0T*GjTi7N0#!j$G~tiHrHmV<`L2pCO{ zQCZ1F?1#trBG$s51&%~|F&q8xGkPK7B*-p}3=+lJB$R3J!dQf8Z=Hk*r0vcZU}a1S zw<3D!-{*kWBLp8w7dnAg-8yi-q;nq5h`a(3c^VjnJR#RoKU;-fsj9+OM~h^`Vms!* zdt{pcM&HR@u!=-DV!02kohCP@$mN&xny5z?GL&))0uzLcHqRA!DQqmiK`kP9oRE(A zF4ebD0dNa@r!r7eT=AKsArr*H@nCn0qXD-92x<W1p`0)x-x*=4T95Y*laP`|6&wFmOI3Mgg?jkRrZu$Jz}4R+w8s!YcQvJxHLwD%VbTzg>;sSt zBrQ?T!#_=p!do7WX_l$R$pFfXgD~FSCZVy+%6AweWp?B;b`~8Cv?SBZY_d0QovXtM z@6yJf7M@YhQ4ySMw27d@Nf33X*3GxpX%DrPS?l3$of7IP`= zL`dg-u4f-dlc8$e4JSl$yy@Y*habh4|9Q+9#>)=dDbw!q}!7aKprPym1|A&~h ze5W*WOQuGC#tSr1Ly6A+X^97n60s}3oTgYe_R6^DFV-7B18rzeJY-p>)V8}z=#Wb7 zLiIe~RxZxn1&e56N85qD-H$Nni8J7Z*dgm#8z&pP&&mDhvmiH*p-t<3M*+;=uxUM4 z+mTe;F_U5Fb+C)r9>dhbrkR0(AxI1}Lz!JYQunE)@J!tWv*dY^?0;f0HueJQ%zP-_ zo2CS?w|0cca{D*rUYJIn+Vb1_GGvr%tQZbU)mH4t82!yx zI}+AQML?!XyTQ*kg3q{&BG#G!cXz>qYP0-oEh_S{mrzgD`O{Tnn`!w?j$&DGQ~)i% z!iE#~FMz=hjhRi2!IJSZ7XulUa6*ua!E|w{DsUG8Kbp}B@e6Txa<;OlH%Uvi91fr| zyvG;WB%FQt0bxc&9}l8yql;^8QWot3pg(R%BuSQZI5^ezGRQ8WOlv5FGTff*2tPZ< zE5Qz=p<>|l08|Vc?t18ecd7R*Ta7kQPrQr-=%3i%qH;kh8eDJe!(ftU{Nr`3SxwTo zi1i=)Xbn7_k6^t(j^-rAifG5=l(+GHNO^47$ax$PBUbxb)hpF;#2o&Elo=ffNijmk z@c?mXKz~2Lwqmav*8)_*{9E65Iu{3*&T`0QYBN9((_F5xE##ba8(`-1rKM(=!~l|k*(^c9sol`rgDUF6vnDX zwI7Fa*#Dx1BGlSTl7sDUAJ}`-e4z}sn23deQ#@YE=d^&}GsLSjD!^WALsr(%p9yaE z+7M-?hUMpTl$7j?#b}UZvA6z-P_? zKA(Ne(XMWVTL2+#3t&2eYp>)imh94S?4JBPuz}emji17V=W1$yX726HdQbweH+(MK zm)2dYPM=fh4?g>AtYr>h%E1bXcK7G9cc`lA6QwHFijXp0^Qk$31mF_}U>h#$!2H}N zjfOI=!~ON?M4n0PamtgU!N>IBu{calKu-1(L>k9P*f@ebq7PUEfe=kTgN_7U=;PQ7 zl2-68PBtu?U565kV_qk)f>qo2-ZVdMkV1#MK2cBQ;|Qh=CVSc%!O33Ha)$){9P`iz z0APPZuFyn&@=1F=F^J$_wF!C!P#r^zjkN|5iXx1;N6+rygNuWc)3trwaI697$bgvc z!6pp0sMmbWJwz5nu(O_zlOGOC%h;nsTB>4S+${+Gv1!TJ4-m_XTR=SMXX#k=Dma%0 zKk*kH1xd?*W|S_nfqe_I94vbSrh*sXY|HX_(nKU_f5Gk^T**f&ORX>9^eUMJ)cJ5S z?^7}{51=seOFv>p7!Vk*FVbNrX$rd$!w{AMoRGD%Nj&UvcS%FhS~k8K6u>yc&f{B4 z5X5XilTg6XP)DWXQ1MJ$m4g$*^K3C%~QnSV9Uw1V94RV}R+mu1m*q7=g`NYQ%agBuBr<0F(O$O9?-u#B7oh z8C*(W|1T*h$YIM66yGC7qWy_nir|noq)3fYx~cEK5F@?NTN0kA|AHWz_}_?;|3Iq- zMw^qp(Vsb{B8mML@82UvezYHAs;|q@*TH3d zMH=FK>^|6#iO=aYpre840xoqlJc;#?( zp@V@?3#S6e7x%f1HaA~|teL9uX2@urnubMH)4T#J zR&O}E5H>RZs6Vq7tiMQOW&M1dSaQGbXh=mNQ12Y!Z(#Dnkvp-dsk9)^++lmt081R?_>c!lsifvT0E7(75v@gL`O#R1QkprL zCjEt(Q&flL-JV(2av`fESdy-wf^XAL@6s9%n?lws@`VJ-r7 zm>}M&ru6{Taxn`oh#BJkHp@^ot*Jt9oR^xSO>$RvVWCY4&!L}mYu zC%BA9vRY1S9@WuPdLx=NX-?z98&hB`*qGilLUlAQ%$zib>;=iUtLEgN)`p)y{WKgS zG5Oip8+`5O#4;woy6Xg^2@xLSU2v`&xVeW8`Zh~bllPR2rhOi{qLVxzp|H^Y)3DbN zg<~TSu8y#Z?gxEhvhh?$!4TDoBQX}ZJajAbMiyvo;E5r)yXn7W3i6GBlO1$0`2yJD zk7%%bVW>E)Mj1l4bTpgM^ReBCr7eV(KA4Wi(~UWDaRv;XWQcNxGWh9FVxk7h?RDa? zA?Fe^UAT4`Zx7;|Dtu;x&CM-oYsRpV39w5i`>T8wLG7g43Nf7&(dQtpA*Izc z$3dL2l-o^W+dh)XZm)A}vj?;3d&onzy~2wjVXEz|Wbdt@368wjFenSKmQ85zmF(wO zWO6OALmS0557hmbQ4Sp}OD+KI#09X1bRwx0&8uXiR-)McwJo?eo6YF2mwj>qMU(!b zdYl96gDgz?bUNZ5I#P)HfrcQ1u|oJQ;Bh}tIhU9tu~b?!44Y<<`!?2nJ$0{Li(=py z+XfSf)o|95r0Z*dU7N{TkUzOr_+4n^Vwy)6=Gn;y7pIc%hanoixA2Y}S%0w(xz}XM zC97Z-#qqOPW({;^^@4oSy5`37f0RG9i1z#wjcIb!B*#or4^Dlz+bk{gaN_Zn{AWu` z%q*s!dkF<+7;s+@94f#LU}>Ipz<2}u4;Tc8B58Yo%r+a@J+Fc=q|b9gIM@RIPCET^ z$SIv48A;q?AkD7~pzm$h!mx3x@EW<|O0G)wGIpM-6zpF~BO+x`!g1x0lDb&Ig$QL< z_{iQ$UaT{fr8!tfKqoN|BLTR~b9cfZWN6uRWzyBOoFNMm$`waL-@!4E`Wn0bB@nF1 zq3aLHJ)sJe?3sn5gQ@bv$dsqwX5BDE9oA^pP2@0V$5f9C*UtVup$EgnliI4M8YHOi zti$XyXk#VeT3FZ&4GDATbWlG!4mPw*$7?99C2p-!!dsC8djyZUkVnr8Pg)Jg z2%RbcZ5#1Wc5}Mz=JednDY=^tq$s-&<2M$=;uUq^q?-5xnOVeXxY0$NR9;Re!z_;Q zTS%581aFHS>gHbM0O8{9 zb3|74gIdq?6Ev~A5To+G|50;>MpK#gij&fXb)|h#G(Y|UL}p3lZeEa zF}f@EGLj7HIAhQChh4EJ5N@)}m?n*{d&D$V%E45V$O{T3@~#HVj6x1^lL7HOky+o2 zuHnoOn@G>eG6zM5B8m_1321mnH^jz#{7>}p2oA}`h-nWr3jWC~M z&mpJ~K1iW(b5of3t_qipM2;g6;rzyO;M>q-nPXJj05xhCA})jIxdc)k#3G1TCBDM( z_#UVaj)uh;;{3SdtLS)fp3G*6POwfM{%qytj_^xZDAXNtMZ=A#3^@dY?_+-CJI}{? z0dRJNpGDFjia(Cmfn+ITAW7w%4LgODvY%*${x<-f)b;@eqXS%yhCZwYU{D&eqXV~N z7^k{aezq&hr3fJuI|dk;fqE06Xan!f`Pgrx))D?15>;O6_f#YnIQGu%^>N?$h;cC^ z&Sjxuc-`HDLg_fSI3dc#7FDHY!LG+jI)fAj@<0X4rbN%69BsKArtxjX zwTyVEt9w}hmLF2ee~8tiQG!df*QjBVabyIv89^m=fJU*Iv_3T`&LxV+s134BPQCrLo1TM=J;g?+U3oDfEL@g!!9Da+r_^7qx4o|$nJ|Jiz3AbH(4$^5NY2&p{CZM;bVy0xtG527aYp^h5%-s;ce)jr{v?0TV1-0|46w0NmF}!xH_8 z)8C8pWpHR=@Jdr>}@UyU3I-ZAMP)Zzc z%om9bX>9~(Ns*SPF-M*p02&iMxq0M9Sb)|#&z~M~>ikCoEliB5Z9w^=dRj6U zev3UgFN~47R6cLqeR3IJsI5byQtB0aN{vY8aH}XMb?AL&ou=?he{ z&wqfy)l#5rH&_Fg<6S7;lxpD=ZOojn9f)|(<+qh3@B$TZIu%9Ya$5X~KLm57sqfYm z7l;9!O8}MswwVe%+O4k5A36=#1Z;#3a}6U z9RSbsxGI$^7EP8$t_I-j%Lp|>`hqcLn~ulUfK1<`I2(ex-yx^$MRLg5_Qrj1A6n@V zzQo_W8jtW4{&wOohQHB4kFjw==3YPhcoA9!oOT&Uw(1#XUkaS6*ixM_5@ zBNMr4kjLQ+ypX;NwzvD31-Ysy!&q*;Ox!PNEQ;|h0BfD=n|=oZMoaOFt!P$qDgHaW z$XFczGoAyMQ`#H2Y$>iLz*hHzu@MOVpO@m5tcEx6`xe?gB)n+5g%;W)2TC4qRQ7!f zZ5c_%Li<0cSYtsY5q4F>Z*y37!9i92HZU0dbEC9#e$nKTo$`87&P(B?J-4casy z9lKq?=#zugeq1KBE{i=f06HE)7$lZ~b^m|4Kz0geiT(>@u@hFK@{26FK=#^B#LE+Q zlLfe_UgZ}ykuyxMno0*-d}>Jn1_xbr>8r$9Byt676=#LaxB(v9UUW917ZC+G+3tgZ zbsE876kUs(;ot!HAP7zNhz;5Njwalvw+A)?A|nm2o?@I5gtt;Jd*;_DO4HzBp%&3C zQTR>)F%zw!w}XH+a=b(|&GoZlkgzHumL>0Q|Ew}(of}|tfe9@3I59={Pl0Rs9bzku zva}*UGa(<{>QNQhU=k|a0SBL_@(o7`%ROx;9R$VqSN939sC zJW?kSW&#ePMN{ayE1GxUSAdhytvbK=ik;$6gaW?_3Fj7#iwk1td7R>h|5Y~$oh~fb zzb329($<>dOc88`i$-ixJn`(R%x{YFF0rs( z`;6OJNbq4Nsl#VTKGC;>JNxySr1YLTVnGuO?YQhKx5rb8EfQSJupgiy6AoSMqCB`@ zi%vw-mvO2f8_Q7@D3P$XWB!D`;%5R};9F=Y7o2n?2lgD8Ds5)S z$Bz)-FCTx77a8(#J)Q&dk&wJhKK>{H=IaMz=MMbOO|I#?fy zNmTqjhR3z2&ya`DQZWNIHojdbj>lfx80`G9*iLT6I*-LFxIjrI>sXnU%z+6n995{F z&aXANR^H&WNO`zjw#1e4i_v0s$rbd-ESX4;v=YJdv`I=~yK(dazMwd85qxi*2i`jy z&2hxN5GHxGy)J*mFm*v%KYV63d$F3j_@ADhVrV^O-tkz z#WrY^_WBD{{>H!IUYJcQN`8v(DoN?lvK2BSwM`{RGv4dz{ecpQN8_FPS6f>0i{yKl z-shJ@lJAew`^*x|1O`0qr)bxg{5<*IMDOEEcAFFF$S7!;C9lvs?#f#ML~tB^1rGe5 ztWq|ufWI3WxPV@kF25UcgxE2805XMr4F?B^8oG+h5H&d@YDkvPFa*tF3@-?pR8vzb zjJaQMDf21L5|R6&QnG}kj4r-ylu)S^`q|aUP)7o0F$ow`CHp;{JmTh4@m4=X;WIdb zjRA{cH5bbZ%Q-sadqn3bu9T)Z^FvTIxtvH&}8m4(fI zB~AT1uDFcSz6z%!6ykk$RuZ%rPDgiiXgq}uc3t-=@us5aZUV9_HN3#f*4LKXmh&S;Qjk5Z%`6bbD1$SWiAc0$>D?&K0wJfH`Y#Q$W8d5#C>}>gZZX;) zgpO&r;yYn>_g6NK%gQI0y*LK_4!SH(DO!b|#?+dIwoT8GEVx`wUDQjvU6qxQ+HRHs ziAKuGVS5Q`y>;ymX!GoXzIL`6Z~5FDu{yA&Jq_1I(Kb<66@1XHNo2S51^iUNQBuZv z0p&aCA~}U$Du-PYath{?biz}{j&nuE)OEVB$NjN!zhg~tVPfhkNK9P?QWw5+(~Ac9 z{r>z`|B1NASLyd-r_fLv+QjKT763Y2XJ`|z^<(EHj%~_rK#|r!PQATs+p`2A_2TP0 ze98lN(uavCoX{OGmF`=vV?97Wf$u$M!*9s&?+X$X{ropjbo!^$$u|$=m2u9rm4P?r zf984ZHHZ{k<|qygl!ik&4>OQ499`zoh4Kp0S5!03G58AxC6GkBK2Q=;*tM!QYtdGq# zc-ImB7&fSVLLKH=uTvU+-s=?b(I7g*b5^w0Rp@otp_SV$`K|krxtWZtb>f_IadNrn zVjp7*M9Gmeb=HEAv6HqEA+;^`F#wf{Zfz`ZgP@^e1r*z9-0$PTEdq=1;jyfcvnszu zycvJj;%^-OoHFxB&lfN1=EJvB8xPkh3kuV+5inE0jsUd;WmMx(h4WPu3>UEdf|XVi z0+QShP?UfcD8OH4P?ZQ76*oMM{sf(s?fAr;@o30COK zSFj%f3)v+oc5L<4@8@0p8!VQ6(?bYZcJvm+PsemCRI>a_2we#Tn3FX>Eh>=g`L_8fls zol!A38Uc~^RgcqFS^u@jQ;VJ-dLean|oU7 z91Smkdq5zwxElV4DF2sVpCwUe9+G7x9htoRiYgV)jUGMK1P2Ob`HI6K1I@d_En1;dpsC{gejhi55R zCq9HN!SKTzhT-FfTOL3V{j?4ade(LMxHH2Mz8g`FgWkSE9VXoIc)^CpTs+7#vJWbz zIW`<`SeW6)eAZJy#BmNeBp$=xlYs zvlxPtj3fLqFvIb~uU>mYkQP&`xkDcvaRP$xAQ7OBE%$@*fu!TH00N2HHzaF!G|*84 z1A}{w$SV&4gD~luu{2Z%M}sl{AG&>@iaqn62@!&OzGKVKuo7ydG&T@2 z17-pCzY{ng!W7KOKa;ofW+O%WCCEaUhb(u)^(czZ*Ol`4r(WNQ&Fs$&|+eXu<^ss2(q927Wy#Gqf9nK zX&02xw#J3=tPRAF|5Qd~=Sg<~@LxVSbK*UovfCT&JXlLw_o zd<#cP2K%KG590oaC2{Ice1f1o>BN!^27w1Jim}j~=>iV82LT_XD6Z`gCl}YYi=47( ziP2RF;-bf_b-cw_&PI!kiJu=;HGK5BpNgGbK}>r%C$Z8b=M>V&@Jb4~jlPqVjSmjh zkVaeMHsjbJZUj1H);>d|V{b-&OXAu>es>}L7z@@4TjI846WuF{(q_%DwA4@Mmn46M z@9h}ZB$wwno;ai)x~z!)1#kHb3ygBJvMT+Ky$_`po(y0^oxZ^_7AFvJh{t_lO*(GD zv-}a~i!)}+&69Be5trw1Z{2=mlK6!Bg5~Hx<8H+rpr_!IJLwCSTv5Bx8^?u;{kJFL zW<`*mfPxTB0=t$|2pcitLTKaHQ5?2TDaFTA=%$fdR8L+Dn{XcU1^g;|(aE^UXy6V; zegz{w(u3=h3s2V571H>$B3e$jCnvz^(C@c1P&=Sd0?$Px*Mn?}2Xml}&AUSos?k#1 z>-gRK`fh?VPnKHVTX=*m{yD#|&#C$*->LfY?qpeLlziCso$LBg19CYR`9P>HRFb%V z((r*fOdq_o8aGPX%UO`LxPSY4FE7ftT> zH%-7uRNuO7dJazZ;zENS`KYeqTUq7qL$xN4;?03BTwI+e4MBI)g|$}2o2M3$;gWpe zC&MTym?!gNlSkvkEc{0Pr^Ob+xBo?H7r!ZZC{u*bJP!tTMXK_!`ygq6v?tGP=0=@tp?Zxq~xuw@9@Xhq5-!HZDix$WJ5W-7V`!vQ2alv==9u zg3&bkd=NH-wJ|>SAHVoE@`jlYfVW~*hAO%^{swv&FB2;(i>qCdwX#x6#jR7^<3An% zVe|BCTJxa=0XF}ixboJ`ya+%lS4CEK5ZCi>FmHUEc5)JHN|b9Odw=fFFz}?w7|K*q zqFf@HA?$qYubAiL!+Dn(;uED@_Sq*|U2`tT9n1x}16<%DF393s;2hwBT;c+-0A!xF zdDDz~y$ci7`l*Baeg=*Ue!K4<#5ldY@9Eky@l_n~@P+U>Rt8UT%<)7YY6)=wY62OD z(J3OtVj^5&P_2^XJeefcz}J@U`04i$>nl(YWa7k1oZCv0Nh9s&aPIe!iHyT!H@p`b zA1-8MH&7|CU|!9ib~b@Ooop0;W-$kU=CCw+PGbUpb+I@w(%0p&F8-X%7=KP-?fhB5 zPV?tfcAP(R*%AJn&YJmi2HS_HeAuI}^RVCWs8aSkf0ncD{5g+3$)C74fIk!_ zor3?tgUuA&$%BU}_!JKwp-lkIR$eOT{MHo;8qBVxx6Ar!x!isY*M&WvJ&~qjFO!0 zl$=D&R3j$Kosye~nP|l1xKmt-7^e}F>rTl_#Pl_BtX=qwXdWG(HVA1DEZ6?P~Yu?%~ zar*GEEBPHK?5X$zWYsm!%#L6uvCCsD6V@SwWkMkq-LOwBzZpbS^kQnFXFX=>T{tQ?xmsnp6+v%$<9%IXr9 zl%|;E{(rywoC6m`vwH9M`~3g^cVOLp&K}oVd+mAewNKi2xb42U3z8?SeoN5BcSAJa zgFpm2c5#4LBIhzlCi;kU+LmqpAuFUcd zDl;uwjp%XjCgRF&VeDjY6hFrPy~+NaDd@_i1Y51*Mi%U#+>6EqyTPzy9sAa?bd-JD zx%JZjq0)a?uxR-P9qq-Q**JXa;js@phdp60{foo{7O@;=K0cQ>#*YP%1ZaB*OA)o9 zGj;J`wV|uUlBR-w8F3Q<%VrDxGt6`JYC^yx#q{d$BhVL!#!LV zSGXdM?~&#wfc=1X0B->{0bT&C131E#oh}T!|1?Y|Oef4UFwej&g;@&oJk0Yj%V3tl zEQeWM{~pd;V#w|Fh`XVHXw* zA#t1PhqxDvsRZoYT@-Sq;_df}w{rbWVRU2lr$efW(+6cpRh&N;MWD4~%?Y)M)7&xD za{dYI0DIykRFjrD=;_|fcbYqwDcS(M0eH8CI!C?; zlAti{2zRq`otWK$w~68!{*;WCvnMzXYxhDGWnreRB-Vj@a7|bkb$VG_55cW2j#Zq& zz8Tr$?26Zt*WV^iYxq-g^V=kJ4S!1NzD-is@CQ?XtlF{Cv{;Q3PC}>s{F7Ly{|vT$ z!%y03LoZbq%tH5t+7fgmj=Y6Nks61~?U%iAzuV<{xZmxvr|lNUh`S1-KPeo17wl~V z9V3zoqYv&KoWve3Z8|&Z2ZEirA<9v|Ctf_%XW!^!^P4%MkAb0%_z8t!4ZUUfv68Qx zrsuIt;^jKe#W-5Y*-3G7^vQ8J{x;Fu0i|-dSqd82&`Wz0SnXDBRndYboO5+Q*c`$4xS%6BLtf(!cf8;(Rgc|4yR%I(Tzwp}6$oQB*mg4%Yr}S+ zvb|lmwRYPn-D8S+zNSkpmF!_4>lmOEM}A)Dg>6n)%3Q0E3HRofLJWU7Tpg3<32j+V zV9gB5RiOS=lX`|%p0V4hR+=B~zQ$=NZVXEEnYMv)y81Dcsh?4%RAItI5+|x$_0iTL zl{hc=7Ci2D9)wSgft+*#(rV@sdV16zFQ~7Pa%&cPQCjka_wgOO5$v*K_IJjm0`@ch zl_#lC+~P2?35~B9T_YJ2w&(FcqJ2OZvIB#Dr)~bUbr2g|@Nx>(rPAHa&c0*7KIG4| zm2gr!!c6(<$bBy|3fecPEvCa-Mj}7ww^e-)srVkNzK0p#Ye(S?m5T2)ixwlotc`)) z8vfuMv$oqEiy?#i)~8=urb#?rkJg9G<~Tvo*wuE|3_yVEyTga)fqJxF|bJ zZ{Q!A9!@Gp3PQz>R_lU_p*_b4RaBWwe#Gc+df`o1Wy0GiI7h{E3|~1u!Mf3S>FofCcCKI#FsJZebMK%vNf9bDK|z(mkMJ(hQgT9N?{Bn zb>eQ<&hMuy4P@rx4V~Ywv<;yth3+K>(OWdIa>w<3yKp0r%?~}|pEYC}=*V<{rj?R5 zj-La5F>Uqn((lm5Mh&kKR*#{!67JQbE(falE|?2>MJ5L#c8YRVPu+xa)y&!XLwO?{y0F@#hw#I9CZ{Wn;$|$U_eK_kOs9yiR^e`k?9T;Uj zqqc6=!*q;uRUQh~MEx#W>OJvxdLg4wrDET3NgxWSTLktipi(og6!D|LLjjjx;dJwV60`hRtMUZ4QM(G zdVY(hU|S#c8;IY&SfS)Z>PuKuhyJlv&Sx4%`J%&;nl$FOR+U zIXE-XWJyfV#iP$Jj{entS0Aj6@@PQGP}AExabu&OA_R*VMNBi`1CMCz=&}UuGu^u$ z5yNjm80@j_Y&v`*W7U%3KRj{NMk+)~ZowWk%@cNrxcH$`3l65!Y86GFN99;l#E4>X zZh$<|Lu)g>+HS-F2!NybirN_LjX59VC?HV|0oG~CHOcY1@a9lSJBlbR9y<#QC_8;O zlTD_j7d(LHHqtLl`COl^h?A@7m67fVKVQE}#4oFWjKs~fbR#}w0pph{_F_9?>W>wz z{_eKcrma1oV&)1sy^~r86f*9Gn@L|`5mVMZj+DyI`Qq(ha!Qcmq^Tg1>8MEEbv&)N zK?Oiep>lWTRq@#olmtG+5F|!*cN`Q%^^O!Z1^x;>-M^SqyiI&`-%LtT&_0yq1576{<3VNQ`H?vsdosA+2> zkK-O6Y53cLe{;9Z%+<8|<5LR#9EvQDJ#L#Bh4!0L=YC(i zK!ujQqsN6YW2TM9YFklJX$cBsQPB`Y8?aNI%ZzdCj2WYA`6xeWK{qVuxGDc(y%ecj z1sQu{it>9ga7|fj_3_wDk3q+CKPbWCM1Mr1i8gE|I255;7Hj2JWpq8Tqa+x(FeH`C z$jz*dWY0cE!N-_N@zlPa(u){bCaT77S8a%}rQ5eDKh`c#jL}yWK`01{UC!2nyeu)Riy#Q=+y%38(>m7!s%%={qI-L+!kcp-UT@@3 z&x+QlZCp34>nmV!&WtjoZ5-+esf;;NORT0tJuksY+r<6_qa{sF(i97Oou)?43(H(- zSyPpko1C9lI6LpgYst}T>Im`jq>hk};+!9vU1;!v29WM?&KTNZ6zhM=!ZQW+bkV|2 zeB4fR8oPfnQf#JHcyMtN?pVC5BH5Y<`xLGkVL}n6`bDu9LVYaQ7U`&s(J!{c<34B` zX3~7zyh;XQKQ(tQF9^g)W{HrvH}C`JL)##u*l#>g+8Wq{J7Hhd2OEQ(xv-_z+)tqd z!v;-i<%PA4dEpySF!2KF^{NUcHqb^LX0A!W#5(25bAh;~7eCXm*iu;VIKI)<3~-La zr`~HS#~MVQe$WmICU_>+P%x3`qF~}Ewt@f06ii^-Z-s&hb&kJq^AQrD>wDlC$VxR6 zuhdmXdUwFmP%=>nD;FgbTk=+87^f?la1^}-pVN2LF>T5B-U0hG@10K1NtzB0G%)#R zG3HIHJh^~5K2vtw?4A`So2Q*e^ ziQj{39i^$_->i57!g7x+i$R6(J1W6LAQq9kKq8>Ylia z&b2yyeI4Bs@4=7KJ;A=Ip?l(0;7Z*S+#s#%G`L#H#dUN~+}R3|8oDP~qmlMM);%$o z$yL!k(O=U&(d&kEPxK@yTGkhL#CsLx6Hh>0`M6@N={P@6XNZK(W%@(Bsz?PX9t z@hT9d@`*WAKG8`jpZErDx&i@>7g`(NcfCxR4G<6la4u%@^Ppm{%{M$57ti!pZ3e6L&=`p`ip?QKS-MHonHj)@h zvXoq{d4f?D{VB~8D!S`wo-jNt=bR_hSU@$!H8fAKBGDB76c(}J*0oMpb*&TQ(FCcM z;%(%JmI-?c=&u9hNEaGctrNZAe~I#NZLJdx;m6QA(UkH3HLVl3K*My;XVlix$;)%Rw$Vb-fR6IdjDxRR}*ye(1rQ(Sk9DuNIV_a7& zo?w8giYIU+4C^2@DV|V7U8Q*98*Her!Zo{6yP*_Mutsu@$Hf@-^?b!#XLZFBCau8s zxB#USNnoe0dITc{rGuolsh|k>)X>GQri$Xt6pjzEBHiyfi@0NhMWh1W1vGrtB3c5b z03L!{)dgQ_`t}UK?eiB8w%zA=r=2LpFneEiUB}LG58|YZr~mFQ0*ej>qNG?G&ct%L z1uFyCQi+M9c$}aschbYh#LJ_>d0b$nhDg>}iI=yD9ec`%KNEx4U@ zudR_b)Yfum3oImz4@fH}UntWdOx4goivj<*F4ylt0Mg7%D1zbI% zshWi9xnbQs?Wdq>GRArDO)kSoDw4!rM}0KRN$k&AS5mS5vBJ?OOPV>mR;JKfOH@PI zSf%sElD&S>LIP(7jFn-feE7*06^Dr%_HL%SX=U%+KYL?!L zZ=5*LHA_Q>#_lB+fB)S6Q19ymL1Uc%)B>Zhk8v(>iD*H!h%&Ab5tgT)R1rnHL=@r@ zQLkzdwYw^!3l`5j>qO)cW_{CY#qbcN^PDz;&&J_3lyFfp5&Dznmo5l|lIuA)Ik0Fj z;5?KcH_#PcHvkIQ+9~-yQQ%?%BgetMEP5MsswfgqC zmG@zLV_&$ou!YrJEC8z#TI%eIwJc~i={vTu?N-f`muX7_EPuJ)myL=1k`G9?X^U5k z^BwS0sq~yrwJ3{Uz^DC^+k$qO{hep-@iCTpOb_iE34X}y%+3&Z!V+x z2B{#~=020$a1bMp;gOgrA9WcHJe1iJvwknW6YtLN=TT}qY3^u+H9aU?t_gxO_tEoc z43@*8O}{kFt!iqff`0H+@`kFwc=`vcpX!Pp>Rmu#trTY1bKkfB6f{3uu$d#e)KRz( zi9*XuNIQ{-ag?jd6@8~SWAs+{q>aNGUDfJ!{}>*hsJFw`5t~}D*~j0f$Hy0cb{xT* zH_TGU?u$vV-{;sv)8kOdV7yO&4b`^7&!OT&Ump75(2;uY+0I`)=O~3QDBOgL@5S#t z4rMn8g1_0`*`^@)omFRe032=^<&TRM@#c*;pNmJ)?>Z_R?>i1VzF<0&cKK@hh;Xe9 zREOE;;DCE`GS1lv-N|v|Fvf&V6Wr)k3#WsyLB&hw&UNOoLXCN>UJx78R!(Ha;GT4> zeMuafcgIu~?#AU@mTy`x>=(d(oSMu!Skq+I91fcDZ^A``@1ku{i@|7ape>avuk(G1 ziZ)$lZ}=1bt~$-%f)~_pnfg7Ve$T7lW9oOK`aOtW=g>s_Ja#w3JdSTQnY9$3`ear& zyyk7&0T-n$^)0*@lUYC3#oEV(pexn`rmaoU7l%{f<}>Q|9re3`zYm?nZ%WW-ru=pA zkNr9xmkPJ7h8^_n;n%cu4y-ZN1f4O|Xu5Tmsp@3YX2zvWHU+v)Hqn}sO(V$Cvf8Hm z>LVWPimUgoHq}IOLDNbYg#{YD8Xq(cXq+Jjicexhh;*stv~sEmyNR@^rY&%-vzgwD zx8l`a#8=Pa=PTabil4;$LS>KQAc~hWg!(Klz-x*fQ$hg_sFe0JGKYv@3|g2{5eZbB z(z19IY@l`wubda!s;f9vPJQWlJ;@TqU5t3!Rf(65jJJV`S8<@&UB$?E*BJR-{JpnE zcv+-1)?PNvYO$9=&8fW%YEJjVNh687Zi=_zC&eC|ZfodqNw-EDTl_SvHHP>WKU(o_ zE?$Or)7IMdvfj34DfV3Vp0=AXSkeQ6N5wPfxvYogdb{Sjz6?0YT;MfAx$4SIG3eLk zm^kLo@2Q+H%M_qqFwN9PyvqWCyIFBXtmZIbCdSZa}&i?`vu(#=*|w|8t)Dd8|l zt?gtIWa)y6!K{gtV|;nxDkf^mzl6F1yEN+QlPt8fuO}wLv6&y3iCoqY^ia(PuBpVE zR((KeGxRlk{l*Fp4YylFgj59d-NwN44i+Cn#A-t71n{RK)Q5<-v$iS!JlYIc6ubc+ zrmYn89v31E{5Bs%a6|Cd;oUlDalt;AMFpGii?uBpP)mDJv6pboRykXhOyp+<+w`u zDE^tVP3wuUDE=PrEe6c&p}4$EL3_?Syw_YJ@umUwa{a) zs?;df#TS_~s=|RrRK|~*P?sW+M=T$KH;?0v&@x9{dGV+Cu-$}OX{s$=lS)QXGBju( z^n)uYb?jSsX)Wv)+)?zhrp#2WL#dh^%1k#P1@IM9N|k)aVKgW+rI0e9!$VhQx*IVr zhovJF%1j@`i=OFnGfR@1QeqfQJTT;>s1>OY@vh2DSFx~AndvtmM=3L9D5cDF6JBDl zt?!Si|WnHGq93kvolLg*RCuYE@>zCXen zw0`5aI3AvKxkM;a0lzEDwzY*8uSMezm70bsrKX|fkCZgk-N0Hyv8ihMb!%%)(@X}% zdXmeLQ@VCjyQ*LWr^YPK zYW36}5m?e+Reai{dZl}10WYaDLQP3|dF;gW`?&xW{7{*eihbKgM2Sq;0O}p8c7;Ze z0Bqid$a$u9DQSS)YCO{dO1yCEP~$Z7xRk;oX6;_Z1#-->?FhaDRD~I^jl3yTqPW4w z=3jEF)+nW!wN`0_bBUVSU}1*NZR#{VE;lm_CT#e->J$7HDd9m)NN>*j)YKAr!>Ofi zT26b~+B;M#CC$?UwYVL-M>soIkNs==wu1;MY||a9&fo>Nv?fAJFy5+E#6}IwnmRsa zsPo-lkZTyc7ckeL2-RP1rjtgDmYj13W@9|I(ZjfcFLO7Rbj2zcK4eKdtwd`SNtKHR zU5cPB`m_>1#JnClLDo(>L07RX9{w>Q%D8ow*|%+ASSmE-i_>Eae5_Y?MjseN{Q81nq$s9W0&+4)s;NOHM4Y-++lFH(1ut-PJ1HigD)TQToKvQ*T+sQ*YoX z3ZUDY7I6>YKEQ{7ci^UN1H@1@9r&5e*6%(%Su=j5uZN2mhi_ypT zvE6ES3g}FSx^!EkxU};n-f?NamUzUaUBC^{rx1DV!WLdVc8o8%+4*G#JM8G`3FkL> zwVSzXf;$&A1fspQbJ-uv8y{4k^F29nj-8ljaQv)r&^Gk(qNfY$9+2Ml{(;gOsH0+Q z8SsJCH`3}Ic?~S=K3*7ZmNapWuEb&@UZH?U>7_ET&}O9koFN*9&h{1F;jhZPOLJ#S z-H&^PALsfRkf=|u)|+u5%o|fqA38j})zz6DITh9n!FV=`_X?{UhC!Qtxv;)ZABxB( zdE0v7%E}Q~xmOoq;=9>Z_xeJQ*TmDf+Sizz3IvaFTbs3|id)+QsVkf<3hP5fwG&Pv zYq0hDDDd5lTZ!j;Bawznk%*of7(~~kq=RAg3qbv*4IveAh=H3bc<|v^T0Q4C4wf+7 zpUFXfB5EAitzg8^bHSV8rNvYf#LBDZHmZ~48RFN0E-toncq*G(Y72d-$^K7RUx>h^ zq~q-iu=%17Fy!&eaZu%k9r?=cmaAD&3-fd(9=vxMCqWB*k2-Ta|ai9 zMj2NZR^M_T!eIyfN!0#{MLvoSOaf__S34Rm+@)yRmD6;O1sA1x%RQD_b*W1b*Hj}= z$yYnSuLYernj{>+^&PmmL(i{06dc^Qjz))E^>p38!lJ}XY?6*l1e;@dgmHI@>FkbJ z6di1YK!99qqW(H}r?a;84*dX7iYeC(5aP=pGk*g4W8qH>f9~Q>R#9Odq90;Ah|Sw~ zICf$4gw<5yfq81Ux)nwG4uQUeuT9n#j$J*z-1&pM)w{4+QKV-S)V7`UuzD?S7Ba;4 z+xW4&9Y-#HY2WP|fD3C!Iu7F)AKctRqHMqIEMXYLp;vs;;N$sP!9`b z*E3lnaJa+~j=NUX<)wbkiOLQ-SeirJZ^j&yAH8aGbC@Ya4wl^P_$Xi>PM^4sEvW|$ z*zcJh*-;cG+>FW|YBH(Ow!|MjXv|>!{VLX-JC8dg}Sm@)!iHHL@zA&tBZ5-6y>1na|6}F3GENPxG&e?VlUy4#{ zE64nicUm3ioCToGQ5(rL3AhsD+=o$@I&9*MBC2e zjx9fDU91o3Gf*$$o*Y(qEHiPqff5x|&~a;W+JHFcPtiyh+v70@H9F{oH5NxM`p$M& z`svEnkfNYk)9`Dn>+Fr}S*vXJ*ygOEPEK48W$l5kKsV=28{kG=!OqUlu#Yo0UgFm7-l&)ori0o)#U|+?4TO&B#qMWo;t=kI& z9ZKCXkbgCRiiye(pDzw9E=HV6grRH7r(gWJ!r+-7mK@~dqUQbQzm=#dFi|dv(H*V#r@C2kP^6HMR%p# z`44;{>&AgP+&g!av<&wgT-X5U_w}-!Q?*90$vzzXPxHhmjNEXZf;9>aw_)@$GNw2H zZ-~|gPRw_|c%o>qJ5+xyEkKL|;DR{r#%oNPryj>DEe=irCNfp1+Vpv?uwmg$PqL@G z%IxAV-~#2AW5zg}BqI{w`}I%*UmSf1U_f=Oh{~D*jJ=G*Q&eT1Ml+lIOs{s2MKj;F&CD(4$Z{m$x zE1`hK`RX_5FNHgm(zL?SxXe#l$MG6n7U75C=GfQveZ;{_ctd#fd%kZ#=`FvR7VkkW z=6a)Iy7w)-sjI-^pi{R=3~Dv>C&t3Sj4|@DsdFpVGW2^fU*NKaP$%7{afX1YG=WI7 zoy7r}d3AF=gU)4pI(B2pX%DIqND-`8*pW~H#7{&d7gQ{oB=;aV_;ML3J zAl*P=6j12#rMhp?IT-2M`_!`4b9Pe5VDFc(evN4(Z~(88u9qo zQW|#%oASfJNG9_lI_cb^+6N*^O-j0E_to<3aI$iR$HkFow%FKXeV|EsLMps zmHlqye-r1{$wpP?yc4gu3lARZPrw3MA(j#*?v8itQT-ZI!A^my;gJ1Q?#>@-Ta$4M z@?)?-=Ooh$FdUtm%rR#COk(GzHedv-a^qo@n*giK6bpVbV(>HTF8nOWg2PnU+P<%VY##O z#Yj-OL%V}~je4)RgZ$Bxpb&D0JIEvWT6qV#ok?hSkh|-5kOzE#OUMhPaS3^+gNntd zxJriWw>z^5z!}3Ezl6L=9M6))I!_$0tU++&4$_^7MP$E{mOP(Tj=Igqfm?B5HL=|J z$^j$YzPOFN9&aPpmal6&cDKVUgQ&cY9OG%Muc|W(xQ>AJ$M7f6!_0C^b06b;EgZ;d znn$gz;0E>o=kiq4V2CG<2l{A=4;M~iC8JL8xh|0^{T^{x3az-ax+u8xzLE7SEKU8D%`##&N-#4?}-M{O%7jL`qwx{1oTpxftDi8H|uir^) z9jsqUneBe@3&+m!>~g8|VjeMR9@CH&mT4`1vp_bf=5Z~BZ?_?WR-8h+f}`r%{Q{M% zxLkzg(rvwc`1P^X!MEqdQ&>ZdyLd`p#>JAXhqj=5%H!~OILUTPA^ZP*{$Jog85Br) z)p8Slfc5|jU?d;~Fb}X2unF)!;3S|Na1-vNX%FZPhyY9iWC4Dv>n4r?*5Q34;4Q!> zfHQzA0N>gO2j~YF1F!-X12zJ701g6<0e%2n05pI`tM-6EK!3n+z@30;fLVY%z=MEw zfHwg90Y?Bo0LlP$>$r(FfKGsZfC#`?KsI10;3>dsfR6!R1Ihq50e>?f5HJuh9B>!F z3djen2D}2;5BLqhXDMi_{_Jdt1Ngxf@y$x;GkFiY)Mi^Myqx^hBC>C-{H}1&U*4Gh z$(?*f3nHTV!f|(r5Tz*4Lt2H1Dfr8Q)o3wFM2Ie;kIQ>^(OV1?;jp3ma1kj&#Rw6m zY=(#-qMw+7zkUeM7=%dD|2hjZ($fCS%8oX3^*`bfExIZDZpw~fV_?T8L^s1kGB8U< z{FCvUt=xu-OfjpP-3a)y!rt%|2lp)4xQ4_)PfP{mz@ASO-qVq?@ty(Sd_oX1TcpB` zI40tK3iXhJFUg2M8=+`tgi90|E;bsz0$d`F0(>G~7?>)27&mb+($>rjd@~)!sHJVB zYotkkOo#C#B0d|^Ptrrs53#NM9tCXaBge%q9_c3`hGZApQSjyZ9Sxi_T*Ab`z3Mm9 zHqsN26s7~!?J915Gd|+Zc!(>*^FTts88iCjDB(!L)7c!2$IO?xctmt`x1^+Qc)=5c z><$9#0&y`OK!%7;oGTCq%xn>nJXu5~W{9{%t1UYT z4tOH6Q`Ot3X}0Vf-7Y>kDI;0`7-iGmqBAp;Yn)9t6Riv@5Kh3qfIk600`6icO4Ue6 zPdG|k4{^KbigGp#e=5E7oQUk?WD${`6PIiqlbDWhcpvQY9+IA(IYoKKkDI%PXDzSV z-gWBM^Qqs!(fcw47{&Rx283+#S-kDk4H z-_fUUzo7mD1_oO~28D)&M+_bk88viR^zaceu_NO~jUE#}cHEugCrq4_a985wDM`sG zQ>Ue-O;4YZk(o6!JI899HG9t7yYHDde?hJY&CCv;lWL90&YY6W+@As2n*!O$hLj|O zvLuu+<_}9$1|%yLK9W&Gu$*Tre`ZBWeZlo=%GWTIr#Sq%`q5nDP%8}=gKKbsEFn}h zN)~-w9a4bby+t6n-9s?0F7OiqY_z(Ab%+^|iC@+n#4j2cL;@GHq9#e%r6`PND8JJ{ zNei(oBVWI)3lg{jpTlRi#dgpZ=2I zK1I2+Br{DjQez!shD!#1=K^=8O1CWhF-9#!DqJ#<4`xt9Dz#W=z?LAj#lrJK1!Br$S{QyYgXdbRpl<_$jI;8EAl%7VM%c^{E=Hz zL8}=lWFahDAI7T1o(@x^mbQ#nbD0632KI)$8tHVeNT+7GVk}kjn{gZb4h6oW@XdT7 z?==^V!{in5>-ry&i|TX)R?uPKWbmyf3X-bv`*!pxjPk|YPE@5rqlcxdrZ~(><|wxY zE|vLrySSqwJ_C;%%fH!3tL7B1&O_JqdjEy=Sdv&q|4MqjD$>h>Olo;Q3vp#5PWD04 z!L_SPj!_mXIi|_s?V@Kzd^gUo1Ypiy!yKe*MVTdsj4w)}k&Bh78Re_H=v$FqP5GUP zTxEV~H6P1!rm7uSOD3aEWG$7fVqhNd(dg)2O^%2SV`4p^)h(>2C^I$H^{(+$$`A3o zI-VKeGHW?fK27mIQPo{q9Web5BwV^y9WK0<&fNGtzboc%6fDf{IV5b zFWBI%Rx^_`MjmPL1iIwUjmraL)nt%z!SnH;u&v9&H{V%{vvp!ir*Vd@hgQ35VJKadyr4XAOce7Iba=un`_ZDd zNvwv+UdLFNoG2798^Tz9#v*XkM2v;mi1sl3U@R}ewY4xUFrj8i9Q?r|Zh?6hOe(AJ zg?TIOi!GuROmCQGn5&%@(HiE)?<|mG!~>I^ODoK~VUC4a4l@QOhiri`qgB~p`^Ykr zqG%oiJJPMy3ZWtZe`b^zN;V}}>sbxM8%Hpejj0zA@&h$`{*T*3?>P z#x-4Wb2fel!Z-7#Y6{^9r}f=hBj&mo&$-6dPtn{Fp;@xhA+vlsX4ulx@ruo_UYG#~ zzdgK!m%FcLczAd%KD`1F4?UXu#Eh-&E$#>mjE}+QJF}TtCcN*Ob{8HY=48#m;|(9U zSjyWQhByBB`QHZ|Fkki85%q@lceUHqHbamz*Za#CSN~P@zfe^ExrrP5bB$qJ-+IRCs(g|YVEr9Pd~Ha+2@{r;l-E!wejUwUfr~L%huOkf8))!w!OW5 z$Ie~5-+6b>-hJ=A|H1wbKRR&m(8q^A`Si2Tk9=|T%VS?1KXLNZ*WaA}_Pg($#Xpps z`SGW-r9c02?)ToHYbxdkVLv)2IeWz9wB#w)$c&WC>>0`-UJElU zF~=G*#hN-RIVLm9mZjp+zO`sXG-lxvrzQ`|oD+|E{5Un!SbdHWQ3224Cow0)CkjGt7xu@RS7qocRSq zy1MwuPEJfRr(|c&fNvFCv~A6GhY(;i1UwlF6Pve~D4wXy$-t|E)#jPDy6m!88jCoVjjnrsEjQmy7GnMuj!%oHO8`~4jEl8XYPd(LoX!<>w9LIzB2w5J^L z6Fw&kf~Vzz#%aViV@4u)4sJ7PklLXu@}>jda;7CuPK0H8YDO~hGaWO)HN-J{TBU-EDGeMz`dQSsjdkl{BlAEAyWz!DDK6X2y)<46EV4YFf$J zGg33aeqaNZLs+`Zv}J;E$X6Fpx)#!-T!L%iW~W-GG3#=yiP_N`WRGks(9_$S5H-Ytc&V(@##<>$v$Fm~OnUIq@BP%^Q!KnKtB&Ft9Cs=#j-Zd*p zRet7Pm{+(1Yqj^*j2!l$acV$(qMOEdKy!-41AM1a8_l51Q@BU)P>$|^t+x6Ys z2VCF1R_Chj`(5ap&;|E}0Qea6VONmigYmuO_NwmH>7N)>)!j9I#@h{R?R<>*s)v7d zkcG|_?nkPne>~Ju;r64;dv$-S!z=y0;PSqsT6`fW>s~sj^}szRoz|r_1L`@@e+WKfxoN!$%icBG{Dup zIv+oLxT<^ge2sdfs(W?%$F9G=d-tcSx>u(!Yg1MC>gjjhTh)DEH97cspXM&`biw-z z9&UV9&jRinIf=RgdvJ_rCG5gZ8DCY+|L)cK_wChb=H|NGeV-fp>!DizXc$_fc+t`` zE}0$Dm_+Necrg=SuDy8lG_{_+*dRhxzs?v0U8`o#o+)GeCw|?-9#hu*(RfGNP#-(YADJ>Y%ySW{&YS zG<@Xn@L^~@lhU!dAlxm^nvMTR;2k$)SbRuKq;fdmJ|sCYOKqnRAE%>nYJOkaX z(CkzzI_&9jXrMXt5`8^}B`3~GzREsTqaqu5FlufVxpQx|d=C+aRs2y*}Bg7r#;fU~PzSjjE*x8brq~s8z zRq?LpsPr6tU&~&;!?U*cWgox56zyvdzf^|$F+NRdH3>nkf$jhG&(U0@(K9?mODH~0ux3kL<&>mtC1}t(T(JVR}OZxa5?ef zDDkMtK{Tr51><4~M%imv%P5+oGAqifct$JNG0E9#yqhrvbqM4G67c|I8I?L^x=!~_ z7w+km1=u%N(LXl_8?#2GBApz?8N7-6_3}@PcoFO|EHg1_SnA|#Y{mlBA1j#}nXF~< zqbhE_@`6OX;PQ=31!v;jBGPR+(-_$xTS^Lg)I!`xZn@MZo{%FQv&`%WjFN5HC}zp3 zTqI#<(u}Oc?Boi*$1}7G|HdR{r*dc!FXA+pq!B4h4)Xz|QID842zuRG=|&k7!e5gX zz19M0|6e{kdPBtU(9~v}bvF3wri;O~S2vgM>aTPs{P+1U2X2%Dl&9g}S>AlP+4eAo z;rGn|LzXy3=es9>YxlJP^#L5Ca~`%ffb+1NtEEXhnw*fN8|RJfJ#X1F+e9l z;YvE_KMz2h7wYCBn54xHpnE=m_+ai@t;9c}f3JZ_eAfY(-ZKFD+X^5}9|7q8Ie_kd zU<&y|AYcBokMA`fEnV|9pZ_dg|5LGFd+|%d;M$8X|5F(L=hL~S2rf%zwP^05);jB+KB2v=S+AK3pFGJeP{OhxPnjFwf9KkxYt5ST zRlf_bXjT^8+DV1egGb0fYf8fc}6$Kns8` zpbi>KH=QzXd<#I?H^2+v1e^pM0qg_32G{_25ReDR0!#pm0t^F$0r~@a0y+cy0WAQH z0X_gvK>63Ws~T_wuph7kK>wRyZUC$V(-$4K8Wji`)o z!@QRLwcP)#es`%(Wh9X1LMps)K;+ zwg~uR$kiWD_&3A-(T*67G{6_eDtR1pErtn0J(|DTDozJ~qEYuInNhW%^Tu-|tL`y}#`!B+IFTTC;aTa0mJ$p94od=+9L4Ctk3UB5&(lCqye3@W8tp zK#9gROuEybYdFSJ6Xe2P<_R}|2cR~<1ZX8G=e__l;E&|IXV0EE?~D_qadG1AyYE)G z88W_n`Ev2xbI*xQn>HyK|Ln8R#JAsmTOsFJoNn2OI&|aK+LZKrvhI;vQnriS?Ps^A zOwSa#$fA_(P{OypBmt5zJ@=D?Hp(>^n-}1+c7dHwe#rHtnbE{U;w{|NjJahoNV$?1Cn#abn`c ziDE%ggqS*Ysz^&q6EkMa5ZT!{7mE60{`~o3jV)L_fA;|K>VhC)pBgTfP7f6iW`>Bz zvMu7xh5f{fd6DALg_FhBm04oX{X@mUwbMn%x25R3ON#D$qzHaTieB$a(f=bUCVVJG z=qFMPJt{@)2`O>_qraA7{P$8!IVr{DGg2&ExKI=p7K#-sR)~imepo#6$RpzM#~&A~ zSFaZ9*RNOkyK&=2v3c`mRhPZ>)?4E6?u}y6&r)nImEzrZ-xcq@_n!Fh!wtIjrVfQ18#)yps+V6g`CQp!~oe{jF+)uuAC`W$`xX>d>Q+P4jJ{SXpHb}V$i;3 z2{B-~5W_ZN{t@A)mZGhc4aE|Ke;naoLiimB|1rX!b_w4e;Vm&j+?j>5Ov{B>wo!;@ z5q?*x5Qh-{2*Mvn_-_!t7~#(%`~{cr-P&VMW(Z_`Jod$66>;M-jLDzHzJ}c>gdaB) z@@K4Lr(-V5O|X}S^PsspHhO3{gt=9`2Z*j>m8u|nQGQ^F!c&ij`v5OeqemkmA_OQj{F34DXHbajlSI`^!=sJyaRKYSoaSJ+79ap@TvOg@h@qVVyd*^Ka9p{oo1@ zA%mhKBg4X?LW6@t!VK@uBAbfBLBM6O3xTR5}W}3Ug(Z7uuNJdt~ zpU|Xnqeepqs0acSm960p{KFVNBns}08?_v&<2I}lQ9$^F;E?FyQBmPh3C$TnGry)y zZ}#!=X)%mA(wz!AqLE5M^C}(^$OgKHhDS$6MMZ~4x2oa+?j1U*_ymmUfV+V&|rvblo1^KBYz-ZmU;~vj7SKL4i18>RXD@lc!u~k>>C{d zK1RAYlmB7L2kh_Y5gLS|;_9s8NB%~IK@cOud-bd4>=HjRIx?hR)zBy(RiEf8k)wW< zJ95iRdBG>qx!3{7)8Oy)=W-E8b&xgnXk&6_tzArhjQngwm{*RET) zZk=dvZrb^l75(96Z92AV*P&gvhQ6lT>f^h4>$V*_z;8p}R^0-+1&9`H zI(6*UvTnDA@X(-s{aahKZr8C}y}BK5)h*2Cj-9%Bd;4@mnA>h@P`|lf(@x#$d3)Eb zQ>&KGZ6;H5Pp{^kTGsQfON(y4t(w$!tK9~EyLD?>rxxSC+0VTZzUsBDTc=I{#sRI{ z-Qv*#t_ac+-$*~8MdJ=_1G;q!=m7kYey4x{|A2tj0gApBc+7ZOw^pAb*93h5wc!zc zWd&|9YkFvJ_@RG<6RiYJ9%Fm~xC`JW%=rCVk2^x6$F8<XN|HN}G>aUkJ z@vR4F(yCRf)-VbFfcACj)WHY{$5a%j(1jK_N~~?eFgT9Sf6GJu)CXX6b3+e#>kFXx zo1c90$#}FoZ=OAS_Pd{c`ssVLJzxL$HL@xb#fm`A)H<7l~k z`*!*L_uosjrxNonoS>2?PMnY!e@nW928l8FS5Bw17_^@H_~VbC*tv6O?w~<~dLSO= z6V-e)1vCT@7v^hS9r#Wj(~VniaO_kx#au;?va+(@@Q#M_hVgF(ejh*??8!LpxZ{rY z#1D8W{NI27eTg|z3H;=1uf3-5#vGFT?z`{g!Gi}S<`k4ahCv^J_NNi%$(LV#dH&X| zTj!(O7jC!PM`UGXg)LjQEC&5*;&vM#plQ>lJutU%=k2%OPTu*2g@tuwym zPNFZfqHWu@y}-j|Km726#GGygpAQ^3AiwzH3xy~0N8!%AIeGG={PN2$)i-G}0DT_y z4w*au^Upt*LGCUiPUmmG{U(3;<(G4xe){R_-+c4U38Zz2VL;~tC~v)h!!m~bv-qPw zC6QJI5Pt*6R|A+Q1`vPpil*_-Z-PMwP2yt!aFzxj&!qu|onihJ{CDr(y%hP_1~QRP zT6XQ)rD&jhV7^H*4=~T9b;GeC}H*f4y+wFv<$c|BXBf|F_?MdxgKhe=qdmm!ZCt$PYyW>m23*`AT}2 z7sQ?K%>U!Zk1OCic}{*4U&;b$A>QOaW%Q{tQigpdrR8H>NrEZ(JFsTZV;^XEN6Jp1 zq5U=~+q@y=vSU~qC@+8fMv#Xeg+Jz<$&@Me_YDJINTNbDfmws zkO#d#kn(oWknuUzJ8lx)CORnZu6bg} z6;1M=?rawrmi3J5Gv+kPC~5dg%1F=<4jMN8=<4H|??1!k(Q6RX?9!!6675VCAPoi> zbkvk51}(01T)uo+9(sM1Tt6>LJ~}g4{xj2}5WDj`DMx=JW$Z~Qqe;UTdU=M-^f$^g z>m-zC)=BMA4p^SMK%Q8puV9_61{xIp$nT|?yJ&-YJ)g9&KBQ^TK$CJ$xvox!Azzer z%F>Dbo8&XI`^&Yq0rH8Qfr}73G;U=;gU9>m<~v?NBGR z1`VxV)9O}4v#=Ts3ja23+Emp4Xye(=UzHy$zibbT{9t+Dw^2@rKk7ZXDdG1Q=nlLXyB8G`f~zk7>hc76mI_@4Muq;4Murpoz#6V_>LPPZX*rgzZp99N1&d< z^HELsqrO-2kFvIm{UMe)gARih<^kIS*E}(3p-KE%Pi|fqB44^ENInM|)`NyMRt^80 zvr^tw0vepSiV8HaJhM)ULY-ukXVPGlXVPGlXVys_-&FWttd2j+8QT~1vnqfz7*L%K zqpY~n!FSTYXKQX>`O3V0@};|jj@F*U}&4=P1skAptaCjZMb8lxNmSEYBe* z3#^m+piW}@Y}82|w&Pj{4gc!(QZwR@{{7Nky?V7lA0?l3uwJA|nIRqQ^Ux$Mv}0Rq z^vmeR_LhAHK5yjpm0K3{l`n&a7eT`Y(D2qHnezNu2+s{X#h`Nr@}v*jXV75uF*>}h z1+LD2))$8S_v_cMJ@diH@OW_ci=jXYr;@7h0Re~2_v{&z1PD7S%z*FeLj`Je%1f#sPruspL) zdIa?nAM1YyI54f6Tt zpO@^H8errH&FhsD%*)DyPbA8n_B-TT3qb?Q!mFU+UwV0FowUX_P_D`zC|70$%Lg+o z^8WM?=>QG)f`&z)VLoW!Q@xKd31tJ%RrL??hb$=hhg|2AmV58LSHAGV3yL0t2AbER zgEUdL7}j~{RkqG%N!ROF%;b%80fE+EG9wG}SLh4Jq)l4_0<(AKd2`A{A|WN zNBg@1`xv4!GBVyLt}Kr%0}B=`P&By8S9Myd=Lx@AC$KF1(ewE`FIDt0Se}dY@?0(4 zb^AZWpLsuI$Png(eD>LARo{z!8q5#KS+izU&~QCEu9qjohjr2>)=7UQzaIXTj5waTSSm#T7&DIZnuurE{-E#y7h2G&*V z3$Z`S@c*iA+Gp23#v^)pUXHTBrzT_#JIqy>(AOV@Z-sxCE?s(K zYflEQQz$_{TIIu2Pdz0^j2I!Yw@4Nh6-lfq$p;^NP~pSzJ^4)<*cPyzpj;6+h9M2C zPbr6N3(2E*9AWa~XNdm=`Tn|Dm3<791@?b7IwzXw|Ka!xbAN?c3SCI~fvm5< zxW5X|0D}&ijE_K>GU8_4`r)d{@~r|3+Gnkg!S?z2`Jr;_15@RfA8e5q ze*N_@^81G8AF!8F=I7_1!yYBMXwjly@4WL)nVz1m_>OUa>02Y;zl~E)519j zw!@Tr_K{dtI3KYc<4M}FkHmI@wAAo`1(%L9zy9p}5931FU5z=)6ZhP6&lTc{eWMCk zrVSc8b?PLscTMF3+YHJ)`#uI8#FzL}=1C{V1~ge7SVmYLj69)98D!tYXnQ#J=J*-% z@~7rMS+*$ukfk-)FZKz`DOSYgym|9fK9C01tC(AsW5pC~`sQ!Z?gY5qpd?h|7PMlEq zAa5o57Ti^=$^-ISLf(`Nu#F<0>7T%F(!hF@JZ1g=$}6wPmtJ~FwSoWo*S}Oa&Jlo5 zPSkA^(MHY#?z>=jACTs{$BnMvG$X$3|FHf?d0fVCmN%Njh562U0dlJP5?Ciubt}rc zYTsDbP`)X1#GmDW<&t?qIbj}fK8x4x>>mojsAC8F##GQ0K`Q($FV_c16I)4^- z(x~t^`v2f}K4~!OMS~WD2AbqI>n60_YMelsVq5FVU*gJd;?KM>`Vd^#q1;oJ$a9t< z)EO&*$6vv{0)JQeXC2|1A2sC(>Eaywgb5QQ_T?)1HhAu8(jR4svQB%p0mR){AHf)D z)!)Ef;mn{EPNGpR|zwGz~gv8g$SkPg%dPED)GCv|~Q7?qoS- zp0O_CS_0RgNDKLnH2z9GQ;BiaH-*0;|L7~UC!Yw{%MGm96%n|A^E>6Gp-agBR`G#Pt+3?^FO44Z72ILtp6wnY>(J>lE)l#lK0F9 z_63Z5;5X}h*0rq1Fs4xJ8ld^#jXUX3^6x4e)#cpyHp;E5Nm=JN{V*>m^W-yWq^v`Z zuAq4*mKYDJ02kt@mPXg26-Usf}_}h=nL*uf2_Uv*|TV4sCJ^Lii z=agzD-qiQM&-BpabJIzbKThhXZMCfm>_tz}Rjk%5)j)GxRxsMSWY0w%`ovrK9MdKZSX+H1vVP z;J-Vd4f-2rr(%tR>tvh@wP601Yu;RI{p6gK2QVv#^GJMtg8yqhEm4QBMVe)-KUqg| zyhI!b#u|p+=f8q_^&INl!>BjkV8mQA<$5F6xwyWG`7EN*Er5)y6i`jCp!JA@1(`3{c^qRPR!kMy^m{Un@U|>YkcP- zma9Cd^f?}6AAvv|2&~@;k^y~=QH_7tatsOt((RH2d?{a4+Q7- zx#nxgBiDPm&e$L3r&VRL726byUlY;K9YZ_}T$umt0}~gvKW{!VL(OS(&6#uZM*75I z5^&(UC)dxFJOT%Asd6x{Fze{7=OfYa@pMyMM z-}oc53p%1t`*bAdP*YZ z6~?&Y!L%voH2HA7jcX)aFXTGamWQ+caLw?C-*8j=39NYn2kz%#nc$i&AA^4OD{!xF zMs99y8vCFG0}sxdkQaP7zs|KLu5oa!jO$EX-{3kK*O<7r!8J0jFU^~x!9N$JO5&j8 z5$mqT+Bf5KO`mlDfqff-D;~s!`M>kNV9E8aSAYZOG&wiUH5SSv*SWa9!nH=V#-*n} zKPiGqsWM^6;{fmhPeuN-Z-#Yr7nh<2qTcjsp{mIiaoNPe9toF4Cr=4r;~zC1sH1 zkbQod#DhS75Qqo)#C*8kb9mRk)S4;R>hggD*GsECSJi(^-{Ej1KJmm8W4JcN{y6a< z&pEEOI!G zZ2wsQQx?b%$|BPyE__%fe){?o`Qz80p-fbhN0bT5BcGZQHsqh8YsJ#G&JU%ryLca1) zmMl4q&Pk=LRbj)xfdhMBzIQI^z&d8;`Vdl)4itnrs*bXvoLk5@@ z>jk5%qMazmy3AC_at``P)HTLEPk%I~YDHdw_sek!&mOMvaE=}a{w4E*>uYG2RXXes zknc>Nz&;uKXoiWl>NoK79>nz|)+>HQ+8he}(WB&#Wsq^PZ%2M}E|)UMxpb~;uzV0t zWA2K1z zpw^gKE{Go=^1+znWq+A#D(ts|hR2cUjiycfRQiTIldlBgL121pkDwz#)eYRMO4=!N z%rEkqbhA#z+{@E{GHsPU(?MOM>i?SXF#5nab0BfvQOy;zU&uKp%H!WiTcuBWjrNza zM0yz~fps3s9LqN8q>OR@4)n@=m!U!Cu+{AV5zSogB-V?IMC1m*8X z%!d^s4$hza)rV(IeE%Y_eEm`Vc1^s>Tj9*ETg7?ZR(aqBzzra70O-#M(+WWd!LTzR z7w-g_SA!0gysOUbn#Hvq?A2o2H9nBX&?ldKaue2QE})M33Hw6+@$}PASE+Zf25=T} zWIp%YbIKlmJlC#W8;SYsw_kkmMU|gM8^(M_o&K3?Vq8zd{%6j!UPc@zA%Evt4mmca zyuO4nNF4fg+}9Y4vDIT32jbak#6iE5Y4+ia{)|zkSeGSW+{7^x=MX+dx27ldb>cDl z$AaqzOp9fW^%8;d%CLMAF+AZIc&pYWQ+E2#uQ0c;ZelqiuIxKdwhz9wPOiw*`i4{V z@f*jF9KUj`z_Cgo#!8O>FRrz6OitV>|4jGU1(B+ca}Hy$$AB~A;8>hvFV019+{bZe zAB;OWN6kJJ@n*fnhhrFyp5!B>V2{w{zUUvD5tI z!77co6H;!#xEANUWo~Y++9SesHRdJd#o)j4jGu!$H>!UBe2jhchs16s|IjX|dW&mv z+&{puhRnUZV4(crHEOn$Qw9%olnUybz_<%ab(`&`Tq)~Bwx@SSbB5tb(X8~IP(8U3ykXeXII z+arz>7&q%>wEelR;aN`;Z^lDjz+IImw%MFdVpxu|*>+V zEinAhKfy%5ZkWh4n{huYDobiya}&@=tiGsk%^hyE^H$o{Jm98%QP-L$G#c^CtTe6F z(tY9!e!O&_xRn=maBa~)F()T^#^m(5<~cLcGjayBv1MoU%b7AQc}8MRml>&3vNLls zQ>Bs;WxkmlS28CMh$Z)#{2Q;2`X?_u2dGz0W@T zK;ko0YP3PU4ImF8<*~SkkEoz=iW*BwDr-ooVayygHAORQOtHv(l#TYnqK8JQXpIlX z$2i}`a@m^sWB!=qIADd zuUD0o&hdBkoB1!Lkt_3HrXPAOLIaEWU2?qeP^G!h2NC)_^(S2*pEjHumF6yb3qsx9~-L1G$+nGLlRs<>WEaK;9;MNC)X*dQHbnHK&>7 z<^$$aW}Ugh++eT&g~YEwtlNfqZLIMlJ7bxvcK4lT|; z=OAeEmGhnRJ!o^;>8<BY8}zGC-zsoNV>=8b z=tcA+e!dwja zer96YmxZ%k;&FHyUV?Yx-S`l0#qGEQpTn1MFEW5c5-%A>FyUkr znM9_L3{pj&CQalbxz3C?vB}MeW}dmo>_y`!p|hx;HqjmQBYKz~0k8RicGAl9_bS%a;Q zt!fu9e~3TMU*N0xdcKqIV(cq(NoQ=*qPM997N9ns^ z<(KRI`U~CP#V&VexU<~3?pk-VyVJesLdh*p8Tdgkx&ci@zd_lk0zHpbpuOl5^f@|# zT8v}HSz{2+$FD#HZpR1kw_T`uNeoFMlgTua4Uza9`Gh0@hKuPIx|xOX2!2Rh7Jcj} zdyD<8UF1aT1pTG{Ugx_|UazZw1BYQenv6~(Y~&b=j5b_Os>wL>xEV?jt)a{5R=S_> z5>xFuy9H$Ibznltl^zqfqBt}H8Hgf*9N>&{^gHw?v<;m^5ymF`IH@Q7feWUa%gmR} zSIuT~KQKTHokE|ab#x=FX@6$10am0ni!b0CcoVR7o{mWkM{ztQyr0wObufpQ+wXe`m1MtW(@q+#T*0?%xBpReHK=oc2PI zC>_m16G$;c&VF)|{DXv<)J!*nX(YRuC9(-Di_HY@NVXZSl|qN zA6|&7@giJNgdfnz9EBvL8HxBhz11zuE$Mf zJ_PYlW7e8W&7Jf-^{`+zkOef4Vz^85V%ErBXJ^=X7G(9Z`dI@kuT=$1T4P1>7;bUR zNApJhI&bD5@U#2^_lO8lEar%N#S+md-V|?(4@IjuAkB3Sm3zmPY|TV;Y2Qp+*& z4(XH6$o1g)lYj|*YJ=LMn$-n0(0S1b0uF1|8{NYJyz4!l6c`w4Fz&|_$phpma)_KE zPr|w%H}k-g*3qN%6kWw8ShK7rtTVt*gx_x8rao|fsc+NS0WMqz^$KAajW&RfZ^dum z9gu5w<9&D%@sT-X39R2aQe_@7LueEoNptC1dXB!pRV2)*MEBl6pTepBO zOt*5Z2CLEf!HVF~JeA)Me9_Feh;3q@JT4d5FW3j|Blb&bjd~3{@t_KKyiOwIgrklF z+*t+LrvdyVPzV`;Ix#2#J%SohPh+of0-HpVJIHUzeI$@0-=l}hpVcBP+f zy5Tc2AtLjQGGm^x+E{07)J=f)9{o4ns=wC9^=W-k_jLQZ;qFatv>Wf5u5we{RCluL z3#`YfD}_f+>rP$mu5?@6cK9}^3*QhFi^ifHl#i;>O4NedQHT+4AVV4HaIU3BgR$Lc zGd$RfldunG;tI(7^}so8*aQ5XL^8>2;t31#goEFE5J=;(B3>Y23NDmL5-DP=NEK;f zGGrZ}$P_suU(6OIqD;&a6{1os6xCvps1dbdsi+h6qCu<_tAS@XiY=l^Y=x~5nCD)JK~BFK2T_`Tj2{B5$8t7 z#Cv1ldCjD_t5R~Isd8-KoUw6nU~SNK;a!#7pbNo&u(t~cc&Q9*iLSLi?Z=)ZL9q7{ KaHu12-19G+0wo0i literal 0 HcmV?d00001 diff --git a/libs/bin/subliminal.exe b/libs/bin/subliminal.exe new file mode 100644 index 0000000000000000000000000000000000000000..51c897be4427c935be43ce0086291897f2a6789d GIT binary patch literal 93046 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 K{sdwk&%Xd+Dkwhy literal 0 HcmV?d00001 diff --git a/libs/bs4/__init__.py b/libs/bs4/__init__.py index aa818ae4..797a6826 100644 --- a/libs/bs4/__init__.py +++ b/libs/bs4/__init__.py @@ -21,14 +21,15 @@ http://www.crummy.com/software/BeautifulSoup/bs4/doc/ # found in the LICENSE file. __author__ = "Leonard Richardson (leonardr@segfault.org)" -__version__ = "4.5.1" -__copyright__ = "Copyright (c) 2004-2016 Leonard Richardson" +__version__ = "4.6.3" +__copyright__ = "Copyright (c) 2004-2018 Leonard Richardson" __license__ = "MIT" __all__ = ['BeautifulSoup'] import os import re +import sys import traceback import warnings @@ -50,7 +51,7 @@ from .element import ( # The very first thing we do is give a useful error if someone is # running this code under Python 3 without converting it. -'You are trying to run the Python 2 version of Beautiful Soup under Python 3. This will not work.'<>'You need to convert the code, either by installing it (`python setup.py install`) or by running 2to3 (`2to3 -w bs4`).' +'You are trying to run the Python 2 version of Beautiful Soup under Python 3. This will not work.'!='You need to convert the code, either by installing it (`python setup.py install`) or by running 2to3 (`2to3 -w bs4`).' class BeautifulSoup(Tag): """ @@ -74,7 +75,7 @@ class BeautifulSoup(Tag): like HTML's
tag), call handle_starttag and then handle_endtag. """ - ROOT_TAG_NAME = u'[document]' + ROOT_TAG_NAME = '[document]' # If the end-user gives no indication which tree builder they # want, look for one with these features. @@ -82,14 +83,46 @@ class BeautifulSoup(Tag): ASCII_SPACES = '\x20\x0a\x09\x0c\x0d' - NO_PARSER_SPECIFIED_WARNING = "No parser was explicitly specified, so I'm using the best available %(markup_type)s parser for this system (\"%(parser)s\"). This usually isn't a problem, but if you run this code on another system, or in a different virtual environment, it may use a different parser and behave differently.\n\nThe code that caused this warning is on line %(line_number)s of the file %(filename)s. To get rid of this warning, change code that looks like this:\n\n BeautifulSoup([your markup])\n\nto this:\n\n BeautifulSoup([your markup], \"%(parser)s\")\n" + NO_PARSER_SPECIFIED_WARNING = "No parser was explicitly specified, so I'm using the best available %(markup_type)s parser for this system (\"%(parser)s\"). This usually isn't a problem, but if you run this code on another system, or in a different virtual environment, it may use a different parser and behave differently.\n\nThe code that caused this warning is on line %(line_number)s of the file %(filename)s. To get rid of this warning, pass the additional argument 'features=\"%(parser)s\"' to the BeautifulSoup constructor.\n" def __init__(self, markup="", features=None, builder=None, parse_only=None, from_encoding=None, exclude_encodings=None, **kwargs): - """The Soup object is initialized as the 'root tag', and the - provided markup (which can be a string or a file-like object) - is fed into the underlying parser.""" + """Constructor. + + :param markup: A string or a file-like object representing + markup to be parsed. + + :param features: Desirable features of the parser to be used. This + may be the name of a specific parser ("lxml", "lxml-xml", + "html.parser", or "html5lib") or it may be the type of markup + to be used ("html", "html5", "xml"). It's recommended that you + name a specific parser, so that Beautiful Soup gives you the + same results across platforms and virtual environments. + + :param builder: A specific TreeBuilder to use instead of looking one + up based on `features`. You shouldn't need to use this. + + :param parse_only: A SoupStrainer. Only parts of the document + matching the SoupStrainer will be considered. This is useful + when parsing part of a document that would otherwise be too + large to fit into memory. + + :param from_encoding: A string indicating the encoding of the + document to be parsed. Pass this in if Beautiful Soup is + guessing wrongly about the document's encoding. + + :param exclude_encodings: A list of strings indicating + encodings known to be wrong. Pass this in if you don't know + the document's encoding but you know Beautiful Soup's guess is + wrong. + + :param kwargs: For backwards compatibility purposes, the + constructor accepts certain keyword arguments used in + Beautiful Soup 3. None of these arguments do anything in + Beautiful Soup 4 and there's no need to actually pass keyword + arguments into the constructor. + """ if 'convertEntities' in kwargs: warnings.warn( @@ -142,18 +175,18 @@ class BeautifulSoup(Tag): from_encoding = from_encoding or deprecated_argument( "fromEncoding", "from_encoding") - if from_encoding and isinstance(markup, unicode): + if from_encoding and isinstance(markup, str): warnings.warn("You provided Unicode markup but also provided a value for from_encoding. Your from_encoding will be ignored.") from_encoding = None if len(kwargs) > 0: - arg = kwargs.keys().pop() + arg = list(kwargs.keys()).pop() raise TypeError( "__init__() got an unexpected keyword argument '%s'" % arg) if builder is None: original_features = features - if isinstance(features, basestring): + if isinstance(features, str): features = [features] if features is None or len(features) == 0: features = self.DEFAULT_BUILDER_FEATURES @@ -171,14 +204,35 @@ class BeautifulSoup(Tag): else: markup_type = "HTML" - caller = traceback.extract_stack()[0] - filename = caller[0] - line_number = caller[1] - warnings.warn(self.NO_PARSER_SPECIFIED_WARNING % dict( - filename=filename, - line_number=line_number, - parser=builder.NAME, - markup_type=markup_type)) + # This code adapted from warnings.py so that we get the same line + # of code as our warnings.warn() call gets, even if the answer is wrong + # (as it may be in a multithreading situation). + caller = None + try: + caller = sys._getframe(1) + except ValueError: + pass + if caller: + globals = caller.f_globals + line_number = caller.f_lineno + else: + globals = sys.__dict__ + line_number= 1 + filename = globals.get('__file__') + if filename: + fnl = filename.lower() + if fnl.endswith((".pyc", ".pyo")): + filename = filename[:-1] + if filename: + # If there is no filename at all, the user is most likely in a REPL, + # and the warning is not necessary. + values = dict( + filename=filename, + line_number=line_number, + parser=builder.NAME, + markup_type=markup_type + ) + warnings.warn(self.NO_PARSER_SPECIFIED_WARNING % values, stacklevel=2) self.builder = builder self.is_xml = builder.is_xml @@ -191,13 +245,13 @@ class BeautifulSoup(Tag): markup = markup.read() elif len(markup) <= 256 and ( (isinstance(markup, bytes) and not b'<' in markup) - or (isinstance(markup, unicode) and not u'<' in markup) + or (isinstance(markup, str) and not '<' in markup) ): # Print out warnings for a couple beginner problems # involving passing non-markup to Beautiful Soup. # Beautiful Soup will still parse the input as markup, # just in case that's what the user really wants. - if (isinstance(markup, unicode) + if (isinstance(markup, str) and not os.path.supports_unicode_filenames): possible_filename = markup.encode("utf8") else: @@ -205,18 +259,18 @@ class BeautifulSoup(Tag): is_file = False try: is_file = os.path.exists(possible_filename) - except Exception, e: + except Exception as e: # This is almost certainly a problem involving # characters not valid in filenames on this # system. Just let it go. pass if is_file: - if isinstance(markup, unicode): + if isinstance(markup, str): markup = markup.encode("utf8") warnings.warn( '"%s" looks like a filename, not markup. You should' - 'probably open this file and pass the filehandle into' - 'Beautiful Soup.' % markup) + ' probably open this file and pass the filehandle into' + ' Beautiful Soup.' % markup) self._check_markup_is_url(markup) for (self.markup, self.original_encoding, self.declared_html_encoding, @@ -263,9 +317,9 @@ class BeautifulSoup(Tag): if isinstance(markup, bytes): space = b' ' cant_start_with = (b"http:", b"https:") - elif isinstance(markup, unicode): - space = u' ' - cant_start_with = (u"http:", u"https:") + elif isinstance(markup, str): + space = ' ' + cant_start_with = ("http:", "https:") else: return @@ -302,9 +356,10 @@ class BeautifulSoup(Tag): self.preserve_whitespace_tag_stack = [] self.pushTag(self) - def new_tag(self, name, namespace=None, nsprefix=None, **attrs): + def new_tag(self, name, namespace=None, nsprefix=None, attrs={}, **kwattrs): """Create a new tag associated with this soup.""" - return Tag(None, self.builder, name, namespace, nsprefix, attrs) + kwattrs.update(attrs) + return Tag(None, self.builder, name, namespace, nsprefix, kwattrs) def new_string(self, s, subclass=NavigableString): """Create a new NavigableString associated with this soup.""" @@ -336,7 +391,7 @@ class BeautifulSoup(Tag): def endData(self, containerClass=NavigableString): if self.current_data: - current_data = u''.join(self.current_data) + current_data = ''.join(self.current_data) # If whitespace is not preserved, and this string contains # nothing but ASCII spaces, replace it with a single space # or newline. @@ -490,9 +545,9 @@ class BeautifulSoup(Tag): encoding_part = '' if eventual_encoding != None: encoding_part = ' encoding="%s"' % eventual_encoding - prefix = u'\n' % encoding_part + prefix = '\n' % encoding_part else: - prefix = u'' + prefix = '' if not pretty_print: indent_level = None else: @@ -526,4 +581,4 @@ class FeatureNotFound(ValueError): if __name__ == '__main__': import sys soup = BeautifulSoup(sys.stdin) - print soup.prettify() + print(soup.prettify()) diff --git a/libs/bs4/builder/__init__.py b/libs/bs4/builder/__init__.py index 601979bf..b80ad684 100644 --- a/libs/bs4/builder/__init__.py +++ b/libs/bs4/builder/__init__.py @@ -93,7 +93,7 @@ class TreeBuilder(object): preserve_whitespace_tags = set() empty_element_tags = None # A tag will be considered an empty-element # tag when and only when it has no contents. - + # A value for these tag/attribute combinations is a space- or # comma-separated list of CDATA, rather than a single CDATA. cdata_list_attributes = {} @@ -125,7 +125,7 @@ class TreeBuilder(object): if self.empty_element_tags is None: return True return tag_name in self.empty_element_tags - + def feed(self, markup): raise NotImplementedError() @@ -160,13 +160,13 @@ class TreeBuilder(object): universal = self.cdata_list_attributes.get('*', []) tag_specific = self.cdata_list_attributes.get( tag_name.lower(), None) - for attr in attrs.keys(): + for attr in list(attrs.keys()): if attr in universal or (tag_specific and attr in tag_specific): # We have a "class"-type attribute whose string # value is a whitespace-separated list of # values. Split it into a list. value = attrs[attr] - if isinstance(value, basestring): + if isinstance(value, str): values = whitespace_re.split(value) else: # html5lib sometimes calls setAttributes twice @@ -232,9 +232,20 @@ class HTMLTreeBuilder(TreeBuilder): """ preserve_whitespace_tags = HTMLAwareEntitySubstitution.preserve_whitespace_tags - empty_element_tags = set(['br' , 'hr', 'input', 'img', 'meta', - 'spacer', 'link', 'frame', 'base']) + empty_element_tags = set([ + # These are from HTML5. + 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr', + + # These are from earlier versions of HTML and are removed in HTML5. + 'basefont', 'bgsound', 'command', 'frame', 'image', 'isindex', 'nextid', 'spacer' + ]) + # The HTML standard defines these as block-level elements. Beautiful + # Soup does not treat these elements differently from other elements, + # but it may do so eventually, and this information is available if + # you need to use it. + block_elements = set(["address", "article", "aside", "blockquote", "canvas", "dd", "div", "dl", "dt", "fieldset", "figcaption", "figure", "footer", "form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hr", "li", "main", "nav", "noscript", "ol", "output", "p", "pre", "section", "table", "tfoot", "ul", "video"]) + # The HTML standard defines these attributes as containing a # space-separated list of values, not a single value. That is, # class="foo bar" means that the 'class' attribute has two values, diff --git a/libs/bs4/builder/_html5lib.py b/libs/bs4/builder/_html5lib.py index c46f8823..d9d468f5 100644 --- a/libs/bs4/builder/_html5lib.py +++ b/libs/bs4/builder/_html5lib.py @@ -6,6 +6,7 @@ __all__ = [ ] import warnings +import re from bs4.builder import ( PERMISSIVE, HTML, @@ -17,7 +18,10 @@ from bs4.element import ( whitespace_re, ) import html5lib -from html5lib.constants import namespaces +from html5lib.constants import ( + namespaces, + prefixes, + ) from bs4.element import ( Comment, Doctype, @@ -29,7 +33,7 @@ try: # Pre-0.99999999 from html5lib.treebuilders import _base as treebuilder_base new_html5lib = False -except ImportError, e: +except ImportError as e: # 0.99999999 and up from html5lib.treebuilders import base as treebuilder_base new_html5lib = True @@ -60,7 +64,7 @@ class HTML5TreeBuilder(HTMLTreeBuilder): parser = html5lib.HTMLParser(tree=self.create_treebuilder) extra_kwargs = dict() - if not isinstance(markup, unicode): + if not isinstance(markup, str): if new_html5lib: extra_kwargs['override_encoding'] = self.user_specified_encoding else: @@ -68,13 +72,13 @@ class HTML5TreeBuilder(HTMLTreeBuilder): doc = parser.parse(markup, **extra_kwargs) # Set the character encoding detected by the tokenizer. - if isinstance(markup, unicode): + if isinstance(markup, str): # We need to special-case this because html5lib sets # charEncoding to UTF-8 if it gets Unicode input. doc.original_encoding = None else: original_encoding = parser.tokenizer.stream.charEncoding[0] - if not isinstance(original_encoding, basestring): + if not isinstance(original_encoding, str): # In 0.99999999 and up, the encoding is an html5lib # Encoding object. We want to use a string for compatibility # with other tree builders. @@ -83,18 +87,22 @@ class HTML5TreeBuilder(HTMLTreeBuilder): def create_treebuilder(self, namespaceHTMLElements): self.underlying_builder = TreeBuilderForHtml5lib( - self.soup, namespaceHTMLElements) + namespaceHTMLElements, self.soup) return self.underlying_builder def test_fragment_to_document(self, fragment): """See `TreeBuilder`.""" - return u'%s' % fragment + return '%s' % fragment class TreeBuilderForHtml5lib(treebuilder_base.TreeBuilder): - def __init__(self, soup, namespaceHTMLElements): - self.soup = soup + def __init__(self, namespaceHTMLElements, soup=None): + if soup: + self.soup = soup + else: + from bs4 import BeautifulSoup + self.soup = BeautifulSoup("", "html.parser") super(TreeBuilderForHtml5lib, self).__init__(namespaceHTMLElements) def documentClass(self): @@ -117,7 +125,8 @@ class TreeBuilderForHtml5lib(treebuilder_base.TreeBuilder): return TextNode(Comment(data), self.soup) def fragmentClass(self): - self.soup = BeautifulSoup("") + from bs4 import BeautifulSoup + self.soup = BeautifulSoup("", "html.parser") self.soup.name = "[document_fragment]" return Element(self.soup, self.soup, None) @@ -131,6 +140,56 @@ class TreeBuilderForHtml5lib(treebuilder_base.TreeBuilder): def getFragment(self): return treebuilder_base.TreeBuilder.getFragment(self).element + def testSerializer(self, element): + from bs4 import BeautifulSoup + rv = [] + doctype_re = re.compile(r'^(.*?)(?: PUBLIC "(.*?)"(?: "(.*?)")?| SYSTEM "(.*?)")?$') + + def serializeElement(element, indent=0): + if isinstance(element, BeautifulSoup): + pass + if isinstance(element, Doctype): + m = doctype_re.match(element) + if m: + name = m.group(1) + if m.lastindex > 1: + publicId = m.group(2) or "" + systemId = m.group(3) or m.group(4) or "" + rv.append("""|%s""" % + (' ' * indent, name, publicId, systemId)) + else: + rv.append("|%s" % (' ' * indent, name)) + else: + rv.append("|%s" % (' ' * indent,)) + elif isinstance(element, Comment): + rv.append("|%s" % (' ' * indent, element)) + elif isinstance(element, NavigableString): + rv.append("|%s\"%s\"" % (' ' * indent, element)) + else: + if element.namespace: + name = "%s %s" % (prefixes[element.namespace], + element.name) + else: + name = element.name + rv.append("|%s<%s>" % (' ' * indent, name)) + if element.attrs: + attributes = [] + for name, value in list(element.attrs.items()): + if isinstance(name, NamespacedAttribute): + name = "%s %s" % (prefixes[name.namespace], name.name) + if isinstance(value, list): + value = " ".join(value) + attributes.append((name, value)) + + for name, value in sorted(attributes): + rv.append('|%s%s="%s"' % (' ' * (indent + 2), name, value)) + indent += 2 + for child in element.children: + serializeElement(child, indent) + serializeElement(element, 0) + + return "\n".join(rv) + class AttrList(object): def __init__(self, element): self.element = element @@ -170,7 +229,7 @@ class Element(treebuilder_base.Node): def appendChild(self, node): string_child = child = None - if isinstance(node, basestring): + if isinstance(node, str): # Some other piece of code decided to pass in a string # instead of creating a TextElement object to contain the # string. @@ -182,10 +241,12 @@ class Element(treebuilder_base.Node): child = node elif node.element.__class__ == NavigableString: string_child = child = node.element + node.parent = self else: child = node.element + node.parent = self - if not isinstance(child, basestring) and child.parent is not None: + if not isinstance(child, str) and child.parent is not None: node.element.extract() if (string_child and self.element.contents @@ -198,7 +259,7 @@ class Element(treebuilder_base.Node): old_element.replace_with(new_element) self.soup._most_recent_element = new_element else: - if isinstance(node, basestring): + if isinstance(node, str): # Create a brand new NavigableString from this string. child = self.soup.new_string(node) @@ -221,6 +282,8 @@ class Element(treebuilder_base.Node): most_recent_element=most_recent_element) def getAttributes(self): + if isinstance(self.element, Comment): + return {} return AttrList(self.element) def setAttributes(self, attributes): @@ -236,7 +299,7 @@ class Element(treebuilder_base.Node): self.soup.builder._replace_cdata_list_attribute_values( self.name, attributes) - for name, value in attributes.items(): + for name, value in list(attributes.items()): self.element[name] = value # The attributes may contain variables that need substitution. @@ -248,11 +311,11 @@ class Element(treebuilder_base.Node): attributes = property(getAttributes, setAttributes) def insertText(self, data, insertBefore=None): + text = TextNode(self.soup.new_string(data), self.soup) if insertBefore: - text = TextNode(self.soup.new_string(data), self.soup) - self.insertBefore(data, insertBefore) + self.insertBefore(text, insertBefore) else: - self.appendChild(data) + self.appendChild(text) def insertBefore(self, node, refNode): index = self.element.index(refNode.element) @@ -274,6 +337,7 @@ class Element(treebuilder_base.Node): # print "MOVE", self.element.contents # print "FROM", self.element # print "TO", new_parent.element + element = self.element new_parent_element = new_parent.element # Determine what this tag's next_element will be once all the children @@ -292,7 +356,6 @@ class Element(treebuilder_base.Node): new_parents_last_descendant_next_element = new_parent_element.next_element to_append = element.contents - append_after = new_parent_element.contents if len(to_append) > 0: # Set the first child's previous_element and previous_sibling # to elements within the new parent @@ -309,12 +372,19 @@ class Element(treebuilder_base.Node): if new_parents_last_child: new_parents_last_child.next_sibling = first_child - # Fix the last child's next_element and next_sibling - last_child = to_append[-1] - last_child.next_element = new_parents_last_descendant_next_element + # Find the very last element being moved. It is now the + # parent's last descendant. It has no .next_sibling and + # its .next_element is whatever the previous last + # descendant had. + last_childs_last_descendant = to_append[-1]._last_descendant(False, True) + + last_childs_last_descendant.next_element = new_parents_last_descendant_next_element if new_parents_last_descendant_next_element: - new_parents_last_descendant_next_element.previous_element = last_child - last_child.next_sibling = None + # TODO: This code has no test coverage and I'm not sure + # how to get html5lib to go through this path, but it's + # just the other side of the previous line. + new_parents_last_descendant_next_element.previous_element = last_childs_last_descendant + last_childs_last_descendant.next_sibling = None for child in to_append: child.parent = new_parent_element diff --git a/libs/bs4/builder/_htmlparser.py b/libs/bs4/builder/_htmlparser.py index 823ca15a..7ae60272 100644 --- a/libs/bs4/builder/_htmlparser.py +++ b/libs/bs4/builder/_htmlparser.py @@ -1,3 +1,4 @@ +# encoding: utf-8 """Use the HTMLParser library to parse HTML files that aren't too bad.""" # Use of this source code is governed by a BSD-style license that can be @@ -7,11 +8,11 @@ __all__ = [ 'HTMLParserTreeBuilder', ] -from HTMLParser import HTMLParser +from html.parser import HTMLParser try: - from HTMLParser import HTMLParseError -except ImportError, e: + from html.parser import HTMLParseError +except ImportError as e: # HTMLParseError is removed in Python 3.5. Since it can never be # thrown in 3.5, we can just define our own class as a placeholder. class HTMLParseError(Exception): @@ -52,7 +53,42 @@ from bs4.builder import ( HTMLPARSER = 'html.parser' class BeautifulSoupHTMLParser(HTMLParser): - def handle_starttag(self, name, attrs): + + def __init__(self, *args, **kwargs): + HTMLParser.__init__(self, *args, **kwargs) + + # Keep a list of empty-element tags that were encountered + # without an explicit closing tag. If we encounter a closing tag + # of this type, we'll associate it with one of those entries. + # + # This isn't a stack because we don't care about the + # order. It's a list of closing tags we've already handled and + # will ignore, assuming they ever show up. + self.already_closed_empty_element = [] + + def error(self, msg): + """In Python 3, HTMLParser subclasses must implement error(), although this + requirement doesn't appear to be documented. + + In Python 2, HTMLParser implements error() as raising an exception. + + In any event, this method is called only on very strange markup and our best strategy + is to pretend it didn't happen and keep going. + """ + warnings.warn(msg) + + def handle_startendtag(self, name, attrs): + # This is only called when the markup looks like + # . + + # is_startend() tells handle_starttag not to close the tag + # just because its name matches a known empty-element tag. We + # know that this is an empty-element tag and we want to call + # handle_endtag ourselves. + tag = self.handle_starttag(name, attrs, handle_empty_element=False) + self.handle_endtag(name) + + def handle_starttag(self, name, attrs, handle_empty_element=True): # XXX namespace attr_dict = {} for key, value in attrs: @@ -62,10 +98,34 @@ class BeautifulSoupHTMLParser(HTMLParser): value = '' attr_dict[key] = value attrvalue = '""' - self.soup.handle_starttag(name, None, None, attr_dict) + #print "START", name + tag = self.soup.handle_starttag(name, None, None, attr_dict) + if tag and tag.is_empty_element and handle_empty_element: + # Unlike other parsers, html.parser doesn't send separate end tag + # events for empty-element tags. (It's handled in + # handle_startendtag, but only if the original markup looked like + # .) + # + # So we need to call handle_endtag() ourselves. Since we + # know the start event is identical to the end event, we + # don't want handle_endtag() to cross off any previous end + # events for tags of this name. + self.handle_endtag(name, check_already_closed=False) - def handle_endtag(self, name): - self.soup.handle_endtag(name) + # But we might encounter an explicit closing tag for this tag + # later on. If so, we want to ignore it. + self.already_closed_empty_element.append(name) + + def handle_endtag(self, name, check_already_closed=True): + #print "END", name + if check_already_closed and name in self.already_closed_empty_element: + # This is a redundant end tag for an empty-element tag. + # We've already called handle_endtag() for it, so just + # check it off the list. + # print "ALREADY CLOSED", name + self.already_closed_empty_element.remove(name) + else: + self.soup.handle_endtag(name) def handle_data(self, data): self.soup.handle_data(data) @@ -81,11 +141,26 @@ class BeautifulSoupHTMLParser(HTMLParser): else: real_name = int(name) - try: - data = unichr(real_name) - except (ValueError, OverflowError), e: - data = u"\N{REPLACEMENT CHARACTER}" - + data = None + if real_name < 256: + # HTML numeric entities are supposed to reference Unicode + # code points, but sometimes they reference code points in + # some other encoding (ahem, Windows-1252). E.g. “ + # instead of É for LEFT DOUBLE QUOTATION MARK. This + # code tries to detect this situation and compensate. + for encoding in (self.soup.original_encoding, 'windows-1252'): + if not encoding: + continue + try: + data = bytearray([real_name]).decode(encoding) + except UnicodeDecodeError as e: + pass + if not data: + try: + data = chr(real_name) + except (ValueError, OverflowError) as e: + pass + data = data or "\N{REPLACEMENT CHARACTER}" self.handle_data(data) def handle_entityref(self, name): @@ -93,7 +168,12 @@ class BeautifulSoupHTMLParser(HTMLParser): if character is not None: data = character else: - data = "&%s;" % name + # If this were XML, it would be ambiguous whether "&foo" + # was an character entity reference with a missing + # semicolon or the literal string "&foo". Since this is + # HTML, we have a complete list of all character entity references, + # and this one wasn't found, so assume it's the literal string "&foo". + data = "&%s" % name self.handle_data(data) def handle_comment(self, data): @@ -148,7 +228,7 @@ class HTMLParserTreeBuilder(HTMLTreeBuilder): declared within markup, whether any characters had to be replaced with REPLACEMENT CHARACTER). """ - if isinstance(markup, unicode): + if isinstance(markup, str): yield (markup, None, None, False) return @@ -165,10 +245,12 @@ class HTMLParserTreeBuilder(HTMLTreeBuilder): parser.soup = self.soup try: parser.feed(markup) - except HTMLParseError, e: + parser.close() + except HTMLParseError as e: warnings.warn(RuntimeWarning( "Python's built-in HTMLParser cannot parse the given document. This is not a bug in Beautiful Soup. The best solution is to install an external parser (lxml or html5lib), and use Beautiful Soup with that parser. See http://www.crummy.com/software/BeautifulSoup/bs4/doc/#installing-a-parser for help.")) raise e + parser.already_closed_empty_element = [] # Patch 3.2 versions of HTMLParser earlier than 3.2.3 to use some # 3.2.3 code. This ensures they don't treat markup like

as a diff --git a/libs/bs4/builder/_lxml.py b/libs/bs4/builder/_lxml.py index d2ca2872..0dc9affe 100644 --- a/libs/bs4/builder/_lxml.py +++ b/libs/bs4/builder/_lxml.py @@ -5,9 +5,13 @@ __all__ = [ 'LXMLTreeBuilder', ] +try: + from collections.abc import Callable # Python 3.6 +except ImportError as e: + from collections import Callable + from io import BytesIO -from StringIO import StringIO -import collections +from io import StringIO from lxml import etree from bs4.element import ( Comment, @@ -58,7 +62,7 @@ class LXMLTreeBuilderForXML(TreeBuilder): # Use the default parser. parser = self.default_parser(encoding) - if isinstance(parser, collections.Callable): + if isinstance(parser, Callable): # Instantiate the parser with default arguments parser = parser(target=self, strip_cdata=False, encoding=encoding) return parser @@ -101,12 +105,12 @@ class LXMLTreeBuilderForXML(TreeBuilder): else: self.processing_instruction_class = XMLProcessingInstruction - if isinstance(markup, unicode): + if isinstance(markup, str): # We were given Unicode. Maybe lxml can parse Unicode on # this system? yield markup, None, document_declared_encoding, False - if isinstance(markup, unicode): + if isinstance(markup, str): # No, apparently not. Convert the Unicode to UTF-8 and # tell lxml to parse it as UTF-8. yield (markup.encode("utf8"), "utf8", @@ -121,7 +125,7 @@ class LXMLTreeBuilderForXML(TreeBuilder): def feed(self, markup): if isinstance(markup, bytes): markup = BytesIO(markup) - elif isinstance(markup, unicode): + elif isinstance(markup, str): markup = StringIO(markup) # Call feed() at least once, even if the markup is empty, @@ -136,7 +140,7 @@ class LXMLTreeBuilderForXML(TreeBuilder): if len(data) != 0: self.parser.feed(data) self.parser.close() - except (UnicodeDecodeError, LookupError, etree.ParserError), e: + except (UnicodeDecodeError, LookupError, etree.ParserError) as e: raise ParserRejectedMarkup(str(e)) def close(self): @@ -147,19 +151,19 @@ class LXMLTreeBuilderForXML(TreeBuilder): attrs = dict(attrs) nsprefix = None # Invert each namespace map as it comes in. - if len(self.nsmaps) > 1: - # There are no new namespaces for this tag, but - # non-default namespaces are in play, so we need a - # separate tag stack to know when they end. - self.nsmaps.append(None) + if len(nsmap) == 0 and len(self.nsmaps) > 1: + # There are no new namespaces for this tag, but + # non-default namespaces are in play, so we need a + # separate tag stack to know when they end. + self.nsmaps.append(None) elif len(nsmap) > 0: # A new namespace mapping has come into play. - inverted_nsmap = dict((value, key) for key, value in nsmap.items()) + inverted_nsmap = dict((value, key) for key, value in list(nsmap.items())) self.nsmaps.append(inverted_nsmap) # Also treat the namespace mapping as a set of attributes on the # tag, so we can recreate it later. attrs = attrs.copy() - for prefix, namespace in nsmap.items(): + for prefix, namespace in list(nsmap.items()): attribute = NamespacedAttribute( "xmlns", prefix, "http://www.w3.org/2000/xmlns/") attrs[attribute] = namespace @@ -168,7 +172,7 @@ class LXMLTreeBuilderForXML(TreeBuilder): # from lxml with namespaces attached to their names, and # turn then into NamespacedAttribute objects. new_attrs = {} - for attr, value in attrs.items(): + for attr, value in list(attrs.items()): namespace, attr = self._getNsTag(attr) if namespace is None: new_attrs[attr] = value @@ -228,7 +232,7 @@ class LXMLTreeBuilderForXML(TreeBuilder): def test_fragment_to_document(self, fragment): """See `TreeBuilder`.""" - return u'\n%s' % fragment + return '\n%s' % fragment class LXMLTreeBuilder(HTMLTreeBuilder, LXMLTreeBuilderForXML): @@ -249,10 +253,10 @@ class LXMLTreeBuilder(HTMLTreeBuilder, LXMLTreeBuilderForXML): self.parser = self.parser_for(encoding) self.parser.feed(markup) self.parser.close() - except (UnicodeDecodeError, LookupError, etree.ParserError), e: + except (UnicodeDecodeError, LookupError, etree.ParserError) as e: raise ParserRejectedMarkup(str(e)) def test_fragment_to_document(self, fragment): """See `TreeBuilder`.""" - return u'%s' % fragment + return '%s' % fragment diff --git a/libs/bs4/dammit.py b/libs/bs4/dammit.py index 2bf67f7f..ae6d4ad8 100644 --- a/libs/bs4/dammit.py +++ b/libs/bs4/dammit.py @@ -11,7 +11,7 @@ XML or HTML to reflect a new encoding; that's the tree builder's job. __license__ = "MIT" import codecs -from htmlentitydefs import codepoint2name +from html.entities import codepoint2name import re import logging import string @@ -46,9 +46,9 @@ except ImportError: pass xml_encoding_re = re.compile( - '^<\?.*encoding=[\'"](.*?)[\'"].*\?>'.encode(), re.I) + '^<\\?.*encoding=[\'"](.*?)[\'"].*\\?>'.encode(), re.I) html_meta_re = re.compile( - '<\s*meta[^>]+charset\s*=\s*["\']?([^>]*?)[ /;\'">]'.encode(), re.I) + '<\\s*meta[^>]+charset\\s*=\\s*["\']?([^>]*?)[ /;\'">]'.encode(), re.I) class EntitySubstitution(object): @@ -59,7 +59,7 @@ class EntitySubstitution(object): reverse_lookup = {} characters_for_re = [] for codepoint, name in list(codepoint2name.items()): - character = unichr(codepoint) + character = chr(codepoint) if codepoint != 34: # There's no point in turning the quotation mark into # ", unless it happens within an attribute value, which @@ -82,7 +82,7 @@ class EntitySubstitution(object): } BARE_AMPERSAND_OR_BRACKET = re.compile("([<>]|" - "&(?!#\d+;|#x[0-9a-fA-F]+;|\w+;)" + "&(?!#\\d+;|#x[0-9a-fA-F]+;|\\w+;)" ")") AMPERSAND_OR_BRACKET = re.compile("([<>&])") @@ -274,7 +274,7 @@ class EncodingDetector: def strip_byte_order_mark(cls, data): """If a byte-order mark is present, strip it and return the encoding it implies.""" encoding = None - if isinstance(data, unicode): + if isinstance(data, str): # Unicode data cannot have a byte-order mark. return data, encoding if (len(data) >= 4) and (data[:2] == b'\xfe\xff') \ @@ -310,7 +310,7 @@ class EncodingDetector: else: xml_endpos = 1024 html_endpos = max(2048, int(len(markup) * 0.05)) - + declared_encoding = None declared_encoding_match = xml_encoding_re.search(markup, endpos=xml_endpos) if not declared_encoding_match and is_html: @@ -352,9 +352,9 @@ class UnicodeDammit: markup, override_encodings, is_html, exclude_encodings) # Short-circuit if the data is in Unicode to begin with. - if isinstance(markup, unicode) or markup == '': + if isinstance(markup, str) or markup == '': self.markup = markup - self.unicode_markup = unicode(markup) + self.unicode_markup = str(markup) self.original_encoding = None return @@ -438,7 +438,7 @@ class UnicodeDammit: def _to_unicode(self, data, encoding, errors="strict"): '''Given a string and its encoding, decodes the string into Unicode. %encoding is a string recognized by encodings.aliases''' - return unicode(data, encoding, errors) + return str(data, encoding, errors) @property def declared_html_encoding(self): @@ -736,7 +736,7 @@ class UnicodeDammit: 0xde : b'\xc3\x9e', # Þ 0xdf : b'\xc3\x9f', # ß 0xe0 : b'\xc3\xa0', # à - 0xe1 : b'\xa1', # á + 0xe1 : b'\xa1', # á 0xe2 : b'\xc3\xa2', # â 0xe3 : b'\xc3\xa3', # ã 0xe4 : b'\xc3\xa4', # ä diff --git a/libs/bs4/diagnose.py b/libs/bs4/diagnose.py index 8768332f..f97c7799 100644 --- a/libs/bs4/diagnose.py +++ b/libs/bs4/diagnose.py @@ -5,8 +5,8 @@ __license__ = "MIT" import cProfile -from StringIO import StringIO -from HTMLParser import HTMLParser +from io import StringIO +from html.parser import HTMLParser import bs4 from bs4 import BeautifulSoup, __version__ from bs4.builder import builder_registry @@ -22,8 +22,8 @@ import cProfile def diagnose(data): """Diagnostic suite for isolating common problems.""" - print "Diagnostic running on Beautiful Soup %s" % __version__ - print "Python version %s" % sys.version + print("Diagnostic running on Beautiful Soup %s" % __version__) + print("Python version %s" % sys.version) basic_parsers = ["html.parser", "html5lib", "lxml"] for name in basic_parsers: @@ -32,16 +32,16 @@ def diagnose(data): break else: basic_parsers.remove(name) - print ( + print(( "I noticed that %s is not installed. Installing it may help." % - name) + name)) if 'lxml' in basic_parsers: - basic_parsers.append(["lxml", "xml"]) + basic_parsers.append("lxml-xml") try: from lxml import etree - print "Found lxml version %s" % ".".join(map(str,etree.LXML_VERSION)) - except ImportError, e: + print("Found lxml version %s" % ".".join(map(str,etree.LXML_VERSION))) + except ImportError as e: print ( "lxml is not installed or couldn't be imported.") @@ -49,37 +49,43 @@ def diagnose(data): if 'html5lib' in basic_parsers: try: import html5lib - print "Found html5lib version %s" % html5lib.__version__ - except ImportError, e: + print("Found html5lib version %s" % html5lib.__version__) + except ImportError as e: print ( "html5lib is not installed or couldn't be imported.") if hasattr(data, 'read'): data = data.read() - elif os.path.exists(data): - print '"%s" looks like a filename. Reading data from the file.' % data - with open(data) as fp: - data = fp.read() elif data.startswith("http:") or data.startswith("https:"): - print '"%s" looks like a URL. Beautiful Soup is not an HTTP client.' % data - print "You need to use some other library to get the document behind the URL, and feed that document to Beautiful Soup." + print('"%s" looks like a URL. Beautiful Soup is not an HTTP client.' % data) + print("You need to use some other library to get the document behind the URL, and feed that document to Beautiful Soup.") return - print + else: + try: + if os.path.exists(data): + print('"%s" looks like a filename. Reading data from the file.' % data) + with open(data) as fp: + data = fp.read() + except ValueError: + # This can happen on some platforms when the 'filename' is + # too long. Assume it's data and not a filename. + pass + print() for parser in basic_parsers: - print "Trying to parse your markup with %s" % parser + print("Trying to parse your markup with %s" % parser) success = False try: - soup = BeautifulSoup(data, parser) + soup = BeautifulSoup(data, features=parser) success = True - except Exception, e: - print "%s could not parse the markup." % parser + except Exception as e: + print("%s could not parse the markup." % parser) traceback.print_exc() if success: - print "Here's what %s did with the markup:" % parser - print soup.prettify() + print("Here's what %s did with the markup:" % parser) + print(soup.prettify()) - print "-" * 80 + print("-" * 80) def lxml_trace(data, html=True, **kwargs): """Print out the lxml events that occur during parsing. @@ -89,7 +95,7 @@ def lxml_trace(data, html=True, **kwargs): """ from lxml import etree for event, element in etree.iterparse(StringIO(data), html=html, **kwargs): - print("%s, %4s, %s" % (event, element.tag, element.text)) + print(("%s, %4s, %s" % (event, element.tag, element.text))) class AnnouncingParser(HTMLParser): """Announces HTMLParser parse events, without doing anything else.""" @@ -171,9 +177,9 @@ def rdoc(num_elements=1000): def benchmark_parsers(num_elements=100000): """Very basic head-to-head performance benchmark.""" - print "Comparative parser benchmark on Beautiful Soup %s" % __version__ + print("Comparative parser benchmark on Beautiful Soup %s" % __version__) data = rdoc(num_elements) - print "Generated a large invalid HTML document (%d bytes)." % len(data) + print("Generated a large invalid HTML document (%d bytes)." % len(data)) for parser in ["lxml", ["lxml", "html"], "html5lib", "html.parser"]: success = False @@ -182,24 +188,24 @@ def benchmark_parsers(num_elements=100000): soup = BeautifulSoup(data, parser) b = time.time() success = True - except Exception, e: - print "%s could not parse the markup." % parser + except Exception as e: + print("%s could not parse the markup." % parser) traceback.print_exc() if success: - print "BS4+%s parsed the markup in %.2fs." % (parser, b-a) + print("BS4+%s parsed the markup in %.2fs." % (parser, b-a)) from lxml import etree a = time.time() etree.HTML(data) b = time.time() - print "Raw lxml parsed the markup in %.2fs." % (b-a) + print("Raw lxml parsed the markup in %.2fs." % (b-a)) import html5lib parser = html5lib.HTMLParser() a = time.time() parser.parse(data) b = time.time() - print "Raw html5lib parsed the markup in %.2fs." % (b-a) + print("Raw html5lib parsed the markup in %.2fs." % (b-a)) def profile(num_elements=100000, parser="lxml"): diff --git a/libs/bs4/element.py b/libs/bs4/element.py index b100d18b..d9389052 100644 --- a/libs/bs4/element.py +++ b/libs/bs4/element.py @@ -2,7 +2,10 @@ # found in the LICENSE file. __license__ = "MIT" -import collections +try: + from collections.abc import Callable # Python 3.6 +except ImportError as e: + from collections import Callable import re import shlex import sys @@ -12,7 +15,7 @@ from bs4.dammit import EntitySubstitution DEFAULT_OUTPUT_ENCODING = "utf-8" PY3K = (sys.version_info[0] > 2) -whitespace_re = re.compile("\s+") +whitespace_re = re.compile(r"\s+") def _alias(attr): """Alias one attribute name to another for backward compatibility""" @@ -26,22 +29,22 @@ def _alias(attr): return alias -class NamespacedAttribute(unicode): +class NamespacedAttribute(str): def __new__(cls, prefix, name, namespace=None): if name is None: - obj = unicode.__new__(cls, prefix) + obj = str.__new__(cls, prefix) elif prefix is None: # Not really namespaced. - obj = unicode.__new__(cls, name) + obj = str.__new__(cls, name) else: - obj = unicode.__new__(cls, prefix + ":" + name) + obj = str.__new__(cls, prefix + ":" + name) obj.prefix = prefix obj.name = name obj.namespace = namespace return obj -class AttributeValueWithCharsetSubstitution(unicode): +class AttributeValueWithCharsetSubstitution(str): """A stand-in object for a character encoding specified in HTML.""" class CharsetMetaAttributeValue(AttributeValueWithCharsetSubstitution): @@ -52,7 +55,7 @@ class CharsetMetaAttributeValue(AttributeValueWithCharsetSubstitution): """ def __new__(cls, original_value): - obj = unicode.__new__(cls, original_value) + obj = str.__new__(cls, original_value) obj.original_value = original_value return obj @@ -69,15 +72,15 @@ class ContentMetaAttributeValue(AttributeValueWithCharsetSubstitution): The value of the 'content' attribute will be one of these objects. """ - CHARSET_RE = re.compile("((^|;)\s*charset=)([^;]*)", re.M) + CHARSET_RE = re.compile(r"((^|;)\s*charset=)([^;]*)", re.M) def __new__(cls, original_value): match = cls.CHARSET_RE.search(original_value) if match is None: # No substitution necessary. - return unicode.__new__(unicode, original_value) + return str.__new__(str, original_value) - obj = unicode.__new__(cls, original_value) + obj = str.__new__(cls, original_value) obj.original_value = original_value return obj @@ -123,6 +126,41 @@ class HTMLAwareEntitySubstitution(EntitySubstitution): return cls._substitute_if_appropriate( ns, EntitySubstitution.substitute_xml) +class Formatter(object): + """Contains information about how to format a parse tree.""" + + # By default, represent void elements as rather than + void_element_close_prefix = '/' + + def substitute_entities(self, *args, **kwargs): + """Transform certain characters into named entities.""" + raise NotImplementedError() + +class HTMLFormatter(Formatter): + """The default HTML formatter.""" + def substitute(self, *args, **kwargs): + return HTMLAwareEntitySubstitution.substitute_html(*args, **kwargs) + +class MinimalHTMLFormatter(Formatter): + """A minimal HTML formatter.""" + def substitute(self, *args, **kwargs): + return HTMLAwareEntitySubstitution.substitute_xml(*args, **kwargs) + +class HTML5Formatter(HTMLFormatter): + """An HTML formatter that omits the slash in a void tag.""" + void_element_close_prefix = None + +class XMLFormatter(Formatter): + """Substitute only the essential XML entities.""" + def substitute(self, *args, **kwargs): + return EntitySubstitution.substitute_xml(*args, **kwargs) + +class HTMLXMLFormatter(Formatter): + """Format XML using HTML rules.""" + def substitute(self, *args, **kwargs): + return HTMLAwareEntitySubstitution.substitute_html(*args, **kwargs) + + class PageElement(object): """Contains the navigational information for some part of the page (either a tag or a piece of text)""" @@ -132,39 +170,48 @@ class PageElement(object): # # "html" - All Unicode characters with corresponding HTML entities # are converted to those entities on output. + # "html5" - The same as "html", but empty void tags are represented as + # rather than # "minimal" - Bare ampersands and angle brackets are converted to # XML entities: & < > # None - The null formatter. Unicode characters are never # converted to entities. This is not recommended, but it's # faster than "minimal". - # A function - This function will be called on every string that + # A callable function - it will be called on every string that needs to undergo entity substitution. + # A Formatter instance - Formatter.substitute(string) will be called on every string that # needs to undergo entity substitution. # - # In an HTML document, the default "html" and "minimal" functions - # will leave the contents of """) [soup.script.extract() for i in soup.find_all("script")] - self.assertEqual("\n\n\n", unicode(soup.body)) + self.assertEqual("\n\n\n", str(soup.body)) def test_extract_works_when_element_is_surrounded_by_identical_strings(self): @@ -1184,7 +1206,7 @@ class TestElementObjects(SoupTest): tag = soup.bTag self.assertEqual(soup.b, tag) self.assertEqual( - '.bTag is deprecated, use .find("b") instead.', + '.bTag is deprecated, use .find("b") instead. If you really were looking for a tag called bTag, use .find("bTag")', str(w[0].message)) def test_has_attr(self): @@ -1284,6 +1306,10 @@ class TestCDAtaListAttributes(SoupTest): soup = self.soup("") self.assertEqual(b'', soup.a.encode()) + def test_get_attribute_list(self): + soup = self.soup("") + self.assertEqual(['abc def'], soup.a.get_attribute_list('id')) + def test_accept_charset(self): soup = self.soup('
') self.assertEqual(['ISO-8859-1', 'UTF-8'], soup.form['accept-charset']) @@ -1343,19 +1369,19 @@ class TestPersistence(SoupTest): soup = BeautifulSoup(b'

 

', 'html.parser') encoding = soup.original_encoding copy = soup.__copy__() - self.assertEqual(u"

 

", unicode(copy)) + self.assertEqual("

 

", str(copy)) self.assertEqual(encoding, copy.original_encoding) def test_unicode_pickle(self): # A tree containing Unicode characters can be pickled. - html = u"\N{SNOWMAN}" + html = "\N{SNOWMAN}" soup = self.soup(html) dumped = pickle.dumps(soup, pickle.HIGHEST_PROTOCOL) loaded = pickle.loads(dumped) self.assertEqual(loaded.decode(), soup.decode()) def test_copy_navigablestring_is_not_attached_to_tree(self): - html = u"Foo
Bar" + html = "FooBar" soup = self.soup(html) s1 = soup.find(string="Foo") s2 = copy.copy(s1) @@ -1367,7 +1393,7 @@ class TestPersistence(SoupTest): self.assertEqual(None, s2.previous_element) def test_copy_navigablestring_subclass_has_same_type(self): - html = u"" + html = "" soup = self.soup(html) s1 = soup.string s2 = copy.copy(s1) @@ -1375,19 +1401,19 @@ class TestPersistence(SoupTest): self.assertTrue(isinstance(s2, Comment)) def test_copy_entire_soup(self): - html = u"
FooBar
end" + html = "
FooBar
end" soup = self.soup(html) soup_copy = copy.copy(soup) self.assertEqual(soup, soup_copy) def test_copy_tag_copies_contents(self): - html = u"
FooBar
end" + html = "
FooBar
end" soup = self.soup(html) div = soup.div div_copy = copy.copy(div) # The two tags look the same, and evaluate to equal. - self.assertEqual(unicode(div), unicode(div_copy)) + self.assertEqual(str(div), str(div_copy)) self.assertEqual(div, div_copy) # But they're not the same object. @@ -1403,67 +1429,75 @@ class TestPersistence(SoupTest): class TestSubstitutions(SoupTest): def test_default_formatter_is_minimal(self): - markup = u"<<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>>" + markup = "<<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>>" soup = self.soup(markup) decoded = soup.decode(formatter="minimal") # The < is converted back into < but the e-with-acute is left alone. self.assertEqual( decoded, self.document_for( - u"<<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>>")) + "<<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>>")) def test_formatter_html(self): - markup = u"<<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>>" + markup = "
<<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>>" soup = self.soup(markup) decoded = soup.decode(formatter="html") self.assertEqual( decoded, - self.document_for("<<Sacré bleu!>>")) + self.document_for("
<<Sacré bleu!>>")) + def test_formatter_html5(self): + markup = "
<<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>>" + soup = self.soup(markup) + decoded = soup.decode(formatter="html5") + self.assertEqual( + decoded, + self.document_for("
<<Sacré bleu!>>")) + def test_formatter_minimal(self): - markup = u"<<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>>" + markup = "<<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>>" soup = self.soup(markup) decoded = soup.decode(formatter="minimal") # The < is converted back into < but the e-with-acute is left alone. self.assertEqual( decoded, self.document_for( - u"<<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>>")) + "<<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>>")) def test_formatter_null(self): - markup = u"<<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>>" + markup = "<<Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>>" soup = self.soup(markup) decoded = soup.decode(formatter=None) # Neither the angle brackets nor the e-with-acute are converted. # This is not valid HTML, but it's what the user wanted. self.assertEqual(decoded, - self.document_for(u"<>")) + self.document_for("<>")) def test_formatter_custom(self): - markup = u"<foo>bar" + markup = "<foo>bar
" soup = self.soup(markup) decoded = soup.decode(formatter = lambda x: x.upper()) # Instead of normal entity conversion code, the custom # callable is called on every string. self.assertEqual( decoded, - self.document_for(u"BAR")) + self.document_for("BAR
")) def test_formatter_is_run_on_attribute_values(self): - markup = u'e' + markup = 'e' soup = self.soup(markup) a = soup.a - expect_minimal = u'e' + expect_minimal = 'e' self.assertEqual(expect_minimal, a.decode()) self.assertEqual(expect_minimal, a.decode(formatter="minimal")) - expect_html = u'e' + expect_html = 'e' self.assertEqual(expect_html, a.decode(formatter="html")) self.assertEqual(markup, a.decode(formatter=None)) - expect_upper = u'E' + expect_upper = 'E' self.assertEqual(expect_upper, a.decode(formatter=lambda x: x.upper())) def test_formatter_skips_script_tag_for_html_documents(self): @@ -1489,24 +1523,24 @@ class TestSubstitutions(SoupTest): # Everything outside the
 tag is reformatted, but everything
         # inside is left alone.
         self.assertEqual(
-            u'
\n foo\n
  \tbar\n  \n  
\n baz\n
', + '
\n foo\n
  \tbar\n  \n  
\n baz\n
', soup.div.prettify()) - def test_prettify_accepts_formatter(self): + def test_prettify_accepts_formatter_function(self): soup = BeautifulSoup("foo", 'html.parser') pretty = soup.prettify(formatter = lambda x: x.upper()) self.assertTrue("FOO" in pretty) def test_prettify_outputs_unicode_by_default(self): soup = self.soup("") - self.assertEqual(unicode, type(soup.prettify())) + self.assertEqual(str, type(soup.prettify())) def test_prettify_can_encode_data(self): soup = self.soup("") self.assertEqual(bytes, type(soup.prettify("utf-8"))) def test_html_entity_substitution_off_by_default(self): - markup = u"Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!" + markup = "Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!" soup = self.soup(markup) encoded = soup.b.encode("utf-8") self.assertEqual(encoded, markup.encode('utf-8')) @@ -1550,48 +1584,48 @@ class TestEncoding(SoupTest): """Test the ability to encode objects into strings.""" def test_unicode_string_can_be_encoded(self): - html = u"\N{SNOWMAN}" + html = "\N{SNOWMAN}" soup = self.soup(html) self.assertEqual(soup.b.string.encode("utf-8"), - u"\N{SNOWMAN}".encode("utf-8")) + "\N{SNOWMAN}".encode("utf-8")) def test_tag_containing_unicode_string_can_be_encoded(self): - html = u"\N{SNOWMAN}" + html = "\N{SNOWMAN}" soup = self.soup(html) self.assertEqual( soup.b.encode("utf-8"), html.encode("utf-8")) def test_encoding_substitutes_unrecognized_characters_by_default(self): - html = u"\N{SNOWMAN}" + html = "\N{SNOWMAN}" soup = self.soup(html) self.assertEqual(soup.b.encode("ascii"), b"") def test_encoding_can_be_made_strict(self): - html = u"\N{SNOWMAN}" + html = "\N{SNOWMAN}" soup = self.soup(html) self.assertRaises( UnicodeEncodeError, soup.encode, "ascii", errors="strict") def test_decode_contents(self): - html = u"\N{SNOWMAN}" + html = "\N{SNOWMAN}" soup = self.soup(html) - self.assertEqual(u"\N{SNOWMAN}", soup.b.decode_contents()) + self.assertEqual("\N{SNOWMAN}", soup.b.decode_contents()) def test_encode_contents(self): - html = u"\N{SNOWMAN}" + html = "\N{SNOWMAN}" soup = self.soup(html) self.assertEqual( - u"\N{SNOWMAN}".encode("utf8"), soup.b.encode_contents( + "\N{SNOWMAN}".encode("utf8"), soup.b.encode_contents( encoding="utf8")) def test_deprecated_renderContents(self): - html = u"\N{SNOWMAN}" + html = "\N{SNOWMAN}" soup = self.soup(html) self.assertEqual( - u"\N{SNOWMAN}".encode("utf8"), soup.b.renderContents()) + "\N{SNOWMAN}".encode("utf8"), soup.b.renderContents()) def test_repr(self): - html = u"\N{SNOWMAN}" + html = "\N{SNOWMAN}" soup = self.soup(html) if PY3K: self.assertEqual(html, repr(soup)) @@ -1714,7 +1748,7 @@ class TestSoupSelector(TreeTest): els = self.soup.select('title') self.assertEqual(len(els), 1) self.assertEqual(els[0].name, 'title') - self.assertEqual(els[0].contents, [u'The title']) + self.assertEqual(els[0].contents, ['The title']) def test_one_tag_many(self): els = self.soup.select('div') @@ -1760,7 +1794,7 @@ class TestSoupSelector(TreeTest): self.assertEqual(dashed[0]['id'], 'dash2') def test_dashed_tag_text(self): - self.assertEqual(self.soup.select('body > custom-dashed-tag')[0].text, u'Hello there.') + self.assertEqual(self.soup.select('body > custom-dashed-tag')[0].text, 'Hello there.') def test_select_dashed_matches_find_all(self): self.assertEqual(self.soup.select('custom-dashed-tag'), self.soup.find_all('custom-dashed-tag')) @@ -1947,12 +1981,12 @@ class TestSoupSelector(TreeTest): # Try to select first paragraph els = self.soup.select('div#inner p:nth-of-type(1)') self.assertEqual(len(els), 1) - self.assertEqual(els[0].string, u'Some text') + self.assertEqual(els[0].string, 'Some text') # Try to select third paragraph els = self.soup.select('div#inner p:nth-of-type(3)') self.assertEqual(len(els), 1) - self.assertEqual(els[0].string, u'Another') + self.assertEqual(els[0].string, 'Another') # Try to select (non-existent!) fourth paragraph els = self.soup.select('div#inner p:nth-of-type(4)') @@ -1965,7 +1999,7 @@ class TestSoupSelector(TreeTest): def test_nth_of_type_direct_descendant(self): els = self.soup.select('div#inner > p:nth-of-type(1)') self.assertEqual(len(els), 1) - self.assertEqual(els[0].string, u'Some text') + self.assertEqual(els[0].string, 'Some text') def test_id_child_selector_nth_of_type(self): self.assertSelects('#inner > p:nth-of-type(2)', ['p1']) @@ -2040,5 +2074,17 @@ class TestSoupSelector(TreeTest): def test_multiple_select_nested(self): self.assertSelects('body > div > x, y > z', ['xid', 'zidb']) + def test_select_duplicate_elements(self): + # When markup contains duplicate elements, a multiple select + # will find all of them. + markup = '
' + soup = BeautifulSoup(markup, 'html.parser') + selected = soup.select(".c1, .c2") + self.assertEqual(3, len(selected)) + # Verify that find_all finds the same elements, though because + # of an implementation detail it finds them in a different + # order. + for element in soup.find_all(class_=['c1', 'c2']): + assert element in selected diff --git a/libs/click/__init__.py b/libs/click/__init__.py new file mode 100644 index 00000000..d3c33660 --- /dev/null +++ b/libs/click/__init__.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- +""" +click +~~~~~ + +Click is a simple Python module inspired by the stdlib optparse to make +writing command line scripts fun. Unlike other modules, it's based +around a simple API that does not come with too much magic and is +composable. + +:copyright: © 2014 by the Pallets team. +:license: BSD, see LICENSE.rst for more details. +""" + +# Core classes +from .core import Context, BaseCommand, Command, MultiCommand, Group, \ + CommandCollection, Parameter, Option, Argument + +# Globals +from .globals import get_current_context + +# Decorators +from .decorators import pass_context, pass_obj, make_pass_decorator, \ + command, group, argument, option, confirmation_option, \ + password_option, version_option, help_option + +# Types +from .types import ParamType, File, Path, Choice, IntRange, Tuple, \ + DateTime, STRING, INT, FLOAT, BOOL, UUID, UNPROCESSED, FloatRange + +# Utilities +from .utils import echo, get_binary_stream, get_text_stream, open_file, \ + format_filename, get_app_dir, get_os_args + +# Terminal functions +from .termui import prompt, confirm, get_terminal_size, echo_via_pager, \ + progressbar, clear, style, unstyle, secho, edit, launch, getchar, \ + pause + +# Exceptions +from .exceptions import ClickException, UsageError, BadParameter, \ + FileError, Abort, NoSuchOption, BadOptionUsage, BadArgumentUsage, \ + MissingParameter + +# Formatting +from .formatting import HelpFormatter, wrap_text + +# Parsing +from .parser import OptionParser + + +__all__ = [ + # Core classes + 'Context', 'BaseCommand', 'Command', 'MultiCommand', 'Group', + 'CommandCollection', 'Parameter', 'Option', 'Argument', + + # Globals + 'get_current_context', + + # Decorators + 'pass_context', 'pass_obj', 'make_pass_decorator', 'command', 'group', + 'argument', 'option', 'confirmation_option', 'password_option', + 'version_option', 'help_option', + + # Types + 'ParamType', 'File', 'Path', 'Choice', 'IntRange', 'Tuple', + 'DateTime', 'STRING', 'INT', 'FLOAT', 'BOOL', 'UUID', 'UNPROCESSED', + 'FloatRange', + + # Utilities + 'echo', 'get_binary_stream', 'get_text_stream', 'open_file', + 'format_filename', 'get_app_dir', 'get_os_args', + + # Terminal functions + 'prompt', 'confirm', 'get_terminal_size', 'echo_via_pager', + 'progressbar', 'clear', 'style', 'unstyle', 'secho', 'edit', 'launch', + 'getchar', 'pause', + + # Exceptions + 'ClickException', 'UsageError', 'BadParameter', 'FileError', + 'Abort', 'NoSuchOption', 'BadOptionUsage', 'BadArgumentUsage', + 'MissingParameter', + + # Formatting + 'HelpFormatter', 'wrap_text', + + # Parsing + 'OptionParser', +] + + +# Controls if click should emit the warning about the use of unicode +# literals. +disable_unicode_literals_warning = False + + +__version__ = '7.0' diff --git a/libs/click/_bashcomplete.py b/libs/click/_bashcomplete.py new file mode 100644 index 00000000..a5f1084c --- /dev/null +++ b/libs/click/_bashcomplete.py @@ -0,0 +1,293 @@ +import copy +import os +import re + +from .utils import echo +from .parser import split_arg_string +from .core import MultiCommand, Option, Argument +from .types import Choice + +try: + from collections import abc +except ImportError: + import collections as abc + +WORDBREAK = '=' + +# Note, only BASH version 4.4 and later have the nosort option. +COMPLETION_SCRIPT_BASH = ''' +%(complete_func)s() { + local IFS=$'\n' + COMPREPLY=( $( env COMP_WORDS="${COMP_WORDS[*]}" \\ + COMP_CWORD=$COMP_CWORD \\ + %(autocomplete_var)s=complete $1 ) ) + return 0 +} + +%(complete_func)setup() { + local COMPLETION_OPTIONS="" + local BASH_VERSION_ARR=(${BASH_VERSION//./ }) + # Only BASH version 4.4 and later have the nosort option. + if [ ${BASH_VERSION_ARR[0]} -gt 4 ] || ([ ${BASH_VERSION_ARR[0]} -eq 4 ] && [ ${BASH_VERSION_ARR[1]} -ge 4 ]); then + COMPLETION_OPTIONS="-o nosort" + fi + + complete $COMPLETION_OPTIONS -F %(complete_func)s %(script_names)s +} + +%(complete_func)setup +''' + +COMPLETION_SCRIPT_ZSH = ''' +%(complete_func)s() { + local -a completions + local -a completions_with_descriptions + local -a response + response=("${(@f)$( env COMP_WORDS=\"${words[*]}\" \\ + COMP_CWORD=$((CURRENT-1)) \\ + %(autocomplete_var)s=\"complete_zsh\" \\ + %(script_names)s )}") + + for key descr in ${(kv)response}; do + if [[ "$descr" == "_" ]]; then + completions+=("$key") + else + completions_with_descriptions+=("$key":"$descr") + fi + done + + if [ -n "$completions_with_descriptions" ]; then + _describe -V unsorted completions_with_descriptions -U -Q + fi + + if [ -n "$completions" ]; then + compadd -U -V unsorted -Q -a completions + fi + compstate[insert]="automenu" +} + +compdef %(complete_func)s %(script_names)s +''' + +_invalid_ident_char_re = re.compile(r'[^a-zA-Z0-9_]') + + +def get_completion_script(prog_name, complete_var, shell): + cf_name = _invalid_ident_char_re.sub('', prog_name.replace('-', '_')) + script = COMPLETION_SCRIPT_ZSH if shell == 'zsh' else COMPLETION_SCRIPT_BASH + return (script % { + 'complete_func': '_%s_completion' % cf_name, + 'script_names': prog_name, + 'autocomplete_var': complete_var, + }).strip() + ';' + + +def resolve_ctx(cli, prog_name, args): + """ + Parse into a hierarchy of contexts. Contexts are connected through the parent variable. + :param cli: command definition + :param prog_name: the program that is running + :param args: full list of args + :return: the final context/command parsed + """ + ctx = cli.make_context(prog_name, args, resilient_parsing=True) + args = ctx.protected_args + ctx.args + while args: + if isinstance(ctx.command, MultiCommand): + if not ctx.command.chain: + cmd_name, cmd, args = ctx.command.resolve_command(ctx, args) + if cmd is None: + return ctx + ctx = cmd.make_context(cmd_name, args, parent=ctx, + resilient_parsing=True) + args = ctx.protected_args + ctx.args + else: + # Walk chained subcommand contexts saving the last one. + while args: + cmd_name, cmd, args = ctx.command.resolve_command(ctx, args) + if cmd is None: + return ctx + sub_ctx = cmd.make_context(cmd_name, args, parent=ctx, + allow_extra_args=True, + allow_interspersed_args=False, + resilient_parsing=True) + args = sub_ctx.args + ctx = sub_ctx + args = sub_ctx.protected_args + sub_ctx.args + else: + break + return ctx + + +def start_of_option(param_str): + """ + :param param_str: param_str to check + :return: whether or not this is the start of an option declaration (i.e. starts "-" or "--") + """ + return param_str and param_str[:1] == '-' + + +def is_incomplete_option(all_args, cmd_param): + """ + :param all_args: the full original list of args supplied + :param cmd_param: the current command paramter + :return: whether or not the last option declaration (i.e. starts "-" or "--") is incomplete and + corresponds to this cmd_param. In other words whether this cmd_param option can still accept + values + """ + if not isinstance(cmd_param, Option): + return False + if cmd_param.is_flag: + return False + last_option = None + for index, arg_str in enumerate(reversed([arg for arg in all_args if arg != WORDBREAK])): + if index + 1 > cmd_param.nargs: + break + if start_of_option(arg_str): + last_option = arg_str + + return True if last_option and last_option in cmd_param.opts else False + + +def is_incomplete_argument(current_params, cmd_param): + """ + :param current_params: the current params and values for this argument as already entered + :param cmd_param: the current command parameter + :return: whether or not the last argument is incomplete and corresponds to this cmd_param. In + other words whether or not the this cmd_param argument can still accept values + """ + if not isinstance(cmd_param, Argument): + return False + current_param_values = current_params[cmd_param.name] + if current_param_values is None: + return True + if cmd_param.nargs == -1: + return True + if isinstance(current_param_values, abc.Iterable) \ + and cmd_param.nargs > 1 and len(current_param_values) < cmd_param.nargs: + return True + return False + + +def get_user_autocompletions(ctx, args, incomplete, cmd_param): + """ + :param ctx: context associated with the parsed command + :param args: full list of args + :param incomplete: the incomplete text to autocomplete + :param cmd_param: command definition + :return: all the possible user-specified completions for the param + """ + results = [] + if isinstance(cmd_param.type, Choice): + # Choices don't support descriptions. + results = [(c, None) + for c in cmd_param.type.choices if str(c).startswith(incomplete)] + elif cmd_param.autocompletion is not None: + dynamic_completions = cmd_param.autocompletion(ctx=ctx, + args=args, + incomplete=incomplete) + results = [c if isinstance(c, tuple) else (c, None) + for c in dynamic_completions] + return results + + +def get_visible_commands_starting_with(ctx, starts_with): + """ + :param ctx: context associated with the parsed command + :starts_with: string that visible commands must start with. + :return: all visible (not hidden) commands that start with starts_with. + """ + for c in ctx.command.list_commands(ctx): + if c.startswith(starts_with): + command = ctx.command.get_command(ctx, c) + if not command.hidden: + yield command + + +def add_subcommand_completions(ctx, incomplete, completions_out): + # Add subcommand completions. + if isinstance(ctx.command, MultiCommand): + completions_out.extend( + [(c.name, c.get_short_help_str()) for c in get_visible_commands_starting_with(ctx, incomplete)]) + + # Walk up the context list and add any other completion possibilities from chained commands + while ctx.parent is not None: + ctx = ctx.parent + if isinstance(ctx.command, MultiCommand) and ctx.command.chain: + remaining_commands = [c for c in get_visible_commands_starting_with(ctx, incomplete) + if c.name not in ctx.protected_args] + completions_out.extend([(c.name, c.get_short_help_str()) for c in remaining_commands]) + + +def get_choices(cli, prog_name, args, incomplete): + """ + :param cli: command definition + :param prog_name: the program that is running + :param args: full list of args + :param incomplete: the incomplete text to autocomplete + :return: all the possible completions for the incomplete + """ + all_args = copy.deepcopy(args) + + ctx = resolve_ctx(cli, prog_name, args) + if ctx is None: + return [] + + # In newer versions of bash long opts with '='s are partitioned, but it's easier to parse + # without the '=' + if start_of_option(incomplete) and WORDBREAK in incomplete: + partition_incomplete = incomplete.partition(WORDBREAK) + all_args.append(partition_incomplete[0]) + incomplete = partition_incomplete[2] + elif incomplete == WORDBREAK: + incomplete = '' + + completions = [] + if start_of_option(incomplete): + # completions for partial options + for param in ctx.command.params: + if isinstance(param, Option) and not param.hidden: + param_opts = [param_opt for param_opt in param.opts + + param.secondary_opts if param_opt not in all_args or param.multiple] + completions.extend([(o, param.help) for o in param_opts if o.startswith(incomplete)]) + return completions + # completion for option values from user supplied values + for param in ctx.command.params: + if is_incomplete_option(all_args, param): + return get_user_autocompletions(ctx, all_args, incomplete, param) + # completion for argument values from user supplied values + for param in ctx.command.params: + if is_incomplete_argument(ctx.params, param): + return get_user_autocompletions(ctx, all_args, incomplete, param) + + add_subcommand_completions(ctx, incomplete, completions) + # Sort before returning so that proper ordering can be enforced in custom types. + return sorted(completions) + + +def do_complete(cli, prog_name, include_descriptions): + cwords = split_arg_string(os.environ['COMP_WORDS']) + cword = int(os.environ['COMP_CWORD']) + args = cwords[1:cword] + try: + incomplete = cwords[cword] + except IndexError: + incomplete = '' + + for item in get_choices(cli, prog_name, args, incomplete): + echo(item[0]) + if include_descriptions: + # ZSH has trouble dealing with empty array parameters when returned from commands, so use a well defined character '_' to indicate no description is present. + echo(item[1] if item[1] else '_') + + return True + + +def bashcomplete(cli, prog_name, complete_var, complete_instr): + if complete_instr.startswith('source'): + shell = 'zsh' if complete_instr == 'source_zsh' else 'bash' + echo(get_completion_script(prog_name, complete_var, shell)) + return True + elif complete_instr == 'complete' or complete_instr == 'complete_zsh': + return do_complete(cli, prog_name, complete_instr == 'complete_zsh') + return False diff --git a/libs/click/_compat.py b/libs/click/_compat.py new file mode 100644 index 00000000..937e2301 --- /dev/null +++ b/libs/click/_compat.py @@ -0,0 +1,703 @@ +import re +import io +import os +import sys +import codecs +from weakref import WeakKeyDictionary + + +PY2 = sys.version_info[0] == 2 +CYGWIN = sys.platform.startswith('cygwin') +# Determine local App Engine environment, per Google's own suggestion +APP_ENGINE = ('APPENGINE_RUNTIME' in os.environ and + 'Development/' in os.environ['SERVER_SOFTWARE']) +WIN = sys.platform.startswith('win') and not APP_ENGINE +DEFAULT_COLUMNS = 80 + + +_ansi_re = re.compile(r'\033\[((?:\d|;)*)([a-zA-Z])') + + +def get_filesystem_encoding(): + return sys.getfilesystemencoding() or sys.getdefaultencoding() + + +def _make_text_stream(stream, encoding, errors, + force_readable=False, force_writable=False): + if encoding is None: + encoding = get_best_encoding(stream) + if errors is None: + errors = 'replace' + return _NonClosingTextIOWrapper(stream, encoding, errors, + line_buffering=True, + force_readable=force_readable, + force_writable=force_writable) + + +def is_ascii_encoding(encoding): + """Checks if a given encoding is ascii.""" + try: + return codecs.lookup(encoding).name == 'ascii' + except LookupError: + return False + + +def get_best_encoding(stream): + """Returns the default stream encoding if not found.""" + rv = getattr(stream, 'encoding', None) or sys.getdefaultencoding() + if is_ascii_encoding(rv): + return 'utf-8' + return rv + + +class _NonClosingTextIOWrapper(io.TextIOWrapper): + + def __init__(self, stream, encoding, errors, + force_readable=False, force_writable=False, **extra): + self._stream = stream = _FixupStream(stream, force_readable, + force_writable) + io.TextIOWrapper.__init__(self, stream, encoding, errors, **extra) + + # The io module is a place where the Python 3 text behavior + # was forced upon Python 2, so we need to unbreak + # it to look like Python 2. + if PY2: + def write(self, x): + if isinstance(x, str) or is_bytes(x): + try: + self.flush() + except Exception: + pass + return self.buffer.write(str(x)) + return io.TextIOWrapper.write(self, x) + + def writelines(self, lines): + for line in lines: + self.write(line) + + def __del__(self): + try: + self.detach() + except Exception: + pass + + def isatty(self): + # https://bitbucket.org/pypy/pypy/issue/1803 + return self._stream.isatty() + + +class _FixupStream(object): + """The new io interface needs more from streams than streams + traditionally implement. As such, this fix-up code is necessary in + some circumstances. + + The forcing of readable and writable flags are there because some tools + put badly patched objects on sys (one such offender are certain version + of jupyter notebook). + """ + + def __init__(self, stream, force_readable=False, force_writable=False): + self._stream = stream + self._force_readable = force_readable + self._force_writable = force_writable + + def __getattr__(self, name): + return getattr(self._stream, name) + + def read1(self, size): + f = getattr(self._stream, 'read1', None) + if f is not None: + return f(size) + # We only dispatch to readline instead of read in Python 2 as we + # do not want cause problems with the different implementation + # of line buffering. + if PY2: + return self._stream.readline(size) + return self._stream.read(size) + + def readable(self): + if self._force_readable: + return True + x = getattr(self._stream, 'readable', None) + if x is not None: + return x() + try: + self._stream.read(0) + except Exception: + return False + return True + + def writable(self): + if self._force_writable: + return True + x = getattr(self._stream, 'writable', None) + if x is not None: + return x() + try: + self._stream.write('') + except Exception: + try: + self._stream.write(b'') + except Exception: + return False + return True + + def seekable(self): + x = getattr(self._stream, 'seekable', None) + if x is not None: + return x() + try: + self._stream.seek(self._stream.tell()) + except Exception: + return False + return True + + +if PY2: + text_type = unicode + bytes = str + raw_input = raw_input + string_types = (str, unicode) + int_types = (int, long) + iteritems = lambda x: x.iteritems() + range_type = xrange + + def is_bytes(x): + return isinstance(x, (buffer, bytearray)) + + _identifier_re = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*$') + + # For Windows, we need to force stdout/stdin/stderr to binary if it's + # fetched for that. This obviously is not the most correct way to do + # it as it changes global state. Unfortunately, there does not seem to + # be a clear better way to do it as just reopening the file in binary + # mode does not change anything. + # + # An option would be to do what Python 3 does and to open the file as + # binary only, patch it back to the system, and then use a wrapper + # stream that converts newlines. It's not quite clear what's the + # correct option here. + # + # This code also lives in _winconsole for the fallback to the console + # emulation stream. + # + # There are also Windows environments where the `msvcrt` module is not + # available (which is why we use try-catch instead of the WIN variable + # here), such as the Google App Engine development server on Windows. In + # those cases there is just nothing we can do. + def set_binary_mode(f): + return f + + try: + import msvcrt + except ImportError: + pass + else: + def set_binary_mode(f): + try: + fileno = f.fileno() + except Exception: + pass + else: + msvcrt.setmode(fileno, os.O_BINARY) + return f + + try: + import fcntl + except ImportError: + pass + else: + def set_binary_mode(f): + try: + fileno = f.fileno() + except Exception: + pass + else: + flags = fcntl.fcntl(fileno, fcntl.F_GETFL) + fcntl.fcntl(fileno, fcntl.F_SETFL, flags & ~os.O_NONBLOCK) + return f + + def isidentifier(x): + return _identifier_re.search(x) is not None + + def get_binary_stdin(): + return set_binary_mode(sys.stdin) + + def get_binary_stdout(): + _wrap_std_stream('stdout') + return set_binary_mode(sys.stdout) + + def get_binary_stderr(): + _wrap_std_stream('stderr') + return set_binary_mode(sys.stderr) + + def get_text_stdin(encoding=None, errors=None): + rv = _get_windows_console_stream(sys.stdin, encoding, errors) + if rv is not None: + return rv + return _make_text_stream(sys.stdin, encoding, errors, + force_readable=True) + + def get_text_stdout(encoding=None, errors=None): + _wrap_std_stream('stdout') + rv = _get_windows_console_stream(sys.stdout, encoding, errors) + if rv is not None: + return rv + return _make_text_stream(sys.stdout, encoding, errors, + force_writable=True) + + def get_text_stderr(encoding=None, errors=None): + _wrap_std_stream('stderr') + rv = _get_windows_console_stream(sys.stderr, encoding, errors) + if rv is not None: + return rv + return _make_text_stream(sys.stderr, encoding, errors, + force_writable=True) + + def filename_to_ui(value): + if isinstance(value, bytes): + value = value.decode(get_filesystem_encoding(), 'replace') + return value +else: + import io + text_type = str + raw_input = input + string_types = (str,) + int_types = (int,) + range_type = range + isidentifier = lambda x: x.isidentifier() + iteritems = lambda x: iter(x.items()) + + def is_bytes(x): + return isinstance(x, (bytes, memoryview, bytearray)) + + def _is_binary_reader(stream, default=False): + try: + return isinstance(stream.read(0), bytes) + except Exception: + return default + # This happens in some cases where the stream was already + # closed. In this case, we assume the default. + + def _is_binary_writer(stream, default=False): + try: + stream.write(b'') + except Exception: + try: + stream.write('') + return False + except Exception: + pass + return default + return True + + def _find_binary_reader(stream): + # We need to figure out if the given stream is already binary. + # This can happen because the official docs recommend detaching + # the streams to get binary streams. Some code might do this, so + # we need to deal with this case explicitly. + if _is_binary_reader(stream, False): + return stream + + buf = getattr(stream, 'buffer', None) + + # Same situation here; this time we assume that the buffer is + # actually binary in case it's closed. + if buf is not None and _is_binary_reader(buf, True): + return buf + + def _find_binary_writer(stream): + # We need to figure out if the given stream is already binary. + # This can happen because the official docs recommend detatching + # the streams to get binary streams. Some code might do this, so + # we need to deal with this case explicitly. + if _is_binary_writer(stream, False): + return stream + + buf = getattr(stream, 'buffer', None) + + # Same situation here; this time we assume that the buffer is + # actually binary in case it's closed. + if buf is not None and _is_binary_writer(buf, True): + return buf + + def _stream_is_misconfigured(stream): + """A stream is misconfigured if its encoding is ASCII.""" + # If the stream does not have an encoding set, we assume it's set + # to ASCII. This appears to happen in certain unittest + # environments. It's not quite clear what the correct behavior is + # but this at least will force Click to recover somehow. + return is_ascii_encoding(getattr(stream, 'encoding', None) or 'ascii') + + def _is_compatible_text_stream(stream, encoding, errors): + stream_encoding = getattr(stream, 'encoding', None) + stream_errors = getattr(stream, 'errors', None) + + # Perfect match. + if stream_encoding == encoding and stream_errors == errors: + return True + + # Otherwise, it's only a compatible stream if we did not ask for + # an encoding. + if encoding is None: + return stream_encoding is not None + + return False + + def _force_correct_text_reader(text_reader, encoding, errors, + force_readable=False): + if _is_binary_reader(text_reader, False): + binary_reader = text_reader + else: + # If there is no target encoding set, we need to verify that the + # reader is not actually misconfigured. + if encoding is None and not _stream_is_misconfigured(text_reader): + return text_reader + + if _is_compatible_text_stream(text_reader, encoding, errors): + return text_reader + + # If the reader has no encoding, we try to find the underlying + # binary reader for it. If that fails because the environment is + # misconfigured, we silently go with the same reader because this + # is too common to happen. In that case, mojibake is better than + # exceptions. + binary_reader = _find_binary_reader(text_reader) + if binary_reader is None: + return text_reader + + # At this point, we default the errors to replace instead of strict + # because nobody handles those errors anyways and at this point + # we're so fundamentally fucked that nothing can repair it. + if errors is None: + errors = 'replace' + return _make_text_stream(binary_reader, encoding, errors, + force_readable=force_readable) + + def _force_correct_text_writer(text_writer, encoding, errors, + force_writable=False): + if _is_binary_writer(text_writer, False): + binary_writer = text_writer + else: + # If there is no target encoding set, we need to verify that the + # writer is not actually misconfigured. + if encoding is None and not _stream_is_misconfigured(text_writer): + return text_writer + + if _is_compatible_text_stream(text_writer, encoding, errors): + return text_writer + + # If the writer has no encoding, we try to find the underlying + # binary writer for it. If that fails because the environment is + # misconfigured, we silently go with the same writer because this + # is too common to happen. In that case, mojibake is better than + # exceptions. + binary_writer = _find_binary_writer(text_writer) + if binary_writer is None: + return text_writer + + # At this point, we default the errors to replace instead of strict + # because nobody handles those errors anyways and at this point + # we're so fundamentally fucked that nothing can repair it. + if errors is None: + errors = 'replace' + return _make_text_stream(binary_writer, encoding, errors, + force_writable=force_writable) + + def get_binary_stdin(): + reader = _find_binary_reader(sys.stdin) + if reader is None: + raise RuntimeError('Was not able to determine binary ' + 'stream for sys.stdin.') + return reader + + def get_binary_stdout(): + writer = _find_binary_writer(sys.stdout) + if writer is None: + raise RuntimeError('Was not able to determine binary ' + 'stream for sys.stdout.') + return writer + + def get_binary_stderr(): + writer = _find_binary_writer(sys.stderr) + if writer is None: + raise RuntimeError('Was not able to determine binary ' + 'stream for sys.stderr.') + return writer + + def get_text_stdin(encoding=None, errors=None): + rv = _get_windows_console_stream(sys.stdin, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_reader(sys.stdin, encoding, errors, + force_readable=True) + + def get_text_stdout(encoding=None, errors=None): + rv = _get_windows_console_stream(sys.stdout, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_writer(sys.stdout, encoding, errors, + force_writable=True) + + def get_text_stderr(encoding=None, errors=None): + rv = _get_windows_console_stream(sys.stderr, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_writer(sys.stderr, encoding, errors, + force_writable=True) + + def filename_to_ui(value): + if isinstance(value, bytes): + value = value.decode(get_filesystem_encoding(), 'replace') + else: + value = value.encode('utf-8', 'surrogateescape') \ + .decode('utf-8', 'replace') + return value + + +def get_streerror(e, default=None): + if hasattr(e, 'strerror'): + msg = e.strerror + else: + if default is not None: + msg = default + else: + msg = str(e) + if isinstance(msg, bytes): + msg = msg.decode('utf-8', 'replace') + return msg + + +def open_stream(filename, mode='r', encoding=None, errors='strict', + atomic=False): + # Standard streams first. These are simple because they don't need + # special handling for the atomic flag. It's entirely ignored. + if filename == '-': + if any(m in mode for m in ['w', 'a', 'x']): + if 'b' in mode: + return get_binary_stdout(), False + return get_text_stdout(encoding=encoding, errors=errors), False + if 'b' in mode: + return get_binary_stdin(), False + return get_text_stdin(encoding=encoding, errors=errors), False + + # Non-atomic writes directly go out through the regular open functions. + if not atomic: + if encoding is None: + return open(filename, mode), True + return io.open(filename, mode, encoding=encoding, errors=errors), True + + # Some usability stuff for atomic writes + if 'a' in mode: + raise ValueError( + 'Appending to an existing file is not supported, because that ' + 'would involve an expensive `copy`-operation to a temporary ' + 'file. Open the file in normal `w`-mode and copy explicitly ' + 'if that\'s what you\'re after.' + ) + if 'x' in mode: + raise ValueError('Use the `overwrite`-parameter instead.') + if 'w' not in mode: + raise ValueError('Atomic writes only make sense with `w`-mode.') + + # Atomic writes are more complicated. They work by opening a file + # as a proxy in the same folder and then using the fdopen + # functionality to wrap it in a Python file. Then we wrap it in an + # atomic file that moves the file over on close. + import tempfile + fd, tmp_filename = tempfile.mkstemp(dir=os.path.dirname(filename), + prefix='.__atomic-write') + + if encoding is not None: + f = io.open(fd, mode, encoding=encoding, errors=errors) + else: + f = os.fdopen(fd, mode) + + return _AtomicFile(f, tmp_filename, os.path.realpath(filename)), True + + +# Used in a destructor call, needs extra protection from interpreter cleanup. +if hasattr(os, 'replace'): + _replace = os.replace + _can_replace = True +else: + _replace = os.rename + _can_replace = not WIN + + +class _AtomicFile(object): + + def __init__(self, f, tmp_filename, real_filename): + self._f = f + self._tmp_filename = tmp_filename + self._real_filename = real_filename + self.closed = False + + @property + def name(self): + return self._real_filename + + def close(self, delete=False): + if self.closed: + return + self._f.close() + if not _can_replace: + try: + os.remove(self._real_filename) + except OSError: + pass + _replace(self._tmp_filename, self._real_filename) + self.closed = True + + def __getattr__(self, name): + return getattr(self._f, name) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, tb): + self.close(delete=exc_type is not None) + + def __repr__(self): + return repr(self._f) + + +auto_wrap_for_ansi = None +colorama = None +get_winterm_size = None + + +def strip_ansi(value): + return _ansi_re.sub('', value) + + +def should_strip_ansi(stream=None, color=None): + if color is None: + if stream is None: + stream = sys.stdin + return not isatty(stream) + return not color + + +# If we're on Windows, we provide transparent integration through +# colorama. This will make ANSI colors through the echo function +# work automatically. +if WIN: + # Windows has a smaller terminal + DEFAULT_COLUMNS = 79 + + from ._winconsole import _get_windows_console_stream, _wrap_std_stream + + def _get_argv_encoding(): + import locale + return locale.getpreferredencoding() + + if PY2: + def raw_input(prompt=''): + sys.stderr.flush() + if prompt: + stdout = _default_text_stdout() + stdout.write(prompt) + stdin = _default_text_stdin() + return stdin.readline().rstrip('\r\n') + + try: + import colorama + except ImportError: + pass + else: + _ansi_stream_wrappers = WeakKeyDictionary() + + def auto_wrap_for_ansi(stream, color=None): + """This function wraps a stream so that calls through colorama + are issued to the win32 console API to recolor on demand. It + also ensures to reset the colors if a write call is interrupted + to not destroy the console afterwards. + """ + try: + cached = _ansi_stream_wrappers.get(stream) + except Exception: + cached = None + if cached is not None: + return cached + strip = should_strip_ansi(stream, color) + ansi_wrapper = colorama.AnsiToWin32(stream, strip=strip) + rv = ansi_wrapper.stream + _write = rv.write + + def _safe_write(s): + try: + return _write(s) + except: + ansi_wrapper.reset_all() + raise + + rv.write = _safe_write + try: + _ansi_stream_wrappers[stream] = rv + except Exception: + pass + return rv + + def get_winterm_size(): + win = colorama.win32.GetConsoleScreenBufferInfo( + colorama.win32.STDOUT).srWindow + return win.Right - win.Left, win.Bottom - win.Top +else: + def _get_argv_encoding(): + return getattr(sys.stdin, 'encoding', None) or get_filesystem_encoding() + + _get_windows_console_stream = lambda *x: None + _wrap_std_stream = lambda *x: None + + +def term_len(x): + return len(strip_ansi(x)) + + +def isatty(stream): + try: + return stream.isatty() + except Exception: + return False + + +def _make_cached_stream_func(src_func, wrapper_func): + cache = WeakKeyDictionary() + def func(): + stream = src_func() + try: + rv = cache.get(stream) + except Exception: + rv = None + if rv is not None: + return rv + rv = wrapper_func() + try: + stream = src_func() # In case wrapper_func() modified the stream + cache[stream] = rv + except Exception: + pass + return rv + return func + + +_default_text_stdin = _make_cached_stream_func( + lambda: sys.stdin, get_text_stdin) +_default_text_stdout = _make_cached_stream_func( + lambda: sys.stdout, get_text_stdout) +_default_text_stderr = _make_cached_stream_func( + lambda: sys.stderr, get_text_stderr) + + +binary_streams = { + 'stdin': get_binary_stdin, + 'stdout': get_binary_stdout, + 'stderr': get_binary_stderr, +} + +text_streams = { + 'stdin': get_text_stdin, + 'stdout': get_text_stdout, + 'stderr': get_text_stderr, +} diff --git a/libs/click/_termui_impl.py b/libs/click/_termui_impl.py new file mode 100644 index 00000000..00a8e5ef --- /dev/null +++ b/libs/click/_termui_impl.py @@ -0,0 +1,621 @@ +# -*- coding: utf-8 -*- +""" +click._termui_impl +~~~~~~~~~~~~~~~~~~ + +This module contains implementations for the termui module. To keep the +import time of Click down, some infrequently used functionality is +placed in this module and only imported as needed. + +:copyright: © 2014 by the Pallets team. +:license: BSD, see LICENSE.rst for more details. +""" + +import os +import sys +import time +import math +import contextlib +from ._compat import _default_text_stdout, range_type, PY2, isatty, \ + open_stream, strip_ansi, term_len, get_best_encoding, WIN, int_types, \ + CYGWIN +from .utils import echo +from .exceptions import ClickException + + +if os.name == 'nt': + BEFORE_BAR = '\r' + AFTER_BAR = '\n' +else: + BEFORE_BAR = '\r\033[?25l' + AFTER_BAR = '\033[?25h\n' + + +def _length_hint(obj): + """Returns the length hint of an object.""" + try: + return len(obj) + except (AttributeError, TypeError): + try: + get_hint = type(obj).__length_hint__ + except AttributeError: + return None + try: + hint = get_hint(obj) + except TypeError: + return None + if hint is NotImplemented or \ + not isinstance(hint, int_types) or \ + hint < 0: + return None + return hint + + +class ProgressBar(object): + + def __init__(self, iterable, length=None, fill_char='#', empty_char=' ', + bar_template='%(bar)s', info_sep=' ', show_eta=True, + show_percent=None, show_pos=False, item_show_func=None, + label=None, file=None, color=None, width=30): + self.fill_char = fill_char + self.empty_char = empty_char + self.bar_template = bar_template + self.info_sep = info_sep + self.show_eta = show_eta + self.show_percent = show_percent + self.show_pos = show_pos + self.item_show_func = item_show_func + self.label = label or '' + if file is None: + file = _default_text_stdout() + self.file = file + self.color = color + self.width = width + self.autowidth = width == 0 + + if length is None: + length = _length_hint(iterable) + if iterable is None: + if length is None: + raise TypeError('iterable or length is required') + iterable = range_type(length) + self.iter = iter(iterable) + self.length = length + self.length_known = length is not None + self.pos = 0 + self.avg = [] + self.start = self.last_eta = time.time() + self.eta_known = False + self.finished = False + self.max_width = None + self.entered = False + self.current_item = None + self.is_hidden = not isatty(self.file) + self._last_line = None + self.short_limit = 0.5 + + def __enter__(self): + self.entered = True + self.render_progress() + return self + + def __exit__(self, exc_type, exc_value, tb): + self.render_finish() + + def __iter__(self): + if not self.entered: + raise RuntimeError('You need to use progress bars in a with block.') + self.render_progress() + return self.generator() + + def is_fast(self): + return time.time() - self.start <= self.short_limit + + def render_finish(self): + if self.is_hidden or self.is_fast(): + return + self.file.write(AFTER_BAR) + self.file.flush() + + @property + def pct(self): + if self.finished: + return 1.0 + return min(self.pos / (float(self.length) or 1), 1.0) + + @property + def time_per_iteration(self): + if not self.avg: + return 0.0 + return sum(self.avg) / float(len(self.avg)) + + @property + def eta(self): + if self.length_known and not self.finished: + return self.time_per_iteration * (self.length - self.pos) + return 0.0 + + def format_eta(self): + if self.eta_known: + t = int(self.eta) + seconds = t % 60 + t //= 60 + minutes = t % 60 + t //= 60 + hours = t % 24 + t //= 24 + if t > 0: + days = t + return '%dd %02d:%02d:%02d' % (days, hours, minutes, seconds) + else: + return '%02d:%02d:%02d' % (hours, minutes, seconds) + return '' + + def format_pos(self): + pos = str(self.pos) + if self.length_known: + pos += '/%s' % self.length + return pos + + def format_pct(self): + return ('% 4d%%' % int(self.pct * 100))[1:] + + def format_bar(self): + if self.length_known: + bar_length = int(self.pct * self.width) + bar = self.fill_char * bar_length + bar += self.empty_char * (self.width - bar_length) + elif self.finished: + bar = self.fill_char * self.width + else: + bar = list(self.empty_char * (self.width or 1)) + if self.time_per_iteration != 0: + bar[int((math.cos(self.pos * self.time_per_iteration) + / 2.0 + 0.5) * self.width)] = self.fill_char + bar = ''.join(bar) + return bar + + def format_progress_line(self): + show_percent = self.show_percent + + info_bits = [] + if self.length_known and show_percent is None: + show_percent = not self.show_pos + + if self.show_pos: + info_bits.append(self.format_pos()) + if show_percent: + info_bits.append(self.format_pct()) + if self.show_eta and self.eta_known and not self.finished: + info_bits.append(self.format_eta()) + if self.item_show_func is not None: + item_info = self.item_show_func(self.current_item) + if item_info is not None: + info_bits.append(item_info) + + return (self.bar_template % { + 'label': self.label, + 'bar': self.format_bar(), + 'info': self.info_sep.join(info_bits) + }).rstrip() + + def render_progress(self): + from .termui import get_terminal_size + + if self.is_hidden: + return + + buf = [] + # Update width in case the terminal has been resized + if self.autowidth: + old_width = self.width + self.width = 0 + clutter_length = term_len(self.format_progress_line()) + new_width = max(0, get_terminal_size()[0] - clutter_length) + if new_width < old_width: + buf.append(BEFORE_BAR) + buf.append(' ' * self.max_width) + self.max_width = new_width + self.width = new_width + + clear_width = self.width + if self.max_width is not None: + clear_width = self.max_width + + buf.append(BEFORE_BAR) + line = self.format_progress_line() + line_len = term_len(line) + if self.max_width is None or self.max_width < line_len: + self.max_width = line_len + + buf.append(line) + buf.append(' ' * (clear_width - line_len)) + line = ''.join(buf) + # Render the line only if it changed. + + if line != self._last_line and not self.is_fast(): + self._last_line = line + echo(line, file=self.file, color=self.color, nl=False) + self.file.flush() + + def make_step(self, n_steps): + self.pos += n_steps + if self.length_known and self.pos >= self.length: + self.finished = True + + if (time.time() - self.last_eta) < 1.0: + return + + self.last_eta = time.time() + + # self.avg is a rolling list of length <= 7 of steps where steps are + # defined as time elapsed divided by the total progress through + # self.length. + if self.pos: + step = (time.time() - self.start) / self.pos + else: + step = time.time() - self.start + + self.avg = self.avg[-6:] + [step] + + self.eta_known = self.length_known + + def update(self, n_steps): + self.make_step(n_steps) + self.render_progress() + + def finish(self): + self.eta_known = 0 + self.current_item = None + self.finished = True + + def generator(self): + """ + Returns a generator which yields the items added to the bar during + construction, and updates the progress bar *after* the yielded block + returns. + """ + if not self.entered: + raise RuntimeError('You need to use progress bars in a with block.') + + if self.is_hidden: + for rv in self.iter: + yield rv + else: + for rv in self.iter: + self.current_item = rv + yield rv + self.update(1) + self.finish() + self.render_progress() + + +def pager(generator, color=None): + """Decide what method to use for paging through text.""" + stdout = _default_text_stdout() + if not isatty(sys.stdin) or not isatty(stdout): + return _nullpager(stdout, generator, color) + pager_cmd = (os.environ.get('PAGER', None) or '').strip() + if pager_cmd: + if WIN: + return _tempfilepager(generator, pager_cmd, color) + return _pipepager(generator, pager_cmd, color) + if os.environ.get('TERM') in ('dumb', 'emacs'): + return _nullpager(stdout, generator, color) + if WIN or sys.platform.startswith('os2'): + return _tempfilepager(generator, 'more <', color) + if hasattr(os, 'system') and os.system('(less) 2>/dev/null') == 0: + return _pipepager(generator, 'less', color) + + import tempfile + fd, filename = tempfile.mkstemp() + os.close(fd) + try: + if hasattr(os, 'system') and os.system('more "%s"' % filename) == 0: + return _pipepager(generator, 'more', color) + return _nullpager(stdout, generator, color) + finally: + os.unlink(filename) + + +def _pipepager(generator, cmd, color): + """Page through text by feeding it to another program. Invoking a + pager through this might support colors. + """ + import subprocess + env = dict(os.environ) + + # If we're piping to less we might support colors under the + # condition that + cmd_detail = cmd.rsplit('/', 1)[-1].split() + if color is None and cmd_detail[0] == 'less': + less_flags = os.environ.get('LESS', '') + ' '.join(cmd_detail[1:]) + if not less_flags: + env['LESS'] = '-R' + color = True + elif 'r' in less_flags or 'R' in less_flags: + color = True + + c = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, + env=env) + encoding = get_best_encoding(c.stdin) + try: + for text in generator: + if not color: + text = strip_ansi(text) + + c.stdin.write(text.encode(encoding, 'replace')) + except (IOError, KeyboardInterrupt): + pass + else: + c.stdin.close() + + # Less doesn't respect ^C, but catches it for its own UI purposes (aborting + # search or other commands inside less). + # + # That means when the user hits ^C, the parent process (click) terminates, + # but less is still alive, paging the output and messing up the terminal. + # + # If the user wants to make the pager exit on ^C, they should set + # `LESS='-K'`. It's not our decision to make. + while True: + try: + c.wait() + except KeyboardInterrupt: + pass + else: + break + + +def _tempfilepager(generator, cmd, color): + """Page through text by invoking a program on a temporary file.""" + import tempfile + filename = tempfile.mktemp() + # TODO: This never terminates if the passed generator never terminates. + text = "".join(generator) + if not color: + text = strip_ansi(text) + encoding = get_best_encoding(sys.stdout) + with open_stream(filename, 'wb')[0] as f: + f.write(text.encode(encoding)) + try: + os.system(cmd + ' "' + filename + '"') + finally: + os.unlink(filename) + + +def _nullpager(stream, generator, color): + """Simply print unformatted text. This is the ultimate fallback.""" + for text in generator: + if not color: + text = strip_ansi(text) + stream.write(text) + + +class Editor(object): + + def __init__(self, editor=None, env=None, require_save=True, + extension='.txt'): + self.editor = editor + self.env = env + self.require_save = require_save + self.extension = extension + + def get_editor(self): + if self.editor is not None: + return self.editor + for key in 'VISUAL', 'EDITOR': + rv = os.environ.get(key) + if rv: + return rv + if WIN: + return 'notepad' + for editor in 'vim', 'nano': + if os.system('which %s >/dev/null 2>&1' % editor) == 0: + return editor + return 'vi' + + def edit_file(self, filename): + import subprocess + editor = self.get_editor() + if self.env: + environ = os.environ.copy() + environ.update(self.env) + else: + environ = None + try: + c = subprocess.Popen('%s "%s"' % (editor, filename), + env=environ, shell=True) + exit_code = c.wait() + if exit_code != 0: + raise ClickException('%s: Editing failed!' % editor) + except OSError as e: + raise ClickException('%s: Editing failed: %s' % (editor, e)) + + def edit(self, text): + import tempfile + + text = text or '' + if text and not text.endswith('\n'): + text += '\n' + + fd, name = tempfile.mkstemp(prefix='editor-', suffix=self.extension) + try: + if WIN: + encoding = 'utf-8-sig' + text = text.replace('\n', '\r\n') + else: + encoding = 'utf-8' + text = text.encode(encoding) + + f = os.fdopen(fd, 'wb') + f.write(text) + f.close() + timestamp = os.path.getmtime(name) + + self.edit_file(name) + + if self.require_save \ + and os.path.getmtime(name) == timestamp: + return None + + f = open(name, 'rb') + try: + rv = f.read() + finally: + f.close() + return rv.decode('utf-8-sig').replace('\r\n', '\n') + finally: + os.unlink(name) + + +def open_url(url, wait=False, locate=False): + import subprocess + + def _unquote_file(url): + try: + import urllib + except ImportError: + import urllib + if url.startswith('file://'): + url = urllib.unquote(url[7:]) + return url + + if sys.platform == 'darwin': + args = ['open'] + if wait: + args.append('-W') + if locate: + args.append('-R') + args.append(_unquote_file(url)) + null = open('/dev/null', 'w') + try: + return subprocess.Popen(args, stderr=null).wait() + finally: + null.close() + elif WIN: + if locate: + url = _unquote_file(url) + args = 'explorer /select,"%s"' % _unquote_file( + url.replace('"', '')) + else: + args = 'start %s "" "%s"' % ( + wait and '/WAIT' or '', url.replace('"', '')) + return os.system(args) + elif CYGWIN: + if locate: + url = _unquote_file(url) + args = 'cygstart "%s"' % (os.path.dirname(url).replace('"', '')) + else: + args = 'cygstart %s "%s"' % ( + wait and '-w' or '', url.replace('"', '')) + return os.system(args) + + try: + if locate: + url = os.path.dirname(_unquote_file(url)) or '.' + else: + url = _unquote_file(url) + c = subprocess.Popen(['xdg-open', url]) + if wait: + return c.wait() + return 0 + except OSError: + if url.startswith(('http://', 'https://')) and not locate and not wait: + import webbrowser + webbrowser.open(url) + return 0 + return 1 + + +def _translate_ch_to_exc(ch): + if ch == u'\x03': + raise KeyboardInterrupt() + if ch == u'\x04' and not WIN: # Unix-like, Ctrl+D + raise EOFError() + if ch == u'\x1a' and WIN: # Windows, Ctrl+Z + raise EOFError() + + +if WIN: + import msvcrt + + @contextlib.contextmanager + def raw_terminal(): + yield + + def getchar(echo): + # The function `getch` will return a bytes object corresponding to + # the pressed character. Since Windows 10 build 1803, it will also + # return \x00 when called a second time after pressing a regular key. + # + # `getwch` does not share this probably-bugged behavior. Moreover, it + # returns a Unicode object by default, which is what we want. + # + # Either of these functions will return \x00 or \xe0 to indicate + # a special key, and you need to call the same function again to get + # the "rest" of the code. The fun part is that \u00e0 is + # "latin small letter a with grave", so if you type that on a French + # keyboard, you _also_ get a \xe0. + # E.g., consider the Up arrow. This returns \xe0 and then \x48. The + # resulting Unicode string reads as "a with grave" + "capital H". + # This is indistinguishable from when the user actually types + # "a with grave" and then "capital H". + # + # When \xe0 is returned, we assume it's part of a special-key sequence + # and call `getwch` again, but that means that when the user types + # the \u00e0 character, `getchar` doesn't return until a second + # character is typed. + # The alternative is returning immediately, but that would mess up + # cross-platform handling of arrow keys and others that start with + # \xe0. Another option is using `getch`, but then we can't reliably + # read non-ASCII characters, because return values of `getch` are + # limited to the current 8-bit codepage. + # + # Anyway, Click doesn't claim to do this Right(tm), and using `getwch` + # is doing the right thing in more situations than with `getch`. + if echo: + func = msvcrt.getwche + else: + func = msvcrt.getwch + + rv = func() + if rv in (u'\x00', u'\xe0'): + # \x00 and \xe0 are control characters that indicate special key, + # see above. + rv += func() + _translate_ch_to_exc(rv) + return rv +else: + import tty + import termios + + @contextlib.contextmanager + def raw_terminal(): + if not isatty(sys.stdin): + f = open('/dev/tty') + fd = f.fileno() + else: + fd = sys.stdin.fileno() + f = None + try: + old_settings = termios.tcgetattr(fd) + try: + tty.setraw(fd) + yield fd + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + sys.stdout.flush() + if f is not None: + f.close() + except termios.error: + pass + + def getchar(echo): + with raw_terminal() as fd: + ch = os.read(fd, 32) + ch = ch.decode(get_best_encoding(sys.stdin), 'replace') + if echo and isatty(sys.stdout): + sys.stdout.write(ch) + _translate_ch_to_exc(ch) + return ch diff --git a/libs/click/_textwrap.py b/libs/click/_textwrap.py new file mode 100644 index 00000000..7e776031 --- /dev/null +++ b/libs/click/_textwrap.py @@ -0,0 +1,38 @@ +import textwrap +from contextlib import contextmanager + + +class TextWrapper(textwrap.TextWrapper): + + def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width): + space_left = max(width - cur_len, 1) + + if self.break_long_words: + last = reversed_chunks[-1] + cut = last[:space_left] + res = last[space_left:] + cur_line.append(cut) + reversed_chunks[-1] = res + elif not cur_line: + cur_line.append(reversed_chunks.pop()) + + @contextmanager + def extra_indent(self, indent): + old_initial_indent = self.initial_indent + old_subsequent_indent = self.subsequent_indent + self.initial_indent += indent + self.subsequent_indent += indent + try: + yield + finally: + self.initial_indent = old_initial_indent + self.subsequent_indent = old_subsequent_indent + + def indent_only(self, text): + rv = [] + for idx, line in enumerate(text.splitlines()): + indent = self.initial_indent + if idx > 0: + indent = self.subsequent_indent + rv.append(indent + line) + return '\n'.join(rv) diff --git a/libs/click/_unicodefun.py b/libs/click/_unicodefun.py new file mode 100644 index 00000000..620edff3 --- /dev/null +++ b/libs/click/_unicodefun.py @@ -0,0 +1,125 @@ +import os +import sys +import codecs + +from ._compat import PY2 + + +# If someone wants to vendor click, we want to ensure the +# correct package is discovered. Ideally we could use a +# relative import here but unfortunately Python does not +# support that. +click = sys.modules[__name__.rsplit('.', 1)[0]] + + +def _find_unicode_literals_frame(): + import __future__ + if not hasattr(sys, '_getframe'): # not all Python implementations have it + return 0 + frm = sys._getframe(1) + idx = 1 + while frm is not None: + if frm.f_globals.get('__name__', '').startswith('click.'): + frm = frm.f_back + idx += 1 + elif frm.f_code.co_flags & __future__.unicode_literals.compiler_flag: + return idx + else: + break + return 0 + + +def _check_for_unicode_literals(): + if not __debug__: + return + if not PY2 or click.disable_unicode_literals_warning: + return + bad_frame = _find_unicode_literals_frame() + if bad_frame <= 0: + return + from warnings import warn + warn(Warning('Click detected the use of the unicode_literals ' + '__future__ import. This is heavily discouraged ' + 'because it can introduce subtle bugs in your ' + 'code. You should instead use explicit u"" literals ' + 'for your unicode strings. For more information see ' + 'https://click.palletsprojects.com/python3/'), + stacklevel=bad_frame) + + +def _verify_python3_env(): + """Ensures that the environment is good for unicode on Python 3.""" + if PY2: + return + try: + import locale + fs_enc = codecs.lookup(locale.getpreferredencoding()).name + except Exception: + fs_enc = 'ascii' + if fs_enc != 'ascii': + return + + extra = '' + if os.name == 'posix': + import subprocess + try: + rv = subprocess.Popen(['locale', '-a'], stdout=subprocess.PIPE, + stderr=subprocess.PIPE).communicate()[0] + except OSError: + rv = b'' + good_locales = set() + has_c_utf8 = False + + # Make sure we're operating on text here. + if isinstance(rv, bytes): + rv = rv.decode('ascii', 'replace') + + for line in rv.splitlines(): + locale = line.strip() + if locale.lower().endswith(('.utf-8', '.utf8')): + good_locales.add(locale) + if locale.lower() in ('c.utf8', 'c.utf-8'): + has_c_utf8 = True + + extra += '\n\n' + if not good_locales: + extra += ( + 'Additional information: on this system no suitable UTF-8\n' + 'locales were discovered. This most likely requires resolving\n' + 'by reconfiguring the locale system.' + ) + elif has_c_utf8: + extra += ( + 'This system supports the C.UTF-8 locale which is recommended.\n' + 'You might be able to resolve your issue by exporting the\n' + 'following environment variables:\n\n' + ' export LC_ALL=C.UTF-8\n' + ' export LANG=C.UTF-8' + ) + else: + extra += ( + 'This system lists a couple of UTF-8 supporting locales that\n' + 'you can pick from. The following suitable locales were\n' + 'discovered: %s' + ) % ', '.join(sorted(good_locales)) + + bad_locale = None + for locale in os.environ.get('LC_ALL'), os.environ.get('LANG'): + if locale and locale.lower().endswith(('.utf-8', '.utf8')): + bad_locale = locale + if locale is not None: + break + if bad_locale is not None: + extra += ( + '\n\nClick discovered that you exported a UTF-8 locale\n' + 'but the locale system could not pick up from it because\n' + 'it does not exist. The exported locale is "%s" but it\n' + 'is not supported' + ) % bad_locale + + raise RuntimeError( + 'Click will abort further execution because Python 3 was' + ' configured to use ASCII as encoding for the environment.' + ' Consult https://click.palletsprojects.com/en/7.x/python3/ for' + ' mitigation steps.' + extra + ) diff --git a/libs/click/_winconsole.py b/libs/click/_winconsole.py new file mode 100644 index 00000000..bbb080dd --- /dev/null +++ b/libs/click/_winconsole.py @@ -0,0 +1,307 @@ +# -*- coding: utf-8 -*- +# This module is based on the excellent work by Adam Bartoš who +# provided a lot of what went into the implementation here in +# the discussion to issue1602 in the Python bug tracker. +# +# There are some general differences in regards to how this works +# compared to the original patches as we do not need to patch +# the entire interpreter but just work in our little world of +# echo and prmopt. + +import io +import os +import sys +import zlib +import time +import ctypes +import msvcrt +from ._compat import _NonClosingTextIOWrapper, text_type, PY2 +from ctypes import byref, POINTER, c_int, c_char, c_char_p, \ + c_void_p, py_object, c_ssize_t, c_ulong, windll, WINFUNCTYPE +try: + from ctypes import pythonapi + PyObject_GetBuffer = pythonapi.PyObject_GetBuffer + PyBuffer_Release = pythonapi.PyBuffer_Release +except ImportError: + pythonapi = None +from ctypes.wintypes import LPWSTR, LPCWSTR + + +c_ssize_p = POINTER(c_ssize_t) + +kernel32 = windll.kernel32 +GetStdHandle = kernel32.GetStdHandle +ReadConsoleW = kernel32.ReadConsoleW +WriteConsoleW = kernel32.WriteConsoleW +GetLastError = kernel32.GetLastError +GetCommandLineW = WINFUNCTYPE(LPWSTR)( + ('GetCommandLineW', windll.kernel32)) +CommandLineToArgvW = WINFUNCTYPE( + POINTER(LPWSTR), LPCWSTR, POINTER(c_int))( + ('CommandLineToArgvW', windll.shell32)) + + +STDIN_HANDLE = GetStdHandle(-10) +STDOUT_HANDLE = GetStdHandle(-11) +STDERR_HANDLE = GetStdHandle(-12) + + +PyBUF_SIMPLE = 0 +PyBUF_WRITABLE = 1 + +ERROR_SUCCESS = 0 +ERROR_NOT_ENOUGH_MEMORY = 8 +ERROR_OPERATION_ABORTED = 995 + +STDIN_FILENO = 0 +STDOUT_FILENO = 1 +STDERR_FILENO = 2 + +EOF = b'\x1a' +MAX_BYTES_WRITTEN = 32767 + + +class Py_buffer(ctypes.Structure): + _fields_ = [ + ('buf', c_void_p), + ('obj', py_object), + ('len', c_ssize_t), + ('itemsize', c_ssize_t), + ('readonly', c_int), + ('ndim', c_int), + ('format', c_char_p), + ('shape', c_ssize_p), + ('strides', c_ssize_p), + ('suboffsets', c_ssize_p), + ('internal', c_void_p) + ] + + if PY2: + _fields_.insert(-1, ('smalltable', c_ssize_t * 2)) + + +# On PyPy we cannot get buffers so our ability to operate here is +# serverly limited. +if pythonapi is None: + get_buffer = None +else: + def get_buffer(obj, writable=False): + buf = Py_buffer() + flags = PyBUF_WRITABLE if writable else PyBUF_SIMPLE + PyObject_GetBuffer(py_object(obj), byref(buf), flags) + try: + buffer_type = c_char * buf.len + return buffer_type.from_address(buf.buf) + finally: + PyBuffer_Release(byref(buf)) + + +class _WindowsConsoleRawIOBase(io.RawIOBase): + + def __init__(self, handle): + self.handle = handle + + def isatty(self): + io.RawIOBase.isatty(self) + return True + + +class _WindowsConsoleReader(_WindowsConsoleRawIOBase): + + def readable(self): + return True + + def readinto(self, b): + bytes_to_be_read = len(b) + if not bytes_to_be_read: + return 0 + elif bytes_to_be_read % 2: + raise ValueError('cannot read odd number of bytes from ' + 'UTF-16-LE encoded console') + + buffer = get_buffer(b, writable=True) + code_units_to_be_read = bytes_to_be_read // 2 + code_units_read = c_ulong() + + rv = ReadConsoleW(self.handle, buffer, code_units_to_be_read, + byref(code_units_read), None) + if GetLastError() == ERROR_OPERATION_ABORTED: + # wait for KeyboardInterrupt + time.sleep(0.1) + if not rv: + raise OSError('Windows error: %s' % GetLastError()) + + if buffer[0] == EOF: + return 0 + return 2 * code_units_read.value + + +class _WindowsConsoleWriter(_WindowsConsoleRawIOBase): + + def writable(self): + return True + + @staticmethod + def _get_error_message(errno): + if errno == ERROR_SUCCESS: + return 'ERROR_SUCCESS' + elif errno == ERROR_NOT_ENOUGH_MEMORY: + return 'ERROR_NOT_ENOUGH_MEMORY' + return 'Windows error %s' % errno + + def write(self, b): + bytes_to_be_written = len(b) + buf = get_buffer(b) + code_units_to_be_written = min(bytes_to_be_written, + MAX_BYTES_WRITTEN) // 2 + code_units_written = c_ulong() + + WriteConsoleW(self.handle, buf, code_units_to_be_written, + byref(code_units_written), None) + bytes_written = 2 * code_units_written.value + + if bytes_written == 0 and bytes_to_be_written > 0: + raise OSError(self._get_error_message(GetLastError())) + return bytes_written + + +class ConsoleStream(object): + + def __init__(self, text_stream, byte_stream): + self._text_stream = text_stream + self.buffer = byte_stream + + @property + def name(self): + return self.buffer.name + + def write(self, x): + if isinstance(x, text_type): + return self._text_stream.write(x) + try: + self.flush() + except Exception: + pass + return self.buffer.write(x) + + def writelines(self, lines): + for line in lines: + self.write(line) + + def __getattr__(self, name): + return getattr(self._text_stream, name) + + def isatty(self): + return self.buffer.isatty() + + def __repr__(self): + return '' % ( + self.name, + self.encoding, + ) + + +class WindowsChunkedWriter(object): + """ + Wraps a stream (such as stdout), acting as a transparent proxy for all + attribute access apart from method 'write()' which we wrap to write in + limited chunks due to a Windows limitation on binary console streams. + """ + def __init__(self, wrapped): + # double-underscore everything to prevent clashes with names of + # attributes on the wrapped stream object. + self.__wrapped = wrapped + + def __getattr__(self, name): + return getattr(self.__wrapped, name) + + def write(self, text): + total_to_write = len(text) + written = 0 + + while written < total_to_write: + to_write = min(total_to_write - written, MAX_BYTES_WRITTEN) + self.__wrapped.write(text[written:written+to_write]) + written += to_write + + +_wrapped_std_streams = set() + + +def _wrap_std_stream(name): + # Python 2 & Windows 7 and below + if PY2 and sys.getwindowsversion()[:2] <= (6, 1) and name not in _wrapped_std_streams: + setattr(sys, name, WindowsChunkedWriter(getattr(sys, name))) + _wrapped_std_streams.add(name) + + +def _get_text_stdin(buffer_stream): + text_stream = _NonClosingTextIOWrapper( + io.BufferedReader(_WindowsConsoleReader(STDIN_HANDLE)), + 'utf-16-le', 'strict', line_buffering=True) + return ConsoleStream(text_stream, buffer_stream) + + +def _get_text_stdout(buffer_stream): + text_stream = _NonClosingTextIOWrapper( + io.BufferedWriter(_WindowsConsoleWriter(STDOUT_HANDLE)), + 'utf-16-le', 'strict', line_buffering=True) + return ConsoleStream(text_stream, buffer_stream) + + +def _get_text_stderr(buffer_stream): + text_stream = _NonClosingTextIOWrapper( + io.BufferedWriter(_WindowsConsoleWriter(STDERR_HANDLE)), + 'utf-16-le', 'strict', line_buffering=True) + return ConsoleStream(text_stream, buffer_stream) + + +if PY2: + def _hash_py_argv(): + return zlib.crc32('\x00'.join(sys.argv[1:])) + + _initial_argv_hash = _hash_py_argv() + + def _get_windows_argv(): + argc = c_int(0) + argv_unicode = CommandLineToArgvW(GetCommandLineW(), byref(argc)) + argv = [argv_unicode[i] for i in range(0, argc.value)] + + if not hasattr(sys, 'frozen'): + argv = argv[1:] + while len(argv) > 0: + arg = argv[0] + if not arg.startswith('-') or arg == '-': + break + argv = argv[1:] + if arg.startswith(('-c', '-m')): + break + + return argv[1:] + + +_stream_factories = { + 0: _get_text_stdin, + 1: _get_text_stdout, + 2: _get_text_stderr, +} + + +def _get_windows_console_stream(f, encoding, errors): + if get_buffer is not None and \ + encoding in ('utf-16-le', None) \ + and errors in ('strict', None) and \ + hasattr(f, 'isatty') and f.isatty(): + func = _stream_factories.get(f.fileno()) + if func is not None: + if not PY2: + f = getattr(f, 'buffer', None) + if f is None: + return None + else: + # If we are on Python 2 we need to set the stream that we + # deal with to binary mode as otherwise the exercise if a + # bit moot. The same problems apply as for + # get_binary_stdin and friends from _compat. + msvcrt.setmode(f.fileno(), os.O_BINARY) + return func(f) diff --git a/libs/click/core.py b/libs/click/core.py new file mode 100644 index 00000000..7a1e3422 --- /dev/null +++ b/libs/click/core.py @@ -0,0 +1,1856 @@ +import errno +import inspect +import os +import sys +from contextlib import contextmanager +from itertools import repeat +from functools import update_wrapper + +from .types import convert_type, IntRange, BOOL +from .utils import PacifyFlushWrapper, make_str, make_default_short_help, \ + echo, get_os_args +from .exceptions import ClickException, UsageError, BadParameter, Abort, \ + MissingParameter, Exit +from .termui import prompt, confirm, style +from .formatting import HelpFormatter, join_options +from .parser import OptionParser, split_opt +from .globals import push_context, pop_context + +from ._compat import PY2, isidentifier, iteritems, string_types +from ._unicodefun import _check_for_unicode_literals, _verify_python3_env + + +_missing = object() + + +SUBCOMMAND_METAVAR = 'COMMAND [ARGS]...' +SUBCOMMANDS_METAVAR = 'COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...' + +DEPRECATED_HELP_NOTICE = ' (DEPRECATED)' +DEPRECATED_INVOKE_NOTICE = 'DeprecationWarning: ' + \ + 'The command %(name)s is deprecated.' + + +def _maybe_show_deprecated_notice(cmd): + if cmd.deprecated: + echo(style(DEPRECATED_INVOKE_NOTICE % {'name': cmd.name}, fg='red'), err=True) + + +def fast_exit(code): + """Exit without garbage collection, this speeds up exit by about 10ms for + things like bash completion. + """ + sys.stdout.flush() + sys.stderr.flush() + os._exit(code) + + +def _bashcomplete(cmd, prog_name, complete_var=None): + """Internal handler for the bash completion support.""" + if complete_var is None: + complete_var = '_%s_COMPLETE' % (prog_name.replace('-', '_')).upper() + complete_instr = os.environ.get(complete_var) + if not complete_instr: + return + + from ._bashcomplete import bashcomplete + if bashcomplete(cmd, prog_name, complete_var, complete_instr): + fast_exit(1) + + +def _check_multicommand(base_command, cmd_name, cmd, register=False): + if not base_command.chain or not isinstance(cmd, MultiCommand): + return + if register: + hint = 'It is not possible to add multi commands as children to ' \ + 'another multi command that is in chain mode' + else: + hint = 'Found a multi command as subcommand to a multi command ' \ + 'that is in chain mode. This is not supported' + raise RuntimeError('%s. Command "%s" is set to chain and "%s" was ' + 'added as subcommand but it in itself is a ' + 'multi command. ("%s" is a %s within a chained ' + '%s named "%s").' % ( + hint, base_command.name, cmd_name, + cmd_name, cmd.__class__.__name__, + base_command.__class__.__name__, + base_command.name)) + + +def batch(iterable, batch_size): + return list(zip(*repeat(iter(iterable), batch_size))) + + +def invoke_param_callback(callback, ctx, param, value): + code = getattr(callback, '__code__', None) + args = getattr(code, 'co_argcount', 3) + + if args < 3: + # This will become a warning in Click 3.0: + from warnings import warn + warn(Warning('Invoked legacy parameter callback "%s". The new ' + 'signature for such callbacks starting with ' + 'click 2.0 is (ctx, param, value).' + % callback), stacklevel=3) + return callback(ctx, value) + return callback(ctx, param, value) + + +@contextmanager +def augment_usage_errors(ctx, param=None): + """Context manager that attaches extra information to exceptions that + fly. + """ + try: + yield + except BadParameter as e: + if e.ctx is None: + e.ctx = ctx + if param is not None and e.param is None: + e.param = param + raise + except UsageError as e: + if e.ctx is None: + e.ctx = ctx + raise + + +def iter_params_for_processing(invocation_order, declaration_order): + """Given a sequence of parameters in the order as should be considered + for processing and an iterable of parameters that exist, this returns + a list in the correct order as they should be processed. + """ + def sort_key(item): + try: + idx = invocation_order.index(item) + except ValueError: + idx = float('inf') + return (not item.is_eager, idx) + + return sorted(declaration_order, key=sort_key) + + +class Context(object): + """The context is a special internal object that holds state relevant + for the script execution at every single level. It's normally invisible + to commands unless they opt-in to getting access to it. + + The context is useful as it can pass internal objects around and can + control special execution features such as reading data from + environment variables. + + A context can be used as context manager in which case it will call + :meth:`close` on teardown. + + .. versionadded:: 2.0 + Added the `resilient_parsing`, `help_option_names`, + `token_normalize_func` parameters. + + .. versionadded:: 3.0 + Added the `allow_extra_args` and `allow_interspersed_args` + parameters. + + .. versionadded:: 4.0 + Added the `color`, `ignore_unknown_options`, and + `max_content_width` parameters. + + :param command: the command class for this context. + :param parent: the parent context. + :param info_name: the info name for this invocation. Generally this + is the most descriptive name for the script or + command. For the toplevel script it is usually + the name of the script, for commands below it it's + the name of the script. + :param obj: an arbitrary object of user data. + :param auto_envvar_prefix: the prefix to use for automatic environment + variables. If this is `None` then reading + from environment variables is disabled. This + does not affect manually set environment + variables which are always read. + :param default_map: a dictionary (like object) with default values + for parameters. + :param terminal_width: the width of the terminal. The default is + inherit from parent context. If no context + defines the terminal width then auto + detection will be applied. + :param max_content_width: the maximum width for content rendered by + Click (this currently only affects help + pages). This defaults to 80 characters if + not overridden. In other words: even if the + terminal is larger than that, Click will not + format things wider than 80 characters by + default. In addition to that, formatters might + add some safety mapping on the right. + :param resilient_parsing: if this flag is enabled then Click will + parse without any interactivity or callback + invocation. Default values will also be + ignored. This is useful for implementing + things such as completion support. + :param allow_extra_args: if this is set to `True` then extra arguments + at the end will not raise an error and will be + kept on the context. The default is to inherit + from the command. + :param allow_interspersed_args: if this is set to `False` then options + and arguments cannot be mixed. The + default is to inherit from the command. + :param ignore_unknown_options: instructs click to ignore options it does + not know and keeps them for later + processing. + :param help_option_names: optionally a list of strings that define how + the default help parameter is named. The + default is ``['--help']``. + :param token_normalize_func: an optional function that is used to + normalize tokens (options, choices, + etc.). This for instance can be used to + implement case insensitive behavior. + :param color: controls if the terminal supports ANSI colors or not. The + default is autodetection. This is only needed if ANSI + codes are used in texts that Click prints which is by + default not the case. This for instance would affect + help output. + """ + + def __init__(self, command, parent=None, info_name=None, obj=None, + auto_envvar_prefix=None, default_map=None, + terminal_width=None, max_content_width=None, + resilient_parsing=False, allow_extra_args=None, + allow_interspersed_args=None, + ignore_unknown_options=None, help_option_names=None, + token_normalize_func=None, color=None): + #: the parent context or `None` if none exists. + self.parent = parent + #: the :class:`Command` for this context. + self.command = command + #: the descriptive information name + self.info_name = info_name + #: the parsed parameters except if the value is hidden in which + #: case it's not remembered. + self.params = {} + #: the leftover arguments. + self.args = [] + #: protected arguments. These are arguments that are prepended + #: to `args` when certain parsing scenarios are encountered but + #: must be never propagated to another arguments. This is used + #: to implement nested parsing. + self.protected_args = [] + if obj is None and parent is not None: + obj = parent.obj + #: the user object stored. + self.obj = obj + self._meta = getattr(parent, 'meta', {}) + + #: A dictionary (-like object) with defaults for parameters. + if default_map is None \ + and parent is not None \ + and parent.default_map is not None: + default_map = parent.default_map.get(info_name) + self.default_map = default_map + + #: This flag indicates if a subcommand is going to be executed. A + #: group callback can use this information to figure out if it's + #: being executed directly or because the execution flow passes + #: onwards to a subcommand. By default it's None, but it can be + #: the name of the subcommand to execute. + #: + #: If chaining is enabled this will be set to ``'*'`` in case + #: any commands are executed. It is however not possible to + #: figure out which ones. If you require this knowledge you + #: should use a :func:`resultcallback`. + self.invoked_subcommand = None + + if terminal_width is None and parent is not None: + terminal_width = parent.terminal_width + #: The width of the terminal (None is autodetection). + self.terminal_width = terminal_width + + if max_content_width is None and parent is not None: + max_content_width = parent.max_content_width + #: The maximum width of formatted content (None implies a sensible + #: default which is 80 for most things). + self.max_content_width = max_content_width + + if allow_extra_args is None: + allow_extra_args = command.allow_extra_args + #: Indicates if the context allows extra args or if it should + #: fail on parsing. + #: + #: .. versionadded:: 3.0 + self.allow_extra_args = allow_extra_args + + if allow_interspersed_args is None: + allow_interspersed_args = command.allow_interspersed_args + #: Indicates if the context allows mixing of arguments and + #: options or not. + #: + #: .. versionadded:: 3.0 + self.allow_interspersed_args = allow_interspersed_args + + if ignore_unknown_options is None: + ignore_unknown_options = command.ignore_unknown_options + #: Instructs click to ignore options that a command does not + #: understand and will store it on the context for later + #: processing. This is primarily useful for situations where you + #: want to call into external programs. Generally this pattern is + #: strongly discouraged because it's not possibly to losslessly + #: forward all arguments. + #: + #: .. versionadded:: 4.0 + self.ignore_unknown_options = ignore_unknown_options + + if help_option_names is None: + if parent is not None: + help_option_names = parent.help_option_names + else: + help_option_names = ['--help'] + + #: The names for the help options. + self.help_option_names = help_option_names + + if token_normalize_func is None and parent is not None: + token_normalize_func = parent.token_normalize_func + + #: An optional normalization function for tokens. This is + #: options, choices, commands etc. + self.token_normalize_func = token_normalize_func + + #: Indicates if resilient parsing is enabled. In that case Click + #: will do its best to not cause any failures and default values + #: will be ignored. Useful for completion. + self.resilient_parsing = resilient_parsing + + # If there is no envvar prefix yet, but the parent has one and + # the command on this level has a name, we can expand the envvar + # prefix automatically. + if auto_envvar_prefix is None: + if parent is not None \ + and parent.auto_envvar_prefix is not None and \ + self.info_name is not None: + auto_envvar_prefix = '%s_%s' % (parent.auto_envvar_prefix, + self.info_name.upper()) + else: + auto_envvar_prefix = auto_envvar_prefix.upper() + self.auto_envvar_prefix = auto_envvar_prefix + + if color is None and parent is not None: + color = parent.color + + #: Controls if styling output is wanted or not. + self.color = color + + self._close_callbacks = [] + self._depth = 0 + + def __enter__(self): + self._depth += 1 + push_context(self) + return self + + def __exit__(self, exc_type, exc_value, tb): + self._depth -= 1 + if self._depth == 0: + self.close() + pop_context() + + @contextmanager + def scope(self, cleanup=True): + """This helper method can be used with the context object to promote + it to the current thread local (see :func:`get_current_context`). + The default behavior of this is to invoke the cleanup functions which + can be disabled by setting `cleanup` to `False`. The cleanup + functions are typically used for things such as closing file handles. + + If the cleanup is intended the context object can also be directly + used as a context manager. + + Example usage:: + + with ctx.scope(): + assert get_current_context() is ctx + + This is equivalent:: + + with ctx: + assert get_current_context() is ctx + + .. versionadded:: 5.0 + + :param cleanup: controls if the cleanup functions should be run or + not. The default is to run these functions. In + some situations the context only wants to be + temporarily pushed in which case this can be disabled. + Nested pushes automatically defer the cleanup. + """ + if not cleanup: + self._depth += 1 + try: + with self as rv: + yield rv + finally: + if not cleanup: + self._depth -= 1 + + @property + def meta(self): + """This is a dictionary which is shared with all the contexts + that are nested. It exists so that click utilities can store some + state here if they need to. It is however the responsibility of + that code to manage this dictionary well. + + The keys are supposed to be unique dotted strings. For instance + module paths are a good choice for it. What is stored in there is + irrelevant for the operation of click. However what is important is + that code that places data here adheres to the general semantics of + the system. + + Example usage:: + + LANG_KEY = __name__ + '.lang' + + def set_language(value): + ctx = get_current_context() + ctx.meta[LANG_KEY] = value + + def get_language(): + return get_current_context().meta.get(LANG_KEY, 'en_US') + + .. versionadded:: 5.0 + """ + return self._meta + + def make_formatter(self): + """Creates the formatter for the help and usage output.""" + return HelpFormatter(width=self.terminal_width, + max_width=self.max_content_width) + + def call_on_close(self, f): + """This decorator remembers a function as callback that should be + executed when the context tears down. This is most useful to bind + resource handling to the script execution. For instance, file objects + opened by the :class:`File` type will register their close callbacks + here. + + :param f: the function to execute on teardown. + """ + self._close_callbacks.append(f) + return f + + def close(self): + """Invokes all close callbacks.""" + for cb in self._close_callbacks: + cb() + self._close_callbacks = [] + + @property + def command_path(self): + """The computed command path. This is used for the ``usage`` + information on the help page. It's automatically created by + combining the info names of the chain of contexts to the root. + """ + rv = '' + if self.info_name is not None: + rv = self.info_name + if self.parent is not None: + rv = self.parent.command_path + ' ' + rv + return rv.lstrip() + + def find_root(self): + """Finds the outermost context.""" + node = self + while node.parent is not None: + node = node.parent + return node + + def find_object(self, object_type): + """Finds the closest object of a given type.""" + node = self + while node is not None: + if isinstance(node.obj, object_type): + return node.obj + node = node.parent + + def ensure_object(self, object_type): + """Like :meth:`find_object` but sets the innermost object to a + new instance of `object_type` if it does not exist. + """ + rv = self.find_object(object_type) + if rv is None: + self.obj = rv = object_type() + return rv + + def lookup_default(self, name): + """Looks up the default for a parameter name. This by default + looks into the :attr:`default_map` if available. + """ + if self.default_map is not None: + rv = self.default_map.get(name) + if callable(rv): + rv = rv() + return rv + + def fail(self, message): + """Aborts the execution of the program with a specific error + message. + + :param message: the error message to fail with. + """ + raise UsageError(message, self) + + def abort(self): + """Aborts the script.""" + raise Abort() + + def exit(self, code=0): + """Exits the application with a given exit code.""" + raise Exit(code) + + def get_usage(self): + """Helper method to get formatted usage string for the current + context and command. + """ + return self.command.get_usage(self) + + def get_help(self): + """Helper method to get formatted help page for the current + context and command. + """ + return self.command.get_help(self) + + def invoke(*args, **kwargs): + """Invokes a command callback in exactly the way it expects. There + are two ways to invoke this method: + + 1. the first argument can be a callback and all other arguments and + keyword arguments are forwarded directly to the function. + 2. the first argument is a click command object. In that case all + arguments are forwarded as well but proper click parameters + (options and click arguments) must be keyword arguments and Click + will fill in defaults. + + Note that before Click 3.2 keyword arguments were not properly filled + in against the intention of this code and no context was created. For + more information about this change and why it was done in a bugfix + release see :ref:`upgrade-to-3.2`. + """ + self, callback = args[:2] + ctx = self + + # It's also possible to invoke another command which might or + # might not have a callback. In that case we also fill + # in defaults and make a new context for this command. + if isinstance(callback, Command): + other_cmd = callback + callback = other_cmd.callback + ctx = Context(other_cmd, info_name=other_cmd.name, parent=self) + if callback is None: + raise TypeError('The given command does not have a ' + 'callback that can be invoked.') + + for param in other_cmd.params: + if param.name not in kwargs and param.expose_value: + kwargs[param.name] = param.get_default(ctx) + + args = args[2:] + with augment_usage_errors(self): + with ctx: + return callback(*args, **kwargs) + + def forward(*args, **kwargs): + """Similar to :meth:`invoke` but fills in default keyword + arguments from the current context if the other command expects + it. This cannot invoke callbacks directly, only other commands. + """ + self, cmd = args[:2] + + # It's also possible to invoke another command which might or + # might not have a callback. + if not isinstance(cmd, Command): + raise TypeError('Callback is not a command.') + + for param in self.params: + if param not in kwargs: + kwargs[param] = self.params[param] + + return self.invoke(cmd, **kwargs) + + +class BaseCommand(object): + """The base command implements the minimal API contract of commands. + Most code will never use this as it does not implement a lot of useful + functionality but it can act as the direct subclass of alternative + parsing methods that do not depend on the Click parser. + + For instance, this can be used to bridge Click and other systems like + argparse or docopt. + + Because base commands do not implement a lot of the API that other + parts of Click take for granted, they are not supported for all + operations. For instance, they cannot be used with the decorators + usually and they have no built-in callback system. + + .. versionchanged:: 2.0 + Added the `context_settings` parameter. + + :param name: the name of the command to use unless a group overrides it. + :param context_settings: an optional dictionary with defaults that are + passed to the context object. + """ + #: the default for the :attr:`Context.allow_extra_args` flag. + allow_extra_args = False + #: the default for the :attr:`Context.allow_interspersed_args` flag. + allow_interspersed_args = True + #: the default for the :attr:`Context.ignore_unknown_options` flag. + ignore_unknown_options = False + + def __init__(self, name, context_settings=None): + #: the name the command thinks it has. Upon registering a command + #: on a :class:`Group` the group will default the command name + #: with this information. You should instead use the + #: :class:`Context`\'s :attr:`~Context.info_name` attribute. + self.name = name + if context_settings is None: + context_settings = {} + #: an optional dictionary with defaults passed to the context. + self.context_settings = context_settings + + def get_usage(self, ctx): + raise NotImplementedError('Base commands cannot get usage') + + def get_help(self, ctx): + raise NotImplementedError('Base commands cannot get help') + + def make_context(self, info_name, args, parent=None, **extra): + """This function when given an info name and arguments will kick + off the parsing and create a new :class:`Context`. It does not + invoke the actual command callback though. + + :param info_name: the info name for this invokation. Generally this + is the most descriptive name for the script or + command. For the toplevel script it's usually + the name of the script, for commands below it it's + the name of the script. + :param args: the arguments to parse as list of strings. + :param parent: the parent context if available. + :param extra: extra keyword arguments forwarded to the context + constructor. + """ + for key, value in iteritems(self.context_settings): + if key not in extra: + extra[key] = value + ctx = Context(self, info_name=info_name, parent=parent, **extra) + with ctx.scope(cleanup=False): + self.parse_args(ctx, args) + return ctx + + def parse_args(self, ctx, args): + """Given a context and a list of arguments this creates the parser + and parses the arguments, then modifies the context as necessary. + This is automatically invoked by :meth:`make_context`. + """ + raise NotImplementedError('Base commands do not know how to parse ' + 'arguments.') + + def invoke(self, ctx): + """Given a context, this invokes the command. The default + implementation is raising a not implemented error. + """ + raise NotImplementedError('Base commands are not invokable by default') + + def main(self, args=None, prog_name=None, complete_var=None, + standalone_mode=True, **extra): + """This is the way to invoke a script with all the bells and + whistles as a command line application. This will always terminate + the application after a call. If this is not wanted, ``SystemExit`` + needs to be caught. + + This method is also available by directly calling the instance of + a :class:`Command`. + + .. versionadded:: 3.0 + Added the `standalone_mode` flag to control the standalone mode. + + :param args: the arguments that should be used for parsing. If not + provided, ``sys.argv[1:]`` is used. + :param prog_name: the program name that should be used. By default + the program name is constructed by taking the file + name from ``sys.argv[0]``. + :param complete_var: the environment variable that controls the + bash completion support. The default is + ``"__COMPLETE"`` with prog_name in + uppercase. + :param standalone_mode: the default behavior is to invoke the script + in standalone mode. Click will then + handle exceptions and convert them into + error messages and the function will never + return but shut down the interpreter. If + this is set to `False` they will be + propagated to the caller and the return + value of this function is the return value + of :meth:`invoke`. + :param extra: extra keyword arguments are forwarded to the context + constructor. See :class:`Context` for more information. + """ + # If we are in Python 3, we will verify that the environment is + # sane at this point or reject further execution to avoid a + # broken script. + if not PY2: + _verify_python3_env() + else: + _check_for_unicode_literals() + + if args is None: + args = get_os_args() + else: + args = list(args) + + if prog_name is None: + prog_name = make_str(os.path.basename( + sys.argv and sys.argv[0] or __file__)) + + # Hook for the Bash completion. This only activates if the Bash + # completion is actually enabled, otherwise this is quite a fast + # noop. + _bashcomplete(self, prog_name, complete_var) + + try: + try: + with self.make_context(prog_name, args, **extra) as ctx: + rv = self.invoke(ctx) + if not standalone_mode: + return rv + # it's not safe to `ctx.exit(rv)` here! + # note that `rv` may actually contain data like "1" which + # has obvious effects + # more subtle case: `rv=[None, None]` can come out of + # chained commands which all returned `None` -- so it's not + # even always obvious that `rv` indicates success/failure + # by its truthiness/falsiness + ctx.exit() + except (EOFError, KeyboardInterrupt): + echo(file=sys.stderr) + raise Abort() + except ClickException as e: + if not standalone_mode: + raise + e.show() + sys.exit(e.exit_code) + except IOError as e: + if e.errno == errno.EPIPE: + sys.stdout = PacifyFlushWrapper(sys.stdout) + sys.stderr = PacifyFlushWrapper(sys.stderr) + sys.exit(1) + else: + raise + except Exit as e: + if standalone_mode: + sys.exit(e.exit_code) + else: + # in non-standalone mode, return the exit code + # note that this is only reached if `self.invoke` above raises + # an Exit explicitly -- thus bypassing the check there which + # would return its result + # the results of non-standalone execution may therefore be + # somewhat ambiguous: if there are codepaths which lead to + # `ctx.exit(1)` and to `return 1`, the caller won't be able to + # tell the difference between the two + return e.exit_code + except Abort: + if not standalone_mode: + raise + echo('Aborted!', file=sys.stderr) + sys.exit(1) + + def __call__(self, *args, **kwargs): + """Alias for :meth:`main`.""" + return self.main(*args, **kwargs) + + +class Command(BaseCommand): + """Commands are the basic building block of command line interfaces in + Click. A basic command handles command line parsing and might dispatch + more parsing to commands nested below it. + + .. versionchanged:: 2.0 + Added the `context_settings` parameter. + + :param name: the name of the command to use unless a group overrides it. + :param context_settings: an optional dictionary with defaults that are + passed to the context object. + :param callback: the callback to invoke. This is optional. + :param params: the parameters to register with this command. This can + be either :class:`Option` or :class:`Argument` objects. + :param help: the help string to use for this command. + :param epilog: like the help string but it's printed at the end of the + help page after everything else. + :param short_help: the short help to use for this command. This is + shown on the command listing of the parent command. + :param add_help_option: by default each command registers a ``--help`` + option. This can be disabled by this parameter. + :param hidden: hide this command from help outputs. + + :param deprecated: issues a message indicating that + the command is deprecated. + """ + + def __init__(self, name, context_settings=None, callback=None, + params=None, help=None, epilog=None, short_help=None, + options_metavar='[OPTIONS]', add_help_option=True, + hidden=False, deprecated=False): + BaseCommand.__init__(self, name, context_settings) + #: the callback to execute when the command fires. This might be + #: `None` in which case nothing happens. + self.callback = callback + #: the list of parameters for this command in the order they + #: should show up in the help page and execute. Eager parameters + #: will automatically be handled before non eager ones. + self.params = params or [] + # if a form feed (page break) is found in the help text, truncate help + # text to the content preceding the first form feed + if help and '\f' in help: + help = help.split('\f', 1)[0] + self.help = help + self.epilog = epilog + self.options_metavar = options_metavar + self.short_help = short_help + self.add_help_option = add_help_option + self.hidden = hidden + self.deprecated = deprecated + + def get_usage(self, ctx): + formatter = ctx.make_formatter() + self.format_usage(ctx, formatter) + return formatter.getvalue().rstrip('\n') + + def get_params(self, ctx): + rv = self.params + help_option = self.get_help_option(ctx) + if help_option is not None: + rv = rv + [help_option] + return rv + + def format_usage(self, ctx, formatter): + """Writes the usage line into the formatter.""" + pieces = self.collect_usage_pieces(ctx) + formatter.write_usage(ctx.command_path, ' '.join(pieces)) + + def collect_usage_pieces(self, ctx): + """Returns all the pieces that go into the usage line and returns + it as a list of strings. + """ + rv = [self.options_metavar] + for param in self.get_params(ctx): + rv.extend(param.get_usage_pieces(ctx)) + return rv + + def get_help_option_names(self, ctx): + """Returns the names for the help option.""" + all_names = set(ctx.help_option_names) + for param in self.params: + all_names.difference_update(param.opts) + all_names.difference_update(param.secondary_opts) + return all_names + + def get_help_option(self, ctx): + """Returns the help option object.""" + help_options = self.get_help_option_names(ctx) + if not help_options or not self.add_help_option: + return + + def show_help(ctx, param, value): + if value and not ctx.resilient_parsing: + echo(ctx.get_help(), color=ctx.color) + ctx.exit() + return Option(help_options, is_flag=True, + is_eager=True, expose_value=False, + callback=show_help, + help='Show this message and exit.') + + def make_parser(self, ctx): + """Creates the underlying option parser for this command.""" + parser = OptionParser(ctx) + for param in self.get_params(ctx): + param.add_to_parser(parser, ctx) + return parser + + def get_help(self, ctx): + """Formats the help into a string and returns it. This creates a + formatter and will call into the following formatting methods: + """ + formatter = ctx.make_formatter() + self.format_help(ctx, formatter) + return formatter.getvalue().rstrip('\n') + + def get_short_help_str(self, limit=45): + """Gets short help for the command or makes it by shortening the long help string.""" + return self.short_help or self.help and make_default_short_help(self.help, limit) or '' + + def format_help(self, ctx, formatter): + """Writes the help into the formatter if it exists. + + This calls into the following methods: + + - :meth:`format_usage` + - :meth:`format_help_text` + - :meth:`format_options` + - :meth:`format_epilog` + """ + self.format_usage(ctx, formatter) + self.format_help_text(ctx, formatter) + self.format_options(ctx, formatter) + self.format_epilog(ctx, formatter) + + def format_help_text(self, ctx, formatter): + """Writes the help text to the formatter if it exists.""" + if self.help: + formatter.write_paragraph() + with formatter.indentation(): + help_text = self.help + if self.deprecated: + help_text += DEPRECATED_HELP_NOTICE + formatter.write_text(help_text) + elif self.deprecated: + formatter.write_paragraph() + with formatter.indentation(): + formatter.write_text(DEPRECATED_HELP_NOTICE) + + def format_options(self, ctx, formatter): + """Writes all the options into the formatter if they exist.""" + opts = [] + for param in self.get_params(ctx): + rv = param.get_help_record(ctx) + if rv is not None: + opts.append(rv) + + if opts: + with formatter.section('Options'): + formatter.write_dl(opts) + + def format_epilog(self, ctx, formatter): + """Writes the epilog into the formatter if it exists.""" + if self.epilog: + formatter.write_paragraph() + with formatter.indentation(): + formatter.write_text(self.epilog) + + def parse_args(self, ctx, args): + parser = self.make_parser(ctx) + opts, args, param_order = parser.parse_args(args=args) + + for param in iter_params_for_processing( + param_order, self.get_params(ctx)): + value, args = param.handle_parse_result(ctx, opts, args) + + if args and not ctx.allow_extra_args and not ctx.resilient_parsing: + ctx.fail('Got unexpected extra argument%s (%s)' + % (len(args) != 1 and 's' or '', + ' '.join(map(make_str, args)))) + + ctx.args = args + return args + + def invoke(self, ctx): + """Given a context, this invokes the attached callback (if it exists) + in the right way. + """ + _maybe_show_deprecated_notice(self) + if self.callback is not None: + return ctx.invoke(self.callback, **ctx.params) + + +class MultiCommand(Command): + """A multi command is the basic implementation of a command that + dispatches to subcommands. The most common version is the + :class:`Group`. + + :param invoke_without_command: this controls how the multi command itself + is invoked. By default it's only invoked + if a subcommand is provided. + :param no_args_is_help: this controls what happens if no arguments are + provided. This option is enabled by default if + `invoke_without_command` is disabled or disabled + if it's enabled. If enabled this will add + ``--help`` as argument if no arguments are + passed. + :param subcommand_metavar: the string that is used in the documentation + to indicate the subcommand place. + :param chain: if this is set to `True` chaining of multiple subcommands + is enabled. This restricts the form of commands in that + they cannot have optional arguments but it allows + multiple commands to be chained together. + :param result_callback: the result callback to attach to this multi + command. + """ + allow_extra_args = True + allow_interspersed_args = False + + def __init__(self, name=None, invoke_without_command=False, + no_args_is_help=None, subcommand_metavar=None, + chain=False, result_callback=None, **attrs): + Command.__init__(self, name, **attrs) + if no_args_is_help is None: + no_args_is_help = not invoke_without_command + self.no_args_is_help = no_args_is_help + self.invoke_without_command = invoke_without_command + if subcommand_metavar is None: + if chain: + subcommand_metavar = SUBCOMMANDS_METAVAR + else: + subcommand_metavar = SUBCOMMAND_METAVAR + self.subcommand_metavar = subcommand_metavar + self.chain = chain + #: The result callback that is stored. This can be set or + #: overridden with the :func:`resultcallback` decorator. + self.result_callback = result_callback + + if self.chain: + for param in self.params: + if isinstance(param, Argument) and not param.required: + raise RuntimeError('Multi commands in chain mode cannot ' + 'have optional arguments.') + + def collect_usage_pieces(self, ctx): + rv = Command.collect_usage_pieces(self, ctx) + rv.append(self.subcommand_metavar) + return rv + + def format_options(self, ctx, formatter): + Command.format_options(self, ctx, formatter) + self.format_commands(ctx, formatter) + + def resultcallback(self, replace=False): + """Adds a result callback to the chain command. By default if a + result callback is already registered this will chain them but + this can be disabled with the `replace` parameter. The result + callback is invoked with the return value of the subcommand + (or the list of return values from all subcommands if chaining + is enabled) as well as the parameters as they would be passed + to the main callback. + + Example:: + + @click.group() + @click.option('-i', '--input', default=23) + def cli(input): + return 42 + + @cli.resultcallback() + def process_result(result, input): + return result + input + + .. versionadded:: 3.0 + + :param replace: if set to `True` an already existing result + callback will be removed. + """ + def decorator(f): + old_callback = self.result_callback + if old_callback is None or replace: + self.result_callback = f + return f + def function(__value, *args, **kwargs): + return f(old_callback(__value, *args, **kwargs), + *args, **kwargs) + self.result_callback = rv = update_wrapper(function, f) + return rv + return decorator + + def format_commands(self, ctx, formatter): + """Extra format methods for multi methods that adds all the commands + after the options. + """ + commands = [] + for subcommand in self.list_commands(ctx): + cmd = self.get_command(ctx, subcommand) + # What is this, the tool lied about a command. Ignore it + if cmd is None: + continue + if cmd.hidden: + continue + + commands.append((subcommand, cmd)) + + # allow for 3 times the default spacing + if len(commands): + limit = formatter.width - 6 - max(len(cmd[0]) for cmd in commands) + + rows = [] + for subcommand, cmd in commands: + help = cmd.get_short_help_str(limit) + rows.append((subcommand, help)) + + if rows: + with formatter.section('Commands'): + formatter.write_dl(rows) + + def parse_args(self, ctx, args): + if not args and self.no_args_is_help and not ctx.resilient_parsing: + echo(ctx.get_help(), color=ctx.color) + ctx.exit() + + rest = Command.parse_args(self, ctx, args) + if self.chain: + ctx.protected_args = rest + ctx.args = [] + elif rest: + ctx.protected_args, ctx.args = rest[:1], rest[1:] + + return ctx.args + + def invoke(self, ctx): + def _process_result(value): + if self.result_callback is not None: + value = ctx.invoke(self.result_callback, value, + **ctx.params) + return value + + if not ctx.protected_args: + # If we are invoked without command the chain flag controls + # how this happens. If we are not in chain mode, the return + # value here is the return value of the command. + # If however we are in chain mode, the return value is the + # return value of the result processor invoked with an empty + # list (which means that no subcommand actually was executed). + if self.invoke_without_command: + if not self.chain: + return Command.invoke(self, ctx) + with ctx: + Command.invoke(self, ctx) + return _process_result([]) + ctx.fail('Missing command.') + + # Fetch args back out + args = ctx.protected_args + ctx.args + ctx.args = [] + ctx.protected_args = [] + + # If we're not in chain mode, we only allow the invocation of a + # single command but we also inform the current context about the + # name of the command to invoke. + if not self.chain: + # Make sure the context is entered so we do not clean up + # resources until the result processor has worked. + with ctx: + cmd_name, cmd, args = self.resolve_command(ctx, args) + ctx.invoked_subcommand = cmd_name + Command.invoke(self, ctx) + sub_ctx = cmd.make_context(cmd_name, args, parent=ctx) + with sub_ctx: + return _process_result(sub_ctx.command.invoke(sub_ctx)) + + # In chain mode we create the contexts step by step, but after the + # base command has been invoked. Because at that point we do not + # know the subcommands yet, the invoked subcommand attribute is + # set to ``*`` to inform the command that subcommands are executed + # but nothing else. + with ctx: + ctx.invoked_subcommand = args and '*' or None + Command.invoke(self, ctx) + + # Otherwise we make every single context and invoke them in a + # chain. In that case the return value to the result processor + # is the list of all invoked subcommand's results. + contexts = [] + while args: + cmd_name, cmd, args = self.resolve_command(ctx, args) + sub_ctx = cmd.make_context(cmd_name, args, parent=ctx, + allow_extra_args=True, + allow_interspersed_args=False) + contexts.append(sub_ctx) + args, sub_ctx.args = sub_ctx.args, [] + + rv = [] + for sub_ctx in contexts: + with sub_ctx: + rv.append(sub_ctx.command.invoke(sub_ctx)) + return _process_result(rv) + + def resolve_command(self, ctx, args): + cmd_name = make_str(args[0]) + original_cmd_name = cmd_name + + # Get the command + cmd = self.get_command(ctx, cmd_name) + + # If we can't find the command but there is a normalization + # function available, we try with that one. + if cmd is None and ctx.token_normalize_func is not None: + cmd_name = ctx.token_normalize_func(cmd_name) + cmd = self.get_command(ctx, cmd_name) + + # If we don't find the command we want to show an error message + # to the user that it was not provided. However, there is + # something else we should do: if the first argument looks like + # an option we want to kick off parsing again for arguments to + # resolve things like --help which now should go to the main + # place. + if cmd is None and not ctx.resilient_parsing: + if split_opt(cmd_name)[0]: + self.parse_args(ctx, ctx.args) + ctx.fail('No such command "%s".' % original_cmd_name) + + return cmd_name, cmd, args[1:] + + def get_command(self, ctx, cmd_name): + """Given a context and a command name, this returns a + :class:`Command` object if it exists or returns `None`. + """ + raise NotImplementedError() + + def list_commands(self, ctx): + """Returns a list of subcommand names in the order they should + appear. + """ + return [] + + +class Group(MultiCommand): + """A group allows a command to have subcommands attached. This is the + most common way to implement nesting in Click. + + :param commands: a dictionary of commands. + """ + + def __init__(self, name=None, commands=None, **attrs): + MultiCommand.__init__(self, name, **attrs) + #: the registered subcommands by their exported names. + self.commands = commands or {} + + def add_command(self, cmd, name=None): + """Registers another :class:`Command` with this group. If the name + is not provided, the name of the command is used. + """ + name = name or cmd.name + if name is None: + raise TypeError('Command has no name.') + _check_multicommand(self, name, cmd, register=True) + self.commands[name] = cmd + + def command(self, *args, **kwargs): + """A shortcut decorator for declaring and attaching a command to + the group. This takes the same arguments as :func:`command` but + immediately registers the created command with this instance by + calling into :meth:`add_command`. + """ + def decorator(f): + cmd = command(*args, **kwargs)(f) + self.add_command(cmd) + return cmd + return decorator + + def group(self, *args, **kwargs): + """A shortcut decorator for declaring and attaching a group to + the group. This takes the same arguments as :func:`group` but + immediately registers the created command with this instance by + calling into :meth:`add_command`. + """ + def decorator(f): + cmd = group(*args, **kwargs)(f) + self.add_command(cmd) + return cmd + return decorator + + def get_command(self, ctx, cmd_name): + return self.commands.get(cmd_name) + + def list_commands(self, ctx): + return sorted(self.commands) + + +class CommandCollection(MultiCommand): + """A command collection is a multi command that merges multiple multi + commands together into one. This is a straightforward implementation + that accepts a list of different multi commands as sources and + provides all the commands for each of them. + """ + + def __init__(self, name=None, sources=None, **attrs): + MultiCommand.__init__(self, name, **attrs) + #: The list of registered multi commands. + self.sources = sources or [] + + def add_source(self, multi_cmd): + """Adds a new multi command to the chain dispatcher.""" + self.sources.append(multi_cmd) + + def get_command(self, ctx, cmd_name): + for source in self.sources: + rv = source.get_command(ctx, cmd_name) + if rv is not None: + if self.chain: + _check_multicommand(self, cmd_name, rv) + return rv + + def list_commands(self, ctx): + rv = set() + for source in self.sources: + rv.update(source.list_commands(ctx)) + return sorted(rv) + + +class Parameter(object): + r"""A parameter to a command comes in two versions: they are either + :class:`Option`\s or :class:`Argument`\s. Other subclasses are currently + not supported by design as some of the internals for parsing are + intentionally not finalized. + + Some settings are supported by both options and arguments. + + .. versionchanged:: 2.0 + Changed signature for parameter callback to also be passed the + parameter. In Click 2.0, the old callback format will still work, + but it will raise a warning to give you change to migrate the + code easier. + + :param param_decls: the parameter declarations for this option or + argument. This is a list of flags or argument + names. + :param type: the type that should be used. Either a :class:`ParamType` + or a Python type. The later is converted into the former + automatically if supported. + :param required: controls if this is optional or not. + :param default: the default value if omitted. This can also be a callable, + in which case it's invoked when the default is needed + without any arguments. + :param callback: a callback that should be executed after the parameter + was matched. This is called as ``fn(ctx, param, + value)`` and needs to return the value. Before Click + 2.0, the signature was ``(ctx, value)``. + :param nargs: the number of arguments to match. If not ``1`` the return + value is a tuple instead of single value. The default for + nargs is ``1`` (except if the type is a tuple, then it's + the arity of the tuple). + :param metavar: how the value is represented in the help page. + :param expose_value: if this is `True` then the value is passed onwards + to the command callback and stored on the context, + otherwise it's skipped. + :param is_eager: eager values are processed before non eager ones. This + should not be set for arguments or it will inverse the + order of processing. + :param envvar: a string or list of strings that are environment variables + that should be checked. + """ + param_type_name = 'parameter' + + def __init__(self, param_decls=None, type=None, required=False, + default=None, callback=None, nargs=None, metavar=None, + expose_value=True, is_eager=False, envvar=None, + autocompletion=None): + self.name, self.opts, self.secondary_opts = \ + self._parse_decls(param_decls or (), expose_value) + + self.type = convert_type(type, default) + + # Default nargs to what the type tells us if we have that + # information available. + if nargs is None: + if self.type.is_composite: + nargs = self.type.arity + else: + nargs = 1 + + self.required = required + self.callback = callback + self.nargs = nargs + self.multiple = False + self.expose_value = expose_value + self.default = default + self.is_eager = is_eager + self.metavar = metavar + self.envvar = envvar + self.autocompletion = autocompletion + + @property + def human_readable_name(self): + """Returns the human readable name of this parameter. This is the + same as the name for options, but the metavar for arguments. + """ + return self.name + + def make_metavar(self): + if self.metavar is not None: + return self.metavar + metavar = self.type.get_metavar(self) + if metavar is None: + metavar = self.type.name.upper() + if self.nargs != 1: + metavar += '...' + return metavar + + def get_default(self, ctx): + """Given a context variable this calculates the default value.""" + # Otherwise go with the regular default. + if callable(self.default): + rv = self.default() + else: + rv = self.default + return self.type_cast_value(ctx, rv) + + def add_to_parser(self, parser, ctx): + pass + + def consume_value(self, ctx, opts): + value = opts.get(self.name) + if value is None: + value = self.value_from_envvar(ctx) + if value is None: + value = ctx.lookup_default(self.name) + return value + + def type_cast_value(self, ctx, value): + """Given a value this runs it properly through the type system. + This automatically handles things like `nargs` and `multiple` as + well as composite types. + """ + if self.type.is_composite: + if self.nargs <= 1: + raise TypeError('Attempted to invoke composite type ' + 'but nargs has been set to %s. This is ' + 'not supported; nargs needs to be set to ' + 'a fixed value > 1.' % self.nargs) + if self.multiple: + return tuple(self.type(x or (), self, ctx) for x in value or ()) + return self.type(value or (), self, ctx) + + def _convert(value, level): + if level == 0: + return self.type(value, self, ctx) + return tuple(_convert(x, level - 1) for x in value or ()) + return _convert(value, (self.nargs != 1) + bool(self.multiple)) + + def process_value(self, ctx, value): + """Given a value and context this runs the logic to convert the + value as necessary. + """ + # If the value we were given is None we do nothing. This way + # code that calls this can easily figure out if something was + # not provided. Otherwise it would be converted into an empty + # tuple for multiple invocations which is inconvenient. + if value is not None: + return self.type_cast_value(ctx, value) + + def value_is_missing(self, value): + if value is None: + return True + if (self.nargs != 1 or self.multiple) and value == (): + return True + return False + + def full_process_value(self, ctx, value): + value = self.process_value(ctx, value) + + if value is None and not ctx.resilient_parsing: + value = self.get_default(ctx) + + if self.required and self.value_is_missing(value): + raise MissingParameter(ctx=ctx, param=self) + + return value + + def resolve_envvar_value(self, ctx): + if self.envvar is None: + return + if isinstance(self.envvar, (tuple, list)): + for envvar in self.envvar: + rv = os.environ.get(envvar) + if rv is not None: + return rv + else: + return os.environ.get(self.envvar) + + def value_from_envvar(self, ctx): + rv = self.resolve_envvar_value(ctx) + if rv is not None and self.nargs != 1: + rv = self.type.split_envvar_value(rv) + return rv + + def handle_parse_result(self, ctx, opts, args): + with augment_usage_errors(ctx, param=self): + value = self.consume_value(ctx, opts) + try: + value = self.full_process_value(ctx, value) + except Exception: + if not ctx.resilient_parsing: + raise + value = None + if self.callback is not None: + try: + value = invoke_param_callback( + self.callback, ctx, self, value) + except Exception: + if not ctx.resilient_parsing: + raise + + if self.expose_value: + ctx.params[self.name] = value + return value, args + + def get_help_record(self, ctx): + pass + + def get_usage_pieces(self, ctx): + return [] + + def get_error_hint(self, ctx): + """Get a stringified version of the param for use in error messages to + indicate which param caused the error. + """ + hint_list = self.opts or [self.human_readable_name] + return ' / '.join('"%s"' % x for x in hint_list) + + +class Option(Parameter): + """Options are usually optional values on the command line and + have some extra features that arguments don't have. + + All other parameters are passed onwards to the parameter constructor. + + :param show_default: controls if the default value should be shown on the + help page. Normally, defaults are not shown. If this + value is a string, it shows the string instead of the + value. This is particularly useful for dynamic options. + :param show_envvar: controls if an environment variable should be shown on + the help page. Normally, environment variables + are not shown. + :param prompt: if set to `True` or a non empty string then the user will be + prompted for input. If set to `True` the prompt will be the + option name capitalized. + :param confirmation_prompt: if set then the value will need to be confirmed + if it was prompted for. + :param hide_input: if this is `True` then the input on the prompt will be + hidden from the user. This is useful for password + input. + :param is_flag: forces this option to act as a flag. The default is + auto detection. + :param flag_value: which value should be used for this flag if it's + enabled. This is set to a boolean automatically if + the option string contains a slash to mark two options. + :param multiple: if this is set to `True` then the argument is accepted + multiple times and recorded. This is similar to ``nargs`` + in how it works but supports arbitrary number of + arguments. + :param count: this flag makes an option increment an integer. + :param allow_from_autoenv: if this is enabled then the value of this + parameter will be pulled from an environment + variable in case a prefix is defined on the + context. + :param help: the help string. + :param hidden: hide this option from help outputs. + """ + param_type_name = 'option' + + def __init__(self, param_decls=None, show_default=False, + prompt=False, confirmation_prompt=False, + hide_input=False, is_flag=None, flag_value=None, + multiple=False, count=False, allow_from_autoenv=True, + type=None, help=None, hidden=False, show_choices=True, + show_envvar=False, **attrs): + default_is_missing = attrs.get('default', _missing) is _missing + Parameter.__init__(self, param_decls, type=type, **attrs) + + if prompt is True: + prompt_text = self.name.replace('_', ' ').capitalize() + elif prompt is False: + prompt_text = None + else: + prompt_text = prompt + self.prompt = prompt_text + self.confirmation_prompt = confirmation_prompt + self.hide_input = hide_input + self.hidden = hidden + + # Flags + if is_flag is None: + if flag_value is not None: + is_flag = True + else: + is_flag = bool(self.secondary_opts) + if is_flag and default_is_missing: + self.default = False + if flag_value is None: + flag_value = not self.default + self.is_flag = is_flag + self.flag_value = flag_value + if self.is_flag and isinstance(self.flag_value, bool) \ + and type is None: + self.type = BOOL + self.is_bool_flag = True + else: + self.is_bool_flag = False + + # Counting + self.count = count + if count: + if type is None: + self.type = IntRange(min=0) + if default_is_missing: + self.default = 0 + + self.multiple = multiple + self.allow_from_autoenv = allow_from_autoenv + self.help = help + self.show_default = show_default + self.show_choices = show_choices + self.show_envvar = show_envvar + + # Sanity check for stuff we don't support + if __debug__: + if self.nargs < 0: + raise TypeError('Options cannot have nargs < 0') + if self.prompt and self.is_flag and not self.is_bool_flag: + raise TypeError('Cannot prompt for flags that are not bools.') + if not self.is_bool_flag and self.secondary_opts: + raise TypeError('Got secondary option for non boolean flag.') + if self.is_bool_flag and self.hide_input \ + and self.prompt is not None: + raise TypeError('Hidden input does not work with boolean ' + 'flag prompts.') + if self.count: + if self.multiple: + raise TypeError('Options cannot be multiple and count ' + 'at the same time.') + elif self.is_flag: + raise TypeError('Options cannot be count and flags at ' + 'the same time.') + + def _parse_decls(self, decls, expose_value): + opts = [] + secondary_opts = [] + name = None + possible_names = [] + + for decl in decls: + if isidentifier(decl): + if name is not None: + raise TypeError('Name defined twice') + name = decl + else: + split_char = decl[:1] == '/' and ';' or '/' + if split_char in decl: + first, second = decl.split(split_char, 1) + first = first.rstrip() + if first: + possible_names.append(split_opt(first)) + opts.append(first) + second = second.lstrip() + if second: + secondary_opts.append(second.lstrip()) + else: + possible_names.append(split_opt(decl)) + opts.append(decl) + + if name is None and possible_names: + possible_names.sort(key=lambda x: -len(x[0])) # group long options first + name = possible_names[0][1].replace('-', '_').lower() + if not isidentifier(name): + name = None + + if name is None: + if not expose_value: + return None, opts, secondary_opts + raise TypeError('Could not determine name for option') + + if not opts and not secondary_opts: + raise TypeError('No options defined but a name was passed (%s). ' + 'Did you mean to declare an argument instead ' + 'of an option?' % name) + + return name, opts, secondary_opts + + def add_to_parser(self, parser, ctx): + kwargs = { + 'dest': self.name, + 'nargs': self.nargs, + 'obj': self, + } + + if self.multiple: + action = 'append' + elif self.count: + action = 'count' + else: + action = 'store' + + if self.is_flag: + kwargs.pop('nargs', None) + if self.is_bool_flag and self.secondary_opts: + parser.add_option(self.opts, action=action + '_const', + const=True, **kwargs) + parser.add_option(self.secondary_opts, action=action + + '_const', const=False, **kwargs) + else: + parser.add_option(self.opts, action=action + '_const', + const=self.flag_value, + **kwargs) + else: + kwargs['action'] = action + parser.add_option(self.opts, **kwargs) + + def get_help_record(self, ctx): + if self.hidden: + return + any_prefix_is_slash = [] + + def _write_opts(opts): + rv, any_slashes = join_options(opts) + if any_slashes: + any_prefix_is_slash[:] = [True] + if not self.is_flag and not self.count: + rv += ' ' + self.make_metavar() + return rv + + rv = [_write_opts(self.opts)] + if self.secondary_opts: + rv.append(_write_opts(self.secondary_opts)) + + help = self.help or '' + extra = [] + if self.show_envvar: + envvar = self.envvar + if envvar is None: + if self.allow_from_autoenv and \ + ctx.auto_envvar_prefix is not None: + envvar = '%s_%s' % (ctx.auto_envvar_prefix, self.name.upper()) + if envvar is not None: + extra.append('env var: %s' % ( + ', '.join('%s' % d for d in envvar) + if isinstance(envvar, (list, tuple)) + else envvar, )) + if self.default is not None and self.show_default: + if isinstance(self.show_default, string_types): + default_string = '({})'.format(self.show_default) + elif isinstance(self.default, (list, tuple)): + default_string = ', '.join('%s' % d for d in self.default) + elif inspect.isfunction(self.default): + default_string = "(dynamic)" + else: + default_string = self.default + extra.append('default: {}'.format(default_string)) + + if self.required: + extra.append('required') + if extra: + help = '%s[%s]' % (help and help + ' ' or '', '; '.join(extra)) + + return ((any_prefix_is_slash and '; ' or ' / ').join(rv), help) + + def get_default(self, ctx): + # If we're a non boolean flag out default is more complex because + # we need to look at all flags in the same group to figure out + # if we're the the default one in which case we return the flag + # value as default. + if self.is_flag and not self.is_bool_flag: + for param in ctx.command.params: + if param.name == self.name and param.default: + return param.flag_value + return None + return Parameter.get_default(self, ctx) + + def prompt_for_value(self, ctx): + """This is an alternative flow that can be activated in the full + value processing if a value does not exist. It will prompt the + user until a valid value exists and then returns the processed + value as result. + """ + # Calculate the default before prompting anything to be stable. + default = self.get_default(ctx) + + # If this is a prompt for a flag we need to handle this + # differently. + if self.is_bool_flag: + return confirm(self.prompt, default) + + return prompt(self.prompt, default=default, type=self.type, + hide_input=self.hide_input, show_choices=self.show_choices, + confirmation_prompt=self.confirmation_prompt, + value_proc=lambda x: self.process_value(ctx, x)) + + def resolve_envvar_value(self, ctx): + rv = Parameter.resolve_envvar_value(self, ctx) + if rv is not None: + return rv + if self.allow_from_autoenv and \ + ctx.auto_envvar_prefix is not None: + envvar = '%s_%s' % (ctx.auto_envvar_prefix, self.name.upper()) + return os.environ.get(envvar) + + def value_from_envvar(self, ctx): + rv = self.resolve_envvar_value(ctx) + if rv is None: + return None + value_depth = (self.nargs != 1) + bool(self.multiple) + if value_depth > 0 and rv is not None: + rv = self.type.split_envvar_value(rv) + if self.multiple and self.nargs != 1: + rv = batch(rv, self.nargs) + return rv + + def full_process_value(self, ctx, value): + if value is None and self.prompt is not None \ + and not ctx.resilient_parsing: + return self.prompt_for_value(ctx) + return Parameter.full_process_value(self, ctx, value) + + +class Argument(Parameter): + """Arguments are positional parameters to a command. They generally + provide fewer features than options but can have infinite ``nargs`` + and are required by default. + + All parameters are passed onwards to the parameter constructor. + """ + param_type_name = 'argument' + + def __init__(self, param_decls, required=None, **attrs): + if required is None: + if attrs.get('default') is not None: + required = False + else: + required = attrs.get('nargs', 1) > 0 + Parameter.__init__(self, param_decls, required=required, **attrs) + if self.default is not None and self.nargs < 0: + raise TypeError('nargs=-1 in combination with a default value ' + 'is not supported.') + + @property + def human_readable_name(self): + if self.metavar is not None: + return self.metavar + return self.name.upper() + + def make_metavar(self): + if self.metavar is not None: + return self.metavar + var = self.type.get_metavar(self) + if not var: + var = self.name.upper() + if not self.required: + var = '[%s]' % var + if self.nargs != 1: + var += '...' + return var + + def _parse_decls(self, decls, expose_value): + if not decls: + if not expose_value: + return None, [], [] + raise TypeError('Could not determine name for argument') + if len(decls) == 1: + name = arg = decls[0] + name = name.replace('-', '_').lower() + else: + raise TypeError('Arguments take exactly one ' + 'parameter declaration, got %d' % len(decls)) + return name, [arg], [] + + def get_usage_pieces(self, ctx): + return [self.make_metavar()] + + def get_error_hint(self, ctx): + return '"%s"' % self.make_metavar() + + def add_to_parser(self, parser, ctx): + parser.add_argument(dest=self.name, nargs=self.nargs, + obj=self) + + +# Circular dependency between decorators and core +from .decorators import command, group diff --git a/libs/click/decorators.py b/libs/click/decorators.py new file mode 100644 index 00000000..c57c5308 --- /dev/null +++ b/libs/click/decorators.py @@ -0,0 +1,311 @@ +import sys +import inspect + +from functools import update_wrapper + +from ._compat import iteritems +from ._unicodefun import _check_for_unicode_literals +from .utils import echo +from .globals import get_current_context + + +def pass_context(f): + """Marks a callback as wanting to receive the current context + object as first argument. + """ + def new_func(*args, **kwargs): + return f(get_current_context(), *args, **kwargs) + return update_wrapper(new_func, f) + + +def pass_obj(f): + """Similar to :func:`pass_context`, but only pass the object on the + context onwards (:attr:`Context.obj`). This is useful if that object + represents the state of a nested system. + """ + def new_func(*args, **kwargs): + return f(get_current_context().obj, *args, **kwargs) + return update_wrapper(new_func, f) + + +def make_pass_decorator(object_type, ensure=False): + """Given an object type this creates a decorator that will work + similar to :func:`pass_obj` but instead of passing the object of the + current context, it will find the innermost context of type + :func:`object_type`. + + This generates a decorator that works roughly like this:: + + from functools import update_wrapper + + def decorator(f): + @pass_context + def new_func(ctx, *args, **kwargs): + obj = ctx.find_object(object_type) + return ctx.invoke(f, obj, *args, **kwargs) + return update_wrapper(new_func, f) + return decorator + + :param object_type: the type of the object to pass. + :param ensure: if set to `True`, a new object will be created and + remembered on the context if it's not there yet. + """ + def decorator(f): + def new_func(*args, **kwargs): + ctx = get_current_context() + if ensure: + obj = ctx.ensure_object(object_type) + else: + obj = ctx.find_object(object_type) + if obj is None: + raise RuntimeError('Managed to invoke callback without a ' + 'context object of type %r existing' + % object_type.__name__) + return ctx.invoke(f, obj, *args, **kwargs) + return update_wrapper(new_func, f) + return decorator + + +def _make_command(f, name, attrs, cls): + if isinstance(f, Command): + raise TypeError('Attempted to convert a callback into a ' + 'command twice.') + try: + params = f.__click_params__ + params.reverse() + del f.__click_params__ + except AttributeError: + params = [] + help = attrs.get('help') + if help is None: + help = inspect.getdoc(f) + if isinstance(help, bytes): + help = help.decode('utf-8') + else: + help = inspect.cleandoc(help) + attrs['help'] = help + _check_for_unicode_literals() + return cls(name=name or f.__name__.lower().replace('_', '-'), + callback=f, params=params, **attrs) + + +def command(name=None, cls=None, **attrs): + r"""Creates a new :class:`Command` and uses the decorated function as + callback. This will also automatically attach all decorated + :func:`option`\s and :func:`argument`\s as parameters to the command. + + The name of the command defaults to the name of the function. If you + want to change that, you can pass the intended name as the first + argument. + + All keyword arguments are forwarded to the underlying command class. + + Once decorated the function turns into a :class:`Command` instance + that can be invoked as a command line utility or be attached to a + command :class:`Group`. + + :param name: the name of the command. This defaults to the function + name with underscores replaced by dashes. + :param cls: the command class to instantiate. This defaults to + :class:`Command`. + """ + if cls is None: + cls = Command + def decorator(f): + cmd = _make_command(f, name, attrs, cls) + cmd.__doc__ = f.__doc__ + return cmd + return decorator + + +def group(name=None, **attrs): + """Creates a new :class:`Group` with a function as callback. This + works otherwise the same as :func:`command` just that the `cls` + parameter is set to :class:`Group`. + """ + attrs.setdefault('cls', Group) + return command(name, **attrs) + + +def _param_memo(f, param): + if isinstance(f, Command): + f.params.append(param) + else: + if not hasattr(f, '__click_params__'): + f.__click_params__ = [] + f.__click_params__.append(param) + + +def argument(*param_decls, **attrs): + """Attaches an argument to the command. All positional arguments are + passed as parameter declarations to :class:`Argument`; all keyword + arguments are forwarded unchanged (except ``cls``). + This is equivalent to creating an :class:`Argument` instance manually + and attaching it to the :attr:`Command.params` list. + + :param cls: the argument class to instantiate. This defaults to + :class:`Argument`. + """ + def decorator(f): + ArgumentClass = attrs.pop('cls', Argument) + _param_memo(f, ArgumentClass(param_decls, **attrs)) + return f + return decorator + + +def option(*param_decls, **attrs): + """Attaches an option to the command. All positional arguments are + passed as parameter declarations to :class:`Option`; all keyword + arguments are forwarded unchanged (except ``cls``). + This is equivalent to creating an :class:`Option` instance manually + and attaching it to the :attr:`Command.params` list. + + :param cls: the option class to instantiate. This defaults to + :class:`Option`. + """ + def decorator(f): + # Issue 926, copy attrs, so pre-defined options can re-use the same cls= + option_attrs = attrs.copy() + + if 'help' in option_attrs: + option_attrs['help'] = inspect.cleandoc(option_attrs['help']) + OptionClass = option_attrs.pop('cls', Option) + _param_memo(f, OptionClass(param_decls, **option_attrs)) + return f + return decorator + + +def confirmation_option(*param_decls, **attrs): + """Shortcut for confirmation prompts that can be ignored by passing + ``--yes`` as parameter. + + This is equivalent to decorating a function with :func:`option` with + the following parameters:: + + def callback(ctx, param, value): + if not value: + ctx.abort() + + @click.command() + @click.option('--yes', is_flag=True, callback=callback, + expose_value=False, prompt='Do you want to continue?') + def dropdb(): + pass + """ + def decorator(f): + def callback(ctx, param, value): + if not value: + ctx.abort() + attrs.setdefault('is_flag', True) + attrs.setdefault('callback', callback) + attrs.setdefault('expose_value', False) + attrs.setdefault('prompt', 'Do you want to continue?') + attrs.setdefault('help', 'Confirm the action without prompting.') + return option(*(param_decls or ('--yes',)), **attrs)(f) + return decorator + + +def password_option(*param_decls, **attrs): + """Shortcut for password prompts. + + This is equivalent to decorating a function with :func:`option` with + the following parameters:: + + @click.command() + @click.option('--password', prompt=True, confirmation_prompt=True, + hide_input=True) + def changeadmin(password): + pass + """ + def decorator(f): + attrs.setdefault('prompt', True) + attrs.setdefault('confirmation_prompt', True) + attrs.setdefault('hide_input', True) + return option(*(param_decls or ('--password',)), **attrs)(f) + return decorator + + +def version_option(version=None, *param_decls, **attrs): + """Adds a ``--version`` option which immediately ends the program + printing out the version number. This is implemented as an eager + option that prints the version and exits the program in the callback. + + :param version: the version number to show. If not provided Click + attempts an auto discovery via setuptools. + :param prog_name: the name of the program (defaults to autodetection) + :param message: custom message to show instead of the default + (``'%(prog)s, version %(version)s'``) + :param others: everything else is forwarded to :func:`option`. + """ + if version is None: + if hasattr(sys, '_getframe'): + module = sys._getframe(1).f_globals.get('__name__') + else: + module = '' + + def decorator(f): + prog_name = attrs.pop('prog_name', None) + message = attrs.pop('message', '%(prog)s, version %(version)s') + + def callback(ctx, param, value): + if not value or ctx.resilient_parsing: + return + prog = prog_name + if prog is None: + prog = ctx.find_root().info_name + ver = version + if ver is None: + try: + import pkg_resources + except ImportError: + pass + else: + for dist in pkg_resources.working_set: + scripts = dist.get_entry_map().get('console_scripts') or {} + for script_name, entry_point in iteritems(scripts): + if entry_point.module_name == module: + ver = dist.version + break + if ver is None: + raise RuntimeError('Could not determine version') + echo(message % { + 'prog': prog, + 'version': ver, + }, color=ctx.color) + ctx.exit() + + attrs.setdefault('is_flag', True) + attrs.setdefault('expose_value', False) + attrs.setdefault('is_eager', True) + attrs.setdefault('help', 'Show the version and exit.') + attrs['callback'] = callback + return option(*(param_decls or ('--version',)), **attrs)(f) + return decorator + + +def help_option(*param_decls, **attrs): + """Adds a ``--help`` option which immediately ends the program + printing out the help page. This is usually unnecessary to add as + this is added by default to all commands unless suppressed. + + Like :func:`version_option`, this is implemented as eager option that + prints in the callback and exits. + + All arguments are forwarded to :func:`option`. + """ + def decorator(f): + def callback(ctx, param, value): + if value and not ctx.resilient_parsing: + echo(ctx.get_help(), color=ctx.color) + ctx.exit() + attrs.setdefault('is_flag', True) + attrs.setdefault('expose_value', False) + attrs.setdefault('help', 'Show this message and exit.') + attrs.setdefault('is_eager', True) + attrs['callback'] = callback + return option(*(param_decls or ('--help',)), **attrs)(f) + return decorator + + +# Circular dependencies between core and decorators +from .core import Command, Group, Argument, Option diff --git a/libs/click/exceptions.py b/libs/click/exceptions.py new file mode 100644 index 00000000..6fa17658 --- /dev/null +++ b/libs/click/exceptions.py @@ -0,0 +1,235 @@ +from ._compat import PY2, filename_to_ui, get_text_stderr +from .utils import echo + + +def _join_param_hints(param_hint): + if isinstance(param_hint, (tuple, list)): + return ' / '.join('"%s"' % x for x in param_hint) + return param_hint + + +class ClickException(Exception): + """An exception that Click can handle and show to the user.""" + + #: The exit code for this exception + exit_code = 1 + + def __init__(self, message): + ctor_msg = message + if PY2: + if ctor_msg is not None: + ctor_msg = ctor_msg.encode('utf-8') + Exception.__init__(self, ctor_msg) + self.message = message + + def format_message(self): + return self.message + + def __str__(self): + return self.message + + if PY2: + __unicode__ = __str__ + + def __str__(self): + return self.message.encode('utf-8') + + def show(self, file=None): + if file is None: + file = get_text_stderr() + echo('Error: %s' % self.format_message(), file=file) + + +class UsageError(ClickException): + """An internal exception that signals a usage error. This typically + aborts any further handling. + + :param message: the error message to display. + :param ctx: optionally the context that caused this error. Click will + fill in the context automatically in some situations. + """ + exit_code = 2 + + def __init__(self, message, ctx=None): + ClickException.__init__(self, message) + self.ctx = ctx + self.cmd = self.ctx and self.ctx.command or None + + def show(self, file=None): + if file is None: + file = get_text_stderr() + color = None + hint = '' + if (self.cmd is not None and + self.cmd.get_help_option(self.ctx) is not None): + hint = ('Try "%s %s" for help.\n' + % (self.ctx.command_path, self.ctx.help_option_names[0])) + if self.ctx is not None: + color = self.ctx.color + echo(self.ctx.get_usage() + '\n%s' % hint, file=file, color=color) + echo('Error: %s' % self.format_message(), file=file, color=color) + + +class BadParameter(UsageError): + """An exception that formats out a standardized error message for a + bad parameter. This is useful when thrown from a callback or type as + Click will attach contextual information to it (for instance, which + parameter it is). + + .. versionadded:: 2.0 + + :param param: the parameter object that caused this error. This can + be left out, and Click will attach this info itself + if possible. + :param param_hint: a string that shows up as parameter name. This + can be used as alternative to `param` in cases + where custom validation should happen. If it is + a string it's used as such, if it's a list then + each item is quoted and separated. + """ + + def __init__(self, message, ctx=None, param=None, + param_hint=None): + UsageError.__init__(self, message, ctx) + self.param = param + self.param_hint = param_hint + + def format_message(self): + if self.param_hint is not None: + param_hint = self.param_hint + elif self.param is not None: + param_hint = self.param.get_error_hint(self.ctx) + else: + return 'Invalid value: %s' % self.message + param_hint = _join_param_hints(param_hint) + + return 'Invalid value for %s: %s' % (param_hint, self.message) + + +class MissingParameter(BadParameter): + """Raised if click required an option or argument but it was not + provided when invoking the script. + + .. versionadded:: 4.0 + + :param param_type: a string that indicates the type of the parameter. + The default is to inherit the parameter type from + the given `param`. Valid values are ``'parameter'``, + ``'option'`` or ``'argument'``. + """ + + def __init__(self, message=None, ctx=None, param=None, + param_hint=None, param_type=None): + BadParameter.__init__(self, message, ctx, param, param_hint) + self.param_type = param_type + + def format_message(self): + if self.param_hint is not None: + param_hint = self.param_hint + elif self.param is not None: + param_hint = self.param.get_error_hint(self.ctx) + else: + param_hint = None + param_hint = _join_param_hints(param_hint) + + param_type = self.param_type + if param_type is None and self.param is not None: + param_type = self.param.param_type_name + + msg = self.message + if self.param is not None: + msg_extra = self.param.type.get_missing_message(self.param) + if msg_extra: + if msg: + msg += '. ' + msg_extra + else: + msg = msg_extra + + return 'Missing %s%s%s%s' % ( + param_type, + param_hint and ' %s' % param_hint or '', + msg and '. ' or '.', + msg or '', + ) + + +class NoSuchOption(UsageError): + """Raised if click attempted to handle an option that does not + exist. + + .. versionadded:: 4.0 + """ + + def __init__(self, option_name, message=None, possibilities=None, + ctx=None): + if message is None: + message = 'no such option: %s' % option_name + UsageError.__init__(self, message, ctx) + self.option_name = option_name + self.possibilities = possibilities + + def format_message(self): + bits = [self.message] + if self.possibilities: + if len(self.possibilities) == 1: + bits.append('Did you mean %s?' % self.possibilities[0]) + else: + possibilities = sorted(self.possibilities) + bits.append('(Possible options: %s)' % ', '.join(possibilities)) + return ' '.join(bits) + + +class BadOptionUsage(UsageError): + """Raised if an option is generally supplied but the use of the option + was incorrect. This is for instance raised if the number of arguments + for an option is not correct. + + .. versionadded:: 4.0 + + :param option_name: the name of the option being used incorrectly. + """ + + def __init__(self, option_name, message, ctx=None): + UsageError.__init__(self, message, ctx) + self.option_name = option_name + + +class BadArgumentUsage(UsageError): + """Raised if an argument is generally supplied but the use of the argument + was incorrect. This is for instance raised if the number of values + for an argument is not correct. + + .. versionadded:: 6.0 + """ + + def __init__(self, message, ctx=None): + UsageError.__init__(self, message, ctx) + + +class FileError(ClickException): + """Raised if a file cannot be opened.""" + + def __init__(self, filename, hint=None): + ui_filename = filename_to_ui(filename) + if hint is None: + hint = 'unknown error' + ClickException.__init__(self, hint) + self.ui_filename = ui_filename + self.filename = filename + + def format_message(self): + return 'Could not open file %s: %s' % (self.ui_filename, self.message) + + +class Abort(RuntimeError): + """An internal signalling exception that signals Click to abort.""" + + +class Exit(RuntimeError): + """An exception that indicates that the application should exit with some + status code. + + :param code: the status code to exit with. + """ + def __init__(self, code=0): + self.exit_code = code diff --git a/libs/click/formatting.py b/libs/click/formatting.py new file mode 100644 index 00000000..a3d6a4d3 --- /dev/null +++ b/libs/click/formatting.py @@ -0,0 +1,256 @@ +from contextlib import contextmanager +from .termui import get_terminal_size +from .parser import split_opt +from ._compat import term_len + + +# Can force a width. This is used by the test system +FORCED_WIDTH = None + + +def measure_table(rows): + widths = {} + for row in rows: + for idx, col in enumerate(row): + widths[idx] = max(widths.get(idx, 0), term_len(col)) + return tuple(y for x, y in sorted(widths.items())) + + +def iter_rows(rows, col_count): + for row in rows: + row = tuple(row) + yield row + ('',) * (col_count - len(row)) + + +def wrap_text(text, width=78, initial_indent='', subsequent_indent='', + preserve_paragraphs=False): + """A helper function that intelligently wraps text. By default, it + assumes that it operates on a single paragraph of text but if the + `preserve_paragraphs` parameter is provided it will intelligently + handle paragraphs (defined by two empty lines). + + If paragraphs are handled, a paragraph can be prefixed with an empty + line containing the ``\\b`` character (``\\x08``) to indicate that + no rewrapping should happen in that block. + + :param text: the text that should be rewrapped. + :param width: the maximum width for the text. + :param initial_indent: the initial indent that should be placed on the + first line as a string. + :param subsequent_indent: the indent string that should be placed on + each consecutive line. + :param preserve_paragraphs: if this flag is set then the wrapping will + intelligently handle paragraphs. + """ + from ._textwrap import TextWrapper + text = text.expandtabs() + wrapper = TextWrapper(width, initial_indent=initial_indent, + subsequent_indent=subsequent_indent, + replace_whitespace=False) + if not preserve_paragraphs: + return wrapper.fill(text) + + p = [] + buf = [] + indent = None + + def _flush_par(): + if not buf: + return + if buf[0].strip() == '\b': + p.append((indent or 0, True, '\n'.join(buf[1:]))) + else: + p.append((indent or 0, False, ' '.join(buf))) + del buf[:] + + for line in text.splitlines(): + if not line: + _flush_par() + indent = None + else: + if indent is None: + orig_len = term_len(line) + line = line.lstrip() + indent = orig_len - term_len(line) + buf.append(line) + _flush_par() + + rv = [] + for indent, raw, text in p: + with wrapper.extra_indent(' ' * indent): + if raw: + rv.append(wrapper.indent_only(text)) + else: + rv.append(wrapper.fill(text)) + + return '\n\n'.join(rv) + + +class HelpFormatter(object): + """This class helps with formatting text-based help pages. It's + usually just needed for very special internal cases, but it's also + exposed so that developers can write their own fancy outputs. + + At present, it always writes into memory. + + :param indent_increment: the additional increment for each level. + :param width: the width for the text. This defaults to the terminal + width clamped to a maximum of 78. + """ + + def __init__(self, indent_increment=2, width=None, max_width=None): + self.indent_increment = indent_increment + if max_width is None: + max_width = 80 + if width is None: + width = FORCED_WIDTH + if width is None: + width = max(min(get_terminal_size()[0], max_width) - 2, 50) + self.width = width + self.current_indent = 0 + self.buffer = [] + + def write(self, string): + """Writes a unicode string into the internal buffer.""" + self.buffer.append(string) + + def indent(self): + """Increases the indentation.""" + self.current_indent += self.indent_increment + + def dedent(self): + """Decreases the indentation.""" + self.current_indent -= self.indent_increment + + def write_usage(self, prog, args='', prefix='Usage: '): + """Writes a usage line into the buffer. + + :param prog: the program name. + :param args: whitespace separated list of arguments. + :param prefix: the prefix for the first line. + """ + usage_prefix = '%*s%s ' % (self.current_indent, prefix, prog) + text_width = self.width - self.current_indent + + if text_width >= (term_len(usage_prefix) + 20): + # The arguments will fit to the right of the prefix. + indent = ' ' * term_len(usage_prefix) + self.write(wrap_text(args, text_width, + initial_indent=usage_prefix, + subsequent_indent=indent)) + else: + # The prefix is too long, put the arguments on the next line. + self.write(usage_prefix) + self.write('\n') + indent = ' ' * (max(self.current_indent, term_len(prefix)) + 4) + self.write(wrap_text(args, text_width, + initial_indent=indent, + subsequent_indent=indent)) + + self.write('\n') + + def write_heading(self, heading): + """Writes a heading into the buffer.""" + self.write('%*s%s:\n' % (self.current_indent, '', heading)) + + def write_paragraph(self): + """Writes a paragraph into the buffer.""" + if self.buffer: + self.write('\n') + + def write_text(self, text): + """Writes re-indented text into the buffer. This rewraps and + preserves paragraphs. + """ + text_width = max(self.width - self.current_indent, 11) + indent = ' ' * self.current_indent + self.write(wrap_text(text, text_width, + initial_indent=indent, + subsequent_indent=indent, + preserve_paragraphs=True)) + self.write('\n') + + def write_dl(self, rows, col_max=30, col_spacing=2): + """Writes a definition list into the buffer. This is how options + and commands are usually formatted. + + :param rows: a list of two item tuples for the terms and values. + :param col_max: the maximum width of the first column. + :param col_spacing: the number of spaces between the first and + second column. + """ + rows = list(rows) + widths = measure_table(rows) + if len(widths) != 2: + raise TypeError('Expected two columns for definition list') + + first_col = min(widths[0], col_max) + col_spacing + + for first, second in iter_rows(rows, len(widths)): + self.write('%*s%s' % (self.current_indent, '', first)) + if not second: + self.write('\n') + continue + if term_len(first) <= first_col - col_spacing: + self.write(' ' * (first_col - term_len(first))) + else: + self.write('\n') + self.write(' ' * (first_col + self.current_indent)) + + text_width = max(self.width - first_col - 2, 10) + lines = iter(wrap_text(second, text_width).splitlines()) + if lines: + self.write(next(lines) + '\n') + for line in lines: + self.write('%*s%s\n' % ( + first_col + self.current_indent, '', line)) + else: + self.write('\n') + + @contextmanager + def section(self, name): + """Helpful context manager that writes a paragraph, a heading, + and the indents. + + :param name: the section name that is written as heading. + """ + self.write_paragraph() + self.write_heading(name) + self.indent() + try: + yield + finally: + self.dedent() + + @contextmanager + def indentation(self): + """A context manager that increases the indentation.""" + self.indent() + try: + yield + finally: + self.dedent() + + def getvalue(self): + """Returns the buffer contents.""" + return ''.join(self.buffer) + + +def join_options(options): + """Given a list of option strings this joins them in the most appropriate + way and returns them in the form ``(formatted_string, + any_prefix_is_slash)`` where the second item in the tuple is a flag that + indicates if any of the option prefixes was a slash. + """ + rv = [] + any_prefix_is_slash = False + for opt in options: + prefix = split_opt(opt)[0] + if prefix == '/': + any_prefix_is_slash = True + rv.append((len(prefix), opt)) + + rv.sort(key=lambda x: x[0]) + + rv = ', '.join(x[1] for x in rv) + return rv, any_prefix_is_slash diff --git a/libs/click/globals.py b/libs/click/globals.py new file mode 100644 index 00000000..843b594a --- /dev/null +++ b/libs/click/globals.py @@ -0,0 +1,48 @@ +from threading import local + + +_local = local() + + +def get_current_context(silent=False): + """Returns the current click context. This can be used as a way to + access the current context object from anywhere. This is a more implicit + alternative to the :func:`pass_context` decorator. This function is + primarily useful for helpers such as :func:`echo` which might be + interested in changing its behavior based on the current context. + + To push the current context, :meth:`Context.scope` can be used. + + .. versionadded:: 5.0 + + :param silent: is set to `True` the return value is `None` if no context + is available. The default behavior is to raise a + :exc:`RuntimeError`. + """ + try: + return getattr(_local, 'stack')[-1] + except (AttributeError, IndexError): + if not silent: + raise RuntimeError('There is no active click context.') + + +def push_context(ctx): + """Pushes a new context to the current stack.""" + _local.__dict__.setdefault('stack', []).append(ctx) + + +def pop_context(): + """Removes the top level from the stack.""" + _local.stack.pop() + + +def resolve_color_default(color=None): + """"Internal helper to get the default value of the color flag. If a + value is passed it's returned unchanged, otherwise it's looked up from + the current context. + """ + if color is not None: + return color + ctx = get_current_context(silent=True) + if ctx is not None: + return ctx.color diff --git a/libs/click/parser.py b/libs/click/parser.py new file mode 100644 index 00000000..1c3ae9c8 --- /dev/null +++ b/libs/click/parser.py @@ -0,0 +1,427 @@ +# -*- coding: utf-8 -*- +""" +click.parser +~~~~~~~~~~~~ + +This module started out as largely a copy paste from the stdlib's +optparse module with the features removed that we do not need from +optparse because we implement them in Click on a higher level (for +instance type handling, help formatting and a lot more). + +The plan is to remove more and more from here over time. + +The reason this is a different module and not optparse from the stdlib +is that there are differences in 2.x and 3.x about the error messages +generated and optparse in the stdlib uses gettext for no good reason +and might cause us issues. +""" + +import re +from collections import deque +from .exceptions import UsageError, NoSuchOption, BadOptionUsage, \ + BadArgumentUsage + + +def _unpack_args(args, nargs_spec): + """Given an iterable of arguments and an iterable of nargs specifications, + it returns a tuple with all the unpacked arguments at the first index + and all remaining arguments as the second. + + The nargs specification is the number of arguments that should be consumed + or `-1` to indicate that this position should eat up all the remainders. + + Missing items are filled with `None`. + """ + args = deque(args) + nargs_spec = deque(nargs_spec) + rv = [] + spos = None + + def _fetch(c): + try: + if spos is None: + return c.popleft() + else: + return c.pop() + except IndexError: + return None + + while nargs_spec: + nargs = _fetch(nargs_spec) + if nargs == 1: + rv.append(_fetch(args)) + elif nargs > 1: + x = [_fetch(args) for _ in range(nargs)] + # If we're reversed, we're pulling in the arguments in reverse, + # so we need to turn them around. + if spos is not None: + x.reverse() + rv.append(tuple(x)) + elif nargs < 0: + if spos is not None: + raise TypeError('Cannot have two nargs < 0') + spos = len(rv) + rv.append(None) + + # spos is the position of the wildcard (star). If it's not `None`, + # we fill it with the remainder. + if spos is not None: + rv[spos] = tuple(args) + args = [] + rv[spos + 1:] = reversed(rv[spos + 1:]) + + return tuple(rv), list(args) + + +def _error_opt_args(nargs, opt): + if nargs == 1: + raise BadOptionUsage(opt, '%s option requires an argument' % opt) + raise BadOptionUsage(opt, '%s option requires %d arguments' % (opt, nargs)) + + +def split_opt(opt): + first = opt[:1] + if first.isalnum(): + return '', opt + if opt[1:2] == first: + return opt[:2], opt[2:] + return first, opt[1:] + + +def normalize_opt(opt, ctx): + if ctx is None or ctx.token_normalize_func is None: + return opt + prefix, opt = split_opt(opt) + return prefix + ctx.token_normalize_func(opt) + + +def split_arg_string(string): + """Given an argument string this attempts to split it into small parts.""" + rv = [] + for match in re.finditer(r"('([^'\\]*(?:\\.[^'\\]*)*)'" + r'|"([^"\\]*(?:\\.[^"\\]*)*)"' + r'|\S+)\s*', string, re.S): + arg = match.group().strip() + if arg[:1] == arg[-1:] and arg[:1] in '"\'': + arg = arg[1:-1].encode('ascii', 'backslashreplace') \ + .decode('unicode-escape') + try: + arg = type(string)(arg) + except UnicodeError: + pass + rv.append(arg) + return rv + + +class Option(object): + + def __init__(self, opts, dest, action=None, nargs=1, const=None, obj=None): + self._short_opts = [] + self._long_opts = [] + self.prefixes = set() + + for opt in opts: + prefix, value = split_opt(opt) + if not prefix: + raise ValueError('Invalid start character for option (%s)' + % opt) + self.prefixes.add(prefix[0]) + if len(prefix) == 1 and len(value) == 1: + self._short_opts.append(opt) + else: + self._long_opts.append(opt) + self.prefixes.add(prefix) + + if action is None: + action = 'store' + + self.dest = dest + self.action = action + self.nargs = nargs + self.const = const + self.obj = obj + + @property + def takes_value(self): + return self.action in ('store', 'append') + + def process(self, value, state): + if self.action == 'store': + state.opts[self.dest] = value + elif self.action == 'store_const': + state.opts[self.dest] = self.const + elif self.action == 'append': + state.opts.setdefault(self.dest, []).append(value) + elif self.action == 'append_const': + state.opts.setdefault(self.dest, []).append(self.const) + elif self.action == 'count': + state.opts[self.dest] = state.opts.get(self.dest, 0) + 1 + else: + raise ValueError('unknown action %r' % self.action) + state.order.append(self.obj) + + +class Argument(object): + + def __init__(self, dest, nargs=1, obj=None): + self.dest = dest + self.nargs = nargs + self.obj = obj + + def process(self, value, state): + if self.nargs > 1: + holes = sum(1 for x in value if x is None) + if holes == len(value): + value = None + elif holes != 0: + raise BadArgumentUsage('argument %s takes %d values' + % (self.dest, self.nargs)) + state.opts[self.dest] = value + state.order.append(self.obj) + + +class ParsingState(object): + + def __init__(self, rargs): + self.opts = {} + self.largs = [] + self.rargs = rargs + self.order = [] + + +class OptionParser(object): + """The option parser is an internal class that is ultimately used to + parse options and arguments. It's modelled after optparse and brings + a similar but vastly simplified API. It should generally not be used + directly as the high level Click classes wrap it for you. + + It's not nearly as extensible as optparse or argparse as it does not + implement features that are implemented on a higher level (such as + types or defaults). + + :param ctx: optionally the :class:`~click.Context` where this parser + should go with. + """ + + def __init__(self, ctx=None): + #: The :class:`~click.Context` for this parser. This might be + #: `None` for some advanced use cases. + self.ctx = ctx + #: This controls how the parser deals with interspersed arguments. + #: If this is set to `False`, the parser will stop on the first + #: non-option. Click uses this to implement nested subcommands + #: safely. + self.allow_interspersed_args = True + #: This tells the parser how to deal with unknown options. By + #: default it will error out (which is sensible), but there is a + #: second mode where it will ignore it and continue processing + #: after shifting all the unknown options into the resulting args. + self.ignore_unknown_options = False + if ctx is not None: + self.allow_interspersed_args = ctx.allow_interspersed_args + self.ignore_unknown_options = ctx.ignore_unknown_options + self._short_opt = {} + self._long_opt = {} + self._opt_prefixes = set(['-', '--']) + self._args = [] + + def add_option(self, opts, dest, action=None, nargs=1, const=None, + obj=None): + """Adds a new option named `dest` to the parser. The destination + is not inferred (unlike with optparse) and needs to be explicitly + provided. Action can be any of ``store``, ``store_const``, + ``append``, ``appnd_const`` or ``count``. + + The `obj` can be used to identify the option in the order list + that is returned from the parser. + """ + if obj is None: + obj = dest + opts = [normalize_opt(opt, self.ctx) for opt in opts] + option = Option(opts, dest, action=action, nargs=nargs, + const=const, obj=obj) + self._opt_prefixes.update(option.prefixes) + for opt in option._short_opts: + self._short_opt[opt] = option + for opt in option._long_opts: + self._long_opt[opt] = option + + def add_argument(self, dest, nargs=1, obj=None): + """Adds a positional argument named `dest` to the parser. + + The `obj` can be used to identify the option in the order list + that is returned from the parser. + """ + if obj is None: + obj = dest + self._args.append(Argument(dest=dest, nargs=nargs, obj=obj)) + + def parse_args(self, args): + """Parses positional arguments and returns ``(values, args, order)`` + for the parsed options and arguments as well as the leftover + arguments if there are any. The order is a list of objects as they + appear on the command line. If arguments appear multiple times they + will be memorized multiple times as well. + """ + state = ParsingState(args) + try: + self._process_args_for_options(state) + self._process_args_for_args(state) + except UsageError: + if self.ctx is None or not self.ctx.resilient_parsing: + raise + return state.opts, state.largs, state.order + + def _process_args_for_args(self, state): + pargs, args = _unpack_args(state.largs + state.rargs, + [x.nargs for x in self._args]) + + for idx, arg in enumerate(self._args): + arg.process(pargs[idx], state) + + state.largs = args + state.rargs = [] + + def _process_args_for_options(self, state): + while state.rargs: + arg = state.rargs.pop(0) + arglen = len(arg) + # Double dashes always handled explicitly regardless of what + # prefixes are valid. + if arg == '--': + return + elif arg[:1] in self._opt_prefixes and arglen > 1: + self._process_opts(arg, state) + elif self.allow_interspersed_args: + state.largs.append(arg) + else: + state.rargs.insert(0, arg) + return + + # Say this is the original argument list: + # [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)] + # ^ + # (we are about to process arg(i)). + # + # Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of + # [arg0, ..., arg(i-1)] (any options and their arguments will have + # been removed from largs). + # + # The while loop will usually consume 1 or more arguments per pass. + # If it consumes 1 (eg. arg is an option that takes no arguments), + # then after _process_arg() is done the situation is: + # + # largs = subset of [arg0, ..., arg(i)] + # rargs = [arg(i+1), ..., arg(N-1)] + # + # If allow_interspersed_args is false, largs will always be + # *empty* -- still a subset of [arg0, ..., arg(i-1)], but + # not a very interesting subset! + + def _match_long_opt(self, opt, explicit_value, state): + if opt not in self._long_opt: + possibilities = [word for word in self._long_opt + if word.startswith(opt)] + raise NoSuchOption(opt, possibilities=possibilities, ctx=self.ctx) + + option = self._long_opt[opt] + if option.takes_value: + # At this point it's safe to modify rargs by injecting the + # explicit value, because no exception is raised in this + # branch. This means that the inserted value will be fully + # consumed. + if explicit_value is not None: + state.rargs.insert(0, explicit_value) + + nargs = option.nargs + if len(state.rargs) < nargs: + _error_opt_args(nargs, opt) + elif nargs == 1: + value = state.rargs.pop(0) + else: + value = tuple(state.rargs[:nargs]) + del state.rargs[:nargs] + + elif explicit_value is not None: + raise BadOptionUsage(opt, '%s option does not take a value' % opt) + + else: + value = None + + option.process(value, state) + + def _match_short_opt(self, arg, state): + stop = False + i = 1 + prefix = arg[0] + unknown_options = [] + + for ch in arg[1:]: + opt = normalize_opt(prefix + ch, self.ctx) + option = self._short_opt.get(opt) + i += 1 + + if not option: + if self.ignore_unknown_options: + unknown_options.append(ch) + continue + raise NoSuchOption(opt, ctx=self.ctx) + if option.takes_value: + # Any characters left in arg? Pretend they're the + # next arg, and stop consuming characters of arg. + if i < len(arg): + state.rargs.insert(0, arg[i:]) + stop = True + + nargs = option.nargs + if len(state.rargs) < nargs: + _error_opt_args(nargs, opt) + elif nargs == 1: + value = state.rargs.pop(0) + else: + value = tuple(state.rargs[:nargs]) + del state.rargs[:nargs] + + else: + value = None + + option.process(value, state) + + if stop: + break + + # If we got any unknown options we re-combinate the string of the + # remaining options and re-attach the prefix, then report that + # to the state as new larg. This way there is basic combinatorics + # that can be achieved while still ignoring unknown arguments. + if self.ignore_unknown_options and unknown_options: + state.largs.append(prefix + ''.join(unknown_options)) + + def _process_opts(self, arg, state): + explicit_value = None + # Long option handling happens in two parts. The first part is + # supporting explicitly attached values. In any case, we will try + # to long match the option first. + if '=' in arg: + long_opt, explicit_value = arg.split('=', 1) + else: + long_opt = arg + norm_long_opt = normalize_opt(long_opt, self.ctx) + + # At this point we will match the (assumed) long option through + # the long option matching code. Note that this allows options + # like "-foo" to be matched as long options. + try: + self._match_long_opt(norm_long_opt, explicit_value, state) + except NoSuchOption: + # At this point the long option matching failed, and we need + # to try with short options. However there is a special rule + # which says, that if we have a two character options prefix + # (applies to "--foo" for instance), we do not dispatch to the + # short option code and will instead raise the no option + # error. + if arg[:2] not in self._opt_prefixes: + return self._match_short_opt(arg, state) + if not self.ignore_unknown_options: + raise + state.largs.append(arg) diff --git a/libs/click/termui.py b/libs/click/termui.py new file mode 100644 index 00000000..bf9a3aa1 --- /dev/null +++ b/libs/click/termui.py @@ -0,0 +1,606 @@ +import os +import sys +import struct +import inspect +import itertools + +from ._compat import raw_input, text_type, string_types, \ + isatty, strip_ansi, get_winterm_size, DEFAULT_COLUMNS, WIN +from .utils import echo +from .exceptions import Abort, UsageError +from .types import convert_type, Choice, Path +from .globals import resolve_color_default + + +# The prompt functions to use. The doc tools currently override these +# functions to customize how they work. +visible_prompt_func = raw_input + +_ansi_colors = { + 'black': 30, + 'red': 31, + 'green': 32, + 'yellow': 33, + 'blue': 34, + 'magenta': 35, + 'cyan': 36, + 'white': 37, + 'reset': 39, + 'bright_black': 90, + 'bright_red': 91, + 'bright_green': 92, + 'bright_yellow': 93, + 'bright_blue': 94, + 'bright_magenta': 95, + 'bright_cyan': 96, + 'bright_white': 97, +} +_ansi_reset_all = '\033[0m' + + +def hidden_prompt_func(prompt): + import getpass + return getpass.getpass(prompt) + + +def _build_prompt(text, suffix, show_default=False, default=None, show_choices=True, type=None): + prompt = text + if type is not None and show_choices and isinstance(type, Choice): + prompt += ' (' + ", ".join(map(str, type.choices)) + ')' + if default is not None and show_default: + prompt = '%s [%s]' % (prompt, default) + return prompt + suffix + + +def prompt(text, default=None, hide_input=False, confirmation_prompt=False, + type=None, value_proc=None, prompt_suffix=': ', show_default=True, + err=False, show_choices=True): + """Prompts a user for input. This is a convenience function that can + be used to prompt a user for input later. + + If the user aborts the input by sending a interrupt signal, this + function will catch it and raise a :exc:`Abort` exception. + + .. versionadded:: 7.0 + Added the show_choices parameter. + + .. versionadded:: 6.0 + Added unicode support for cmd.exe on Windows. + + .. versionadded:: 4.0 + Added the `err` parameter. + + :param text: the text to show for the prompt. + :param default: the default value to use if no input happens. If this + is not given it will prompt until it's aborted. + :param hide_input: if this is set to true then the input value will + be hidden. + :param confirmation_prompt: asks for confirmation for the value. + :param type: the type to use to check the value against. + :param value_proc: if this parameter is provided it's a function that + is invoked instead of the type conversion to + convert a value. + :param prompt_suffix: a suffix that should be added to the prompt. + :param show_default: shows or hides the default value in the prompt. + :param err: if set to true the file defaults to ``stderr`` instead of + ``stdout``, the same as with echo. + :param show_choices: Show or hide choices if the passed type is a Choice. + For example if type is a Choice of either day or week, + show_choices is true and text is "Group by" then the + prompt will be "Group by (day, week): ". + """ + result = None + + def prompt_func(text): + f = hide_input and hidden_prompt_func or visible_prompt_func + try: + # Write the prompt separately so that we get nice + # coloring through colorama on Windows + echo(text, nl=False, err=err) + return f('') + except (KeyboardInterrupt, EOFError): + # getpass doesn't print a newline if the user aborts input with ^C. + # Allegedly this behavior is inherited from getpass(3). + # A doc bug has been filed at https://bugs.python.org/issue24711 + if hide_input: + echo(None, err=err) + raise Abort() + + if value_proc is None: + value_proc = convert_type(type, default) + + prompt = _build_prompt(text, prompt_suffix, show_default, default, show_choices, type) + + while 1: + while 1: + value = prompt_func(prompt) + if value: + break + elif default is not None: + if isinstance(value_proc, Path): + # validate Path default value(exists, dir_okay etc.) + value = default + break + return default + try: + result = value_proc(value) + except UsageError as e: + echo('Error: %s' % e.message, err=err) + continue + if not confirmation_prompt: + return result + while 1: + value2 = prompt_func('Repeat for confirmation: ') + if value2: + break + if value == value2: + return result + echo('Error: the two entered values do not match', err=err) + + +def confirm(text, default=False, abort=False, prompt_suffix=': ', + show_default=True, err=False): + """Prompts for confirmation (yes/no question). + + If the user aborts the input by sending a interrupt signal this + function will catch it and raise a :exc:`Abort` exception. + + .. versionadded:: 4.0 + Added the `err` parameter. + + :param text: the question to ask. + :param default: the default for the prompt. + :param abort: if this is set to `True` a negative answer aborts the + exception by raising :exc:`Abort`. + :param prompt_suffix: a suffix that should be added to the prompt. + :param show_default: shows or hides the default value in the prompt. + :param err: if set to true the file defaults to ``stderr`` instead of + ``stdout``, the same as with echo. + """ + prompt = _build_prompt(text, prompt_suffix, show_default, + default and 'Y/n' or 'y/N') + while 1: + try: + # Write the prompt separately so that we get nice + # coloring through colorama on Windows + echo(prompt, nl=False, err=err) + value = visible_prompt_func('').lower().strip() + except (KeyboardInterrupt, EOFError): + raise Abort() + if value in ('y', 'yes'): + rv = True + elif value in ('n', 'no'): + rv = False + elif value == '': + rv = default + else: + echo('Error: invalid input', err=err) + continue + break + if abort and not rv: + raise Abort() + return rv + + +def get_terminal_size(): + """Returns the current size of the terminal as tuple in the form + ``(width, height)`` in columns and rows. + """ + # If shutil has get_terminal_size() (Python 3.3 and later) use that + if sys.version_info >= (3, 3): + import shutil + shutil_get_terminal_size = getattr(shutil, 'get_terminal_size', None) + if shutil_get_terminal_size: + sz = shutil_get_terminal_size() + return sz.columns, sz.lines + + # We provide a sensible default for get_winterm_size() when being invoked + # inside a subprocess. Without this, it would not provide a useful input. + if get_winterm_size is not None: + size = get_winterm_size() + if size == (0, 0): + return (79, 24) + else: + return size + + def ioctl_gwinsz(fd): + try: + import fcntl + import termios + cr = struct.unpack( + 'hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234')) + except Exception: + return + return cr + + cr = ioctl_gwinsz(0) or ioctl_gwinsz(1) or ioctl_gwinsz(2) + if not cr: + try: + fd = os.open(os.ctermid(), os.O_RDONLY) + try: + cr = ioctl_gwinsz(fd) + finally: + os.close(fd) + except Exception: + pass + if not cr or not cr[0] or not cr[1]: + cr = (os.environ.get('LINES', 25), + os.environ.get('COLUMNS', DEFAULT_COLUMNS)) + return int(cr[1]), int(cr[0]) + + +def echo_via_pager(text_or_generator, color=None): + """This function takes a text and shows it via an environment specific + pager on stdout. + + .. versionchanged:: 3.0 + Added the `color` flag. + + :param text_or_generator: the text to page, or alternatively, a + generator emitting the text to page. + :param color: controls if the pager supports ANSI colors or not. The + default is autodetection. + """ + color = resolve_color_default(color) + + if inspect.isgeneratorfunction(text_or_generator): + i = text_or_generator() + elif isinstance(text_or_generator, string_types): + i = [text_or_generator] + else: + i = iter(text_or_generator) + + # convert every element of i to a text type if necessary + text_generator = (el if isinstance(el, string_types) else text_type(el) + for el in i) + + from ._termui_impl import pager + return pager(itertools.chain(text_generator, "\n"), color) + + +def progressbar(iterable=None, length=None, label=None, show_eta=True, + show_percent=None, show_pos=False, + item_show_func=None, fill_char='#', empty_char='-', + bar_template='%(label)s [%(bar)s] %(info)s', + info_sep=' ', width=36, file=None, color=None): + """This function creates an iterable context manager that can be used + to iterate over something while showing a progress bar. It will + either iterate over the `iterable` or `length` items (that are counted + up). While iteration happens, this function will print a rendered + progress bar to the given `file` (defaults to stdout) and will attempt + to calculate remaining time and more. By default, this progress bar + will not be rendered if the file is not a terminal. + + The context manager creates the progress bar. When the context + manager is entered the progress bar is already displayed. With every + iteration over the progress bar, the iterable passed to the bar is + advanced and the bar is updated. When the context manager exits, + a newline is printed and the progress bar is finalized on screen. + + No printing must happen or the progress bar will be unintentionally + destroyed. + + Example usage:: + + with progressbar(items) as bar: + for item in bar: + do_something_with(item) + + Alternatively, if no iterable is specified, one can manually update the + progress bar through the `update()` method instead of directly + iterating over the progress bar. The update method accepts the number + of steps to increment the bar with:: + + with progressbar(length=chunks.total_bytes) as bar: + for chunk in chunks: + process_chunk(chunk) + bar.update(chunks.bytes) + + .. versionadded:: 2.0 + + .. versionadded:: 4.0 + Added the `color` parameter. Added a `update` method to the + progressbar object. + + :param iterable: an iterable to iterate over. If not provided the length + is required. + :param length: the number of items to iterate over. By default the + progressbar will attempt to ask the iterator about its + length, which might or might not work. If an iterable is + also provided this parameter can be used to override the + length. If an iterable is not provided the progress bar + will iterate over a range of that length. + :param label: the label to show next to the progress bar. + :param show_eta: enables or disables the estimated time display. This is + automatically disabled if the length cannot be + determined. + :param show_percent: enables or disables the percentage display. The + default is `True` if the iterable has a length or + `False` if not. + :param show_pos: enables or disables the absolute position display. The + default is `False`. + :param item_show_func: a function called with the current item which + can return a string to show the current item + next to the progress bar. Note that the current + item can be `None`! + :param fill_char: the character to use to show the filled part of the + progress bar. + :param empty_char: the character to use to show the non-filled part of + the progress bar. + :param bar_template: the format string to use as template for the bar. + The parameters in it are ``label`` for the label, + ``bar`` for the progress bar and ``info`` for the + info section. + :param info_sep: the separator between multiple info items (eta etc.) + :param width: the width of the progress bar in characters, 0 means full + terminal width + :param file: the file to write to. If this is not a terminal then + only the label is printed. + :param color: controls if the terminal supports ANSI colors or not. The + default is autodetection. This is only needed if ANSI + codes are included anywhere in the progress bar output + which is not the case by default. + """ + from ._termui_impl import ProgressBar + color = resolve_color_default(color) + return ProgressBar(iterable=iterable, length=length, show_eta=show_eta, + show_percent=show_percent, show_pos=show_pos, + item_show_func=item_show_func, fill_char=fill_char, + empty_char=empty_char, bar_template=bar_template, + info_sep=info_sep, file=file, label=label, + width=width, color=color) + + +def clear(): + """Clears the terminal screen. This will have the effect of clearing + the whole visible space of the terminal and moving the cursor to the + top left. This does not do anything if not connected to a terminal. + + .. versionadded:: 2.0 + """ + if not isatty(sys.stdout): + return + # If we're on Windows and we don't have colorama available, then we + # clear the screen by shelling out. Otherwise we can use an escape + # sequence. + if WIN: + os.system('cls') + else: + sys.stdout.write('\033[2J\033[1;1H') + + +def style(text, fg=None, bg=None, bold=None, dim=None, underline=None, + blink=None, reverse=None, reset=True): + """Styles a text with ANSI styles and returns the new string. By + default the styling is self contained which means that at the end + of the string a reset code is issued. This can be prevented by + passing ``reset=False``. + + Examples:: + + click.echo(click.style('Hello World!', fg='green')) + click.echo(click.style('ATTENTION!', blink=True)) + click.echo(click.style('Some things', reverse=True, fg='cyan')) + + Supported color names: + + * ``black`` (might be a gray) + * ``red`` + * ``green`` + * ``yellow`` (might be an orange) + * ``blue`` + * ``magenta`` + * ``cyan`` + * ``white`` (might be light gray) + * ``bright_black`` + * ``bright_red`` + * ``bright_green`` + * ``bright_yellow`` + * ``bright_blue`` + * ``bright_magenta`` + * ``bright_cyan`` + * ``bright_white`` + * ``reset`` (reset the color code only) + + .. versionadded:: 2.0 + + .. versionadded:: 7.0 + Added support for bright colors. + + :param text: the string to style with ansi codes. + :param fg: if provided this will become the foreground color. + :param bg: if provided this will become the background color. + :param bold: if provided this will enable or disable bold mode. + :param dim: if provided this will enable or disable dim mode. This is + badly supported. + :param underline: if provided this will enable or disable underline. + :param blink: if provided this will enable or disable blinking. + :param reverse: if provided this will enable or disable inverse + rendering (foreground becomes background and the + other way round). + :param reset: by default a reset-all code is added at the end of the + string which means that styles do not carry over. This + can be disabled to compose styles. + """ + bits = [] + if fg: + try: + bits.append('\033[%dm' % (_ansi_colors[fg])) + except KeyError: + raise TypeError('Unknown color %r' % fg) + if bg: + try: + bits.append('\033[%dm' % (_ansi_colors[bg] + 10)) + except KeyError: + raise TypeError('Unknown color %r' % bg) + if bold is not None: + bits.append('\033[%dm' % (1 if bold else 22)) + if dim is not None: + bits.append('\033[%dm' % (2 if dim else 22)) + if underline is not None: + bits.append('\033[%dm' % (4 if underline else 24)) + if blink is not None: + bits.append('\033[%dm' % (5 if blink else 25)) + if reverse is not None: + bits.append('\033[%dm' % (7 if reverse else 27)) + bits.append(text) + if reset: + bits.append(_ansi_reset_all) + return ''.join(bits) + + +def unstyle(text): + """Removes ANSI styling information from a string. Usually it's not + necessary to use this function as Click's echo function will + automatically remove styling if necessary. + + .. versionadded:: 2.0 + + :param text: the text to remove style information from. + """ + return strip_ansi(text) + + +def secho(message=None, file=None, nl=True, err=False, color=None, **styles): + """This function combines :func:`echo` and :func:`style` into one + call. As such the following two calls are the same:: + + click.secho('Hello World!', fg='green') + click.echo(click.style('Hello World!', fg='green')) + + All keyword arguments are forwarded to the underlying functions + depending on which one they go with. + + .. versionadded:: 2.0 + """ + if message is not None: + message = style(message, **styles) + return echo(message, file=file, nl=nl, err=err, color=color) + + +def edit(text=None, editor=None, env=None, require_save=True, + extension='.txt', filename=None): + r"""Edits the given text in the defined editor. If an editor is given + (should be the full path to the executable but the regular operating + system search path is used for finding the executable) it overrides + the detected editor. Optionally, some environment variables can be + used. If the editor is closed without changes, `None` is returned. In + case a file is edited directly the return value is always `None` and + `require_save` and `extension` are ignored. + + If the editor cannot be opened a :exc:`UsageError` is raised. + + Note for Windows: to simplify cross-platform usage, the newlines are + automatically converted from POSIX to Windows and vice versa. As such, + the message here will have ``\n`` as newline markers. + + :param text: the text to edit. + :param editor: optionally the editor to use. Defaults to automatic + detection. + :param env: environment variables to forward to the editor. + :param require_save: if this is true, then not saving in the editor + will make the return value become `None`. + :param extension: the extension to tell the editor about. This defaults + to `.txt` but changing this might change syntax + highlighting. + :param filename: if provided it will edit this file instead of the + provided text contents. It will not use a temporary + file as an indirection in that case. + """ + from ._termui_impl import Editor + editor = Editor(editor=editor, env=env, require_save=require_save, + extension=extension) + if filename is None: + return editor.edit(text) + editor.edit_file(filename) + + +def launch(url, wait=False, locate=False): + """This function launches the given URL (or filename) in the default + viewer application for this file type. If this is an executable, it + might launch the executable in a new session. The return value is + the exit code of the launched application. Usually, ``0`` indicates + success. + + Examples:: + + click.launch('https://click.palletsprojects.com/') + click.launch('/my/downloaded/file', locate=True) + + .. versionadded:: 2.0 + + :param url: URL or filename of the thing to launch. + :param wait: waits for the program to stop. + :param locate: if this is set to `True` then instead of launching the + application associated with the URL it will attempt to + launch a file manager with the file located. This + might have weird effects if the URL does not point to + the filesystem. + """ + from ._termui_impl import open_url + return open_url(url, wait=wait, locate=locate) + + +# If this is provided, getchar() calls into this instead. This is used +# for unittesting purposes. +_getchar = None + + +def getchar(echo=False): + """Fetches a single character from the terminal and returns it. This + will always return a unicode character and under certain rare + circumstances this might return more than one character. The + situations which more than one character is returned is when for + whatever reason multiple characters end up in the terminal buffer or + standard input was not actually a terminal. + + Note that this will always read from the terminal, even if something + is piped into the standard input. + + Note for Windows: in rare cases when typing non-ASCII characters, this + function might wait for a second character and then return both at once. + This is because certain Unicode characters look like special-key markers. + + .. versionadded:: 2.0 + + :param echo: if set to `True`, the character read will also show up on + the terminal. The default is to not show it. + """ + f = _getchar + if f is None: + from ._termui_impl import getchar as f + return f(echo) + + +def raw_terminal(): + from ._termui_impl import raw_terminal as f + return f() + + +def pause(info='Press any key to continue ...', err=False): + """This command stops execution and waits for the user to press any + key to continue. This is similar to the Windows batch "pause" + command. If the program is not run through a terminal, this command + will instead do nothing. + + .. versionadded:: 2.0 + + .. versionadded:: 4.0 + Added the `err` parameter. + + :param info: the info string to print before pausing. + :param err: if set to message goes to ``stderr`` instead of + ``stdout``, the same as with echo. + """ + if not isatty(sys.stdin) or not isatty(sys.stdout): + return + try: + if info: + echo(info, nl=False, err=err) + try: + getchar() + except (KeyboardInterrupt, EOFError): + pass + finally: + if info: + echo(err=err) diff --git a/libs/click/testing.py b/libs/click/testing.py new file mode 100644 index 00000000..1b2924e0 --- /dev/null +++ b/libs/click/testing.py @@ -0,0 +1,374 @@ +import os +import sys +import shutil +import tempfile +import contextlib +import shlex + +from ._compat import iteritems, PY2, string_types + + +# If someone wants to vendor click, we want to ensure the +# correct package is discovered. Ideally we could use a +# relative import here but unfortunately Python does not +# support that. +clickpkg = sys.modules[__name__.rsplit('.', 1)[0]] + + +if PY2: + from cStringIO import StringIO +else: + import io + from ._compat import _find_binary_reader + + +class EchoingStdin(object): + + def __init__(self, input, output): + self._input = input + self._output = output + + def __getattr__(self, x): + return getattr(self._input, x) + + def _echo(self, rv): + self._output.write(rv) + return rv + + def read(self, n=-1): + return self._echo(self._input.read(n)) + + def readline(self, n=-1): + return self._echo(self._input.readline(n)) + + def readlines(self): + return [self._echo(x) for x in self._input.readlines()] + + def __iter__(self): + return iter(self._echo(x) for x in self._input) + + def __repr__(self): + return repr(self._input) + + +def make_input_stream(input, charset): + # Is already an input stream. + if hasattr(input, 'read'): + if PY2: + return input + rv = _find_binary_reader(input) + if rv is not None: + return rv + raise TypeError('Could not find binary reader for input stream.') + + if input is None: + input = b'' + elif not isinstance(input, bytes): + input = input.encode(charset) + if PY2: + return StringIO(input) + return io.BytesIO(input) + + +class Result(object): + """Holds the captured result of an invoked CLI script.""" + + def __init__(self, runner, stdout_bytes, stderr_bytes, exit_code, + exception, exc_info=None): + #: The runner that created the result + self.runner = runner + #: The standard output as bytes. + self.stdout_bytes = stdout_bytes + #: The standard error as bytes, or False(y) if not available + self.stderr_bytes = stderr_bytes + #: The exit code as integer. + self.exit_code = exit_code + #: The exception that happened if one did. + self.exception = exception + #: The traceback + self.exc_info = exc_info + + @property + def output(self): + """The (standard) output as unicode string.""" + return self.stdout + + @property + def stdout(self): + """The standard output as unicode string.""" + return self.stdout_bytes.decode(self.runner.charset, 'replace') \ + .replace('\r\n', '\n') + + @property + def stderr(self): + """The standard error as unicode string.""" + if not self.stderr_bytes: + raise ValueError("stderr not separately captured") + return self.stderr_bytes.decode(self.runner.charset, 'replace') \ + .replace('\r\n', '\n') + + + def __repr__(self): + return '<%s %s>' % ( + type(self).__name__, + self.exception and repr(self.exception) or 'okay', + ) + + +class CliRunner(object): + """The CLI runner provides functionality to invoke a Click command line + script for unittesting purposes in a isolated environment. This only + works in single-threaded systems without any concurrency as it changes the + global interpreter state. + + :param charset: the character set for the input and output data. This is + UTF-8 by default and should not be changed currently as + the reporting to Click only works in Python 2 properly. + :param env: a dictionary with environment variables for overriding. + :param echo_stdin: if this is set to `True`, then reading from stdin writes + to stdout. This is useful for showing examples in + some circumstances. Note that regular prompts + will automatically echo the input. + :param mix_stderr: if this is set to `False`, then stdout and stderr are + preserved as independent streams. This is useful for + Unix-philosophy apps that have predictable stdout and + noisy stderr, such that each may be measured + independently + """ + + def __init__(self, charset=None, env=None, echo_stdin=False, + mix_stderr=True): + if charset is None: + charset = 'utf-8' + self.charset = charset + self.env = env or {} + self.echo_stdin = echo_stdin + self.mix_stderr = mix_stderr + + def get_default_prog_name(self, cli): + """Given a command object it will return the default program name + for it. The default is the `name` attribute or ``"root"`` if not + set. + """ + return cli.name or 'root' + + def make_env(self, overrides=None): + """Returns the environment overrides for invoking a script.""" + rv = dict(self.env) + if overrides: + rv.update(overrides) + return rv + + @contextlib.contextmanager + def isolation(self, input=None, env=None, color=False): + """A context manager that sets up the isolation for invoking of a + command line tool. This sets up stdin with the given input data + and `os.environ` with the overrides from the given dictionary. + This also rebinds some internals in Click to be mocked (like the + prompt functionality). + + This is automatically done in the :meth:`invoke` method. + + .. versionadded:: 4.0 + The ``color`` parameter was added. + + :param input: the input stream to put into sys.stdin. + :param env: the environment overrides as dictionary. + :param color: whether the output should contain color codes. The + application can still override this explicitly. + """ + input = make_input_stream(input, self.charset) + + old_stdin = sys.stdin + old_stdout = sys.stdout + old_stderr = sys.stderr + old_forced_width = clickpkg.formatting.FORCED_WIDTH + clickpkg.formatting.FORCED_WIDTH = 80 + + env = self.make_env(env) + + if PY2: + bytes_output = StringIO() + if self.echo_stdin: + input = EchoingStdin(input, bytes_output) + sys.stdout = bytes_output + if not self.mix_stderr: + bytes_error = StringIO() + sys.stderr = bytes_error + else: + bytes_output = io.BytesIO() + if self.echo_stdin: + input = EchoingStdin(input, bytes_output) + input = io.TextIOWrapper(input, encoding=self.charset) + sys.stdout = io.TextIOWrapper( + bytes_output, encoding=self.charset) + if not self.mix_stderr: + bytes_error = io.BytesIO() + sys.stderr = io.TextIOWrapper( + bytes_error, encoding=self.charset) + + if self.mix_stderr: + sys.stderr = sys.stdout + + sys.stdin = input + + def visible_input(prompt=None): + sys.stdout.write(prompt or '') + val = input.readline().rstrip('\r\n') + sys.stdout.write(val + '\n') + sys.stdout.flush() + return val + + def hidden_input(prompt=None): + sys.stdout.write((prompt or '') + '\n') + sys.stdout.flush() + return input.readline().rstrip('\r\n') + + def _getchar(echo): + char = sys.stdin.read(1) + if echo: + sys.stdout.write(char) + sys.stdout.flush() + return char + + default_color = color + + def should_strip_ansi(stream=None, color=None): + if color is None: + return not default_color + return not color + + old_visible_prompt_func = clickpkg.termui.visible_prompt_func + old_hidden_prompt_func = clickpkg.termui.hidden_prompt_func + old__getchar_func = clickpkg.termui._getchar + old_should_strip_ansi = clickpkg.utils.should_strip_ansi + clickpkg.termui.visible_prompt_func = visible_input + clickpkg.termui.hidden_prompt_func = hidden_input + clickpkg.termui._getchar = _getchar + clickpkg.utils.should_strip_ansi = should_strip_ansi + + old_env = {} + try: + for key, value in iteritems(env): + old_env[key] = os.environ.get(key) + if value is None: + try: + del os.environ[key] + except Exception: + pass + else: + os.environ[key] = value + yield (bytes_output, not self.mix_stderr and bytes_error) + finally: + for key, value in iteritems(old_env): + if value is None: + try: + del os.environ[key] + except Exception: + pass + else: + os.environ[key] = value + sys.stdout = old_stdout + sys.stderr = old_stderr + sys.stdin = old_stdin + clickpkg.termui.visible_prompt_func = old_visible_prompt_func + clickpkg.termui.hidden_prompt_func = old_hidden_prompt_func + clickpkg.termui._getchar = old__getchar_func + clickpkg.utils.should_strip_ansi = old_should_strip_ansi + clickpkg.formatting.FORCED_WIDTH = old_forced_width + + def invoke(self, cli, args=None, input=None, env=None, + catch_exceptions=True, color=False, mix_stderr=False, **extra): + """Invokes a command in an isolated environment. The arguments are + forwarded directly to the command line script, the `extra` keyword + arguments are passed to the :meth:`~clickpkg.Command.main` function of + the command. + + This returns a :class:`Result` object. + + .. versionadded:: 3.0 + The ``catch_exceptions`` parameter was added. + + .. versionchanged:: 3.0 + The result object now has an `exc_info` attribute with the + traceback if available. + + .. versionadded:: 4.0 + The ``color`` parameter was added. + + :param cli: the command to invoke + :param args: the arguments to invoke. It may be given as an iterable + or a string. When given as string it will be interpreted + as a Unix shell command. More details at + :func:`shlex.split`. + :param input: the input data for `sys.stdin`. + :param env: the environment overrides. + :param catch_exceptions: Whether to catch any other exceptions than + ``SystemExit``. + :param extra: the keyword arguments to pass to :meth:`main`. + :param color: whether the output should contain color codes. The + application can still override this explicitly. + """ + exc_info = None + with self.isolation(input=input, env=env, color=color) as outstreams: + exception = None + exit_code = 0 + + if isinstance(args, string_types): + args = shlex.split(args) + + try: + prog_name = extra.pop("prog_name") + except KeyError: + prog_name = self.get_default_prog_name(cli) + + try: + cli.main(args=args or (), prog_name=prog_name, **extra) + except SystemExit as e: + exc_info = sys.exc_info() + exit_code = e.code + if exit_code is None: + exit_code = 0 + + if exit_code != 0: + exception = e + + if not isinstance(exit_code, int): + sys.stdout.write(str(exit_code)) + sys.stdout.write('\n') + exit_code = 1 + + except Exception as e: + if not catch_exceptions: + raise + exception = e + exit_code = 1 + exc_info = sys.exc_info() + finally: + sys.stdout.flush() + stdout = outstreams[0].getvalue() + stderr = outstreams[1] and outstreams[1].getvalue() + + return Result(runner=self, + stdout_bytes=stdout, + stderr_bytes=stderr, + exit_code=exit_code, + exception=exception, + exc_info=exc_info) + + @contextlib.contextmanager + def isolated_filesystem(self): + """A context manager that creates a temporary folder and changes + the current working directory to it for isolated filesystem tests. + """ + cwd = os.getcwd() + t = tempfile.mkdtemp() + os.chdir(t) + try: + yield t + finally: + os.chdir(cwd) + try: + shutil.rmtree(t) + except (OSError, IOError): + pass diff --git a/libs/click/types.py b/libs/click/types.py new file mode 100644 index 00000000..1f88032f --- /dev/null +++ b/libs/click/types.py @@ -0,0 +1,668 @@ +import os +import stat +from datetime import datetime + +from ._compat import open_stream, text_type, filename_to_ui, \ + get_filesystem_encoding, get_streerror, _get_argv_encoding, PY2 +from .exceptions import BadParameter +from .utils import safecall, LazyFile + + +class ParamType(object): + """Helper for converting values through types. The following is + necessary for a valid type: + + * it needs a name + * it needs to pass through None unchanged + * it needs to convert from a string + * it needs to convert its result type through unchanged + (eg: needs to be idempotent) + * it needs to be able to deal with param and context being `None`. + This can be the case when the object is used with prompt + inputs. + """ + is_composite = False + + #: the descriptive name of this type + name = None + + #: if a list of this type is expected and the value is pulled from a + #: string environment variable, this is what splits it up. `None` + #: means any whitespace. For all parameters the general rule is that + #: whitespace splits them up. The exception are paths and files which + #: are split by ``os.path.pathsep`` by default (":" on Unix and ";" on + #: Windows). + envvar_list_splitter = None + + def __call__(self, value, param=None, ctx=None): + if value is not None: + return self.convert(value, param, ctx) + + def get_metavar(self, param): + """Returns the metavar default for this param if it provides one.""" + + def get_missing_message(self, param): + """Optionally might return extra information about a missing + parameter. + + .. versionadded:: 2.0 + """ + + def convert(self, value, param, ctx): + """Converts the value. This is not invoked for values that are + `None` (the missing value). + """ + return value + + def split_envvar_value(self, rv): + """Given a value from an environment variable this splits it up + into small chunks depending on the defined envvar list splitter. + + If the splitter is set to `None`, which means that whitespace splits, + then leading and trailing whitespace is ignored. Otherwise, leading + and trailing splitters usually lead to empty items being included. + """ + return (rv or '').split(self.envvar_list_splitter) + + def fail(self, message, param=None, ctx=None): + """Helper method to fail with an invalid value message.""" + raise BadParameter(message, ctx=ctx, param=param) + + +class CompositeParamType(ParamType): + is_composite = True + + @property + def arity(self): + raise NotImplementedError() + + +class FuncParamType(ParamType): + + def __init__(self, func): + self.name = func.__name__ + self.func = func + + def convert(self, value, param, ctx): + try: + return self.func(value) + except ValueError: + try: + value = text_type(value) + except UnicodeError: + value = str(value).decode('utf-8', 'replace') + self.fail(value, param, ctx) + + +class UnprocessedParamType(ParamType): + name = 'text' + + def convert(self, value, param, ctx): + return value + + def __repr__(self): + return 'UNPROCESSED' + + +class StringParamType(ParamType): + name = 'text' + + def convert(self, value, param, ctx): + if isinstance(value, bytes): + enc = _get_argv_encoding() + try: + value = value.decode(enc) + except UnicodeError: + fs_enc = get_filesystem_encoding() + if fs_enc != enc: + try: + value = value.decode(fs_enc) + except UnicodeError: + value = value.decode('utf-8', 'replace') + return value + return value + + def __repr__(self): + return 'STRING' + + +class Choice(ParamType): + """The choice type allows a value to be checked against a fixed set + of supported values. All of these values have to be strings. + + You should only pass a list or tuple of choices. Other iterables + (like generators) may lead to surprising results. + + See :ref:`choice-opts` for an example. + + :param case_sensitive: Set to false to make choices case + insensitive. Defaults to true. + """ + + name = 'choice' + + def __init__(self, choices, case_sensitive=True): + self.choices = choices + self.case_sensitive = case_sensitive + + def get_metavar(self, param): + return '[%s]' % '|'.join(self.choices) + + def get_missing_message(self, param): + return 'Choose from:\n\t%s.' % ',\n\t'.join(self.choices) + + def convert(self, value, param, ctx): + # Exact match + if value in self.choices: + return value + + # Match through normalization and case sensitivity + # first do token_normalize_func, then lowercase + # preserve original `value` to produce an accurate message in + # `self.fail` + normed_value = value + normed_choices = self.choices + + if ctx is not None and \ + ctx.token_normalize_func is not None: + normed_value = ctx.token_normalize_func(value) + normed_choices = [ctx.token_normalize_func(choice) for choice in + self.choices] + + if not self.case_sensitive: + normed_value = normed_value.lower() + normed_choices = [choice.lower() for choice in normed_choices] + + if normed_value in normed_choices: + return normed_value + + self.fail('invalid choice: %s. (choose from %s)' % + (value, ', '.join(self.choices)), param, ctx) + + def __repr__(self): + return 'Choice(%r)' % list(self.choices) + + +class DateTime(ParamType): + """The DateTime type converts date strings into `datetime` objects. + + The format strings which are checked are configurable, but default to some + common (non-timezone aware) ISO 8601 formats. + + When specifying *DateTime* formats, you should only pass a list or a tuple. + Other iterables, like generators, may lead to surprising results. + + The format strings are processed using ``datetime.strptime``, and this + consequently defines the format strings which are allowed. + + Parsing is tried using each format, in order, and the first format which + parses successfully is used. + + :param formats: A list or tuple of date format strings, in the order in + which they should be tried. Defaults to + ``'%Y-%m-%d'``, ``'%Y-%m-%dT%H:%M:%S'``, + ``'%Y-%m-%d %H:%M:%S'``. + """ + name = 'datetime' + + def __init__(self, formats=None): + self.formats = formats or [ + '%Y-%m-%d', + '%Y-%m-%dT%H:%M:%S', + '%Y-%m-%d %H:%M:%S' + ] + + def get_metavar(self, param): + return '[{}]'.format('|'.join(self.formats)) + + def _try_to_convert_date(self, value, format): + try: + return datetime.strptime(value, format) + except ValueError: + return None + + def convert(self, value, param, ctx): + # Exact match + for format in self.formats: + dtime = self._try_to_convert_date(value, format) + if dtime: + return dtime + + self.fail( + 'invalid datetime format: {}. (choose from {})'.format( + value, ', '.join(self.formats))) + + def __repr__(self): + return 'DateTime' + + +class IntParamType(ParamType): + name = 'integer' + + def convert(self, value, param, ctx): + try: + return int(value) + except (ValueError, UnicodeError): + self.fail('%s is not a valid integer' % value, param, ctx) + + def __repr__(self): + return 'INT' + + +class IntRange(IntParamType): + """A parameter that works similar to :data:`click.INT` but restricts + the value to fit into a range. The default behavior is to fail if the + value falls outside the range, but it can also be silently clamped + between the two edges. + + See :ref:`ranges` for an example. + """ + name = 'integer range' + + def __init__(self, min=None, max=None, clamp=False): + self.min = min + self.max = max + self.clamp = clamp + + def convert(self, value, param, ctx): + rv = IntParamType.convert(self, value, param, ctx) + if self.clamp: + if self.min is not None and rv < self.min: + return self.min + if self.max is not None and rv > self.max: + return self.max + if self.min is not None and rv < self.min or \ + self.max is not None and rv > self.max: + if self.min is None: + self.fail('%s is bigger than the maximum valid value ' + '%s.' % (rv, self.max), param, ctx) + elif self.max is None: + self.fail('%s is smaller than the minimum valid value ' + '%s.' % (rv, self.min), param, ctx) + else: + self.fail('%s is not in the valid range of %s to %s.' + % (rv, self.min, self.max), param, ctx) + return rv + + def __repr__(self): + return 'IntRange(%r, %r)' % (self.min, self.max) + + +class FloatParamType(ParamType): + name = 'float' + + def convert(self, value, param, ctx): + try: + return float(value) + except (UnicodeError, ValueError): + self.fail('%s is not a valid floating point value' % + value, param, ctx) + + def __repr__(self): + return 'FLOAT' + + +class FloatRange(FloatParamType): + """A parameter that works similar to :data:`click.FLOAT` but restricts + the value to fit into a range. The default behavior is to fail if the + value falls outside the range, but it can also be silently clamped + between the two edges. + + See :ref:`ranges` for an example. + """ + name = 'float range' + + def __init__(self, min=None, max=None, clamp=False): + self.min = min + self.max = max + self.clamp = clamp + + def convert(self, value, param, ctx): + rv = FloatParamType.convert(self, value, param, ctx) + if self.clamp: + if self.min is not None and rv < self.min: + return self.min + if self.max is not None and rv > self.max: + return self.max + if self.min is not None and rv < self.min or \ + self.max is not None and rv > self.max: + if self.min is None: + self.fail('%s is bigger than the maximum valid value ' + '%s.' % (rv, self.max), param, ctx) + elif self.max is None: + self.fail('%s is smaller than the minimum valid value ' + '%s.' % (rv, self.min), param, ctx) + else: + self.fail('%s is not in the valid range of %s to %s.' + % (rv, self.min, self.max), param, ctx) + return rv + + def __repr__(self): + return 'FloatRange(%r, %r)' % (self.min, self.max) + + +class BoolParamType(ParamType): + name = 'boolean' + + def convert(self, value, param, ctx): + if isinstance(value, bool): + return bool(value) + value = value.lower() + if value in ('true', 't', '1', 'yes', 'y'): + return True + elif value in ('false', 'f', '0', 'no', 'n'): + return False + self.fail('%s is not a valid boolean' % value, param, ctx) + + def __repr__(self): + return 'BOOL' + + +class UUIDParameterType(ParamType): + name = 'uuid' + + def convert(self, value, param, ctx): + import uuid + try: + if PY2 and isinstance(value, text_type): + value = value.encode('ascii') + return uuid.UUID(value) + except (UnicodeError, ValueError): + self.fail('%s is not a valid UUID value' % value, param, ctx) + + def __repr__(self): + return 'UUID' + + +class File(ParamType): + """Declares a parameter to be a file for reading or writing. The file + is automatically closed once the context tears down (after the command + finished working). + + Files can be opened for reading or writing. The special value ``-`` + indicates stdin or stdout depending on the mode. + + By default, the file is opened for reading text data, but it can also be + opened in binary mode or for writing. The encoding parameter can be used + to force a specific encoding. + + The `lazy` flag controls if the file should be opened immediately or upon + first IO. The default is to be non-lazy for standard input and output + streams as well as files opened for reading, `lazy` otherwise. When opening a + file lazily for reading, it is still opened temporarily for validation, but + will not be held open until first IO. lazy is mainly useful when opening + for writing to avoid creating the file until it is needed. + + Starting with Click 2.0, files can also be opened atomically in which + case all writes go into a separate file in the same folder and upon + completion the file will be moved over to the original location. This + is useful if a file regularly read by other users is modified. + + See :ref:`file-args` for more information. + """ + name = 'filename' + envvar_list_splitter = os.path.pathsep + + def __init__(self, mode='r', encoding=None, errors='strict', lazy=None, + atomic=False): + self.mode = mode + self.encoding = encoding + self.errors = errors + self.lazy = lazy + self.atomic = atomic + + def resolve_lazy_flag(self, value): + if self.lazy is not None: + return self.lazy + if value == '-': + return False + elif 'w' in self.mode: + return True + return False + + def convert(self, value, param, ctx): + try: + if hasattr(value, 'read') or hasattr(value, 'write'): + return value + + lazy = self.resolve_lazy_flag(value) + + if lazy: + f = LazyFile(value, self.mode, self.encoding, self.errors, + atomic=self.atomic) + if ctx is not None: + ctx.call_on_close(f.close_intelligently) + return f + + f, should_close = open_stream(value, self.mode, + self.encoding, self.errors, + atomic=self.atomic) + # If a context is provided, we automatically close the file + # at the end of the context execution (or flush out). If a + # context does not exist, it's the caller's responsibility to + # properly close the file. This for instance happens when the + # type is used with prompts. + if ctx is not None: + if should_close: + ctx.call_on_close(safecall(f.close)) + else: + ctx.call_on_close(safecall(f.flush)) + return f + except (IOError, OSError) as e: + self.fail('Could not open file: %s: %s' % ( + filename_to_ui(value), + get_streerror(e), + ), param, ctx) + + +class Path(ParamType): + """The path type is similar to the :class:`File` type but it performs + different checks. First of all, instead of returning an open file + handle it returns just the filename. Secondly, it can perform various + basic checks about what the file or directory should be. + + .. versionchanged:: 6.0 + `allow_dash` was added. + + :param exists: if set to true, the file or directory needs to exist for + this value to be valid. If this is not required and a + file does indeed not exist, then all further checks are + silently skipped. + :param file_okay: controls if a file is a possible value. + :param dir_okay: controls if a directory is a possible value. + :param writable: if true, a writable check is performed. + :param readable: if true, a readable check is performed. + :param resolve_path: if this is true, then the path is fully resolved + before the value is passed onwards. This means + that it's absolute and symlinks are resolved. It + will not expand a tilde-prefix, as this is + supposed to be done by the shell only. + :param allow_dash: If this is set to `True`, a single dash to indicate + standard streams is permitted. + :param path_type: optionally a string type that should be used to + represent the path. The default is `None` which + means the return value will be either bytes or + unicode depending on what makes most sense given the + input data Click deals with. + """ + envvar_list_splitter = os.path.pathsep + + def __init__(self, exists=False, file_okay=True, dir_okay=True, + writable=False, readable=True, resolve_path=False, + allow_dash=False, path_type=None): + self.exists = exists + self.file_okay = file_okay + self.dir_okay = dir_okay + self.writable = writable + self.readable = readable + self.resolve_path = resolve_path + self.allow_dash = allow_dash + self.type = path_type + + if self.file_okay and not self.dir_okay: + self.name = 'file' + self.path_type = 'File' + elif self.dir_okay and not self.file_okay: + self.name = 'directory' + self.path_type = 'Directory' + else: + self.name = 'path' + self.path_type = 'Path' + + def coerce_path_result(self, rv): + if self.type is not None and not isinstance(rv, self.type): + if self.type is text_type: + rv = rv.decode(get_filesystem_encoding()) + else: + rv = rv.encode(get_filesystem_encoding()) + return rv + + def convert(self, value, param, ctx): + rv = value + + is_dash = self.file_okay and self.allow_dash and rv in (b'-', '-') + + if not is_dash: + if self.resolve_path: + rv = os.path.realpath(rv) + + try: + st = os.stat(rv) + except OSError: + if not self.exists: + return self.coerce_path_result(rv) + self.fail('%s "%s" does not exist.' % ( + self.path_type, + filename_to_ui(value) + ), param, ctx) + + if not self.file_okay and stat.S_ISREG(st.st_mode): + self.fail('%s "%s" is a file.' % ( + self.path_type, + filename_to_ui(value) + ), param, ctx) + if not self.dir_okay and stat.S_ISDIR(st.st_mode): + self.fail('%s "%s" is a directory.' % ( + self.path_type, + filename_to_ui(value) + ), param, ctx) + if self.writable and not os.access(value, os.W_OK): + self.fail('%s "%s" is not writable.' % ( + self.path_type, + filename_to_ui(value) + ), param, ctx) + if self.readable and not os.access(value, os.R_OK): + self.fail('%s "%s" is not readable.' % ( + self.path_type, + filename_to_ui(value) + ), param, ctx) + + return self.coerce_path_result(rv) + + +class Tuple(CompositeParamType): + """The default behavior of Click is to apply a type on a value directly. + This works well in most cases, except for when `nargs` is set to a fixed + count and different types should be used for different items. In this + case the :class:`Tuple` type can be used. This type can only be used + if `nargs` is set to a fixed number. + + For more information see :ref:`tuple-type`. + + This can be selected by using a Python tuple literal as a type. + + :param types: a list of types that should be used for the tuple items. + """ + + def __init__(self, types): + self.types = [convert_type(ty) for ty in types] + + @property + def name(self): + return "<" + " ".join(ty.name for ty in self.types) + ">" + + @property + def arity(self): + return len(self.types) + + def convert(self, value, param, ctx): + if len(value) != len(self.types): + raise TypeError('It would appear that nargs is set to conflict ' + 'with the composite type arity.') + return tuple(ty(x, param, ctx) for ty, x in zip(self.types, value)) + + +def convert_type(ty, default=None): + """Converts a callable or python ty into the most appropriate param + ty. + """ + guessed_type = False + if ty is None and default is not None: + if isinstance(default, tuple): + ty = tuple(map(type, default)) + else: + ty = type(default) + guessed_type = True + + if isinstance(ty, tuple): + return Tuple(ty) + if isinstance(ty, ParamType): + return ty + if ty is text_type or ty is str or ty is None: + return STRING + if ty is int: + return INT + # Booleans are only okay if not guessed. This is done because for + # flags the default value is actually a bit of a lie in that it + # indicates which of the flags is the one we want. See get_default() + # for more information. + if ty is bool and not guessed_type: + return BOOL + if ty is float: + return FLOAT + if guessed_type: + return STRING + + # Catch a common mistake + if __debug__: + try: + if issubclass(ty, ParamType): + raise AssertionError('Attempted to use an uninstantiated ' + 'parameter type (%s).' % ty) + except TypeError: + pass + return FuncParamType(ty) + + +#: A dummy parameter type that just does nothing. From a user's +#: perspective this appears to just be the same as `STRING` but internally +#: no string conversion takes place. This is necessary to achieve the +#: same bytes/unicode behavior on Python 2/3 in situations where you want +#: to not convert argument types. This is usually useful when working +#: with file paths as they can appear in bytes and unicode. +#: +#: For path related uses the :class:`Path` type is a better choice but +#: there are situations where an unprocessed type is useful which is why +#: it is is provided. +#: +#: .. versionadded:: 4.0 +UNPROCESSED = UnprocessedParamType() + +#: A unicode string parameter type which is the implicit default. This +#: can also be selected by using ``str`` as type. +STRING = StringParamType() + +#: An integer parameter. This can also be selected by using ``int`` as +#: type. +INT = IntParamType() + +#: A floating point value parameter. This can also be selected by using +#: ``float`` as type. +FLOAT = FloatParamType() + +#: A boolean parameter. This is the default for boolean flags. This can +#: also be selected by using ``bool`` as a type. +BOOL = BoolParamType() + +#: A UUID parameter. +UUID = UUIDParameterType() diff --git a/libs/click/utils.py b/libs/click/utils.py new file mode 100644 index 00000000..fc84369f --- /dev/null +++ b/libs/click/utils.py @@ -0,0 +1,440 @@ +import os +import sys + +from .globals import resolve_color_default + +from ._compat import text_type, open_stream, get_filesystem_encoding, \ + get_streerror, string_types, PY2, binary_streams, text_streams, \ + filename_to_ui, auto_wrap_for_ansi, strip_ansi, should_strip_ansi, \ + _default_text_stdout, _default_text_stderr, is_bytes, WIN + +if not PY2: + from ._compat import _find_binary_writer +elif WIN: + from ._winconsole import _get_windows_argv, \ + _hash_py_argv, _initial_argv_hash + + +echo_native_types = string_types + (bytes, bytearray) + + +def _posixify(name): + return '-'.join(name.split()).lower() + + +def safecall(func): + """Wraps a function so that it swallows exceptions.""" + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except Exception: + pass + return wrapper + + +def make_str(value): + """Converts a value into a valid string.""" + if isinstance(value, bytes): + try: + return value.decode(get_filesystem_encoding()) + except UnicodeError: + return value.decode('utf-8', 'replace') + return text_type(value) + + +def make_default_short_help(help, max_length=45): + """Return a condensed version of help string.""" + words = help.split() + total_length = 0 + result = [] + done = False + + for word in words: + if word[-1:] == '.': + done = True + new_length = result and 1 + len(word) or len(word) + if total_length + new_length > max_length: + result.append('...') + done = True + else: + if result: + result.append(' ') + result.append(word) + if done: + break + total_length += new_length + + return ''.join(result) + + +class LazyFile(object): + """A lazy file works like a regular file but it does not fully open + the file but it does perform some basic checks early to see if the + filename parameter does make sense. This is useful for safely opening + files for writing. + """ + + def __init__(self, filename, mode='r', encoding=None, errors='strict', + atomic=False): + self.name = filename + self.mode = mode + self.encoding = encoding + self.errors = errors + self.atomic = atomic + + if filename == '-': + self._f, self.should_close = open_stream(filename, mode, + encoding, errors) + else: + if 'r' in mode: + # Open and close the file in case we're opening it for + # reading so that we can catch at least some errors in + # some cases early. + open(filename, mode).close() + self._f = None + self.should_close = True + + def __getattr__(self, name): + return getattr(self.open(), name) + + def __repr__(self): + if self._f is not None: + return repr(self._f) + return '' % (self.name, self.mode) + + def open(self): + """Opens the file if it's not yet open. This call might fail with + a :exc:`FileError`. Not handling this error will produce an error + that Click shows. + """ + if self._f is not None: + return self._f + try: + rv, self.should_close = open_stream(self.name, self.mode, + self.encoding, + self.errors, + atomic=self.atomic) + except (IOError, OSError) as e: + from .exceptions import FileError + raise FileError(self.name, hint=get_streerror(e)) + self._f = rv + return rv + + def close(self): + """Closes the underlying file, no matter what.""" + if self._f is not None: + self._f.close() + + def close_intelligently(self): + """This function only closes the file if it was opened by the lazy + file wrapper. For instance this will never close stdin. + """ + if self.should_close: + self.close() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, tb): + self.close_intelligently() + + def __iter__(self): + self.open() + return iter(self._f) + + +class KeepOpenFile(object): + + def __init__(self, file): + self._file = file + + def __getattr__(self, name): + return getattr(self._file, name) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, tb): + pass + + def __repr__(self): + return repr(self._file) + + def __iter__(self): + return iter(self._file) + + +def echo(message=None, file=None, nl=True, err=False, color=None): + """Prints a message plus a newline to the given file or stdout. On + first sight, this looks like the print function, but it has improved + support for handling Unicode and binary data that does not fail no + matter how badly configured the system is. + + Primarily it means that you can print binary data as well as Unicode + data on both 2.x and 3.x to the given file in the most appropriate way + possible. This is a very carefree function in that it will try its + best to not fail. As of Click 6.0 this includes support for unicode + output on the Windows console. + + In addition to that, if `colorama`_ is installed, the echo function will + also support clever handling of ANSI codes. Essentially it will then + do the following: + + - add transparent handling of ANSI color codes on Windows. + - hide ANSI codes automatically if the destination file is not a + terminal. + + .. _colorama: https://pypi.org/project/colorama/ + + .. versionchanged:: 6.0 + As of Click 6.0 the echo function will properly support unicode + output on the windows console. Not that click does not modify + the interpreter in any way which means that `sys.stdout` or the + print statement or function will still not provide unicode support. + + .. versionchanged:: 2.0 + Starting with version 2.0 of Click, the echo function will work + with colorama if it's installed. + + .. versionadded:: 3.0 + The `err` parameter was added. + + .. versionchanged:: 4.0 + Added the `color` flag. + + :param message: the message to print + :param file: the file to write to (defaults to ``stdout``) + :param err: if set to true the file defaults to ``stderr`` instead of + ``stdout``. This is faster and easier than calling + :func:`get_text_stderr` yourself. + :param nl: if set to `True` (the default) a newline is printed afterwards. + :param color: controls if the terminal supports ANSI colors or not. The + default is autodetection. + """ + if file is None: + if err: + file = _default_text_stderr() + else: + file = _default_text_stdout() + + # Convert non bytes/text into the native string type. + if message is not None and not isinstance(message, echo_native_types): + message = text_type(message) + + if nl: + message = message or u'' + if isinstance(message, text_type): + message += u'\n' + else: + message += b'\n' + + # If there is a message, and we're in Python 3, and the value looks + # like bytes, we manually need to find the binary stream and write the + # message in there. This is done separately so that most stream + # types will work as you would expect. Eg: you can write to StringIO + # for other cases. + if message and not PY2 and is_bytes(message): + binary_file = _find_binary_writer(file) + if binary_file is not None: + file.flush() + binary_file.write(message) + binary_file.flush() + return + + # ANSI-style support. If there is no message or we are dealing with + # bytes nothing is happening. If we are connected to a file we want + # to strip colors. If we are on windows we either wrap the stream + # to strip the color or we use the colorama support to translate the + # ansi codes to API calls. + if message and not is_bytes(message): + color = resolve_color_default(color) + if should_strip_ansi(file, color): + message = strip_ansi(message) + elif WIN: + if auto_wrap_for_ansi is not None: + file = auto_wrap_for_ansi(file) + elif not color: + message = strip_ansi(message) + + if message: + file.write(message) + file.flush() + + +def get_binary_stream(name): + """Returns a system stream for byte processing. This essentially + returns the stream from the sys module with the given name but it + solves some compatibility issues between different Python versions. + Primarily this function is necessary for getting binary streams on + Python 3. + + :param name: the name of the stream to open. Valid names are ``'stdin'``, + ``'stdout'`` and ``'stderr'`` + """ + opener = binary_streams.get(name) + if opener is None: + raise TypeError('Unknown standard stream %r' % name) + return opener() + + +def get_text_stream(name, encoding=None, errors='strict'): + """Returns a system stream for text processing. This usually returns + a wrapped stream around a binary stream returned from + :func:`get_binary_stream` but it also can take shortcuts on Python 3 + for already correctly configured streams. + + :param name: the name of the stream to open. Valid names are ``'stdin'``, + ``'stdout'`` and ``'stderr'`` + :param encoding: overrides the detected default encoding. + :param errors: overrides the default error mode. + """ + opener = text_streams.get(name) + if opener is None: + raise TypeError('Unknown standard stream %r' % name) + return opener(encoding, errors) + + +def open_file(filename, mode='r', encoding=None, errors='strict', + lazy=False, atomic=False): + """This is similar to how the :class:`File` works but for manual + usage. Files are opened non lazy by default. This can open regular + files as well as stdin/stdout if ``'-'`` is passed. + + If stdin/stdout is returned the stream is wrapped so that the context + manager will not close the stream accidentally. This makes it possible + to always use the function like this without having to worry to + accidentally close a standard stream:: + + with open_file(filename) as f: + ... + + .. versionadded:: 3.0 + + :param filename: the name of the file to open (or ``'-'`` for stdin/stdout). + :param mode: the mode in which to open the file. + :param encoding: the encoding to use. + :param errors: the error handling for this file. + :param lazy: can be flipped to true to open the file lazily. + :param atomic: in atomic mode writes go into a temporary file and it's + moved on close. + """ + if lazy: + return LazyFile(filename, mode, encoding, errors, atomic=atomic) + f, should_close = open_stream(filename, mode, encoding, errors, + atomic=atomic) + if not should_close: + f = KeepOpenFile(f) + return f + + +def get_os_args(): + """This returns the argument part of sys.argv in the most appropriate + form for processing. What this means is that this return value is in + a format that works for Click to process but does not necessarily + correspond well to what's actually standard for the interpreter. + + On most environments the return value is ``sys.argv[:1]`` unchanged. + However if you are on Windows and running Python 2 the return value + will actually be a list of unicode strings instead because the + default behavior on that platform otherwise will not be able to + carry all possible values that sys.argv can have. + + .. versionadded:: 6.0 + """ + # We can only extract the unicode argv if sys.argv has not been + # changed since the startup of the application. + if PY2 and WIN and _initial_argv_hash == _hash_py_argv(): + return _get_windows_argv() + return sys.argv[1:] + + +def format_filename(filename, shorten=False): + """Formats a filename for user display. The main purpose of this + function is to ensure that the filename can be displayed at all. This + will decode the filename to unicode if necessary in a way that it will + not fail. Optionally, it can shorten the filename to not include the + full path to the filename. + + :param filename: formats a filename for UI display. This will also convert + the filename into unicode without failing. + :param shorten: this optionally shortens the filename to strip of the + path that leads up to it. + """ + if shorten: + filename = os.path.basename(filename) + return filename_to_ui(filename) + + +def get_app_dir(app_name, roaming=True, force_posix=False): + r"""Returns the config folder for the application. The default behavior + is to return whatever is most appropriate for the operating system. + + To give you an idea, for an app called ``"Foo Bar"``, something like + the following folders could be returned: + + Mac OS X: + ``~/Library/Application Support/Foo Bar`` + Mac OS X (POSIX): + ``~/.foo-bar`` + Unix: + ``~/.config/foo-bar`` + Unix (POSIX): + ``~/.foo-bar`` + Win XP (roaming): + ``C:\Documents and Settings\\Local Settings\Application Data\Foo Bar`` + Win XP (not roaming): + ``C:\Documents and Settings\\Application Data\Foo Bar`` + Win 7 (roaming): + ``C:\Users\\AppData\Roaming\Foo Bar`` + Win 7 (not roaming): + ``C:\Users\\AppData\Local\Foo Bar`` + + .. versionadded:: 2.0 + + :param app_name: the application name. This should be properly capitalized + and can contain whitespace. + :param roaming: controls if the folder should be roaming or not on Windows. + Has no affect otherwise. + :param force_posix: if this is set to `True` then on any POSIX system the + folder will be stored in the home folder with a leading + dot instead of the XDG config home or darwin's + application support folder. + """ + if WIN: + key = roaming and 'APPDATA' or 'LOCALAPPDATA' + folder = os.environ.get(key) + if folder is None: + folder = os.path.expanduser('~') + return os.path.join(folder, app_name) + if force_posix: + return os.path.join(os.path.expanduser('~/.' + _posixify(app_name))) + if sys.platform == 'darwin': + return os.path.join(os.path.expanduser( + '~/Library/Application Support'), app_name) + return os.path.join( + os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')), + _posixify(app_name)) + + +class PacifyFlushWrapper(object): + """This wrapper is used to catch and suppress BrokenPipeErrors resulting + from ``.flush()`` being called on broken pipe during the shutdown/final-GC + of the Python interpreter. Notably ``.flush()`` is always called on + ``sys.stdout`` and ``sys.stderr``. So as to have minimal impact on any + other cleanup code, and the case where the underlying file is not a broken + pipe, all calls and attributes are proxied. + """ + + def __init__(self, wrapped): + self.wrapped = wrapped + + def flush(self): + try: + self.wrapped.flush() + except IOError as e: + import errno + if e.errno != errno.EPIPE: + raise + + def __getattr__(self, attr): + return getattr(self.wrapped, attr) diff --git a/libs/decorator.py b/libs/decorator.py new file mode 100644 index 00000000..44303eed --- /dev/null +++ b/libs/decorator.py @@ -0,0 +1,432 @@ +# ######################### LICENSE ############################ # + +# Copyright (c) 2005-2018, Michele Simionato +# All rights reserved. + +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: + +# Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# Redistributions in bytecode form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. + +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. + +""" +Decorator module, see http://pypi.python.org/pypi/decorator +for the documentation. +""" +from __future__ import print_function + +import re +import sys +import inspect +import operator +import itertools +import collections + +__version__ = '4.3.0' + +if sys.version >= '3': + from inspect import getfullargspec + + def get_init(cls): + return cls.__init__ +else: + FullArgSpec = collections.namedtuple( + 'FullArgSpec', 'args varargs varkw defaults ' + 'kwonlyargs kwonlydefaults annotations') + + def getfullargspec(f): + "A quick and dirty replacement for getfullargspec for Python 2.X" + return FullArgSpec._make(inspect.getargspec(f) + ([], None, {})) + + def get_init(cls): + return cls.__init__.__func__ + +try: + iscoroutinefunction = inspect.iscoroutinefunction +except AttributeError: + # let's assume there are no coroutine functions in old Python + def iscoroutinefunction(f): + return False + + +DEF = re.compile(r'\s*def\s*([_\w][_\w\d]*)\s*\(') + + +# basic functionality +class FunctionMaker(object): + """ + An object with the ability to create functions with a given signature. + It has attributes name, doc, module, signature, defaults, dict and + methods update and make. + """ + + # Atomic get-and-increment provided by the GIL + _compile_count = itertools.count() + + # make pylint happy + args = varargs = varkw = defaults = kwonlyargs = kwonlydefaults = () + + def __init__(self, func=None, name=None, signature=None, + defaults=None, doc=None, module=None, funcdict=None): + self.shortsignature = signature + if func: + # func can be a class or a callable, but not an instance method + self.name = func.__name__ + if self.name == '': # small hack for lambda functions + self.name = '_lambda_' + self.doc = func.__doc__ + self.module = func.__module__ + if inspect.isfunction(func): + argspec = getfullargspec(func) + self.annotations = getattr(func, '__annotations__', {}) + for a in ('args', 'varargs', 'varkw', 'defaults', 'kwonlyargs', + 'kwonlydefaults'): + setattr(self, a, getattr(argspec, a)) + for i, arg in enumerate(self.args): + setattr(self, 'arg%d' % i, arg) + allargs = list(self.args) + allshortargs = list(self.args) + if self.varargs: + allargs.append('*' + self.varargs) + allshortargs.append('*' + self.varargs) + elif self.kwonlyargs: + allargs.append('*') # single star syntax + for a in self.kwonlyargs: + allargs.append('%s=None' % a) + allshortargs.append('%s=%s' % (a, a)) + if self.varkw: + allargs.append('**' + self.varkw) + allshortargs.append('**' + self.varkw) + self.signature = ', '.join(allargs) + self.shortsignature = ', '.join(allshortargs) + self.dict = func.__dict__.copy() + # func=None happens when decorating a caller + if name: + self.name = name + if signature is not None: + self.signature = signature + if defaults: + self.defaults = defaults + if doc: + self.doc = doc + if module: + self.module = module + if funcdict: + self.dict = funcdict + # check existence required attributes + assert hasattr(self, 'name') + if not hasattr(self, 'signature'): + raise TypeError('You are decorating a non function: %s' % func) + + def update(self, func, **kw): + "Update the signature of func with the data in self" + func.__name__ = self.name + func.__doc__ = getattr(self, 'doc', None) + func.__dict__ = getattr(self, 'dict', {}) + func.__defaults__ = self.defaults + func.__kwdefaults__ = self.kwonlydefaults or None + func.__annotations__ = getattr(self, 'annotations', None) + try: + frame = sys._getframe(3) + except AttributeError: # for IronPython and similar implementations + callermodule = '?' + else: + callermodule = frame.f_globals.get('__name__', '?') + func.__module__ = getattr(self, 'module', callermodule) + func.__dict__.update(kw) + + def make(self, src_templ, evaldict=None, addsource=False, **attrs): + "Make a new function from a given template and update the signature" + src = src_templ % vars(self) # expand name and signature + evaldict = evaldict or {} + mo = DEF.search(src) + if mo is None: + raise SyntaxError('not a valid function template\n%s' % src) + name = mo.group(1) # extract the function name + names = set([name] + [arg.strip(' *') for arg in + self.shortsignature.split(',')]) + for n in names: + if n in ('_func_', '_call_'): + raise NameError('%s is overridden in\n%s' % (n, src)) + + if not src.endswith('\n'): # add a newline for old Pythons + src += '\n' + + # Ensure each generated function has a unique filename for profilers + # (such as cProfile) that depend on the tuple of (, + # , ) being unique. + filename = '' % (next(self._compile_count),) + try: + code = compile(src, filename, 'single') + exec(code, evaldict) + except Exception: + print('Error in generated code:', file=sys.stderr) + print(src, file=sys.stderr) + raise + func = evaldict[name] + if addsource: + attrs['__source__'] = src + self.update(func, **attrs) + return func + + @classmethod + def create(cls, obj, body, evaldict, defaults=None, + doc=None, module=None, addsource=True, **attrs): + """ + Create a function from the strings name, signature and body. + evaldict is the evaluation dictionary. If addsource is true an + attribute __source__ is added to the result. The attributes attrs + are added, if any. + """ + if isinstance(obj, str): # "name(signature)" + name, rest = obj.strip().split('(', 1) + signature = rest[:-1] # strip a right parens + func = None + else: # a function + name = None + signature = None + func = obj + self = cls(func, name, signature, defaults, doc, module) + ibody = '\n'.join(' ' + line for line in body.splitlines()) + caller = evaldict.get('_call_') # when called from `decorate` + if caller and iscoroutinefunction(caller): + body = ('async def %(name)s(%(signature)s):\n' + ibody).replace( + 'return', 'return await') + else: + body = 'def %(name)s(%(signature)s):\n' + ibody + return self.make(body, evaldict, addsource, **attrs) + + +def decorate(func, caller, extras=()): + """ + decorate(func, caller) decorates a function using a caller. + """ + evaldict = dict(_call_=caller, _func_=func) + es = '' + for i, extra in enumerate(extras): + ex = '_e%d_' % i + evaldict[ex] = extra + es += ex + ', ' + fun = FunctionMaker.create( + func, "return _call_(_func_, %s%%(shortsignature)s)" % es, + evaldict, __wrapped__=func) + if hasattr(func, '__qualname__'): + fun.__qualname__ = func.__qualname__ + return fun + + +def decorator(caller, _func=None): + """decorator(caller) converts a caller function into a decorator""" + if _func is not None: # return a decorated function + # this is obsolete behavior; you should use decorate instead + return decorate(_func, caller) + # else return a decorator function + defaultargs, defaults = '', () + if inspect.isclass(caller): + name = caller.__name__.lower() + doc = 'decorator(%s) converts functions/generators into ' \ + 'factories of %s objects' % (caller.__name__, caller.__name__) + elif inspect.isfunction(caller): + if caller.__name__ == '': + name = '_lambda_' + else: + name = caller.__name__ + doc = caller.__doc__ + nargs = caller.__code__.co_argcount + ndefs = len(caller.__defaults__ or ()) + defaultargs = ', '.join(caller.__code__.co_varnames[nargs-ndefs:nargs]) + if defaultargs: + defaultargs += ',' + defaults = caller.__defaults__ + else: # assume caller is an object with a __call__ method + name = caller.__class__.__name__.lower() + doc = caller.__call__.__doc__ + evaldict = dict(_call=caller, _decorate_=decorate) + dec = FunctionMaker.create( + '%s(%s func)' % (name, defaultargs), + 'if func is None: return lambda func: _decorate_(func, _call, (%s))\n' + 'return _decorate_(func, _call, (%s))' % (defaultargs, defaultargs), + evaldict, doc=doc, module=caller.__module__, __wrapped__=caller) + if defaults: + dec.__defaults__ = defaults + (None,) + return dec + + +# ####################### contextmanager ####################### # + +try: # Python >= 3.2 + from contextlib import _GeneratorContextManager +except ImportError: # Python >= 2.5 + from contextlib import GeneratorContextManager as _GeneratorContextManager + + +class ContextManager(_GeneratorContextManager): + def __call__(self, func): + """Context manager decorator""" + return FunctionMaker.create( + func, "with _self_: return _func_(%(shortsignature)s)", + dict(_self_=self, _func_=func), __wrapped__=func) + + +init = getfullargspec(_GeneratorContextManager.__init__) +n_args = len(init.args) +if n_args == 2 and not init.varargs: # (self, genobj) Python 2.7 + def __init__(self, g, *a, **k): + return _GeneratorContextManager.__init__(self, g(*a, **k)) + ContextManager.__init__ = __init__ +elif n_args == 2 and init.varargs: # (self, gen, *a, **k) Python 3.4 + pass +elif n_args == 4: # (self, gen, args, kwds) Python 3.5 + def __init__(self, g, *a, **k): + return _GeneratorContextManager.__init__(self, g, a, k) + ContextManager.__init__ = __init__ + +_contextmanager = decorator(ContextManager) + + +def contextmanager(func): + # Enable Pylint config: contextmanager-decorators=decorator.contextmanager + return _contextmanager(func) + + +# ############################ dispatch_on ############################ # + +def append(a, vancestors): + """ + Append ``a`` to the list of the virtual ancestors, unless it is already + included. + """ + add = True + for j, va in enumerate(vancestors): + if issubclass(va, a): + add = False + break + if issubclass(a, va): + vancestors[j] = a + add = False + if add: + vancestors.append(a) + + +# inspired from simplegeneric by P.J. Eby and functools.singledispatch +def dispatch_on(*dispatch_args): + """ + Factory of decorators turning a function into a generic function + dispatching on the given arguments. + """ + assert dispatch_args, 'No dispatch args passed' + dispatch_str = '(%s,)' % ', '.join(dispatch_args) + + def check(arguments, wrong=operator.ne, msg=''): + """Make sure one passes the expected number of arguments""" + if wrong(len(arguments), len(dispatch_args)): + raise TypeError('Expected %d arguments, got %d%s' % + (len(dispatch_args), len(arguments), msg)) + + def gen_func_dec(func): + """Decorator turning a function into a generic function""" + + # first check the dispatch arguments + argset = set(getfullargspec(func).args) + if not set(dispatch_args) <= argset: + raise NameError('Unknown dispatch arguments %s' % dispatch_str) + + typemap = {} + + def vancestors(*types): + """ + Get a list of sets of virtual ancestors for the given types + """ + check(types) + ras = [[] for _ in range(len(dispatch_args))] + for types_ in typemap: + for t, type_, ra in zip(types, types_, ras): + if issubclass(t, type_) and type_ not in t.mro(): + append(type_, ra) + return [set(ra) for ra in ras] + + def ancestors(*types): + """ + Get a list of virtual MROs, one for each type + """ + check(types) + lists = [] + for t, vas in zip(types, vancestors(*types)): + n_vas = len(vas) + if n_vas > 1: + raise RuntimeError( + 'Ambiguous dispatch for %s: %s' % (t, vas)) + elif n_vas == 1: + va, = vas + mro = type('t', (t, va), {}).mro()[1:] + else: + mro = t.mro() + lists.append(mro[:-1]) # discard t and object + return lists + + def register(*types): + """ + Decorator to register an implementation for the given types + """ + check(types) + + def dec(f): + check(getfullargspec(f).args, operator.lt, ' in ' + f.__name__) + typemap[types] = f + return f + return dec + + def dispatch_info(*types): + """ + An utility to introspect the dispatch algorithm + """ + check(types) + lst = [] + for anc in itertools.product(*ancestors(*types)): + lst.append(tuple(a.__name__ for a in anc)) + return lst + + def _dispatch(dispatch_args, *args, **kw): + types = tuple(type(arg) for arg in dispatch_args) + try: # fast path + f = typemap[types] + except KeyError: + pass + else: + return f(*args, **kw) + combinations = itertools.product(*ancestors(*types)) + next(combinations) # the first one has been already tried + for types_ in combinations: + f = typemap.get(types_) + if f is not None: + return f(*args, **kw) + + # else call the default implementation + return func(*args, **kw) + + return FunctionMaker.create( + func, 'return _f_(%s, %%(shortsignature)s)' % dispatch_str, + dict(_f_=_dispatch), register=register, default=func, + typemap=typemap, vancestors=vancestors, ancestors=ancestors, + dispatch_info=dispatch_info, __wrapped__=func) + + gen_func_dec.__name__ = 'dispatch_on' + dispatch_str + return gen_func_dec diff --git a/libs/dogpile/__init__.py b/libs/dogpile/__init__.py index f48ad105..fc8fd452 100644 --- a/libs/dogpile/__init__.py +++ b/libs/dogpile/__init__.py @@ -1,6 +1,4 @@ -# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages -try: - __import__('pkg_resources').declare_namespace(__name__) -except ImportError: - from pkgutil import extend_path - __path__ = extend_path(__path__, __name__) +__version__ = '0.7.1' + +from .lock import Lock # noqa +from .lock import NeedRegenerationException # noqa diff --git a/libs/dogpile/cache/__init__.py b/libs/dogpile/cache/__init__.py index 7cd49eaa..fb57cbcc 100644 --- a/libs/dogpile/cache/__init__.py +++ b/libs/dogpile/cache/__init__.py @@ -1,3 +1,4 @@ -__version__ = '0.5.4' +from .region import CacheRegion, register_backend, make_region # noqa -from .region import CacheRegion, register_backend, make_region +# backwards compat +from .. import __version__ # noqa diff --git a/libs/dogpile/cache/api.py b/libs/dogpile/cache/api.py index 4c5be493..d66e5a70 100644 --- a/libs/dogpile/cache/api.py +++ b/libs/dogpile/cache/api.py @@ -1,5 +1,5 @@ import operator -from .compat import py3k +from ..util.compat import py3k class NoValue(object): @@ -13,17 +13,26 @@ class NoValue(object): def payload(self): return self + def __repr__(self): + """Ensure __repr__ is a consistent value in case NoValue is used to + fill another cache key. + + """ + return '' + if py3k: - def __bool__(self): #pragma NO COVERAGE + def __bool__(self): # pragma NO COVERAGE return False else: - def __nonzero__(self): #pragma NO COVERAGE + def __nonzero__(self): # pragma NO COVERAGE return False + NO_VALUE = NoValue() """Value returned from ``get()`` that describes a key not present.""" + class CachedValue(tuple): """Represent a value stored in the cache. @@ -47,6 +56,7 @@ class CachedValue(tuple): def __reduce__(self): return CachedValue, (self.payload, self.metadata) + class CacheBackend(object): """Base class for backend implementations.""" @@ -58,7 +68,7 @@ class CacheBackend(object): """ - def __init__(self, arguments): #pragma NO COVERAGE + def __init__(self, arguments): # pragma NO COVERAGE """Construct a new :class:`.CacheBackend`. Subclasses should override this to @@ -74,12 +84,15 @@ class CacheBackend(object): def from_config_dict(cls, config_dict, prefix): prefix_len = len(prefix) return cls( - dict( - (key[prefix_len:], config_dict[key]) - for key in config_dict - if key.startswith(prefix) - ) + dict( + (key[prefix_len:], config_dict[key]) + for key in config_dict + if key.startswith(prefix) ) + ) + + def has_lock_timeout(self): + return False def get_mutex(self, key): """Return an optional mutexing object for the given key. @@ -114,7 +127,7 @@ class CacheBackend(object): """ return None - def get(self, key): #pragma NO COVERAGE + def get(self, key): # pragma NO COVERAGE """Retrieve a value from the cache. The returned value should be an instance of @@ -124,7 +137,7 @@ class CacheBackend(object): """ raise NotImplementedError() - def get_multi(self, keys): #pragma NO COVERAGE + def get_multi(self, keys): # pragma NO COVERAGE """Retrieve multiple values from the cache. The returned value should be a list, corresponding @@ -135,7 +148,7 @@ class CacheBackend(object): """ raise NotImplementedError() - def set(self, key, value): #pragma NO COVERAGE + def set(self, key, value): # pragma NO COVERAGE """Set a value in the cache. The key will be whatever was passed @@ -147,21 +160,30 @@ class CacheBackend(object): """ raise NotImplementedError() - def set_multi(self, mapping): #pragma NO COVERAGE + def set_multi(self, mapping): # pragma NO COVERAGE """Set multiple values in the cache. - The key will be whatever was passed + ``mapping`` is a dict in which + the key will be whatever was passed to the registry, processed by the "key mangling" function, if any. The value will always be an instance of :class:`.CachedValue`. + When implementing a new :class:`.CacheBackend` or cutomizing via + :class:`.ProxyBackend`, be aware that when this method is invoked by + :meth:`.Region.get_or_create_multi`, the ``mapping`` values are the + same ones returned to the upstream caller. If the subclass alters the + values in any way, it must not do so 'in-place' on the ``mapping`` dict + -- that will have the undesirable effect of modifying the returned + values as well. + .. versionadded:: 0.5.0 """ raise NotImplementedError() - def delete(self, key): #pragma NO COVERAGE + def delete(self, key): # pragma NO COVERAGE """Delete a value from the cache. The key will be whatever was passed @@ -175,7 +197,7 @@ class CacheBackend(object): """ raise NotImplementedError() - def delete_multi(self, keys): #pragma NO COVERAGE + def delete_multi(self, keys): # pragma NO COVERAGE """Delete multiple values from the cache. The key will be whatever was passed diff --git a/libs/dogpile/cache/backends/__init__.py b/libs/dogpile/cache/backends/__init__.py index 84c31abe..041f05a3 100644 --- a/libs/dogpile/cache/backends/__init__.py +++ b/libs/dogpile/cache/backends/__init__.py @@ -1,10 +1,22 @@ from dogpile.cache.region import register_backend -register_backend("dogpile.cache.null", "dogpile.cache.backends.null", "NullBackend") -register_backend("dogpile.cache.dbm", "dogpile.cache.backends.file", "DBMBackend") -register_backend("dogpile.cache.pylibmc", "dogpile.cache.backends.memcached", "PylibmcBackend") -register_backend("dogpile.cache.bmemcached", "dogpile.cache.backends.memcached", "BMemcachedBackend") -register_backend("dogpile.cache.memcached", "dogpile.cache.backends.memcached", "MemcachedBackend") -register_backend("dogpile.cache.memory", "dogpile.cache.backends.memory", "MemoryBackend") -register_backend("dogpile.cache.memory_pickle", "dogpile.cache.backends.memory", "MemoryPickleBackend") -register_backend("dogpile.cache.redis", "dogpile.cache.backends.redis", "RedisBackend") +register_backend( + "dogpile.cache.null", "dogpile.cache.backends.null", "NullBackend") +register_backend( + "dogpile.cache.dbm", "dogpile.cache.backends.file", "DBMBackend") +register_backend( + "dogpile.cache.pylibmc", "dogpile.cache.backends.memcached", + "PylibmcBackend") +register_backend( + "dogpile.cache.bmemcached", "dogpile.cache.backends.memcached", + "BMemcachedBackend") +register_backend( + "dogpile.cache.memcached", "dogpile.cache.backends.memcached", + "MemcachedBackend") +register_backend( + "dogpile.cache.memory", "dogpile.cache.backends.memory", "MemoryBackend") +register_backend( + "dogpile.cache.memory_pickle", "dogpile.cache.backends.memory", + "MemoryPickleBackend") +register_backend( + "dogpile.cache.redis", "dogpile.cache.backends.redis", "RedisBackend") diff --git a/libs/dogpile/cache/backends/file.py b/libs/dogpile/cache/backends/file.py index 5d2d2e96..309c055a 100644 --- a/libs/dogpile/cache/backends/file.py +++ b/libs/dogpile/cache/backends/file.py @@ -7,14 +7,15 @@ Provides backends that deal with local filesystem access. """ from __future__ import with_statement -from dogpile.cache.api import CacheBackend, NO_VALUE +from ..api import CacheBackend, NO_VALUE from contextlib import contextmanager -from dogpile.cache import compat -from dogpile.cache import util +from ...util import compat +from ... import util import os __all__ = 'DBMBackend', 'FileLock', 'AbstractFileLock' + class DBMBackend(CacheBackend): """A file-backend using a dbm file to store keys. @@ -135,19 +136,19 @@ class DBMBackend(CacheBackend): """ def __init__(self, arguments): self.filename = os.path.abspath( - os.path.normpath(arguments['filename']) - ) + os.path.normpath(arguments['filename']) + ) dir_, filename = os.path.split(self.filename) self.lock_factory = arguments.get("lock_factory", FileLock) self._rw_lock = self._init_lock( - arguments.get('rw_lockfile'), - ".rw.lock", dir_, filename) + arguments.get('rw_lockfile'), + ".rw.lock", dir_, filename) self._dogpile_lock = self._init_lock( - arguments.get('dogpile_lockfile'), - ".dogpile.lock", - dir_, filename, - util.KeyReentrantMutex.factory) + arguments.get('dogpile_lockfile'), + ".dogpile.lock", + dir_, filename, + util.KeyReentrantMutex.factory) # TODO: make this configurable if compat.py3k: @@ -162,9 +163,9 @@ class DBMBackend(CacheBackend): lock = self.lock_factory(os.path.join(basedir, basefile + suffix)) elif argument is not False: lock = self.lock_factory( - os.path.abspath( - os.path.normpath(argument) - )) + os.path.abspath( + os.path.normpath(argument) + )) else: return None if wrapper: @@ -209,8 +210,9 @@ class DBMBackend(CacheBackend): @contextmanager def _dbm_file(self, write): with self._use_rw_lock(write): - dbm = self.dbmmodule.open(self.filename, - "w" if write else "r") + dbm = self.dbmmodule.open( + self.filename, + "w" if write else "r") yield dbm dbm.close() @@ -233,12 +235,14 @@ class DBMBackend(CacheBackend): def set(self, key, value): with self._dbm_file(True) as dbm: - dbm[key] = compat.pickle.dumps(value) + dbm[key] = compat.pickle.dumps(value, + compat.pickle.HIGHEST_PROTOCOL) def set_multi(self, mapping): with self._dbm_file(True) as dbm: - for key,value in mapping.items(): - dbm[key] = compat.pickle.dumps(value) + for key, value in mapping.items(): + dbm[key] = compat.pickle.dumps(value, + compat.pickle.HIGHEST_PROTOCOL) def delete(self, key): with self._dbm_file(True) as dbm: @@ -255,6 +259,7 @@ class DBMBackend(CacheBackend): except KeyError: pass + class AbstractFileLock(object): """Coordinate read/write access to a file. @@ -275,10 +280,10 @@ class AbstractFileLock(object): file. Note that multithreaded environments must provide a thread-safe - version of this lock. The recommended approach for file-descriptor-based - locks is to use a Python ``threading.local()`` so that a unique file descriptor - is held per thread. See the source code of :class:`.FileLock` for an - implementation example. + version of this lock. The recommended approach for file- + descriptor-based locks is to use a Python ``threading.local()`` so + that a unique file descriptor is held per thread. See the source + code of :class:`.FileLock` for an implementation example. """ @@ -377,6 +382,7 @@ class AbstractFileLock(object): """ raise NotImplementedError() + class FileLock(AbstractFileLock): """Use lockfiles to coordinate read/write access to a file. diff --git a/libs/dogpile/cache/backends/memcached.py b/libs/dogpile/cache/backends/memcached.py index 03943504..6758a998 100644 --- a/libs/dogpile/cache/backends/memcached.py +++ b/libs/dogpile/cache/backends/memcached.py @@ -6,15 +6,16 @@ Provides backends for talking to `memcached `_. """ -from dogpile.cache.api import CacheBackend, NO_VALUE -from dogpile.cache import compat -from dogpile.cache import util +from ..api import CacheBackend, NO_VALUE +from ...util import compat +from ... import util import random import time __all__ = 'GenericMemcachedBackend', 'MemcachedBackend',\ 'PylibmcBackend', 'BMemcachedBackend', 'MemcachedLock' + class MemcachedLock(object): """Simple distributed lock using memcached. @@ -23,20 +24,21 @@ class MemcachedLock(object): """ - def __init__(self, client_fn, key): + def __init__(self, client_fn, key, timeout=0): self.client_fn = client_fn self.key = "_lock" + key + self.timeout = timeout def acquire(self, wait=True): client = self.client_fn() i = 0 while True: - if client.add(self.key, 1): + if client.add(self.key, 1, self.timeout): return True elif not wait: return False else: - sleep_time = (((i+1)*random.random()) + 2**i) / 2.5 + sleep_time = (((i + 1) * random.random()) + 2 ** i) / 2.5 time.sleep(sleep_time) if i < 15: i += 1 @@ -45,6 +47,7 @@ class MemcachedLock(object): client = self.client_fn() client.delete(self.key) + class GenericMemcachedBackend(CacheBackend): """Base class for memcached backends. @@ -60,6 +63,12 @@ class GenericMemcachedBackend(CacheBackend): processes will be talking to the same memcached instance. When left at False, dogpile will coordinate on a regular threading mutex. + :param lock_timeout: integer, number of seconds after acquiring a lock that + memcached should expire it. This argument is only valid when + ``distributed_lock`` is ``True``. + + .. versionadded:: 0.5.7 + :param memcached_expire_time: integer, when present will be passed as the ``time`` parameter to ``pylibmc.Client.set``. This is used to set the memcached expiry time for a value. @@ -104,9 +113,13 @@ class GenericMemcachedBackend(CacheBackend): # automatically. self.url = util.to_list(arguments['url']) self.distributed_lock = arguments.get('distributed_lock', False) + self.lock_timeout = arguments.get('lock_timeout', 0) self.memcached_expire_time = arguments.get( 'memcached_expire_time', 0) + def has_lock_timeout(self): + return self.lock_timeout != 0 + def _imports(self): """client library imports go here.""" raise NotImplementedError() @@ -118,6 +131,7 @@ class GenericMemcachedBackend(CacheBackend): @util.memoized_property def _clients(self): backend = self + class ClientPool(compat.threading.local): def __init__(self): self.memcached = backend._create_client() @@ -138,7 +152,8 @@ class GenericMemcachedBackend(CacheBackend): def get_mutex(self, key): if self.distributed_lock: - return MemcachedLock(lambda: self.client, key) + return MemcachedLock(lambda: self.client, key, + timeout=self.lock_timeout) else: return None @@ -157,13 +172,15 @@ class GenericMemcachedBackend(CacheBackend): ] def set(self, key, value): - self.client.set(key, + self.client.set( + key, value, **self.set_arguments ) def set_multi(self, mapping): - self.client.set_multi(mapping, + self.client.set_multi( + mapping, **self.set_arguments ) @@ -173,6 +190,7 @@ class GenericMemcachedBackend(CacheBackend): def delete_multi(self, keys): self.client.delete_multi(keys) + class MemcacheArgs(object): """Mixin which provides support for the 'time' argument to set(), 'min_compress_len' to other methods. @@ -183,13 +201,15 @@ class MemcacheArgs(object): self.set_arguments = {} if "memcached_expire_time" in arguments: - self.set_arguments["time"] =\ - arguments["memcached_expire_time"] + self.set_arguments["time"] = arguments["memcached_expire_time"] if "min_compress_len" in arguments: - self.set_arguments["min_compress_len"] =\ - arguments["min_compress_len"] + self.set_arguments["min_compress_len"] = \ + arguments["min_compress_len"] super(MemcacheArgs, self).__init__(arguments) +pylibmc = None + + class PylibmcBackend(MemcacheArgs, GenericMemcachedBackend): """A backend for the `pylibmc `_ @@ -229,19 +249,24 @@ class PylibmcBackend(MemcacheArgs, GenericMemcachedBackend): self.behaviors = arguments.get('behaviors', {}) super(PylibmcBackend, self).__init__(arguments) - def _imports(self): global pylibmc - import pylibmc + import pylibmc # noqa def _create_client(self): - return pylibmc.Client(self.url, + return pylibmc.Client( + self.url, binary=self.binary, behaviors=self.behaviors ) +memcache = None + + class MemcachedBackend(MemcacheArgs, GenericMemcachedBackend): - """A backend using the standard `Python-memcached `_ + """A backend using the standard + `Python-memcached `_ library. Example:: @@ -259,14 +284,19 @@ class MemcachedBackend(MemcacheArgs, GenericMemcachedBackend): """ def _imports(self): global memcache - import memcache + import memcache # noqa def _create_client(self): return memcache.Client(self.url) + +bmemcached = None + + class BMemcachedBackend(GenericMemcachedBackend): """A backend for the - `python-binary-memcached `_ + `python-binary-memcached `_ memcached client. This is a pure Python memcached client which @@ -312,16 +342,18 @@ class BMemcachedBackend(GenericMemcachedBackend): """ - def add(self, key, value): + def add(self, key, value, timeout=0): try: - return super(RepairBMemcachedAPI, self).add(key, value) + return super(RepairBMemcachedAPI, self).add( + key, value, timeout) except ValueError: return False self.Client = RepairBMemcachedAPI def _create_client(self): - return self.Client(self.url, + return self.Client( + self.url, username=self.username, password=self.password ) diff --git a/libs/dogpile/cache/backends/memory.py b/libs/dogpile/cache/backends/memory.py index cee989bc..e2083f7f 100644 --- a/libs/dogpile/cache/backends/memory.py +++ b/libs/dogpile/cache/backends/memory.py @@ -10,8 +10,9 @@ places the value as given into the dictionary. """ -from dogpile.cache.api import CacheBackend, NO_VALUE -from dogpile.cache.compat import pickle +from ..api import CacheBackend, NO_VALUE +from ...util.compat import pickle + class MemoryBackend(CacheBackend): """A backend that uses a plain dictionary. @@ -58,14 +59,15 @@ class MemoryBackend(CacheBackend): return value def get_multi(self, keys): - ret = [self._cache.get(key, NO_VALUE) + ret = [ + self._cache.get(key, NO_VALUE) for key in keys] if self.pickle_values: ret = [ - pickle.loads(value) - if value is not NO_VALUE else value - for value in ret - ] + pickle.loads(value) + if value is not NO_VALUE else value + for value in ret + ] return ret def set(self, key, value): diff --git a/libs/dogpile/cache/backends/null.py b/libs/dogpile/cache/backends/null.py index c1f46a9d..603cca3f 100644 --- a/libs/dogpile/cache/backends/null.py +++ b/libs/dogpile/cache/backends/null.py @@ -10,15 +10,15 @@ caching for a region that is otherwise used normally. """ -from dogpile.cache.api import CacheBackend, NO_VALUE +from ..api import CacheBackend, NO_VALUE __all__ = ['NullBackend'] class NullLock(object): - def acquire(self): - pass + def acquire(self, wait=True): + return True def release(self): pass diff --git a/libs/dogpile/cache/backends/redis.py b/libs/dogpile/cache/backends/redis.py index 365bdbc7..d665320a 100644 --- a/libs/dogpile/cache/backends/redis.py +++ b/libs/dogpile/cache/backends/redis.py @@ -7,8 +7,8 @@ Provides backends for talking to `Redis `_. """ from __future__ import absolute_import -from dogpile.cache.api import CacheBackend, NO_VALUE -from dogpile.cache.compat import pickle, u +from ..api import CacheBackend, NO_VALUE +from ...util.compat import pickle, u redis = None @@ -30,7 +30,7 @@ class RedisBackend(CacheBackend): 'port': 6379, 'db': 0, 'redis_expiration_time': 60*60*2, # 2 hours - 'distributed_lock':True + 'distributed_lock': True } ) @@ -91,6 +91,7 @@ class RedisBackend(CacheBackend): """ def __init__(self, arguments): + arguments = arguments.copy() self._imports() self.url = arguments.pop('url', None) self.host = arguments.pop('host', 'localhost') @@ -110,7 +111,7 @@ class RedisBackend(CacheBackend): def _imports(self): # defer imports until backend is used global redis - import redis + import redis # noqa def _create_client(self): if self.connection_pool is not None: @@ -133,7 +134,6 @@ class RedisBackend(CacheBackend): ) return redis.StrictRedis(**args) - def get_mutex(self, key): if self.distributed_lock: return self.client.lock(u('_lock{0}').format(key), @@ -148,9 +148,12 @@ class RedisBackend(CacheBackend): return pickle.loads(value) def get_multi(self, keys): + if not keys: + return [] values = self.client.mget(keys) - return [pickle.loads(v) if v is not None else NO_VALUE - for v in values] + return [ + pickle.loads(v) if v is not None else NO_VALUE + for v in values] def set(self, key, value): if self.redis_expiration_time: @@ -178,4 +181,3 @@ class RedisBackend(CacheBackend): def delete_multi(self, keys): self.client.delete(*keys) - diff --git a/libs/dogpile/cache/exception.py b/libs/dogpile/cache/exception.py index 58068f8b..adeb1b4a 100644 --- a/libs/dogpile/cache/exception.py +++ b/libs/dogpile/cache/exception.py @@ -15,3 +15,11 @@ class RegionNotConfigured(DogpileCacheException): class ValidationError(DogpileCacheException): """Error validating a value or option.""" + + +class PluginNotFound(DogpileCacheException): + """The specified plugin could not be found. + + .. versionadded:: 0.6.4 + + """ diff --git a/libs/dogpile/cache/plugins/mako_cache.py b/libs/dogpile/cache/plugins/mako_cache.py index a1544d62..61f4ffaf 100644 --- a/libs/dogpile/cache/plugins/mako_cache.py +++ b/libs/dogpile/cache/plugins/mako_cache.py @@ -2,7 +2,8 @@ Mako Integration ---------------- -dogpile.cache includes a `Mako `_ plugin that replaces `Beaker `_ +dogpile.cache includes a `Mako `_ plugin +that replaces `Beaker `_ as the cache backend. Setup a Mako template lookup using the "dogpile.cache" cache implementation and a region dictionary:: @@ -31,9 +32,9 @@ and a region dictionary:: } ) -To use the above configuration in a template, use the ``cached=True`` argument on any -Mako tag which accepts it, in conjunction with the name of the desired region -as the ``cache_region`` argument:: +To use the above configuration in a template, use the ``cached=True`` +argument on any Mako tag which accepts it, in conjunction with the +name of the desired region as the ``cache_region`` argument:: <%def name="mysection()" cached="True" cache_region="memcached"> some content that's cached @@ -43,6 +44,7 @@ as the ``cache_region`` argument:: """ from mako.cache import CacheImpl + class MakoPlugin(CacheImpl): """A Mako ``CacheImpl`` which talks to dogpile.cache.""" @@ -70,8 +72,9 @@ class MakoPlugin(CacheImpl): def get_and_replace(self, key, creation_function, **kw): expiration_time = kw.pop("timeout", None) - return self._get_region(**kw).get_or_create(key, creation_function, - expiration_time=expiration_time) + return self._get_region(**kw).get_or_create( + key, creation_function, + expiration_time=expiration_time) def get_or_create(self, key, creation_function, **kw): return self.get_and_replace(key, creation_function, **kw) diff --git a/libs/dogpile/cache/proxy.py b/libs/dogpile/cache/proxy.py index c1518d21..15c6b574 100644 --- a/libs/dogpile/cache/proxy.py +++ b/libs/dogpile/cache/proxy.py @@ -12,6 +12,7 @@ base backend. from .api import CacheBackend + class ProxyBackend(CacheBackend): """A decorator class for altering the functionality of backends. @@ -62,7 +63,9 @@ class ProxyBackend(CacheBackend): Return an object that be used as a backend by a :class:`.CacheRegion` object. ''' - assert(isinstance(backend, CacheBackend) or isinstance(backend, ProxyBackend)) + assert( + isinstance(backend, CacheBackend) or + isinstance(backend, ProxyBackend)) self.proxied = backend return self @@ -82,12 +85,11 @@ class ProxyBackend(CacheBackend): def get_multi(self, keys): return self.proxied.get_multi(keys) - def set_multi(self, keys): - self.proxied.set_multi(keys) + def set_multi(self, mapping): + self.proxied.set_multi(mapping) def delete_multi(self, keys): self.proxied.delete_multi(keys) def get_mutex(self, key): return self.proxied.get_mutex(key) - diff --git a/libs/dogpile/cache/region.py b/libs/dogpile/cache/region.py index 39cfd0aa..261a8db4 100644 --- a/libs/dogpile/cache/region.py +++ b/libs/dogpile/cache/region.py @@ -1,21 +1,22 @@ from __future__ import with_statement -from dogpile.core import Lock, NeedRegenerationException -from dogpile.core.nameregistry import NameRegistry +from .. import Lock, NeedRegenerationException +from ..util import NameRegistry from . import exception -from .util import function_key_generator, PluginLoader, \ - memoized_property, coerce_string_conf, function_multi_key_generator +from ..util import PluginLoader, memoized_property, coerce_string_conf +from .util import function_key_generator, function_multi_key_generator from .api import NO_VALUE, CachedValue from .proxy import ProxyBackend -from . import compat +from ..util import compat import time import datetime from numbers import Number -from functools import wraps +from functools import wraps, partial import threading +from decorator import decorate _backend_loader = PluginLoader("dogpile.cache") register_backend = _backend_loader.register -from . import backends +from . import backends # noqa value_version = 1 """An integer placed in the :class:`.CachedValue` @@ -24,8 +25,171 @@ values from a previous, backwards-incompatible version. """ + +class RegionInvalidationStrategy(object): + """Region invalidation strategy interface + + Implement this interface and pass implementation instance + to :meth:`.CacheRegion.configure` to override default region invalidation. + + Example:: + + class CustomInvalidationStrategy(RegionInvalidationStrategy): + + def __init__(self): + self._soft_invalidated = None + self._hard_invalidated = None + + def invalidate(self, hard=None): + if hard: + self._soft_invalidated = None + self._hard_invalidated = time.time() + else: + self._soft_invalidated = time.time() + self._hard_invalidated = None + + def is_invalidated(self, timestamp): + return ((self._soft_invalidated and + timestamp < self._soft_invalidated) or + (self._hard_invalidated and + timestamp < self._hard_invalidated)) + + def was_hard_invalidated(self): + return bool(self._hard_invalidated) + + def is_hard_invalidated(self, timestamp): + return (self._hard_invalidated and + timestamp < self._hard_invalidated) + + def was_soft_invalidated(self): + return bool(self._soft_invalidated) + + def is_soft_invalidated(self, timestamp): + return (self._soft_invalidated and + timestamp < self._soft_invalidated) + + The custom implementation is injected into a :class:`.CacheRegion` + at configure time using the + :paramref:`.CacheRegion.configure.region_invalidator` parameter:: + + region = CacheRegion() + + region = region.configure(region_invalidator=CustomInvalidationStrategy()) + + Invalidation strategies that wish to have access to the + :class:`.CacheRegion` itself should construct the invalidator given the + region as an argument:: + + class MyInvalidator(RegionInvalidationStrategy): + def __init__(self, region): + self.region = region + # ... + + # ... + + region = CacheRegion() + region = region.configure(region_invalidator=MyInvalidator(region)) + + .. versionadded:: 0.6.2 + + .. seealso:: + + :paramref:`.CacheRegion.configure.region_invalidator` + + """ + + def invalidate(self, hard=True): + """Region invalidation. + + :class:`.CacheRegion` propagated call. + The default invalidation system works by setting + a current timestamp (using ``time.time()``) to consider all older + timestamps effectively invalidated. + + """ + + raise NotImplementedError() + + def is_hard_invalidated(self, timestamp): + """Check timestamp to determine if it was hard invalidated. + + :return: Boolean. True if ``timestamp`` is older than + the last region invalidation time and region is invalidated + in hard mode. + + """ + + raise NotImplementedError() + + def is_soft_invalidated(self, timestamp): + """Check timestamp to determine if it was soft invalidated. + + :return: Boolean. True if ``timestamp`` is older than + the last region invalidation time and region is invalidated + in soft mode. + + """ + + raise NotImplementedError() + + def is_invalidated(self, timestamp): + """Check timestamp to determine if it was invalidated. + + :return: Boolean. True if ``timestamp`` is older than + the last region invalidation time. + + """ + + raise NotImplementedError() + + def was_soft_invalidated(self): + """Indicate the region was invalidated in soft mode. + + :return: Boolean. True if region was invalidated in soft mode. + + """ + + raise NotImplementedError() + + def was_hard_invalidated(self): + """Indicate the region was invalidated in hard mode. + + :return: Boolean. True if region was invalidated in hard mode. + + """ + + raise NotImplementedError() + + +class DefaultInvalidationStrategy(RegionInvalidationStrategy): + + def __init__(self): + self._is_hard_invalidated = None + self._invalidated = None + + def invalidate(self, hard=True): + self._is_hard_invalidated = bool(hard) + self._invalidated = time.time() + + def is_invalidated(self, timestamp): + return (self._invalidated is not None and + timestamp < self._invalidated) + + def was_hard_invalidated(self): + return self._is_hard_invalidated is True + + def is_hard_invalidated(self, timestamp): + return self.was_hard_invalidated() and self.is_invalidated(timestamp) + + def was_soft_invalidated(self): + return self._is_hard_invalidated is False + + def is_soft_invalidated(self, timestamp): + return self.was_soft_invalidated() and self.is_invalidated(timestamp) + + class CacheRegion(object): - """A front end to a particular cache backend. + r"""A front end to a particular cache backend. :param name: Optional, a string name for the region. This isn't used internally @@ -78,6 +242,13 @@ class CacheRegion(object): def my_function(a, b, **kw): return my_data() + .. seealso:: + + :func:`.function_key_generator` - default key generator + + :func:`.kwarg_function_key_generator` - optional gen that also + uses keyword arguments + :param function_multi_key_generator: Optional. Similar to ``function_key_generator`` parameter, but it's used in :meth:`.CacheRegion.cache_multi_on_arguments`. Generated function @@ -156,7 +327,8 @@ class CacheRegion(object): """ - def __init__(self, + def __init__( + self, name=None, function_key_generator=function_key_generator, function_multi_key_generator=function_multi_key_generator, @@ -167,21 +339,20 @@ class CacheRegion(object): self.name = name self.function_key_generator = function_key_generator self.function_multi_key_generator = function_multi_key_generator - if key_mangler: - self.key_mangler = key_mangler - else: - self.key_mangler = None - self._hard_invalidated = None - self._soft_invalidated = None + self.key_mangler = self._user_defined_key_mangler = key_mangler self.async_creation_runner = async_creation_runner + self.region_invalidator = DefaultInvalidationStrategy() - def configure(self, backend, + def configure( + self, backend, expiration_time=None, arguments=None, _config_argument_dict=None, _config_prefix=None, - wrap=None - ): + wrap=None, + replace_existing_backend=False, + region_invalidator=None + ): """Configure a :class:`.CacheRegion`. The :class:`.CacheRegion` itself @@ -220,14 +391,33 @@ class CacheRegion(object): :ref:`changing_backend_behavior` + :param replace_existing_backend: if True, the existing cache backend + will be replaced. Without this flag, an exception is raised if + a backend is already configured. + + .. versionadded:: 0.5.7 + + :param region_invalidator: Optional. Override default invalidation + strategy with custom implementation of + :class:`.RegionInvalidationStrategy`. + + .. versionadded:: 0.6.2 + """ - if "backend" in self.__dict__: + if "backend" in self.__dict__ and not replace_existing_backend: raise exception.RegionAlreadyConfigured( - "This region is already " - "configured with backend: %s" - % self.backend) - backend_cls = _backend_loader.load(backend) + "This region is already " + "configured with backend: %s. " + "Specify replace_existing_backend=True to replace." + % self.backend) + + try: + backend_cls = _backend_loader.load(backend) + except PluginLoader.NotFound: + raise exception.PluginNotFound( + "Couldn't find cache plugin to load: %s" % backend) + if _config_argument_dict: self.backend = backend_cls.from_config_dict( _config_argument_dict, @@ -239,20 +429,24 @@ class CacheRegion(object): if not expiration_time or isinstance(expiration_time, Number): self.expiration_time = expiration_time elif isinstance(expiration_time, datetime.timedelta): - self.expiration_time = int(compat.timedelta_total_seconds(expiration_time)) + self.expiration_time = int( + compat.timedelta_total_seconds(expiration_time)) else: raise exception.ValidationError( 'expiration_time is not a number or timedelta.') - if self.key_mangler is None: + if not self._user_defined_key_mangler: self.key_mangler = self.backend.key_mangler self._lock_registry = NameRegistry(self._create_mutex) - if getattr(wrap,'__iter__', False): + if getattr(wrap, '__iter__', False): for wrapper in reversed(wrap): self.wrap(wrapper) + if region_invalidator: + self.region_invalidator = region_invalidator + return self def wrap(self, proxy): @@ -270,7 +464,6 @@ class CacheRegion(object): self.backend = proxy.wrap(self.backend) - def _mutex(self, key): return self._lock_registry.get(key) @@ -292,29 +485,60 @@ class CacheRegion(object): else: return self._LockWrapper() + # cached value + _actual_backend = None + + @property + def actual_backend(self): + """Return the ultimate backend underneath any proxies. + + The backend might be the result of one or more ``proxy.wrap`` + applications. If so, derive the actual underlying backend. + + .. versionadded:: 0.6.6 + + """ + if self._actual_backend is None: + _backend = self.backend + while hasattr(_backend, 'proxied'): + _backend = _backend.proxied + self._actual_backend = _backend + return self._actual_backend + def invalidate(self, hard=True): """Invalidate this :class:`.CacheRegion`. - Invalidation works by setting a current timestamp - (using ``time.time()``) + The default invalidation system works by setting + a current timestamp (using ``time.time()``) representing the "minimum creation time" for a value. Any retrieved value whose creation time is prior to this timestamp is considered to be stale. It does not - affect the data in the cache in any way, and is also - local to this instance of :class:`.CacheRegion`. + affect the data in the cache in any way, and is + **local to this instance of :class:`.CacheRegion`.** + + .. warning:: + + The :meth:`.CacheRegion.invalidate` method's default mode of + operation is to set a timestamp **local to this CacheRegion + in this Python process only**. It does not impact other Python + processes or regions as the timestamp is **only stored locally in + memory**. To implement invalidation where the + timestamp is stored in the cache or similar so that all Python + processes can be affected by an invalidation timestamp, implement a + custom :class:`.RegionInvalidationStrategy`. Once set, the invalidation time is honored by the :meth:`.CacheRegion.get_or_create`, :meth:`.CacheRegion.get_or_create_multi` and :meth:`.CacheRegion.get` methods. - The method - supports both "hard" and "soft" invalidation options. With "hard" - invalidation, :meth:`.CacheRegion.get_or_create` will force an immediate - regeneration of the value which all getters will wait for. With - "soft" invalidation, subsequent getters will return the "old" value until - the new one is available. + The method supports both "hard" and "soft" invalidation + options. With "hard" invalidation, + :meth:`.CacheRegion.get_or_create` will force an immediate + regeneration of the value which all getters will wait for. + With "soft" invalidation, subsequent getters will return the + "old" value until the new one is available. Usage of "soft" invalidation requires that the region or the method is given a non-None expiration time. @@ -329,12 +553,7 @@ class CacheRegion(object): .. versionadded:: 0.5.1 """ - if hard: - self._hard_invalidated = time.time() - self._soft_invalidated = None - else: - self._hard_invalidated = None - self._soft_invalidated = time.time() + self.region_invalidator.invalidate(hard) def configure_from_config(self, config_dict, prefix): """Configure from a configuration dictionary @@ -363,13 +582,16 @@ class CacheRegion(object): config_dict = coerce_string_conf(config_dict) return self.configure( config_dict["%sbackend" % prefix], - expiration_time = config_dict.get( - "%sexpiration_time" % prefix, None), + expiration_time=config_dict.get( + "%sexpiration_time" % prefix, None), _config_argument_dict=config_dict, - _config_prefix="%sarguments." % prefix + _config_prefix="%sarguments." % prefix, + wrap=config_dict.get( + "%swrap" % prefix, None), + replace_existing_backend=config_dict.get( + "%sreplace_existing_backend" % prefix, False), ) - @memoized_property def backend(self): raise exception.RegionNotConfigured( @@ -443,7 +665,7 @@ class CacheRegion(object): key = self.key_mangler(key) value = self.backend.get(key) value = self._unexpired_value_fn( - expiration_time, ignore_expiration)(value) + expiration_time, ignore_expiration)(value) return value.payload @@ -456,15 +678,14 @@ class CacheRegion(object): current_time = time.time() - invalidated = self._hard_invalidated or self._soft_invalidated def value_fn(value): if value is NO_VALUE: return value elif expiration_time is not None and \ - current_time - value.metadata["ct"] > expiration_time: + current_time - value.metadata["ct"] > expiration_time: return NO_VALUE - elif invalidated and \ - value.metadata["ct"] < invalidated: + elif self.region_invalidator.is_invalidated( + value.metadata["ct"]): return NO_VALUE else: return value @@ -512,18 +733,19 @@ class CacheRegion(object): backend_values = self.backend.get_multi(keys) _unexpired_value_fn = self._unexpired_value_fn( - expiration_time, ignore_expiration) + expiration_time, ignore_expiration) return [ - value.payload if value is not NO_VALUE else value - for value in - ( - _unexpired_value_fn(value) for value in - backend_values - ) - ] + value.payload if value is not NO_VALUE else value + for value in + ( + _unexpired_value_fn(value) for value in + backend_values + ) + ] - def get_or_create(self, key, creator, expiration_time=None, - should_cache_fn=None): + def get_or_create( + self, key, creator, expiration_time=None, should_cache_fn=None, + creator_args=None): """Return a cached value based on the given key. If the value does not exist or is considered to be expired @@ -559,14 +781,19 @@ class CacheRegion(object): :param creator: function which creates a new value. + :param creator_args: optional tuple of (args, kwargs) that will be + passed to the creator function if present. + + .. versionadded:: 0.7.0 + :param expiration_time: optional expiration time which will overide the expiration time already configured on this :class:`.CacheRegion` if not None. To set no expiration, use the value -1. - :param should_cache_fn: optional callable function which will receive the - value returned by the "creator", and will then return True or False, - indicating if the value should actually be cached or not. If it - returns False, the value is still returned, but isn't cached. + :param should_cache_fn: optional callable function which will receive + the value returned by the "creator", and will then return True or + False, indicating if the value should actually be cached or not. If + it returns False, the value is still returned, but isn't cached. E.g.:: def dont_cache_none(value): @@ -587,7 +814,8 @@ class CacheRegion(object): :meth:`.CacheRegion.cache_on_arguments` - applies :meth:`.get_or_create` to any function using a decorator. - :meth:`.CacheRegion.get_or_create_multi` - multiple key/value version + :meth:`.CacheRegion.get_or_create_multi` - multiple key/value + version """ orig_key = key @@ -596,20 +824,21 @@ class CacheRegion(object): def get_value(): value = self.backend.get(key) - if value is NO_VALUE or \ - value.metadata['v'] != value_version or \ - (self._hard_invalidated and - value.metadata["ct"] < self._hard_invalidated): + if (value is NO_VALUE or value.metadata['v'] != value_version or + self.region_invalidator.is_hard_invalidated( + value.metadata["ct"])): raise NeedRegenerationException() ct = value.metadata["ct"] - if self._soft_invalidated: - if ct < self._soft_invalidated: - ct = time.time() - expiration_time - .0001 + if self.region_invalidator.is_soft_invalidated(ct): + ct = time.time() - expiration_time - .0001 return value.payload, ct def gen_value(): - created_value = creator() + if creator_args: + created_value = creator(*creator_args[0], **creator_args[1]) + else: + created_value = creator() value = self._value(created_value) if not should_cache_fn or \ @@ -621,14 +850,24 @@ class CacheRegion(object): if expiration_time is None: expiration_time = self.expiration_time - if expiration_time is None and self._soft_invalidated: + if (expiration_time is None and + self.region_invalidator.was_soft_invalidated()): raise exception.DogpileCacheException( - "Non-None expiration time required " - "for soft invalidation") + "Non-None expiration time required " + "for soft invalidation") + + if expiration_time == -1: + expiration_time = None if self.async_creation_runner: def async_creator(mutex): - return self.async_creation_runner(self, orig_key, creator, mutex) + if creator_args: + @wraps(creator) + def go(): + return creator(*creator_args[0], **creator_args[1]) + else: + go = creator + return self.async_creation_runner(self, orig_key, go, mutex) else: async_creator = None @@ -640,8 +879,8 @@ class CacheRegion(object): async_creator) as value: return value - def get_or_create_multi(self, keys, creator, expiration_time=None, - should_cache_fn=None): + def get_or_create_multi( + self, keys, creator, expiration_time=None, should_cache_fn=None): """Return a sequence of cached values based on a sequence of keys. The behavior for generation of values based on keys corresponds @@ -655,6 +894,13 @@ class CacheRegion(object): and :meth:`.Region.set_multi` to get and set values from the backend. + If you are using a :class:`.CacheBackend` or :class:`.ProxyBackend` + that modifies values, take note this function invokes + ``.set_multi()`` for newly generated values using the same values it + returns to the calling function. A correct implementation of + ``.set_multi()`` will not modify values in-place on the submitted + ``mapping`` dict. + :param keys: Sequence of keys to be retrieved. :param creator: function which accepts a sequence of keys and @@ -665,9 +911,9 @@ class CacheRegion(object): if not None. To set no expiration, use the value -1. :param should_cache_fn: optional callable function which will receive - each value returned by the "creator", and will then return True or False, - indicating if the value should actually be cached or not. If it - returns False, the value is still returned, but isn't cached. + each value returned by the "creator", and will then return True or + False, indicating if the value should actually be cached or not. If + it returns False, the value is still returned, but isn't cached. .. versionadded:: 0.5.0 @@ -683,19 +929,17 @@ class CacheRegion(object): def get_value(key): value = values.get(key, NO_VALUE) - if value is NO_VALUE or \ - value.metadata['v'] != value_version or \ - (self._hard_invalidated and - value.metadata["ct"] < self._hard_invalidated): + if (value is NO_VALUE or value.metadata['v'] != value_version or + self.region_invalidator.is_hard_invalidated( + value.metadata['ct'])): # dogpile.core understands a 0 here as # "the value is not available", e.g. # _has_value() will return False. return value.payload, 0 else: ct = value.metadata["ct"] - if self._soft_invalidated: - if ct < self._soft_invalidated: - ct = time.time() - expiration_time - .0001 + if self.region_invalidator.is_soft_invalidated(ct): + ct = time.time() - expiration_time - .0001 return value.payload, ct @@ -708,10 +952,14 @@ class CacheRegion(object): if expiration_time is None: expiration_time = self.expiration_time - if expiration_time is None and self._soft_invalidated: + if (expiration_time is None and + self.region_invalidator.was_soft_invalidated()): raise exception.DogpileCacheException( - "Non-None expiration time required " - "for soft invalidation") + "Non-None expiration time required " + "for soft invalidation") + + if expiration_time == -1: + expiration_time = None mutexes = {} @@ -732,8 +980,8 @@ class CacheRegion(object): gen_value, lambda: get_value(mangled_key), expiration_time, - async_creator=lambda mutex: - async_creator(orig_key, mutex)): + async_creator=lambda mutex: async_creator(orig_key, mutex) + ): pass try: if mutexes: @@ -750,11 +998,14 @@ class CacheRegion(object): if not should_cache_fn: self.backend.set_multi(values_w_created) else: - self.backend.set_multi(dict( + values_to_cache = dict( (k, v) for k, v in values_w_created.items() if should_cache_fn(v[0]) - )) + ) + + if values_to_cache: + self.backend.set_multi(values_to_cache) values.update(values_w_created) return [values[orig_to_mangled[k]].payload for k in keys] @@ -762,13 +1013,14 @@ class CacheRegion(object): for mutex in mutexes.values(): mutex.release() - def _value(self, value): """Return a :class:`.CachedValue` given a value.""" - return CachedValue(value, { - "ct": time.time(), - "v": value_version - }) + return CachedValue( + value, + { + "ct": time.time(), + "v": value_version + }) def set(self, key, value): """Place a new value in the cache under the given key.""" @@ -777,7 +1029,6 @@ class CacheRegion(object): key = self.key_mangler(key) self.backend.set(key, self._value(value)) - def set_multi(self, mapping): """Place new values in the cache under the given keys. @@ -788,13 +1039,13 @@ class CacheRegion(object): return if self.key_mangler: - mapping = dict((self.key_mangler(k), self._value(v)) - for k, v in mapping.items()) + mapping = dict(( + self.key_mangler(k), self._value(v)) + for k, v in mapping.items()) else: mapping = dict((k, self._value(v)) for k, v in mapping.items()) self.backend.set_multi(mapping) - def delete(self, key): """Remove a value from the cache. @@ -807,7 +1058,6 @@ class CacheRegion(object): self.backend.delete(key) - def delete_multi(self, keys): """Remove multiple values from the cache. @@ -823,11 +1073,12 @@ class CacheRegion(object): self.backend.delete_multi(keys) - - def cache_on_arguments(self, namespace=None, - expiration_time=None, - should_cache_fn=None, - to_str=compat.string_type): + def cache_on_arguments( + self, namespace=None, + expiration_time=None, + should_cache_fn=None, + to_str=compat.string_type, + function_key_generator=None): """A function decorator that will cache the return value of the function using a key derived from the function itself and its arguments. @@ -866,9 +1117,9 @@ class CacheRegion(object): generate_something.set(3, 5, 6) - The above example is equivalent to calling ``generate_something(5, 6)``, - if the function were to produce the value ``3`` as the value to be - cached. + The above example is equivalent to calling + ``generate_something(5, 6)``, if the function were to produce + the value ``3`` as the value to be cached. .. versionadded:: 0.4.1 Added ``set()`` method to decorated function. @@ -881,6 +1132,14 @@ class CacheRegion(object): .. versionadded:: 0.5.0 Added ``refresh()`` method to decorated function. + ``original()`` on other hand will invoke the decorated function + without any caching:: + + newvalue = generate_something.original(5, 6) + + .. versionadded:: 0.6.0 Added ``original()`` method to decorated + function. + Lastly, the ``get()`` method returns either the value cached for the given key, or the token ``NO_VALUE`` if no such key exists:: @@ -986,6 +1245,12 @@ class CacheRegion(object): .. versionadded:: 0.5.0 + :param function_key_generator: a function that will produce a + "cache key". This function will supersede the one configured on the + :class:`.CacheRegion` itself. + + .. versionadded:: 0.5.5 + .. seealso:: :meth:`.CacheRegion.cache_multi_on_arguments` @@ -994,23 +1259,35 @@ class CacheRegion(object): """ expiration_time_is_callable = compat.callable(expiration_time) - def decorator(fn): + + if function_key_generator is None: + function_key_generator = self.function_key_generator + + def get_or_create_for_user_func(key_generator, user_func, *arg, **kw): + key = key_generator(*arg, **kw) + + timeout = expiration_time() if expiration_time_is_callable \ + else expiration_time + return self.get_or_create(key, user_func, timeout, + should_cache_fn, (arg, kw)) + + def cache_decorator(user_func): if to_str is compat.string_type: # backwards compatible - key_generator = self.function_key_generator(namespace, fn) + key_generator = function_key_generator(namespace, user_func) else: - key_generator = self.function_key_generator(namespace, fn, - to_str=to_str) - @wraps(fn) - def decorate(*arg, **kw): + key_generator = function_key_generator( + namespace, user_func, + to_str=to_str) + + def refresh(*arg, **kw): + """ + Like invalidate, but regenerates the value instead + """ key = key_generator(*arg, **kw) - @wraps(fn) - def creator(): - return fn(*arg, **kw) - timeout = expiration_time() if expiration_time_is_callable \ - else expiration_time - return self.get_or_create(key, creator, timeout, - should_cache_fn) + value = user_func(*arg, **kw) + self.set(key, value) + return value def invalidate(*arg, **kw): key = key_generator(*arg, **kw) @@ -1024,23 +1301,24 @@ class CacheRegion(object): key = key_generator(*arg, **kw) return self.get(key) - def refresh(*arg, **kw): - key = key_generator(*arg, **kw) - value = fn(*arg, **kw) - self.set(key, value) - return value + user_func.set = set_ + user_func.invalidate = invalidate + user_func.get = get + user_func.refresh = refresh + user_func.original = user_func - decorate.set = set_ - decorate.invalidate = invalidate - decorate.refresh = refresh - decorate.get = get + # Use `decorate` to preserve the signature of :param:`user_func`. - return decorate - return decorator + return decorate(user_func, partial( + get_or_create_for_user_func, key_generator)) - def cache_multi_on_arguments(self, namespace=None, expiration_time=None, - should_cache_fn=None, - asdict=False, to_str=compat.string_type): + return cache_decorator + + def cache_multi_on_arguments( + self, namespace=None, expiration_time=None, + should_cache_fn=None, + asdict=False, to_str=compat.string_type, + function_multi_key_generator=None): """A function decorator that will cache multiple return values from the function using a sequence of keys derived from the function itself and the arguments passed to it. @@ -1058,15 +1336,16 @@ class CacheRegion(object): ] The decorated function can be called normally. The decorator - will produce a list of cache keys using a mechanism similar to that - of :meth:`.CacheRegion.cache_on_arguments`, combining the name - of the function with the optional namespace and with the string form - of each key. It will then consult the cache using the same mechanism as that - of :meth:`.CacheRegion.get_multi` to retrieve all current values; - the originally passed keys corresponding to those values which - aren't generated or need regeneration will - be assembled into a new argument list, and the decorated function - is then called with that subset of arguments. + will produce a list of cache keys using a mechanism similar to + that of :meth:`.CacheRegion.cache_on_arguments`, combining the + name of the function with the optional namespace and with the + string form of each key. It will then consult the cache using + the same mechanism as that of :meth:`.CacheRegion.get_multi` + to retrieve all current values; the originally passed keys + corresponding to those values which aren't generated or need + regeneration will be assembled into a new argument list, and + the decorated function is then called with that subset of + arguments. The returned result is a list:: @@ -1077,10 +1356,10 @@ class CacheRegion(object): cache and conditionally call the function. See that method for additional behavioral details. - Unlike the :meth:`.CacheRegion.cache_on_arguments` - method, :meth:`.CacheRegion.cache_multi_on_arguments` works only - with a single function signature, one which takes a simple list of keys as - arguments. + Unlike the :meth:`.CacheRegion.cache_on_arguments` method, + :meth:`.CacheRegion.cache_multi_on_arguments` works only with + a single function signature, one which takes a simple list of + keys as arguments. Like :meth:`.CacheRegion.cache_on_arguments`, the decorated function is also provided with a ``set()`` method, which here accepts a @@ -1119,9 +1398,10 @@ class CacheRegion(object): expiration time. May be passed as an integer or a callable. - :param should_cache_fn: passed to :meth:`.CacheRegion.get_or_create_multi`. - This function is given a value as returned by the creator, and only - if it returns True will that value be placed in the cache. + :param should_cache_fn: passed to + :meth:`.CacheRegion.get_or_create_multi`. This function is given a + value as returned by the creator, and only if it returns True will + that value be placed in the cache. :param asdict: if ``True``, the decorated function should return its result as a dictionary of keys->values, and the final result @@ -1142,6 +1422,12 @@ class CacheRegion(object): .. versionadded:: 0.5.0 + :param function_multi_key_generator: a function that will produce a + list of keys. This function will supersede the one configured on the + :class:`.CacheRegion` itself. + + .. versionadded:: 0.5.5 + .. seealso:: :meth:`.CacheRegion.cache_on_arguments` @@ -1150,43 +1436,53 @@ class CacheRegion(object): """ expiration_time_is_callable = compat.callable(expiration_time) - def decorator(fn): - key_generator = self.function_multi_key_generator(namespace, fn, - to_str=to_str) - @wraps(fn) - def decorate(*arg, **kw): - cache_keys = arg - keys = key_generator(*arg, **kw) - key_lookup = dict(zip(keys, cache_keys)) - @wraps(fn) - def creator(*keys_to_create): - return fn(*[key_lookup[k] for k in keys_to_create]) - timeout = expiration_time() if expiration_time_is_callable \ - else expiration_time + if function_multi_key_generator is None: + function_multi_key_generator = self.function_multi_key_generator - if asdict: - def dict_create(*keys): - d_values = creator(*keys) - return [d_values.get(key_lookup[k], NO_VALUE) for k in keys] + def get_or_create_for_user_func(key_generator, user_func, *arg, **kw): + cache_keys = arg + keys = key_generator(*arg, **kw) + key_lookup = dict(zip(keys, cache_keys)) - def wrap_cache_fn(value): - if value is NO_VALUE: - return False - elif not should_cache_fn: - return True - else: - return should_cache_fn(value) + @wraps(user_func) + def creator(*keys_to_create): + return user_func(*[key_lookup[k] for k in keys_to_create]) - result = self.get_or_create_multi(keys, dict_create, timeout, - wrap_cache_fn) - result = dict((k, v) for k, v in zip(cache_keys, result) - if v is not NO_VALUE) - else: - result = self.get_or_create_multi(keys, creator, timeout, - should_cache_fn) + timeout = expiration_time() if expiration_time_is_callable \ + else expiration_time - return result + if asdict: + def dict_create(*keys): + d_values = creator(*keys) + return [ + d_values.get(key_lookup[k], NO_VALUE) + for k in keys] + + def wrap_cache_fn(value): + if value is NO_VALUE: + return False + elif not should_cache_fn: + return True + else: + return should_cache_fn(value) + + result = self.get_or_create_multi( + keys, dict_create, timeout, wrap_cache_fn) + result = dict( + (k, v) for k, v in zip(cache_keys, result) + if v is not NO_VALUE) + else: + result = self.get_or_create_multi( + keys, creator, timeout, + should_cache_fn) + + return result + + def cache_decorator(user_func): + key_generator = function_multi_key_generator( + namespace, user_func, + to_str=to_str) def invalidate(*arg): keys = key_generator(*arg) @@ -1196,10 +1492,10 @@ class CacheRegion(object): keys = list(mapping) gen_keys = key_generator(*keys) self.set_multi(dict( - (gen_key, mapping[key]) - for gen_key, key - in zip(gen_keys, keys)) - ) + (gen_key, mapping[key]) + for gen_key, key + in zip(gen_keys, keys)) + ) def get(*arg): keys = key_generator(*arg) @@ -1207,25 +1503,29 @@ class CacheRegion(object): def refresh(*arg): keys = key_generator(*arg) - values = fn(*arg) + values = user_func(*arg) if asdict: self.set_multi( - dict(zip(keys, [values[a] for a in arg])) - ) + dict(zip(keys, [values[a] for a in arg])) + ) return values else: self.set_multi( - dict(zip(keys, values)) - ) + dict(zip(keys, values)) + ) return values - decorate.set = set_ - decorate.invalidate = invalidate - decorate.refresh = refresh - decorate.get = get + user_func.set = set_ + user_func.invalidate = invalidate + user_func.refresh = refresh + user_func.get = get + + # Use `decorate` to preserve the signature of :param:`user_func`. + + return decorate(user_func, partial(get_or_create_for_user_func, key_generator)) + + return cache_decorator - return decorate - return decorator diff --git a/libs/dogpile/cache/util.py b/libs/dogpile/cache/util.py index 0f732a6c..16bcd1c9 100644 --- a/libs/dogpile/cache/util.py +++ b/libs/dogpile/cache/util.py @@ -1,57 +1,6 @@ from hashlib import sha1 -import inspect -import re -import collections -from . import compat - - -def coerce_string_conf(d): - result = {} - for k, v in d.items(): - if not isinstance(v, compat.string_types): - result[k] = v - continue - - v = v.strip() - if re.match(r'^[-+]?\d+$', v): - result[k] = int(v) - elif re.match(r'^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?$', v): - result[k] = float(v) - elif v.lower() in ('false', 'true'): - result[k] = v.lower() == 'true' - elif v == 'None': - result[k] = None - else: - result[k] = v - return result - -class PluginLoader(object): - def __init__(self, group): - self.group = group - self.impls = {} - - def load(self, name): - if name in self.impls: - return self.impls[name]() - else: # pragma NO COVERAGE - import pkg_resources - for impl in pkg_resources.iter_entry_points( - self.group, - name): - self.impls[name] = impl.load - return impl.load() - else: - raise Exception( - "Can't load plugin %s %s" % - (self.group, name)) - - def register(self, name, modulepath, objname): - def load(): - mod = __import__(modulepath) - for token in modulepath.split(".")[1:]: - mod = getattr(mod, token) - return getattr(mod, objname) - self.impls[name] = load +from ..util import compat +from ..util import langhelpers def function_key_generator(namespace, fn, to_str=compat.string_type): @@ -62,8 +11,14 @@ def function_key_generator(namespace, fn, to_str=compat.string_type): This is used by :meth:`.CacheRegion.cache_on_arguments` to generate a cache key from a decorated function. - It can be replaced using the ``function_key_generator`` - argument passed to :func:`.make_region`. + An alternate function may be used by specifying + the :paramref:`.CacheRegion.function_key_generator` argument + for :class:`.CacheRegion`. + + .. seealso:: + + :func:`.kwarg_function_key_generator` - similar function that also + takes keyword arguments into account """ @@ -72,19 +27,21 @@ def function_key_generator(namespace, fn, to_str=compat.string_type): else: namespace = '%s:%s|%s' % (fn.__module__, fn.__name__, namespace) - args = inspect.getargspec(fn) + args = compat.inspect_getargspec(fn) has_self = args[0] and args[0][0] in ('self', 'cls') + def generate_key(*args, **kw): if kw: raise ValueError( - "dogpile.cache's default key creation " - "function does not accept keyword arguments.") + "dogpile.cache's default key creation " + "function does not accept keyword arguments.") if has_self: args = args[1:] return namespace + "|" + " ".join(map(to_str, args)) return generate_key + def function_multi_key_generator(namespace, fn, to_str=compat.string_type): if namespace is None: @@ -92,23 +49,80 @@ def function_multi_key_generator(namespace, fn, to_str=compat.string_type): else: namespace = '%s:%s|%s' % (fn.__module__, fn.__name__, namespace) - args = inspect.getargspec(fn) + args = compat.inspect_getargspec(fn) has_self = args[0] and args[0][0] in ('self', 'cls') + def generate_keys(*args, **kw): if kw: raise ValueError( - "dogpile.cache's default key creation " - "function does not accept keyword arguments.") + "dogpile.cache's default key creation " + "function does not accept keyword arguments.") if has_self: args = args[1:] return [namespace + "|" + key for key in map(to_str, args)] return generate_keys + +def kwarg_function_key_generator(namespace, fn, to_str=compat.string_type): + """Return a function that generates a string + key, based on a given function as well as + arguments to the returned function itself. + + For kwargs passed in, we will build a dict of + all argname (key) argvalue (values) including + default args from the argspec and then + alphabetize the list before generating the + key. + + .. versionadded:: 0.6.2 + + .. seealso:: + + :func:`.function_key_generator` - default key generation function + + """ + + if namespace is None: + namespace = '%s:%s' % (fn.__module__, fn.__name__) + else: + namespace = '%s:%s|%s' % (fn.__module__, fn.__name__, namespace) + + argspec = compat.inspect_getargspec(fn) + default_list = list(argspec.defaults or []) + # Reverse the list, as we want to compare the argspec by negative index, + # meaning default_list[0] should be args[-1], which works well with + # enumerate() + default_list.reverse() + # use idx*-1 to create the correct right-lookup index. + args_with_defaults = dict((argspec.args[(idx*-1)], default) + for idx, default in enumerate(default_list, 1)) + if argspec.args and argspec.args[0] in ('self', 'cls'): + arg_index_start = 1 + else: + arg_index_start = 0 + + def generate_key(*args, **kwargs): + as_kwargs = dict( + [(argspec.args[idx], arg) + for idx, arg in enumerate(args[arg_index_start:], + arg_index_start)]) + as_kwargs.update(kwargs) + for arg, val in args_with_defaults.items(): + if arg not in as_kwargs: + as_kwargs[arg] = val + + argument_values = [as_kwargs[key] + for key in sorted(as_kwargs.keys())] + return namespace + '|' + " ".join(map(to_str, argument_values)) + return generate_key + + def sha1_mangle_key(key): """a SHA1 key mangler.""" return sha1(key).hexdigest() + def length_conditional_mangler(length, mangler): """a key mangler that mangles if the length of the key is past a certain threshold. @@ -121,69 +135,11 @@ def length_conditional_mangler(length, mangler): return key return mangle -class memoized_property(object): - """A read-only @property that is only evaluated once.""" - def __init__(self, fget, doc=None): - self.fget = fget - self.__doc__ = doc or fget.__doc__ - self.__name__ = fget.__name__ +# in the 0.6 release these functions were moved to the dogpile.util namespace. +# They are linked here to maintain compatibility with older versions. - def __get__(self, obj, cls): - if obj is None: - return self - obj.__dict__[self.__name__] = result = self.fget(obj) - return result - -def to_list(x, default=None): - """Coerce to a list.""" - if x is None: - return default - if not isinstance(x, (list, tuple)): - return [x] - else: - return x - - -class KeyReentrantMutex(object): - - def __init__(self, key, mutex, keys): - self.key = key - self.mutex = mutex - self.keys = keys - - @classmethod - def factory(cls, mutex): - # this collection holds zero or one - # thread idents as the key; a set of - # keynames held as the value. - keystore = collections.defaultdict(set) - def fac(key): - return KeyReentrantMutex(key, mutex, keystore) - return fac - - def acquire(self, wait=True): - current_thread = compat.threading.current_thread().ident - keys = self.keys.get(current_thread) - if keys is not None and \ - self.key not in keys: - # current lockholder, new key. add it in - keys.add(self.key) - return True - elif self.mutex.acquire(wait=wait): - # after acquire, create new set and add our key - self.keys[current_thread].add(self.key) - return True - else: - return False - - def release(self): - current_thread = compat.threading.current_thread().ident - keys = self.keys.get(current_thread) - assert keys is not None, "this thread didn't do the acquire" - assert self.key in keys, "No acquire held for key '%s'" % self.key - keys.remove(self.key) - if not keys: - # when list of keys empty, remove - # the thread ident and unlock. - del self.keys[current_thread] - self.mutex.release() +coerce_string_conf = langhelpers.coerce_string_conf +KeyReentrantMutex = langhelpers.KeyReentrantMutex +memoized_property = langhelpers.memoized_property +PluginLoader = langhelpers.PluginLoader +to_list = langhelpers.to_list diff --git a/libs/dogpile/core.py b/libs/dogpile/core.py new file mode 100644 index 00000000..2bcfaf81 --- /dev/null +++ b/libs/dogpile/core.py @@ -0,0 +1,17 @@ +"""Compatibility namespace for those using dogpile.core. + +As of dogpile.cache 0.6.0, dogpile.core as a separate package +is no longer used by dogpile.cache. + +Note that this namespace will not take effect if an actual +dogpile.core installation is present. + +""" + +from .util import nameregistry # noqa +from .util import readwrite_lock # noqa +from .util.readwrite_lock import ReadWriteMutex # noqa +from .util.nameregistry import NameRegistry # noqa +from .lock import Lock # noqa +from .lock import NeedRegenerationException # noqa +from . import __version__ # noqa diff --git a/libs/dogpile/core/__init__.py b/libs/dogpile/core/__init__.py deleted file mode 100644 index fb9d756d..00000000 --- a/libs/dogpile/core/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -from .dogpile import NeedRegenerationException, Lock -from .nameregistry import NameRegistry -from .readwrite_lock import ReadWriteMutex -from .legacy import Dogpile, SyncReaderDogpile - -__all__ = [ - 'Dogpile', 'SyncReaderDogpile', 'NeedRegenerationException', - 'NameRegistry', 'ReadWriteMutex', 'Lock'] - -__version__ = '0.4.1' - diff --git a/libs/dogpile/core/legacy.py b/libs/dogpile/core/legacy.py deleted file mode 100644 index dad4e160..00000000 --- a/libs/dogpile/core/legacy.py +++ /dev/null @@ -1,154 +0,0 @@ -from __future__ import with_statement - -from .util import threading -from .readwrite_lock import ReadWriteMutex -from .dogpile import Lock -import time -import contextlib - -class Dogpile(object): - """Dogpile lock class. - - .. deprecated:: 0.4.0 - The :class:`.Lock` object specifies the full - API of the :class:`.Dogpile` object in a single way, - rather than providing multiple modes of usage which - don't necessarily work in the majority of cases. - :class:`.Dogpile` is now a wrapper around the :class:`.Lock` object - which provides dogpile.core's original usage pattern. - This usage pattern began as something simple, but was - not of general use in real-world caching environments without - several extra complicating factors; the :class:`.Lock` - object presents the "real-world" API more succinctly, - and also fixes a cross-process concurrency issue. - - :param expiretime: Expiration time in seconds. Set to - ``None`` for never expires. - :param init: if True, set the 'createdtime' to the - current time. - :param lock: a mutex object that provides - ``acquire()`` and ``release()`` methods. - - """ - def __init__(self, expiretime, init=False, lock=None): - """Construct a new :class:`.Dogpile`. - - """ - if lock: - self.dogpilelock = lock - else: - self.dogpilelock = threading.Lock() - - self.expiretime = expiretime - if init: - self.createdtime = time.time() - - createdtime = -1 - """The last known 'creation time' of the value, - stored as an epoch (i.e. from ``time.time()``). - - If the value here is -1, it is assumed the value - should recreate immediately. - - """ - - def acquire(self, creator, - value_fn=None, - value_and_created_fn=None): - """Acquire the lock, returning a context manager. - - :param creator: Creation function, used if this thread - is chosen to create a new value. - - :param value_fn: Optional function that returns - the value from some datasource. Will be returned - if regeneration is not needed. - - :param value_and_created_fn: Like value_fn, but returns a tuple - of (value, createdtime). The returned createdtime - will replace the "createdtime" value on this dogpile - lock. This option removes the need for the dogpile lock - itself to remain persistent across usages; another - dogpile can come along later and pick up where the - previous one left off. - - """ - - if value_and_created_fn is None: - if value_fn is None: - def value_and_created_fn(): - return None, self.createdtime - else: - def value_and_created_fn(): - return value_fn(), self.createdtime - - def creator_wrapper(): - value = creator() - self.createdtime = time.time() - return value, self.createdtime - else: - def creator_wrapper(): - value = creator() - self.createdtime = time.time() - return value - - return Lock( - self.dogpilelock, - creator_wrapper, - value_and_created_fn, - self.expiretime - ) - - @property - def is_expired(self): - """Return true if the expiration time is reached, or no - value is available.""" - - return not self.has_value or \ - ( - self.expiretime is not None and - time.time() - self.createdtime > self.expiretime - ) - - @property - def has_value(self): - """Return true if the creation function has proceeded - at least once.""" - return self.createdtime > 0 - - -class SyncReaderDogpile(Dogpile): - """Provide a read-write lock function on top of the :class:`.Dogpile` - class. - - .. deprecated:: 0.4.0 - The :class:`.ReadWriteMutex` object can be used directly. - - """ - def __init__(self, *args, **kw): - super(SyncReaderDogpile, self).__init__(*args, **kw) - self.readwritelock = ReadWriteMutex() - - @contextlib.contextmanager - def acquire_write_lock(self): - """Return the "write" lock context manager. - - This will provide a section that is mutexed against - all readers/writers for the dogpile-maintained value. - - """ - - self.readwritelock.acquire_write_lock() - try: - yield - finally: - self.readwritelock.release_write_lock() - - @contextlib.contextmanager - def acquire(self, *arg, **kw): - with super(SyncReaderDogpile, self).acquire(*arg, **kw) as value: - self.readwritelock.acquire_read_lock() - try: - yield value - finally: - self.readwritelock.release_read_lock() diff --git a/libs/dogpile/core/util.py b/libs/dogpile/core/util.py deleted file mode 100644 index f53c6818..00000000 --- a/libs/dogpile/core/util.py +++ /dev/null @@ -1,8 +0,0 @@ -import sys -py3k = sys.version_info >= (3, 0) - -try: - import threading -except ImportError: - import dummy_threading as threading - diff --git a/libs/dogpile/core/dogpile.py b/libs/dogpile/lock.py similarity index 54% rename from libs/dogpile/core/dogpile.py rename to libs/dogpile/lock.py index 2e3ca0e9..2ac22dcf 100644 --- a/libs/dogpile/core/dogpile.py +++ b/libs/dogpile/lock.py @@ -3,6 +3,7 @@ import logging log = logging.getLogger(__name__) + class NeedRegenerationException(Exception): """An exception that when raised in the 'with' block, forces the 'has_value' flag to False and incurs a @@ -12,6 +13,7 @@ class NeedRegenerationException(Exception): NOT_REGENERATED = object() + class Lock(object): """Dogpile lock class. @@ -21,11 +23,6 @@ class Lock(object): continue to return the previous version of that value. - .. versionadded:: 0.4.0 - The :class:`.Lock` class was added as a single-use object - representing the dogpile API without dependence on - any shared state between multiple instances. - :param mutex: A mutex object that provides ``acquire()`` and ``release()`` methods. :param creator: Callable which returns a tuple of the form @@ -52,17 +49,16 @@ class Lock(object): this to be used to defer invocation of the creator callable until some later time. - .. versionadded:: 0.4.1 added the async_creator argument. - """ - def __init__(self, - mutex, - creator, - value_and_created_fn, - expiretime, - async_creator=None, - ): + def __init__( + self, + mutex, + creator, + value_and_created_fn, + expiretime, + async_creator=None, + ): self.mutex = mutex self.creator = creator self.value_and_created_fn = value_and_created_fn @@ -73,11 +69,10 @@ class Lock(object): """Return true if the expiration time is reached, or no value is available.""" - return not self._has_value(createdtime) or \ - ( - self.expiretime is not None and - time.time() - createdtime > self.expiretime - ) + return not self._has_value(createdtime) or ( + self.expiretime is not None and + time.time() - createdtime > self.expiretime + ) def _has_value(self, createdtime): """Return true if the creation function has proceeded @@ -95,68 +90,100 @@ class Lock(object): value = NOT_REGENERATED createdtime = -1 - generated = self._enter_create(createdtime) + generated = self._enter_create(value, createdtime) if generated is not NOT_REGENERATED: generated, createdtime = generated return generated elif value is NOT_REGENERATED: + # we called upon the creator, and it said that it + # didn't regenerate. this typically means another + # thread is running the creation function, and that the + # cache should still have a value. However, + # we don't have a value at all, which is unusual since we just + # checked for it, so check again (TODO: is this a real codepath?) try: value, createdtime = value_fn() return value except NeedRegenerationException: - raise Exception("Generation function should " - "have just been called by a concurrent " - "thread.") + raise Exception( + "Generation function should " + "have just been called by a concurrent " + "thread.") else: return value - def _enter_create(self, createdtime): - + def _enter_create(self, value, createdtime): if not self._is_expired(createdtime): return NOT_REGENERATED - async = False + _async = False if self._has_value(createdtime): + has_value = True if not self.mutex.acquire(False): - log.debug("creation function in progress " - "elsewhere, returning") + log.debug( + "creation function in progress " + "elsewhere, returning") return NOT_REGENERATED else: + has_value = False log.debug("no value, waiting for create lock") self.mutex.acquire() try: log.debug("value creation lock %r acquired" % self.mutex) - # see if someone created the value already - try: - value, createdtime = self.value_and_created_fn() - except NeedRegenerationException: - pass - else: - if not self._is_expired(createdtime): - log.debug("value already present") - return value, createdtime - elif self.async_creator: - log.debug("Passing creation lock to async runner") - self.async_creator(self.mutex) - async = True - return value, createdtime + if not has_value: + # we entered without a value, or at least with "creationtime == + # 0". Run the "getter" function again, to see if another + # thread has already generated the value while we waited on the + # mutex, or if the caller is otherwise telling us there is a + # value already which allows us to use async regeneration. (the + # latter is used by the multi-key routine). + try: + value, createdtime = self.value_and_created_fn() + except NeedRegenerationException: + # nope, nobody created the value, we're it. + # we must create it right now + pass + else: + has_value = True + # caller is telling us there is a value and that we can + # use async creation if it is expired. + if not self._is_expired(createdtime): + # it's not expired, return it + log.debug("Concurrent thread created the value") + return value, createdtime - log.debug("Calling creation function") - created = self.creator() - return created + # otherwise it's expired, call creator again + + if has_value and self.async_creator: + # we have a value we can return, safe to use async_creator + log.debug("Passing creation lock to async runner") + + # so...run it! + self.async_creator(self.mutex) + _async = True + + # and return the expired value for now + return value, createdtime + + # it's expired, and it's our turn to create it synchronously, *or*, + # there's no value at all, and we have to create it synchronously + log.debug( + "Calling creation function for %s value", + "not-yet-present" if not has_value else + "previously expired" + ) + return self.creator() finally: - if not async: + if not _async: self.mutex.release() log.debug("Released creation lock") - def __enter__(self): return self._enter() def __exit__(self, type, value, traceback): pass - diff --git a/libs/dogpile/util/__init__.py b/libs/dogpile/util/__init__.py new file mode 100644 index 00000000..91b07520 --- /dev/null +++ b/libs/dogpile/util/__init__.py @@ -0,0 +1,4 @@ +from .nameregistry import NameRegistry # noqa +from .readwrite_lock import ReadWriteMutex # noqa +from .langhelpers import PluginLoader, memoized_property, \ + coerce_string_conf, to_list, KeyReentrantMutex # noqa diff --git a/libs/dogpile/cache/compat.py b/libs/dogpile/util/compat.py similarity index 50% rename from libs/dogpile/cache/compat.py rename to libs/dogpile/util/compat.py index eb42d5f3..198c7627 100644 --- a/libs/dogpile/cache/compat.py +++ b/libs/dogpile/util/compat.py @@ -1,6 +1,5 @@ import sys - py2k = sys.version_info < (3, 0) py3k = sys.version_info >= (3, 0) py32 = sys.version_info >= (3, 2) @@ -11,10 +10,10 @@ win32 = sys.platform.startswith('win') try: import threading except ImportError: - import dummy_threading as threading + import dummy_threading as threading # noqa -if py3k: # pragma: no cover +if py3k: # pragma: no cover string_types = str, text_type = str string_type = str @@ -45,24 +44,44 @@ else: def ue(s): return unicode(s, "unicode_escape") - import ConfigParser as configparser - import StringIO as io + import ConfigParser as configparser # noqa + import StringIO as io # noqa + + callable = callable # noqa + import thread # noqa - callable = callable - import thread +if py3k: + import collections + ArgSpec = collections.namedtuple( + "ArgSpec", + ["args", "varargs", "keywords", "defaults"]) + from inspect import getfullargspec as inspect_getfullargspec + + def inspect_getargspec(func): + return ArgSpec( + *inspect_getfullargspec(func)[0:4] + ) +else: + from inspect import getargspec as inspect_getargspec # noqa if py3k or jython: import pickle else: - import cPickle as pickle + import cPickle as pickle # noqa + +if py3k: + def read_config_file(config, fileobj): + return config.read_file(fileobj) +else: + def read_config_file(config, fileobj): + return config.readfp(fileobj) def timedelta_total_seconds(td): if py27: return td.total_seconds() else: - return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 1e6) / 1e6 - - + return (td.microseconds + ( + td.seconds + td.days * 24 * 3600) * 1e6) / 1e6 diff --git a/libs/dogpile/util/langhelpers.py b/libs/dogpile/util/langhelpers.py new file mode 100644 index 00000000..4ff8e3e3 --- /dev/null +++ b/libs/dogpile/util/langhelpers.py @@ -0,0 +1,123 @@ +import re +import collections +from . import compat + + +def coerce_string_conf(d): + result = {} + for k, v in d.items(): + if not isinstance(v, compat.string_types): + result[k] = v + continue + + v = v.strip() + if re.match(r'^[-+]?\d+$', v): + result[k] = int(v) + elif re.match(r'^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?$', v): + result[k] = float(v) + elif v.lower() in ('false', 'true'): + result[k] = v.lower() == 'true' + elif v == 'None': + result[k] = None + else: + result[k] = v + return result + + +class PluginLoader(object): + def __init__(self, group): + self.group = group + self.impls = {} + + def load(self, name): + if name in self.impls: + return self.impls[name]() + else: # pragma NO COVERAGE + import pkg_resources + for impl in pkg_resources.iter_entry_points( + self.group, name): + self.impls[name] = impl.load + return impl.load() + else: + raise self.NotFound( + "Can't load plugin %s %s" % (self.group, name) + ) + + def register(self, name, modulepath, objname): + def load(): + mod = __import__(modulepath, fromlist=[objname]) + return getattr(mod, objname) + self.impls[name] = load + + class NotFound(Exception): + """The specified plugin could not be found.""" + + +class memoized_property(object): + """A read-only @property that is only evaluated once.""" + def __init__(self, fget, doc=None): + self.fget = fget + self.__doc__ = doc or fget.__doc__ + self.__name__ = fget.__name__ + + def __get__(self, obj, cls): + if obj is None: + return self + obj.__dict__[self.__name__] = result = self.fget(obj) + return result + + +def to_list(x, default=None): + """Coerce to a list.""" + if x is None: + return default + if not isinstance(x, (list, tuple)): + return [x] + else: + return x + + +class KeyReentrantMutex(object): + + def __init__(self, key, mutex, keys): + self.key = key + self.mutex = mutex + self.keys = keys + + @classmethod + def factory(cls, mutex): + # this collection holds zero or one + # thread idents as the key; a set of + # keynames held as the value. + keystore = collections.defaultdict(set) + + def fac(key): + return KeyReentrantMutex(key, mutex, keystore) + return fac + + def acquire(self, wait=True): + current_thread = compat.threading.current_thread().ident + keys = self.keys.get(current_thread) + if keys is not None and \ + self.key not in keys: + # current lockholder, new key. add it in + keys.add(self.key) + return True + elif self.mutex.acquire(wait=wait): + # after acquire, create new set and add our key + self.keys[current_thread].add(self.key) + return True + else: + return False + + def release(self): + current_thread = compat.threading.current_thread().ident + keys = self.keys.get(current_thread) + assert keys is not None, "this thread didn't do the acquire" + assert self.key in keys, "No acquire held for key '%s'" % self.key + keys.remove(self.key) + if not keys: + # when list of keys empty, remove + # the thread ident and unlock. + del self.keys[current_thread] + self.mutex.release() diff --git a/libs/dogpile/core/nameregistry.py b/libs/dogpile/util/nameregistry.py similarity index 93% rename from libs/dogpile/core/nameregistry.py rename to libs/dogpile/util/nameregistry.py index a73f450c..7087f7cd 100644 --- a/libs/dogpile/core/nameregistry.py +++ b/libs/dogpile/util/nameregistry.py @@ -1,6 +1,7 @@ -from .util import threading +from .compat import threading import weakref + class NameRegistry(object): """Generates and return an object, keeping it as a singleton for a certain identifier for as long as its @@ -49,7 +50,7 @@ class NameRegistry(object): self.creator = creator def get(self, identifier, *args, **kw): - """Get and possibly create the value. + r"""Get and possibly create the value. :param identifier: Hash key for the value. If the creation function is called, this identifier @@ -74,10 +75,12 @@ class NameRegistry(object): if identifier in self._values: return self._values[identifier] else: - self._values[identifier] = value = self.creator(identifier, *args, **kw) + self._values[identifier] = value = self.creator( + identifier, *args, **kw) return value except KeyError: - self._values[identifier] = value = self.creator(identifier, *args, **kw) + self._values[identifier] = value = self.creator( + identifier, *args, **kw) return value finally: self._mutex.release() diff --git a/libs/dogpile/core/readwrite_lock.py b/libs/dogpile/util/readwrite_lock.py similarity index 89% rename from libs/dogpile/core/readwrite_lock.py rename to libs/dogpile/util/readwrite_lock.py index 1ea25e47..9b953edb 100644 --- a/libs/dogpile/core/readwrite_lock.py +++ b/libs/dogpile/util/readwrite_lock.py @@ -1,27 +1,29 @@ -from .util import threading +from .compat import threading import logging log = logging.getLogger(__name__) + class LockError(Exception): pass + class ReadWriteMutex(object): """A mutex which allows multiple readers, single writer. - + :class:`.ReadWriteMutex` uses a Python ``threading.Condition`` to provide this functionality across threads within a process. - + The Beaker package also contained a file-lock based version of this concept, so that readers/writers could be synchronized - across processes with a common filesystem. A future Dogpile + across processes with a common filesystem. A future Dogpile release may include this additional class at some point. - + """ def __init__(self): # counts how many asynchronous methods are executing - self.async = 0 + self.async_ = 0 # pointer to thread that is the current sync operation self.current_sync_operation = None @@ -29,7 +31,7 @@ class ReadWriteMutex(object): # condition object to lock on self.condition = threading.Condition(threading.Lock()) - def acquire_read_lock(self, wait = True): + def acquire_read_lock(self, wait=True): """Acquire the 'read' lock.""" self.condition.acquire() try: @@ -43,35 +45,35 @@ class ReadWriteMutex(object): if self.current_sync_operation is not None: return False - self.async += 1 + self.async_ += 1 log.debug("%s acquired read lock", self) finally: self.condition.release() - if not wait: + if not wait: return True def release_read_lock(self): """Release the 'read' lock.""" self.condition.acquire() try: - self.async -= 1 + self.async_ -= 1 - # check if we are the last asynchronous reader thread + # check if we are the last asynchronous reader thread # out the door. - if self.async == 0: + if self.async_ == 0: # yes. so if a sync operation is waiting, notifyAll to wake # it up if self.current_sync_operation is not None: self.condition.notifyAll() - elif self.async < 0: + elif self.async_ < 0: raise LockError("Synchronizer error - too many " "release_read_locks called") log.debug("%s released read lock", self) finally: self.condition.release() - def acquire_write_lock(self, wait = True): + def acquire_write_lock(self, wait=True): """Acquire the 'write' lock.""" self.condition.acquire() try: @@ -88,13 +90,13 @@ class ReadWriteMutex(object): if self.current_sync_operation is not None: return False - # establish ourselves as the current sync + # establish ourselves as the current sync # this indicates to other read/write operations # that they should wait until this is None again self.current_sync_operation = threading.currentThread() # now wait again for asyncs to finish - if self.async > 0: + if self.async_ > 0: if wait: # wait self.condition.wait() @@ -106,7 +108,7 @@ class ReadWriteMutex(object): finally: self.condition.release() - if not wait: + if not wait: return True def release_write_lock(self): @@ -117,7 +119,7 @@ class ReadWriteMutex(object): raise LockError("Synchronizer error - current thread doesn't " "have the write lock") - # reset the current sync operation so + # reset the current sync operation so # another can get it self.current_sync_operation = None diff --git a/libs/enzyme/__init__.py b/libs/enzyme/__init__.py index 9a171b52..3bd89f33 100644 --- a/libs/enzyme/__init__.py +++ b/libs/enzyme/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- __title__ = 'enzyme' -__version__ = '0.4.2' +__version__ = '0.4.1' __author__ = 'Antoine Bertin' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2013 Antoine Bertin' diff --git a/libs/enzyme/tests/test_mkv.py b/libs/enzyme/tests/test_mkv.py index 2403661e..1bfa0456 100644 --- a/libs/enzyme/tests/test_mkv.py +++ b/libs/enzyme/tests/test_mkv.py @@ -21,8 +21,8 @@ def setUpModule(): class MKVTestCase(unittest.TestCase): def test_test1(self): - with io.open(os.path.join(TEST_DIR, 'test1.mkv'), 'rb') as stream: - mkv = MKV(stream) + stream = io.open(os.path.join(TEST_DIR, 'test1.mkv'), 'rb') + mkv = MKV(stream) # info self.assertTrue(mkv.info.title is None) self.assertTrue(mkv.info.duration == timedelta(minutes=1, seconds=27, milliseconds=336)) @@ -90,8 +90,8 @@ class MKVTestCase(unittest.TestCase): self.assertTrue(mkv.tags[0].simpletags[2].binary is None) def test_test2(self): - with io.open(os.path.join(TEST_DIR, 'test2.mkv'), 'rb') as stream: - mkv = MKV(stream) + stream = io.open(os.path.join(TEST_DIR, 'test2.mkv'), 'rb') + mkv = MKV(stream) # info self.assertTrue(mkv.info.title is None) self.assertTrue(mkv.info.duration == timedelta(seconds=47, milliseconds=509)) @@ -159,8 +159,8 @@ class MKVTestCase(unittest.TestCase): self.assertTrue(mkv.tags[0].simpletags[2].binary is None) def test_test3(self): - with io.open(os.path.join(TEST_DIR, 'test3.mkv'), 'rb') as stream: - mkv = MKV(stream) + stream = io.open(os.path.join(TEST_DIR, 'test3.mkv'), 'rb') + mkv = MKV(stream) # info self.assertTrue(mkv.info.title is None) self.assertTrue(mkv.info.duration == timedelta(seconds=49, milliseconds=64)) @@ -228,8 +228,8 @@ class MKVTestCase(unittest.TestCase): self.assertTrue(mkv.tags[0].simpletags[2].binary is None) def test_test5(self): - with io.open(os.path.join(TEST_DIR, 'test5.mkv'), 'rb') as stream: - mkv = MKV(stream) + stream = io.open(os.path.join(TEST_DIR, 'test5.mkv'), 'rb') + mkv = MKV(stream) # info self.assertTrue(mkv.info.title is None) self.assertTrue(mkv.info.duration == timedelta(seconds=46, milliseconds=665)) @@ -391,8 +391,8 @@ class MKVTestCase(unittest.TestCase): self.assertTrue(mkv.tags[0].simpletags[2].binary is None) def test_test6(self): - with io.open(os.path.join(TEST_DIR, 'test6.mkv'), 'rb') as stream: - mkv = MKV(stream) + stream = io.open(os.path.join(TEST_DIR, 'test6.mkv'), 'rb') + mkv = MKV(stream) # info self.assertTrue(mkv.info.title is None) self.assertTrue(mkv.info.duration == timedelta(seconds=87, milliseconds=336)) @@ -460,8 +460,8 @@ class MKVTestCase(unittest.TestCase): self.assertTrue(mkv.tags[0].simpletags[2].binary is None) def test_test7(self): - with io.open(os.path.join(TEST_DIR, 'test7.mkv'), 'rb') as stream: - mkv = MKV(stream) + stream = io.open(os.path.join(TEST_DIR, 'test7.mkv'), 'rb') + mkv = MKV(stream) # info self.assertTrue(mkv.info.title is None) self.assertTrue(mkv.info.duration == timedelta(seconds=37, milliseconds=43)) @@ -529,8 +529,8 @@ class MKVTestCase(unittest.TestCase): self.assertTrue(mkv.tags[0].simpletags[2].binary is None) def test_test8(self): - with io.open(os.path.join(TEST_DIR, 'test8.mkv'), 'rb') as stream: - mkv = MKV(stream) + stream = io.open(os.path.join(TEST_DIR, 'test8.mkv'), 'rb') + mkv = MKV(stream) # info self.assertTrue(mkv.info.title is None) self.assertTrue(mkv.info.duration == timedelta(seconds=47, milliseconds=341)) diff --git a/libs/pbr/__init__.py b/libs/pbr/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/libs/pbr/builddoc.py b/libs/pbr/builddoc.py new file mode 100644 index 00000000..f5c66ce0 --- /dev/null +++ b/libs/pbr/builddoc.py @@ -0,0 +1,292 @@ +# Copyright 2011 OpenStack Foundation +# Copyright 2012-2013 Hewlett-Packard Development Company, L.P. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from distutils import log +import fnmatch +import os +import sys + +try: + import cStringIO +except ImportError: + import io as cStringIO + +try: + import sphinx + # NOTE(dhellmann): Newer versions of Sphinx have moved the apidoc + # module into sphinx.ext and the API is slightly different (the + # function expects sys.argv[1:] instead of sys.argv[:]. So, figure + # out where we can import it from and set a flag so we can invoke + # it properly. See this change in sphinx for details: + # https://github.com/sphinx-doc/sphinx/commit/87630c8ae8bff8c0e23187676e6343d8903003a6 + try: + from sphinx.ext import apidoc + apidoc_use_padding = False + except ImportError: + from sphinx import apidoc + apidoc_use_padding = True + from sphinx import application + from sphinx import setup_command +except Exception as e: + # NOTE(dhellmann): During the installation of docutils, setuptools + # tries to import pbr code to find the egg_info.writer hooks. That + # imports this module, which imports sphinx, which imports + # docutils, which is being installed. Because docutils uses 2to3 + # to convert its code during installation under python 3, the + # import fails, but it fails with an error other than ImportError + # (today it's a NameError on StandardError, an exception base + # class). Convert the exception type here so it can be caught in + # packaging.py where we try to determine if we can import and use + # sphinx by importing this module. See bug #1403510 for details. + raise ImportError(str(e)) +from pbr import git +from pbr import options +from pbr import version + + +_deprecated_options = ['autodoc_tree_index_modules', 'autodoc_index_modules', + 'autodoc_tree_excludes', 'autodoc_exclude_modules'] +_deprecated_envs = ['AUTODOC_TREE_INDEX_MODULES', 'AUTODOC_INDEX_MODULES'] +_rst_template = """%(heading)s +%(underline)s + +.. automodule:: %(module)s + :members: + :undoc-members: + :show-inheritance: +""" + + +def _find_modules(arg, dirname, files): + for filename in files: + if filename.endswith('.py') and filename != '__init__.py': + arg["%s.%s" % (dirname.replace('/', '.'), + filename[:-3])] = True + + +class LocalBuildDoc(setup_command.BuildDoc): + + builders = ['html'] + command_name = 'build_sphinx' + sphinx_initialized = False + + def _get_source_dir(self): + option_dict = self.distribution.get_option_dict('build_sphinx') + pbr_option_dict = self.distribution.get_option_dict('pbr') + _, api_doc_dir = pbr_option_dict.get('api_doc_dir', (None, 'api')) + if 'source_dir' in option_dict: + source_dir = os.path.join(option_dict['source_dir'][1], + api_doc_dir) + else: + source_dir = 'doc/source/' + api_doc_dir + if not os.path.exists(source_dir): + os.makedirs(source_dir) + return source_dir + + def generate_autoindex(self, excluded_modules=None): + log.info("[pbr] Autodocumenting from %s" + % os.path.abspath(os.curdir)) + modules = {} + source_dir = self._get_source_dir() + for pkg in self.distribution.packages: + if '.' not in pkg: + for dirpath, dirnames, files in os.walk(pkg): + _find_modules(modules, dirpath, files) + + def include(module): + return not any(fnmatch.fnmatch(module, pat) + for pat in excluded_modules) + + module_list = sorted(mod for mod in modules.keys() if include(mod)) + autoindex_filename = os.path.join(source_dir, 'autoindex.rst') + with open(autoindex_filename, 'w') as autoindex: + autoindex.write(""".. toctree:: + :maxdepth: 1 + +""") + for module in module_list: + output_filename = os.path.join(source_dir, + "%s.rst" % module) + heading = "The :mod:`%s` Module" % module + underline = "=" * len(heading) + values = dict(module=module, heading=heading, + underline=underline) + + log.info("[pbr] Generating %s" + % output_filename) + with open(output_filename, 'w') as output_file: + output_file.write(_rst_template % values) + autoindex.write(" %s.rst\n" % module) + + def _sphinx_tree(self): + source_dir = self._get_source_dir() + cmd = ['-H', 'Modules', '-o', source_dir, '.'] + if apidoc_use_padding: + cmd.insert(0, 'apidoc') + apidoc.main(cmd + self.autodoc_tree_excludes) + + def _sphinx_run(self): + if not self.verbose: + status_stream = cStringIO.StringIO() + else: + status_stream = sys.stdout + confoverrides = {} + if self.project: + confoverrides['project'] = self.project + if self.version: + confoverrides['version'] = self.version + if self.release: + confoverrides['release'] = self.release + if self.today: + confoverrides['today'] = self.today + if self.sphinx_initialized: + confoverrides['suppress_warnings'] = [ + 'app.add_directive', 'app.add_role', + 'app.add_generic_role', 'app.add_node', + 'image.nonlocal_uri', + ] + app = application.Sphinx( + self.source_dir, self.config_dir, + self.builder_target_dir, self.doctree_dir, + self.builder, confoverrides, status_stream, + freshenv=self.fresh_env, warningiserror=self.warning_is_error) + self.sphinx_initialized = True + + try: + app.build(force_all=self.all_files) + except Exception as err: + from docutils import utils + if isinstance(err, utils.SystemMessage): + sys.stder.write('reST markup error:\n') + sys.stderr.write(err.args[0].encode('ascii', + 'backslashreplace')) + sys.stderr.write('\n') + else: + raise + + if self.link_index: + src = app.config.master_doc + app.builder.out_suffix + dst = app.builder.get_outfilename('index') + os.symlink(src, dst) + + def run(self): + option_dict = self.distribution.get_option_dict('pbr') + + # TODO(stephenfin): Remove this (and the entire file) when 5.0 is + # released + warn_opts = set(option_dict.keys()).intersection(_deprecated_options) + warn_env = list(filter(lambda x: os.getenv(x), _deprecated_envs)) + if warn_opts or warn_env: + msg = ('The autodoc and autodoc_tree features are deprecated in ' + '4.2 and will be removed in a future release. You should ' + 'use the sphinxcontrib-apidoc Sphinx extension instead. ' + 'Refer to the pbr documentation for more information.') + if warn_opts: + msg += ' Deprecated options: %s' % list(warn_opts) + if warn_env: + msg += ' Deprecated environment variables: %s' % warn_env + + log.warn(msg) + + if git._git_is_installed(): + git.write_git_changelog(option_dict=option_dict) + git.generate_authors(option_dict=option_dict) + tree_index = options.get_boolean_option(option_dict, + 'autodoc_tree_index_modules', + 'AUTODOC_TREE_INDEX_MODULES') + auto_index = options.get_boolean_option(option_dict, + 'autodoc_index_modules', + 'AUTODOC_INDEX_MODULES') + if not os.getenv('SPHINX_DEBUG'): + # NOTE(afazekas): These options can be used together, + # but they do a very similar thing in a different way + if tree_index: + self._sphinx_tree() + if auto_index: + self.generate_autoindex( + set(option_dict.get( + "autodoc_exclude_modules", + [None, ""])[1].split())) + + self.finalize_options() + + is_multibuilder_sphinx = version.SemanticVersion.from_pip_string( + sphinx.__version__) >= version.SemanticVersion(1, 6) + + # TODO(stephenfin): Remove support for Sphinx < 1.6 in 4.0 + if not is_multibuilder_sphinx: + log.warn('[pbr] Support for Sphinx < 1.6 will be dropped in ' + 'pbr 4.0. Upgrade to Sphinx 1.6+') + + # TODO(stephenfin): Remove this at the next MAJOR version bump + if self.builders != ['html']: + log.warn("[pbr] Sphinx 1.6 added native support for " + "specifying multiple builders in the " + "'[sphinx_build] builder' configuration option, " + "found in 'setup.cfg'. As a result, the " + "'[sphinx_build] builders' option has been " + "deprecated and will be removed in pbr 4.0. Migrate " + "to the 'builder' configuration option.") + if is_multibuilder_sphinx: + self.builder = self.builders + + if is_multibuilder_sphinx: + # Sphinx >= 1.6 + return setup_command.BuildDoc.run(self) + + # Sphinx < 1.6 + for builder in self.builders: + self.builder = builder + self.finalize_options() + self._sphinx_run() + + def initialize_options(self): + # Not a new style class, super keyword does not work. + setup_command.BuildDoc.initialize_options(self) + + # NOTE(dstanek): exclude setup.py from the autodoc tree index + # builds because all projects will have an issue with it + self.autodoc_tree_excludes = ['setup.py'] + + def finalize_options(self): + from pbr import util + + # Not a new style class, super keyword does not work. + setup_command.BuildDoc.finalize_options(self) + + # Handle builder option from command line - override cfg + option_dict = self.distribution.get_option_dict('build_sphinx') + if 'command line' in option_dict.get('builder', [[]])[0]: + self.builders = option_dict['builder'][1] + # Allow builders to be configurable - as a comma separated list. + if not isinstance(self.builders, list) and self.builders: + self.builders = self.builders.split(',') + + self.project = self.distribution.get_name() + self.version = self.distribution.get_version() + self.release = self.distribution.get_version() + + # NOTE(dstanek): check for autodoc tree exclusion overrides + # in the setup.cfg + opt = 'autodoc_tree_excludes' + option_dict = self.distribution.get_option_dict('pbr') + if opt in option_dict: + self.autodoc_tree_excludes = util.split_multiline( + option_dict[opt][1]) + + # handle Sphinx < 1.5.0 + if not hasattr(self, 'warning_is_error'): + self.warning_is_error = False diff --git a/libs/pbr/cmd/__init__.py b/libs/pbr/cmd/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/libs/pbr/cmd/main.py b/libs/pbr/cmd/main.py new file mode 100644 index 00000000..29cd61d7 --- /dev/null +++ b/libs/pbr/cmd/main.py @@ -0,0 +1,112 @@ +# Copyright 2014 Hewlett-Packard Development Company, L.P. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import argparse +import json +import sys + +import pkg_resources + +import pbr.version + + +def _get_metadata(package_name): + try: + return json.loads( + pkg_resources.get_distribution( + package_name).get_metadata('pbr.json')) + except pkg_resources.DistributionNotFound: + raise Exception('Package {0} not installed'.format(package_name)) + except Exception: + return None + + +def get_sha(args): + sha = _get_info(args.name)['sha'] + if sha: + print(sha) + + +def get_info(args): + print("{name}\t{version}\t{released}\t{sha}".format( + **_get_info(args.name))) + + +def _get_info(name): + metadata = _get_metadata(name) + version = pkg_resources.get_distribution(name).version + if metadata: + if metadata['is_release']: + released = 'released' + else: + released = 'pre-release' + sha = metadata['git_version'] + else: + version_parts = version.split('.') + if version_parts[-1].startswith('g'): + sha = version_parts[-1][1:] + released = 'pre-release' + else: + sha = "" + released = "released" + for part in version_parts: + if not part.isdigit(): + released = "pre-release" + return dict(name=name, version=version, sha=sha, released=released) + + +def freeze(args): + sorted_dists = sorted(pkg_resources.working_set, + key=lambda dist: dist.project_name.lower()) + for dist in sorted_dists: + info = _get_info(dist.project_name) + output = "{name}=={version}".format(**info) + if info['sha']: + output += " # git sha {sha}".format(**info) + print(output) + + +def main(): + parser = argparse.ArgumentParser( + description='pbr: Python Build Reasonableness') + parser.add_argument( + '-v', '--version', action='version', + version=str(pbr.version.VersionInfo('pbr'))) + + subparsers = parser.add_subparsers( + title='commands', description='valid commands', help='additional help') + + cmd_sha = subparsers.add_parser('sha', help='print sha of package') + cmd_sha.set_defaults(func=get_sha) + cmd_sha.add_argument('name', help='package to print sha of') + + cmd_info = subparsers.add_parser( + 'info', help='print version info for package') + cmd_info.set_defaults(func=get_info) + cmd_info.add_argument('name', help='package to print info of') + + cmd_freeze = subparsers.add_parser( + 'freeze', help='print version info for all installed packages') + cmd_freeze.set_defaults(func=freeze) + + args = parser.parse_args() + try: + args.func(args) + except Exception as e: + print(e) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/libs/pbr/core.py b/libs/pbr/core.py new file mode 100644 index 00000000..645a2ef1 --- /dev/null +++ b/libs/pbr/core.py @@ -0,0 +1,145 @@ +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Copyright (C) 2013 Association of Universities for Research in Astronomy +# (AURA) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# 3. The name of AURA and its representatives may not be used to +# endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED +# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. + +import logging +import os +import sys +import warnings + +from distutils import errors + +from pbr import util + + +if sys.version_info[0] == 3: + string_type = str + integer_types = (int,) +else: + string_type = basestring # noqa + integer_types = (int, long) # noqa + + +def pbr(dist, attr, value): + """Implements the actual pbr setup() keyword. + + When used, this should be the only keyword in your setup() aside from + `setup_requires`. + + If given as a string, the value of pbr is assumed to be the relative path + to the setup.cfg file to use. Otherwise, if it evaluates to true, it + simply assumes that pbr should be used, and the default 'setup.cfg' is + used. + + This works by reading the setup.cfg file, parsing out the supported + metadata and command options, and using them to rebuild the + `DistributionMetadata` object and set the newly added command options. + + The reason for doing things this way is that a custom `Distribution` class + will not play nicely with setup_requires; however, this implementation may + not work well with distributions that do use a `Distribution` subclass. + """ + + if not value: + return + if isinstance(value, string_type): + path = os.path.abspath(value) + else: + path = os.path.abspath('setup.cfg') + if not os.path.exists(path): + raise errors.DistutilsFileError( + 'The setup.cfg file %s does not exist.' % path) + + # Converts the setup.cfg file to setup() arguments + try: + attrs = util.cfg_to_args(path, dist.script_args) + except Exception: + e = sys.exc_info()[1] + # NB: This will output to the console if no explicit logging has + # been setup - but thats fine, this is a fatal distutils error, so + # being pretty isn't the #1 goal.. being diagnosable is. + logging.exception('Error parsing') + raise errors.DistutilsSetupError( + 'Error parsing %s: %s: %s' % (path, e.__class__.__name__, e)) + + # There are some metadata fields that are only supported by + # setuptools and not distutils, and hence are not in + # dist.metadata. We are OK to write these in. For gory details + # see + # https://github.com/pypa/setuptools/pull/1343 + _DISTUTILS_UNSUPPORTED_METADATA = ( + 'long_description_content_type', 'project_urls', 'provides_extras' + ) + + # Repeat some of the Distribution initialization code with the newly + # provided attrs + if attrs: + # Skips 'options' and 'licence' support which are rarely used; may + # add back in later if demanded + for key, val in attrs.items(): + if hasattr(dist.metadata, 'set_' + key): + getattr(dist.metadata, 'set_' + key)(val) + elif hasattr(dist.metadata, key): + setattr(dist.metadata, key, val) + elif hasattr(dist, key): + setattr(dist, key, val) + elif key in _DISTUTILS_UNSUPPORTED_METADATA: + setattr(dist.metadata, key, val) + else: + msg = 'Unknown distribution option: %s' % repr(key) + warnings.warn(msg) + + # Re-finalize the underlying Distribution + try: + super(dist.__class__, dist).finalize_options() + except TypeError: + # If dist is not declared as a new-style class (with object as + # a subclass) then super() will not work on it. This is the case + # for Python 2. In that case, fall back to doing this the ugly way + dist.__class__.__bases__[-1].finalize_options(dist) + + # This bit comes out of distribute/setuptools + if isinstance(dist.metadata.version, integer_types + (float,)): + # Some people apparently take "version number" too literally :) + dist.metadata.version = str(dist.metadata.version) diff --git a/libs/pbr/extra_files.py b/libs/pbr/extra_files.py new file mode 100644 index 00000000..a72db0c1 --- /dev/null +++ b/libs/pbr/extra_files.py @@ -0,0 +1,35 @@ +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from distutils import errors +import os + +_extra_files = [] + + +def get_extra_files(): + global _extra_files + return _extra_files + + +def set_extra_files(extra_files): + # Let's do a sanity check + for filename in extra_files: + if not os.path.exists(filename): + raise errors.DistutilsFileError( + '%s from the extra_files option in setup.cfg does not ' + 'exist' % filename) + global _extra_files + _extra_files[:] = extra_files[:] diff --git a/libs/pbr/find_package.py b/libs/pbr/find_package.py new file mode 100644 index 00000000..717e93da --- /dev/null +++ b/libs/pbr/find_package.py @@ -0,0 +1,29 @@ +# Copyright 2013 Hewlett-Packard Development Company, L.P. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os + +import setuptools + + +def smart_find_packages(package_list): + """Run find_packages the way we intend.""" + packages = [] + for pkg in package_list.strip().split("\n"): + pkg_path = pkg.replace('.', os.path.sep) + packages.append(pkg) + packages.extend(['%s.%s' % (pkg, f) + for f in setuptools.find_packages(pkg_path)]) + return "\n".join(set(packages)) diff --git a/libs/pbr/git.py b/libs/pbr/git.py new file mode 100644 index 00000000..6e18adae --- /dev/null +++ b/libs/pbr/git.py @@ -0,0 +1,331 @@ +# Copyright 2011 OpenStack Foundation +# Copyright 2012-2013 Hewlett-Packard Development Company, L.P. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from __future__ import unicode_literals + +import distutils.errors +from distutils import log +import errno +import io +import os +import re +import subprocess +import time + +import pkg_resources + +from pbr import options +from pbr import version + + +def _run_shell_command(cmd, throw_on_error=False, buffer=True, env=None): + if buffer: + out_location = subprocess.PIPE + err_location = subprocess.PIPE + else: + out_location = None + err_location = None + + newenv = os.environ.copy() + if env: + newenv.update(env) + + output = subprocess.Popen(cmd, + stdout=out_location, + stderr=err_location, + env=newenv) + out = output.communicate() + if output.returncode and throw_on_error: + raise distutils.errors.DistutilsError( + "%s returned %d" % (cmd, output.returncode)) + if len(out) == 0 or not out[0] or not out[0].strip(): + return '' + # Since we don't control the history, and forcing users to rebase arbitrary + # history to fix utf8 issues is harsh, decode with replace. + return out[0].strip().decode('utf-8', 'replace') + + +def _run_git_command(cmd, git_dir, **kwargs): + if not isinstance(cmd, (list, tuple)): + cmd = [cmd] + return _run_shell_command( + ['git', '--git-dir=%s' % git_dir] + cmd, **kwargs) + + +def _get_git_directory(): + try: + return _run_shell_command(['git', 'rev-parse', '--git-dir']) + except OSError as e: + if e.errno == errno.ENOENT: + # git not installed. + return '' + raise + + +def _git_is_installed(): + try: + # We cannot use 'which git' as it may not be available + # in some distributions, So just try 'git --version' + # to see if we run into trouble + _run_shell_command(['git', '--version']) + except OSError: + return False + return True + + +def _get_highest_tag(tags): + """Find the highest tag from a list. + + Pass in a list of tag strings and this will return the highest + (latest) as sorted by the pkg_resources version parser. + """ + return max(tags, key=pkg_resources.parse_version) + + +def _find_git_files(dirname='', git_dir=None): + """Behave like a file finder entrypoint plugin. + + We don't actually use the entrypoints system for this because it runs + at absurd times. We only want to do this when we are building an sdist. + """ + file_list = [] + if git_dir is None: + git_dir = _run_git_functions() + if git_dir: + log.info("[pbr] In git context, generating filelist from git") + file_list = _run_git_command(['ls-files', '-z'], git_dir) + # Users can fix utf8 issues locally with a single commit, so we are + # strict here. + file_list = file_list.split(b'\x00'.decode('utf-8')) + return [f for f in file_list if f] + + +def _get_raw_tag_info(git_dir): + describe = _run_git_command(['describe', '--always'], git_dir) + if "-" in describe: + return describe.rsplit("-", 2)[-2] + if "." in describe: + return 0 + return None + + +def get_is_release(git_dir): + return _get_raw_tag_info(git_dir) == 0 + + +def _run_git_functions(): + git_dir = None + if _git_is_installed(): + git_dir = _get_git_directory() + return git_dir or None + + +def get_git_short_sha(git_dir=None): + """Return the short sha for this repo, if it exists.""" + if not git_dir: + git_dir = _run_git_functions() + if git_dir: + return _run_git_command( + ['log', '-n1', '--pretty=format:%h'], git_dir) + return None + + +def _clean_changelog_message(msg): + """Cleans any instances of invalid sphinx wording. + + This escapes/removes any instances of invalid characters + that can be interpreted by sphinx as a warning or error + when translating the Changelog into an HTML file for + documentation building within projects. + + * Escapes '_' which is interpreted as a link + * Escapes '*' which is interpreted as a new line + * Escapes '`' which is interpreted as a literal + """ + + msg = msg.replace('*', '\*') + msg = msg.replace('_', '\_') + msg = msg.replace('`', '\`') + + return msg + + +def _iter_changelog(changelog): + """Convert a oneline log iterator to formatted strings. + + :param changelog: An iterator of one line log entries like + that given by _iter_log_oneline. + :return: An iterator over (release, formatted changelog) tuples. + """ + first_line = True + current_release = None + yield current_release, "CHANGES\n=======\n\n" + for hash, tags, msg in changelog: + if tags: + current_release = _get_highest_tag(tags) + underline = len(current_release) * '-' + if not first_line: + yield current_release, '\n' + yield current_release, ( + "%(tag)s\n%(underline)s\n\n" % + dict(tag=current_release, underline=underline)) + + if not msg.startswith("Merge "): + if msg.endswith("."): + msg = msg[:-1] + msg = _clean_changelog_message(msg) + yield current_release, "* %(msg)s\n" % dict(msg=msg) + first_line = False + + +def _iter_log_oneline(git_dir=None): + """Iterate over --oneline log entries if possible. + + This parses the output into a structured form but does not apply + presentation logic to the output - making it suitable for different + uses. + + :return: An iterator of (hash, tags_set, 1st_line) tuples, or None if + changelog generation is disabled / not available. + """ + if git_dir is None: + git_dir = _get_git_directory() + if not git_dir: + return [] + return _iter_log_inner(git_dir) + + +def _is_valid_version(candidate): + try: + version.SemanticVersion.from_pip_string(candidate) + return True + except ValueError: + return False + + +def _iter_log_inner(git_dir): + """Iterate over --oneline log entries. + + This parses the output intro a structured form but does not apply + presentation logic to the output - making it suitable for different + uses. + + :return: An iterator of (hash, tags_set, 1st_line) tuples. + """ + log.info('[pbr] Generating ChangeLog') + log_cmd = ['log', '--decorate=full', '--format=%h%x00%s%x00%d'] + changelog = _run_git_command(log_cmd, git_dir) + for line in changelog.split('\n'): + line_parts = line.split('\x00') + if len(line_parts) != 3: + continue + sha, msg, refname = line_parts + tags = set() + + # refname can be: + # + # HEAD, tag: refs/tags/1.4.0, refs/remotes/origin/master, \ + # refs/heads/master + # refs/tags/1.3.4 + if "refs/tags/" in refname: + refname = refname.strip()[1:-1] # remove wrapping ()'s + # If we start with "tag: refs/tags/1.2b1, tag: refs/tags/1.2" + # The first split gives us "['', '1.2b1, tag:', '1.2']" + # Which is why we do the second split below on the comma + for tag_string in refname.split("refs/tags/")[1:]: + # git tag does not allow : or " " in tag names, so we split + # on ", " which is the separator between elements + candidate = tag_string.split(", ")[0] + if _is_valid_version(candidate): + tags.add(candidate) + + yield sha, tags, msg + + +def write_git_changelog(git_dir=None, dest_dir=os.path.curdir, + option_dict=None, changelog=None): + """Write a changelog based on the git changelog.""" + start = time.time() + if not option_dict: + option_dict = {} + should_skip = options.get_boolean_option(option_dict, 'skip_changelog', + 'SKIP_WRITE_GIT_CHANGELOG') + if should_skip: + return + if not changelog: + changelog = _iter_log_oneline(git_dir=git_dir) + if changelog: + changelog = _iter_changelog(changelog) + if not changelog: + return + new_changelog = os.path.join(dest_dir, 'ChangeLog') + # If there's already a ChangeLog and it's not writable, just use it + if (os.path.exists(new_changelog) + and not os.access(new_changelog, os.W_OK)): + log.info('[pbr] ChangeLog not written (file already' + ' exists and it is not writeable)') + return + log.info('[pbr] Writing ChangeLog') + with io.open(new_changelog, "w", encoding="utf-8") as changelog_file: + for release, content in changelog: + changelog_file.write(content) + stop = time.time() + log.info('[pbr] ChangeLog complete (%0.1fs)' % (stop - start)) + + +def generate_authors(git_dir=None, dest_dir='.', option_dict=dict()): + """Create AUTHORS file using git commits.""" + should_skip = options.get_boolean_option(option_dict, 'skip_authors', + 'SKIP_GENERATE_AUTHORS') + if should_skip: + return + start = time.time() + old_authors = os.path.join(dest_dir, 'AUTHORS.in') + new_authors = os.path.join(dest_dir, 'AUTHORS') + # If there's already an AUTHORS file and it's not writable, just use it + if (os.path.exists(new_authors) + and not os.access(new_authors, os.W_OK)): + return + log.info('[pbr] Generating AUTHORS') + ignore_emails = '((jenkins|zuul)@review|infra@lists|jenkins@openstack)' + if git_dir is None: + git_dir = _get_git_directory() + if git_dir: + authors = [] + + # don't include jenkins email address in AUTHORS file + git_log_cmd = ['log', '--format=%aN <%aE>'] + authors += _run_git_command(git_log_cmd, git_dir).split('\n') + authors = [a for a in authors if not re.search(ignore_emails, a)] + + # get all co-authors from commit messages + co_authors_out = _run_git_command('log', git_dir) + co_authors = re.findall('Co-authored-by:.+', co_authors_out, + re.MULTILINE) + co_authors = [signed.split(":", 1)[1].strip() + for signed in co_authors if signed] + + authors += co_authors + authors = sorted(set(authors)) + + with open(new_authors, 'wb') as new_authors_fh: + if os.path.exists(old_authors): + with open(old_authors, "rb") as old_authors_fh: + new_authors_fh.write(old_authors_fh.read()) + new_authors_fh.write(('\n'.join(authors) + '\n') + .encode('utf-8')) + stop = time.time() + log.info('[pbr] AUTHORS complete (%0.1fs)' % (stop - start)) diff --git a/libs/pbr/hooks/__init__.py b/libs/pbr/hooks/__init__.py new file mode 100644 index 00000000..f0056c0e --- /dev/null +++ b/libs/pbr/hooks/__init__.py @@ -0,0 +1,28 @@ +# Copyright 2013 Hewlett-Packard Development Company, L.P. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from pbr.hooks import backwards +from pbr.hooks import commands +from pbr.hooks import files +from pbr.hooks import metadata + + +def setup_hook(config): + """Filter config parsed from a setup.cfg to inject our defaults.""" + metadata_config = metadata.MetadataConfig(config) + metadata_config.run() + backwards.BackwardsCompatConfig(config).run() + commands.CommandsConfig(config).run() + files.FilesConfig(config, metadata_config.get_name()).run() diff --git a/libs/pbr/hooks/backwards.py b/libs/pbr/hooks/backwards.py new file mode 100644 index 00000000..01f07ab8 --- /dev/null +++ b/libs/pbr/hooks/backwards.py @@ -0,0 +1,33 @@ +# Copyright 2013 Hewlett-Packard Development Company, L.P. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from pbr.hooks import base +from pbr import packaging + + +class BackwardsCompatConfig(base.BaseConfig): + + section = 'backwards_compat' + + def hook(self): + self.config['include_package_data'] = 'True' + packaging.append_text_list( + self.config, 'dependency_links', + packaging.parse_dependency_links()) + packaging.append_text_list( + self.config, 'tests_require', + packaging.parse_requirements( + packaging.TEST_REQUIREMENTS_FILES, + strip_markers=True)) diff --git a/libs/pbr/hooks/base.py b/libs/pbr/hooks/base.py new file mode 100644 index 00000000..6672a362 --- /dev/null +++ b/libs/pbr/hooks/base.py @@ -0,0 +1,34 @@ +# Copyright 2013 Hewlett-Packard Development Company, L.P. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +class BaseConfig(object): + + section = None + + def __init__(self, config): + self._global_config = config + self.config = self._global_config.get(self.section, dict()) + self.pbr_config = config.get('pbr', dict()) + + def run(self): + self.hook() + self.save() + + def hook(self): + pass + + def save(self): + self._global_config[self.section] = self.config diff --git a/libs/pbr/hooks/commands.py b/libs/pbr/hooks/commands.py new file mode 100644 index 00000000..aa4db704 --- /dev/null +++ b/libs/pbr/hooks/commands.py @@ -0,0 +1,66 @@ +# Copyright 2013 Hewlett-Packard Development Company, L.P. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os + +from setuptools.command import easy_install + +from pbr.hooks import base +from pbr import options +from pbr import packaging + + +class CommandsConfig(base.BaseConfig): + + section = 'global' + + def __init__(self, config): + super(CommandsConfig, self).__init__(config) + self.commands = self.config.get('commands', "") + + def save(self): + self.config['commands'] = self.commands + super(CommandsConfig, self).save() + + def add_command(self, command): + self.commands = "%s\n%s" % (self.commands, command) + + def hook(self): + self.add_command('pbr.packaging.LocalEggInfo') + self.add_command('pbr.packaging.LocalSDist') + self.add_command('pbr.packaging.LocalInstallScripts') + self.add_command('pbr.packaging.LocalDevelop') + self.add_command('pbr.packaging.LocalRPMVersion') + self.add_command('pbr.packaging.LocalDebVersion') + if os.name != 'nt': + easy_install.get_script_args = packaging.override_get_script_args + + if packaging.have_sphinx(): + self.add_command('pbr.builddoc.LocalBuildDoc') + + if os.path.exists('.testr.conf') and packaging.have_testr(): + # There is a .testr.conf file. We want to use it. + self.add_command('pbr.packaging.TestrTest') + elif self.config.get('nosetests', False) and packaging.have_nose(): + # We seem to still have nose configured + self.add_command('pbr.packaging.NoseTest') + + use_egg = options.get_boolean_option( + self.pbr_config, 'use-egg', 'PBR_USE_EGG') + # We always want non-egg install unless explicitly requested + if 'manpages' in self.pbr_config or not use_egg: + self.add_command('pbr.packaging.LocalInstall') + else: + self.add_command('pbr.packaging.InstallWithGit') diff --git a/libs/pbr/hooks/files.py b/libs/pbr/hooks/files.py new file mode 100644 index 00000000..48bf9e31 --- /dev/null +++ b/libs/pbr/hooks/files.py @@ -0,0 +1,103 @@ +# Copyright 2013 Hewlett-Packard Development Company, L.P. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os +import sys + +from pbr import find_package +from pbr.hooks import base + + +def get_manpath(): + manpath = 'share/man' + if os.path.exists(os.path.join(sys.prefix, 'man')): + # This works around a bug with install where it expects every node + # in the relative data directory to be an actual directory, since at + # least Debian derivatives (and probably other platforms as well) + # like to symlink Unixish /usr/local/man to /usr/local/share/man. + manpath = 'man' + return manpath + + +def get_man_section(section): + return os.path.join(get_manpath(), 'man%s' % section) + + +class FilesConfig(base.BaseConfig): + + section = 'files' + + def __init__(self, config, name): + super(FilesConfig, self).__init__(config) + self.name = name + self.data_files = self.config.get('data_files', '') + + def save(self): + self.config['data_files'] = self.data_files + super(FilesConfig, self).save() + + def expand_globs(self): + finished = [] + for line in self.data_files.split("\n"): + if line.rstrip().endswith('*') and '=' in line: + (target, source_glob) = line.split('=') + source_prefix = source_glob.strip()[:-1] + target = target.strip() + if not target.endswith(os.path.sep): + target += os.path.sep + for (dirpath, dirnames, fnames) in os.walk(source_prefix): + finished.append( + "%s = " % dirpath.replace(source_prefix, target)) + finished.extend( + [" %s" % os.path.join(dirpath, f) for f in fnames]) + else: + finished.append(line) + + self.data_files = "\n".join(finished) + + def add_man_path(self, man_path): + self.data_files = "%s\n%s =" % (self.data_files, man_path) + + def add_man_page(self, man_page): + self.data_files = "%s\n %s" % (self.data_files, man_page) + + def get_man_sections(self): + man_sections = dict() + manpages = self.pbr_config['manpages'] + for manpage in manpages.split(): + section_number = manpage.strip()[-1] + section = man_sections.get(section_number, list()) + section.append(manpage.strip()) + man_sections[section_number] = section + return man_sections + + def hook(self): + packages = self.config.get('packages', self.name).strip() + expanded = [] + for pkg in packages.split("\n"): + if os.path.isdir(pkg.strip()): + expanded.append(find_package.smart_find_packages(pkg.strip())) + + self.config['packages'] = "\n".join(expanded) + + self.expand_globs() + + if 'manpages' in self.pbr_config: + man_sections = self.get_man_sections() + for (section, pages) in man_sections.items(): + manpath = get_man_section(section) + self.add_man_path(manpath) + for page in pages: + self.add_man_page(page) diff --git a/libs/pbr/hooks/metadata.py b/libs/pbr/hooks/metadata.py new file mode 100644 index 00000000..3f65b6d7 --- /dev/null +++ b/libs/pbr/hooks/metadata.py @@ -0,0 +1,32 @@ +# Copyright 2013 Hewlett-Packard Development Company, L.P. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from pbr.hooks import base +from pbr import packaging + + +class MetadataConfig(base.BaseConfig): + + section = 'metadata' + + def hook(self): + self.config['version'] = packaging.get_version( + self.config['name'], self.config.get('version', None)) + packaging.append_text_list( + self.config, 'requires_dist', + packaging.parse_requirements()) + + def get_name(self): + return self.config['name'] diff --git a/libs/pbr/options.py b/libs/pbr/options.py new file mode 100644 index 00000000..105b200e --- /dev/null +++ b/libs/pbr/options.py @@ -0,0 +1,53 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Copyright (C) 2013 Association of Universities for Research in Astronomy +# (AURA) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# 3. The name of AURA and its representatives may not be used to +# endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED +# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. + +import os + + +TRUE_VALUES = ('true', '1', 'yes') + + +def get_boolean_option(option_dict, option_name, env_name): + return ((option_name in option_dict + and option_dict[option_name][1].lower() in TRUE_VALUES) or + str(os.getenv(env_name)).lower() in TRUE_VALUES) diff --git a/libs/pbr/packaging.py b/libs/pbr/packaging.py new file mode 100644 index 00000000..77a4b226 --- /dev/null +++ b/libs/pbr/packaging.py @@ -0,0 +1,855 @@ +# Copyright 2011 OpenStack Foundation +# Copyright 2012-2013 Hewlett-Packard Development Company, L.P. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Utilities with minimum-depends for use in setup.py +""" + +from __future__ import unicode_literals + +from distutils.command import install as du_install +from distutils import log +import email +import email.errors +import os +import re +import sys +import warnings + +import pkg_resources +import setuptools +from setuptools.command import develop +from setuptools.command import easy_install +from setuptools.command import egg_info +from setuptools.command import install +from setuptools.command import install_scripts +from setuptools.command import sdist + +from pbr import extra_files +from pbr import git +from pbr import options +import pbr.pbr_json +from pbr import testr_command +from pbr import version + +REQUIREMENTS_FILES = ('requirements.txt', 'tools/pip-requires') +PY_REQUIREMENTS_FILES = [x % sys.version_info[0] for x in ( + 'requirements-py%d.txt', 'tools/pip-requires-py%d')] +TEST_REQUIREMENTS_FILES = ('test-requirements.txt', 'tools/test-requires') + + +def get_requirements_files(): + files = os.environ.get("PBR_REQUIREMENTS_FILES") + if files: + return tuple(f.strip() for f in files.split(',')) + # Returns a list composed of: + # - REQUIREMENTS_FILES with -py2 or -py3 in the name + # (e.g. requirements-py3.txt) + # - REQUIREMENTS_FILES + + return PY_REQUIREMENTS_FILES + list(REQUIREMENTS_FILES) + + +def append_text_list(config, key, text_list): + """Append a \n separated list to possibly existing value.""" + new_value = [] + current_value = config.get(key, "") + if current_value: + new_value.append(current_value) + new_value.extend(text_list) + config[key] = '\n'.join(new_value) + + +def _any_existing(file_list): + return [f for f in file_list if os.path.exists(f)] + + +# Get requirements from the first file that exists +def get_reqs_from_files(requirements_files): + existing = _any_existing(requirements_files) + + # TODO(stephenfin): Remove this in pbr 6.0+ + deprecated = [f for f in existing if f in PY_REQUIREMENTS_FILES] + if deprecated: + warnings.warn('Support for \'-pyN\'-suffixed requirements files is ' + 'removed in pbr 5.0 and these files are now ignored. ' + 'Use environment markers instead. Conflicting files: ' + '%r' % deprecated, + DeprecationWarning) + + existing = [f for f in existing if f not in PY_REQUIREMENTS_FILES] + for requirements_file in existing: + with open(requirements_file, 'r') as fil: + return fil.read().split('\n') + + return [] + + +def parse_requirements(requirements_files=None, strip_markers=False): + + if requirements_files is None: + requirements_files = get_requirements_files() + + def egg_fragment(match): + # take a versioned egg fragment and return a + # versioned package requirement e.g. + # nova-1.2.3 becomes nova>=1.2.3 + return re.sub(r'([\w.]+)-([\w.-]+)', + r'\1>=\2', + match.groups()[-1]) + + requirements = [] + for line in get_reqs_from_files(requirements_files): + # Ignore comments + if (not line.strip()) or line.startswith('#'): + continue + + # Ignore index URL lines + if re.match(r'^\s*(-i|--index-url|--extra-index-url).*', line): + continue + + # Handle nested requirements files such as: + # -r other-requirements.txt + if line.startswith('-r'): + req_file = line.partition(' ')[2] + requirements += parse_requirements( + [req_file], strip_markers=strip_markers) + continue + + try: + project_name = pkg_resources.Requirement.parse(line).project_name + except ValueError: + project_name = None + + # For the requirements list, we need to inject only the portion + # after egg= so that distutils knows the package it's looking for + # such as: + # -e git://github.com/openstack/nova/master#egg=nova + # -e git://github.com/openstack/nova/master#egg=nova-1.2.3 + # -e git+https://foo.com/zipball#egg=bar&subdirectory=baz + if re.match(r'\s*-e\s+', line): + line = re.sub(r'\s*-e\s+.*#egg=([^&]+).*$', egg_fragment, line) + # such as: + # http://github.com/openstack/nova/zipball/master#egg=nova + # http://github.com/openstack/nova/zipball/master#egg=nova-1.2.3 + # git+https://foo.com/zipball#egg=bar&subdirectory=baz + elif re.match(r'\s*(https?|git(\+(https|ssh))?):', line): + line = re.sub(r'\s*(https?|git(\+(https|ssh))?):.*#egg=([^&]+).*$', + egg_fragment, line) + # -f lines are for index locations, and don't get used here + elif re.match(r'\s*-f\s+', line): + line = None + reason = 'Index Location' + + if line is not None: + line = re.sub('#.*$', '', line) + if strip_markers: + semi_pos = line.find(';') + if semi_pos < 0: + semi_pos = None + line = line[:semi_pos] + requirements.append(line) + else: + log.info( + '[pbr] Excluding %s: %s' % (project_name, reason)) + + return requirements + + +def parse_dependency_links(requirements_files=None): + if requirements_files is None: + requirements_files = get_requirements_files() + dependency_links = [] + # dependency_links inject alternate locations to find packages listed + # in requirements + for line in get_reqs_from_files(requirements_files): + # skip comments and blank lines + if re.match(r'(\s*#)|(\s*$)', line): + continue + # lines with -e or -f need the whole line, minus the flag + if re.match(r'\s*-[ef]\s+', line): + dependency_links.append(re.sub(r'\s*-[ef]\s+', '', line)) + # lines that are only urls can go in unmolested + elif re.match(r'\s*(https?|git(\+(https|ssh))?):', line): + dependency_links.append(line) + return dependency_links + + +class InstallWithGit(install.install): + """Extracts ChangeLog and AUTHORS from git then installs. + + This is useful for e.g. readthedocs where the package is + installed and then docs built. + """ + + command_name = 'install' + + def run(self): + _from_git(self.distribution) + return install.install.run(self) + + +class LocalInstall(install.install): + """Runs python setup.py install in a sensible manner. + + Force a non-egg installed in the manner of + single-version-externally-managed, which allows us to install manpages + and config files. + """ + + command_name = 'install' + + def run(self): + _from_git(self.distribution) + return du_install.install.run(self) + + +class TestrTest(testr_command.Testr): + """Make setup.py test do the right thing.""" + + command_name = 'test' + description = 'DEPRECATED: Run unit tests using testr' + + def run(self): + warnings.warn('testr integration is deprecated in pbr 4.2 and will ' + 'be removed in a future release. Please call your test ' + 'runner directly', + DeprecationWarning) + + # Can't use super - base class old-style class + testr_command.Testr.run(self) + + +class LocalRPMVersion(setuptools.Command): + __doc__ = """Output the rpm *compatible* version string of this package""" + description = __doc__ + + user_options = [] + command_name = "rpm_version" + + def run(self): + log.info("[pbr] Extracting rpm version") + name = self.distribution.get_name() + print(version.VersionInfo(name).semantic_version().rpm_string()) + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + +class LocalDebVersion(setuptools.Command): + __doc__ = """Output the deb *compatible* version string of this package""" + description = __doc__ + + user_options = [] + command_name = "deb_version" + + def run(self): + log.info("[pbr] Extracting deb version") + name = self.distribution.get_name() + print(version.VersionInfo(name).semantic_version().debian_string()) + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + +def have_testr(): + return testr_command.have_testr + + +try: + from nose import commands + + class NoseTest(commands.nosetests): + """Fallback test runner if testr is a no-go.""" + + command_name = 'test' + description = 'DEPRECATED: Run unit tests using nose' + + def run(self): + warnings.warn('nose integration in pbr is deprecated. Please use ' + 'the native nose setuptools configuration or call ' + 'nose directly', + DeprecationWarning) + + # Can't use super - base class old-style class + commands.nosetests.run(self) + + _have_nose = True + +except ImportError: + _have_nose = False + + +def have_nose(): + return _have_nose + +_wsgi_text = """#PBR Generated from %(group)r + +import threading + +from %(module_name)s import %(import_target)s + +if __name__ == "__main__": + import argparse + import socket + import sys + import wsgiref.simple_server as wss + + parser = argparse.ArgumentParser( + description=%(import_target)s.__doc__, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + usage='%%(prog)s [-h] [--port PORT] [--host IP] -- [passed options]') + parser.add_argument('--port', '-p', type=int, default=8000, + help='TCP port to listen on') + parser.add_argument('--host', '-b', default='', + help='IP to bind the server to') + parser.add_argument('args', + nargs=argparse.REMAINDER, + metavar='-- [passed options]', + help="'--' is the separator of the arguments used " + "to start the WSGI server and the arguments passed " + "to the WSGI application.") + args = parser.parse_args() + if args.args: + if args.args[0] == '--': + args.args.pop(0) + else: + parser.error("unrecognized arguments: %%s" %% ' '.join(args.args)) + sys.argv[1:] = args.args + server = wss.make_server(args.host, args.port, %(invoke_target)s()) + + print("*" * 80) + print("STARTING test server %(module_name)s.%(invoke_target)s") + url = "http://%%s:%%d/" %% (server.server_name, server.server_port) + print("Available at %%s" %% url) + print("DANGER! For testing only, do not use in production") + print("*" * 80) + sys.stdout.flush() + + server.serve_forever() +else: + application = None + app_lock = threading.Lock() + + with app_lock: + if application is None: + application = %(invoke_target)s() + +""" + +_script_text = """# PBR Generated from %(group)r + +import sys + +from %(module_name)s import %(import_target)s + + +if __name__ == "__main__": + sys.exit(%(invoke_target)s()) +""" + + +# the following allows us to specify different templates per entry +# point group when generating pbr scripts. +ENTRY_POINTS_MAP = { + 'console_scripts': _script_text, + 'gui_scripts': _script_text, + 'wsgi_scripts': _wsgi_text +} + + +def generate_script(group, entry_point, header, template): + """Generate the script based on the template. + + :param str group: + The entry-point group name, e.g., "console_scripts". + :param str header: + The first line of the script, e.g., "!#/usr/bin/env python". + :param str template: + The script template. + :returns: + The templated script content + :rtype: + str + """ + if not entry_point.attrs or len(entry_point.attrs) > 2: + raise ValueError("Script targets must be of the form " + "'func' or 'Class.class_method'.") + script_text = template % dict( + group=group, + module_name=entry_point.module_name, + import_target=entry_point.attrs[0], + invoke_target='.'.join(entry_point.attrs), + ) + return header + script_text + + +def override_get_script_args( + dist, executable=os.path.normpath(sys.executable), is_wininst=False): + """Override entrypoints console_script.""" + header = easy_install.get_script_header("", executable, is_wininst) + for group, template in ENTRY_POINTS_MAP.items(): + for name, ep in dist.get_entry_map(group).items(): + yield (name, generate_script(group, ep, header, template)) + + +class LocalDevelop(develop.develop): + + command_name = 'develop' + + def install_wrapper_scripts(self, dist): + if sys.platform == 'win32': + return develop.develop.install_wrapper_scripts(self, dist) + if not self.exclude_scripts: + for args in override_get_script_args(dist): + self.write_script(*args) + + +class LocalInstallScripts(install_scripts.install_scripts): + """Intercepts console scripts entry_points.""" + command_name = 'install_scripts' + + def _make_wsgi_scripts_only(self, dist, executable, is_wininst): + header = easy_install.get_script_header("", executable, is_wininst) + wsgi_script_template = ENTRY_POINTS_MAP['wsgi_scripts'] + for name, ep in dist.get_entry_map('wsgi_scripts').items(): + content = generate_script( + 'wsgi_scripts', ep, header, wsgi_script_template) + self.write_script(name, content) + + def run(self): + import distutils.command.install_scripts + + self.run_command("egg_info") + if self.distribution.scripts: + # run first to set up self.outfiles + distutils.command.install_scripts.install_scripts.run(self) + else: + self.outfiles = [] + + ei_cmd = self.get_finalized_command("egg_info") + dist = pkg_resources.Distribution( + ei_cmd.egg_base, + pkg_resources.PathMetadata(ei_cmd.egg_base, ei_cmd.egg_info), + ei_cmd.egg_name, ei_cmd.egg_version, + ) + bs_cmd = self.get_finalized_command('build_scripts') + executable = getattr( + bs_cmd, 'executable', easy_install.sys_executable) + is_wininst = getattr( + self.get_finalized_command("bdist_wininst"), '_is_running', False + ) + + if 'bdist_wheel' in self.distribution.have_run: + # We're building a wheel which has no way of generating mod_wsgi + # scripts for us. Let's build them. + # NOTE(sigmavirus24): This needs to happen here because, as the + # comment below indicates, no_ep is True when building a wheel. + self._make_wsgi_scripts_only(dist, executable, is_wininst) + + if self.no_ep: + # no_ep is True if we're installing into an .egg file or building + # a .whl file, in those cases, we do not want to build all of the + # entry-points listed for this package. + return + + if os.name != 'nt': + get_script_args = override_get_script_args + else: + get_script_args = easy_install.get_script_args + executable = '"%s"' % executable + + for args in get_script_args(dist, executable, is_wininst): + self.write_script(*args) + + +class LocalManifestMaker(egg_info.manifest_maker): + """Add any files that are in git and some standard sensible files.""" + + def _add_pbr_defaults(self): + for template_line in [ + 'include AUTHORS', + 'include ChangeLog', + 'exclude .gitignore', + 'exclude .gitreview', + 'global-exclude *.pyc' + ]: + self.filelist.process_template_line(template_line) + + def add_defaults(self): + """Add all the default files to self.filelist: + + Extends the functionality provided by distutils to also included + additional sane defaults, such as the ``AUTHORS`` and ``ChangeLog`` + files generated by *pbr*. + + Warns if (``README`` or ``README.txt``) or ``setup.py`` are missing; + everything else is optional. + """ + option_dict = self.distribution.get_option_dict('pbr') + + sdist.sdist.add_defaults(self) + self.filelist.append(self.template) + self.filelist.append(self.manifest) + self.filelist.extend(extra_files.get_extra_files()) + should_skip = options.get_boolean_option(option_dict, 'skip_git_sdist', + 'SKIP_GIT_SDIST') + if not should_skip: + rcfiles = git._find_git_files() + if rcfiles: + self.filelist.extend(rcfiles) + elif os.path.exists(self.manifest): + self.read_manifest() + ei_cmd = self.get_finalized_command('egg_info') + self._add_pbr_defaults() + self.filelist.include_pattern("*", prefix=ei_cmd.egg_info) + + +class LocalEggInfo(egg_info.egg_info): + """Override the egg_info command to regenerate SOURCES.txt sensibly.""" + + command_name = 'egg_info' + + def find_sources(self): + """Generate SOURCES.txt only if there isn't one already. + + If we are in an sdist command, then we always want to update + SOURCES.txt. If we are not in an sdist command, then it doesn't + matter one flip, and is actually destructive. + However, if we're in a git context, it's always the right thing to do + to recreate SOURCES.txt + """ + manifest_filename = os.path.join(self.egg_info, "SOURCES.txt") + if (not os.path.exists(manifest_filename) or + os.path.exists('.git') or + 'sdist' in sys.argv): + log.info("[pbr] Processing SOURCES.txt") + mm = LocalManifestMaker(self.distribution) + mm.manifest = manifest_filename + mm.run() + self.filelist = mm.filelist + else: + log.info("[pbr] Reusing existing SOURCES.txt") + self.filelist = egg_info.FileList() + for entry in open(manifest_filename, 'r').read().split('\n'): + self.filelist.append(entry) + + +def _from_git(distribution): + option_dict = distribution.get_option_dict('pbr') + changelog = git._iter_log_oneline() + if changelog: + changelog = git._iter_changelog(changelog) + git.write_git_changelog(option_dict=option_dict, changelog=changelog) + git.generate_authors(option_dict=option_dict) + + +class LocalSDist(sdist.sdist): + """Builds the ChangeLog and Authors files from VC first.""" + + command_name = 'sdist' + + def checking_reno(self): + """Ensure reno is installed and configured. + + We can't run reno-based commands if reno isn't installed/available, and + don't want to if the user isn't using it. + """ + if hasattr(self, '_has_reno'): + return self._has_reno + + option_dict = self.distribution.get_option_dict('pbr') + should_skip = options.get_boolean_option(option_dict, 'skip_reno', + 'SKIP_GENERATE_RENO') + if should_skip: + self._has_reno = False + return False + + try: + # versions of reno witout this module will not have the required + # feature, hence the import + from reno import setup_command # noqa + except ImportError: + log.info('[pbr] reno was not found or is too old. Skipping ' + 'release notes') + self._has_reno = False + return False + + conf, output_file, cache_file = setup_command.load_config( + self.distribution) + + if not os.path.exists(os.path.join(conf.reporoot, conf.notespath)): + log.info('[pbr] reno does not appear to be configured. Skipping ' + 'release notes') + self._has_reno = False + return False + + self._files = [output_file, cache_file] + + log.info('[pbr] Generating release notes') + self._has_reno = True + + return True + + sub_commands = [('build_reno', checking_reno)] + sdist.sdist.sub_commands + + def run(self): + _from_git(self.distribution) + # sdist.sdist is an old style class, can't use super() + sdist.sdist.run(self) + + def make_distribution(self): + # This is included in make_distribution because setuptools doesn't use + # 'get_file_list'. As such, this is the only hook point that runs after + # the commands in 'sub_commands' + if self.checking_reno(): + self.filelist.extend(self._files) + self.filelist.sort() + sdist.sdist.make_distribution(self) + +try: + from pbr import builddoc + _have_sphinx = True + # Import the symbols from their new home so the package API stays + # compatible. + LocalBuildDoc = builddoc.LocalBuildDoc +except ImportError: + _have_sphinx = False + LocalBuildDoc = None + + +def have_sphinx(): + return _have_sphinx + + +def _get_increment_kwargs(git_dir, tag): + """Calculate the sort of semver increment needed from git history. + + Every commit from HEAD to tag is consider for Sem-Ver metadata lines. + See the pbr docs for their syntax. + + :return: a dict of kwargs for passing into SemanticVersion.increment. + """ + result = {} + if tag: + version_spec = tag + "..HEAD" + else: + version_spec = "HEAD" + # Get the raw body of the commit messages so that we don't have to + # parse out any formatting whitespace and to avoid user settings on + # git log output affecting out ability to have working sem ver headers. + changelog = git._run_git_command(['log', '--pretty=%B', version_spec], + git_dir) + header_len = len('sem-ver:') + commands = [line[header_len:].strip() for line in changelog.split('\n') + if line.lower().startswith('sem-ver:')] + symbols = set() + for command in commands: + symbols.update([symbol.strip() for symbol in command.split(',')]) + + def _handle_symbol(symbol, symbols, impact): + if symbol in symbols: + result[impact] = True + symbols.discard(symbol) + _handle_symbol('bugfix', symbols, 'patch') + _handle_symbol('feature', symbols, 'minor') + _handle_symbol('deprecation', symbols, 'minor') + _handle_symbol('api-break', symbols, 'major') + for symbol in symbols: + log.info('[pbr] Unknown Sem-Ver symbol %r' % symbol) + # We don't want patch in the kwargs since it is not a keyword argument - + # its the default minimum increment. + result.pop('patch', None) + return result + + +def _get_revno_and_last_tag(git_dir): + """Return the commit data about the most recent tag. + + We use git-describe to find this out, but if there are no + tags then we fall back to counting commits since the beginning + of time. + """ + changelog = git._iter_log_oneline(git_dir=git_dir) + row_count = 0 + for row_count, (ignored, tag_set, ignored) in enumerate(changelog): + version_tags = set() + semver_to_tag = dict() + for tag in list(tag_set): + try: + semver = version.SemanticVersion.from_pip_string(tag) + semver_to_tag[semver] = tag + version_tags.add(semver) + except Exception: + pass + if version_tags: + return semver_to_tag[max(version_tags)], row_count + return "", row_count + + +def _get_version_from_git_target(git_dir, target_version): + """Calculate a version from a target version in git_dir. + + This is used for untagged versions only. A new version is calculated as + necessary based on git metadata - distance to tags, current hash, contents + of commit messages. + + :param git_dir: The git directory we're working from. + :param target_version: If None, the last tagged version (or 0 if there are + no tags yet) is incremented as needed to produce an appropriate target + version following semver rules. Otherwise target_version is used as a + constraint - if semver rules would result in a newer version then an + exception is raised. + :return: A semver version object. + """ + tag, distance = _get_revno_and_last_tag(git_dir) + last_semver = version.SemanticVersion.from_pip_string(tag or '0') + if distance == 0: + new_version = last_semver + else: + new_version = last_semver.increment( + **_get_increment_kwargs(git_dir, tag)) + if target_version is not None and new_version > target_version: + raise ValueError( + "git history requires a target version of %(new)s, but target " + "version is %(target)s" % + dict(new=new_version, target=target_version)) + if distance == 0: + return last_semver + new_dev = new_version.to_dev(distance) + if target_version is not None: + target_dev = target_version.to_dev(distance) + if target_dev > new_dev: + return target_dev + return new_dev + + +def _get_version_from_git(pre_version=None): + """Calculate a version string from git. + + If the revision is tagged, return that. Otherwise calculate a semantic + version description of the tree. + + The number of revisions since the last tag is included in the dev counter + in the version for untagged versions. + + :param pre_version: If supplied use this as the target version rather than + inferring one from the last tag + commit messages. + """ + git_dir = git._run_git_functions() + if git_dir: + try: + tagged = git._run_git_command( + ['describe', '--exact-match'], git_dir, + throw_on_error=True).replace('-', '.') + target_version = version.SemanticVersion.from_pip_string(tagged) + except Exception: + if pre_version: + # not released yet - use pre_version as the target + target_version = version.SemanticVersion.from_pip_string( + pre_version) + else: + # not released yet - just calculate from git history + target_version = None + result = _get_version_from_git_target(git_dir, target_version) + return result.release_string() + # If we don't know the version, return an empty string so at least + # the downstream users of the value always have the same type of + # object to work with. + try: + return unicode() + except NameError: + return '' + + +def _get_version_from_pkg_metadata(package_name): + """Get the version from package metadata if present. + + This looks for PKG-INFO if present (for sdists), and if not looks + for METADATA (for wheels) and failing that will return None. + """ + pkg_metadata_filenames = ['PKG-INFO', 'METADATA'] + pkg_metadata = {} + for filename in pkg_metadata_filenames: + try: + pkg_metadata_file = open(filename, 'r') + except (IOError, OSError): + continue + try: + pkg_metadata = email.message_from_file(pkg_metadata_file) + except email.errors.MessageError: + continue + + # Check to make sure we're in our own dir + if pkg_metadata.get('Name', None) != package_name: + return None + return pkg_metadata.get('Version', None) + + +def get_version(package_name, pre_version=None): + """Get the version of the project. + + First, try getting it from PKG-INFO or METADATA, if it exists. If it does, + that means we're in a distribution tarball or that install has happened. + Otherwise, if there is no PKG-INFO or METADATA file, pull the version + from git. + + We do not support setup.py version sanity in git archive tarballs, nor do + we support packagers directly sucking our git repo into theirs. We expect + that a source tarball be made from our git repo - or that if someone wants + to make a source tarball from a fork of our repo with additional tags in it + that they understand and desire the results of doing that. + + :param pre_version: The version field from setup.cfg - if set then this + version will be the next release. + """ + version = os.environ.get( + "PBR_VERSION", + os.environ.get("OSLO_PACKAGE_VERSION", None)) + if version: + return version + version = _get_version_from_pkg_metadata(package_name) + if version: + return version + version = _get_version_from_git(pre_version) + # Handle http://bugs.python.org/issue11638 + # version will either be an empty unicode string or a valid + # unicode version string, but either way it's unicode and needs to + # be encoded. + if sys.version_info[0] == 2: + version = version.encode('utf-8') + if version: + return version + raise Exception("Versioning for this project requires either an sdist" + " tarball, or access to an upstream git repository." + " It's also possible that there is a mismatch between" + " the package name in setup.cfg and the argument given" + " to pbr.version.VersionInfo. Project name {name} was" + " given, but was not able to be found.".format( + name=package_name)) + + +# This is added because pbr uses pbr to install itself. That means that +# any changes to the egg info writer entrypoints must be forward and +# backward compatible. This maintains the pbr.packaging.write_pbr_json +# path. +write_pbr_json = pbr.pbr_json.write_pbr_json diff --git a/libs/pbr/pbr_json.py b/libs/pbr/pbr_json.py new file mode 100644 index 00000000..08c3da22 --- /dev/null +++ b/libs/pbr/pbr_json.py @@ -0,0 +1,34 @@ +# Copyright 2011 OpenStack Foundation +# Copyright 2012-2013 Hewlett-Packard Development Company, L.P. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import json + +from pbr import git + + +def write_pbr_json(cmd, basename, filename): + if not hasattr(cmd.distribution, 'pbr') or not cmd.distribution.pbr: + return + git_dir = git._run_git_functions() + if not git_dir: + return + values = dict() + git_version = git.get_git_short_sha(git_dir) + is_release = git.get_is_release(git_dir) + if git_version is not None: + values['git_version'] = git_version + values['is_release'] = is_release + cmd.write_file('pbr', filename, json.dumps(values, sort_keys=True)) diff --git a/libs/pbr/sphinxext.py b/libs/pbr/sphinxext.py new file mode 100644 index 00000000..ef613052 --- /dev/null +++ b/libs/pbr/sphinxext.py @@ -0,0 +1,99 @@ +# Copyright 2018 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os.path + +from six.moves import configparser +from sphinx.util import logging + +import pbr.version + +_project = None +logger = logging.getLogger(__name__) + + +def _find_setup_cfg(srcdir): + """Find the 'setup.cfg' file, if it exists. + + This assumes we're using 'doc/source' for documentation, but also allows + for single level 'doc' paths. + """ + # TODO(stephenfin): Are we sure that this will always exist, e.g. for + # an sdist or wheel? Perhaps we should check for 'PKG-INFO' or + # 'METADATA' files, a la 'pbr.packaging._get_version_from_pkg_metadata' + for path in [ + os.path.join(srcdir, os.pardir, 'setup.cfg'), + os.path.join(srcdir, os.pardir, os.pardir, 'setup.cfg')]: + if os.path.exists(path): + return path + + return None + + +def _get_project_name(srcdir): + """Return string name of project name, or None. + + This extracts metadata from 'setup.cfg'. We don't rely on + distutils/setuptools as we don't want to actually install the package + simply to build docs. + """ + global _project + + if _project is None: + parser = configparser.ConfigParser() + + path = _find_setup_cfg(srcdir) + if not path or not parser.read(path): + logger.info('Could not find a setup.cfg to extract project name ' + 'from') + return None + + try: + # for project name we use the name in setup.cfg, but if the + # length is longer then 32 we use summary. Otherwise thAe + # menu rendering looks brolen + project = parser.get('metadata', 'name') + if len(project.split()) == 1 and len(project) > 32: + project = parser.get('metadata', 'summary') + except configparser.Error: + logger.info('Could not extract project metadata from setup.cfg') + return None + + _project = project + + return _project + + +def _builder_inited(app): + # TODO(stephenfin): Once Sphinx 1.8 is released, we should move the below + # to a 'config-inited' handler + + project_name = _get_project_name(app.srcdir) + try: + version_info = pbr.version.VersionInfo(project_name) + except Exception: + version_info = None + + if version_info and not app.config.version and not app.config.release: + app.config.version = version_info.canonical_version_string() + app.config.release = version_info.version_string_with_vcs() + + +def setup(app): + app.connect('builder-inited', _builder_inited) + return { + 'parallel_read_safe': True, + 'parallel_write_safe': True, + } diff --git a/libs/pbr/testr_command.py b/libs/pbr/testr_command.py new file mode 100644 index 00000000..d143565f --- /dev/null +++ b/libs/pbr/testr_command.py @@ -0,0 +1,167 @@ +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Copyright (c) 2013 Testrepository Contributors +# +# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause +# license at the users choice. A copy of both licenses are available in the +# project source as Apache-2.0 and BSD. You may not use this file except in +# compliance with one of these two licences. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# license you chose for the specific language governing permissions and +# limitations under that license. + +"""setuptools/distutils command to run testr via setup.py + +PBR will hook in the Testr class to provide "setup.py test" when +.testr.conf is present in the repository (see pbr/hooks/commands.py). + +If we are activated but testrepository is not installed, we provide a +sensible error. + +You can pass --coverage which will also export PYTHON='coverage run +--source ' and automatically combine the coverage from +each testr backend test runner after the run completes. + +""" + +from distutils import cmd +import distutils.errors +import logging +import os +import sys +import warnings + +logger = logging.getLogger(__name__) + + +class TestrReal(cmd.Command): + + description = "DEPRECATED: Run unit tests using testr" + + user_options = [ + ('coverage', None, "Replace PYTHON with coverage and merge coverage " + "from each testr worker."), + ('testr-args=', 't', "Run 'testr' with these args"), + ('omit=', 'o', "Files to omit from coverage calculations"), + ('coverage-package-name=', None, "Use this name to select packages " + "for coverage (one or more, " + "comma-separated)"), + ('slowest', None, "Show slowest test times after tests complete."), + ('no-parallel', None, "Run testr serially"), + ('log-level=', 'l', "Log level (default: info)"), + ] + + boolean_options = ['coverage', 'slowest', 'no_parallel'] + + def _run_testr(self, *args): + logger.debug("_run_testr called with args = %r", args) + return commands.run_argv([sys.argv[0]] + list(args), + sys.stdin, sys.stdout, sys.stderr) + + def initialize_options(self): + self.testr_args = None + self.coverage = None + self.omit = "" + self.slowest = None + self.coverage_package_name = None + self.no_parallel = None + self.log_level = 'info' + + def finalize_options(self): + self.log_level = getattr( + logging, + self.log_level.upper(), + logging.INFO) + logging.basicConfig(level=self.log_level) + logger.debug("finalize_options called") + if self.testr_args is None: + self.testr_args = [] + else: + self.testr_args = self.testr_args.split() + if self.omit: + self.omit = "--omit=%s" % self.omit + logger.debug("finalize_options: self.__dict__ = %r", self.__dict__) + + def run(self): + """Set up testr repo, then run testr.""" + logger.debug("run called") + + warnings.warn('testr integration in pbr is deprecated. Please use ' + 'the \'testr\' setup command or call testr directly', + DeprecationWarning) + + if not os.path.isdir(".testrepository"): + self._run_testr("init") + + if self.coverage: + self._coverage_before() + if not self.no_parallel: + testr_ret = self._run_testr("run", "--parallel", *self.testr_args) + else: + testr_ret = self._run_testr("run", *self.testr_args) + if testr_ret: + raise distutils.errors.DistutilsError( + "testr failed (%d)" % testr_ret) + if self.slowest: + print("Slowest Tests") + self._run_testr("slowest") + if self.coverage: + self._coverage_after() + + def _coverage_before(self): + logger.debug("_coverage_before called") + package = self.distribution.get_name() + if package.startswith('python-'): + package = package[7:] + + # Use this as coverage package name + if self.coverage_package_name: + package = self.coverage_package_name + options = "--source %s --parallel-mode" % package + os.environ['PYTHON'] = ("coverage run %s" % options) + logger.debug("os.environ['PYTHON'] = %r", os.environ['PYTHON']) + + def _coverage_after(self): + logger.debug("_coverage_after called") + os.system("coverage combine") + os.system("coverage html -d ./cover %s" % self.omit) + os.system("coverage xml -o ./cover/coverage.xml %s" % self.omit) + + +class TestrFake(cmd.Command): + description = "Run unit tests using testr" + user_options = [] + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + print("Install testrepository to run 'testr' command properly.") + + +try: + from testrepository import commands + have_testr = True + Testr = TestrReal +except ImportError: + have_testr = False + Testr = TestrFake diff --git a/libs/pbr/tests/__init__.py b/libs/pbr/tests/__init__.py new file mode 100644 index 00000000..583e0c6b --- /dev/null +++ b/libs/pbr/tests/__init__.py @@ -0,0 +1,26 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +import testscenarios + + +def load_tests(loader, standard_tests, pattern): + # top level directory cached on loader instance + this_dir = os.path.dirname(__file__) + package_tests = loader.discover(start_dir=this_dir, pattern=pattern) + result = loader.suiteClass() + result.addTests(testscenarios.generate_scenarios(standard_tests)) + result.addTests(testscenarios.generate_scenarios(package_tests)) + return result diff --git a/libs/pbr/tests/base.py b/libs/pbr/tests/base.py new file mode 100644 index 00000000..9c409b0a --- /dev/null +++ b/libs/pbr/tests/base.py @@ -0,0 +1,221 @@ +# Copyright 2010-2011 OpenStack Foundation +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# Copyright (C) 2013 Association of Universities for Research in Astronomy +# (AURA) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# 3. The name of AURA and its representatives may not be used to +# endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED +# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + +"""Common utilities used in testing""" + +import os +import shutil +import subprocess +import sys + +import fixtures +import testresources +import testtools +from testtools import content + +from pbr import options + + +class DiveDir(fixtures.Fixture): + """Dive into given directory and return back on cleanup. + + :ivar path: The target directory. + """ + + def __init__(self, path): + self.path = path + + def setUp(self): + super(DiveDir, self).setUp() + self.addCleanup(os.chdir, os.getcwd()) + os.chdir(self.path) + + +class BaseTestCase(testtools.TestCase, testresources.ResourcedTestCase): + + def setUp(self): + super(BaseTestCase, self).setUp() + test_timeout = os.environ.get('OS_TEST_TIMEOUT', 30) + try: + test_timeout = int(test_timeout) + except ValueError: + # If timeout value is invalid, fail hard. + print("OS_TEST_TIMEOUT set to invalid value" + " defaulting to no timeout") + test_timeout = 0 + if test_timeout > 0: + self.useFixture(fixtures.Timeout(test_timeout, gentle=True)) + + if os.environ.get('OS_STDOUT_CAPTURE') in options.TRUE_VALUES: + stdout = self.useFixture(fixtures.StringStream('stdout')).stream + self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout)) + if os.environ.get('OS_STDERR_CAPTURE') in options.TRUE_VALUES: + stderr = self.useFixture(fixtures.StringStream('stderr')).stream + self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr)) + self.log_fixture = self.useFixture( + fixtures.FakeLogger('pbr')) + + # Older git does not have config --local, so create a temporary home + # directory to permit using git config --global without stepping on + # developer configuration. + self.useFixture(fixtures.TempHomeDir()) + self.useFixture(fixtures.NestedTempfile()) + self.useFixture(fixtures.FakeLogger()) + # TODO(lifeless) we should remove PBR_VERSION from the environment. + # rather than setting it, because thats not representative - we need to + # test non-preversioned codepaths too! + self.useFixture(fixtures.EnvironmentVariable('PBR_VERSION', '0.0')) + + self.temp_dir = self.useFixture(fixtures.TempDir()).path + self.package_dir = os.path.join(self.temp_dir, 'testpackage') + shutil.copytree(os.path.join(os.path.dirname(__file__), 'testpackage'), + self.package_dir) + self.addCleanup(os.chdir, os.getcwd()) + os.chdir(self.package_dir) + self.addCleanup(self._discard_testpackage) + # Tests can opt into non-PBR_VERSION by setting preversioned=False as + # an attribute. + if not getattr(self, 'preversioned', True): + self.useFixture(fixtures.EnvironmentVariable('PBR_VERSION')) + setup_cfg_path = os.path.join(self.package_dir, 'setup.cfg') + with open(setup_cfg_path, 'rt') as cfg: + content = cfg.read() + content = content.replace(u'version = 0.1.dev', u'') + with open(setup_cfg_path, 'wt') as cfg: + cfg.write(content) + + def _discard_testpackage(self): + # Remove pbr.testpackage from sys.modules so that it can be freshly + # re-imported by the next test + for k in list(sys.modules): + if (k == 'pbr_testpackage' or + k.startswith('pbr_testpackage.')): + del sys.modules[k] + + def run_pbr(self, *args, **kwargs): + return self._run_cmd('pbr', args, **kwargs) + + def run_setup(self, *args, **kwargs): + return self._run_cmd(sys.executable, ('setup.py',) + args, **kwargs) + + def _run_cmd(self, cmd, args=[], allow_fail=True, cwd=None): + """Run a command in the root of the test working copy. + + Runs a command, with the given argument list, in the root of the test + working copy--returns the stdout and stderr streams and the exit code + from the subprocess. + + :param cwd: If falsy run within the test package dir, otherwise run + within the named path. + """ + cwd = cwd or self.package_dir + result = _run_cmd([cmd] + list(args), cwd=cwd) + if result[2] and not allow_fail: + raise Exception("Command failed retcode=%s" % result[2]) + return result + + +class CapturedSubprocess(fixtures.Fixture): + """Run a process and capture its output. + + :attr stdout: The output (a string). + :attr stderr: The standard error (a string). + :attr returncode: The return code of the process. + + Note that stdout and stderr are decoded from the bytestrings subprocess + returns using error=replace. + """ + + def __init__(self, label, *args, **kwargs): + """Create a CapturedSubprocess. + + :param label: A label for the subprocess in the test log. E.g. 'foo'. + :param *args: The *args to pass to Popen. + :param **kwargs: The **kwargs to pass to Popen. + """ + super(CapturedSubprocess, self).__init__() + self.label = label + self.args = args + self.kwargs = kwargs + self.kwargs['stderr'] = subprocess.PIPE + self.kwargs['stdin'] = subprocess.PIPE + self.kwargs['stdout'] = subprocess.PIPE + + def setUp(self): + super(CapturedSubprocess, self).setUp() + proc = subprocess.Popen(*self.args, **self.kwargs) + out, err = proc.communicate() + self.out = out.decode('utf-8', 'replace') + self.err = err.decode('utf-8', 'replace') + self.addDetail(self.label + '-stdout', content.text_content(self.out)) + self.addDetail(self.label + '-stderr', content.text_content(self.err)) + self.returncode = proc.returncode + if proc.returncode: + raise AssertionError('Failed process %s' % proc.returncode) + self.addCleanup(delattr, self, 'out') + self.addCleanup(delattr, self, 'err') + self.addCleanup(delattr, self, 'returncode') + + +def _run_cmd(args, cwd): + """Run the command args in cwd. + + :param args: The command to run e.g. ['git', 'status'] + :param cwd: The directory to run the comamnd in. + :return: ((stdout, stderr), returncode) + """ + p = subprocess.Popen( + args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, cwd=cwd) + streams = tuple(s.decode('latin1').strip() for s in p.communicate()) + for stream_content in streams: + print(stream_content) + return (streams) + (p.returncode,) + + +def _config_git(): + _run_cmd( + ['git', 'config', '--global', 'user.email', 'example@example.com'], + None) + _run_cmd( + ['git', 'config', '--global', 'user.name', 'OpenStack Developer'], + None) + _run_cmd( + ['git', 'config', '--global', 'user.signingkey', + 'example@example.com'], None) diff --git a/libs/pbr/tests/test_commands.py b/libs/pbr/tests/test_commands.py new file mode 100644 index 00000000..51e27116 --- /dev/null +++ b/libs/pbr/tests/test_commands.py @@ -0,0 +1,84 @@ +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Copyright (C) 2013 Association of Universities for Research in Astronomy +# (AURA) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# 3. The name of AURA and its representatives may not be used to +# endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED +# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + +from testtools import content + +from pbr.tests import base + + +class TestCommands(base.BaseTestCase): + def test_custom_build_py_command(self): + """Test custom build_py command. + + Test that a custom subclass of the build_py command runs when listed in + the commands [global] option, rather than the normal build command. + """ + + stdout, stderr, return_code = self.run_setup('build_py') + self.addDetail('stdout', content.text_content(stdout)) + self.addDetail('stderr', content.text_content(stderr)) + self.assertIn('Running custom build_py command.', stdout) + self.assertEqual(0, return_code) + + def test_custom_deb_version_py_command(self): + """Test custom deb_version command.""" + stdout, stderr, return_code = self.run_setup('deb_version') + self.addDetail('stdout', content.text_content(stdout)) + self.addDetail('stderr', content.text_content(stderr)) + self.assertIn('Extracting deb version', stdout) + self.assertEqual(0, return_code) + + def test_custom_rpm_version_py_command(self): + """Test custom rpm_version command.""" + stdout, stderr, return_code = self.run_setup('rpm_version') + self.addDetail('stdout', content.text_content(stdout)) + self.addDetail('stderr', content.text_content(stderr)) + self.assertIn('Extracting rpm version', stdout) + self.assertEqual(0, return_code) + + def test_freeze_command(self): + """Test that freeze output is sorted in a case-insensitive manner.""" + stdout, stderr, return_code = self.run_pbr('freeze') + self.assertEqual(0, return_code) + pkgs = [] + for l in stdout.split('\n'): + pkgs.append(l.split('==')[0].lower()) + pkgs_sort = sorted(pkgs[:]) + self.assertEqual(pkgs_sort, pkgs) diff --git a/libs/pbr/tests/test_core.py b/libs/pbr/tests/test_core.py new file mode 100644 index 00000000..0ee6f532 --- /dev/null +++ b/libs/pbr/tests/test_core.py @@ -0,0 +1,151 @@ +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Copyright (C) 2013 Association of Universities for Research in Astronomy +# (AURA) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# 3. The name of AURA and its representatives may not be used to +# endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED +# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + +import glob +import os +import tarfile + +import fixtures + +from pbr.tests import base + + +class TestCore(base.BaseTestCase): + + cmd_names = ('pbr_test_cmd', 'pbr_test_cmd_with_class') + + def check_script_install(self, install_stdout): + for cmd_name in self.cmd_names: + install_txt = 'Installing %s script to %s' % (cmd_name, + self.temp_dir) + self.assertIn(install_txt, install_stdout) + + cmd_filename = os.path.join(self.temp_dir, cmd_name) + + script_txt = open(cmd_filename, 'r').read() + self.assertNotIn('pkg_resources', script_txt) + + stdout, _, return_code = self._run_cmd(cmd_filename) + self.assertIn("PBR", stdout) + + def test_setup_py_keywords(self): + """setup.py --keywords. + + Test that the `./setup.py --keywords` command returns the correct + value without balking. + """ + + self.run_setup('egg_info') + stdout, _, _ = self.run_setup('--keywords') + assert stdout == 'packaging,distutils,setuptools' + + def test_setup_py_build_sphinx(self): + stdout, _, return_code = self.run_setup('build_sphinx') + self.assertEqual(0, return_code) + + def test_sdist_extra_files(self): + """Test that the extra files are correctly added.""" + + stdout, _, return_code = self.run_setup('sdist', '--formats=gztar') + + # There can be only one + try: + tf_path = glob.glob(os.path.join('dist', '*.tar.gz'))[0] + except IndexError: + assert False, 'source dist not found' + + tf = tarfile.open(tf_path) + names = ['/'.join(p.split('/')[1:]) for p in tf.getnames()] + + self.assertIn('extra-file.txt', names) + + def test_console_script_install(self): + """Test that we install a non-pkg-resources console script.""" + + if os.name == 'nt': + self.skipTest('Windows support is passthrough') + + stdout, _, return_code = self.run_setup( + 'install_scripts', '--install-dir=%s' % self.temp_dir) + + self.useFixture( + fixtures.EnvironmentVariable('PYTHONPATH', '.')) + + self.check_script_install(stdout) + + def test_console_script_develop(self): + """Test that we develop a non-pkg-resources console script.""" + + if os.name == 'nt': + self.skipTest('Windows support is passthrough') + + self.useFixture( + fixtures.EnvironmentVariable( + 'PYTHONPATH', ".:%s" % self.temp_dir)) + + stdout, _, return_code = self.run_setup( + 'develop', '--install-dir=%s' % self.temp_dir) + + self.check_script_install(stdout) + + +class TestGitSDist(base.BaseTestCase): + + def setUp(self): + super(TestGitSDist, self).setUp() + + stdout, _, return_code = self._run_cmd('git', ('init',)) + if return_code: + self.skipTest("git not installed") + + stdout, _, return_code = self._run_cmd('git', ('add', '.')) + stdout, _, return_code = self._run_cmd( + 'git', ('commit', '-m', 'Turn this into a git repo')) + + stdout, _, return_code = self.run_setup('sdist', '--formats=gztar') + + def test_sdist_git_extra_files(self): + """Test that extra files found in git are correctly added.""" + # There can be only one + tf_path = glob.glob(os.path.join('dist', '*.tar.gz'))[0] + tf = tarfile.open(tf_path) + names = ['/'.join(p.split('/')[1:]) for p in tf.getnames()] + + self.assertIn('git-extra-file.txt', names) diff --git a/libs/pbr/tests/test_files.py b/libs/pbr/tests/test_files.py new file mode 100644 index 00000000..e60b6ca7 --- /dev/null +++ b/libs/pbr/tests/test_files.py @@ -0,0 +1,78 @@ +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from __future__ import print_function + +import os + +import fixtures + +from pbr.hooks import files +from pbr.tests import base + + +class FilesConfigTest(base.BaseTestCase): + + def setUp(self): + super(FilesConfigTest, self).setUp() + + pkg_fixture = fixtures.PythonPackage( + "fake_package", [ + ("fake_module.py", b""), + ("other_fake_module.py", b""), + ]) + self.useFixture(pkg_fixture) + pkg_etc = os.path.join(pkg_fixture.base, 'etc') + pkg_sub = os.path.join(pkg_etc, 'sub') + subpackage = os.path.join( + pkg_fixture.base, 'fake_package', 'subpackage') + os.makedirs(pkg_sub) + os.makedirs(subpackage) + with open(os.path.join(pkg_etc, "foo"), 'w') as foo_file: + foo_file.write("Foo Data") + with open(os.path.join(pkg_sub, "bar"), 'w') as foo_file: + foo_file.write("Bar Data") + with open(os.path.join(subpackage, "__init__.py"), 'w') as foo_file: + foo_file.write("# empty") + + self.useFixture(base.DiveDir(pkg_fixture.base)) + + def test_implicit_auto_package(self): + config = dict( + files=dict( + ) + ) + files.FilesConfig(config, 'fake_package').run() + self.assertIn('subpackage', config['files']['packages']) + + def test_auto_package(self): + config = dict( + files=dict( + packages='fake_package', + ) + ) + files.FilesConfig(config, 'fake_package').run() + self.assertIn('subpackage', config['files']['packages']) + + def test_data_files_globbing(self): + config = dict( + files=dict( + data_files="\n etc/pbr = etc/*" + ) + ) + files.FilesConfig(config, 'fake_package').run() + self.assertIn( + '\netc/pbr/ = \n etc/foo\netc/pbr/sub = \n etc/sub/bar', + config['files']['data_files']) diff --git a/libs/pbr/tests/test_hooks.py b/libs/pbr/tests/test_hooks.py new file mode 100644 index 00000000..3f747904 --- /dev/null +++ b/libs/pbr/tests/test_hooks.py @@ -0,0 +1,75 @@ +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Copyright (C) 2013 Association of Universities for Research in Astronomy +# (AURA) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# 3. The name of AURA and its representatives may not be used to +# endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED +# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + +import os + +from testtools import matchers +from testtools import skipUnless + +from pbr import testr_command +from pbr.tests import base +from pbr.tests import util + + +class TestHooks(base.BaseTestCase): + def setUp(self): + super(TestHooks, self).setUp() + with util.open_config( + os.path.join(self.package_dir, 'setup.cfg')) as cfg: + cfg.set('global', 'setup-hooks', + 'pbr_testpackage._setup_hooks.test_hook_1\n' + 'pbr_testpackage._setup_hooks.test_hook_2') + + def test_global_setup_hooks(self): + """Test setup_hooks. + + Test that setup_hooks listed in the [global] section of setup.cfg are + executed in order. + """ + + stdout, _, return_code = self.run_setup('egg_info') + assert 'test_hook_1\ntest_hook_2' in stdout + assert return_code == 0 + + @skipUnless(testr_command.have_testr, "testrepository not available") + def test_custom_commands_known(self): + stdout, _, return_code = self.run_setup('--help-commands') + self.assertFalse(return_code) + self.assertThat(stdout, matchers.Contains(" testr ")) diff --git a/libs/pbr/tests/test_integration.py b/libs/pbr/tests/test_integration.py new file mode 100644 index 00000000..8e96f21f --- /dev/null +++ b/libs/pbr/tests/test_integration.py @@ -0,0 +1,269 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os.path +import shlex +import sys + +import fixtures +import testtools +import textwrap + +from pbr.tests import base +from pbr.tests import test_packaging + +PIPFLAGS = shlex.split(os.environ.get('PIPFLAGS', '')) +PIPVERSION = os.environ.get('PIPVERSION', 'pip') +PBRVERSION = os.environ.get('PBRVERSION', 'pbr') +REPODIR = os.environ.get('REPODIR', '') +WHEELHOUSE = os.environ.get('WHEELHOUSE', '') +PIP_CMD = ['-m', 'pip'] + PIPFLAGS + ['install', '-f', WHEELHOUSE] +PROJECTS = shlex.split(os.environ.get('PROJECTS', '')) +PBR_ROOT = os.path.abspath(os.path.join(__file__, '..', '..', '..')) + + +def all_projects(): + if not REPODIR: + return + # Future: make this path parameterisable. + excludes = set(['tempest', 'requirements']) + for name in PROJECTS: + name = name.strip() + short_name = name.split('/')[-1] + try: + with open(os.path.join( + REPODIR, short_name, 'setup.py'), 'rt') as f: + if 'pbr' not in f.read(): + continue + except IOError: + continue + if short_name in excludes: + continue + yield (short_name, dict(name=name, short_name=short_name)) + + +class TestIntegration(base.BaseTestCase): + + scenarios = list(all_projects()) + + def setUp(self): + # Integration tests need a higher default - big repos can be slow to + # clone, particularly under guest load. + env = fixtures.EnvironmentVariable( + 'OS_TEST_TIMEOUT', os.environ.get('OS_TEST_TIMEOUT', '600')) + with env: + super(TestIntegration, self).setUp() + base._config_git() + + @testtools.skipUnless( + os.environ.get('PBR_INTEGRATION', None) == '1', + 'integration tests not enabled') + def test_integration(self): + # Test that we can: + # - run sdist from the repo in a venv + # - install the resulting tarball in a new venv + # - pip install the repo + # - pip install -e the repo + # We don't break these into separate tests because we'd need separate + # source dirs to isolate from side effects of running pip, and the + # overheads of setup would start to beat the benefits of parallelism. + self.useFixture(base.CapturedSubprocess( + 'sync-req', + ['python', 'update.py', os.path.join(REPODIR, self.short_name)], + cwd=os.path.join(REPODIR, 'requirements'))) + self.useFixture(base.CapturedSubprocess( + 'commit-requirements', + 'git diff --quiet || git commit -amrequirements', + cwd=os.path.join(REPODIR, self.short_name), shell=True)) + path = os.path.join( + self.useFixture(fixtures.TempDir()).path, 'project') + self.useFixture(base.CapturedSubprocess( + 'clone', + ['git', 'clone', os.path.join(REPODIR, self.short_name), path])) + venv = self.useFixture( + test_packaging.Venv('sdist', + modules=['pip', 'wheel', PBRVERSION], + pip_cmd=PIP_CMD)) + python = venv.python + self.useFixture(base.CapturedSubprocess( + 'sdist', [python, 'setup.py', 'sdist'], cwd=path)) + venv = self.useFixture( + test_packaging.Venv('tarball', + modules=['pip', 'wheel', PBRVERSION], + pip_cmd=PIP_CMD)) + python = venv.python + filename = os.path.join( + path, 'dist', os.listdir(os.path.join(path, 'dist'))[0]) + self.useFixture(base.CapturedSubprocess( + 'tarball', [python] + PIP_CMD + [filename])) + venv = self.useFixture( + test_packaging.Venv('install-git', + modules=['pip', 'wheel', PBRVERSION], + pip_cmd=PIP_CMD)) + root = venv.path + python = venv.python + self.useFixture(base.CapturedSubprocess( + 'install-git', [python] + PIP_CMD + ['git+file://' + path])) + if self.short_name == 'nova': + found = False + for _, _, filenames in os.walk(root): + if 'migrate.cfg' in filenames: + found = True + self.assertTrue(found) + venv = self.useFixture( + test_packaging.Venv('install-e', + modules=['pip', 'wheel', PBRVERSION], + pip_cmd=PIP_CMD)) + root = venv.path + python = venv.python + self.useFixture(base.CapturedSubprocess( + 'install-e', [python] + PIP_CMD + ['-e', path])) + + +class TestInstallWithoutPbr(base.BaseTestCase): + + @testtools.skipUnless( + os.environ.get('PBR_INTEGRATION', None) == '1', + 'integration tests not enabled') + def test_install_without_pbr(self): + # Test easy-install of a thing that depends on a thing using pbr + tempdir = self.useFixture(fixtures.TempDir()).path + # A directory containing sdists of the things we're going to depend on + # in using-package. + dist_dir = os.path.join(tempdir, 'distdir') + os.mkdir(dist_dir) + self._run_cmd(sys.executable, ('setup.py', 'sdist', '-d', dist_dir), + allow_fail=False, cwd=PBR_ROOT) + # testpkg - this requires a pbr-using package + test_pkg_dir = os.path.join(tempdir, 'testpkg') + os.mkdir(test_pkg_dir) + pkgs = { + 'pkgTest': { + 'setup.py': textwrap.dedent("""\ + #!/usr/bin/env python + import setuptools + setuptools.setup( + name = 'pkgTest', + tests_require = ['pkgReq'], + test_suite='pkgReq' + ) + """), + 'setup.cfg': textwrap.dedent("""\ + [easy_install] + find_links = %s + """ % dist_dir)}, + 'pkgReq': { + 'requirements.txt': textwrap.dedent("""\ + pbr + """), + 'pkgReq/__init__.py': textwrap.dedent("""\ + print("FakeTest loaded and ran") + """)}, + } + pkg_dirs = self.useFixture( + test_packaging.CreatePackages(pkgs)).package_dirs + test_pkg_dir = pkg_dirs['pkgTest'] + req_pkg_dir = pkg_dirs['pkgReq'] + + self._run_cmd(sys.executable, ('setup.py', 'sdist', '-d', dist_dir), + allow_fail=False, cwd=req_pkg_dir) + # A venv to test within + venv = self.useFixture(test_packaging.Venv('nopbr', ['pip', 'wheel'])) + python = venv.python + # Run the depending script + self.useFixture(base.CapturedSubprocess( + 'nopbr', [python] + ['setup.py', 'test'], cwd=test_pkg_dir)) + + +class TestMarkersPip(base.BaseTestCase): + + scenarios = [ + ('pip-1.5', {'modules': ['pip>=1.5,<1.6']}), + ('pip-6.0', {'modules': ['pip>=6.0,<6.1']}), + ('pip-latest', {'modules': ['pip']}), + ('setuptools-EL7', {'modules': ['pip==1.4.1', 'setuptools==0.9.8']}), + ('setuptools-Trusty', {'modules': ['pip==1.5', 'setuptools==2.2']}), + ('setuptools-minimum', {'modules': ['pip==1.5', 'setuptools==0.7.2']}), + ] + + @testtools.skipUnless( + os.environ.get('PBR_INTEGRATION', None) == '1', + 'integration tests not enabled') + def test_pip_versions(self): + pkgs = { + 'test_markers': + {'requirements.txt': textwrap.dedent("""\ + pkg_a; python_version=='1.2' + pkg_b; python_version!='1.2' + """)}, + 'pkg_a': {}, + 'pkg_b': {}, + } + pkg_dirs = self.useFixture( + test_packaging.CreatePackages(pkgs)).package_dirs + temp_dir = self.useFixture(fixtures.TempDir()).path + repo_dir = os.path.join(temp_dir, 'repo') + venv = self.useFixture(test_packaging.Venv('markers')) + bin_python = venv.python + os.mkdir(repo_dir) + for module in self.modules: + self._run_cmd( + bin_python, + ['-m', 'pip', 'install', '--upgrade', module], + cwd=venv.path, allow_fail=False) + for pkg in pkg_dirs: + self._run_cmd( + bin_python, ['setup.py', 'sdist', '-d', repo_dir], + cwd=pkg_dirs[pkg], allow_fail=False) + self._run_cmd( + bin_python, + ['-m', 'pip', 'install', '--no-index', '-f', repo_dir, + 'test_markers'], + cwd=venv.path, allow_fail=False) + self.assertIn('pkg-b', self._run_cmd( + bin_python, ['-m', 'pip', 'freeze'], cwd=venv.path, + allow_fail=False)[0]) + + +class TestLTSSupport(base.BaseTestCase): + + # These versions come from the versions installed from the 'virtualenv' + # command from the 'python-virtualenv' package. + scenarios = [ + ('EL7', {'modules': ['pip==1.4.1', 'setuptools==0.9.8'], + 'py3support': True}), # And EPEL6 + ('Trusty', {'modules': ['pip==1.5', 'setuptools==2.2'], + 'py3support': True}), + ('Jessie', {'modules': ['pip==1.5.6', 'setuptools==5.5.1'], + 'py3support': True}), + # Wheezy has pip1.1, which cannot be called with '-m pip' + # So we'll use a different version of pip here. + ('WheezyPrecise', {'modules': ['pip==1.4.1', 'setuptools==0.6c11'], + 'py3support': False}) + ] + + @testtools.skipUnless( + os.environ.get('PBR_INTEGRATION', None) == '1', + 'integration tests not enabled') + def test_lts_venv_default_versions(self): + if (sys.version_info[0] == 3 and not self.py3support): + self.skipTest('This combination will not install with py3, ' + 'skipping test') + venv = self.useFixture( + test_packaging.Venv('setuptools', modules=self.modules)) + bin_python = venv.python + pbr = 'file://%s#egg=pbr' % PBR_ROOT + # Installing PBR is a reasonable indication that we are not broken on + # this particular combination of setuptools and pip. + self._run_cmd(bin_python, ['-m', 'pip', 'install', pbr], + cwd=venv.path, allow_fail=False) diff --git a/libs/pbr/tests/test_packaging.py b/libs/pbr/tests/test_packaging.py new file mode 100644 index 00000000..d19dd05b --- /dev/null +++ b/libs/pbr/tests/test_packaging.py @@ -0,0 +1,923 @@ +# Copyright (c) 2013 New Dream Network, LLC (DreamHost) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Copyright (C) 2013 Association of Universities for Research in Astronomy +# (AURA) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# 3. The name of AURA and its representatives may not be used to +# endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED +# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + +import email +import email.errors +import imp +import os +import re +import sysconfig +import tempfile +import textwrap + +import fixtures +import mock +import pkg_resources +import six +import testscenarios +import testtools +from testtools import matchers +import virtualenv +from wheel import wheelfile + +from pbr import git +from pbr import packaging +from pbr.tests import base + + +PBR_ROOT = os.path.abspath(os.path.join(__file__, '..', '..', '..')) + + +class TestRepo(fixtures.Fixture): + """A git repo for testing with. + + Use of TempHomeDir with this fixture is strongly recommended as due to the + lack of config --local in older gits, it will write to the users global + configuration without TempHomeDir. + """ + + def __init__(self, basedir): + super(TestRepo, self).__init__() + self._basedir = basedir + + def setUp(self): + super(TestRepo, self).setUp() + base._run_cmd(['git', 'init', '.'], self._basedir) + base._config_git() + base._run_cmd(['git', 'add', '.'], self._basedir) + + def commit(self, message_content='test commit'): + files = len(os.listdir(self._basedir)) + path = self._basedir + '/%d' % files + open(path, 'wt').close() + base._run_cmd(['git', 'add', path], self._basedir) + base._run_cmd(['git', 'commit', '-m', message_content], self._basedir) + + def uncommit(self): + base._run_cmd(['git', 'reset', '--hard', 'HEAD^'], self._basedir) + + def tag(self, version): + base._run_cmd( + ['git', 'tag', '-sm', 'test tag', version], self._basedir) + + +class GPGKeyFixture(fixtures.Fixture): + """Creates a GPG key for testing. + + It's recommended that this be used in concert with a unique home + directory. + """ + + def setUp(self): + super(GPGKeyFixture, self).setUp() + tempdir = self.useFixture(fixtures.TempDir()) + gnupg_version_re = re.compile('^gpg\s.*\s([\d+])\.([\d+])\.([\d+])') + gnupg_version = base._run_cmd(['gpg', '--version'], tempdir.path) + for line in gnupg_version[0].split('\n'): + gnupg_version = gnupg_version_re.match(line) + if gnupg_version: + gnupg_version = (int(gnupg_version.group(1)), + int(gnupg_version.group(2)), + int(gnupg_version.group(3))) + break + else: + if gnupg_version is None: + gnupg_version = (0, 0, 0) + config_file = tempdir.path + '/key-config' + f = open(config_file, 'wt') + try: + if gnupg_version[0] == 2 and gnupg_version[1] >= 1: + f.write(""" + %no-protection + %transient-key + """) + f.write(""" + %no-ask-passphrase + Key-Type: RSA + Name-Real: Example Key + Name-Comment: N/A + Name-Email: example@example.com + Expire-Date: 2d + Preferences: (setpref) + %commit + """) + finally: + f.close() + # Note that --quick-random (--debug-quick-random in GnuPG 2.x) + # does not have a corresponding preferences file setting and + # must be passed explicitly on the command line instead + if gnupg_version[0] == 1: + gnupg_random = '--quick-random' + elif gnupg_version[0] >= 2: + gnupg_random = '--debug-quick-random' + else: + gnupg_random = '' + base._run_cmd( + ['gpg', '--gen-key', '--batch', gnupg_random, config_file], + tempdir.path) + + +class Venv(fixtures.Fixture): + """Create a virtual environment for testing with. + + :attr path: The path to the environment root. + :attr python: The path to the python binary in the environment. + """ + + def __init__(self, reason, modules=(), pip_cmd=None): + """Create a Venv fixture. + + :param reason: A human readable string to bake into the venv + file path to aid diagnostics in the case of failures. + :param modules: A list of modules to install, defaults to latest + pip, wheel, and the working copy of PBR. + :attr pip_cmd: A list to override the default pip_cmd passed to + python for installing base packages. + """ + self._reason = reason + if modules == (): + pbr = 'file://%s#egg=pbr' % PBR_ROOT + modules = ['pip', 'wheel', pbr] + self.modules = modules + if pip_cmd is None: + self.pip_cmd = ['-m', 'pip', 'install'] + else: + self.pip_cmd = pip_cmd + + def _setUp(self): + path = self.useFixture(fixtures.TempDir()).path + virtualenv.create_environment(path, clear=True) + python = os.path.join(path, 'bin', 'python') + command = [python] + self.pip_cmd + ['-U'] + if self.modules and len(self.modules) > 0: + command.extend(self.modules) + self.useFixture(base.CapturedSubprocess( + 'mkvenv-' + self._reason, command)) + self.addCleanup(delattr, self, 'path') + self.addCleanup(delattr, self, 'python') + self.path = path + self.python = python + return path, python + + +class CreatePackages(fixtures.Fixture): + """Creates packages from dict with defaults + + :param package_dirs: A dict of package name to directory strings + {'pkg_a': '/tmp/path/to/tmp/pkg_a', 'pkg_b': '/tmp/path/to/tmp/pkg_b'} + """ + + defaults = { + 'setup.py': textwrap.dedent(six.u("""\ + #!/usr/bin/env python + import setuptools + setuptools.setup( + setup_requires=['pbr'], + pbr=True, + ) + """)), + 'setup.cfg': textwrap.dedent(six.u("""\ + [metadata] + name = {pkg_name} + """)) + } + + def __init__(self, packages): + """Creates packages from dict with defaults + + :param packages: a dict where the keys are the package name and a + value that is a second dict that may be empty, containing keys of + filenames and a string value of the contents. + {'package-a': {'requirements.txt': 'string', 'setup.cfg': 'string'} + """ + self.packages = packages + + def _writeFile(self, directory, file_name, contents): + path = os.path.abspath(os.path.join(directory, file_name)) + path_dir = os.path.dirname(path) + if not os.path.exists(path_dir): + if path_dir.startswith(directory): + os.makedirs(path_dir) + else: + raise ValueError + with open(path, 'wt') as f: + f.write(contents) + + def _setUp(self): + tmpdir = self.useFixture(fixtures.TempDir()).path + package_dirs = {} + for pkg_name in self.packages: + pkg_path = os.path.join(tmpdir, pkg_name) + package_dirs[pkg_name] = pkg_path + os.mkdir(pkg_path) + for cf in ['setup.py', 'setup.cfg']: + if cf in self.packages[pkg_name]: + contents = self.packages[pkg_name].pop(cf) + else: + contents = self.defaults[cf].format(pkg_name=pkg_name) + self._writeFile(pkg_path, cf, contents) + + for cf in self.packages[pkg_name]: + self._writeFile(pkg_path, cf, self.packages[pkg_name][cf]) + self.useFixture(TestRepo(pkg_path)).commit() + self.addCleanup(delattr, self, 'package_dirs') + self.package_dirs = package_dirs + return package_dirs + + +class TestPackagingInGitRepoWithCommit(base.BaseTestCase): + + scenarios = [ + ('preversioned', dict(preversioned=True)), + ('postversioned', dict(preversioned=False)), + ] + + def setUp(self): + super(TestPackagingInGitRepoWithCommit, self).setUp() + self.repo = self.useFixture(TestRepo(self.package_dir)) + self.repo.commit() + + def test_authors(self): + self.run_setup('sdist', allow_fail=False) + # One commit, something should be in the authors list + with open(os.path.join(self.package_dir, 'AUTHORS'), 'r') as f: + body = f.read() + self.assertNotEqual(body, '') + + def test_changelog(self): + self.run_setup('sdist', allow_fail=False) + with open(os.path.join(self.package_dir, 'ChangeLog'), 'r') as f: + body = f.read() + # One commit, something should be in the ChangeLog list + self.assertNotEqual(body, '') + + def test_changelog_handles_astrisk(self): + self.repo.commit(message_content="Allow *.openstack.org to work") + self.run_setup('sdist', allow_fail=False) + with open(os.path.join(self.package_dir, 'ChangeLog'), 'r') as f: + body = f.read() + self.assertIn('\*', body) + + def test_changelog_handles_dead_links_in_commit(self): + self.repo.commit(message_content="See os_ for to_do about qemu_.") + self.run_setup('sdist', allow_fail=False) + with open(os.path.join(self.package_dir, 'ChangeLog'), 'r') as f: + body = f.read() + self.assertIn('os\_', body) + self.assertIn('to\_do', body) + self.assertIn('qemu\_', body) + + def test_changelog_handles_backticks(self): + self.repo.commit(message_content="Allow `openstack.org` to `work") + self.run_setup('sdist', allow_fail=False) + with open(os.path.join(self.package_dir, 'ChangeLog'), 'r') as f: + body = f.read() + self.assertIn('\`', body) + + def test_manifest_exclude_honoured(self): + self.run_setup('sdist', allow_fail=False) + with open(os.path.join( + self.package_dir, + 'pbr_testpackage.egg-info/SOURCES.txt'), 'r') as f: + body = f.read() + self.assertThat( + body, matchers.Not(matchers.Contains('pbr_testpackage/extra.py'))) + self.assertThat(body, matchers.Contains('pbr_testpackage/__init__.py')) + + def test_install_writes_changelog(self): + stdout, _, _ = self.run_setup( + 'install', '--root', self.temp_dir + 'installed', + allow_fail=False) + self.expectThat(stdout, matchers.Contains('Generating ChangeLog')) + + +class TestExtrafileInstallation(base.BaseTestCase): + def test_install_glob(self): + stdout, _, _ = self.run_setup( + 'install', '--root', self.temp_dir + 'installed', + allow_fail=False) + self.expectThat( + stdout, matchers.Contains('copying data_files/a.txt')) + self.expectThat( + stdout, matchers.Contains('copying data_files/b.txt')) + + +class TestPackagingInGitRepoWithoutCommit(base.BaseTestCase): + + def setUp(self): + super(TestPackagingInGitRepoWithoutCommit, self).setUp() + self.useFixture(TestRepo(self.package_dir)) + self.run_setup('sdist', allow_fail=False) + + def test_authors(self): + # No commits, no authors in list + with open(os.path.join(self.package_dir, 'AUTHORS'), 'r') as f: + body = f.read() + self.assertEqual('\n', body) + + def test_changelog(self): + # No commits, nothing should be in the ChangeLog list + with open(os.path.join(self.package_dir, 'ChangeLog'), 'r') as f: + body = f.read() + self.assertEqual('CHANGES\n=======\n\n', body) + + +class TestPackagingWheels(base.BaseTestCase): + + def setUp(self): + super(TestPackagingWheels, self).setUp() + self.useFixture(TestRepo(self.package_dir)) + # Build the wheel + self.run_setup('bdist_wheel', allow_fail=False) + # Slowly construct the path to the generated whl + dist_dir = os.path.join(self.package_dir, 'dist') + relative_wheel_filename = os.listdir(dist_dir)[0] + absolute_wheel_filename = os.path.join( + dist_dir, relative_wheel_filename) + wheel_file = wheelfile.WheelFile(absolute_wheel_filename) + wheel_name = wheel_file.parsed_filename.group('namever') + # Create a directory path to unpack the wheel to + self.extracted_wheel_dir = os.path.join(dist_dir, wheel_name) + # Extract the wheel contents to the directory we just created + wheel_file.extractall(self.extracted_wheel_dir) + wheel_file.close() + + def test_data_directory_has_wsgi_scripts(self): + # Build the path to the scripts directory + scripts_dir = os.path.join( + self.extracted_wheel_dir, 'pbr_testpackage-0.0.data/scripts') + self.assertTrue(os.path.exists(scripts_dir)) + scripts = os.listdir(scripts_dir) + + self.assertIn('pbr_test_wsgi', scripts) + self.assertIn('pbr_test_wsgi_with_class', scripts) + self.assertNotIn('pbr_test_cmd', scripts) + self.assertNotIn('pbr_test_cmd_with_class', scripts) + + def test_generates_c_extensions(self): + built_package_dir = os.path.join( + self.extracted_wheel_dir, 'pbr_testpackage') + static_object_filename = 'testext.so' + soabi = get_soabi() + if soabi: + static_object_filename = 'testext.{0}.so'.format(soabi) + static_object_path = os.path.join( + built_package_dir, static_object_filename) + + self.assertTrue(os.path.exists(built_package_dir)) + self.assertTrue(os.path.exists(static_object_path)) + + +class TestPackagingHelpers(testtools.TestCase): + + def test_generate_script(self): + group = 'console_scripts' + entry_point = pkg_resources.EntryPoint( + name='test-ep', + module_name='pbr.packaging', + attrs=('LocalInstallScripts',)) + header = '#!/usr/bin/env fake-header\n' + template = ('%(group)s %(module_name)s %(import_target)s ' + '%(invoke_target)s') + + generated_script = packaging.generate_script( + group, entry_point, header, template) + + expected_script = ( + '#!/usr/bin/env fake-header\nconsole_scripts pbr.packaging ' + 'LocalInstallScripts LocalInstallScripts' + ) + self.assertEqual(expected_script, generated_script) + + def test_generate_script_validates_expectations(self): + group = 'console_scripts' + entry_point = pkg_resources.EntryPoint( + name='test-ep', + module_name='pbr.packaging') + header = '#!/usr/bin/env fake-header\n' + template = ('%(group)s %(module_name)s %(import_target)s ' + '%(invoke_target)s') + self.assertRaises( + ValueError, packaging.generate_script, group, entry_point, header, + template) + + entry_point = pkg_resources.EntryPoint( + name='test-ep', + module_name='pbr.packaging', + attrs=('attr1', 'attr2', 'attr3')) + self.assertRaises( + ValueError, packaging.generate_script, group, entry_point, header, + template) + + +class TestPackagingInPlainDirectory(base.BaseTestCase): + + def setUp(self): + super(TestPackagingInPlainDirectory, self).setUp() + + def test_authors(self): + self.run_setup('sdist', allow_fail=False) + # Not a git repo, no AUTHORS file created + filename = os.path.join(self.package_dir, 'AUTHORS') + self.assertFalse(os.path.exists(filename)) + + def test_changelog(self): + self.run_setup('sdist', allow_fail=False) + # Not a git repo, no ChangeLog created + filename = os.path.join(self.package_dir, 'ChangeLog') + self.assertFalse(os.path.exists(filename)) + + def test_install_no_ChangeLog(self): + stdout, _, _ = self.run_setup( + 'install', '--root', self.temp_dir + 'installed', + allow_fail=False) + self.expectThat( + stdout, matchers.Not(matchers.Contains('Generating ChangeLog'))) + + +class TestPresenceOfGit(base.BaseTestCase): + + def testGitIsInstalled(self): + with mock.patch.object(git, + '_run_shell_command') as _command: + _command.return_value = 'git version 1.8.4.1' + self.assertEqual(True, git._git_is_installed()) + + def testGitIsNotInstalled(self): + with mock.patch.object(git, + '_run_shell_command') as _command: + _command.side_effect = OSError + self.assertEqual(False, git._git_is_installed()) + + +class ParseRequirementsTest(base.BaseTestCase): + + def test_empty_requirements(self): + actual = packaging.parse_requirements([]) + self.assertEqual([], actual) + + def test_default_requirements(self): + """Ensure default files used if no files provided.""" + tempdir = tempfile.mkdtemp() + requirements = os.path.join(tempdir, 'requirements.txt') + with open(requirements, 'w') as f: + f.write('pbr') + # the defaults are relative to where pbr is called from so we need to + # override them. This is OK, however, as we want to validate that + # defaults are used - not what those defaults are + with mock.patch.object(packaging, 'REQUIREMENTS_FILES', ( + requirements,)): + result = packaging.parse_requirements() + self.assertEqual(['pbr'], result) + + def test_override_with_env(self): + """Ensure environment variable used if no files provided.""" + _, tmp_file = tempfile.mkstemp(prefix='openstack', suffix='.setup') + with open(tmp_file, 'w') as fh: + fh.write("foo\nbar") + self.useFixture( + fixtures.EnvironmentVariable('PBR_REQUIREMENTS_FILES', tmp_file)) + self.assertEqual(['foo', 'bar'], + packaging.parse_requirements()) + + def test_override_with_env_multiple_files(self): + _, tmp_file = tempfile.mkstemp(prefix='openstack', suffix='.setup') + with open(tmp_file, 'w') as fh: + fh.write("foo\nbar") + self.useFixture( + fixtures.EnvironmentVariable('PBR_REQUIREMENTS_FILES', + "no-such-file," + tmp_file)) + self.assertEqual(['foo', 'bar'], + packaging.parse_requirements()) + + def test_index_present(self): + tempdir = tempfile.mkdtemp() + requirements = os.path.join(tempdir, 'requirements.txt') + with open(requirements, 'w') as f: + f.write('-i https://myindex.local') + f.write(' --index-url https://myindex.local') + f.write(' --extra-index-url https://myindex.local') + result = packaging.parse_requirements([requirements]) + self.assertEqual([], result) + + def test_nested_requirements(self): + tempdir = tempfile.mkdtemp() + requirements = os.path.join(tempdir, 'requirements.txt') + nested = os.path.join(tempdir, 'nested.txt') + with open(requirements, 'w') as f: + f.write('-r ' + nested) + with open(nested, 'w') as f: + f.write('pbr') + result = packaging.parse_requirements([requirements]) + self.assertEqual(['pbr'], result) + + +class ParseRequirementsTestScenarios(base.BaseTestCase): + + versioned_scenarios = [ + ('non-versioned', {'versioned': False, 'expected': ['bar']}), + ('versioned', {'versioned': True, 'expected': ['bar>=1.2.3']}) + ] + + subdirectory_scenarios = [ + ('non-subdirectory', {'has_subdirectory': False}), + ('has-subdirectory', {'has_subdirectory': True}) + ] + + scenarios = [ + ('normal', {'url': "foo\nbar", 'expected': ['foo', 'bar']}), + ('normal_with_comments', { + 'url': "# this is a comment\nfoo\n# and another one\nbar", + 'expected': ['foo', 'bar']}), + ('removes_index_lines', {'url': '-f foobar', 'expected': []}), + ] + + scenarios = scenarios + testscenarios.multiply_scenarios([ + ('ssh_egg_url', {'url': 'git+ssh://foo.com/zipball#egg=bar'}), + ('git_https_egg_url', {'url': 'git+https://foo.com/zipball#egg=bar'}), + ('http_egg_url', {'url': 'https://foo.com/zipball#egg=bar'}), + ], versioned_scenarios, subdirectory_scenarios) + + scenarios = scenarios + testscenarios.multiply_scenarios( + [ + ('git_egg_url', + {'url': 'git://foo.com/zipball#egg=bar', 'name': 'bar'}) + ], [ + ('non-editable', {'editable': False}), + ('editable', {'editable': True}), + ], + versioned_scenarios, subdirectory_scenarios) + + def test_parse_requirements(self): + tmp_file = tempfile.NamedTemporaryFile() + req_string = self.url + if hasattr(self, 'editable') and self.editable: + req_string = ("-e %s" % req_string) + if hasattr(self, 'versioned') and self.versioned: + req_string = ("%s-1.2.3" % req_string) + if hasattr(self, 'has_subdirectory') and self.has_subdirectory: + req_string = ("%s&subdirectory=baz" % req_string) + with open(tmp_file.name, 'w') as fh: + fh.write(req_string) + self.assertEqual(self.expected, + packaging.parse_requirements([tmp_file.name])) + + +class ParseDependencyLinksTest(base.BaseTestCase): + + def setUp(self): + super(ParseDependencyLinksTest, self).setUp() + _, self.tmp_file = tempfile.mkstemp(prefix="openstack", + suffix=".setup") + + def test_parse_dependency_normal(self): + with open(self.tmp_file, "w") as fh: + fh.write("http://test.com\n") + self.assertEqual( + ["http://test.com"], + packaging.parse_dependency_links([self.tmp_file])) + + def test_parse_dependency_with_git_egg_url(self): + with open(self.tmp_file, "w") as fh: + fh.write("-e git://foo.com/zipball#egg=bar") + self.assertEqual( + ["git://foo.com/zipball#egg=bar"], + packaging.parse_dependency_links([self.tmp_file])) + + +class TestVersions(base.BaseTestCase): + + scenarios = [ + ('preversioned', dict(preversioned=True)), + ('postversioned', dict(preversioned=False)), + ] + + def setUp(self): + super(TestVersions, self).setUp() + self.repo = self.useFixture(TestRepo(self.package_dir)) + self.useFixture(GPGKeyFixture()) + self.useFixture(base.DiveDir(self.package_dir)) + + def test_email_parsing_errors_are_handled(self): + mocked_open = mock.mock_open() + with mock.patch('pbr.packaging.open', mocked_open): + with mock.patch('email.message_from_file') as message_from_file: + message_from_file.side_effect = [ + email.errors.MessageError('Test'), + {'Name': 'pbr_testpackage'}] + version = packaging._get_version_from_pkg_metadata( + 'pbr_testpackage') + + self.assertTrue(message_from_file.called) + self.assertIsNone(version) + + def test_capitalized_headers(self): + self.repo.commit() + self.repo.tag('1.2.3') + self.repo.commit('Sem-Ver: api-break') + version = packaging._get_version_from_git() + self.assertThat(version, matchers.StartsWith('2.0.0.dev1')) + + def test_capitalized_headers_partial(self): + self.repo.commit() + self.repo.tag('1.2.3') + self.repo.commit('Sem-ver: api-break') + version = packaging._get_version_from_git() + self.assertThat(version, matchers.StartsWith('2.0.0.dev1')) + + def test_tagged_version_has_tag_version(self): + self.repo.commit() + self.repo.tag('1.2.3') + version = packaging._get_version_from_git('1.2.3') + self.assertEqual('1.2.3', version) + + def test_non_canonical_tagged_version_bump(self): + self.repo.commit() + self.repo.tag('1.4') + self.repo.commit('Sem-Ver: api-break') + version = packaging._get_version_from_git() + self.assertThat(version, matchers.StartsWith('2.0.0.dev1')) + + def test_untagged_version_has_dev_version_postversion(self): + self.repo.commit() + self.repo.tag('1.2.3') + self.repo.commit() + version = packaging._get_version_from_git() + self.assertThat(version, matchers.StartsWith('1.2.4.dev1')) + + def test_untagged_pre_release_has_pre_dev_version_postversion(self): + self.repo.commit() + self.repo.tag('1.2.3.0a1') + self.repo.commit() + version = packaging._get_version_from_git() + self.assertThat(version, matchers.StartsWith('1.2.3.0a2.dev1')) + + def test_untagged_version_minor_bump(self): + self.repo.commit() + self.repo.tag('1.2.3') + self.repo.commit('sem-ver: deprecation') + version = packaging._get_version_from_git() + self.assertThat(version, matchers.StartsWith('1.3.0.dev1')) + + def test_untagged_version_major_bump(self): + self.repo.commit() + self.repo.tag('1.2.3') + self.repo.commit('sem-ver: api-break') + version = packaging._get_version_from_git() + self.assertThat(version, matchers.StartsWith('2.0.0.dev1')) + + def test_untagged_version_has_dev_version_preversion(self): + self.repo.commit() + self.repo.tag('1.2.3') + self.repo.commit() + version = packaging._get_version_from_git('1.2.5') + self.assertThat(version, matchers.StartsWith('1.2.5.dev1')) + + def test_untagged_version_after_pre_has_dev_version_preversion(self): + self.repo.commit() + self.repo.tag('1.2.3.0a1') + self.repo.commit() + version = packaging._get_version_from_git('1.2.5') + self.assertThat(version, matchers.StartsWith('1.2.5.dev1')) + + def test_untagged_version_after_rc_has_dev_version_preversion(self): + self.repo.commit() + self.repo.tag('1.2.3.0a1') + self.repo.commit() + version = packaging._get_version_from_git('1.2.3') + self.assertThat(version, matchers.StartsWith('1.2.3.0a2.dev1')) + + def test_preversion_too_low_simple(self): + # That is, the target version is either already released or not high + # enough for the semver requirements given api breaks etc. + self.repo.commit() + self.repo.tag('1.2.3') + self.repo.commit() + # Note that we can't target 1.2.3 anymore - with 1.2.3 released we + # need to be working on 1.2.4. + err = self.assertRaises( + ValueError, packaging._get_version_from_git, '1.2.3') + self.assertThat(err.args[0], matchers.StartsWith('git history')) + + def test_preversion_too_low_semver_headers(self): + # That is, the target version is either already released or not high + # enough for the semver requirements given api breaks etc. + self.repo.commit() + self.repo.tag('1.2.3') + self.repo.commit('sem-ver: feature') + # Note that we can't target 1.2.4, the feature header means we need + # to be working on 1.3.0 or above. + err = self.assertRaises( + ValueError, packaging._get_version_from_git, '1.2.4') + self.assertThat(err.args[0], matchers.StartsWith('git history')) + + def test_get_kwargs_corner_cases(self): + # No tags: + git_dir = self.repo._basedir + '/.git' + get_kwargs = lambda tag: packaging._get_increment_kwargs(git_dir, tag) + + def _check_combinations(tag): + self.repo.commit() + self.assertEqual(dict(), get_kwargs(tag)) + self.repo.commit('sem-ver: bugfix') + self.assertEqual(dict(), get_kwargs(tag)) + self.repo.commit('sem-ver: feature') + self.assertEqual(dict(minor=True), get_kwargs(tag)) + self.repo.uncommit() + self.repo.commit('sem-ver: deprecation') + self.assertEqual(dict(minor=True), get_kwargs(tag)) + self.repo.uncommit() + self.repo.commit('sem-ver: api-break') + self.assertEqual(dict(major=True), get_kwargs(tag)) + self.repo.commit('sem-ver: deprecation') + self.assertEqual(dict(major=True, minor=True), get_kwargs(tag)) + _check_combinations('') + self.repo.tag('1.2.3') + _check_combinations('1.2.3') + + def test_invalid_tag_ignored(self): + # Fix for bug 1356784 - we treated any tag as a version, not just those + # that are valid versions. + self.repo.commit() + self.repo.tag('1') + self.repo.commit() + # when the tree is tagged and its wrong: + self.repo.tag('badver') + version = packaging._get_version_from_git() + self.assertThat(version, matchers.StartsWith('1.0.1.dev1')) + # When the tree isn't tagged, we also fall through. + self.repo.commit() + version = packaging._get_version_from_git() + self.assertThat(version, matchers.StartsWith('1.0.1.dev2')) + # We don't fall through x.y versions + self.repo.commit() + self.repo.tag('1.2') + self.repo.commit() + self.repo.tag('badver2') + version = packaging._get_version_from_git() + self.assertThat(version, matchers.StartsWith('1.2.1.dev1')) + # Or x.y.z versions + self.repo.commit() + self.repo.tag('1.2.3') + self.repo.commit() + self.repo.tag('badver3') + version = packaging._get_version_from_git() + self.assertThat(version, matchers.StartsWith('1.2.4.dev1')) + # Or alpha/beta/pre versions + self.repo.commit() + self.repo.tag('1.2.4.0a1') + self.repo.commit() + self.repo.tag('badver4') + version = packaging._get_version_from_git() + self.assertThat(version, matchers.StartsWith('1.2.4.0a2.dev1')) + # Non-release related tags are ignored. + self.repo.commit() + self.repo.tag('2') + self.repo.commit() + self.repo.tag('non-release-tag/2014.12.16-1') + version = packaging._get_version_from_git() + self.assertThat(version, matchers.StartsWith('2.0.1.dev1')) + + def test_valid_tag_honoured(self): + # Fix for bug 1370608 - we converted any target into a 'dev version' + # even if there was a distance of 0 - indicating that we were on the + # tag itself. + self.repo.commit() + self.repo.tag('1.3.0.0a1') + version = packaging._get_version_from_git() + self.assertEqual('1.3.0.0a1', version) + + def test_skip_write_git_changelog(self): + # Fix for bug 1467440 + self.repo.commit() + self.repo.tag('1.2.3') + os.environ['SKIP_WRITE_GIT_CHANGELOG'] = '1' + version = packaging._get_version_from_git('1.2.3') + self.assertEqual('1.2.3', version) + + def tearDown(self): + super(TestVersions, self).tearDown() + os.environ.pop('SKIP_WRITE_GIT_CHANGELOG', None) + + +class TestRequirementParsing(base.BaseTestCase): + + def test_requirement_parsing(self): + pkgs = { + 'test_reqparse': + { + 'requirements.txt': textwrap.dedent("""\ + bar + quux<1.0; python_version=='2.6' + requests-aws>=0.1.4 # BSD License (3 clause) + Routes>=1.12.3,!=2.0,!=2.1;python_version=='2.7' + requests-kerberos>=0.6;python_version=='2.7' # MIT + """), + 'setup.cfg': textwrap.dedent("""\ + [metadata] + name = test_reqparse + + [extras] + test = + foo + baz>3.2 :python_version=='2.7' # MIT + bar>3.3 :python_version=='2.7' # MIT # Apache + """)}, + } + pkg_dirs = self.useFixture(CreatePackages(pkgs)).package_dirs + pkg_dir = pkg_dirs['test_reqparse'] + # pkg_resources.split_sections uses None as the title of an + # anonymous section instead of the empty string. Weird. + expected_requirements = { + None: ['bar', 'requests-aws>=0.1.4'], + ":(python_version=='2.6')": ['quux<1.0'], + ":(python_version=='2.7')": ['Routes!=2.0,!=2.1,>=1.12.3', + 'requests-kerberos>=0.6'], + 'test': ['foo'], + "test:(python_version=='2.7')": ['baz>3.2', 'bar>3.3'] + } + venv = self.useFixture(Venv('reqParse')) + bin_python = venv.python + # Two things are tested by this + # 1) pbr properly parses markers from requiremnts.txt and setup.cfg + # 2) bdist_wheel causes pbr to not evaluate markers + self._run_cmd(bin_python, ('setup.py', 'bdist_wheel'), + allow_fail=False, cwd=pkg_dir) + egg_info = os.path.join(pkg_dir, 'test_reqparse.egg-info') + + requires_txt = os.path.join(egg_info, 'requires.txt') + with open(requires_txt, 'rt') as requires: + generated_requirements = dict( + pkg_resources.split_sections(requires)) + + # NOTE(dhellmann): We have to spell out the comparison because + # the rendering for version specifiers in a range is not + # consistent across versions of setuptools. + + for section, expected in expected_requirements.items(): + exp_parsed = [ + pkg_resources.Requirement.parse(s) + for s in expected + ] + gen_parsed = [ + pkg_resources.Requirement.parse(s) + for s in generated_requirements[section] + ] + self.assertEqual(exp_parsed, gen_parsed) + + +def get_soabi(): + soabi = None + try: + soabi = sysconfig.get_config_var('SOABI') + arch = sysconfig.get_config_var('MULTIARCH') + except IOError: + pass + if soabi and arch and 'pypy' in sysconfig.get_scheme_names(): + soabi = '%s-%s' % (soabi, arch) + if soabi is None and 'pypy' in sysconfig.get_scheme_names(): + # NOTE(sigmavirus24): PyPy only added support for the SOABI config var + # to sysconfig in 2015. That was well after 2.2.1 was published in the + # Ubuntu 14.04 archive. + for suffix, _, _ in imp.get_suffixes(): + if suffix.startswith('.pypy') and suffix.endswith('.so'): + soabi = suffix.split('.')[1] + break + return soabi diff --git a/libs/pbr/tests/test_pbr_json.py b/libs/pbr/tests/test_pbr_json.py new file mode 100644 index 00000000..f0669713 --- /dev/null +++ b/libs/pbr/tests/test_pbr_json.py @@ -0,0 +1,30 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock + +from pbr import pbr_json +from pbr.tests import base + + +class TestJsonContent(base.BaseTestCase): + @mock.patch('pbr.git._run_git_functions', return_value=True) + @mock.patch('pbr.git.get_git_short_sha', return_value="123456") + @mock.patch('pbr.git.get_is_release', return_value=True) + def test_content(self, mock_get_is, mock_get_git, mock_run): + cmd = mock.Mock() + pbr_json.write_pbr_json(cmd, "basename", "pbr.json") + cmd.write_file.assert_called_once_with( + 'pbr', + 'pbr.json', + '{"git_version": "123456", "is_release": true}' + ) diff --git a/libs/pbr/tests/test_setup.py b/libs/pbr/tests/test_setup.py new file mode 100644 index 00000000..85d40ebf --- /dev/null +++ b/libs/pbr/tests/test_setup.py @@ -0,0 +1,445 @@ +# Copyright (c) 2011 OpenStack Foundation +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from __future__ import print_function + +import os + +try: + import cStringIO as io + BytesIO = io.StringIO +except ImportError: + import io + BytesIO = io.BytesIO + +import fixtures + +from pbr import git +from pbr import options +from pbr import packaging +from pbr.tests import base + + +class SkipFileWrites(base.BaseTestCase): + + scenarios = [ + ('changelog_option_true', + dict(option_key='skip_changelog', option_value='True', + env_key='SKIP_WRITE_GIT_CHANGELOG', env_value=None, + pkg_func=git.write_git_changelog, filename='ChangeLog')), + ('changelog_option_false', + dict(option_key='skip_changelog', option_value='False', + env_key='SKIP_WRITE_GIT_CHANGELOG', env_value=None, + pkg_func=git.write_git_changelog, filename='ChangeLog')), + ('changelog_env_true', + dict(option_key='skip_changelog', option_value='False', + env_key='SKIP_WRITE_GIT_CHANGELOG', env_value='True', + pkg_func=git.write_git_changelog, filename='ChangeLog')), + ('changelog_both_true', + dict(option_key='skip_changelog', option_value='True', + env_key='SKIP_WRITE_GIT_CHANGELOG', env_value='True', + pkg_func=git.write_git_changelog, filename='ChangeLog')), + ('authors_option_true', + dict(option_key='skip_authors', option_value='True', + env_key='SKIP_GENERATE_AUTHORS', env_value=None, + pkg_func=git.generate_authors, filename='AUTHORS')), + ('authors_option_false', + dict(option_key='skip_authors', option_value='False', + env_key='SKIP_GENERATE_AUTHORS', env_value=None, + pkg_func=git.generate_authors, filename='AUTHORS')), + ('authors_env_true', + dict(option_key='skip_authors', option_value='False', + env_key='SKIP_GENERATE_AUTHORS', env_value='True', + pkg_func=git.generate_authors, filename='AUTHORS')), + ('authors_both_true', + dict(option_key='skip_authors', option_value='True', + env_key='SKIP_GENERATE_AUTHORS', env_value='True', + pkg_func=git.generate_authors, filename='AUTHORS')), + ] + + def setUp(self): + super(SkipFileWrites, self).setUp() + self.temp_path = self.useFixture(fixtures.TempDir()).path + self.root_dir = os.path.abspath(os.path.curdir) + self.git_dir = os.path.join(self.root_dir, ".git") + if not os.path.exists(self.git_dir): + self.skipTest("%s is missing; skipping git-related checks" + % self.git_dir) + return + self.filename = os.path.join(self.temp_path, self.filename) + self.option_dict = dict() + if self.option_key is not None: + self.option_dict[self.option_key] = ('setup.cfg', + self.option_value) + self.useFixture( + fixtures.EnvironmentVariable(self.env_key, self.env_value)) + + def test_skip(self): + self.pkg_func(git_dir=self.git_dir, + dest_dir=self.temp_path, + option_dict=self.option_dict) + self.assertEqual( + not os.path.exists(self.filename), + (self.option_value.lower() in options.TRUE_VALUES + or self.env_value is not None)) + +_changelog_content = """7780758\x00Break parser\x00 (tag: refs/tags/1_foo.1) +04316fe\x00Make python\x00 (refs/heads/review/monty_taylor/27519) +378261a\x00Add an integration test script.\x00 +3c373ac\x00Merge "Lib\x00 (HEAD, tag: refs/tags/2013.2.rc2, tag: refs/tags/2013.2, refs/heads/mile-proposed) +182feb3\x00Fix pip invocation for old versions of pip.\x00 (tag: refs/tags/0.5.17) +fa4f46e\x00Remove explicit depend on distribute.\x00 (tag: refs/tags/0.5.16) +d1c53dd\x00Use pip instead of easy_install for installation.\x00 +a793ea1\x00Merge "Skip git-checkout related tests when .git is missing"\x00 +6c27ce7\x00Skip git-checkout related tests when .git is missing\x00 +451e513\x00Bug fix: create_stack() fails when waiting\x00 +4c8cfe4\x00Improve test coverage: network delete API\x00 (tag: refs/tags/(evil)) +d7e6167\x00Bug fix: Fix pass thru filtering in list_networks\x00 (tag: refs/tags/ev()il) +c47ec15\x00Consider 'in-use' a non-pending volume for caching\x00 (tag: refs/tags/ev)il) +8696fbd\x00Improve test coverage: private extension API\x00 (tag: refs/tags/ev(il) +f0440f8\x00Improve test coverage: hypervisor list\x00 (tag: refs/tags/e(vi)l) +04984a5\x00Refactor hooks file.\x00 (HEAD, tag: 0.6.7,b, tag: refs/tags/(12), refs/heads/master) +a65e8ee\x00Remove jinja pin.\x00 (tag: refs/tags/0.5.14, tag: refs/tags/0.5.13) +""" # noqa + + +def _make_old_git_changelog_format(line): + """Convert post-1.8.1 git log format to pre-1.8.1 git log format""" + + if not line.strip(): + return line + sha, msg, refname = line.split('\x00') + refname = refname.replace('tag: ', '') + return '\x00'.join((sha, msg, refname)) + +_old_git_changelog_content = '\n'.join( + _make_old_git_changelog_format(line) + for line in _changelog_content.split('\n')) + + +class GitLogsTest(base.BaseTestCase): + + scenarios = [ + ('pre1.8.3', {'changelog': _old_git_changelog_content}), + ('post1.8.3', {'changelog': _changelog_content}), + ] + + def setUp(self): + super(GitLogsTest, self).setUp() + self.temp_path = self.useFixture(fixtures.TempDir()).path + self.root_dir = os.path.abspath(os.path.curdir) + self.git_dir = os.path.join(self.root_dir, ".git") + self.useFixture( + fixtures.EnvironmentVariable('SKIP_GENERATE_AUTHORS')) + self.useFixture( + fixtures.EnvironmentVariable('SKIP_WRITE_GIT_CHANGELOG')) + + def test_write_git_changelog(self): + self.useFixture(fixtures.FakePopen(lambda _: { + "stdout": BytesIO(self.changelog.encode('utf-8')) + })) + + git.write_git_changelog(git_dir=self.git_dir, + dest_dir=self.temp_path) + + with open(os.path.join(self.temp_path, "ChangeLog"), "r") as ch_fh: + changelog_contents = ch_fh.read() + self.assertIn("2013.2", changelog_contents) + self.assertIn("0.5.17", changelog_contents) + self.assertIn("------", changelog_contents) + self.assertIn("Refactor hooks file", changelog_contents) + self.assertIn( + "Bug fix: create\_stack() fails when waiting", + changelog_contents) + self.assertNotIn("Refactor hooks file.", changelog_contents) + self.assertNotIn("182feb3", changelog_contents) + self.assertNotIn("review/monty_taylor/27519", changelog_contents) + self.assertNotIn("0.5.13", changelog_contents) + self.assertNotIn("0.6.7", changelog_contents) + self.assertNotIn("12", changelog_contents) + self.assertNotIn("(evil)", changelog_contents) + self.assertNotIn("ev()il", changelog_contents) + self.assertNotIn("ev(il", changelog_contents) + self.assertNotIn("ev)il", changelog_contents) + self.assertNotIn("e(vi)l", changelog_contents) + self.assertNotIn('Merge "', changelog_contents) + self.assertNotIn('1\_foo.1', changelog_contents) + + def test_generate_authors(self): + author_old = u"Foo Foo " + author_new = u"Bar Bar " + co_author = u"Foo Bar " + co_author_by = u"Co-authored-by: " + co_author + + git_log_cmd = ( + "git --git-dir=%s log --format=%%aN <%%aE>" + % self.git_dir) + git_co_log_cmd = ("git --git-dir=%s log" % self.git_dir) + git_top_level = "git rev-parse --show-toplevel" + cmd_map = { + git_log_cmd: author_new, + git_co_log_cmd: co_author_by, + git_top_level: self.root_dir, + } + + exist_files = [self.git_dir, + os.path.join(self.temp_path, "AUTHORS.in")] + self.useFixture(fixtures.MonkeyPatch( + "os.path.exists", + lambda path: os.path.abspath(path) in exist_files)) + + def _fake_run_shell_command(cmd, **kwargs): + return cmd_map[" ".join(cmd)] + + self.useFixture(fixtures.MonkeyPatch( + "pbr.git._run_shell_command", + _fake_run_shell_command)) + + with open(os.path.join(self.temp_path, "AUTHORS.in"), "w") as auth_fh: + auth_fh.write("%s\n" % author_old) + + git.generate_authors(git_dir=self.git_dir, + dest_dir=self.temp_path) + + with open(os.path.join(self.temp_path, "AUTHORS"), "r") as auth_fh: + authors = auth_fh.read() + self.assertTrue(author_old in authors) + self.assertTrue(author_new in authors) + self.assertTrue(co_author in authors) + + +class _SphinxConfig(object): + man_pages = ['foo'] + + +class BaseSphinxTest(base.BaseTestCase): + + def setUp(self): + super(BaseSphinxTest, self).setUp() + + # setup_command requires the Sphinx instance to have some + # attributes that aren't set normally with the way we use the + # class (because we replace the constructor). Add default + # values directly to the class definition. + import sphinx.application + sphinx.application.Sphinx.messagelog = [] + sphinx.application.Sphinx.statuscode = 0 + + self.useFixture(fixtures.MonkeyPatch( + "sphinx.application.Sphinx.__init__", lambda *a, **kw: None)) + self.useFixture(fixtures.MonkeyPatch( + "sphinx.application.Sphinx.build", lambda *a, **kw: None)) + self.useFixture(fixtures.MonkeyPatch( + "sphinx.application.Sphinx.config", _SphinxConfig)) + self.useFixture(fixtures.MonkeyPatch( + "sphinx.config.Config.init_values", lambda *a: None)) + self.useFixture(fixtures.MonkeyPatch( + "sphinx.config.Config.__init__", lambda *a: None)) + from distutils import dist + self.distr = dist.Distribution() + self.distr.packages = ("fake_package",) + self.distr.command_options["build_sphinx"] = { + "source_dir": ["a", "."]} + pkg_fixture = fixtures.PythonPackage( + "fake_package", [("fake_module.py", b""), + ("another_fake_module_for_testing.py", b""), + ("fake_private_module.py", b"")]) + self.useFixture(pkg_fixture) + self.useFixture(base.DiveDir(pkg_fixture.base)) + self.distr.command_options["pbr"] = {} + if hasattr(self, "excludes"): + self.distr.command_options["pbr"]["autodoc_exclude_modules"] = ( + 'setup.cfg', + "fake_package.fake_private_module\n" + "fake_package.another_fake_*\n" + "fake_package.unknown_module") + if hasattr(self, 'has_opt') and self.has_opt: + options = self.distr.command_options["pbr"] + options["autodoc_index_modules"] = ('setup.cfg', self.autodoc) + + +class BuildSphinxTest(BaseSphinxTest): + + scenarios = [ + ('true_autodoc_caps', + dict(has_opt=True, autodoc='True', has_autodoc=True)), + ('true_autodoc_caps_with_excludes', + dict(has_opt=True, autodoc='True', has_autodoc=True, + excludes="fake_package.fake_private_module\n" + "fake_package.another_fake_*\n" + "fake_package.unknown_module")), + ('true_autodoc_lower', + dict(has_opt=True, autodoc='true', has_autodoc=True)), + ('false_autodoc', + dict(has_opt=True, autodoc='False', has_autodoc=False)), + ('no_autodoc', + dict(has_opt=False, autodoc='False', has_autodoc=False)), + ] + + def test_build_doc(self): + build_doc = packaging.LocalBuildDoc(self.distr) + build_doc.run() + + self.assertTrue( + os.path.exists("api/autoindex.rst") == self.has_autodoc) + self.assertTrue( + os.path.exists( + "api/fake_package.fake_module.rst") == self.has_autodoc) + if not self.has_autodoc or hasattr(self, "excludes"): + assertion = self.assertFalse + else: + assertion = self.assertTrue + assertion( + os.path.exists( + "api/fake_package.fake_private_module.rst")) + assertion( + os.path.exists( + "api/fake_package.another_fake_module_for_testing.rst")) + + def test_builders_config(self): + build_doc = packaging.LocalBuildDoc(self.distr) + build_doc.finalize_options() + + self.assertEqual(1, len(build_doc.builders)) + self.assertIn('html', build_doc.builders) + + build_doc = packaging.LocalBuildDoc(self.distr) + build_doc.builders = '' + build_doc.finalize_options() + + self.assertEqual('', build_doc.builders) + + build_doc = packaging.LocalBuildDoc(self.distr) + build_doc.builders = 'man' + build_doc.finalize_options() + + self.assertEqual(1, len(build_doc.builders)) + self.assertIn('man', build_doc.builders) + + build_doc = packaging.LocalBuildDoc(self.distr) + build_doc.builders = 'html,man,doctest' + build_doc.finalize_options() + + self.assertIn('html', build_doc.builders) + self.assertIn('man', build_doc.builders) + self.assertIn('doctest', build_doc.builders) + + def test_cmd_builder_override(self): + + if self.has_opt: + self.distr.command_options["pbr"] = { + "autodoc_index_modules": ('setup.cfg', self.autodoc) + } + + self.distr.command_options["build_sphinx"]["builder"] = ( + "command line", "non-existing-builder") + + build_doc = packaging.LocalBuildDoc(self.distr) + self.assertNotIn('non-existing-builder', build_doc.builders) + self.assertIn('html', build_doc.builders) + + # process command line options which should override config + build_doc.finalize_options() + + self.assertIn('non-existing-builder', build_doc.builders) + self.assertNotIn('html', build_doc.builders) + + def test_cmd_builder_override_multiple_builders(self): + + if self.has_opt: + self.distr.command_options["pbr"] = { + "autodoc_index_modules": ('setup.cfg', self.autodoc) + } + + self.distr.command_options["build_sphinx"]["builder"] = ( + "command line", "builder1,builder2") + + build_doc = packaging.LocalBuildDoc(self.distr) + build_doc.finalize_options() + + self.assertEqual(["builder1", "builder2"], build_doc.builders) + + +class APIAutoDocTest(base.BaseTestCase): + + def setUp(self): + super(APIAutoDocTest, self).setUp() + + # setup_command requires the Sphinx instance to have some + # attributes that aren't set normally with the way we use the + # class (because we replace the constructor). Add default + # values directly to the class definition. + import sphinx.application + sphinx.application.Sphinx.messagelog = [] + sphinx.application.Sphinx.statuscode = 0 + + self.useFixture(fixtures.MonkeyPatch( + "sphinx.application.Sphinx.__init__", lambda *a, **kw: None)) + self.useFixture(fixtures.MonkeyPatch( + "sphinx.application.Sphinx.build", lambda *a, **kw: None)) + self.useFixture(fixtures.MonkeyPatch( + "sphinx.application.Sphinx.config", _SphinxConfig)) + self.useFixture(fixtures.MonkeyPatch( + "sphinx.config.Config.init_values", lambda *a: None)) + self.useFixture(fixtures.MonkeyPatch( + "sphinx.config.Config.__init__", lambda *a: None)) + from distutils import dist + self.distr = dist.Distribution() + self.distr.packages = ("fake_package",) + self.distr.command_options["build_sphinx"] = { + "source_dir": ["a", "."]} + self.sphinx_options = self.distr.command_options["build_sphinx"] + pkg_fixture = fixtures.PythonPackage( + "fake_package", [("fake_module.py", b""), + ("another_fake_module_for_testing.py", b""), + ("fake_private_module.py", b"")]) + self.useFixture(pkg_fixture) + self.useFixture(base.DiveDir(pkg_fixture.base)) + self.pbr_options = self.distr.command_options.setdefault('pbr', {}) + self.pbr_options["autodoc_index_modules"] = ('setup.cfg', 'True') + + def test_default_api_build_dir(self): + build_doc = packaging.LocalBuildDoc(self.distr) + build_doc.run() + + print('PBR OPTIONS:', self.pbr_options) + print('DISTR OPTIONS:', self.distr.command_options) + + self.assertTrue(os.path.exists("api/autoindex.rst")) + self.assertTrue(os.path.exists("api/fake_package.fake_module.rst")) + self.assertTrue( + os.path.exists( + "api/fake_package.fake_private_module.rst")) + self.assertTrue( + os.path.exists( + "api/fake_package.another_fake_module_for_testing.rst")) + + def test_different_api_build_dir(self): + # Options have to come out of the settings dict as a tuple + # showing the source and the value. + self.pbr_options['api_doc_dir'] = (None, 'contributor/api') + build_doc = packaging.LocalBuildDoc(self.distr) + build_doc.run() + + print('PBR OPTIONS:', self.pbr_options) + print('DISTR OPTIONS:', self.distr.command_options) + + self.assertTrue(os.path.exists("contributor/api/autoindex.rst")) + self.assertTrue( + os.path.exists("contributor/api/fake_package.fake_module.rst")) + self.assertTrue( + os.path.exists( + "contributor/api/fake_package.fake_private_module.rst")) diff --git a/libs/pbr/tests/test_util.py b/libs/pbr/tests/test_util.py new file mode 100644 index 00000000..370a7dee --- /dev/null +++ b/libs/pbr/tests/test_util.py @@ -0,0 +1,91 @@ +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. (HP) +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import io +import textwrap + +import six +from six.moves import configparser +import sys + +from pbr.tests import base +from pbr import util + + +class TestExtrasRequireParsingScenarios(base.BaseTestCase): + + scenarios = [ + ('simple_extras', { + 'config_text': """ + [extras] + first = + foo + bar==1.0 + second = + baz>=3.2 + foo + """, + 'expected_extra_requires': { + 'first': ['foo', 'bar==1.0'], + 'second': ['baz>=3.2', 'foo'], + 'test': ['requests-mock'], + "test:(python_version=='2.6')": ['ordereddict'], + } + }), + ('with_markers', { + 'config_text': """ + [extras] + test = + foo:python_version=='2.6' + bar + baz<1.6 :python_version=='2.6' + zaz :python_version>'1.0' + """, + 'expected_extra_requires': { + "test:(python_version=='2.6')": ['foo', 'baz<1.6'], + "test": ['bar', 'zaz']}}), + ('no_extras', { + 'config_text': """ + [metadata] + long_description = foo + """, + 'expected_extra_requires': + {} + })] + + def config_from_ini(self, ini): + config = {} + if sys.version_info >= (3, 2): + parser = configparser.ConfigParser() + else: + parser = configparser.SafeConfigParser() + ini = textwrap.dedent(six.u(ini)) + parser.readfp(io.StringIO(ini)) + for section in parser.sections(): + config[section] = dict(parser.items(section)) + return config + + def test_extras_parsing(self): + config = self.config_from_ini(self.config_text) + kwargs = util.setup_cfg_to_setup_kwargs(config) + + self.assertEqual(self.expected_extra_requires, + kwargs['extras_require']) + + +class TestInvalidMarkers(base.BaseTestCase): + + def test_invalid_marker_raises_error(self): + config = {'extras': {'test': "foo :bad_marker>'1.0'"}} + self.assertRaises(SyntaxError, util.setup_cfg_to_setup_kwargs, config) diff --git a/libs/pbr/tests/test_version.py b/libs/pbr/tests/test_version.py new file mode 100644 index 00000000..d861d572 --- /dev/null +++ b/libs/pbr/tests/test_version.py @@ -0,0 +1,311 @@ +# Copyright 2012 Red Hat, Inc. +# Copyright 2012-2013 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import itertools + +from testtools import matchers + +from pbr.tests import base +from pbr import version + + +from_pip_string = version.SemanticVersion.from_pip_string + + +class TestSemanticVersion(base.BaseTestCase): + + def test_ordering(self): + ordered_versions = [ + "1.2.3.dev6", + "1.2.3.dev7", + "1.2.3.a4.dev12", + "1.2.3.a4.dev13", + "1.2.3.a4", + "1.2.3.a5.dev1", + "1.2.3.a5", + "1.2.3.b3.dev1", + "1.2.3.b3", + "1.2.3.rc2.dev1", + "1.2.3.rc2", + "1.2.3.rc3.dev1", + "1.2.3", + "1.2.4", + "1.3.3", + "2.2.3", + ] + for v in ordered_versions: + sv = version.SemanticVersion.from_pip_string(v) + self.expectThat(sv, matchers.Equals(sv)) + for left, right in itertools.combinations(ordered_versions, 2): + l_pos = ordered_versions.index(left) + r_pos = ordered_versions.index(right) + if l_pos < r_pos: + m1 = matchers.LessThan + m2 = matchers.GreaterThan + else: + m1 = matchers.GreaterThan + m2 = matchers.LessThan + left_sv = version.SemanticVersion.from_pip_string(left) + right_sv = version.SemanticVersion.from_pip_string(right) + self.expectThat(left_sv, m1(right_sv)) + self.expectThat(right_sv, m2(left_sv)) + + def test_from_pip_string_legacy_alpha(self): + expected = version.SemanticVersion( + 1, 2, 0, prerelease_type='rc', prerelease=1) + parsed = from_pip_string('1.2.0rc1') + self.assertEqual(expected, parsed) + + def test_from_pip_string_legacy_postN(self): + # When pbr trunk was incompatible with PEP-440, a stable release was + # made that used postN versions to represent developer builds. As + # we expect only to be parsing versions of our own, we map those + # into dev builds of the next version. + expected = version.SemanticVersion(1, 2, 4, dev_count=5) + parsed = from_pip_string('1.2.3.post5') + self.expectThat(expected, matchers.Equals(parsed)) + expected = version.SemanticVersion(1, 2, 3, 'a', 5, dev_count=6) + parsed = from_pip_string('1.2.3.0a4.post6') + self.expectThat(expected, matchers.Equals(parsed)) + # We can't define a mapping for .postN.devM, so it should raise. + self.expectThat( + lambda: from_pip_string('1.2.3.post5.dev6'), + matchers.raises(ValueError)) + + def test_from_pip_string_v_version(self): + parsed = from_pip_string('v1.2.3') + expected = version.SemanticVersion(1, 2, 3) + self.expectThat(expected, matchers.Equals(parsed)) + + expected = version.SemanticVersion(1, 2, 3, 'a', 5, dev_count=6) + parsed = from_pip_string('V1.2.3.0a4.post6') + self.expectThat(expected, matchers.Equals(parsed)) + + self.expectThat( + lambda: from_pip_string('x1.2.3'), + matchers.raises(ValueError)) + + def test_from_pip_string_legacy_nonzero_lead_in(self): + # reported in bug 1361251 + expected = version.SemanticVersion( + 0, 0, 1, prerelease_type='a', prerelease=2) + parsed = from_pip_string('0.0.1a2') + self.assertEqual(expected, parsed) + + def test_from_pip_string_legacy_short_nonzero_lead_in(self): + expected = version.SemanticVersion( + 0, 1, 0, prerelease_type='a', prerelease=2) + parsed = from_pip_string('0.1a2') + self.assertEqual(expected, parsed) + + def test_from_pip_string_legacy_no_0_prerelease(self): + expected = version.SemanticVersion( + 2, 1, 0, prerelease_type='rc', prerelease=1) + parsed = from_pip_string('2.1.0.rc1') + self.assertEqual(expected, parsed) + + def test_from_pip_string_legacy_no_0_prerelease_2(self): + expected = version.SemanticVersion( + 2, 0, 0, prerelease_type='rc', prerelease=1) + parsed = from_pip_string('2.0.0.rc1') + self.assertEqual(expected, parsed) + + def test_from_pip_string_legacy_non_440_beta(self): + expected = version.SemanticVersion( + 2014, 2, prerelease_type='b', prerelease=2) + parsed = from_pip_string('2014.2.b2') + self.assertEqual(expected, parsed) + + def test_from_pip_string_pure_git_hash(self): + self.assertRaises(ValueError, from_pip_string, '6eed5ae') + + def test_from_pip_string_non_digit_start(self): + self.assertRaises(ValueError, from_pip_string, + 'non-release-tag/2014.12.16-1') + + def test_final_version(self): + semver = version.SemanticVersion(1, 2, 3) + self.assertEqual((1, 2, 3, 'final', 0), semver.version_tuple()) + self.assertEqual("1.2.3", semver.brief_string()) + self.assertEqual("1.2.3", semver.debian_string()) + self.assertEqual("1.2.3", semver.release_string()) + self.assertEqual("1.2.3", semver.rpm_string()) + self.assertEqual(semver, from_pip_string("1.2.3")) + + def test_parsing_short_forms(self): + semver = version.SemanticVersion(1, 0, 0) + self.assertEqual(semver, from_pip_string("1")) + self.assertEqual(semver, from_pip_string("1.0")) + self.assertEqual(semver, from_pip_string("1.0.0")) + + def test_dev_version(self): + semver = version.SemanticVersion(1, 2, 4, dev_count=5) + self.assertEqual((1, 2, 4, 'dev', 4), semver.version_tuple()) + self.assertEqual("1.2.4", semver.brief_string()) + self.assertEqual("1.2.4~dev5", semver.debian_string()) + self.assertEqual("1.2.4.dev5", semver.release_string()) + self.assertEqual("1.2.3.dev5", semver.rpm_string()) + self.assertEqual(semver, from_pip_string("1.2.4.dev5")) + + def test_dev_no_git_version(self): + semver = version.SemanticVersion(1, 2, 4, dev_count=5) + self.assertEqual((1, 2, 4, 'dev', 4), semver.version_tuple()) + self.assertEqual("1.2.4", semver.brief_string()) + self.assertEqual("1.2.4~dev5", semver.debian_string()) + self.assertEqual("1.2.4.dev5", semver.release_string()) + self.assertEqual("1.2.3.dev5", semver.rpm_string()) + self.assertEqual(semver, from_pip_string("1.2.4.dev5")) + + def test_dev_zero_version(self): + semver = version.SemanticVersion(1, 2, 0, dev_count=5) + self.assertEqual((1, 2, 0, 'dev', 4), semver.version_tuple()) + self.assertEqual("1.2.0", semver.brief_string()) + self.assertEqual("1.2.0~dev5", semver.debian_string()) + self.assertEqual("1.2.0.dev5", semver.release_string()) + self.assertEqual("1.1.9999.dev5", semver.rpm_string()) + self.assertEqual(semver, from_pip_string("1.2.0.dev5")) + + def test_alpha_dev_version(self): + semver = version.SemanticVersion(1, 2, 4, 'a', 1, 12) + self.assertEqual((1, 2, 4, 'alphadev', 12), semver.version_tuple()) + self.assertEqual("1.2.4", semver.brief_string()) + self.assertEqual("1.2.4~a1.dev12", semver.debian_string()) + self.assertEqual("1.2.4.0a1.dev12", semver.release_string()) + self.assertEqual("1.2.3.a1.dev12", semver.rpm_string()) + self.assertEqual(semver, from_pip_string("1.2.4.0a1.dev12")) + + def test_alpha_version(self): + semver = version.SemanticVersion(1, 2, 4, 'a', 1) + self.assertEqual((1, 2, 4, 'alpha', 1), semver.version_tuple()) + self.assertEqual("1.2.4", semver.brief_string()) + self.assertEqual("1.2.4~a1", semver.debian_string()) + self.assertEqual("1.2.4.0a1", semver.release_string()) + self.assertEqual("1.2.3.a1", semver.rpm_string()) + self.assertEqual(semver, from_pip_string("1.2.4.0a1")) + + def test_alpha_zero_version(self): + semver = version.SemanticVersion(1, 2, 0, 'a', 1) + self.assertEqual((1, 2, 0, 'alpha', 1), semver.version_tuple()) + self.assertEqual("1.2.0", semver.brief_string()) + self.assertEqual("1.2.0~a1", semver.debian_string()) + self.assertEqual("1.2.0.0a1", semver.release_string()) + self.assertEqual("1.1.9999.a1", semver.rpm_string()) + self.assertEqual(semver, from_pip_string("1.2.0.0a1")) + + def test_alpha_major_zero_version(self): + semver = version.SemanticVersion(1, 0, 0, 'a', 1) + self.assertEqual((1, 0, 0, 'alpha', 1), semver.version_tuple()) + self.assertEqual("1.0.0", semver.brief_string()) + self.assertEqual("1.0.0~a1", semver.debian_string()) + self.assertEqual("1.0.0.0a1", semver.release_string()) + self.assertEqual("0.9999.9999.a1", semver.rpm_string()) + self.assertEqual(semver, from_pip_string("1.0.0.0a1")) + + def test_alpha_default_version(self): + semver = version.SemanticVersion(1, 2, 4, 'a') + self.assertEqual((1, 2, 4, 'alpha', 0), semver.version_tuple()) + self.assertEqual("1.2.4", semver.brief_string()) + self.assertEqual("1.2.4~a0", semver.debian_string()) + self.assertEqual("1.2.4.0a0", semver.release_string()) + self.assertEqual("1.2.3.a0", semver.rpm_string()) + self.assertEqual(semver, from_pip_string("1.2.4.0a0")) + + def test_beta_dev_version(self): + semver = version.SemanticVersion(1, 2, 4, 'b', 1, 12) + self.assertEqual((1, 2, 4, 'betadev', 12), semver.version_tuple()) + self.assertEqual("1.2.4", semver.brief_string()) + self.assertEqual("1.2.4~b1.dev12", semver.debian_string()) + self.assertEqual("1.2.4.0b1.dev12", semver.release_string()) + self.assertEqual("1.2.3.b1.dev12", semver.rpm_string()) + self.assertEqual(semver, from_pip_string("1.2.4.0b1.dev12")) + + def test_beta_version(self): + semver = version.SemanticVersion(1, 2, 4, 'b', 1) + self.assertEqual((1, 2, 4, 'beta', 1), semver.version_tuple()) + self.assertEqual("1.2.4", semver.brief_string()) + self.assertEqual("1.2.4~b1", semver.debian_string()) + self.assertEqual("1.2.4.0b1", semver.release_string()) + self.assertEqual("1.2.3.b1", semver.rpm_string()) + self.assertEqual(semver, from_pip_string("1.2.4.0b1")) + + def test_decrement_nonrelease(self): + # The prior version of any non-release is a release + semver = version.SemanticVersion(1, 2, 4, 'b', 1) + self.assertEqual( + version.SemanticVersion(1, 2, 3), semver.decrement()) + + def test_decrement_nonrelease_zero(self): + # We set an arbitrary max version of 9999 when decrementing versions + # - this is part of handling rpm support. + semver = version.SemanticVersion(1, 0, 0) + self.assertEqual( + version.SemanticVersion(0, 9999, 9999), semver.decrement()) + + def test_decrement_release(self): + # The next patch version of a release version requires a change to the + # patch level. + semver = version.SemanticVersion(2, 2, 5) + self.assertEqual( + version.SemanticVersion(2, 2, 4), semver.decrement()) + + def test_increment_nonrelease(self): + # The next patch version of a non-release version is another + # non-release version as the next release doesn't need to be + # incremented. + semver = version.SemanticVersion(1, 2, 4, 'b', 1) + self.assertEqual( + version.SemanticVersion(1, 2, 4, 'b', 2), semver.increment()) + # Major and minor increments however need to bump things. + self.assertEqual( + version.SemanticVersion(1, 3, 0), semver.increment(minor=True)) + self.assertEqual( + version.SemanticVersion(2, 0, 0), semver.increment(major=True)) + + def test_increment_release(self): + # The next patch version of a release version requires a change to the + # patch level. + semver = version.SemanticVersion(1, 2, 5) + self.assertEqual( + version.SemanticVersion(1, 2, 6), semver.increment()) + self.assertEqual( + version.SemanticVersion(1, 3, 0), semver.increment(minor=True)) + self.assertEqual( + version.SemanticVersion(2, 0, 0), semver.increment(major=True)) + + def test_rc_dev_version(self): + semver = version.SemanticVersion(1, 2, 4, 'rc', 1, 12) + self.assertEqual((1, 2, 4, 'candidatedev', 12), semver.version_tuple()) + self.assertEqual("1.2.4", semver.brief_string()) + self.assertEqual("1.2.4~rc1.dev12", semver.debian_string()) + self.assertEqual("1.2.4.0rc1.dev12", semver.release_string()) + self.assertEqual("1.2.3.rc1.dev12", semver.rpm_string()) + self.assertEqual(semver, from_pip_string("1.2.4.0rc1.dev12")) + + def test_rc_version(self): + semver = version.SemanticVersion(1, 2, 4, 'rc', 1) + self.assertEqual((1, 2, 4, 'candidate', 1), semver.version_tuple()) + self.assertEqual("1.2.4", semver.brief_string()) + self.assertEqual("1.2.4~rc1", semver.debian_string()) + self.assertEqual("1.2.4.0rc1", semver.release_string()) + self.assertEqual("1.2.3.rc1", semver.rpm_string()) + self.assertEqual(semver, from_pip_string("1.2.4.0rc1")) + + def test_to_dev(self): + self.assertEqual( + version.SemanticVersion(1, 2, 3, dev_count=1), + version.SemanticVersion(1, 2, 3).to_dev(1)) + self.assertEqual( + version.SemanticVersion(1, 2, 3, 'rc', 1, dev_count=1), + version.SemanticVersion(1, 2, 3, 'rc', 1).to_dev(1)) diff --git a/libs/pbr/tests/test_wsgi.py b/libs/pbr/tests/test_wsgi.py new file mode 100644 index 00000000..f840610d --- /dev/null +++ b/libs/pbr/tests/test_wsgi.py @@ -0,0 +1,163 @@ +# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. (HP) +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os +import re +import subprocess +import sys +try: + # python 2 + from urllib2 import urlopen +except ImportError: + # python 3 + from urllib.request import urlopen + +from pbr.tests import base + + +class TestWsgiScripts(base.BaseTestCase): + + cmd_names = ('pbr_test_wsgi', 'pbr_test_wsgi_with_class') + + def _get_path(self): + if os.path.isdir("%s/lib64" % self.temp_dir): + path = "%s/lib64" % self.temp_dir + elif os.path.isdir("%s/lib" % self.temp_dir): + path = "%s/lib" % self.temp_dir + elif os.path.isdir("%s/site-packages" % self.temp_dir): + return ".:%s/site-packages" % self.temp_dir + else: + raise Exception("Could not determine path for test") + return ".:%s/python%s.%s/site-packages" % ( + path, + sys.version_info[0], + sys.version_info[1]) + + def test_wsgi_script_install(self): + """Test that we install a non-pkg-resources wsgi script.""" + if os.name == 'nt': + self.skipTest('Windows support is passthrough') + + stdout, _, return_code = self.run_setup( + 'install', '--prefix=%s' % self.temp_dir) + + self._check_wsgi_install_content(stdout) + + def test_wsgi_script_run(self): + """Test that we install a runnable wsgi script. + + This test actually attempts to start and interact with the + wsgi script in question to demonstrate that it's a working + wsgi script using simple server. + + """ + if os.name == 'nt': + self.skipTest('Windows support is passthrough') + + stdout, _, return_code = self.run_setup( + 'install', '--prefix=%s' % self.temp_dir) + + self._check_wsgi_install_content(stdout) + + # Live test run the scripts and see that they respond to wsgi + # requests. + for cmd_name in self.cmd_names: + self._test_wsgi(cmd_name, b'Hello World') + + def _test_wsgi(self, cmd_name, output, extra_args=None): + cmd = os.path.join(self.temp_dir, 'bin', cmd_name) + print("Running %s -p 0" % cmd) + popen_cmd = [cmd, '-p', '0'] + if extra_args: + popen_cmd.extend(extra_args) + + env = {'PYTHONPATH': self._get_path()} + + p = subprocess.Popen(popen_cmd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, cwd=self.temp_dir, + env=env) + self.addCleanup(p.kill) + + stdoutdata = p.stdout.readline() # ****... + + stdoutdata = p.stdout.readline() # STARTING test server... + self.assertIn( + b"STARTING test server pbr_testpackage.wsgi", + stdoutdata) + + stdoutdata = p.stdout.readline() # Available at ... + print(stdoutdata) + m = re.search(b'(http://[^:]+:\d+)/', stdoutdata) + self.assertIsNotNone(m, "Regex failed to match on %s" % stdoutdata) + + stdoutdata = p.stdout.readline() # DANGER! ... + self.assertIn( + b"DANGER! For testing only, do not use in production", + stdoutdata) + + stdoutdata = p.stdout.readline() # ***... + + f = urlopen(m.group(1).decode('utf-8')) + self.assertEqual(output, f.read()) + + # Request again so that the application can force stderr.flush(), + # otherwise the log is buffered and the next readline() will hang. + urlopen(m.group(1).decode('utf-8')) + + stdoutdata = p.stderr.readline() + # we should have logged an HTTP request, return code 200, that + # returned the right amount of bytes + status = '"GET / HTTP/1.1" 200 %d' % len(output) + self.assertIn(status.encode('utf-8'), stdoutdata) + + def _check_wsgi_install_content(self, install_stdout): + for cmd_name in self.cmd_names: + install_txt = 'Installing %s script to %s' % (cmd_name, + self.temp_dir) + self.assertIn(install_txt, install_stdout) + + cmd_filename = os.path.join(self.temp_dir, 'bin', cmd_name) + + script_txt = open(cmd_filename, 'r').read() + self.assertNotIn('pkg_resources', script_txt) + + main_block = """if __name__ == "__main__": + import argparse + import socket + import sys + import wsgiref.simple_server as wss""" + + if cmd_name == 'pbr_test_wsgi': + app_name = "main" + else: + app_name = "WSGI.app" + + starting_block = ("STARTING test server pbr_testpackage.wsgi." + "%s" % app_name) + + else_block = """else: + application = None""" + + self.assertIn(main_block, script_txt) + self.assertIn(starting_block, script_txt) + self.assertIn(else_block, script_txt) + + def test_with_argument(self): + if os.name == 'nt': + self.skipTest('Windows support is passthrough') + + stdout, _, return_code = self.run_setup( + 'install', '--prefix=%s' % self.temp_dir) + + self._test_wsgi('pbr_test_wsgi', b'Foo Bar', ["--", "-c", "Foo Bar"]) diff --git a/libs/pbr/tests/testpackage/CHANGES.txt b/libs/pbr/tests/testpackage/CHANGES.txt new file mode 100644 index 00000000..709b9d4c --- /dev/null +++ b/libs/pbr/tests/testpackage/CHANGES.txt @@ -0,0 +1,86 @@ +Changelog +=========== + +0.3 (unreleased) +------------------ + +- The ``glob_data_files`` hook became a pre-command hook for the install_data + command instead of being a setup-hook. This is to support the additional + functionality of requiring data_files with relative destination paths to be + install relative to the package's install path (i.e. site-packages). + +- Dropped support for and deprecated the easier_install custom command. + Although it should still work, it probably won't be used anymore for + stsci_python packages. + +- Added support for the ``build_optional_ext`` command, which replaces/extends + the default ``build_ext`` command. See the README for more details. + +- Added the ``tag_svn_revision`` setup_hook as a replacement for the + setuptools-specific tag_svn_revision option to the egg_info command. This + new hook is easier to use than the old tag_svn_revision option: It's + automatically enabled by the presence of ``.dev`` in the version string, and + disabled otherwise. + +- The ``svn_info_pre_hook`` and ``svn_info_post_hook`` have been replaced with + ``version_pre_command_hook`` and ``version_post_command_hook`` respectively. + However, a new ``version_setup_hook``, which has the same purpose, has been + added. It is generally easier to use and will give more consistent results + in that it will run every time setup.py is run, regardless of which command + is used. ``stsci.distutils`` itself uses this hook--see the `setup.cfg` file + and `stsci/distutils/__init__.py` for example usage. + +- Instead of creating an `svninfo.py` module, the new ``version_`` hooks create + a file called `version.py`. In addition to the SVN info that was included + in `svninfo.py`, it includes a ``__version__`` variable to be used by the + package's `__init__.py`. This allows there to be a hard-coded + ``__version__`` variable included in the source code, rather than using + pkg_resources to get the version. + +- In `version.py`, the variables previously named ``__svn_version__`` and + ``__full_svn_info__`` are now named ``__svn_revision__`` and + ``__svn_full_info__``. + +- Fixed a bug when using stsci.distutils in the installation of other packages + in the ``stsci.*`` namespace package. If stsci.distutils was not already + installed, and was downloaded automatically by distribute through the + setup_requires option, then ``stsci.distutils`` would fail to import. This + is because the way the namespace package (nspkg) mechanism currently works, + all packages belonging to the nspkg *must* be on the import path at initial + import time. + + So when installing stsci.tools, for example, if ``stsci.tools`` is imported + from within the source code at install time, but before ``stsci.distutils`` + is downloaded and added to the path, the ``stsci`` package is already + imported and can't be extended to include the path of ``stsci.distutils`` + after the fact. The easiest way of dealing with this, it seems, is to + delete ``stsci`` from ``sys.modules``, which forces it to be reimported, now + the its ``__path__`` extended to include ``stsci.distutil``'s path. + + +0.2.2 (2011-11-09) +------------------ + +- Fixed check for the issue205 bug on actual setuptools installs; before it + only worked on distribute. setuptools has the issue205 bug prior to version + 0.6c10. + +- Improved the fix for the issue205 bug, especially on setuptools. + setuptools, prior to 0.6c10, did not back of sys.modules either before + sandboxing, which causes serious problems. In fact, it's so bad that it's + not enough to add a sys.modules backup to the current sandbox: It's in fact + necessary to monkeypatch setuptools.sandbox.run_setup so that any subsequent + calls to it also back up sys.modules. + + +0.2.1 (2011-09-02) +------------------ + +- Fixed the dependencies so that setuptools is requirement but 'distribute' + specifically. Previously installation could fail if users had plain + setuptools installed and not distribute + +0.2 (2011-08-23) +------------------ + +- Initial public release diff --git a/libs/pbr/tests/testpackage/LICENSE.txt b/libs/pbr/tests/testpackage/LICENSE.txt new file mode 100644 index 00000000..7e8019a8 --- /dev/null +++ b/libs/pbr/tests/testpackage/LICENSE.txt @@ -0,0 +1,29 @@ +Copyright (C) 2005 Association of Universities for Research in Astronomy (AURA) + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + 3. The name of AURA and its representatives may not be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + diff --git a/libs/pbr/tests/testpackage/MANIFEST.in b/libs/pbr/tests/testpackage/MANIFEST.in new file mode 100644 index 00000000..2e35f3ed --- /dev/null +++ b/libs/pbr/tests/testpackage/MANIFEST.in @@ -0,0 +1,2 @@ +include data_files/* +exclude pbr_testpackage/extra.py diff --git a/libs/pbr/tests/testpackage/README.txt b/libs/pbr/tests/testpackage/README.txt new file mode 100644 index 00000000..b6d84a7b --- /dev/null +++ b/libs/pbr/tests/testpackage/README.txt @@ -0,0 +1,148 @@ +Introduction +============ +This package contains utilities used to package some of STScI's Python +projects; specifically those projects that comprise stsci_python_ and +Astrolib_. + +It currently consists mostly of some setup_hook scripts meant for use with +`distutils2/packaging`_ and/or pbr_, and a customized easy_install command +meant for use with distribute_. + +This package is not meant for general consumption, though it might be worth +looking at for examples of how to do certain things with your own packages, but +YMMV. + +Features +======== + +Hook Scripts +------------ +Currently the main features of this package are a couple of setup_hook scripts. +In distutils2, a setup_hook is a script that runs at the beginning of any +pysetup command, and can modify the package configuration read from setup.cfg. +There are also pre- and post-command hooks that only run before/after a +specific setup command (eg. build_ext, install) is run. + +stsci.distutils.hooks.use_packages_root +''''''''''''''''''''''''''''''''''''''' +If using the ``packages_root`` option under the ``[files]`` section of +setup.cfg, this hook will add that path to ``sys.path`` so that modules in your +package can be imported and used in setup. This can be used even if +``packages_root`` is not specified--in this case it adds ``''`` to +``sys.path``. + +stsci.distutils.hooks.version_setup_hook +'''''''''''''''''''''''''''''''''''''''' +Creates a Python module called version.py which currently contains four +variables: + +* ``__version__`` (the release version) +* ``__svn_revision__`` (the SVN revision info as returned by the ``svnversion`` + command) +* ``__svn_full_info__`` (as returned by the ``svn info`` command) +* ``__setup_datetime__`` (the date and time that setup.py was last run). + +These variables can be imported in the package's `__init__.py` for degugging +purposes. The version.py module will *only* be created in a package that +imports from the version module in its `__init__.py`. It should be noted that +this is generally preferable to writing these variables directly into +`__init__.py`, since this provides more control and is less likely to +unexpectedly break things in `__init__.py`. + +stsci.distutils.hooks.version_pre_command_hook +'''''''''''''''''''''''''''''''''''''''''''''' +Identical to version_setup_hook, but designed to be used as a pre-command +hook. + +stsci.distutils.hooks.version_post_command_hook +''''''''''''''''''''''''''''''''''''''''''''''' +The complement to version_pre_command_hook. This will delete any version.py +files created during a build in order to prevent them from cluttering an SVN +working copy (note, however, that version.py is *not* deleted from the build/ +directory, so a copy of it is still preserved). It will also not be deleted +if the current directory is not an SVN working copy. For example, if source +code extracted from a source tarball it will be preserved. + +stsci.distutils.hooks.tag_svn_revision +'''''''''''''''''''''''''''''''''''''' +A setup_hook to add the SVN revision of the current working copy path to the +package version string, but only if the version ends in .dev. + +For example, ``mypackage-1.0.dev`` becomes ``mypackage-1.0.dev1234``. This is +in accordance with the version string format standardized by PEP 386. + +This should be used as a replacement for the ``tag_svn_revision`` option to +the egg_info command. This hook is more compatible with packaging/distutils2, +which does not include any VCS support. This hook is also more flexible in +that it turns the revision number on/off depending on the presence of ``.dev`` +in the version string, so that it's not automatically added to the version in +final releases. + +This hook does require the ``svnversion`` command to be available in order to +work. It does not examine the working copy metadata directly. + +stsci.distutils.hooks.numpy_extension_hook +'''''''''''''''''''''''''''''''''''''''''' +This is a pre-command hook for the build_ext command. To use it, add a +``[build_ext]`` section to your setup.cfg, and add to it:: + + pre-hook.numpy-extension-hook = stsci.distutils.hooks.numpy_extension_hook + +This hook must be used to build extension modules that use Numpy. The primary +side-effect of this hook is to add the correct numpy include directories to +`include_dirs`. To use it, add 'numpy' to the 'include-dirs' option of each +extension module that requires numpy to build. The value 'numpy' will be +replaced with the actual path to the numpy includes. + +stsci.distutils.hooks.is_display_option +''''''''''''''''''''''''''''''''''''''' +This is not actually a hook, but is a useful utility function that can be used +in writing other hooks. Basically, it returns ``True`` if setup.py was run +with a "display option" such as --version or --help. This can be used to +prevent your hook from running in such cases. + +stsci.distutils.hooks.glob_data_files +''''''''''''''''''''''''''''''''''''' +A pre-command hook for the install_data command. Allows filename wildcards as +understood by ``glob.glob()`` to be used in the data_files option. This hook +must be used in order to have this functionality since it does not normally +exist in distutils. + +This hook also ensures that data files are installed relative to the package +path. data_files shouldn't normally be installed this way, but the +functionality is required for a few special cases. + + +Commands +-------- +build_optional_ext +'''''''''''''''''' +This serves as an optional replacement for the default built_ext command, +which compiles C extension modules. Its purpose is to allow extension modules +to be *optional*, so that if their build fails the rest of the package is +still allowed to be built and installed. This can be used when an extension +module is not definitely required to use the package. + +To use this custom command, add:: + + commands = stsci.distutils.command.build_optional_ext.build_optional_ext + +under the ``[global]`` section of your package's setup.cfg. Then, to mark +an individual extension module as optional, under the setup.cfg section for +that extension add:: + + optional = True + +Optionally, you may also add a custom failure message by adding:: + + fail_message = The foobar extension module failed to compile. + This could be because you lack such and such headers. + This package will still work, but such and such features + will be disabled. + + +.. _stsci_python: http://www.stsci.edu/resources/software_hardware/pyraf/stsci_python +.. _Astrolib: http://www.scipy.org/AstroLib/ +.. _distutils2/packaging: http://distutils2.notmyidea.org/ +.. _d2to1: http://pypi.python.org/pypi/d2to1 +.. _distribute: http://pypi.python.org/pypi/distribute diff --git a/libs/pbr/tests/testpackage/data_files/a.txt b/libs/pbr/tests/testpackage/data_files/a.txt new file mode 100644 index 00000000..e69de29b diff --git a/libs/pbr/tests/testpackage/data_files/b.txt b/libs/pbr/tests/testpackage/data_files/b.txt new file mode 100644 index 00000000..e69de29b diff --git a/libs/pbr/tests/testpackage/data_files/c.rst b/libs/pbr/tests/testpackage/data_files/c.rst new file mode 100644 index 00000000..e69de29b diff --git a/libs/pbr/tests/testpackage/doc/source/conf.py b/libs/pbr/tests/testpackage/doc/source/conf.py new file mode 100644 index 00000000..73585100 --- /dev/null +++ b/libs/pbr/tests/testpackage/doc/source/conf.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys + +sys.path.insert(0, os.path.abspath('../..')) +# -- General configuration ---------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = [ + 'sphinx.ext.autodoc', + #'sphinx.ext.intersphinx', +] + +# autodoc generation is a bit aggressive and a nuisance when doing heavy +# text edit cycles. +# execute "export SPHINX_DEBUG=1" in your terminal to disable + +# The suffix of source filenames. +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'testpackage' +copyright = u'2013, OpenStack Foundation' + +# If true, '()' will be appended to :func: etc. cross-reference text. +add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +add_module_names = True + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# -- Options for HTML output -------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. Major themes that come with +# Sphinx are currently 'default' and 'sphinxdoc'. +# html_theme_path = ["."] +# html_theme = '_theme' +# html_static_path = ['static'] + +# Output file base name for HTML help builder. +htmlhelp_basename = '%sdoc' % project + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass +# [howto/manual]). +latex_documents = [ + ('index', + '%s.tex' % project, + u'%s Documentation' % project, + u'OpenStack Foundation', 'manual'), +] + +# Example configuration for intersphinx: refer to the Python standard library. +#intersphinx_mapping = {'http://docs.python.org/': None} diff --git a/libs/pbr/tests/testpackage/doc/source/index.rst b/libs/pbr/tests/testpackage/doc/source/index.rst new file mode 100644 index 00000000..9ce317fd --- /dev/null +++ b/libs/pbr/tests/testpackage/doc/source/index.rst @@ -0,0 +1,23 @@ +.. testpackage documentation master file, created by + sphinx-quickstart on Tue Jul 9 22:26:36 2013. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to testpackage's documentation! +======================================================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + installation + usage + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/libs/pbr/tests/testpackage/doc/source/installation.rst b/libs/pbr/tests/testpackage/doc/source/installation.rst new file mode 100644 index 00000000..65bca43f --- /dev/null +++ b/libs/pbr/tests/testpackage/doc/source/installation.rst @@ -0,0 +1,12 @@ +============ +Installation +============ + +At the command line:: + + $ pip install testpackage + +Or, if you have virtualenvwrapper installed:: + + $ mkvirtualenv testpackage + $ pip install testpackage diff --git a/libs/pbr/tests/testpackage/doc/source/usage.rst b/libs/pbr/tests/testpackage/doc/source/usage.rst new file mode 100644 index 00000000..af97d795 --- /dev/null +++ b/libs/pbr/tests/testpackage/doc/source/usage.rst @@ -0,0 +1,7 @@ +======== +Usage +======== + +To use testpackage in a project:: + + import testpackage diff --git a/libs/pbr/tests/testpackage/extra-file.txt b/libs/pbr/tests/testpackage/extra-file.txt new file mode 100644 index 00000000..e69de29b diff --git a/libs/pbr/tests/testpackage/git-extra-file.txt b/libs/pbr/tests/testpackage/git-extra-file.txt new file mode 100644 index 00000000..e69de29b diff --git a/libs/pbr/tests/testpackage/pbr_testpackage/__init__.py b/libs/pbr/tests/testpackage/pbr_testpackage/__init__.py new file mode 100644 index 00000000..aa56dc6f --- /dev/null +++ b/libs/pbr/tests/testpackage/pbr_testpackage/__init__.py @@ -0,0 +1,3 @@ +import pbr.version + +__version__ = pbr.version.VersionInfo('pbr_testpackage').version_string() diff --git a/libs/pbr/tests/testpackage/pbr_testpackage/_setup_hooks.py b/libs/pbr/tests/testpackage/pbr_testpackage/_setup_hooks.py new file mode 100644 index 00000000..f8b30876 --- /dev/null +++ b/libs/pbr/tests/testpackage/pbr_testpackage/_setup_hooks.py @@ -0,0 +1,65 @@ +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Copyright (C) 2013 Association of Universities for Research in Astronomy +# (AURA) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# 3. The name of AURA and its representatives may not be used to +# endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED +# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + +from distutils.command import build_py + + +def test_hook_1(config): + print('test_hook_1') + + +def test_hook_2(config): + print('test_hook_2') + + +class test_command(build_py.build_py): + command_name = 'build_py' + + def run(self): + print('Running custom build_py command.') + return build_py.build_py.run(self) + + +def test_pre_hook(cmdobj): + print('build_ext pre-hook') + + +def test_post_hook(cmdobj): + print('build_ext post-hook') diff --git a/libs/pbr/tests/testpackage/pbr_testpackage/cmd.py b/libs/pbr/tests/testpackage/pbr_testpackage/cmd.py new file mode 100644 index 00000000..4cc4522f --- /dev/null +++ b/libs/pbr/tests/testpackage/pbr_testpackage/cmd.py @@ -0,0 +1,26 @@ +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import print_function + + +def main(): + print("PBR Test Command") + + +class Foo(object): + + @classmethod + def bar(self): + print("PBR Test Command - with class!") diff --git a/libs/pbr/tests/testpackage/pbr_testpackage/extra.py b/libs/pbr/tests/testpackage/pbr_testpackage/extra.py new file mode 100644 index 00000000..e69de29b diff --git a/libs/pbr/tests/testpackage/pbr_testpackage/package_data/1.txt b/libs/pbr/tests/testpackage/pbr_testpackage/package_data/1.txt new file mode 100644 index 00000000..e69de29b diff --git a/libs/pbr/tests/testpackage/pbr_testpackage/package_data/2.txt b/libs/pbr/tests/testpackage/pbr_testpackage/package_data/2.txt new file mode 100644 index 00000000..e69de29b diff --git a/libs/pbr/tests/testpackage/pbr_testpackage/wsgi.py b/libs/pbr/tests/testpackage/pbr_testpackage/wsgi.py new file mode 100644 index 00000000..1edd54d3 --- /dev/null +++ b/libs/pbr/tests/testpackage/pbr_testpackage/wsgi.py @@ -0,0 +1,40 @@ +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import print_function + +import argparse +import functools +import sys + + +def application(env, start_response, data): + sys.stderr.flush() # Force the previous request log to be written. + start_response('200 OK', [('Content-Type', 'text/html')]) + return [data.encode('utf-8')] + + +def main(): + parser = argparse.ArgumentParser(description='Return a string.') + parser.add_argument('--content', '-c', help='String returned', + default='Hello World') + args = parser.parse_args() + return functools.partial(application, data=args.content) + + +class WSGI(object): + + @classmethod + def app(self): + return functools.partial(application, data='Hello World') diff --git a/libs/pbr/tests/testpackage/setup.py b/libs/pbr/tests/testpackage/setup.py new file mode 100644 index 00000000..2d9f685b --- /dev/null +++ b/libs/pbr/tests/testpackage/setup.py @@ -0,0 +1,21 @@ +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import setuptools + +setuptools.setup( + setup_requires=['pbr'], + pbr=True, +) diff --git a/libs/pbr/tests/testpackage/src/testext.c b/libs/pbr/tests/testpackage/src/testext.c new file mode 100644 index 00000000..1b366e9b --- /dev/null +++ b/libs/pbr/tests/testpackage/src/testext.c @@ -0,0 +1,29 @@ +#include + + +static PyMethodDef TestextMethods[] = { + {NULL, NULL, 0, NULL} +}; + + +#if PY_MAJOR_VERSION >=3 +static struct PyModuleDef testextmodule = { + PyModuleDef_HEAD_INIT, /* This should correspond to a PyModuleDef_Base type */ + "testext", /* This is the module name */ + "Test extension module", /* This is the module docstring */ + -1, /* This defines the size of the module and says everything is global */ + TestextMethods /* This is the method definition */ +}; + +PyObject* +PyInit_testext(void) +{ + return PyModule_Create(&testextmodule); +} +#else +PyMODINIT_FUNC +inittestext(void) +{ + Py_InitModule("testext", TestextMethods); +} +#endif diff --git a/libs/pbr/tests/testpackage/test-requirements.txt b/libs/pbr/tests/testpackage/test-requirements.txt new file mode 100644 index 00000000..8755eb4c --- /dev/null +++ b/libs/pbr/tests/testpackage/test-requirements.txt @@ -0,0 +1,2 @@ +ordereddict;python_version=='2.6' +requests-mock diff --git a/libs/pbr/tests/util.py b/libs/pbr/tests/util.py new file mode 100644 index 00000000..0e7bcf15 --- /dev/null +++ b/libs/pbr/tests/util.py @@ -0,0 +1,78 @@ +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Copyright (C) 2013 Association of Universities for Research in Astronomy +# (AURA) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# 3. The name of AURA and its representatives may not be used to +# endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED +# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + +import contextlib +import os +import shutil +import stat +import sys + +try: + import ConfigParser as configparser +except ImportError: + import configparser + + +@contextlib.contextmanager +def open_config(filename): + if sys.version_info >= (3, 2): + cfg = configparser.ConfigParser() + else: + cfg = configparser.SafeConfigParser() + cfg.read(filename) + yield cfg + with open(filename, 'w') as fp: + cfg.write(fp) + + +def rmtree(path): + """shutil.rmtree() with error handler. + + Handle 'access denied' from trying to delete read-only files. + """ + + def onerror(func, path, exc_info): + if not os.access(path, os.W_OK): + os.chmod(path, stat.S_IWUSR) + func(path) + else: + raise + + return shutil.rmtree(path, onerror=onerror) diff --git a/libs/pbr/util.py b/libs/pbr/util.py new file mode 100644 index 00000000..63e913d9 --- /dev/null +++ b/libs/pbr/util.py @@ -0,0 +1,611 @@ +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Copyright (C) 2013 Association of Universities for Research in Astronomy +# (AURA) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# 3. The name of AURA and its representatives may not be used to +# endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED +# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. + +"""The code in this module is mostly copy/pasted out of the distutils2 source +code, as recommended by Tarek Ziade. As such, it may be subject to some change +as distutils2 development continues, and will have to be kept up to date. + +I didn't want to use it directly from distutils2 itself, since I do not want it +to be an installation dependency for our packages yet--it is still too unstable +(the latest version on PyPI doesn't even install). +""" + +# These first two imports are not used, but are needed to get around an +# irritating Python bug that can crop up when using ./setup.py test. +# See: http://www.eby-sarna.com/pipermail/peak/2010-May/003355.html +try: + import multiprocessing # noqa +except ImportError: + pass +import logging # noqa + +from collections import defaultdict +import os +import re +import sys +import traceback + +import distutils.ccompiler +from distutils import errors +from distutils import log +import pkg_resources +from setuptools import dist as st_dist +from setuptools import extension + +try: + import ConfigParser as configparser +except ImportError: + import configparser + +from pbr import extra_files +import pbr.hooks + +# A simplified RE for this; just checks that the line ends with version +# predicates in () +_VERSION_SPEC_RE = re.compile(r'\s*(.*?)\s*\((.*)\)\s*$') + + +# Mappings from setup() keyword arguments to setup.cfg options; +# The values are (section, option) tuples, or simply (section,) tuples if +# the option has the same name as the setup() argument +D1_D2_SETUP_ARGS = { + "name": ("metadata",), + "version": ("metadata",), + "author": ("metadata",), + "author_email": ("metadata",), + "maintainer": ("metadata",), + "maintainer_email": ("metadata",), + "url": ("metadata", "home_page"), + "project_urls": ("metadata",), + "description": ("metadata", "summary"), + "keywords": ("metadata",), + "long_description": ("metadata", "description"), + "long_description_content_type": ("metadata", "description_content_type"), + "download_url": ("metadata",), + "classifiers": ("metadata", "classifier"), + "platforms": ("metadata", "platform"), # ** + "license": ("metadata",), + # Use setuptools install_requires, not + # broken distutils requires + "install_requires": ("metadata", "requires_dist"), + "setup_requires": ("metadata", "setup_requires_dist"), + "python_requires": ("metadata",), + "provides": ("metadata", "provides_dist"), # ** + "obsoletes": ("metadata", "obsoletes_dist"), # ** + "package_dir": ("files", 'packages_root'), + "packages": ("files",), + "package_data": ("files",), + "namespace_packages": ("files",), + "data_files": ("files",), + "scripts": ("files",), + "py_modules": ("files", "modules"), # ** + "cmdclass": ("global", "commands"), + # Not supported in distutils2, but provided for + # backwards compatibility with setuptools + "use_2to3": ("backwards_compat", "use_2to3"), + "zip_safe": ("backwards_compat", "zip_safe"), + "tests_require": ("backwards_compat", "tests_require"), + "dependency_links": ("backwards_compat",), + "include_package_data": ("backwards_compat",), +} + +# setup() arguments that can have multiple values in setup.cfg +MULTI_FIELDS = ("classifiers", + "platforms", + "install_requires", + "provides", + "obsoletes", + "namespace_packages", + "packages", + "package_data", + "data_files", + "scripts", + "py_modules", + "dependency_links", + "setup_requires", + "tests_require", + "cmdclass") + +# setup() arguments that can have mapping values in setup.cfg +MAP_FIELDS = ("project_urls",) + +# setup() arguments that contain boolean values +BOOL_FIELDS = ("use_2to3", "zip_safe", "include_package_data") + + +CSV_FIELDS = ("keywords",) + + +def resolve_name(name): + """Resolve a name like ``module.object`` to an object and return it. + + Raise ImportError if the module or name is not found. + """ + + parts = name.split('.') + cursor = len(parts) - 1 + module_name = parts[:cursor] + attr_name = parts[-1] + + while cursor > 0: + try: + ret = __import__('.'.join(module_name), fromlist=[attr_name]) + break + except ImportError: + if cursor == 0: + raise + cursor -= 1 + module_name = parts[:cursor] + attr_name = parts[cursor] + ret = '' + + for part in parts[cursor:]: + try: + ret = getattr(ret, part) + except AttributeError: + raise ImportError(name) + + return ret + + +def cfg_to_args(path='setup.cfg', script_args=()): + """Distutils2 to distutils1 compatibility util. + + This method uses an existing setup.cfg to generate a dictionary of + keywords that can be used by distutils.core.setup(kwargs**). + + :param path: + The setup.cfg path. + :param script_args: + List of commands setup.py was called with. + :raises DistutilsFileError: + When the setup.cfg file is not found. + """ + + # The method source code really starts here. + if sys.version_info >= (3, 2): + parser = configparser.ConfigParser() + else: + parser = configparser.SafeConfigParser() + if not os.path.exists(path): + raise errors.DistutilsFileError("file '%s' does not exist" % + os.path.abspath(path)) + try: + parser.read(path, encoding='utf-8') + except TypeError: + # Python 2 doesn't accept the encoding kwarg + parser.read(path) + config = {} + for section in parser.sections(): + config[section] = dict() + for k, value in parser.items(section): + config[section][k.replace('-', '_')] = value + + # Run setup_hooks, if configured + setup_hooks = has_get_option(config, 'global', 'setup_hooks') + package_dir = has_get_option(config, 'files', 'packages_root') + + # Add the source package directory to sys.path in case it contains + # additional hooks, and to make sure it's on the path before any existing + # installations of the package + if package_dir: + package_dir = os.path.abspath(package_dir) + sys.path.insert(0, package_dir) + + try: + if setup_hooks: + setup_hooks = [ + hook for hook in split_multiline(setup_hooks) + if hook != 'pbr.hooks.setup_hook'] + for hook in setup_hooks: + hook_fn = resolve_name(hook) + try: + hook_fn(config) + except SystemExit: + log.error('setup hook %s terminated the installation') + except Exception: + e = sys.exc_info()[1] + log.error('setup hook %s raised exception: %s\n' % + (hook, e)) + log.error(traceback.format_exc()) + sys.exit(1) + + # Run the pbr hook + pbr.hooks.setup_hook(config) + + kwargs = setup_cfg_to_setup_kwargs(config, script_args) + + # Set default config overrides + kwargs['include_package_data'] = True + kwargs['zip_safe'] = False + + register_custom_compilers(config) + + ext_modules = get_extension_modules(config) + if ext_modules: + kwargs['ext_modules'] = ext_modules + + entry_points = get_entry_points(config) + if entry_points: + kwargs['entry_points'] = entry_points + + # Handle the [files]/extra_files option + files_extra_files = has_get_option(config, 'files', 'extra_files') + if files_extra_files: + extra_files.set_extra_files(split_multiline(files_extra_files)) + + finally: + # Perform cleanup if any paths were added to sys.path + if package_dir: + sys.path.pop(0) + + return kwargs + + +def setup_cfg_to_setup_kwargs(config, script_args=()): + """Convert config options to kwargs. + + Processes the setup.cfg options and converts them to arguments accepted + by setuptools' setup() function. + """ + + kwargs = {} + + # Temporarily holds install_requires and extra_requires while we + # parse env_markers. + all_requirements = {} + + for arg in D1_D2_SETUP_ARGS: + if len(D1_D2_SETUP_ARGS[arg]) == 2: + # The distutils field name is different than distutils2's. + section, option = D1_D2_SETUP_ARGS[arg] + + elif len(D1_D2_SETUP_ARGS[arg]) == 1: + # The distutils field name is the same thant distutils2's. + section = D1_D2_SETUP_ARGS[arg][0] + option = arg + + in_cfg_value = has_get_option(config, section, option) + if not in_cfg_value: + # There is no such option in the setup.cfg + if arg == "long_description": + in_cfg_value = has_get_option(config, section, + "description_file") + if in_cfg_value: + in_cfg_value = split_multiline(in_cfg_value) + value = '' + for filename in in_cfg_value: + description_file = open(filename) + try: + value += description_file.read().strip() + '\n\n' + finally: + description_file.close() + in_cfg_value = value + else: + continue + + if arg in CSV_FIELDS: + in_cfg_value = split_csv(in_cfg_value) + if arg in MULTI_FIELDS: + in_cfg_value = split_multiline(in_cfg_value) + elif arg in MAP_FIELDS: + in_cfg_map = {} + for i in split_multiline(in_cfg_value): + k, v = i.split('=') + in_cfg_map[k.strip()] = v.strip() + in_cfg_value = in_cfg_map + elif arg in BOOL_FIELDS: + # Provide some flexibility here... + if in_cfg_value.lower() in ('true', 't', '1', 'yes', 'y'): + in_cfg_value = True + else: + in_cfg_value = False + + if in_cfg_value: + if arg in ('install_requires', 'tests_require'): + # Replaces PEP345-style version specs with the sort expected by + # setuptools + in_cfg_value = [_VERSION_SPEC_RE.sub(r'\1\2', pred) + for pred in in_cfg_value] + if arg == 'install_requires': + # Split install_requires into package,env_marker tuples + # These will be re-assembled later + install_requires = [] + requirement_pattern = ( + r'(?P[^;]*);?(?P[^#]*?)(?:\s*#.*)?$') + for requirement in in_cfg_value: + m = re.match(requirement_pattern, requirement) + requirement_package = m.group('package').strip() + env_marker = m.group('env_marker').strip() + install_requires.append((requirement_package, env_marker)) + all_requirements[''] = install_requires + elif arg == 'package_dir': + in_cfg_value = {'': in_cfg_value} + elif arg in ('package_data', 'data_files'): + data_files = {} + firstline = True + prev = None + for line in in_cfg_value: + if '=' in line: + key, value = line.split('=', 1) + key, value = (key.strip(), value.strip()) + if key in data_files: + # Multiple duplicates of the same package name; + # this is for backwards compatibility of the old + # format prior to d2to1 0.2.6. + prev = data_files[key] + prev.extend(value.split()) + else: + prev = data_files[key.strip()] = value.split() + elif firstline: + raise errors.DistutilsOptionError( + 'malformed package_data first line %r (misses ' + '"=")' % line) + else: + prev.extend(line.strip().split()) + firstline = False + if arg == 'data_files': + # the data_files value is a pointlessly different structure + # from the package_data value + data_files = data_files.items() + in_cfg_value = data_files + elif arg == 'cmdclass': + cmdclass = {} + dist = st_dist.Distribution() + for cls_name in in_cfg_value: + cls = resolve_name(cls_name) + cmd = cls(dist) + cmdclass[cmd.get_command_name()] = cls + in_cfg_value = cmdclass + + kwargs[arg] = in_cfg_value + + # Transform requirements with embedded environment markers to + # setuptools' supported marker-per-requirement format. + # + # install_requires are treated as a special case of extras, before + # being put back in the expected place + # + # fred = + # foo:marker + # bar + # -> {'fred': ['bar'], 'fred:marker':['foo']} + + if 'extras' in config: + requirement_pattern = ( + r'(?P[^:]*):?(?P[^#]*?)(?:\s*#.*)?$') + extras = config['extras'] + # Add contents of test-requirements, if any, into an extra named + # 'test' if one does not already exist. + if 'test' not in extras: + from pbr import packaging + extras['test'] = "\n".join(packaging.parse_requirements( + packaging.TEST_REQUIREMENTS_FILES)).replace(';', ':') + + for extra in extras: + extra_requirements = [] + requirements = split_multiline(extras[extra]) + for requirement in requirements: + m = re.match(requirement_pattern, requirement) + extras_value = m.group('package').strip() + env_marker = m.group('env_marker') + extra_requirements.append((extras_value, env_marker)) + all_requirements[extra] = extra_requirements + + # Transform the full list of requirements into: + # - install_requires, for those that have no extra and no + # env_marker + # - named extras, for those with an extra name (which may include + # an env_marker) + # - and as a special case, install_requires with an env_marker are + # treated as named extras where the name is the empty string + + extras_require = {} + for req_group in all_requirements: + for requirement, env_marker in all_requirements[req_group]: + if env_marker: + extras_key = '%s:(%s)' % (req_group, env_marker) + # We do not want to poison wheel creation with locally + # evaluated markers. sdists always re-create the egg_info + # and as such do not need guarded, and pip will never call + # multiple setup.py commands at once. + if 'bdist_wheel' not in script_args: + try: + if pkg_resources.evaluate_marker('(%s)' % env_marker): + extras_key = req_group + except SyntaxError: + log.error( + "Marker evaluation failed, see the following " + "error. For more information see: " + "http://docs.openstack.org/" + "pbr/latest/user/using.html#environment-markers" + ) + raise + else: + extras_key = req_group + extras_require.setdefault(extras_key, []).append(requirement) + + kwargs['install_requires'] = extras_require.pop('', []) + kwargs['extras_require'] = extras_require + + return kwargs + + +def register_custom_compilers(config): + """Handle custom compilers. + + This has no real equivalent in distutils, where additional compilers could + only be added programmatically, so we have to hack it in somehow. + """ + + compilers = has_get_option(config, 'global', 'compilers') + if compilers: + compilers = split_multiline(compilers) + for compiler in compilers: + compiler = resolve_name(compiler) + + # In distutils2 compilers these class attributes exist; for + # distutils1 we just have to make something up + if hasattr(compiler, 'name'): + name = compiler.name + else: + name = compiler.__name__ + if hasattr(compiler, 'description'): + desc = compiler.description + else: + desc = 'custom compiler %s' % name + + module_name = compiler.__module__ + # Note; this *will* override built in compilers with the same name + # TODO(embray): Maybe display a warning about this? + cc = distutils.ccompiler.compiler_class + cc[name] = (module_name, compiler.__name__, desc) + + # HACK!!!! Distutils assumes all compiler modules are in the + # distutils package + sys.modules['distutils.' + module_name] = sys.modules[module_name] + + +def get_extension_modules(config): + """Handle extension modules""" + + EXTENSION_FIELDS = ("sources", + "include_dirs", + "define_macros", + "undef_macros", + "library_dirs", + "libraries", + "runtime_library_dirs", + "extra_objects", + "extra_compile_args", + "extra_link_args", + "export_symbols", + "swig_opts", + "depends") + + ext_modules = [] + for section in config: + if ':' in section: + labels = section.split(':', 1) + else: + # Backwards compatibility for old syntax; don't use this though + labels = section.split('=', 1) + labels = [l.strip() for l in labels] + if (len(labels) == 2) and (labels[0] == 'extension'): + ext_args = {} + for field in EXTENSION_FIELDS: + value = has_get_option(config, section, field) + # All extension module options besides name can have multiple + # values + if not value: + continue + value = split_multiline(value) + if field == 'define_macros': + macros = [] + for macro in value: + macro = macro.split('=', 1) + if len(macro) == 1: + macro = (macro[0].strip(), None) + else: + macro = (macro[0].strip(), macro[1].strip()) + macros.append(macro) + value = macros + ext_args[field] = value + if ext_args: + if 'name' not in ext_args: + ext_args['name'] = labels[1] + ext_modules.append(extension.Extension(ext_args.pop('name'), + **ext_args)) + return ext_modules + + +def get_entry_points(config): + """Process the [entry_points] section of setup.cfg. + + Processes setup.cfg to handle setuptools entry points. This is, of course, + not a standard feature of distutils2/packaging, but as there is not + currently a standard alternative in packaging, we provide support for them. + """ + + if 'entry_points' not in config: + return {} + + return dict((option, split_multiline(value)) + for option, value in config['entry_points'].items()) + + +def has_get_option(config, section, option): + if section in config and option in config[section]: + return config[section][option] + else: + return False + + +def split_multiline(value): + """Special behaviour when we have a multi line options""" + + value = [element for element in + (line.strip() for line in value.split('\n')) + if element and not element.startswith('#')] + return value + + +def split_csv(value): + """Special behaviour when we have a comma separated options""" + + value = [element for element in + (chunk.strip() for chunk in value.split(',')) + if element] + return value + + +# The following classes are used to hack Distribution.command_options a bit +class DefaultGetDict(defaultdict): + """Like defaultdict, but get() also sets and returns the default value.""" + + def get(self, key, default=None): + if default is None: + default = self.default_factory() + return super(DefaultGetDict, self).setdefault(key, default) diff --git a/libs/pbr/version.py b/libs/pbr/version.py new file mode 100644 index 00000000..5eb217af --- /dev/null +++ b/libs/pbr/version.py @@ -0,0 +1,483 @@ + +# Copyright 2012 OpenStack Foundation +# Copyright 2012-2013 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Utilities for consuming the version from pkg_resources. +""" + +import itertools +import operator +import sys + + +def _is_int(string): + try: + int(string) + return True + except ValueError: + return False + + +class SemanticVersion(object): + """A pure semantic version independent of serialisation. + + See the pbr doc 'semver' for details on the semantics. + """ + + def __init__( + self, major, minor=0, patch=0, prerelease_type=None, + prerelease=None, dev_count=None): + """Create a SemanticVersion. + + :param major: Major component of the version. + :param minor: Minor component of the version. Defaults to 0. + :param patch: Patch level component. Defaults to 0. + :param prerelease_type: What sort of prerelease version this is - + one of a(alpha), b(beta) or rc(release candidate). + :param prerelease: For prerelease versions, what number prerelease. + Defaults to 0. + :param dev_count: How many commits since the last release. + """ + self._major = major + self._minor = minor + self._patch = patch + self._prerelease_type = prerelease_type + self._prerelease = prerelease + if self._prerelease_type and not self._prerelease: + self._prerelease = 0 + self._dev_count = dev_count or 0 # Normalise 0 to None. + + def __eq__(self, other): + if not isinstance(other, SemanticVersion): + return False + return self.__dict__ == other.__dict__ + + def __hash__(self): + return sum(map(hash, self.__dict__.values())) + + def _sort_key(self): + """Return a key for sorting SemanticVersion's on.""" + # key things: + # - final is after rc's, so we make that a/b/rc/z + # - dev==None is after all other devs, so we use sys.maxsize there. + # - unqualified dev releases come before any pre-releases. + # So we do: + # (major, minor, patch) - gets the major grouping. + # (0|1) unqualified dev flag + # (a/b/rc/z) - release segment grouping + # pre-release level + # dev count, maxsize for releases. + rc_lookup = {'a': 'a', 'b': 'b', 'rc': 'rc', None: 'z'} + if self._dev_count and not self._prerelease_type: + uq_dev = 0 + else: + uq_dev = 1 + return ( + self._major, self._minor, self._patch, + uq_dev, + rc_lookup[self._prerelease_type], self._prerelease, + self._dev_count or sys.maxsize) + + def __lt__(self, other): + """Compare self and other, another Semantic Version.""" + # NB(lifeless) this could perhaps be rewritten as + # lt (tuple_of_one, tuple_of_other) with a single check for + # the typeerror corner cases - that would likely be faster + # if this ever becomes performance sensitive. + if not isinstance(other, SemanticVersion): + raise TypeError("ordering to non-SemanticVersion is undefined") + return self._sort_key() < other._sort_key() + + def __le__(self, other): + return self == other or self < other + + def __ge__(self, other): + return not self < other + + def __gt__(self, other): + return not self <= other + + def __ne__(self, other): + return not self == other + + def __repr__(self): + return "pbr.version.SemanticVersion(%s)" % self.release_string() + + @classmethod + def from_pip_string(klass, version_string): + """Create a SemanticVersion from a pip version string. + + This method will parse a version like 1.3.0 into a SemanticVersion. + + This method is responsible for accepting any version string that any + older version of pbr ever created. + + Therefore: versions like 1.3.0a1 versions are handled, parsed into a + canonical form and then output - resulting in 1.3.0.0a1. + Pre pbr-semver dev versions like 0.10.1.3.g83bef74 will be parsed but + output as 0.10.1.dev3.g83bef74. + + :raises ValueError: Never tagged versions sdisted by old pbr result in + just the git hash, e.g. '1234567' which poses a substantial problem + since they collide with the semver versions when all the digits are + numerals. Such versions will result in a ValueError being thrown if + any non-numeric digits are present. They are an exception to the + general case of accepting anything we ever output, since they were + never intended and would permanently mess up versions on PyPI if + ever released - we're treating that as a critical bug that we ever + made them and have stopped doing that. + """ + + try: + return klass._from_pip_string_unsafe(version_string) + except IndexError: + raise ValueError("Invalid version %r" % version_string) + + @classmethod + def _from_pip_string_unsafe(klass, version_string): + # Versions need to start numerically, ignore if not + version_string = version_string.lstrip('vV') + if not version_string[:1].isdigit(): + raise ValueError("Invalid version %r" % version_string) + input_components = version_string.split('.') + # decimals first (keep pre-release and dev/hashes to the right) + components = [c for c in input_components if c.isdigit()] + digit_len = len(components) + if digit_len == 0: + raise ValueError("Invalid version %r" % version_string) + elif digit_len < 3: + if (digit_len < len(input_components) and + input_components[digit_len][0].isdigit()): + # Handle X.YaZ - Y is a digit not a leadin to pre-release. + mixed_component = input_components[digit_len] + last_component = ''.join(itertools.takewhile( + lambda x: x.isdigit(), mixed_component)) + components.append(last_component) + input_components[digit_len:digit_len + 1] = [ + last_component, mixed_component[len(last_component):]] + digit_len += 1 + components.extend([0] * (3 - digit_len)) + components.extend(input_components[digit_len:]) + major = int(components[0]) + minor = int(components[1]) + dev_count = None + post_count = None + prerelease_type = None + prerelease = None + + def _parse_type(segment): + # Discard leading digits (the 0 in 0a1) + isdigit = operator.methodcaller('isdigit') + segment = ''.join(itertools.dropwhile(isdigit, segment)) + isalpha = operator.methodcaller('isalpha') + prerelease_type = ''.join(itertools.takewhile(isalpha, segment)) + prerelease = segment[len(prerelease_type)::] + return prerelease_type, int(prerelease) + if _is_int(components[2]): + patch = int(components[2]) + else: + # legacy version e.g. 1.2.0a1 (canonical is 1.2.0.0a1) + # or 1.2.dev4.g1234 or 1.2.b4 + patch = 0 + components[2:2] = [0] + remainder = components[3:] + remainder_starts_with_int = False + try: + if remainder and int(remainder[0]): + remainder_starts_with_int = True + except ValueError: + pass + if remainder_starts_with_int: + # old dev format - 0.1.2.3.g1234 + dev_count = int(remainder[0]) + else: + if remainder and (remainder[0][0] == '0' or + remainder[0][0] in ('a', 'b', 'r')): + # Current RC/beta layout + prerelease_type, prerelease = _parse_type(remainder[0]) + remainder = remainder[1:] + while remainder: + component = remainder[0] + if component.startswith('dev'): + dev_count = int(component[3:]) + elif component.startswith('post'): + dev_count = None + post_count = int(component[4:]) + else: + raise ValueError( + 'Unknown remainder %r in %r' + % (remainder, version_string)) + remainder = remainder[1:] + result = SemanticVersion( + major, minor, patch, prerelease_type=prerelease_type, + prerelease=prerelease, dev_count=dev_count) + if post_count: + if dev_count: + raise ValueError( + 'Cannot combine postN and devN - no mapping in %r' + % (version_string,)) + result = result.increment().to_dev(post_count) + return result + + def brief_string(self): + """Return the short version minus any alpha/beta tags.""" + return "%s.%s.%s" % (self._major, self._minor, self._patch) + + def debian_string(self): + """Return the version number to use when building a debian package. + + This translates the PEP440/semver precedence rules into Debian version + sorting operators. + """ + return self._long_version("~") + + def decrement(self): + """Return a decremented SemanticVersion. + + Decrementing versions doesn't make a lot of sense - this method only + exists to support rendering of pre-release versions strings into + serialisations (such as rpm) with no sort-before operator. + + The 9999 magic version component is from the spec on this - pbr-semver. + + :return: A new SemanticVersion object. + """ + if self._patch: + new_patch = self._patch - 1 + new_minor = self._minor + new_major = self._major + else: + new_patch = 9999 + if self._minor: + new_minor = self._minor - 1 + new_major = self._major + else: + new_minor = 9999 + if self._major: + new_major = self._major - 1 + else: + new_major = 0 + return SemanticVersion( + new_major, new_minor, new_patch) + + def increment(self, minor=False, major=False): + """Return an incremented SemanticVersion. + + The default behaviour is to perform a patch level increment. When + incrementing a prerelease version, the patch level is not changed + - the prerelease serial is changed (e.g. beta 0 -> beta 1). + + Incrementing non-pre-release versions will not introduce pre-release + versions - except when doing a patch incremental to a pre-release + version the new version will only consist of major/minor/patch. + + :param minor: Increment the minor version. + :param major: Increment the major version. + :return: A new SemanticVersion object. + """ + if self._prerelease_type: + new_prerelease_type = self._prerelease_type + new_prerelease = self._prerelease + 1 + new_patch = self._patch + else: + new_prerelease_type = None + new_prerelease = None + new_patch = self._patch + 1 + if minor: + new_minor = self._minor + 1 + new_patch = 0 + new_prerelease_type = None + new_prerelease = None + else: + new_minor = self._minor + if major: + new_major = self._major + 1 + new_minor = 0 + new_patch = 0 + new_prerelease_type = None + new_prerelease = None + else: + new_major = self._major + return SemanticVersion( + new_major, new_minor, new_patch, + new_prerelease_type, new_prerelease) + + def _long_version(self, pre_separator, rc_marker=""): + """Construct a long string version of this semver. + + :param pre_separator: What separator to use between components + that sort before rather than after. If None, use . and lower the + version number of the component to preserve sorting. (Used for + rpm support) + """ + if ((self._prerelease_type or self._dev_count) + and pre_separator is None): + segments = [self.decrement().brief_string()] + pre_separator = "." + else: + segments = [self.brief_string()] + if self._prerelease_type: + segments.append( + "%s%s%s%s" % (pre_separator, rc_marker, self._prerelease_type, + self._prerelease)) + if self._dev_count: + if not self._prerelease_type: + segments.append(pre_separator) + else: + segments.append('.') + segments.append('dev') + segments.append(self._dev_count) + return "".join(str(s) for s in segments) + + def release_string(self): + """Return the full version of the package. + + This including suffixes indicating VCS status. + """ + return self._long_version(".", "0") + + def rpm_string(self): + """Return the version number to use when building an RPM package. + + This translates the PEP440/semver precedence rules into RPM version + sorting operators. Because RPM has no sort-before operator (such as the + ~ operator in dpkg), we show all prerelease versions as being versions + of the release before. + """ + return self._long_version(None) + + def to_dev(self, dev_count): + """Return a development version of this semver. + + :param dev_count: The number of commits since the last release. + """ + return SemanticVersion( + self._major, self._minor, self._patch, self._prerelease_type, + self._prerelease, dev_count=dev_count) + + def version_tuple(self): + """Present the version as a version_info tuple. + + For documentation on version_info tuples see the Python + documentation for sys.version_info. + + Since semver and PEP-440 represent overlapping but not subsets of + versions, we have to have some heuristic / mapping rules, and have + extended the releaselevel field to have alphadev, betadev and + candidatedev values. When they are present the dev count is used + to provide the serial. + - a/b/rc take precedence. + - if there is no pre-release version the dev version is used. + - serial is taken from the dev/a/b/c component. + - final non-dev versions never get serials. + """ + segments = [self._major, self._minor, self._patch] + if self._prerelease_type: + type_map = {('a', False): 'alpha', + ('b', False): 'beta', + ('rc', False): 'candidate', + ('a', True): 'alphadev', + ('b', True): 'betadev', + ('rc', True): 'candidatedev', + } + segments.append( + type_map[(self._prerelease_type, bool(self._dev_count))]) + segments.append(self._dev_count or self._prerelease) + elif self._dev_count: + segments.append('dev') + segments.append(self._dev_count - 1) + else: + segments.append('final') + segments.append(0) + return tuple(segments) + + +class VersionInfo(object): + + def __init__(self, package): + """Object that understands versioning for a package + + :param package: name of the python package, such as glance, or + python-glanceclient + """ + self.package = package + self.version = None + self._cached_version = None + self._semantic = None + + def __str__(self): + """Make the VersionInfo object behave like a string.""" + return self.version_string() + + def __repr__(self): + """Include the name.""" + return "pbr.version.VersionInfo(%s:%s)" % ( + self.package, self.version_string()) + + def _get_version_from_pkg_resources(self): + """Obtain a version from pkg_resources or setup-time logic if missing. + + This will try to get the version of the package from the pkg_resources + record associated with the package, and if there is no such record + falls back to the logic sdist would use. + """ + # Lazy import because pkg_resources is costly to import so defer until + # we absolutely need it. + import pkg_resources + try: + requirement = pkg_resources.Requirement.parse(self.package) + provider = pkg_resources.get_provider(requirement) + result_string = provider.version + except pkg_resources.DistributionNotFound: + # The most likely cause for this is running tests in a tree + # produced from a tarball where the package itself has not been + # installed into anything. Revert to setup-time logic. + from pbr import packaging + result_string = packaging.get_version(self.package) + return SemanticVersion.from_pip_string(result_string) + + def release_string(self): + """Return the full version of the package. + + This including suffixes indicating VCS status. + """ + return self.semantic_version().release_string() + + def semantic_version(self): + """Return the SemanticVersion object for this version.""" + if self._semantic is None: + self._semantic = self._get_version_from_pkg_resources() + return self._semantic + + def version_string(self): + """Return the short version minus any alpha/beta tags.""" + return self.semantic_version().brief_string() + + # Compatibility functions + canonical_version_string = version_string + version_string_with_vcs = release_string + + def cached_version_string(self, prefix=""): + """Return a cached version string. + + This will return a cached version string if one is already cached, + irrespective of prefix. If none is cached, one will be created with + prefix and then cached and returned. + """ + if not self._cached_version: + self._cached_version = "%s%s" % (prefix, + self.version_string()) + return self._cached_version diff --git a/libs/pytz/__init__.py b/libs/pytz/__init__.py new file mode 100644 index 00000000..6e923173 --- /dev/null +++ b/libs/pytz/__init__.py @@ -0,0 +1,1527 @@ +''' +datetime.tzinfo timezone definitions generated from the +Olson timezone database: + + ftp://elsie.nci.nih.gov/pub/tz*.tar.gz + +See the datetime section of the Python Library Reference for information +on how to use these modules. +''' + +import sys +import datetime +import os.path + +from pytz.exceptions import AmbiguousTimeError +from pytz.exceptions import InvalidTimeError +from pytz.exceptions import NonExistentTimeError +from pytz.exceptions import UnknownTimeZoneError +from pytz.lazy import LazyDict, LazyList, LazySet +from pytz.tzinfo import unpickler, BaseTzInfo +from pytz.tzfile import build_tzinfo + + +# The IANA (nee Olson) database is updated several times a year. +OLSON_VERSION = '2018g' +VERSION = '2018.7' # pip compatible version number. +__version__ = VERSION + +OLSEN_VERSION = OLSON_VERSION # Old releases had this misspelling + +__all__ = [ + 'timezone', 'utc', 'country_timezones', 'country_names', + 'AmbiguousTimeError', 'InvalidTimeError', + 'NonExistentTimeError', 'UnknownTimeZoneError', + 'all_timezones', 'all_timezones_set', + 'common_timezones', 'common_timezones_set', + 'BaseTzInfo', +] + + +if sys.version_info[0] > 2: # Python 3.x + + # Python 3.x doesn't have unicode(), making writing code + # for Python 2.3 and Python 3.x a pain. + unicode = str + + def ascii(s): + r""" + >>> ascii('Hello') + 'Hello' + >>> ascii('\N{TRADE MARK SIGN}') #doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + UnicodeEncodeError: ... + """ + if type(s) == bytes: + s = s.decode('ASCII') + else: + s.encode('ASCII') # Raise an exception if not ASCII + return s # But the string - not a byte string. + +else: # Python 2.x + + def ascii(s): + r""" + >>> ascii('Hello') + 'Hello' + >>> ascii(u'Hello') + 'Hello' + >>> ascii(u'\N{TRADE MARK SIGN}') #doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + UnicodeEncodeError: ... + """ + return s.encode('ASCII') + + +def open_resource(name): + """Open a resource from the zoneinfo subdir for reading. + + Uses the pkg_resources module if available and no standard file + found at the calculated location. + + It is possible to specify different location for zoneinfo + subdir by using the PYTZ_TZDATADIR environment variable. + """ + name_parts = name.lstrip('/').split('/') + for part in name_parts: + if part == os.path.pardir or os.path.sep in part: + raise ValueError('Bad path segment: %r' % part) + zoneinfo_dir = os.environ.get('PYTZ_TZDATADIR', None) + if zoneinfo_dir is not None: + filename = os.path.join(zoneinfo_dir, *name_parts) + else: + filename = os.path.join(os.path.dirname(__file__), + 'zoneinfo', *name_parts) + if not os.path.exists(filename): + # http://bugs.launchpad.net/bugs/383171 - we avoid using this + # unless absolutely necessary to help when a broken version of + # pkg_resources is installed. + try: + from pkg_resources import resource_stream + except ImportError: + resource_stream = None + + if resource_stream is not None: + return resource_stream(__name__, 'zoneinfo/' + name) + return open(filename, 'rb') + + +def resource_exists(name): + """Return true if the given resource exists""" + try: + open_resource(name).close() + return True + except IOError: + return False + + +_tzinfo_cache = {} + + +def timezone(zone): + r''' Return a datetime.tzinfo implementation for the given timezone + + >>> from datetime import datetime, timedelta + >>> utc = timezone('UTC') + >>> eastern = timezone('US/Eastern') + >>> eastern.zone + 'US/Eastern' + >>> timezone(unicode('US/Eastern')) is eastern + True + >>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc) + >>> loc_dt = utc_dt.astimezone(eastern) + >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)' + >>> loc_dt.strftime(fmt) + '2002-10-27 01:00:00 EST (-0500)' + >>> (loc_dt - timedelta(minutes=10)).strftime(fmt) + '2002-10-27 00:50:00 EST (-0500)' + >>> eastern.normalize(loc_dt - timedelta(minutes=10)).strftime(fmt) + '2002-10-27 01:50:00 EDT (-0400)' + >>> (loc_dt + timedelta(minutes=10)).strftime(fmt) + '2002-10-27 01:10:00 EST (-0500)' + + Raises UnknownTimeZoneError if passed an unknown zone. + + >>> try: + ... timezone('Asia/Shangri-La') + ... except UnknownTimeZoneError: + ... print('Unknown') + Unknown + + >>> try: + ... timezone(unicode('\N{TRADE MARK SIGN}')) + ... except UnknownTimeZoneError: + ... print('Unknown') + Unknown + + ''' + if zone.upper() == 'UTC': + return utc + + try: + zone = ascii(zone) + except UnicodeEncodeError: + # All valid timezones are ASCII + raise UnknownTimeZoneError(zone) + + zone = _unmunge_zone(zone) + if zone not in _tzinfo_cache: + if zone in all_timezones_set: + fp = open_resource(zone) + try: + _tzinfo_cache[zone] = build_tzinfo(zone, fp) + finally: + fp.close() + else: + raise UnknownTimeZoneError(zone) + + return _tzinfo_cache[zone] + + +def _unmunge_zone(zone): + """Undo the time zone name munging done by older versions of pytz.""" + return zone.replace('_plus_', '+').replace('_minus_', '-') + + +ZERO = datetime.timedelta(0) +HOUR = datetime.timedelta(hours=1) + + +class UTC(BaseTzInfo): + """UTC + + Optimized UTC implementation. It unpickles using the single module global + instance defined beneath this class declaration. + """ + zone = "UTC" + + _utcoffset = ZERO + _dst = ZERO + _tzname = zone + + def fromutc(self, dt): + if dt.tzinfo is None: + return self.localize(dt) + return super(utc.__class__, self).fromutc(dt) + + def utcoffset(self, dt): + return ZERO + + def tzname(self, dt): + return "UTC" + + def dst(self, dt): + return ZERO + + def __reduce__(self): + return _UTC, () + + def localize(self, dt, is_dst=False): + '''Convert naive time to local time''' + if dt.tzinfo is not None: + raise ValueError('Not naive datetime (tzinfo is already set)') + return dt.replace(tzinfo=self) + + def normalize(self, dt, is_dst=False): + '''Correct the timezone information on the given datetime''' + if dt.tzinfo is self: + return dt + if dt.tzinfo is None: + raise ValueError('Naive time - no tzinfo set') + return dt.astimezone(self) + + def __repr__(self): + return "" + + def __str__(self): + return "UTC" + + +UTC = utc = UTC() # UTC is a singleton + + +def _UTC(): + """Factory function for utc unpickling. + + Makes sure that unpickling a utc instance always returns the same + module global. + + These examples belong in the UTC class above, but it is obscured; or in + the README.txt, but we are not depending on Python 2.4 so integrating + the README.txt examples with the unit tests is not trivial. + + >>> import datetime, pickle + >>> dt = datetime.datetime(2005, 3, 1, 14, 13, 21, tzinfo=utc) + >>> naive = dt.replace(tzinfo=None) + >>> p = pickle.dumps(dt, 1) + >>> naive_p = pickle.dumps(naive, 1) + >>> len(p) - len(naive_p) + 17 + >>> new = pickle.loads(p) + >>> new == dt + True + >>> new is dt + False + >>> new.tzinfo is dt.tzinfo + True + >>> utc is UTC is timezone('UTC') + True + >>> utc is timezone('GMT') + False + """ + return utc +_UTC.__safe_for_unpickling__ = True + + +def _p(*args): + """Factory function for unpickling pytz tzinfo instances. + + Just a wrapper around tzinfo.unpickler to save a few bytes in each pickle + by shortening the path. + """ + return unpickler(*args) +_p.__safe_for_unpickling__ = True + + +class _CountryTimezoneDict(LazyDict): + """Map ISO 3166 country code to a list of timezone names commonly used + in that country. + + iso3166_code is the two letter code used to identify the country. + + >>> def print_list(list_of_strings): + ... 'We use a helper so doctests work under Python 2.3 -> 3.x' + ... for s in list_of_strings: + ... print(s) + + >>> print_list(country_timezones['nz']) + Pacific/Auckland + Pacific/Chatham + >>> print_list(country_timezones['ch']) + Europe/Zurich + >>> print_list(country_timezones['CH']) + Europe/Zurich + >>> print_list(country_timezones[unicode('ch')]) + Europe/Zurich + >>> print_list(country_timezones['XXX']) + Traceback (most recent call last): + ... + KeyError: 'XXX' + + Previously, this information was exposed as a function rather than a + dictionary. This is still supported:: + + >>> print_list(country_timezones('nz')) + Pacific/Auckland + Pacific/Chatham + """ + def __call__(self, iso3166_code): + """Backwards compatibility.""" + return self[iso3166_code] + + def _fill(self): + data = {} + zone_tab = open_resource('zone.tab') + try: + for line in zone_tab: + line = line.decode('UTF-8') + if line.startswith('#'): + continue + code, coordinates, zone = line.split(None, 4)[:3] + if zone not in all_timezones_set: + continue + try: + data[code].append(zone) + except KeyError: + data[code] = [zone] + self.data = data + finally: + zone_tab.close() + +country_timezones = _CountryTimezoneDict() + + +class _CountryNameDict(LazyDict): + '''Dictionary proving ISO3166 code -> English name. + + >>> print(country_names['au']) + Australia + ''' + def _fill(self): + data = {} + zone_tab = open_resource('iso3166.tab') + try: + for line in zone_tab.readlines(): + line = line.decode('UTF-8') + if line.startswith('#'): + continue + code, name = line.split(None, 1) + data[code] = name.strip() + self.data = data + finally: + zone_tab.close() + +country_names = _CountryNameDict() + + +# Time-zone info based solely on fixed offsets + +class _FixedOffset(datetime.tzinfo): + + zone = None # to match the standard pytz API + + def __init__(self, minutes): + if abs(minutes) >= 1440: + raise ValueError("absolute offset is too large", minutes) + self._minutes = minutes + self._offset = datetime.timedelta(minutes=minutes) + + def utcoffset(self, dt): + return self._offset + + def __reduce__(self): + return FixedOffset, (self._minutes, ) + + def dst(self, dt): + return ZERO + + def tzname(self, dt): + return None + + def __repr__(self): + return 'pytz.FixedOffset(%d)' % self._minutes + + def localize(self, dt, is_dst=False): + '''Convert naive time to local time''' + if dt.tzinfo is not None: + raise ValueError('Not naive datetime (tzinfo is already set)') + return dt.replace(tzinfo=self) + + def normalize(self, dt, is_dst=False): + '''Correct the timezone information on the given datetime''' + if dt.tzinfo is self: + return dt + if dt.tzinfo is None: + raise ValueError('Naive time - no tzinfo set') + return dt.astimezone(self) + + +def FixedOffset(offset, _tzinfos={}): + """return a fixed-offset timezone based off a number of minutes. + + >>> one = FixedOffset(-330) + >>> one + pytz.FixedOffset(-330) + >>> str(one.utcoffset(datetime.datetime.now())) + '-1 day, 18:30:00' + >>> str(one.dst(datetime.datetime.now())) + '0:00:00' + + >>> two = FixedOffset(1380) + >>> two + pytz.FixedOffset(1380) + >>> str(two.utcoffset(datetime.datetime.now())) + '23:00:00' + >>> str(two.dst(datetime.datetime.now())) + '0:00:00' + + The datetime.timedelta must be between the range of -1 and 1 day, + non-inclusive. + + >>> FixedOffset(1440) + Traceback (most recent call last): + ... + ValueError: ('absolute offset is too large', 1440) + + >>> FixedOffset(-1440) + Traceback (most recent call last): + ... + ValueError: ('absolute offset is too large', -1440) + + An offset of 0 is special-cased to return UTC. + + >>> FixedOffset(0) is UTC + True + + There should always be only one instance of a FixedOffset per timedelta. + This should be true for multiple creation calls. + + >>> FixedOffset(-330) is one + True + >>> FixedOffset(1380) is two + True + + It should also be true for pickling. + + >>> import pickle + >>> pickle.loads(pickle.dumps(one)) is one + True + >>> pickle.loads(pickle.dumps(two)) is two + True + """ + if offset == 0: + return UTC + + info = _tzinfos.get(offset) + if info is None: + # We haven't seen this one before. we need to save it. + + # Use setdefault to avoid a race condition and make sure we have + # only one + info = _tzinfos.setdefault(offset, _FixedOffset(offset)) + + return info + +FixedOffset.__safe_for_unpickling__ = True + + +def _test(): + import doctest + sys.path.insert(0, os.pardir) + import pytz + return doctest.testmod(pytz) + +if __name__ == '__main__': + _test() +all_timezones = \ +['Africa/Abidjan', + 'Africa/Accra', + 'Africa/Addis_Ababa', + 'Africa/Algiers', + 'Africa/Asmara', + 'Africa/Asmera', + 'Africa/Bamako', + 'Africa/Bangui', + 'Africa/Banjul', + 'Africa/Bissau', + 'Africa/Blantyre', + 'Africa/Brazzaville', + 'Africa/Bujumbura', + 'Africa/Cairo', + 'Africa/Casablanca', + 'Africa/Ceuta', + 'Africa/Conakry', + 'Africa/Dakar', + 'Africa/Dar_es_Salaam', + 'Africa/Djibouti', + 'Africa/Douala', + 'Africa/El_Aaiun', + 'Africa/Freetown', + 'Africa/Gaborone', + 'Africa/Harare', + 'Africa/Johannesburg', + 'Africa/Juba', + 'Africa/Kampala', + 'Africa/Khartoum', + 'Africa/Kigali', + 'Africa/Kinshasa', + 'Africa/Lagos', + 'Africa/Libreville', + 'Africa/Lome', + 'Africa/Luanda', + 'Africa/Lubumbashi', + 'Africa/Lusaka', + 'Africa/Malabo', + 'Africa/Maputo', + 'Africa/Maseru', + 'Africa/Mbabane', + 'Africa/Mogadishu', + 'Africa/Monrovia', + 'Africa/Nairobi', + 'Africa/Ndjamena', + 'Africa/Niamey', + 'Africa/Nouakchott', + 'Africa/Ouagadougou', + 'Africa/Porto-Novo', + 'Africa/Sao_Tome', + 'Africa/Timbuktu', + 'Africa/Tripoli', + 'Africa/Tunis', + 'Africa/Windhoek', + 'America/Adak', + 'America/Anchorage', + 'America/Anguilla', + 'America/Antigua', + 'America/Araguaina', + 'America/Argentina/Buenos_Aires', + 'America/Argentina/Catamarca', + 'America/Argentina/ComodRivadavia', + 'America/Argentina/Cordoba', + 'America/Argentina/Jujuy', + 'America/Argentina/La_Rioja', + 'America/Argentina/Mendoza', + 'America/Argentina/Rio_Gallegos', + 'America/Argentina/Salta', + 'America/Argentina/San_Juan', + 'America/Argentina/San_Luis', + 'America/Argentina/Tucuman', + 'America/Argentina/Ushuaia', + 'America/Aruba', + 'America/Asuncion', + 'America/Atikokan', + 'America/Atka', + 'America/Bahia', + 'America/Bahia_Banderas', + 'America/Barbados', + 'America/Belem', + 'America/Belize', + 'America/Blanc-Sablon', + 'America/Boa_Vista', + 'America/Bogota', + 'America/Boise', + 'America/Buenos_Aires', + 'America/Cambridge_Bay', + 'America/Campo_Grande', + 'America/Cancun', + 'America/Caracas', + 'America/Catamarca', + 'America/Cayenne', + 'America/Cayman', + 'America/Chicago', + 'America/Chihuahua', + 'America/Coral_Harbour', + 'America/Cordoba', + 'America/Costa_Rica', + 'America/Creston', + 'America/Cuiaba', + 'America/Curacao', + 'America/Danmarkshavn', + 'America/Dawson', + 'America/Dawson_Creek', + 'America/Denver', + 'America/Detroit', + 'America/Dominica', + 'America/Edmonton', + 'America/Eirunepe', + 'America/El_Salvador', + 'America/Ensenada', + 'America/Fort_Nelson', + 'America/Fort_Wayne', + 'America/Fortaleza', + 'America/Glace_Bay', + 'America/Godthab', + 'America/Goose_Bay', + 'America/Grand_Turk', + 'America/Grenada', + 'America/Guadeloupe', + 'America/Guatemala', + 'America/Guayaquil', + 'America/Guyana', + 'America/Halifax', + 'America/Havana', + 'America/Hermosillo', + 'America/Indiana/Indianapolis', + 'America/Indiana/Knox', + 'America/Indiana/Marengo', + 'America/Indiana/Petersburg', + 'America/Indiana/Tell_City', + 'America/Indiana/Vevay', + 'America/Indiana/Vincennes', + 'America/Indiana/Winamac', + 'America/Indianapolis', + 'America/Inuvik', + 'America/Iqaluit', + 'America/Jamaica', + 'America/Jujuy', + 'America/Juneau', + 'America/Kentucky/Louisville', + 'America/Kentucky/Monticello', + 'America/Knox_IN', + 'America/Kralendijk', + 'America/La_Paz', + 'America/Lima', + 'America/Los_Angeles', + 'America/Louisville', + 'America/Lower_Princes', + 'America/Maceio', + 'America/Managua', + 'America/Manaus', + 'America/Marigot', + 'America/Martinique', + 'America/Matamoros', + 'America/Mazatlan', + 'America/Mendoza', + 'America/Menominee', + 'America/Merida', + 'America/Metlakatla', + 'America/Mexico_City', + 'America/Miquelon', + 'America/Moncton', + 'America/Monterrey', + 'America/Montevideo', + 'America/Montreal', + 'America/Montserrat', + 'America/Nassau', + 'America/New_York', + 'America/Nipigon', + 'America/Nome', + 'America/Noronha', + 'America/North_Dakota/Beulah', + 'America/North_Dakota/Center', + 'America/North_Dakota/New_Salem', + 'America/Ojinaga', + 'America/Panama', + 'America/Pangnirtung', + 'America/Paramaribo', + 'America/Phoenix', + 'America/Port-au-Prince', + 'America/Port_of_Spain', + 'America/Porto_Acre', + 'America/Porto_Velho', + 'America/Puerto_Rico', + 'America/Punta_Arenas', + 'America/Rainy_River', + 'America/Rankin_Inlet', + 'America/Recife', + 'America/Regina', + 'America/Resolute', + 'America/Rio_Branco', + 'America/Rosario', + 'America/Santa_Isabel', + 'America/Santarem', + 'America/Santiago', + 'America/Santo_Domingo', + 'America/Sao_Paulo', + 'America/Scoresbysund', + 'America/Shiprock', + 'America/Sitka', + 'America/St_Barthelemy', + 'America/St_Johns', + 'America/St_Kitts', + 'America/St_Lucia', + 'America/St_Thomas', + 'America/St_Vincent', + 'America/Swift_Current', + 'America/Tegucigalpa', + 'America/Thule', + 'America/Thunder_Bay', + 'America/Tijuana', + 'America/Toronto', + 'America/Tortola', + 'America/Vancouver', + 'America/Virgin', + 'America/Whitehorse', + 'America/Winnipeg', + 'America/Yakutat', + 'America/Yellowknife', + 'Antarctica/Casey', + 'Antarctica/Davis', + 'Antarctica/DumontDUrville', + 'Antarctica/Macquarie', + 'Antarctica/Mawson', + 'Antarctica/McMurdo', + 'Antarctica/Palmer', + 'Antarctica/Rothera', + 'Antarctica/South_Pole', + 'Antarctica/Syowa', + 'Antarctica/Troll', + 'Antarctica/Vostok', + 'Arctic/Longyearbyen', + 'Asia/Aden', + 'Asia/Almaty', + 'Asia/Amman', + 'Asia/Anadyr', + 'Asia/Aqtau', + 'Asia/Aqtobe', + 'Asia/Ashgabat', + 'Asia/Ashkhabad', + 'Asia/Atyrau', + 'Asia/Baghdad', + 'Asia/Bahrain', + 'Asia/Baku', + 'Asia/Bangkok', + 'Asia/Barnaul', + 'Asia/Beirut', + 'Asia/Bishkek', + 'Asia/Brunei', + 'Asia/Calcutta', + 'Asia/Chita', + 'Asia/Choibalsan', + 'Asia/Chongqing', + 'Asia/Chungking', + 'Asia/Colombo', + 'Asia/Dacca', + 'Asia/Damascus', + 'Asia/Dhaka', + 'Asia/Dili', + 'Asia/Dubai', + 'Asia/Dushanbe', + 'Asia/Famagusta', + 'Asia/Gaza', + 'Asia/Harbin', + 'Asia/Hebron', + 'Asia/Ho_Chi_Minh', + 'Asia/Hong_Kong', + 'Asia/Hovd', + 'Asia/Irkutsk', + 'Asia/Istanbul', + 'Asia/Jakarta', + 'Asia/Jayapura', + 'Asia/Jerusalem', + 'Asia/Kabul', + 'Asia/Kamchatka', + 'Asia/Karachi', + 'Asia/Kashgar', + 'Asia/Kathmandu', + 'Asia/Katmandu', + 'Asia/Khandyga', + 'Asia/Kolkata', + 'Asia/Krasnoyarsk', + 'Asia/Kuala_Lumpur', + 'Asia/Kuching', + 'Asia/Kuwait', + 'Asia/Macao', + 'Asia/Macau', + 'Asia/Magadan', + 'Asia/Makassar', + 'Asia/Manila', + 'Asia/Muscat', + 'Asia/Nicosia', + 'Asia/Novokuznetsk', + 'Asia/Novosibirsk', + 'Asia/Omsk', + 'Asia/Oral', + 'Asia/Phnom_Penh', + 'Asia/Pontianak', + 'Asia/Pyongyang', + 'Asia/Qatar', + 'Asia/Qyzylorda', + 'Asia/Rangoon', + 'Asia/Riyadh', + 'Asia/Saigon', + 'Asia/Sakhalin', + 'Asia/Samarkand', + 'Asia/Seoul', + 'Asia/Shanghai', + 'Asia/Singapore', + 'Asia/Srednekolymsk', + 'Asia/Taipei', + 'Asia/Tashkent', + 'Asia/Tbilisi', + 'Asia/Tehran', + 'Asia/Tel_Aviv', + 'Asia/Thimbu', + 'Asia/Thimphu', + 'Asia/Tokyo', + 'Asia/Tomsk', + 'Asia/Ujung_Pandang', + 'Asia/Ulaanbaatar', + 'Asia/Ulan_Bator', + 'Asia/Urumqi', + 'Asia/Ust-Nera', + 'Asia/Vientiane', + 'Asia/Vladivostok', + 'Asia/Yakutsk', + 'Asia/Yangon', + 'Asia/Yekaterinburg', + 'Asia/Yerevan', + 'Atlantic/Azores', + 'Atlantic/Bermuda', + 'Atlantic/Canary', + 'Atlantic/Cape_Verde', + 'Atlantic/Faeroe', + 'Atlantic/Faroe', + 'Atlantic/Jan_Mayen', + 'Atlantic/Madeira', + 'Atlantic/Reykjavik', + 'Atlantic/South_Georgia', + 'Atlantic/St_Helena', + 'Atlantic/Stanley', + 'Australia/ACT', + 'Australia/Adelaide', + 'Australia/Brisbane', + 'Australia/Broken_Hill', + 'Australia/Canberra', + 'Australia/Currie', + 'Australia/Darwin', + 'Australia/Eucla', + 'Australia/Hobart', + 'Australia/LHI', + 'Australia/Lindeman', + 'Australia/Lord_Howe', + 'Australia/Melbourne', + 'Australia/NSW', + 'Australia/North', + 'Australia/Perth', + 'Australia/Queensland', + 'Australia/South', + 'Australia/Sydney', + 'Australia/Tasmania', + 'Australia/Victoria', + 'Australia/West', + 'Australia/Yancowinna', + 'Brazil/Acre', + 'Brazil/DeNoronha', + 'Brazil/East', + 'Brazil/West', + 'CET', + 'CST6CDT', + 'Canada/Atlantic', + 'Canada/Central', + 'Canada/Eastern', + 'Canada/Mountain', + 'Canada/Newfoundland', + 'Canada/Pacific', + 'Canada/Saskatchewan', + 'Canada/Yukon', + 'Chile/Continental', + 'Chile/EasterIsland', + 'Cuba', + 'EET', + 'EST', + 'EST5EDT', + 'Egypt', + 'Eire', + 'Etc/GMT', + 'Etc/GMT+0', + 'Etc/GMT+1', + 'Etc/GMT+10', + 'Etc/GMT+11', + 'Etc/GMT+12', + 'Etc/GMT+2', + 'Etc/GMT+3', + 'Etc/GMT+4', + 'Etc/GMT+5', + 'Etc/GMT+6', + 'Etc/GMT+7', + 'Etc/GMT+8', + 'Etc/GMT+9', + 'Etc/GMT-0', + 'Etc/GMT-1', + 'Etc/GMT-10', + 'Etc/GMT-11', + 'Etc/GMT-12', + 'Etc/GMT-13', + 'Etc/GMT-14', + 'Etc/GMT-2', + 'Etc/GMT-3', + 'Etc/GMT-4', + 'Etc/GMT-5', + 'Etc/GMT-6', + 'Etc/GMT-7', + 'Etc/GMT-8', + 'Etc/GMT-9', + 'Etc/GMT0', + 'Etc/Greenwich', + 'Etc/UCT', + 'Etc/UTC', + 'Etc/Universal', + 'Etc/Zulu', + 'Europe/Amsterdam', + 'Europe/Andorra', + 'Europe/Astrakhan', + 'Europe/Athens', + 'Europe/Belfast', + 'Europe/Belgrade', + 'Europe/Berlin', + 'Europe/Bratislava', + 'Europe/Brussels', + 'Europe/Bucharest', + 'Europe/Budapest', + 'Europe/Busingen', + 'Europe/Chisinau', + 'Europe/Copenhagen', + 'Europe/Dublin', + 'Europe/Gibraltar', + 'Europe/Guernsey', + 'Europe/Helsinki', + 'Europe/Isle_of_Man', + 'Europe/Istanbul', + 'Europe/Jersey', + 'Europe/Kaliningrad', + 'Europe/Kiev', + 'Europe/Kirov', + 'Europe/Lisbon', + 'Europe/Ljubljana', + 'Europe/London', + 'Europe/Luxembourg', + 'Europe/Madrid', + 'Europe/Malta', + 'Europe/Mariehamn', + 'Europe/Minsk', + 'Europe/Monaco', + 'Europe/Moscow', + 'Europe/Nicosia', + 'Europe/Oslo', + 'Europe/Paris', + 'Europe/Podgorica', + 'Europe/Prague', + 'Europe/Riga', + 'Europe/Rome', + 'Europe/Samara', + 'Europe/San_Marino', + 'Europe/Sarajevo', + 'Europe/Saratov', + 'Europe/Simferopol', + 'Europe/Skopje', + 'Europe/Sofia', + 'Europe/Stockholm', + 'Europe/Tallinn', + 'Europe/Tirane', + 'Europe/Tiraspol', + 'Europe/Ulyanovsk', + 'Europe/Uzhgorod', + 'Europe/Vaduz', + 'Europe/Vatican', + 'Europe/Vienna', + 'Europe/Vilnius', + 'Europe/Volgograd', + 'Europe/Warsaw', + 'Europe/Zagreb', + 'Europe/Zaporozhye', + 'Europe/Zurich', + 'GB', + 'GB-Eire', + 'GMT', + 'GMT+0', + 'GMT-0', + 'GMT0', + 'Greenwich', + 'HST', + 'Hongkong', + 'Iceland', + 'Indian/Antananarivo', + 'Indian/Chagos', + 'Indian/Christmas', + 'Indian/Cocos', + 'Indian/Comoro', + 'Indian/Kerguelen', + 'Indian/Mahe', + 'Indian/Maldives', + 'Indian/Mauritius', + 'Indian/Mayotte', + 'Indian/Reunion', + 'Iran', + 'Israel', + 'Jamaica', + 'Japan', + 'Kwajalein', + 'Libya', + 'MET', + 'MST', + 'MST7MDT', + 'Mexico/BajaNorte', + 'Mexico/BajaSur', + 'Mexico/General', + 'NZ', + 'NZ-CHAT', + 'Navajo', + 'PRC', + 'PST8PDT', + 'Pacific/Apia', + 'Pacific/Auckland', + 'Pacific/Bougainville', + 'Pacific/Chatham', + 'Pacific/Chuuk', + 'Pacific/Easter', + 'Pacific/Efate', + 'Pacific/Enderbury', + 'Pacific/Fakaofo', + 'Pacific/Fiji', + 'Pacific/Funafuti', + 'Pacific/Galapagos', + 'Pacific/Gambier', + 'Pacific/Guadalcanal', + 'Pacific/Guam', + 'Pacific/Honolulu', + 'Pacific/Johnston', + 'Pacific/Kiritimati', + 'Pacific/Kosrae', + 'Pacific/Kwajalein', + 'Pacific/Majuro', + 'Pacific/Marquesas', + 'Pacific/Midway', + 'Pacific/Nauru', + 'Pacific/Niue', + 'Pacific/Norfolk', + 'Pacific/Noumea', + 'Pacific/Pago_Pago', + 'Pacific/Palau', + 'Pacific/Pitcairn', + 'Pacific/Pohnpei', + 'Pacific/Ponape', + 'Pacific/Port_Moresby', + 'Pacific/Rarotonga', + 'Pacific/Saipan', + 'Pacific/Samoa', + 'Pacific/Tahiti', + 'Pacific/Tarawa', + 'Pacific/Tongatapu', + 'Pacific/Truk', + 'Pacific/Wake', + 'Pacific/Wallis', + 'Pacific/Yap', + 'Poland', + 'Portugal', + 'ROC', + 'ROK', + 'Singapore', + 'Turkey', + 'UCT', + 'US/Alaska', + 'US/Aleutian', + 'US/Arizona', + 'US/Central', + 'US/East-Indiana', + 'US/Eastern', + 'US/Hawaii', + 'US/Indiana-Starke', + 'US/Michigan', + 'US/Mountain', + 'US/Pacific', + 'US/Samoa', + 'UTC', + 'Universal', + 'W-SU', + 'WET', + 'Zulu'] +all_timezones = LazyList( + tz for tz in all_timezones if resource_exists(tz)) + +all_timezones_set = LazySet(all_timezones) +common_timezones = \ +['Africa/Abidjan', + 'Africa/Accra', + 'Africa/Addis_Ababa', + 'Africa/Algiers', + 'Africa/Asmara', + 'Africa/Bamako', + 'Africa/Bangui', + 'Africa/Banjul', + 'Africa/Bissau', + 'Africa/Blantyre', + 'Africa/Brazzaville', + 'Africa/Bujumbura', + 'Africa/Cairo', + 'Africa/Casablanca', + 'Africa/Ceuta', + 'Africa/Conakry', + 'Africa/Dakar', + 'Africa/Dar_es_Salaam', + 'Africa/Djibouti', + 'Africa/Douala', + 'Africa/El_Aaiun', + 'Africa/Freetown', + 'Africa/Gaborone', + 'Africa/Harare', + 'Africa/Johannesburg', + 'Africa/Juba', + 'Africa/Kampala', + 'Africa/Khartoum', + 'Africa/Kigali', + 'Africa/Kinshasa', + 'Africa/Lagos', + 'Africa/Libreville', + 'Africa/Lome', + 'Africa/Luanda', + 'Africa/Lubumbashi', + 'Africa/Lusaka', + 'Africa/Malabo', + 'Africa/Maputo', + 'Africa/Maseru', + 'Africa/Mbabane', + 'Africa/Mogadishu', + 'Africa/Monrovia', + 'Africa/Nairobi', + 'Africa/Ndjamena', + 'Africa/Niamey', + 'Africa/Nouakchott', + 'Africa/Ouagadougou', + 'Africa/Porto-Novo', + 'Africa/Sao_Tome', + 'Africa/Tripoli', + 'Africa/Tunis', + 'Africa/Windhoek', + 'America/Adak', + 'America/Anchorage', + 'America/Anguilla', + 'America/Antigua', + 'America/Araguaina', + 'America/Argentina/Buenos_Aires', + 'America/Argentina/Catamarca', + 'America/Argentina/Cordoba', + 'America/Argentina/Jujuy', + 'America/Argentina/La_Rioja', + 'America/Argentina/Mendoza', + 'America/Argentina/Rio_Gallegos', + 'America/Argentina/Salta', + 'America/Argentina/San_Juan', + 'America/Argentina/San_Luis', + 'America/Argentina/Tucuman', + 'America/Argentina/Ushuaia', + 'America/Aruba', + 'America/Asuncion', + 'America/Atikokan', + 'America/Bahia', + 'America/Bahia_Banderas', + 'America/Barbados', + 'America/Belem', + 'America/Belize', + 'America/Blanc-Sablon', + 'America/Boa_Vista', + 'America/Bogota', + 'America/Boise', + 'America/Cambridge_Bay', + 'America/Campo_Grande', + 'America/Cancun', + 'America/Caracas', + 'America/Cayenne', + 'America/Cayman', + 'America/Chicago', + 'America/Chihuahua', + 'America/Costa_Rica', + 'America/Creston', + 'America/Cuiaba', + 'America/Curacao', + 'America/Danmarkshavn', + 'America/Dawson', + 'America/Dawson_Creek', + 'America/Denver', + 'America/Detroit', + 'America/Dominica', + 'America/Edmonton', + 'America/Eirunepe', + 'America/El_Salvador', + 'America/Fort_Nelson', + 'America/Fortaleza', + 'America/Glace_Bay', + 'America/Godthab', + 'America/Goose_Bay', + 'America/Grand_Turk', + 'America/Grenada', + 'America/Guadeloupe', + 'America/Guatemala', + 'America/Guayaquil', + 'America/Guyana', + 'America/Halifax', + 'America/Havana', + 'America/Hermosillo', + 'America/Indiana/Indianapolis', + 'America/Indiana/Knox', + 'America/Indiana/Marengo', + 'America/Indiana/Petersburg', + 'America/Indiana/Tell_City', + 'America/Indiana/Vevay', + 'America/Indiana/Vincennes', + 'America/Indiana/Winamac', + 'America/Inuvik', + 'America/Iqaluit', + 'America/Jamaica', + 'America/Juneau', + 'America/Kentucky/Louisville', + 'America/Kentucky/Monticello', + 'America/Kralendijk', + 'America/La_Paz', + 'America/Lima', + 'America/Los_Angeles', + 'America/Lower_Princes', + 'America/Maceio', + 'America/Managua', + 'America/Manaus', + 'America/Marigot', + 'America/Martinique', + 'America/Matamoros', + 'America/Mazatlan', + 'America/Menominee', + 'America/Merida', + 'America/Metlakatla', + 'America/Mexico_City', + 'America/Miquelon', + 'America/Moncton', + 'America/Monterrey', + 'America/Montevideo', + 'America/Montserrat', + 'America/Nassau', + 'America/New_York', + 'America/Nipigon', + 'America/Nome', + 'America/Noronha', + 'America/North_Dakota/Beulah', + 'America/North_Dakota/Center', + 'America/North_Dakota/New_Salem', + 'America/Ojinaga', + 'America/Panama', + 'America/Pangnirtung', + 'America/Paramaribo', + 'America/Phoenix', + 'America/Port-au-Prince', + 'America/Port_of_Spain', + 'America/Porto_Velho', + 'America/Puerto_Rico', + 'America/Punta_Arenas', + 'America/Rainy_River', + 'America/Rankin_Inlet', + 'America/Recife', + 'America/Regina', + 'America/Resolute', + 'America/Rio_Branco', + 'America/Santarem', + 'America/Santiago', + 'America/Santo_Domingo', + 'America/Sao_Paulo', + 'America/Scoresbysund', + 'America/Sitka', + 'America/St_Barthelemy', + 'America/St_Johns', + 'America/St_Kitts', + 'America/St_Lucia', + 'America/St_Thomas', + 'America/St_Vincent', + 'America/Swift_Current', + 'America/Tegucigalpa', + 'America/Thule', + 'America/Thunder_Bay', + 'America/Tijuana', + 'America/Toronto', + 'America/Tortola', + 'America/Vancouver', + 'America/Whitehorse', + 'America/Winnipeg', + 'America/Yakutat', + 'America/Yellowknife', + 'Antarctica/Casey', + 'Antarctica/Davis', + 'Antarctica/DumontDUrville', + 'Antarctica/Macquarie', + 'Antarctica/Mawson', + 'Antarctica/McMurdo', + 'Antarctica/Palmer', + 'Antarctica/Rothera', + 'Antarctica/Syowa', + 'Antarctica/Troll', + 'Antarctica/Vostok', + 'Arctic/Longyearbyen', + 'Asia/Aden', + 'Asia/Almaty', + 'Asia/Amman', + 'Asia/Anadyr', + 'Asia/Aqtau', + 'Asia/Aqtobe', + 'Asia/Ashgabat', + 'Asia/Atyrau', + 'Asia/Baghdad', + 'Asia/Bahrain', + 'Asia/Baku', + 'Asia/Bangkok', + 'Asia/Barnaul', + 'Asia/Beirut', + 'Asia/Bishkek', + 'Asia/Brunei', + 'Asia/Chita', + 'Asia/Choibalsan', + 'Asia/Colombo', + 'Asia/Damascus', + 'Asia/Dhaka', + 'Asia/Dili', + 'Asia/Dubai', + 'Asia/Dushanbe', + 'Asia/Famagusta', + 'Asia/Gaza', + 'Asia/Hebron', + 'Asia/Ho_Chi_Minh', + 'Asia/Hong_Kong', + 'Asia/Hovd', + 'Asia/Irkutsk', + 'Asia/Jakarta', + 'Asia/Jayapura', + 'Asia/Jerusalem', + 'Asia/Kabul', + 'Asia/Kamchatka', + 'Asia/Karachi', + 'Asia/Kathmandu', + 'Asia/Khandyga', + 'Asia/Kolkata', + 'Asia/Krasnoyarsk', + 'Asia/Kuala_Lumpur', + 'Asia/Kuching', + 'Asia/Kuwait', + 'Asia/Macau', + 'Asia/Magadan', + 'Asia/Makassar', + 'Asia/Manila', + 'Asia/Muscat', + 'Asia/Nicosia', + 'Asia/Novokuznetsk', + 'Asia/Novosibirsk', + 'Asia/Omsk', + 'Asia/Oral', + 'Asia/Phnom_Penh', + 'Asia/Pontianak', + 'Asia/Pyongyang', + 'Asia/Qatar', + 'Asia/Qyzylorda', + 'Asia/Riyadh', + 'Asia/Sakhalin', + 'Asia/Samarkand', + 'Asia/Seoul', + 'Asia/Shanghai', + 'Asia/Singapore', + 'Asia/Srednekolymsk', + 'Asia/Taipei', + 'Asia/Tashkent', + 'Asia/Tbilisi', + 'Asia/Tehran', + 'Asia/Thimphu', + 'Asia/Tokyo', + 'Asia/Tomsk', + 'Asia/Ulaanbaatar', + 'Asia/Urumqi', + 'Asia/Ust-Nera', + 'Asia/Vientiane', + 'Asia/Vladivostok', + 'Asia/Yakutsk', + 'Asia/Yangon', + 'Asia/Yekaterinburg', + 'Asia/Yerevan', + 'Atlantic/Azores', + 'Atlantic/Bermuda', + 'Atlantic/Canary', + 'Atlantic/Cape_Verde', + 'Atlantic/Faroe', + 'Atlantic/Madeira', + 'Atlantic/Reykjavik', + 'Atlantic/South_Georgia', + 'Atlantic/St_Helena', + 'Atlantic/Stanley', + 'Australia/Adelaide', + 'Australia/Brisbane', + 'Australia/Broken_Hill', + 'Australia/Currie', + 'Australia/Darwin', + 'Australia/Eucla', + 'Australia/Hobart', + 'Australia/Lindeman', + 'Australia/Lord_Howe', + 'Australia/Melbourne', + 'Australia/Perth', + 'Australia/Sydney', + 'Canada/Atlantic', + 'Canada/Central', + 'Canada/Eastern', + 'Canada/Mountain', + 'Canada/Newfoundland', + 'Canada/Pacific', + 'Europe/Amsterdam', + 'Europe/Andorra', + 'Europe/Astrakhan', + 'Europe/Athens', + 'Europe/Belgrade', + 'Europe/Berlin', + 'Europe/Bratislava', + 'Europe/Brussels', + 'Europe/Bucharest', + 'Europe/Budapest', + 'Europe/Busingen', + 'Europe/Chisinau', + 'Europe/Copenhagen', + 'Europe/Dublin', + 'Europe/Gibraltar', + 'Europe/Guernsey', + 'Europe/Helsinki', + 'Europe/Isle_of_Man', + 'Europe/Istanbul', + 'Europe/Jersey', + 'Europe/Kaliningrad', + 'Europe/Kiev', + 'Europe/Kirov', + 'Europe/Lisbon', + 'Europe/Ljubljana', + 'Europe/London', + 'Europe/Luxembourg', + 'Europe/Madrid', + 'Europe/Malta', + 'Europe/Mariehamn', + 'Europe/Minsk', + 'Europe/Monaco', + 'Europe/Moscow', + 'Europe/Oslo', + 'Europe/Paris', + 'Europe/Podgorica', + 'Europe/Prague', + 'Europe/Riga', + 'Europe/Rome', + 'Europe/Samara', + 'Europe/San_Marino', + 'Europe/Sarajevo', + 'Europe/Saratov', + 'Europe/Simferopol', + 'Europe/Skopje', + 'Europe/Sofia', + 'Europe/Stockholm', + 'Europe/Tallinn', + 'Europe/Tirane', + 'Europe/Ulyanovsk', + 'Europe/Uzhgorod', + 'Europe/Vaduz', + 'Europe/Vatican', + 'Europe/Vienna', + 'Europe/Vilnius', + 'Europe/Volgograd', + 'Europe/Warsaw', + 'Europe/Zagreb', + 'Europe/Zaporozhye', + 'Europe/Zurich', + 'GMT', + 'Indian/Antananarivo', + 'Indian/Chagos', + 'Indian/Christmas', + 'Indian/Cocos', + 'Indian/Comoro', + 'Indian/Kerguelen', + 'Indian/Mahe', + 'Indian/Maldives', + 'Indian/Mauritius', + 'Indian/Mayotte', + 'Indian/Reunion', + 'Pacific/Apia', + 'Pacific/Auckland', + 'Pacific/Bougainville', + 'Pacific/Chatham', + 'Pacific/Chuuk', + 'Pacific/Easter', + 'Pacific/Efate', + 'Pacific/Enderbury', + 'Pacific/Fakaofo', + 'Pacific/Fiji', + 'Pacific/Funafuti', + 'Pacific/Galapagos', + 'Pacific/Gambier', + 'Pacific/Guadalcanal', + 'Pacific/Guam', + 'Pacific/Honolulu', + 'Pacific/Kiritimati', + 'Pacific/Kosrae', + 'Pacific/Kwajalein', + 'Pacific/Majuro', + 'Pacific/Marquesas', + 'Pacific/Midway', + 'Pacific/Nauru', + 'Pacific/Niue', + 'Pacific/Norfolk', + 'Pacific/Noumea', + 'Pacific/Pago_Pago', + 'Pacific/Palau', + 'Pacific/Pitcairn', + 'Pacific/Pohnpei', + 'Pacific/Port_Moresby', + 'Pacific/Rarotonga', + 'Pacific/Saipan', + 'Pacific/Tahiti', + 'Pacific/Tarawa', + 'Pacific/Tongatapu', + 'Pacific/Wake', + 'Pacific/Wallis', + 'US/Alaska', + 'US/Arizona', + 'US/Central', + 'US/Eastern', + 'US/Hawaii', + 'US/Mountain', + 'US/Pacific', + 'UTC'] +common_timezones = LazyList( + tz for tz in common_timezones if tz in all_timezones) + +common_timezones_set = LazySet(common_timezones) diff --git a/libs/pytz/exceptions.py b/libs/pytz/exceptions.py new file mode 100644 index 00000000..18df33e8 --- /dev/null +++ b/libs/pytz/exceptions.py @@ -0,0 +1,48 @@ +''' +Custom exceptions raised by pytz. +''' + +__all__ = [ + 'UnknownTimeZoneError', 'InvalidTimeError', 'AmbiguousTimeError', + 'NonExistentTimeError', +] + + +class UnknownTimeZoneError(KeyError): + '''Exception raised when pytz is passed an unknown timezone. + + >>> isinstance(UnknownTimeZoneError(), LookupError) + True + + This class is actually a subclass of KeyError to provide backwards + compatibility with code relying on the undocumented behavior of earlier + pytz releases. + + >>> isinstance(UnknownTimeZoneError(), KeyError) + True + ''' + pass + + +class InvalidTimeError(Exception): + '''Base class for invalid time exceptions.''' + + +class AmbiguousTimeError(InvalidTimeError): + '''Exception raised when attempting to create an ambiguous wallclock time. + + At the end of a DST transition period, a particular wallclock time will + occur twice (once before the clocks are set back, once after). Both + possibilities may be correct, unless further information is supplied. + + See DstTzInfo.normalize() for more info + ''' + + +class NonExistentTimeError(InvalidTimeError): + '''Exception raised when attempting to create a wallclock time that + cannot exist. + + At the start of a DST transition period, the wallclock time jumps forward. + The instants jumped over never occur. + ''' diff --git a/libs/pytz/lazy.py b/libs/pytz/lazy.py new file mode 100644 index 00000000..39344fc1 --- /dev/null +++ b/libs/pytz/lazy.py @@ -0,0 +1,172 @@ +from threading import RLock +try: + from collections.abc import Mapping as DictMixin +except ImportError: # Python < 3.3 + try: + from UserDict import DictMixin # Python 2 + except ImportError: # Python 3.0-3.3 + from collections import Mapping as DictMixin + + +# With lazy loading, we might end up with multiple threads triggering +# it at the same time. We need a lock. +_fill_lock = RLock() + + +class LazyDict(DictMixin): + """Dictionary populated on first use.""" + data = None + + def __getitem__(self, key): + if self.data is None: + _fill_lock.acquire() + try: + if self.data is None: + self._fill() + finally: + _fill_lock.release() + return self.data[key.upper()] + + def __contains__(self, key): + if self.data is None: + _fill_lock.acquire() + try: + if self.data is None: + self._fill() + finally: + _fill_lock.release() + return key in self.data + + def __iter__(self): + if self.data is None: + _fill_lock.acquire() + try: + if self.data is None: + self._fill() + finally: + _fill_lock.release() + return iter(self.data) + + def __len__(self): + if self.data is None: + _fill_lock.acquire() + try: + if self.data is None: + self._fill() + finally: + _fill_lock.release() + return len(self.data) + + def keys(self): + if self.data is None: + _fill_lock.acquire() + try: + if self.data is None: + self._fill() + finally: + _fill_lock.release() + return self.data.keys() + + +class LazyList(list): + """List populated on first use.""" + + _props = [ + '__str__', '__repr__', '__unicode__', + '__hash__', '__sizeof__', '__cmp__', + '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', + 'append', 'count', 'index', 'extend', 'insert', 'pop', 'remove', + 'reverse', 'sort', '__add__', '__radd__', '__iadd__', '__mul__', + '__rmul__', '__imul__', '__contains__', '__len__', '__nonzero__', + '__getitem__', '__setitem__', '__delitem__', '__iter__', + '__reversed__', '__getslice__', '__setslice__', '__delslice__'] + + def __new__(cls, fill_iter=None): + + if fill_iter is None: + return list() + + # We need a new class as we will be dynamically messing with its + # methods. + class LazyList(list): + pass + + fill_iter = [fill_iter] + + def lazy(name): + def _lazy(self, *args, **kw): + _fill_lock.acquire() + try: + if len(fill_iter) > 0: + list.extend(self, fill_iter.pop()) + for method_name in cls._props: + delattr(LazyList, method_name) + finally: + _fill_lock.release() + return getattr(list, name)(self, *args, **kw) + return _lazy + + for name in cls._props: + setattr(LazyList, name, lazy(name)) + + new_list = LazyList() + return new_list + +# Not all versions of Python declare the same magic methods. +# Filter out properties that don't exist in this version of Python +# from the list. +LazyList._props = [prop for prop in LazyList._props if hasattr(list, prop)] + + +class LazySet(set): + """Set populated on first use.""" + + _props = ( + '__str__', '__repr__', '__unicode__', + '__hash__', '__sizeof__', '__cmp__', + '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', + '__contains__', '__len__', '__nonzero__', + '__getitem__', '__setitem__', '__delitem__', '__iter__', + '__sub__', '__and__', '__xor__', '__or__', + '__rsub__', '__rand__', '__rxor__', '__ror__', + '__isub__', '__iand__', '__ixor__', '__ior__', + 'add', 'clear', 'copy', 'difference', 'difference_update', + 'discard', 'intersection', 'intersection_update', 'isdisjoint', + 'issubset', 'issuperset', 'pop', 'remove', + 'symmetric_difference', 'symmetric_difference_update', + 'union', 'update') + + def __new__(cls, fill_iter=None): + + if fill_iter is None: + return set() + + class LazySet(set): + pass + + fill_iter = [fill_iter] + + def lazy(name): + def _lazy(self, *args, **kw): + _fill_lock.acquire() + try: + if len(fill_iter) > 0: + for i in fill_iter.pop(): + set.add(self, i) + for method_name in cls._props: + delattr(LazySet, method_name) + finally: + _fill_lock.release() + return getattr(set, name)(self, *args, **kw) + return _lazy + + for name in cls._props: + setattr(LazySet, name, lazy(name)) + + new_set = LazySet() + return new_set + +# Not all versions of Python declare the same magic methods. +# Filter out properties that don't exist in this version of Python +# from the list. +LazySet._props = [prop for prop in LazySet._props if hasattr(set, prop)] diff --git a/libs/pytz/reference.py b/libs/pytz/reference.py new file mode 100644 index 00000000..f765ca0a --- /dev/null +++ b/libs/pytz/reference.py @@ -0,0 +1,140 @@ +''' +Reference tzinfo implementations from the Python docs. +Used for testing against as they are only correct for the years +1987 to 2006. Do not use these for real code. +''' + +from datetime import tzinfo, timedelta, datetime +from pytz import HOUR, ZERO, UTC + +__all__ = [ + 'FixedOffset', + 'LocalTimezone', + 'USTimeZone', + 'Eastern', + 'Central', + 'Mountain', + 'Pacific', + 'UTC' +] + + +# A class building tzinfo objects for fixed-offset time zones. +# Note that FixedOffset(0, "UTC") is a different way to build a +# UTC tzinfo object. +class FixedOffset(tzinfo): + """Fixed offset in minutes east from UTC.""" + + def __init__(self, offset, name): + self.__offset = timedelta(minutes=offset) + self.__name = name + + def utcoffset(self, dt): + return self.__offset + + def tzname(self, dt): + return self.__name + + def dst(self, dt): + return ZERO + + +import time as _time + +STDOFFSET = timedelta(seconds=-_time.timezone) +if _time.daylight: + DSTOFFSET = timedelta(seconds=-_time.altzone) +else: + DSTOFFSET = STDOFFSET + +DSTDIFF = DSTOFFSET - STDOFFSET + + +# A class capturing the platform's idea of local time. +class LocalTimezone(tzinfo): + + def utcoffset(self, dt): + if self._isdst(dt): + return DSTOFFSET + else: + return STDOFFSET + + def dst(self, dt): + if self._isdst(dt): + return DSTDIFF + else: + return ZERO + + def tzname(self, dt): + return _time.tzname[self._isdst(dt)] + + def _isdst(self, dt): + tt = (dt.year, dt.month, dt.day, + dt.hour, dt.minute, dt.second, + dt.weekday(), 0, -1) + stamp = _time.mktime(tt) + tt = _time.localtime(stamp) + return tt.tm_isdst > 0 + +Local = LocalTimezone() + + +def first_sunday_on_or_after(dt): + days_to_go = 6 - dt.weekday() + if days_to_go: + dt += timedelta(days_to_go) + return dt + + +# In the US, DST starts at 2am (standard time) on the first Sunday in April. +DSTSTART = datetime(1, 4, 1, 2) +# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct. +# which is the first Sunday on or after Oct 25. +DSTEND = datetime(1, 10, 25, 1) + + +# A complete implementation of current DST rules for major US time zones. +class USTimeZone(tzinfo): + + def __init__(self, hours, reprname, stdname, dstname): + self.stdoffset = timedelta(hours=hours) + self.reprname = reprname + self.stdname = stdname + self.dstname = dstname + + def __repr__(self): + return self.reprname + + def tzname(self, dt): + if self.dst(dt): + return self.dstname + else: + return self.stdname + + def utcoffset(self, dt): + return self.stdoffset + self.dst(dt) + + def dst(self, dt): + if dt is None or dt.tzinfo is None: + # An exception may be sensible here, in one or both cases. + # It depends on how you want to treat them. The default + # fromutc() implementation (called by the default astimezone() + # implementation) passes a datetime with dt.tzinfo is self. + return ZERO + assert dt.tzinfo is self + + # Find first Sunday in April & the last in October. + start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year)) + end = first_sunday_on_or_after(DSTEND.replace(year=dt.year)) + + # Can't compare naive to aware objects, so strip the timezone from + # dt first. + if start <= dt.replace(tzinfo=None) < end: + return HOUR + else: + return ZERO + +Eastern = USTimeZone(-5, "Eastern", "EST", "EDT") +Central = USTimeZone(-6, "Central", "CST", "CDT") +Mountain = USTimeZone(-7, "Mountain", "MST", "MDT") +Pacific = USTimeZone(-8, "Pacific", "PST", "PDT") diff --git a/libs/pytz/tzfile.py b/libs/pytz/tzfile.py new file mode 100644 index 00000000..25117f32 --- /dev/null +++ b/libs/pytz/tzfile.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python +''' +$Id: tzfile.py,v 1.8 2004/06/03 00:15:24 zenzen Exp $ +''' + +from datetime import datetime +from struct import unpack, calcsize + +from pytz.tzinfo import StaticTzInfo, DstTzInfo, memorized_ttinfo +from pytz.tzinfo import memorized_datetime, memorized_timedelta + + +def _byte_string(s): + """Cast a string or byte string to an ASCII byte string.""" + return s.encode('ASCII') + +_NULL = _byte_string('\0') + + +def _std_string(s): + """Cast a string or byte string to an ASCII string.""" + return str(s.decode('ASCII')) + + +def build_tzinfo(zone, fp): + head_fmt = '>4s c 15x 6l' + head_size = calcsize(head_fmt) + (magic, format, ttisgmtcnt, ttisstdcnt, leapcnt, timecnt, + typecnt, charcnt) = unpack(head_fmt, fp.read(head_size)) + + # Make sure it is a tzfile(5) file + assert magic == _byte_string('TZif'), 'Got magic %s' % repr(magic) + + # Read out the transition times, localtime indices and ttinfo structures. + data_fmt = '>%(timecnt)dl %(timecnt)dB %(ttinfo)s %(charcnt)ds' % dict( + timecnt=timecnt, ttinfo='lBB' * typecnt, charcnt=charcnt) + data_size = calcsize(data_fmt) + data = unpack(data_fmt, fp.read(data_size)) + + # make sure we unpacked the right number of values + assert len(data) == 2 * timecnt + 3 * typecnt + 1 + transitions = [memorized_datetime(trans) + for trans in data[:timecnt]] + lindexes = list(data[timecnt:2 * timecnt]) + ttinfo_raw = data[2 * timecnt:-1] + tznames_raw = data[-1] + del data + + # Process ttinfo into separate structs + ttinfo = [] + tznames = {} + i = 0 + while i < len(ttinfo_raw): + # have we looked up this timezone name yet? + tzname_offset = ttinfo_raw[i + 2] + if tzname_offset not in tznames: + nul = tznames_raw.find(_NULL, tzname_offset) + if nul < 0: + nul = len(tznames_raw) + tznames[tzname_offset] = _std_string( + tznames_raw[tzname_offset:nul]) + ttinfo.append((ttinfo_raw[i], + bool(ttinfo_raw[i + 1]), + tznames[tzname_offset])) + i += 3 + + # Now build the timezone object + if len(ttinfo) == 1 or len(transitions) == 0: + ttinfo[0][0], ttinfo[0][2] + cls = type(zone, (StaticTzInfo,), dict( + zone=zone, + _utcoffset=memorized_timedelta(ttinfo[0][0]), + _tzname=ttinfo[0][2])) + else: + # Early dates use the first standard time ttinfo + i = 0 + while ttinfo[i][1]: + i += 1 + if ttinfo[i] == ttinfo[lindexes[0]]: + transitions[0] = datetime.min + else: + transitions.insert(0, datetime.min) + lindexes.insert(0, i) + + # calculate transition info + transition_info = [] + for i in range(len(transitions)): + inf = ttinfo[lindexes[i]] + utcoffset = inf[0] + if not inf[1]: + dst = 0 + else: + for j in range(i - 1, -1, -1): + prev_inf = ttinfo[lindexes[j]] + if not prev_inf[1]: + break + dst = inf[0] - prev_inf[0] # dst offset + + # Bad dst? Look further. DST > 24 hours happens when + # a timzone has moved across the international dateline. + if dst <= 0 or dst > 3600 * 3: + for j in range(i + 1, len(transitions)): + stdinf = ttinfo[lindexes[j]] + if not stdinf[1]: + dst = inf[0] - stdinf[0] + if dst > 0: + break # Found a useful std time. + + tzname = inf[2] + + # Round utcoffset and dst to the nearest minute or the + # datetime library will complain. Conversions to these timezones + # might be up to plus or minus 30 seconds out, but it is + # the best we can do. + utcoffset = int((utcoffset + 30) // 60) * 60 + dst = int((dst + 30) // 60) * 60 + transition_info.append(memorized_ttinfo(utcoffset, dst, tzname)) + + cls = type(zone, (DstTzInfo,), dict( + zone=zone, + _utc_transition_times=transitions, + _transition_info=transition_info)) + + return cls() + +if __name__ == '__main__': + import os.path + from pprint import pprint + base = os.path.join(os.path.dirname(__file__), 'zoneinfo') + tz = build_tzinfo('Australia/Melbourne', + open(os.path.join(base, 'Australia', 'Melbourne'), 'rb')) + tz = build_tzinfo('US/Eastern', + open(os.path.join(base, 'US', 'Eastern'), 'rb')) + pprint(tz._utc_transition_times) diff --git a/libs/pytz/tzinfo.py b/libs/pytz/tzinfo.py new file mode 100644 index 00000000..725978d5 --- /dev/null +++ b/libs/pytz/tzinfo.py @@ -0,0 +1,577 @@ +'''Base classes and helpers for building zone specific tzinfo classes''' + +from datetime import datetime, timedelta, tzinfo +from bisect import bisect_right +try: + set +except NameError: + from sets import Set as set + +import pytz +from pytz.exceptions import AmbiguousTimeError, NonExistentTimeError + +__all__ = [] + +_timedelta_cache = {} + + +def memorized_timedelta(seconds): + '''Create only one instance of each distinct timedelta''' + try: + return _timedelta_cache[seconds] + except KeyError: + delta = timedelta(seconds=seconds) + _timedelta_cache[seconds] = delta + return delta + +_epoch = datetime.utcfromtimestamp(0) +_datetime_cache = {0: _epoch} + + +def memorized_datetime(seconds): + '''Create only one instance of each distinct datetime''' + try: + return _datetime_cache[seconds] + except KeyError: + # NB. We can't just do datetime.utcfromtimestamp(seconds) as this + # fails with negative values under Windows (Bug #90096) + dt = _epoch + timedelta(seconds=seconds) + _datetime_cache[seconds] = dt + return dt + +_ttinfo_cache = {} + + +def memorized_ttinfo(*args): + '''Create only one instance of each distinct tuple''' + try: + return _ttinfo_cache[args] + except KeyError: + ttinfo = ( + memorized_timedelta(args[0]), + memorized_timedelta(args[1]), + args[2] + ) + _ttinfo_cache[args] = ttinfo + return ttinfo + +_notime = memorized_timedelta(0) + + +def _to_seconds(td): + '''Convert a timedelta to seconds''' + return td.seconds + td.days * 24 * 60 * 60 + + +class BaseTzInfo(tzinfo): + # Overridden in subclass + _utcoffset = None + _tzname = None + zone = None + + def __str__(self): + return self.zone + + +class StaticTzInfo(BaseTzInfo): + '''A timezone that has a constant offset from UTC + + These timezones are rare, as most locations have changed their + offset at some point in their history + ''' + def fromutc(self, dt): + '''See datetime.tzinfo.fromutc''' + if dt.tzinfo is not None and dt.tzinfo is not self: + raise ValueError('fromutc: dt.tzinfo is not self') + return (dt + self._utcoffset).replace(tzinfo=self) + + def utcoffset(self, dt, is_dst=None): + '''See datetime.tzinfo.utcoffset + + is_dst is ignored for StaticTzInfo, and exists only to + retain compatibility with DstTzInfo. + ''' + return self._utcoffset + + def dst(self, dt, is_dst=None): + '''See datetime.tzinfo.dst + + is_dst is ignored for StaticTzInfo, and exists only to + retain compatibility with DstTzInfo. + ''' + return _notime + + def tzname(self, dt, is_dst=None): + '''See datetime.tzinfo.tzname + + is_dst is ignored for StaticTzInfo, and exists only to + retain compatibility with DstTzInfo. + ''' + return self._tzname + + def localize(self, dt, is_dst=False): + '''Convert naive time to local time''' + if dt.tzinfo is not None: + raise ValueError('Not naive datetime (tzinfo is already set)') + return dt.replace(tzinfo=self) + + def normalize(self, dt, is_dst=False): + '''Correct the timezone information on the given datetime. + + This is normally a no-op, as StaticTzInfo timezones never have + ambiguous cases to correct: + + >>> from pytz import timezone + >>> gmt = timezone('GMT') + >>> isinstance(gmt, StaticTzInfo) + True + >>> dt = datetime(2011, 5, 8, 1, 2, 3, tzinfo=gmt) + >>> gmt.normalize(dt) is dt + True + + The supported method of converting between timezones is to use + datetime.astimezone(). Currently normalize() also works: + + >>> la = timezone('America/Los_Angeles') + >>> dt = la.localize(datetime(2011, 5, 7, 1, 2, 3)) + >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)' + >>> gmt.normalize(dt).strftime(fmt) + '2011-05-07 08:02:03 GMT (+0000)' + ''' + if dt.tzinfo is self: + return dt + if dt.tzinfo is None: + raise ValueError('Naive time - no tzinfo set') + return dt.astimezone(self) + + def __repr__(self): + return '' % (self.zone,) + + def __reduce__(self): + # Special pickle to zone remains a singleton and to cope with + # database changes. + return pytz._p, (self.zone,) + + +class DstTzInfo(BaseTzInfo): + '''A timezone that has a variable offset from UTC + + The offset might change if daylight saving time comes into effect, + or at a point in history when the region decides to change their + timezone definition. + ''' + # Overridden in subclass + + # Sorted list of DST transition times, UTC + _utc_transition_times = None + + # [(utcoffset, dstoffset, tzname)] corresponding to + # _utc_transition_times entries + _transition_info = None + + zone = None + + # Set in __init__ + + _tzinfos = None + _dst = None # DST offset + + def __init__(self, _inf=None, _tzinfos=None): + if _inf: + self._tzinfos = _tzinfos + self._utcoffset, self._dst, self._tzname = _inf + else: + _tzinfos = {} + self._tzinfos = _tzinfos + self._utcoffset, self._dst, self._tzname = ( + self._transition_info[0]) + _tzinfos[self._transition_info[0]] = self + for inf in self._transition_info[1:]: + if inf not in _tzinfos: + _tzinfos[inf] = self.__class__(inf, _tzinfos) + + def fromutc(self, dt): + '''See datetime.tzinfo.fromutc''' + if (dt.tzinfo is not None and + getattr(dt.tzinfo, '_tzinfos', None) is not self._tzinfos): + raise ValueError('fromutc: dt.tzinfo is not self') + dt = dt.replace(tzinfo=None) + idx = max(0, bisect_right(self._utc_transition_times, dt) - 1) + inf = self._transition_info[idx] + return (dt + inf[0]).replace(tzinfo=self._tzinfos[inf]) + + def normalize(self, dt): + '''Correct the timezone information on the given datetime + + If date arithmetic crosses DST boundaries, the tzinfo + is not magically adjusted. This method normalizes the + tzinfo to the correct one. + + To test, first we need to do some setup + + >>> from pytz import timezone + >>> utc = timezone('UTC') + >>> eastern = timezone('US/Eastern') + >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)' + + We next create a datetime right on an end-of-DST transition point, + the instant when the wallclocks are wound back one hour. + + >>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc) + >>> loc_dt = utc_dt.astimezone(eastern) + >>> loc_dt.strftime(fmt) + '2002-10-27 01:00:00 EST (-0500)' + + Now, if we subtract a few minutes from it, note that the timezone + information has not changed. + + >>> before = loc_dt - timedelta(minutes=10) + >>> before.strftime(fmt) + '2002-10-27 00:50:00 EST (-0500)' + + But we can fix that by calling the normalize method + + >>> before = eastern.normalize(before) + >>> before.strftime(fmt) + '2002-10-27 01:50:00 EDT (-0400)' + + The supported method of converting between timezones is to use + datetime.astimezone(). Currently, normalize() also works: + + >>> th = timezone('Asia/Bangkok') + >>> am = timezone('Europe/Amsterdam') + >>> dt = th.localize(datetime(2011, 5, 7, 1, 2, 3)) + >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)' + >>> am.normalize(dt).strftime(fmt) + '2011-05-06 20:02:03 CEST (+0200)' + ''' + if dt.tzinfo is None: + raise ValueError('Naive time - no tzinfo set') + + # Convert dt in localtime to UTC + offset = dt.tzinfo._utcoffset + dt = dt.replace(tzinfo=None) + dt = dt - offset + # convert it back, and return it + return self.fromutc(dt) + + def localize(self, dt, is_dst=False): + '''Convert naive time to local time. + + This method should be used to construct localtimes, rather + than passing a tzinfo argument to a datetime constructor. + + is_dst is used to determine the correct timezone in the ambigous + period at the end of daylight saving time. + + >>> from pytz import timezone + >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)' + >>> amdam = timezone('Europe/Amsterdam') + >>> dt = datetime(2004, 10, 31, 2, 0, 0) + >>> loc_dt1 = amdam.localize(dt, is_dst=True) + >>> loc_dt2 = amdam.localize(dt, is_dst=False) + >>> loc_dt1.strftime(fmt) + '2004-10-31 02:00:00 CEST (+0200)' + >>> loc_dt2.strftime(fmt) + '2004-10-31 02:00:00 CET (+0100)' + >>> str(loc_dt2 - loc_dt1) + '1:00:00' + + Use is_dst=None to raise an AmbiguousTimeError for ambiguous + times at the end of daylight saving time + + >>> try: + ... loc_dt1 = amdam.localize(dt, is_dst=None) + ... except AmbiguousTimeError: + ... print('Ambiguous') + Ambiguous + + is_dst defaults to False + + >>> amdam.localize(dt) == amdam.localize(dt, False) + True + + is_dst is also used to determine the correct timezone in the + wallclock times jumped over at the start of daylight saving time. + + >>> pacific = timezone('US/Pacific') + >>> dt = datetime(2008, 3, 9, 2, 0, 0) + >>> ploc_dt1 = pacific.localize(dt, is_dst=True) + >>> ploc_dt2 = pacific.localize(dt, is_dst=False) + >>> ploc_dt1.strftime(fmt) + '2008-03-09 02:00:00 PDT (-0700)' + >>> ploc_dt2.strftime(fmt) + '2008-03-09 02:00:00 PST (-0800)' + >>> str(ploc_dt2 - ploc_dt1) + '1:00:00' + + Use is_dst=None to raise a NonExistentTimeError for these skipped + times. + + >>> try: + ... loc_dt1 = pacific.localize(dt, is_dst=None) + ... except NonExistentTimeError: + ... print('Non-existent') + Non-existent + ''' + if dt.tzinfo is not None: + raise ValueError('Not naive datetime (tzinfo is already set)') + + # Find the two best possibilities. + possible_loc_dt = set() + for delta in [timedelta(days=-1), timedelta(days=1)]: + loc_dt = dt + delta + idx = max(0, bisect_right( + self._utc_transition_times, loc_dt) - 1) + inf = self._transition_info[idx] + tzinfo = self._tzinfos[inf] + loc_dt = tzinfo.normalize(dt.replace(tzinfo=tzinfo)) + if loc_dt.replace(tzinfo=None) == dt: + possible_loc_dt.add(loc_dt) + + if len(possible_loc_dt) == 1: + return possible_loc_dt.pop() + + # If there are no possibly correct timezones, we are attempting + # to convert a time that never happened - the time period jumped + # during the start-of-DST transition period. + if len(possible_loc_dt) == 0: + # If we refuse to guess, raise an exception. + if is_dst is None: + raise NonExistentTimeError(dt) + + # If we are forcing the pre-DST side of the DST transition, we + # obtain the correct timezone by winding the clock forward a few + # hours. + elif is_dst: + return self.localize( + dt + timedelta(hours=6), is_dst=True) - timedelta(hours=6) + + # If we are forcing the post-DST side of the DST transition, we + # obtain the correct timezone by winding the clock back. + else: + return self.localize( + dt - timedelta(hours=6), + is_dst=False) + timedelta(hours=6) + + # If we get this far, we have multiple possible timezones - this + # is an ambiguous case occuring during the end-of-DST transition. + + # If told to be strict, raise an exception since we have an + # ambiguous case + if is_dst is None: + raise AmbiguousTimeError(dt) + + # Filter out the possiblilities that don't match the requested + # is_dst + filtered_possible_loc_dt = [ + p for p in possible_loc_dt if bool(p.tzinfo._dst) == is_dst + ] + + # Hopefully we only have one possibility left. Return it. + if len(filtered_possible_loc_dt) == 1: + return filtered_possible_loc_dt[0] + + if len(filtered_possible_loc_dt) == 0: + filtered_possible_loc_dt = list(possible_loc_dt) + + # If we get this far, we have in a wierd timezone transition + # where the clocks have been wound back but is_dst is the same + # in both (eg. Europe/Warsaw 1915 when they switched to CET). + # At this point, we just have to guess unless we allow more + # hints to be passed in (such as the UTC offset or abbreviation), + # but that is just getting silly. + # + # Choose the earliest (by UTC) applicable timezone if is_dst=True + # Choose the latest (by UTC) applicable timezone if is_dst=False + # i.e., behave like end-of-DST transition + dates = {} # utc -> local + for local_dt in filtered_possible_loc_dt: + utc_time = ( + local_dt.replace(tzinfo=None) - local_dt.tzinfo._utcoffset) + assert utc_time not in dates + dates[utc_time] = local_dt + return dates[[min, max][not is_dst](dates)] + + def utcoffset(self, dt, is_dst=None): + '''See datetime.tzinfo.utcoffset + + The is_dst parameter may be used to remove ambiguity during DST + transitions. + + >>> from pytz import timezone + >>> tz = timezone('America/St_Johns') + >>> ambiguous = datetime(2009, 10, 31, 23, 30) + + >>> str(tz.utcoffset(ambiguous, is_dst=False)) + '-1 day, 20:30:00' + + >>> str(tz.utcoffset(ambiguous, is_dst=True)) + '-1 day, 21:30:00' + + >>> try: + ... tz.utcoffset(ambiguous) + ... except AmbiguousTimeError: + ... print('Ambiguous') + Ambiguous + + ''' + if dt is None: + return None + elif dt.tzinfo is not self: + dt = self.localize(dt, is_dst) + return dt.tzinfo._utcoffset + else: + return self._utcoffset + + def dst(self, dt, is_dst=None): + '''See datetime.tzinfo.dst + + The is_dst parameter may be used to remove ambiguity during DST + transitions. + + >>> from pytz import timezone + >>> tz = timezone('America/St_Johns') + + >>> normal = datetime(2009, 9, 1) + + >>> str(tz.dst(normal)) + '1:00:00' + >>> str(tz.dst(normal, is_dst=False)) + '1:00:00' + >>> str(tz.dst(normal, is_dst=True)) + '1:00:00' + + >>> ambiguous = datetime(2009, 10, 31, 23, 30) + + >>> str(tz.dst(ambiguous, is_dst=False)) + '0:00:00' + >>> str(tz.dst(ambiguous, is_dst=True)) + '1:00:00' + >>> try: + ... tz.dst(ambiguous) + ... except AmbiguousTimeError: + ... print('Ambiguous') + Ambiguous + + ''' + if dt is None: + return None + elif dt.tzinfo is not self: + dt = self.localize(dt, is_dst) + return dt.tzinfo._dst + else: + return self._dst + + def tzname(self, dt, is_dst=None): + '''See datetime.tzinfo.tzname + + The is_dst parameter may be used to remove ambiguity during DST + transitions. + + >>> from pytz import timezone + >>> tz = timezone('America/St_Johns') + + >>> normal = datetime(2009, 9, 1) + + >>> tz.tzname(normal) + 'NDT' + >>> tz.tzname(normal, is_dst=False) + 'NDT' + >>> tz.tzname(normal, is_dst=True) + 'NDT' + + >>> ambiguous = datetime(2009, 10, 31, 23, 30) + + >>> tz.tzname(ambiguous, is_dst=False) + 'NST' + >>> tz.tzname(ambiguous, is_dst=True) + 'NDT' + >>> try: + ... tz.tzname(ambiguous) + ... except AmbiguousTimeError: + ... print('Ambiguous') + Ambiguous + ''' + if dt is None: + return self.zone + elif dt.tzinfo is not self: + dt = self.localize(dt, is_dst) + return dt.tzinfo._tzname + else: + return self._tzname + + def __repr__(self): + if self._dst: + dst = 'DST' + else: + dst = 'STD' + if self._utcoffset > _notime: + return '' % ( + self.zone, self._tzname, self._utcoffset, dst + ) + else: + return '' % ( + self.zone, self._tzname, self._utcoffset, dst + ) + + def __reduce__(self): + # Special pickle to zone remains a singleton and to cope with + # database changes. + return pytz._p, ( + self.zone, + _to_seconds(self._utcoffset), + _to_seconds(self._dst), + self._tzname + ) + + +def unpickler(zone, utcoffset=None, dstoffset=None, tzname=None): + """Factory function for unpickling pytz tzinfo instances. + + This is shared for both StaticTzInfo and DstTzInfo instances, because + database changes could cause a zones implementation to switch between + these two base classes and we can't break pickles on a pytz version + upgrade. + """ + # Raises a KeyError if zone no longer exists, which should never happen + # and would be a bug. + tz = pytz.timezone(zone) + + # A StaticTzInfo - just return it + if utcoffset is None: + return tz + + # This pickle was created from a DstTzInfo. We need to + # determine which of the list of tzinfo instances for this zone + # to use in order to restore the state of any datetime instances using + # it correctly. + utcoffset = memorized_timedelta(utcoffset) + dstoffset = memorized_timedelta(dstoffset) + try: + return tz._tzinfos[(utcoffset, dstoffset, tzname)] + except KeyError: + # The particular state requested in this timezone no longer exists. + # This indicates a corrupt pickle, or the timezone database has been + # corrected violently enough to make this particular + # (utcoffset,dstoffset) no longer exist in the zone, or the + # abbreviation has been changed. + pass + + # See if we can find an entry differing only by tzname. Abbreviations + # get changed from the initial guess by the database maintainers to + # match reality when this information is discovered. + for localized_tz in tz._tzinfos.values(): + if (localized_tz._utcoffset == utcoffset and + localized_tz._dst == dstoffset): + return localized_tz + + # This (utcoffset, dstoffset) information has been removed from the + # zone. Add it back. This might occur when the database maintainers have + # corrected incorrect information. datetime instances using this + # incorrect information will continue to do so, exactly as they were + # before being pickled. This is purely an overly paranoid safety net - I + # doubt this will ever been needed in real life. + inf = (utcoffset, dstoffset, tzname) + tz._tzinfos[inf] = tz.__class__(inf, tz._tzinfos) + return tz._tzinfos[inf] diff --git a/libs/pytz/zoneinfo/Africa/Abidjan b/libs/pytz/zoneinfo/Africa/Abidjan new file mode 100644 index 0000000000000000000000000000000000000000..65d19ec2651aeb46c42ce7a74ae6ecbf3001edbb GIT binary patch literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVL@uDeu^JFFpbENZj?%D{cub+o_;fLw)Ui%&zZNC z0sSuhOue7EB_D9j$Ss7@OeiHihAA|s)Z*hp|BI-XhhnEelUu~Ury BmV^KR literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Africa/Addis_Ababa b/libs/pytz/zoneinfo/Africa/Addis_Ababa new file mode 100644 index 0000000000000000000000000000000000000000..6e19601f7d3a420c1d4832178352c1eafbd08146 GIT binary patch literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J%-iV(Jecdi06jW zDp%_Aw$HQP&c%q@-T3hK@|`L_cck_+HR_-zq7GMMs?fi#3a6HLRkpnA>9frBb5;I! ztIxjM6uDn+wjU-syr;2{_jdCr@3SW=kiJwwr>MfWC8<3gRCVd3*b6guWX16s<_9v3 zsWrPPo)W_h1b;lHRiPi#(%TS2$h1TPFZyRC5EWCT);7NG5@sj5x3(Ge?4|kZZEW0? zgg#S4lQdTx21gGfhT(tp-P}L;D(hM*j=n;?LEQP&{vZw^9w9CvJ|RvaULkHFej$#1 zYR?eY5Z^wvbBK3{dx(EX29O*eSwQmO%47n`1(FR#K9Gzka)M+9$qSMh{O{bVb(r1J F>Q4~B_9FlQ literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Africa/Asmara b/libs/pytz/zoneinfo/Africa/Asmara new file mode 100644 index 0000000000000000000000000000000000000000..6e19601f7d3a420c1d4832178352c1eafbd08146 GIT binary patch literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J0|N_42?K|ZZwP~~ ffgyuCkY->6p%4;G{tpBo(?LcNZvz+5G6OCE+{Pmd literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Africa/Blantyre b/libs/pytz/zoneinfo/Africa/Blantyre new file mode 100644 index 0000000000000000000000000000000000000000..31cfad771a5c7c609e495da650e3ffbcf07c974d GIT binary patch literal 157 zcmWHE%1kq2zyM4@5fBCeMj!^UIhx##egSgidO)Hw3Jfehz99_Gjv*i}LkI~5{RaZP MhH(K+)ivS*0NorDCIA2c literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Africa/Brazzaville b/libs/pytz/zoneinfo/Africa/Brazzaville new file mode 100644 index 0000000000000000000000000000000000000000..cbdc0450fc3b97bc436f6d90798a30ebe0ac30b9 GIT binary patch literal 157 zcmWHE%1kq2zyM4@5fBCeMj!^UITi+f4ghkP4M3uN0t_rZz99_Zjv*i}LkI~5{RaZP MhH(K+)ivY-0B~s$p8x;= literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Africa/Bujumbura b/libs/pytz/zoneinfo/Africa/Bujumbura new file mode 100644 index 0000000000000000000000000000000000000000..31cfad771a5c7c609e495da650e3ffbcf07c974d GIT binary patch literal 157 zcmWHE%1kq2zyM4@5fBCeMj!^UIhx##egSgidO)Hw3Jfehz99_Gjv*i}LkI~5{RaZP MhH(K+)ivS*0NorDCIA2c literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Africa/Cairo b/libs/pytz/zoneinfo/Africa/Cairo new file mode 100644 index 0000000000000000000000000000000000000000..0272fa1ba0a09ae8380be83cc8838d97d0aa6d25 GIT binary patch literal 1963 zcmdVaZ)nw39LMo=RO)m6)8UR|~2n$6tOt>w*SdS&XQU)(!N8TDIDBFTE;ahYRr=`5p*i;2$3a*5Q(;$cNpNmNOL(p$)W4n2*z;dJCw^~lvfUec#D&jx zOS`I2{jvUx?OD7?Tx=L8duOaueYIQUr3o>0x%`;DJeU;yw`9xy&Nh+hue4VV4XCT9 z4%&hD)~G-C_sGFl_6V`L&_l@{$+Bz}%`M4ZY(J5|vIIuV8 zjZtH_Hruh8YLO?m*}PMYYFux^-g;1|@f}jew@ecg_H45g)iRa8;e@=cev`O;b)CFp zPQEC3a*`}8OsP8)U&~3^uZp7hC0lg%OI0iyZE;(bn%w!RynB0tC^__towDwIbYIrnwaQ$jCtFA#mw7kyDPSol-kIXd5Q3Ju;(tI=5JQGyMvP|{f zn4V`(oB40F1Pe|^!kYR6x@PTTrsmyDkXmt{N$psxQz!R_>4G&uR^&hW8ItwSKiB?W zA>y^}^@-xC5%(0w=ZoRjzSk^Fi)1pzN1DG!_(=bYH$CX?r2`AMBX8U5-Z%2bk#~-~ zb>zJxZytH~$lFKWKhglw0n&ok^?)?tbzLBBAblW>Ae|tsAiW^XAl)GCApIZ>Asrzt zd0kIPQ(o5<(iYMe(iqYi`qubDZ=7om=niQQ>5rp9q(hDtksgsIy{=1+HodM-q*0_( zq*bI>j%JZ=k#>=OIT}Vf=4ct|8EM+-t6-M>vjs+DrB#a%|dp| c>$c15_6ylCuiG)N+cNyW?OD`~TS-~;FR%xpXaE2J literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Africa/Casablanca b/libs/pytz/zoneinfo/Africa/Casablanca new file mode 100644 index 0000000000000000000000000000000000000000..04d16090dc9af7d28246fd6a855eb902c5d46559 GIT binary patch literal 969 zcmcK2KS-2u9LMoT?}EcFb0`Xj_*IBQ1)fwkgw;}Fr``EHr+23fEk#uHP+JH-MbJ`> z1#!eTiW(AE1s7>h!Yzpk9U3ZJtcNg69G<@KZ`(OF^gAA2_h(Rk?@v&@e6iEpbkh0_ z59gRZyw5%=uGeOh9noBQJe>Qq61{uY8804r;Vph|k2l(GoZNWxBg|a68)as%#aoxN zUiQM7c-z|(QS-x};r4}M)YASuY@K}R?b&@j?m6`>>Y3gb_U>=>dLO(G&o0eG`SV}H zzQ?Q<%XB)`9KQOzw4fA1{AdoP{u3%mmzIej|#*-r2e_pSd@!kr`c zj@-Se_m4C{I+)r5>0xRU{xDrkZG-gT&-v_g6z&5&*!+9CZ+ZHROa39*@&q-LAGM*UXab0 kx*JosgY3uD4Iw)+bxSy=m%nK}|6xx}$8}-dhjLAS0KM)Bq5uE@ literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Africa/Ceuta b/libs/pytz/zoneinfo/Africa/Ceuta new file mode 100644 index 0000000000000000000000000000000000000000..dd75e3e6e4116dcf9c72bf46c0d97b3a81d9aec3 GIT binary patch literal 2050 zcmdtiZ%9>l9LMoPNmYCc`N&4W|{r7yW3rfw(8z7 zsWn8t16jmq6@`T?*TU8V7g1Csg#^PzMMN9@B}%qMg^2y$r=SOW)RP|UcR2TTxNzY< zIPZ^d(VEIM>py3J`G%VlGB?jd!sg@0KQ;yS?)NWic0CR>-}PHZ+7ETL97?l(-Zv+3 zbjQ2aFFQ9|#{!=4v0LATyAy-cy3afgM69a|N1R{Z_0GL0E2=!dE4n==9MkZ3VBqgX zVM#g=c>hF`HKJZ6e#=)fvMMMaELkC=3Vf1~l`Es&nUWZnBx9o9lO&f{lb%e}v9}X- zT;~w|@Z10$-}+452k+~I#%r2fcTrO|{-&OaBbr)zRnmg3lAhfr6X$*}8N>HTW?GG8 z_Jm~eutuGHqgA3`WJcf9GNa>y%>3(s%xY-t@GXEbpBJ1mJH65k}HFB;ghkl=+}RBac86~*%vFNtq)~s z=pQL-?2u(;ol?H_fiBNEAuCERX+`{QS()9Ul@Z5v)o|5S_jc*(o}jL|P^)Wi_;lUj z#aeZKgRK81Tm4PN^7-m`4Q$Vl4MkZJtQ;y|q>hk{xzA-&tR>YcgSEQrPpOG{sx_C- zO6{Yoy7_36eA&^aTXvt(tu1?WTkSy&HH6geWB2L%^5XH;)z8)c?OeUR-Tlnl$1%o; z9r60vingq{k#;QKThRrDK5IcPcd^g%ng=fr=Gc~PJ3q2*-y6L2z2Q`KN0{E zfujili2(@$i2?}&i3175(L{oT;%H*wFA|KSi3SOWLp(@8NJL0TNK8mj9HK(PLgGRK zLn1>$Lt;aML!v{%b2RZG0U{A1AtEs%K_XEiVIpxNfg+J2p(3#&!6MNj;X0alk$@dd z#7M|U%t+8k)JWJ!+(_U^`3rP^ho$f{Kx<}nh`*Tz|o8WG6={hAj5!+12PcE zNFYOjj0G|n$Y>zLfs6+-AdY54kRfq2V}c9{GAhWhAmf4z3^Fpv&>&-j3=T3n$nYTJ zgA5QdLdXy~nlVBK2^l41n2>Qo1_~J|WT=p_LIw*NEo8Wm@j?a+88KwY9L<;^gXU;P s4H-6M+>n7oMh+P|WbE*NH+Yd|2q&bngzm!h(^b literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Africa/Conakry b/libs/pytz/zoneinfo/Africa/Conakry new file mode 100644 index 0000000000000000000000000000000000000000..65d19ec2651aeb46c42ce7a74ae6ecbf3001edbb GIT binary patch literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVL@u>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J?RDAhhosltO!9lI#q zAENYUm@>vDW!6u0w$0SpJ54$LPPwc>xsM^8Kd-0!XHn1elxe0?wALKQscWeJ?Zf4A zoHtU+ab9Z5rBsWr&ci=jDKGAoCl)uAKM+)Xasu+!_mlaD5&1hg7!sevm?95~3!_2| z3o$Olzz`!t3@!555QB?6I>hi0<3j|1hyW1+B8JF8M2-RxM&vjUfkci35lZA(A_s$r z1`$r=cp?Xch$wPMh?pV=g@`J0ScteH2NpRpL}-y?Lj)H&I{dfrjl=wB2>XMLUnl#* AVE_OC literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Africa/Freetown b/libs/pytz/zoneinfo/Africa/Freetown new file mode 100644 index 0000000000000000000000000000000000000000..65d19ec2651aeb46c42ce7a74ae6ecbf3001edbb GIT binary patch literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVL@uGwXU9&d$p(IMKUsi;r&zL$G6T2uKbLLP)UkKM>?rJ34@9kkud>WIc!mIRQk2 QoB^WAa0(a5VY)_K034z`A^-pY literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Africa/Juba b/libs/pytz/zoneinfo/Africa/Juba new file mode 100644 index 0000000000000000000000000000000000000000..83eca03ab87f54441e6fbff4cfc08408c4e77c16 GIT binary patch literal 669 zcmcK1t4jny9Ki9}dwI9+spsoFy~9Ne;vt9{EaHm;@c{$3NrRKTGl;<^c$icWi+_N{ z2Z+gHwTNIFs~E$)s-RfW5w(jHmk>O&Y7#@hevUg zHO}UUjBI_F=u$<;RL#W4UUgzZnYwOYCjBXs5@qd*UgJLP%6gM9-i;^I*Dt2wbX+%{ z$5qqLhRp0etLDm?ZmHg>*4d(No4HW!#buf8J5U|oq0S}ORqpLpcE0bME?k)B@&#PnpDS0GAuf3M&V89wa_Q}DCR0VF}P zAZd_1NFpQ?k_yR%Btx==+H^=hBq5R!Nr~h{k|J4=v`AhgF_IZcjpRm>BiTc3dL%zG S0%Q!xDDa=g;o8{@47*>))q+(3 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Africa/Kampala b/libs/pytz/zoneinfo/Africa/Kampala new file mode 100644 index 0000000000000000000000000000000000000000..6e19601f7d3a420c1d4832178352c1eafbd08146 GIT binary patch literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4JCiJWd%I>j z&&ztaQt;>B9;6fcvh!9y{#JP*)*L`*_5R>CYegaXj=X-)bH2JVo z?ZW4_4bTT^gmgk$A-#}hNH?S%(hq4^)pkT$B0Z6&NLQpS(idrrbVgbuy^-cfcceYi eAK5`wy9ZV@GwXU9&d$p(IMKUsi;r&zL$G6T2uKbLLP)UkKM>?rJ34@9kkud>WIc!mIRQk2 QoB^WAa0(a5VY)_K034z`A^-pY literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Africa/Mbabane b/libs/pytz/zoneinfo/Africa/Mbabane new file mode 100644 index 0000000000000000000000000000000000000000..b8b9270a142bf3b1b4e3a773a0d7ffc52c489bb0 GIT binary patch literal 262 zcmWHE%1kq2zyK^j5fBCeHXsJEIU9gPliT@>GwXU9&d$p(IMKUsi;r&zL$G6T2uKbLLP)UkKM>?rJ34@9kkud>WIc!mIRQk2 QoB^WAa0(a5VY)_K034z`A^-pY literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Africa/Mogadishu b/libs/pytz/zoneinfo/Africa/Mogadishu new file mode 100644 index 0000000000000000000000000000000000000000..6e19601f7d3a420c1d4832178352c1eafbd08146 GIT binary patch literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J7i><+{*5JG}A|A8Q?YS|nR4YCeo1{qdz0qr;70svU3GM@kd literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Africa/Nairobi b/libs/pytz/zoneinfo/Africa/Nairobi new file mode 100644 index 0000000000000000000000000000000000000000..6e19601f7d3a420c1d4832178352c1eafbd08146 GIT binary patch literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4JmwR;JSOpFW+d@>+;J^=<61_l`gMh+j} m5QcC^5DpFj$pAqJ3FiL?0+1abeIR>4G%0p*0qxc`FBF7@J@O%%G$>4YQ-1qL5yZf9spI>psucK1)YUs;;FC-JQEs@pMEdQei)t9FaX^C%&6~k%Q@Bl^R;rGrK!t*1Im` z^2I_p6l{_2eqC`ici4rfTQKh_Vou1sZ-XUjI2ZL()1H{f%yIBU#;l+5{_yc1W&ofd zP#`E6K@A86C8&X+;P6aJjEDgXcg literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Africa/Tunis b/libs/pytz/zoneinfo/Africa/Tunis new file mode 100644 index 0000000000000000000000000000000000000000..0cd8ffbae1f125920be88989f19c140ede9ae0b8 GIT binary patch literal 701 zcmci9Jxjwt9DwmlUqlP7CqhK&Ag#3yI#hHiovdvU-wI|>rw$@?3(^nJ(Sk$t132iU zDEJjru&AwXt3!i=-Q)&W(R%_q=;Y#syI&Go2+!YIEw7J@U#mmEVY6a-bKg_d$9Ac> zc#+<8>&v^P`eV$!da~T>yZdlW$;yBo+fS=_W<(8*_NzoRq=qx+b~2c>rz&BU%~Zv7qV43e?=l~}bPDm7 zEWEzR8ULv?0zSVl5d6OM(~nDtt}Xqs!j}>OA)M4JK^GAl(~2_Yhq-Sbv~D_8v<_npUEyu~}m85$o8$c=gJ8x9NF z!}CLJZ*$@AufA=@_5NyXqQJYJD2eY5myYMEve^^yxBpS)FH>}&D4{CC$GS4($^;Ac zbnxuZRCzaa)sG!hee_O0`M73kzQlCx;)1E$ic9_5G1Kt=jWiC;tEO15gla}rb5B58 za@tgDS(db3K2qVda|s`$sqX#mmzqyoi^IjQM+~Y*&*2zbbYMq#bZoSBp@5Baf)v>D*mc{?KkJo0^@vqt7j*K)_f*Qz7dmyMORerXqQyXsN^AUFrngI#QPeLpD-u*z z;!c^J5v-nYdu2{syvVthEpz9B#FOV@C(cetPtrc&0yEuYLc7kS()1Z~s}9 zUv^19TmOkFSpAJIEa+2(zuu5VDIbfXi{r95{E#Rf8IdJ3&EomNZ}s}`4ye+ulX}Bf zuc)#u1KK%SqRQ9o)*CyLRYhEt_Es)b-nm>|nRQcDUagdymWGQ>XLID{J2yo2N3rt7 z>2a}T|D1ejY(&)5Ps^=C?}*yc+q&-HNwqEIvfkb}pz6cNbVJc@)i85hHzro8#tZv& zlRH;64cF`DYm3#ZM|?e%fCW* zj|Cb zzK@T~_1P7d%kOV+T)}>Sdu_lx`(0pviLm!bzOER*zqd7DiM=mcU+Q&js4#Dpc^$7S z-`w*Hyso@;=CaOQ%n9Jb`Rn5S?~#R>Kk#ynn3sFJ-<-8){usyZgVi<2=#b%A&G?W3 zq8%X@hR88v1O|zW5*a2kPGq3SNRgph%~+AaTFq#Y;UeQj28@gt88R|vWYEZ{kzpg_ zMh1?I92q(?c4Y9#=#k-D&G@Y*07wLo5Fjx?f`CK;2?G)bBoIg>kWe78K!Slp!)n5T z#KUR=fb2@Mh(BsfTPknkY!v6=uO5kf+Q#0Uuz5+x)| zNSu&BA(28tg~SR877{J12^SJCs|gqqF{=p~5;G)dNYs$9A#pIBoav^lt?U*U?R~(!imJwY66Nx)M`SC#MEkn zibNF&D-u^Eut;Q)&?2!#f{R2K2`>^~s|hd?VXFx-5@V|gG7@DZ%t)M(KqHYxLXCH0 z9UK@EdauXrnRg$bziVB+?f+@^KheH>3o}7a6Q=0Nr5UN|sUo>FEiE-IRfPQs4Q6_I literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Anchorage b/libs/pytz/zoneinfo/America/Anchorage new file mode 100644 index 0000000000000000000000000000000000000000..9bbb2fd3b361ea8aa4c126d14df5fa370343a63f GIT binary patch literal 2371 zcmciCZA{fw0LSqQ0v8C15>k=qB=Y>=0R^EbF9-r60dl(ukxF8BSP2AU_(W1Vaw{?0 z9L`3^IkwuQo#|G(7TvVgCgupY9>$_{YbHzAdYVOYJKvM57d7AI|G)G99PW7g`??!i zp3HIl>j^WzaCr8a!#!oE`Hb$#^NlC`+&11+EPo#_Q!^(vxcqOdkdA>;SHO!YGO#<@ zHLJZu2Q@AC1=l9&kfKDNGdol}UtZ@6i<;75!xOIXAI|FAz8UpJe0f<$`i6bCpB$BU zym`hIb#PeTx#y_st}Xp?cFSH@bbY&wsc3WET~H_Iq^@?&UC^rMg)MQ#2G;7>^ysMA zAB*+;i-{_3e4)PQlvBkY3(@x;zN|!7fxNGGR4wq#mkFD`6AN>%%fyvuL{iMxGCA$2 zNS>M2so{G?>f~2CZK_SAkG!ul&cCEG2M_D4;#%***zEp@Jt`Ej#F{-qRIF#U_T|Ko7^z{KaGP$%gJ-#sZF+83&q9Xcdjty8*a z*E_1X`mA2wd{C7vdP|pAR2}u z#M%kO?^ky6Pf4q2Jddw9I5rjGOyZrWxw_&S19i% zow~)Du3CmYdefyy_0)k5`Se(tc&6(SxmibuR?kw|)_+yB=gpJPwvLI8m}%KreN1%v z=jg8dbE<3dH{Cr~tL~8rz2(||wRP}4z3q!mwY}$cz2k&O^{nmH&kf|OfWTP+LBThB zLqeUm@O3yoyykHD{T=HaL4JR4TR^D&M%Z7X>^+9BBi8Tl-x&~Z?+L4_+>W9;a~?IP z#+-8gC@*n4>bX>!OHrk{nJ0h`&tDh!e{U_^`~!#Q6?3?!_|3EI)b&qsM_*AnvOQ#f zRB1(*dLfNDq)EAYDM(fb;=r1kwql6-Y0TW+2@_ z+F>>QKpJ8-9YI=x^aN=N(iNmFNMDe~Ae}*4gY*Vz4$>W@JxG6$23bvqkQO05LYjnh z32773C!|qGr;t`5y+WFWbPH*h)$|K#nALO)X_?jZ3~3tDHKc7w-;l;3okLoO^bTnr z(mkYoR?|PEfmYK&q=i<~L!^mF7m+q1eMB0GbP{PL(o3Y7NH>voBK<@fYBe22T52^t zMVe|gT}9f8^c86=(pjXnNNkwlp-TPl=ntxBFds{-rg@Q22p=h9-?{$-zN~um~qXaZ%9a z8N(r-@oXxn)X75>LG(Oz%)vtKIFqO6{j4tDPky}Qrn}Gk8x4)VN(MKcJMI%+o|Jp} zziMG+TrxG!@|o+Wdi8fozU0Q$Ui!1_{VK}-gCn!QI;ak=?n`btrw$L6Bp-$9e9@YV z@|WuJ>#+Q-uG59SCMm4_P{qWSDSj&GtJ4bkH}g){Zp=#E#ICN_PmBsT^}`RN^62im zZWy>{8ji=^7iYTnb10 zK^l2oC!`h9i{D8zq#M!>>4!8#IwCE-t|!ve>$)Osk-kV{q%+dm>v|*2y{zzV5R8j9t&E`xgfM2OH<~xM3<~QDr>gL*%X_l+%YkkOHs_xL=N(uA5cu4)Y zGvF@|CzQPw_g9S{)ZzBig{}Ds-QJcjbR<9Pj*sUGom0O0bt*W-& zJRD6np6Q)4G2J(`TKB!qM)xm`=>x@M(X6#Lu%hKJ-%#+Ex5eE<*3QK}&)QPO3tC$? zUf9|fl|ZYtZ)$-y?p|wuI^(6}Y*%qPJ#v2EB=Zj|$p_oB!?*j9wUEV-)sW?!vL3Ra zQ&vQlMAk$WMOH@=jSFDFCU!DJ38^IHd@r3Zx9A4yP1? zRDzV^lvr97lQ gq(G!Xq(r1frxb})iIj<1`LzFEXJn5w+uIZQ2~&paD*ylh literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Argentina/Catamarca b/libs/pytz/zoneinfo/America/Argentina/Catamarca new file mode 100644 index 0000000000000000000000000000000000000000..b798105e0f660c7b85a663d945d9eb7f04505e52 GIT binary patch literal 1100 zcmc)IJ!n%=9ES0mHqjUh1{4$#sn}8)6%K7<#gB3%LqW_^14<_c5d=YTu!3lGaS{Sj z1fiwTu0oGPwKUZp8*8*f38X~?hk)XysKgMzdi+1fNnCXDUe5hp?sVaOGJ~g1b_IW2 ztKBH zns>I+nKVnO6Sj6Bs%m3(`#Ex2)w|-l{&vhX)~9TvR8e1QL;7oFxA|5~+V3+*)Q`IZ zdSxW3-1Q#4di;=yww=juAD%RA@!@=X?<(Jw8r$m%C=^mbD^f@zq}*iUw0EWN1Egrac(XX1kM%9LD;#H z4WiC1mqRViy{?Ahyaz3Z)0rS8XP4@c(=F%sO)~$mlKikcH!`skSqoVVSq)jvE9)T( zdSyjqNn}lAQDjwQS!7*gVPs`wX=H6=aj&e7Ebo=|kphqkyix*EgI9_`szAy>>hMY- zNF_)qUa18s#w*nzr8uNIuat+> hhZKlZh?I!b=#?UoD!o!BX64uZf1R=Y(rjOM>?dvN>pB1c literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Argentina/ComodRivadavia b/libs/pytz/zoneinfo/America/Argentina/ComodRivadavia new file mode 100644 index 0000000000000000000000000000000000000000..b798105e0f660c7b85a663d945d9eb7f04505e52 GIT binary patch literal 1100 zcmc)IJ!n%=9ES0mHqjUh1{4$#sn}8)6%K7<#gB3%LqW_^14<_c5d=YTu!3lGaS{Sj z1fiwTu0oGPwKUZp8*8*f38X~?hk)XysKgMzdi+1fNnCXDUe5hp?sVaOGJ~g1b_IW2 ztKBH zns>I+nKVnO6Sj6Bs%m3(`#Ex2)w|-l{&vhX)~9TvR8e1QL;7oFxA|5~+V3+*)Q`IZ zdSxW3-1Q#4di;=yww=juAD%RA@!@=X?<(Jw8r$m%C=^mbD^f@zq}*iUw0EWN1Egrac(XX1kM%9LD;#H z4WiC1mqRViy{?Ahyaz3Z)0rS8XP4@c(=F%sO)~$mlKikcH!`skSqoVVSq)jvE9)T( zdSyjqNn}lAQDjwQS!7*gVPs`wX=H6=aj&e7Ebo=|kphqkyix*EgI9_`szAy>>hMY- zNF_)qUa18s#w*nzr8uNIuat+> hhZKlZh?I!b=#?UoD!o!BX64uZf1R=Y(rjOM>?dvN>pB1c literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Argentina/Cordoba b/libs/pytz/zoneinfo/America/Argentina/Cordoba new file mode 100644 index 0000000000000000000000000000000000000000..5df3cf6e6377be897b4d09fe438ec75eb8c15ad1 GIT binary patch literal 1100 zcmc)IKWLLd9Eb5YZK5$03@9if60xNuA{=dF#6RVg3{^2p4Je%yL=XhU!3v_$#YqTA z5QLV-c2zuwYH6cAjWyb#1X2;fA)vS^A~D3OJ%7(RiHlC=<=)Sc>4oo;9XffU$NS^A zLjK|K>zBiQ?PYn5U(c)i7Y6+Y8(!$CO?iK6@r;^YchO8wPUxA|J->A0m3sL4oq2RV zqGxB;`(`MwACHduPj;16`BK$9-PqJ~M}z*{aza)1rc9-NS3SQ{@aHqP^!&%GW+7wM zg8pD?@uXf%A2*FXVbvIGnlGVKs@W6uoA1YTYfaj;DmC@BG3+nZw(D=@r1@Stq<-8U z^p{7H%3ka9S56$z;m*^=Erm(l87&mMQlE9#r*p;b&8t;+^++-9SwF6K78CWF+IsVF zEY*Cbcg!aA!0;+P@Fo}Aw=}NzmyX49*4jW@`(Hkx;IF3*+uc26ZMo`s?j5wYV!W`m zFROtLYv0xbQSM&H!A#am%h{&-+sDF~?uDG6OoT%;(8-=iv|ETk@^Fr+f4l!nyil;V);oKhZAA5tJv dAyOhzqf?4Rszl1foP65^?DhZv literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Argentina/Jujuy b/libs/pytz/zoneinfo/America/Argentina/Jujuy new file mode 100644 index 0000000000000000000000000000000000000000..7d2ba91c679aca41eb44aec6115745ab107531e8 GIT binary patch literal 1072 zcmc)IKWLLd9Eb5YHqjV51QZkzDcDjQ5stPoqE${h6vQkwQ0U|!f*>dkRuGLY4k833 z2trGvT}98K8ro{l#+urp1X2;vA*i@nL}G~ldj6i<#6>6Hm&@lRyu%CMCo_EFcz5u} zwO0P&aP`UIdE+JdxqUsS9-JT454Suw#cerVTt2O)HeIk&lM`mTd0)@mc&Q$}dT$?} zi<#N!&Dsv<%#*P(J-4rDGpsKN{Bas|i&;n6l;CJ+*K-uNTv|%;L%wyOefn z$$YfcuB2J+KW6LuqpCjMuwNo4Riitu8z06^b3?yvmaFP(eMEn&?l#{`N&92wi28YF zP_K?AmAlrf*G(KU(e_h??fFU59?us#QlCx7r?Z95#E|WL``YYSxvIO&-a?{QRXcAE zwWS)*%%0hV85mh_2Hs@bvd+2CnwG!3BVlfN8C&E;oSRz+0_RGVAnaV(22tl;R6?!J zy{m=dJO{0Z)0v=O&Mws}r$^53OJx3G8~I#!X7p|m*$CMQ*$UaqE1My^d1X6fKV(B> zM`TN6Ph?YMS7cjcUu0usXRmCH?Cq7!k=>E)y|O>jfLA&|T0nY0n(#^&NE=8WUTFmB z#4D{Jy&%mX-5~8C{h)7%2kA(X4`>PL326%H3TewLeIbo`r8A^8q&K8Fq&uWNq(7uV UuXKo6`IP_NVysUJJT1rW#RmjD0& literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Argentina/La_Rioja b/libs/pytz/zoneinfo/America/Argentina/La_Rioja new file mode 100644 index 0000000000000000000000000000000000000000..7654aebf0b084f081db62e2b03dfc287df974e35 GIT binary patch literal 1114 zcmd7RJ!n%=9ES0mHqjUh1{4$#sn`z_6%K7<#m^%d3SyQT5IQ+XK@b!ND~MJXCm|p~ z5Lz1TDtH{CrLFeVSfd3?AuS>}1Qa*LN)7d^$NzJj#6>sn<=o#T(}nj*w&!GTTkyxV zLVn?J9hAfSy36voeLbh{U+C5kHa<5an{#?(?u;5;f6ht1g1Jw1NorF!`4t$lPp zW+uiqXxo!BlY@i$@!pauU7EE|Hr343@vxp+PO9>uPFt?tRnM;E^>pTznf`Fq&SadL zG4E|9kur1X6LxWbR4oqG?B~cSRcnju+PfjMv^H&*$`$oxu}^=k>@wdou5qe$8&|&Eo)Tki(+A0ON&kf3o2pu6q417 z+J5s`V`uHD**%dodTc@I`PH_oZnoD0>}|K%MC|GMiu^%0CXH<=9r=Ss6d z*txO|qR!3Fh8moEQw_y=4;qeSvO!wTPSqi&UC!^rWdC6*`C@l=!0bRKLuNy!L+10! zgvg9unG%^3nG~57nHHHBnHZTFnHrfJnH-tjE7K$MdnExR10)5naf=#%0_vXXy5Kw3y%NMcB4NNQfm4N1-`*&*p6 n`FSNlBts-cBu6AkBulTPiR9^(M6q8!@ju8kwol?c&>s5abp}>@eSp3HxLAfcklB zK(CA@l)Dnwt0(rENc&KJ%gCf@kB;O!l3z^6=hOMl4RPK1qG+}*AG2L%uZb7DZM?mPOV@7DiS^mPXb_7Wc~P$nsuUA1MH-z$+ynHF%{6qza@A zqzeT(;+0yEV!TofQVvoNQV>!RQWE-_xRIhH{(!2GvXHuv!jQ_mQW{d5SBgWb lL&`(yLkdJHL`pY=QzW@=X?P~x4 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Argentina/Rio_Gallegos b/libs/pytz/zoneinfo/America/Argentina/Rio_Gallegos new file mode 100644 index 0000000000000000000000000000000000000000..3c849fce2f09e040563440b93d1eae975eee4eea GIT binary patch literal 1100 zcmc)IPe{{Y9LMqBbgQ+51_eb(7%Ac+cu?1(Kk~#5LgdAS!czwcg23pIL6mjr6bxbn zf#S+*&{JTEv-Z@ol?NG2iJ-%v=vHLdSpR(bzMoUkrB3~RJbV52JN@AE9vD1%qBHp8 zS|e{bTs?Al-gHTRZe7c$`{(=hgRL*j^tOziUOcU4HeawaQEIk+FUbp$HRJVHK9reQ?^vSqn=;R>iNE#X8yw!yU^#< zf_ZN%9Z9p89C zwxnv$%$`ER==`$MOY3d#$U4*edbDNKITvbZ{L4EM{^f3rG|0b*bA{$0aIRPm!p@a! z5Or>;9BOjzO*ItfIcPfEHxQ)d>{8uwy5#)6$-qCXBp>X~jNKbT)j6j>En7Ficr7+D!v8d)1z+$*ai%X?*gqyVG>uatn);FThfDv&aeI=oT{ zQVCLuS873u@k%vFIY>Q7K}bbNN$6|hL5h<21FAyGLh3>aLn`x1X-I8eDGsU5E9D{e hAq658A|)ad62A literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Argentina/Salta b/libs/pytz/zoneinfo/America/Argentina/Salta new file mode 100644 index 0000000000000000000000000000000000000000..a4b71c1ff07647569d5284a3bf4832dccc9fae9c GIT binary patch literal 1072 zcmc)IKWLLd9Eb5YZKE+13@9ifQn95ZA{=dFM5~-+D2Q2VLFwcmf*@ENtRNa)oP>Y` zL1<~TYteJ4mbTinu|_+TKq4YI1Qa(#B!>8}=kGZuanZ^5u^!$yN>fx)m_R;yM zS(w|b?QqUK9v|0>`--Y~scfHYZJ4FwZF*@np-Kl+wp6>To?Xf7<;*R!{Nbuy$vCxQ z-rGt%X{zZHw!S~2>Jtt7Iec0*dSbfqZo)J-q;0cQQD5q#`fFvk`BqHY@AF60kEubu zI+j%KTAyA!dB{XM&lGm#r%h)pU+7AGGF>0f6}q>tSKZ}fg?Qj}-0UqRY8AEf=FyH+ z&(FGY)95P7iww!%QxKi>uteSIbr7(r-Hz_VmWAYu4IFVb1%xFcIV#I zLNV?^`{B$`ke0Jc^~vd#^ZOD*|FDgGt~)z+r;2QZ?1XHE?B$isklnnp9kL&?A+jT~ zC9)^7DY7fFEwV4NF|xB)wnp~$%I3)K$o5{@A8EiV9Uv_rJs?eZr3<7Dqz|t&f^_1Q zR*+thW{_@>c94G1H^fCclH>zgLV7})Lb^iQ@=9MwV_xYDY0WFWAJNJRvPfa0d8#1LOSp6@t`i*DY_xxY)M3;!>vv6CnIy+5v1 z_7@J>UCW5O=SSteO;6SAmW-TTJ}rtHF6d%mTFtd*Wa;_~asTBT{oq_! zmFG4}J(f`qr>5kiy%kZpSkv>HTWaB0KrXDrM0Gf>tBpJ2$>pqEOx{$B@2}{kq!CN% zovufR)NX0D|_2ggOLKO$RibE>^Aq1)BE_|lw^U+cTnx5|+IUOFOv+#Z!H zlS9H>9h9r552;Z1sr`ps9GQZO%fMkHA;Iug)NjPm5NE%2UNFq*~ z36ctui_<29WaG5yAo(B(AsHblAvvK-iWi%e{ReoYh2(`KhGd4M=CrvX$vJIyNP0+q lPMaW-A(A4JBa$SNC6XqRr_&~iY5R%)L8jq-Hr@jR;h$gt?34fi literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Argentina/San_Luis b/libs/pytz/zoneinfo/America/Argentina/San_Luis new file mode 100644 index 0000000000000000000000000000000000000000..acfbbe4349fc5fe53196304a7bbcdaefda890918 GIT binary patch literal 1130 zcmd7RPe{{Y9LMqB+v3w% ze6p48DYMXZ%vSftRdu*#zlA4Ntv#V@ABRnSO_!~gE9!f-U;n7=G(SrzyEJ)NE#K+Y zD}yQJt|oQkNVkbMpDJw4kD2B~zR;5XYFd_W7g{$Yb?d8=*_PR4)3s-2*HoL)#bMJK zRXWqZ%4DuD6*84xbKuKaeehn=biY2L2b^<}hUj0u!Wb|4a2O36!Wdr*L+7Sn27z;> z`5@+8*#>dv-pof5%!5 z36UASG9@ynS0+VfMW#jOMJ7gOMy5vQMkYsQN2W*S_eugt23|=4$pJ~iD_J0EcqI=c z5hN2N6|dxiB!gt*m2{ANypj--5t0&;6Ot5?75cQek-X%e5Re#>8Il^38T zy?SXVuI!aAy?kuHX=*u{-IN(OEs;#NwfnPa{dgwZwsECuD;>_pJgZ}7d$zq&R-3OM zigwqYm~B(-M&~{${rrXQ8CYR@76zkx7e~#${L$#3wKmYu_?LGm`0H-K#!!R%7P5AF z!Sk#wl)Rv|Mc-?(_GKy1Z0(y$Ai}fPd?1Wg* wbmx@zkp7&~AkrbyBGMz$B+{i*+C=(vN~1`pNUJy~ulhgq8r~@t@9qr$1mS)4g8%>k literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Argentina/Ushuaia b/libs/pytz/zoneinfo/America/Argentina/Ushuaia new file mode 100644 index 0000000000000000000000000000000000000000..1fc3256773606e80a3d300ddcbfffafe72b56e45 GIT binary patch literal 1100 zcmc)IJ!n%=9ES0mw$T_11{4$#sn}8)5e{u*#E)_$LqUu~EhwEFL=Xy!gB3)hi#P}Y z34+klXjj40p<3H&j>a18Py%TY!J(kIDJn6ICO{e^n)@{N6X zHfjpf8?+tBnnxoe`tk0fDqbktC!1e{7iL{=~Z_Q1Z)>tmzmi%PeKAg$7Z&{<-%SZAZ!AI3$cID$s6}9cg z;g)3Wso7bG8-4$v(u=EY@6amK`zq6tan6Mr8vpVRH~n%qh8yHx*ttSI2%IaHgC^%n zHi$U4SPnHi_j)N5<2h(PknRssa<;2(IbCvo-=zN^R+0~Pr-#R{AZsCuA*&(Fd1XCh zL9eWcEQzd%EQ+j(EQ_p*ER3v-ERC#$OzK2iWufmcdEYVb-CNEJvKNF81& z1gQim#VfTS#dxI}q#UFkq#&dsq$Kn;@gPM>`~g)VWg>g&~!Br8J~AuM~$==aur1 i`j7&V3Xu|#8og2^Ql(eQ#Eg8}|F1K;N1E;Fiv9#%wCh>` literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Aruba b/libs/pytz/zoneinfo/America/Aruba new file mode 100644 index 0000000000000000000000000000000000000000..d3b318d2d67190354d7d47fc691218577042457f GIT binary patch literal 198 zcmWHE%1kq2zyQoZ5fBCeCLji}`6kQhDSw;s#)FaR|Ns553=IGOAK1ab^8f$w0}Na~ lz99^{1}4S^435DeAYDKZLW0@i*>4b`7X5zD7>7@KLGdbhFOi3%YDPwXpWw6<#-q|c^KX%xuU;iZ2 zF6Z0nXQyfU$y)Qs(K4CQeA3R0AJj)T^_p3$N9kjW*O|woF?pgeW@b;W(~OawCga{m z@?`%JGe?Uwv-=A>_tF^2I(^z?$CEVl&P|(B)~`}G!Gv?GW!~D5jf_99(Y&CI{@E?L zQ|{W_zMnPk-gTSb^^MN|{-S-VtzBcCy=FoCMR~fu#TFd>P!?95F=p3(DXi}|&qO}e zMJs#E;)(C-lJI%6i0bki@7w3PVp9CEXG_}BrSx#0Eo=5< zMSRGtEUlGQD{ha(D(zx0-U{)4;v z!ijD6#m4Kpp{dAj-0-Vxs?4-6E$o%e^ONkB85d-0YQNp;9hdD1W6kzkO;X+Wi>c{c zFFU%r&CXBuYHiy&^YYu3TK8tF*;O6W_>Oj4UzD!9m%nHCWO&+;xyQbeJY5G9Yz8 z3V~DtDFspsq!>swka8gPKnj9X1SyHHs|ivRq$)^Rkh&m+K`Mik2B{5F9Hcr(d64=b z1wtx>l*rfB2q}`Us}fQsq)teokV+w?LTZH+3#k@TE~H*a!H|j}B|~b46wTLF4JjK^ zH>7Y#<&e@LwL^-BR1YbiuWP{nzkZ1eToVhT?u-; z#K_FT`v3nb83u;`|95U+WcmMp^#TSCFq;QV3V=uk5g*?W24@!_4hG_IAPxv&a0RkK vfDuZDkl?KUKv49qB?Ux-oCl&oP6W{)XM$*uQ$aMyxnP?5PUZso!ITRCV)1m0 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Atka b/libs/pytz/zoneinfo/America/Atka new file mode 100644 index 0000000000000000000000000000000000000000..43236498f681cc06f64ca2afa613880331fe6fbb GIT binary patch literal 2356 zcmciCZ%ma{0LSsm-|Iy&Dj-Bm!Hl7RhrdBt9tc86oi^IjQM+~Y*&*2zbbYMq#bZoSBp@5Baf)v>D*mc{?KkJo0^@vqt7j*K)_f*Qz7dmyMORerXqQyXsN^AUFrngI#QPeLpD-u*z z;!c^J5v-nYdu2{syvVthEpz9B#FOV@C(cetPtrc&0yEuYLc7kS()1Z~s}9 zUv^19TmOkFSpAJIEa+2(zuu5VDIbfXi{r95{E#Rf8IdJ3&EomNZ}s}`4ye+ulX}Bf zuc)#u1KK%SqRQ9o)*CyLRYhEt_Es)b-nm>|nRQcDUagdymWGQ>XLID{J2yo2N3rt7 z>2a}T|D1ejY(&)5Ps^=C?}*yc+q&-HNwqEIvfkb}pz6cNbVJc@)i85hHzro8#tZv& zlRH;64cF`DYm3#ZM|?e%fCW* zj|Cb zzK@T~_1P7d%kOV+T)}>Sdu_lx`(0pviLm!bzOER*zqd7DiM=mcU+Q&js4#Dpc^$7S z-`w*Hyso@;=CaOQ%n9Jb`Rn5S?~#R>Kk#ynn3sFJ-<-8){usyZgVi<2=#b%A&G?W3 zq8%X@hR88v1O|zW5*a2kPGq3SNRgph%~+AaTFq#Y;UeQj28@gt88R|vWYEZ{kzpg_ zMh1?I92q(?c4Y9#=#k-D&G@Y*07wLo5Fjx?f`CK;2?G)bBoIg>kWe78K!Slp!)n5T z#KUR=fb2@Mh(BsfTPknkY!v6=uO5kf+Q#0Uuz5+x)| zNSu&BA(28tg~SR877{J12^SJCs|gqqF{=p~5;G)dNYs$9A#pIBoav^lt?U*U?R~(!imJwY66Nx)M`SC#MEkn zibNF&D-u^Eut;Q)&?2!#f{R2K2`>^~s|hd?VXFx-5@V|gG7@DZ%t)M(KqHYxLXCH0 z9UK@EdauXrnRg$bziVB+?f+@^KheH>3o}7a6Q=0Nr5UN|sUo>FEiE-IRfPQs4Q6_I literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Bahia b/libs/pytz/zoneinfo/America/Bahia new file mode 100644 index 0000000000000000000000000000000000000000..143eafc2c0ab9d7f54bb0a21649d32ed1b4489d1 GIT binary patch literal 1036 zcmbu-KSD=b`c5eKH%r9@!(%q6Q%#Ufg z5X<*bXMe0YB})^h^=EyPsUA&hb?TYaQm<_7e$_00sncI0mrdh~>)Lejt!Xy9t;92? z?`8npdM#dJiA+HreCwtd=Qw-04Zdg76G4ou1p zGpaie-V?oYNq05&nanYb}Z(c@g5BC+KPR;lm^M3s) z{KpFZ4>^`V)(HAVkX4XnkadE7A!MbXUkX_ZSqxbXSq@nbSrAzfSrS<@=odv+4fOcxXDnUv?YC(!YszJ&@ z>Ol%ZDhm3NkeY(ND5NT+ETk@^Fr+f1G^949IHWqHJRJ7#ZUyZnlK)g+s>hd@k5hjD D$US9O literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Bahia_Banderas b/libs/pytz/zoneinfo/America/Bahia_Banderas new file mode 100644 index 0000000000000000000000000000000000000000..cd531078d0e8a4752bc242e83836dfdf0ae307ca GIT binary patch literal 1574 zcmdUuOGp(_0EUldSSC2wgD3*2APl>y>CLPxyOx>WG`p2q>b30JP18{;tb80u1R-(^ zf=GlQdQq4`nLQS5auXF5kqUtep{SsfAW0$9`NmZQLGAhvXTD)Jv-v(>VK9&=ep|Ty z!evGJ@;>)+?+5eNXw`6UV5k3{=as)N((Uh?XjM-?*7yfH?kFKlnNZ$hB-YK5D>4H{ zQvSG1Ub5M6Cl1M#;Y*E_`QJp!`$%Kek3o?-7HzHWeJR%TL|AJdc8Rq1&(^xmX0g8d zxs~41DL3TySe~**nX&w=wK1baZi+f$WiC#and2QsR@fq$HBe`48JQ}x`*s*P&t@v` z`B<^_=9tQ@pCR&2zg62xm&p9e`)Ygobm`0KR6Alm$(?bHYL|E>3ua$13x;pW-D3@A z;o}au=S{J>_jbEbrZ z{ozav{kLP=T|Oc7(-L8}edD5-X4~yjMDiYD*!KONkXe)8bG$y`-R=_}ee?A7X6Sc) zf1U3yWU}=T3L+ImEQnkX!5nJIAfmxZ+4#39c_JM|JcxV{0U;7XM1;r)5z?WS5+Wu; zPKclkNg<*#WQ7RJkQO2?L|%x%5Q!loLu7^s?NCb%5!<1b8zMMFa){^<*%`t^q-Tf^ zkslHOBmzhXkQg9AK%#(z;ZVl`2?P=eBos(2j9?(qFv5Yv!w3ix5hEl>OpKr)Q9;6T zsN;eJ28j$38YDJIaFFOA;X&eq1PFB3W=2wEF@Y; gxR7|^e;06s?%S=qj`JqD5?t|d-h_BpvMWB~Cwj`Z5C8xG literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Barbados b/libs/pytz/zoneinfo/America/Barbados new file mode 100644 index 0000000000000000000000000000000000000000..7bb7ac4d6ab698cc62fde7d1a357be3c06e34e8c GIT binary patch literal 330 zcmWHE%1kq2zyK^j5fBCeE+7W61y)w7Jl)Zn{FIO3(*wRKJs0?=GdvI|$-5v}_UM67 zNWcZ*`2Pta26h3A%uG=D|NjYT1_m(6^8f$U3yd89{~tfVz~kc^!r%nNjxInP90IZh z3_?h7&3_;Oxd=pqTm>=!ZBAzC~b{6>LU12Qsw+VS%sn(j$bI0_I<*&!e-w2b-LseF2|S4`@q3z6qSY+lhXHs zDZftZ^Jb)KvuC>Yoa*|>V_Ywd>>Fi>xYHh%9}S09xP{W<8H*2<)A zU0$fvtH$X`%d}s5_1EsU8NJ@u{?@%2Gc&OYZp`>jK~MBwnWD8e=XQ8<)>ii%$J(dF z?Y8#r*6CYX4xK5TmmBc^<%IuODL-ggimc6)#mH)8IkG-e3Lq6Sr36v~DS}i%${=-+ yLP#Z~6jCcwiXqi9r5sWZDacMLA|;WUNKvFJQWlHy-41Qt+@I=tb5eRf@O}Wi)&79)F>$gce{&1EtBcZv}Yj&$&pcnFV<^PQ_FFU{ledA#F%f8otL-L>9p3_q-N~u63Cx{0b^N@5#zceGu~x z{lsJOm@(PF^Ng8!)voHh;;$Y~=Xp^#WeFeDlh4vB{ZL?R*~k(fwOr;Un)b=tT{ zV5g0YghpZ`!I9`lcqBeD0AvKn5Rfq-gFr@s48v*1fegfHM}iE+X~%*L1{n=99ArF% UWI#?kB4kL;|2d|lT~}RlA9t(iegFUf literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Blanc-Sablon b/libs/pytz/zoneinfo/America/Blanc-Sablon new file mode 100644 index 0000000000000000000000000000000000000000..f9f13a1679fa9ca7cee6a41bf094a861d4c6824a GIT binary patch literal 298 zcmWHE%1kq2zyPd35fBCeHXsJEMH+y_ydA9x^LO11INk9m;ga%~f=dZ+FEBAOF|#oJ z|9`54f#LuEs}~qq{{KIIfPn+d<^hueKE5FgjxInP48-9;91sFDje&s?OoWi&ivK_m jXKM*G_y2#8Yd|!}MIajFDi95F8JMPu>$rf9HsJyQxN&4H literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Boa_Vista b/libs/pytz/zoneinfo/America/Boa_Vista new file mode 100644 index 0000000000000000000000000000000000000000..69e17a00e161ef17bb763868db7793b609a39556 GIT binary patch literal 644 zcmb8sJ4*vW6hPrkd=TQRv9XB8*eVPrU?L);wkikiuNE`{>zG4R2R}4o;fp z+N^Ho?^P>Y^;(y`rd=D>Uzv=F+@9&t{bw_FzT`#YB{P1or(=CjW~TArC9_8=RjKRL zOT?tDwZTA#CunUx>NwUu-UYhl>^Y(371x`ved* literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Bogota b/libs/pytz/zoneinfo/America/Bogota new file mode 100644 index 0000000000000000000000000000000000000000..b7ded8e64e79bd69589598da5a5b1b39d92344a1 GIT binary patch literal 262 zcmWHE%1kq2zyK^j5fBCeRv-qk1sZ_FjEK+zE#~3??R~Kh_5c6>XJlq#X8!+w*8~Oz zFv;@&|M3Hi9RL6C+`z!&;~T=@1jM=qCO~Wo)D8h5Bv=Zx&-loJtGg>|+#tLH8etT8ceMf^x@jSvf@1u| zSvs;zy7Z*2nToF2*bvunZDn(~HLZm`*DM=7Y_fRxb>8RS{_4NBzIVUh>$m&==k41N z*pwswaW3-@5BHLJ_)b>l)%*79&}#$nI`l$%sPC0)`tYjql#jcj^~j<>TSgiZ#9!l2 z%88$9#pJ~rIrUM2m_C&+ox{mO>`#(mozo)xsVO;QYody1n5!czC)Lct3GK=nRj&R8 zI!X?ys3XUvd+c>}-B*1&`qF6;Gt{nQKj;&2?}Vg$xm{e}^_;w6SChCg&?slGFA?#j ztK^*gG;vefZ8BkDv6>s@mWko1D)INlI_aAzl{_*_&pY$8nt%L?UeI?=r3B~6o1Z=8h5LrB`@mYqK&Hfk6~HjX%!_G z4$0D(dQp0!ORl+ED9T=aOP7CEr@XD5`u_J%LRI z$v9n|dsbC{pCN1Ke=BN-W99nkVX@)OG5O%=AyL;cA|D#;67`KI<;LDE;$iQ3-O$;o z9`W?+NBsfS7_(0|m6WNbt3mBg^(g6dmhKU;HFrvGYd$Hq$6uB^Dtg3@=?NXo>sP_iFZIrZeQM|6X}xPasGjca)6ZP2 zRjqC9diSXU)mGo6_v}wr;bG<%IU^!+=6~wvID6xSaGZlWEW&ZRm6+u??}oyn?OXD{ zm~Fok%Dp~OS!ABIKH;q~Po;VIHve&9_6@#&F*(OveMI6AGCgE|$OMrYB2z@>h)mLI zW{FG_nI|$)WTt4R$_I1h%w&Pta!ePQFEU|d#>kYBIa|%7ky%^Kw2^ru6GvvwF?D3_ z9Fs?8&oO;u{u~KFGQg1nBnKQxK(c_O0m*~aBm&67P9+EyJe@Ft6 z3?eB+a)=}m$)eSy5y_*~BofJ_)ua;1B}X!mY;vR%$tOobk&JSr6v-))R3xiNT9Le3 zO=6MET1{$^+*(a?k?bPrMe@s$U?jsFDMoV4kz^#x9BD@KY&D5SGHo@fMsjU6$wsn` oq#MaMl5iyBNXqelmUFkM{Bl$I4DZs+oXo5YZ+3QOc4n6QZ`ww0CjbBd literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Buenos_Aires b/libs/pytz/zoneinfo/America/Buenos_Aires new file mode 100644 index 0000000000000000000000000000000000000000..dfebfb99abb86536da12b96677dc0c76c088cab8 GIT binary patch literal 1100 zcmc)IKWLLd9Eb5YHqjUg78Dc_iP%ya5so%7;-B(LhN?764Je%yL=XhU!3v_$#YqTA z5QLV-c5x6qhiYk~J&iTCLkXlJfzzV5R8j9t&E`xgfM2OH<~xM3<~QDr>gL*%X_l+%YkkOHs_xL=N(uA5cu4)Y zGvF@|CzQPw_g9S{)ZzBig{}Ds-QJcjbR<9Pj*sUGom0O0bt*W-& zJRD6np6Q)4G2J(`TKB!qM)xm`=>x@M(X6#Lu%hKJ-%#+Ex5eE<*3QK}&)QPO3tC$? zUf9|fl|ZYtZ)$-y?p|wuI^(6}Y*%qPJ#v2EB=Zj|$p_oB!?*j9wUEV-)sW?!vL3Ra zQ&vQlMAk$WMOH@=jSFDFCU!DJ38^IHd@r3Zx9A4yP1? zRDzV^lvr97lQ gq(G!Xq(r1frxb})iIj<1`LzFEXJn5w+uIZQ2~&paD*ylh literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Cambridge_Bay b/libs/pytz/zoneinfo/America/Cambridge_Bay new file mode 100644 index 0000000000000000000000000000000000000000..f8db4b6ebf5b66d3d566fc3975d6151783f6e645 GIT binary patch literal 2084 zcmd^s#^KxUfIg>*r9A|hfIcss2ibvH}IZwtqG z2K_Js8s8bWb$#dB3;CH)qPjf`+!yh5JUPMw``>>w}U} z`J$Runk&Eat|{_4+VLB<8;%`@U1yKZtl+a-7Syve&4mPG@fnEb14+Sj|!_)pjB zf{us@99W_YBb};f$27e-xJ50g9o5C&m((*w-|3}kd8#Dqp_JSmlhQ|DkCdhUSW|ZK z7xnD-XJuKx$2@oTh%Dd#vw8mTURlw4*{tk5tY4_^H>;ZW=<{#cb~C z(y#kYnKvR)-8|`_X$e;8misXi@%nURsNO`=rs(MLGPCvWuXu`S*AOhTFl-<**YaDIVCmavGM(-J)WL6&X11c3;CZDNhFSaoRsRG>50S< zBNG#eulkdedz0brGq_6*cbW6|dp_w41SGUBETI~E*4R^C&Lb?VIQB(_Jt9f&huq_z zYxqC-kg&rEh!+qy9IYP^Mk!8#L@Ny(iBJA6-ZkceStKF(HTf<7`=fs zhtVBKdm#ORGziimNQ)e8k04EQv|WO<$4$?bF^B~=W|L693Y%7=AOy-9c`-}X6{7_+`ztA7> F`~~dE4qX5M literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Campo_Grande b/libs/pytz/zoneinfo/America/Campo_Grande new file mode 100644 index 0000000000000000000000000000000000000000..495ef4568347ab6d3887a2cdd762c960f567a0ca GIT binary patch literal 2002 zcmc)KZ%mbC7{~EPD&|mYt~O(Zq0Eeg<8?SEJ)zO1Owsg!8-HMU|)M97Z!_wHpc>YWdJ_?$5|#&&&w ziHe%7dBMM)Tij20d79nJ=cz~DZ;E!@vF?wK>&2#XWT~DWXqCR$r`k6>SudskZZCCK zOaDZc4!k}rgZ=O6P-2Pv`Pwvl<+klIYU}js?Kx(wG+)QgekbE;Lw5XRs+kzROaI!x z-(+;1(3y=toBP@eZDyq3%xZa2XHU6cvdU-Ltjl|3PAsr<&iBgPSs6O_%!tVjlA3+^ zm^}E~J$BwUH97VR&E5K~gxcTL`O7~MX>QYS#(I;tE@UIAjS`JjYILke7G&n?f}RGm zaP+*5bsUn12S+sjK)zYjHO(%rz1KYQT6lF( zmc=*QWkVCDX!>C-I`xjM7&mt1(O2ZLzJqpE^V??i>3SRAw9gbDFS8{p+hxtZ7qm39 z$UI*4gsu&;O`>q2CVpKmWm)N3*7bmtr%Y)1$uxQLa=%u*IxOo4cGwNid@UP4TVtPE zVdUvI^X#T@x7plQqLtG>G*#7Mts4JIwiMl|TfV6?)%Rb~>W>qqCiQ!*Ilfb#{iRd4 zHLaEHJ-zn1iic#!N2hFU{tS8kz%jcs{hHL(?y_~)ew6x>jkf;c8EJ?$X~X9iP2=4g zwDE&cd1zs$WD0ORv>$UYzDF$$aWz6fouq}BgmE@dxC5VvMXM;Grr(iXe5D~P)9k%F9fI2;OxWWMCY^Fk3mq}>2?XkIn| literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Cancun b/libs/pytz/zoneinfo/America/Cancun new file mode 100644 index 0000000000000000000000000000000000000000..de6930cd8ace99cd0a9658951c64b98b71a5f389 GIT binary patch literal 802 zcmchUODIH90EW*n@+`_@!%_(g8HVwG%rFh(eOFdjF_Thrv$8y8vhvs}8!4p-E0K+) zlvs*{jgsZrh>+uacVl588~^Ft?{@2MzRwz+jQPZmrI{~WR$(sh1?zh^cG29WT^)}r z-}IYZ6OO3b;al5ZR;m0a!mjh3JN4-$B2fIK0*{}f;r&cCK6_=;-mz-l`H(G#i>h_$ zUbd|`sy%TeJCX}d=fIi_jwhT@RZ@0^hMex)m<*TJIN{e>5%Cr}k?RT3^YG&I?uSM6 zB470_my73QU+27QV3GXr76Xz<BH zns>I+nKVnO6Sj6Bs%m3(`#Ex2)w|-l{&vhX)~9TvR8e1QL;7oFxA|5~+V3+*)Q`IZ zdSxW3-1Q#4di;=yww=juAD%RA@!@=X?<(Jw8r$m%C=^mbD^f@zq}*iUw0EWN1Egrac(XX1kM%9LD;#H z4WiC1mqRViy{?Ahyaz3Z)0rS8XP4@c(=F%sO)~$mlKikcH!`skSqoVVSq)jvE9)T( zdSyjqNn}lAQDjwQS!7*gVPs`wX=H6=aj&e7Ebo=|kphqkyix*EgI9_`szAy>>hMY- zNF_)qUa18s#w*nzr8uNIuat+> hhZKlZh?I!b=#?UoD!o!BX64uZf1R=Y(rjOM>?dvN>pB1c literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Cayenne b/libs/pytz/zoneinfo/America/Cayenne new file mode 100644 index 0000000000000000000000000000000000000000..ff59657820c61230c738875e72036cd133326f41 GIT binary patch literal 210 zcmWHE%1kq2zyQoZ5fBCe7@KF}7wrkZ51SsS|Ns9#BNNmA|K~n1F#P|2`~U;X|NmDn uFmU+zhA`+Fm;kXcL@PrG3C06W{SUGNM1!mW8Bq^p6KNHf4bWOUV=e$vi8s6e literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Cayman b/libs/pytz/zoneinfo/America/Cayman new file mode 100644 index 0000000000000000000000000000000000000000..55b083463a8b5b19d62b6f2707665c08eca5e65b GIT binary patch literal 194 zcmWHE%1kq2zyQoZ5fBCeCLji}c^ZI3_m{*Mj74l=1wj+F14U?9S&?zpP%=nM8G=GAw+LbysUe+MCSs)FY9gtova(V; zpca}P2#i$7LQ*ouYDzJJtQ}CHS>!6rNNMlZ^KY7Irkk2>x@b9@A2N93#ru4&8F@F3 zlD|BE`x8FA@9c-~gSGuqwlPAled8CkZuua+{;31%x%Ud>`FnmgWfH9bPJLEhaz9~S?p`LZ)Gam@O*!&vS(d4+o+wqtmzvGb%+{~vW~%C? zm+RM)$EhtdN9e6#!_>9}KV8$$qiTm9)U};$YP+AWY~Q_8z4=wAyjAHobq$TOV@18G zpV2IDS0$*OB@fE3^b*rB_cnPa`bM)m?E(Gn;44jIr`kSUj-Likex8~z%A4~JuADB<#wn>Xrn%1B-(%SZ@`P8#TAE;kwK69_qpTGEs za@Q5PTgqHfwjOA6D$tKQ7y#y7SBR(b=Wyr}X9e*!Vp4bM$=O zbK$+_m%*v}ctEZ>-jgdQ4yBmhmK6E5G2D1+!o|BO(8%gQ@hLrG`Yb*oeHRQ=zBwmp zzbW6VeiOR1f6Pb9|DiD5f5>a9f5r1Mz&x%_YFnuXwpN+I`bBzB?PF%}i;u~WH3jD6 z`wQfhq6~9tUWS~O6>ox4;^p*9Ld+Q>LnQdzvFgl#UJ2=QrV9BnSPyMKp@!`}uFrb= za}~PzGd+C$4s~|nU^(aR_3GSdKgfui-ZJOKHOcv@Yt02gTO{nFyG@v9uO2yIjv48$ z))yU4GU0Vk=!m8pRAkv=9aTL^MHgr3n3Wf(*xW)HwJ<=9PR^8zuRW~d!p6y%QSYm< z{=+1G=phr|>5)rL>@nkZx5=dkUNH%ky*hFG!{)LTZaw~KWhUg;>&r_XQdguurzg(M zSCgVkbkd}2R8sdgNsheLBsZ;*l)!Y8QoTe{yJF2%&#cl{H&0e+ON;d6tuZQnX11R4 zEZ1{#v(?O6lRl~)m= zZ|eL~-TY*V-14E<+*%kew^g>A{ER?RD|VR$aYy9#{0(Md&|WD>FEs_8E?pR3t_s~B z>N|p$t2^p8>!P0d>dvy2dPz&FT3WnF->#if2vN%T^CkeSH4LpT2+k9bdmc{pIic zuJCL{OUB9Oq^stQ(cl|KNF|h&lH#4 zHv4@3!1WJy(QDtVzMgf+J|Y{5>?E?4$X+6wiR>n_oydM78;b0xquo+uPaW;1BD;!g zE3&W1#v(h5Y%Q|4$mSxui)=5lzsLq7JB(~Gvd4~glaXC^wA+mAGqTahP9s~5>@~94 z$ZjLsjqEqF;mD37TaN6xquq35*B$M)Bm0hQyrbQDWb2W=M>ZeXePsKQ{YM($Xgh$k z0OW@JxG6$1|c0nT7>k-(KZR`64EB5Pv|s?Z|D@ywu(oukY@4d7Sb-HUr57{ zjyc+vAw6@nP2$t zJ|c}oI*GIr=_S%k9^FLR$)lf0LwR%*X(^AMI@+cpU3Ii=Mf!>~7U?X~TBNr~bCK>M z?d8#5q`^EojI@|XkC7(x=(3}2Gmkzajpos5q}52Tk!B;^M%s<^8)-Pwairx)&mC>k zd34>;ww*`c9c|-zbRKCv(tD)&NcWNUBmGBi0OSrpZUN*TaI`l8au+z-+knS?;An3I z9(MwAEAY4%keh+W-GJNEy(SH n+%L!tga6+#|L%?%V9%T}_S}g`8yz(&DkdT=Ha03YDrUfMOLBGg literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Chihuahua b/libs/pytz/zoneinfo/America/Chihuahua new file mode 100644 index 0000000000000000000000000000000000000000..b2687241cd05b6904a7d95bae697642becbe5b1a GIT binary patch literal 1508 zcmd6mZD`F=9LIl`d1$R1A|Vn=wC*142{UHbX6$Y@cQbd`-OOgpHpaRhcZyat97@y- zCGwEAl$odUN+^UBlUkB3DI$4Ff9Lb}MtP;Y@I9SAr~jM(o6jc@tgeigKQ_>Q;j*B; zyoaCa?5l78P}|d4SrodQ@hH?4l@#jw(fmHHs!`$_2G>u^Dwc$TH}#2==c}anZk;@6 zs!AF2SyFnV)b#JqWX7jqW@hI@N$rRg=ZtYQd`kdSO?g%DFc|{6~^i?u7`+3y)HZT3$^`PS55?+GwN_<2{8FA>u2!yyk%}okwJL3>R1OQ7s<9uX>cd>K`o|Ngel*Ffd2w0R zUL0s@ZavoP4)mGY)0cGJ`n#sS`LJFe>{S~oH|vHOchttr61{2kdDR#bmd0;;Rd`5H z!rgUh^Se~pay4IVeK1b8om^+Ow~Or9nP-~zy%bMBPJeqMJpcAM&e0YT$7wSX;W+0{ z$Y960(&35X?NPtJ=lBESUmTDOuFt;h{e6MIFvhXN8i+X%dmsiuEOKd0;y>8LFbZN7 z!z_qh5W^srK}>ULZG#xcunuA#!#;?C3=1J9GHiqx39%AlCd5vNp%6 z#K_FT`v3nb83u;`|95U+WcmMp^#TSCFq;QV3V=uk5g*?W24@!_4hG_IAPxv&a0RkK vfDuZDkl?KUKv49qB?Ux-oCl&oP6W{)XM$*uQ$aMyxnP?5PUZso!ITRCV)1m0 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Cordoba b/libs/pytz/zoneinfo/America/Cordoba new file mode 100644 index 0000000000000000000000000000000000000000..5df3cf6e6377be897b4d09fe438ec75eb8c15ad1 GIT binary patch literal 1100 zcmc)IKWLLd9Eb5YZK5$03@9if60xNuA{=dF#6RVg3{^2p4Je%yL=XhU!3v_$#YqTA z5QLV-c2zuwYH6cAjWyb#1X2;fA)vS^A~D3OJ%7(RiHlC=<=)Sc>4oo;9XffU$NS^A zLjK|K>zBiQ?PYn5U(c)i7Y6+Y8(!$CO?iK6@r;^YchO8wPUxA|J->A0m3sL4oq2RV zqGxB;`(`MwACHduPj;16`BK$9-PqJ~M}z*{aza)1rc9-NS3SQ{@aHqP^!&%GW+7wM zg8pD?@uXf%A2*FXVbvIGnlGVKs@W6uoA1YTYfaj;DmC@BG3+nZw(D=@r1@Stq<-8U z^p{7H%3ka9S56$z;m*^=Erm(l87&mMQlE9#r*p;b&8t;+^++-9SwF6K78CWF+IsVF zEY*Cbcg!aA!0;+P@Fo}Aw=}NzmyX49*4jW@`(Hkx;IF3*+uc26ZMo`s?j5wYV!W`m zFROtLYv0xbQSM&H!A#am%h{&-+sDF~?uDG6OoT%;(8-=iv|ETk@^Fr+f4l!nyil;V);oKhZAA5tJv dAyOhzqf?4Rszl1foP65^?DhZv literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Costa_Rica b/libs/pytz/zoneinfo/America/Costa_Rica new file mode 100644 index 0000000000000000000000000000000000000000..525a67ea7913b5a9e2507067c164de30259d0fbb GIT binary patch literal 332 zcmWHE%1kq2zyK^j5fBCeE+7W61sj0G;um7Rf@Yoxg4^=~gvGQIgr^4ts84^8ppm!j zf@b-l1kFP?FEBDQLE-=Z8;uzlz$DB6|2sD@a{m9ndI1Bkk8cP=uosYUb^+qx5TL;j z5JG};{sTc~yOuAA200640?27#8t6O_4RRuw209Z&gPaPYLCyuyASZ)q%ACyw^n)1} E04OF+k^lez literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Creston b/libs/pytz/zoneinfo/America/Creston new file mode 100644 index 0000000000000000000000000000000000000000..0fba741732f73cf241e702d41f4ccb9be60124d3 GIT binary patch literal 224 zcmWHE%1kq2zyK^j5fBCeW*`Q!c^ZJk>}%cy^L|=0FfuXz|3B#n1H=FSb0;vc{QuwI sz`y}v`}l@1_y&hC1OPD%gpgp(e;|mnE!YF1LDqq2GOXkRy1|?a0DglyQ2+n{ literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Cuiaba b/libs/pytz/zoneinfo/America/Cuiaba new file mode 100644 index 0000000000000000000000000000000000000000..8a4ee7d08fcae58764f1b1c4e8ce19f548c4734d GIT binary patch literal 1974 zcmc)KZ%mbC7{~EPD&{C_E;nO^VayDL<8?TsM>2LPQ#3swc|t-hg(;CFN{k2!LDy*W zg-IYjjh-c5!BQDy?fb#b?_${jT_X6y9o-C1V5Bu~dr{~!}7!*=3$vY8y2q1X5A zGig0XbXMaf^I&IzO^*zi*=;ZBLwEjYGRkJzjEgNYCmPr}=lW#s>@=NwYSd%~3C-*{ zB=dg1-_GBzCd+=M+1tLCQ0M!)V8v%5t?e34+hB6mhioLdQF5adnmgVr3)8c8VQ+(3 zGUmVc#*GFW-;10X-xo>6D7i;Y^D~&w+ZjRj? z{>E%+FV>3bADhamuvSi-k*$UI>elb-Ox43zwCdBisZRb$s}Jvz=P!5b_NH}G)7xiX zD1TITd~(9p=FOCy`w!V&sn?{gw%OKQ`$g)DH`)60r=%g;qzzx5H;wmg)W(m-8Au&SA^e|If|P>Pf)s;PgOr2R<8=ig6?t7rNKIZ>6jGJfm4(!W z6oyoWl!nxX6o*uYl!w%Z6o^#lbtNJ-dR>u7l}MRLok*cbrAVnrtw^y*wMe;0ywzo?vLar$B*>b0-J&3?;&sb{tc%wz46-uF(jaStEDo|d z$nqfTgDeoTLSDB-$QpUwA|b2fb<2dTlh-X2vQo%WA!~&!7P4B%av|%5t@Ho6|F*Wk m-pVcGZ~vb?OIhN!EkDw{wYAPo(8`Us^A=q^*3WMtIuIsLqRoy+m zpc8pdC&wpsPkK!E##6d46w|4|grwd_rT?*41}YI5yljx6(+^1>JWFQ#PKGzGWMr)@ z?&6WSPcyFLuk-!-1dMqtDP>IMM)|+b>Vwm045$4Ur9%0Fr!sEN?yQ=!ccmPM51if~ zsu;T{!=esS2&sgWLTc5t#gJ-9Iiwy^5UGfiL~0^Mk*Y{pq%Kkzsf?6HY9qyK+UiJo eq&_kMWD3Y6kZB+jL8gLCrsls)Cuqmz2EPDo&71K6 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Dawson b/libs/pytz/zoneinfo/America/Dawson new file mode 100644 index 0000000000000000000000000000000000000000..db9ceadd963f3ad7cd3664d821e2c5cd9676469f GIT binary patch literal 2084 zcmd_qUrg0y9LMnk{y|B$XG;|6V2@%U2!}rc9>xAC?uc=O;&}3ynHqlv{(*#!T7tN- zS~=xgPHn4BkIb9anmDw%S9I8`y(*$-EMBB$=9cS>xpgvYYKG2CE|bhVrRJgQGsQnN z!(@H&r_4S%W*$CvRdjrTp0n?BnHx>h*)89zd1bfsBdb1BkEUPNIe7_{Gd^l^Q@d5} z$TueM=gn%t;7OA|+$9BxLnbh=Q3|_WF-3=#NpaIVx}<%cEDUw%(z?k~mJ`+GB|pic zl%Nj!C#m4gOuhK-O||5!d%EK4*J^2hhI#Dl$LjIkzswWw9Z*lU{$QT!`AC-444UQj z`=zqbm=%@VWW{g8I#lqM3JssuRcTvQ)xc4`YIL=F`uzb@{cVec_Z&8>KdqLU^*c@N zk$^n2G-jTSE2;BWn8=zt61kUUqGeYkdSi)RJNvd;`=wvU#)s9q0+a4PRWwyPga_i zA3fP}q(N`JbBl%>gDnW^p4i^sy%&FchnqL9o|$E51y0w$S-DB zMxX2&yl8fh^~kG<6Xvz=8f8z{+oto(O6l6rZuY*LC&@|no-i?aQu5@K|KRq#&T5~} z^Lo3JCVJk!Hs2KYKFvLq=Y1ea(|PyJa_?Ey;fOC*X-}O!&)HMUZ~LLveyFu_CAsH) zBmZItp1nF>KX?k+1F{KZ7sxh{eIOfg+MOUWknJG*K{kZ!2-y;{CuCDj zyDMZ{$i9$`Av;61hU^X59I`uPd&vHf4I(>4w&=8bL^kQPyF|8$>=W52vQuQM$X=1n zBD+Pli|iNKFtTH0%gCOQO*`$bk!>UUMmCP@9N9XucVzR(?vd>y`$rmpbO33A)Aj&q zg41>ZX@k@D0cix%38WQBFOX&+-9Xxb^aE)K(h;O3PTLctDNfrJq%BU{7o;&rXOPw) zy+N9TbO&h<(jTNjNQaOXAw5Ex@xVSh_ I94Jct8Dumjbn)ly@Uf1E)E1<+6Y}RjA#>RkYqK{Wudrez*P!CtxCusH+fnF zZM1`J3L=RJ1SxD2L2a6-oG#OJvti0ovoy2$dY_MWt=#nu=ll+{{XfaWU8xP`pKpc! z!{KYv!+TAyUPGg|{iU&ld(gkur`OGOr#oxp$^IGp)E4B~=EruzJd{l0md)g@%k$zR z|6<(9%j`}6>gqL@y>r38?my>pL&yD_u5S1C}pOU4CBlh?G7AcN4+wjVIF&FD?aJEunCn{`7 zPo0Z**80+5+QoucNnEdG|4PGfpv9Ol9CSfk-@T?RdRIobaGR=Ktx40iL(e`v2lTWh zO{>27g1>ii8LNc)wQoDN1z87K2w4ePDpJ=%7DHA;mP6J<7L3#tktLBekwuYJk!6u} zk%f_!k)@Hfk;ReKk>!!~kpd#M0;GgUtpO{*jWAtB*Xe_ literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Denver b/libs/pytz/zoneinfo/America/Denver new file mode 100644 index 0000000000000000000000000000000000000000..5fbe26b1d93d1acb2561c390c1e097d07f1a262e GIT binary patch literal 2444 zcmdtjeN5F=9LMnkqQH%ZQ;8vb6-0*B?gOursm&?$b8b?d7UesOi|Njd(VTTGm*mXq%nh`_OY8GIv2h@FWtq%9yq zpPH0YHYBL9x|w=v#e|wxIIhF9MpXC}}?Nyq3Ob5vJb$tvNO`8x57 zNR>1kp=X`^LCrpNN#EFeTFvp#k~i%*sOGK=%6aQP6gTI7E^k@(wwNFHo=i^FA~|qD zr#Lo>l#!D<^^!~6I=EM-oo!U<-OuTaBb6$%*{ic&TBNeQtuklR47IRitz1+&rgD?- zlegu3q85jz%DluYBJbNMnLmDB6rB1=-u~%;Skmv%cMR+nOFMqlckbFQ3L8GsceU

gzP=p8ch855>q;fg!QFZ&W@w zvR~A+4$FrI+eK~tQMsmjy?EGpM%T5qsYlWe>qoslRUh4{JtbwzbJ?%G$?3{_+O2)z zvC4O#K(G7eXSKeoT0V9rMm+A%mrooV6%AF1vaw@WY{;FI8yk*_O>r0G=JGDFIWVsM zd54vMqPHC@P|dX-y?tkr3Jv-5Z%WwTuYY~@ z-y00>?i3;ze5)rU%)Dz6Vc(~8rXEg;xDrhw&L~3X?MMSE|QAVVWNFk9*BBexXi4+s5CQ?qMo>o&(q@q?+QlzF< zQ&gm?9A!o7%28OPvK*yFYRgevq`F9Xk@_M9Mk;JIB}Qs&HAP0MY&B&@>WmZ`sWeBa zky>*U8>u!&xsiHv6db9z)s!5mxz!XMsk+sa9jQA~c%<@3>56p4ZvA`Q4n) zJKPlARO$WWNw&XmczW&Odv?!9d29Ap@Ab|;XXIl3?{ZO1=&$?(=8|_nC)Zq-l=4$5 z<@xDyO~xVR^A3v7JgZW5kEDJ5s!l%#1)%V>0${(wV(2=DN=N^!3qznYOw} zX9Ww*4fE6VjfJTuJFieppE711GMw(5e(kEPJps0&A4lcJyI>Dfa&rFb~3O8TQx zdUUQT>sl=3d$LtUBw{MJ{Hf*yg659p-zk5=Y%{lVNX<)0H&rvg(N&|rn)wqS>IG*m zm^;7i*VTi+$Xy>irSIPTx!m*8MqSf>L>6}MQ1>?MmD=VFs;(?1^>wwXetf_LO4jSZ z@GcWfU#Npe+svY|e7*SPURm;GjS6jVm8I|HsfM*7S=N`N?yoMB<&TZ36*-v_Zv0e* zC&p!^|4p^>$Ejvj?is!6^cAyuazHm78a8W2cIma<$IOF6ZF*hvKC`}msaBzPWy8)^ zwXvj69*Trib9#rg1jZgW?qm8qc21`@XqBSD6Oj0G8t(~bri4l*8I#ek3zIqi^; zF(HF;+EF3HLdJy*3>g_RG-PbZ;E>TF!$Zc03=kP1GDKvI$RLqXI_)r#aUugnMv4p- z87neaWVFa|k?|q}Mn;Sb85y(F4jLJ?(+(RMH!^T!=Mvn|189x#LBmzhX zkQg9AK%(HZVL;-51OkZ!5(*?1NHCCSAmKpbfdmAJ2oe$`CQcg^Bq~lD79=iC8yF-q zNNAAQAi+VRgM}yA)!KIg#-(U77{KbUP!=@ zh#?_EVul0_iJH@f4T+o61`dgw(}oU-9TGewdPw+?_#pvAB8Y?#i6Ih1B#KTOMkJ0- y8%QLQP8&)jmPjy>Xd>Z6;)w(l|CbT<*~0p5S&Kt+N-Imti$fI^r4^;+zP|v~6Kewi literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Dominica b/libs/pytz/zoneinfo/America/Dominica new file mode 100644 index 0000000000000000000000000000000000000000..bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954 GIT binary patch literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Edmonton b/libs/pytz/zoneinfo/America/Edmonton new file mode 100644 index 0000000000000000000000000000000000000000..3fa0579891a9762b7c131ec5ece5d6d02495bfc0 GIT binary patch literal 2388 zcmdtiZ%oxy9LMnkp}>tlrxHU!BP|I6Mi>RMixs4Hr-gV79MKa` zWy~@ORD#wDQ`cy0pgG+7qjT5gvJx#Ton@mxTP*C}&ig!K>rs!|`riHS>v!St=j~eM zUXw2VansCSc(~Wi!~2XE#!j5?8XVAX4h5_3oiFKb?>4pP#Y=i`+jOz7;S=4Pc~res zc2V|4^{W1ik7d8_Bk^fRnD);9y~$e>Ej};5AWz4AE&iN%MowO;6u!Z1>F@KdQO! zU)DGE99MCkIr8SM18QEmU(Rp%Ox%+Bjl6Z)dtyP4dFNL{V#$7ozH4Z=Si1cuefO>{BDe8`zNc-My0>`0zOQz(%3Jud z&d*z|@_!qZ18x9bN#SgMMv+`6PQPCc}w zNSAs7RatDZc9nmpTvsD?MdmS8@qLo4oO?l3jz-9pzEQDi-?)5utWQ+6dF3O+9iqDS zkX+rhRy^uFscYKX)nmyA^yBqzRU5uT*A*10x+@-CAD^u1k5_7UaHMj-o1+_k_(iSl zTp^!086lqZWXq=p#zkXAjBMKO6;EgWCD%0`66>SR$qmJwVuNo|d$JBF&)8YLF?xsE zI6R^^O?cF^T|N4_FDg}YORL^In4?;%>-3hLu_`cN%IBJ(DLpT6eGjwWa=FtboO$LcGtUb1l(@`ngb1)-u79yKzd6>1EDl*6vOKFsS#2nq)JGc zkUAlSvYJXErQ)a+QY?;YA?4zz7g8{eiXkOKYK9aIsTxu?tEn4OIIF1~QaY=t9a21w z>LKMr>W35%sUT89q=rZlkt!l(w3<31g|wPVBBivNS|Y{ds3uZQj(Q>m<)|o9QjVG; zMMbKLlohEfQdq00EK*vlsV!1mtEnzhUZlQAfjKISl$fK&NRc_JjFg$9&PbuHrqW2M zt)|vUv8|@sNV$=EBLzn)j+7j!IsSi(?l7TWY=WQU%t%R3NlkL5rKO~$q&ofvy_=8y literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Eirunepe b/libs/pytz/zoneinfo/America/Eirunepe new file mode 100644 index 0000000000000000000000000000000000000000..99b7d06ea467e79107b8abf448cbff3193cb6c80 GIT binary patch literal 676 zcmbW!y-Ncz7=ZEgY(c8%B7%rtoh|rP5E04PK{1t^#KEcHA0UX!)j<$koJ4VPwUd*y z4w9|tbgreX72GU>_*Gg<%yZ}@6f{Tpg&U5`^SY}W=_&DRMeG|kD`q$6?!)X}CfA!5 zxz?J_KPz=vIgrKK8&$0JtI|NrFXcC7xjCsSCk=U5zED+nS-u_(`frhK`K~kSqi0Un zQ%O}X+{;Gv$#0zX>1HjaKKGCH&~;V~Z@uV|)096t{iw%wc2%tFL67TO6;D@Xa@|ua zURZm^7$?;B>2Mrlt};RxQ_!KXG5K@R$yt1#7nfWSpO~>bVXyVG+&`4CKVhhW6bZCd zkTOUeq)?!(gp>-jwUA;+HKZI;4=IRLL`otxk)naNDpEGk)(fsy6^|J4f^IDC9V7@S>zI5-5T69hs?u;@P!fGh*)16c^7L6(AOki{UHB+I#g I?l9v508!&K$p8QV literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Ensenada b/libs/pytz/zoneinfo/America/Ensenada new file mode 100644 index 0000000000000000000000000000000000000000..ada6bf78b2815d3d99c97d521ab9a6b35c8af8c3 GIT binary patch literal 2342 zcmdtiUrg0y9LMn=gpw%wq@u(hds2#laOA%z_Rpvz$O)7qi5Z#kXHW)-p%f81w$_^C ziw?_G^yKV9Fak&n6DH46gd6Zt(c~ zb>BpnIz#PmPhD)@EZ^sCl}lyGaycPGM!orJZ1EN~9-pMfr_}UT z)~!{`6S8#V%3`^GUZjo+&XlO>3=@5Exx@@EGqE54E-QLw%nh$z5Z$m^-+1sNSy>XU zSJiy0;xd2IH|2k*ZjSg;$0v5G_}NL55Z0m+hCern6T8*wz8;fwu33^hj~dUZU9zV6 zag%a%qoh_H(P{N@lJ4E7Gm7U*W_*dxN*kB8q1ie+W{%1pi_+`<98>GhUe!4lK2;mu ziZr);@VdIS?GJO?i-*8W6WK-d*tp#hm1F_P`op*=)90r z$s0PT^Dixt%`crY1z*>Quc^b_(_0{gJNKKSV;Nhr-n$dtfe5^u0@fPo>BD?lX_p_NwqI9&opHBOT+LLb0G4B9OxS`jWezCL}#~oa;Q?8n%m7& zr#DG+S-pAsg+vJo4hp^|IAo5!{yV=w;7Ebv1OhLM6A}otwK&)E9<;!{m3uEO@cA8I zvEM1;c{jL0C7 zQ6j@c#)%9R8L6usDl%4AJ6L42$Z(PIA_L~j88I?sWX#B*kx?VVM#hZ{92q$>bY$$v z;E~ZI!$-!C1i;ls00{vS10)DY6p%0=aX0NT85NA)!KI zg#-(U77{KbUP!=PZN!j}x!RZ^K|`X3gbj%s5;!DsNa&E*A;CkUhlJ17#t#XgtBoKM zLRT9@B#1~9kuV~0L;{IK5(y;|OC*>`G?8#3@k9dZY9oq-)YZlm3974&DiT&Cu1H{! z$ReRdVv7V9i7paeB)&+1U2TMs5WCtKBSChxQAWay#2E=R5@{sVNUZUHAM7w&^K4u5 UBwxBG&6ASkOHK8pdQ!sv0^LWd&Hw-a literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Fort_Nelson b/libs/pytz/zoneinfo/America/Fort_Nelson new file mode 100644 index 0000000000000000000000000000000000000000..5a0b7f1ca032be1adf8ba91f863e727c5ec6a026 GIT binary patch literal 2240 zcmdtiUrg0y9LMnk^7jb&%{)qk^`s_(DTW{@nyEO#KtK)_ty#9B;c99Y*4Bs?t>_;Q zI{UMVzS&KzCNgHTkqmEKt*i;lM2yHv7)mNy0ura^{abfkb=P{%p6B)3x&OR__f}S< z`~GpF+&^4Sy}NuT)VQbd;30j#EnvT@OVrNUm$!9po-5y#T{OqdpnRX%Wls3MmhQj- z)7`gEPEH)to(?OgdRz5}rcZ2d`yTzV?sePOxKn?s+-6T#m+Q~@8|*Kea`e}f40|T; z9@9UyL7wna^S{<3US4O8)=vYjxjEy{Rt`1bl_=$(jpI_W569@B5=%ZBe zy_I6ZUW$|OrzV?8+vnMc&B+>B;zp&pE^`CNOmb?Y zBu@-!ioZrudcW1w!3Sl2dyC%MRc#kE?$(8^57@NoCw0;8)%LbWcA4}YbL`^0Crn0Z zl+8@uXqKc8*sSPmlbsYP+5L%T>D7K&c4XY-^n5AH_b2FzwvXlZ`Y~Pk&TDeV)>FEw zw#lw8YS%Rny<&6IRM+M{X4hWoGIG0OWO=!6s1jS6l%v72VH+Huso`PalOo*n-}ps_La&bce4)^LHY_3( zs;}|Ic;9i}E4;pG1%*Lhajv_i?%wTganM)jzByrkzrlYopO8D7R#d%+%m|qhGACqG zo^Dpiw2*lr6GLW(OwH5H4VfG=J7jvu{E!JEGeo9{%n_L+GD~Ed$UKpWA~Qv%>gnc+ zOxDxQ7MU(GUu43_jFBlLb4DhO%o>?CGH+z!$jp(cBXdV4kIdfFO&^&*k^m$F=%s)U za=>v(0J4Cj0m%cB2qY6oDv(?t$w0D!q{GwY14#&y5hNu@PLQM^SwYf*-qmpuiv#e G%l{V~?gk?O literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Fort_Wayne b/libs/pytz/zoneinfo/America/Fort_Wayne new file mode 100644 index 0000000000000000000000000000000000000000..09511ccdcf97a5baa8e1b0eb75e040eee6b6e0c4 GIT binary patch literal 1666 zcmdVaT};kV0LStFL*AOuCUenZx^UBrvdmhxOs$2dygYfS)X7^*(Gj&G`CoXy!A)V7 zj1gwph`4Am!&tLPDN)B;GiE#Fg4v$G^F7?Tvbk}do&V?GbJNZ5`vh`JHYPfMoH6Db zE@z#&yhpm`(ReP#J$385Y}z-$J$<5IK3qA&eb}2J9~}s~PolrdCq?6QSLLwtH1(tI z&gph~rg!RRNjIEcr$zTg9C!NEQT;sF>h^bR(=P@Z+?N-Q$bt46ckp0^RE>G=tCE0x zT{q8tlQ~DeEtuxM|1w2?F#kQ+7OB1So^l$3+PD9eN{g?O>1hi@`f#((h%HnZU59jL z*nE|FwM;Mk6s;DWJSZ3UqzZp+sm!`QLuBXs<&ydku{0%KE~^|8%Ok^OAm@Py{1}!i zk}irB?+0V$3-!H%Z{Pi3)V$|q=@bSEsWXJKmn^$}xo_DFq8EfCi+vg;n z&ScNK-{G6O*dK5fq?x7iA@!2N?{$g&PIRztwO~~w!=^^t&CWy?? zYNm+H5t*db%o3Rcfep0Nn>CzSfnwSBt{aJGm1g( z*qSV~CL%=QQNiBjO)E$tRn`L$Mludk@~lMNYMzET}(z!mnX>Re68uKKL%o@{oz9~VU^ zQgA~T84>wBcB82^5xakqvBIbr%xX8j(5RAHYrn6ek6-JXXUo$1DetS+`tr=G8yk15 zJ{dOG=8es9{?Wz!wWbZy$I~=IIw7r)UY@2I(#_MfL;4{Nk&Z}9q$ko8>58;P`g)qi zNM}#e8tIKR=RN6;v`6|Q8z4I%TOfOQnoW>hJk2)9KFCJMPM&5fWG`ehOqyq@Xf>a| NqTK>92NRj5P=m+f{;YDv|wwq zt@m%>3RQdyXvm>JA0njZ|CORoX73;G9l>KjLYlrMUI($NLKZywc2WBZGAyeTAc`m0Wt zQHd`e(JOKeOTxSXy)tRH_YT}oMXpi?gztq z)BZPQUfV^R{_IYfU;T|;(7fN=96VxgS@oQ`HRV;Cv8cghOns=C{!)`UwnrEJoM~eQ@L_eggC1`P}>l*Qf4HRol&BWc9LiUDI}2 zs?uULTzyo+mnL*=aG$K5h_N+u2TjdqXKiF^uUUWKxZN<;Wj5|OXlsY+OkI7iy}!TM zXvJIlzzenVV0Mo_)L10-iOt$jnl25K<=U7LD~(?Uv?+c zW9>8T5=`ts;9x zHjC^Q*)FnQWW&ggku4*8MmCM?8rim|+c&atPq%Yq>&V`b%_F-jcsYq!&mtkZvIDK>C3+1nCIU5~L?cQ;@D8ZSiz{K^o)fI)k*v z)Aa^v4$>W@JxG6$1|c0nT7>inX%f;Uq)nc#Pe`LYU8j&%dAeR9%|g0`vkw>1`{M-4z&U$vX@B8DMxwydN_~QvO|KY<^ zYCe2#w`XIaqm#E{VrS2H4T*ZIT{=C|(7;<7`th80Z9cBu?jF$Ymv(5_nX?jpv`!-S z?w1~wDv=eNq-Rly^qRdudT0A2DkVeuIFlthJVyGq>nbrp=^FESpvFFr)_zT0wEvYh zI-vfmI%{5QT=fHu-*Q6}R-aK<{xMC=y)W*Pdhw(-$iT@vB`IQ`B)iKbxy3Jo!>V=g z<9RaVN2v}yn=Zp1=4eW7o~AYo)wHc6b@-le9Z?XaBex8ZQJF!So*yQoqhD%9dW&QR zUDvFrCzAF4g^UTjAY&S@$=K#YGOq53WZyim-l|F&fApYEC@z+Xm78_a^zAyiXrWFS zRHVM_ES(xUUZ*+x>9j8{%?ZhroO>O0#^+d>dFqqSY6_Ow2RcY@{X3cC|0sFYjWRc{ zN#?D8qw|NKmIb*tH9vBBBE7v8DeY*7UEG@daQkHE? z)#BPIvb-=-S8Pp^m6KAWq##sQCH9cj8Q)}02Zxj)I2w zvaYc~D|TPh^>zDnLwSw*tNiM>EGwXOtH6K$*UGYPZ*({;tLcuT_3wA{(}1>?#XH;U zbHuqk=HoV}7ZC94<@<|kH9ySaVtKe)9q&TEHq&%cPq(G!Xq(r1fq)1y+B~qrXsS_y_sT3&{ zsTC;}sTL_0sTV02sTe64sTnES)>Ms@ZENaA3P&nON=Ir(ibtwP%17!)764fRWC@Tp zKo$X61!Ng)%{m|pfvg0w6v$d2i-D{LvK+{IAPa)52(l!|njnjUtO~L$wq{+Bg|Rg& zgDefQHpt>2tAi{LvOdTH+5T566r5s~Da~Wv?lh;@6Q30CN{Dkiy@{@0UlW6W0s=0- ADF6Tf literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Goose_Bay b/libs/pytz/zoneinfo/America/Goose_Bay new file mode 100644 index 0000000000000000000000000000000000000000..a3f299079aebb8524bf77e7f92e0a7e6d0a7b6fb GIT binary patch literal 3210 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 Hmp}Fofim_s literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Hermosillo b/libs/pytz/zoneinfo/America/Hermosillo new file mode 100644 index 0000000000000000000000000000000000000000..26c269d9674847059472850a5287339df9213bff GIT binary patch literal 440 zcmWHE%1kq2zyNGO5fBCeejo<1MV4-RQLz3~-h*vvVF}xvFD2~YF-zF-x9!8}SA_{T zCT{t_z);X&nBF2_R4mqD?4BlI67-|NRNX_s%;;7FBNG@hGqQl;|Nql}Ffjc8KX(EH z%m4qY7cg-A|KH!hzyo443P8ktd_x#~gF_gcfj9t&eO*F8&Vhpv5}f`Y2tdvU(I78? zXplERG{`F;8sr@?4fGO-26+oaL%asEwx9t-g1iW#LEZ$>Ag_XGkas~e$jcy_M&9NE IhKV^B0F_3Lv;Y7A literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Indiana/Indianapolis b/libs/pytz/zoneinfo/America/Indiana/Indianapolis new file mode 100644 index 0000000000000000000000000000000000000000..09511ccdcf97a5baa8e1b0eb75e040eee6b6e0c4 GIT binary patch literal 1666 zcmdVaT};kV0LStFL*AOuCUenZx^UBrvdmhxOs$2dygYfS)X7^*(Gj&G`CoXy!A)V7 zj1gwph`4Am!&tLPDN)B;GiE#Fg4v$G^F7?Tvbk}do&V?GbJNZ5`vh`JHYPfMoH6Db zE@z#&yhpm`(ReP#J$385Y}z-$J$<5IK3qA&eb}2J9~}s~PolrdCq?6QSLLwtH1(tI z&gph~rg!RRNjIEcr$zTg9C!NEQT;sF>h^bR(=P@Z+?N-Q$bt46ckp0^RE>G=tCE0x zT{q8tlQ~DeEtuxM|1w2?F#kQ+7OB1So^l$3+PD9eN{g?O>1hi@`f#((h%HnZU59jL z*nE|FwM;Mk6s;DWJSZ3UqzZp+sm!`QLuBXs<&ydku{0%KE~^|8%Ok^OAm@Py{1}!i zk}irB?+0V$3-!H%Z{Pi3)V$|q=@bSEsWXJKmn^$}xo_DFq8EfCi+vg;n z&ScNK-{G6O*dK5fq?x7iA@!2N?{$g&PIRztwO~~w!=^^t&CWy?? zYNm+H5t*db%o3R-DV@{%eY?zBUH7p6`TTcudiDF_T~hY^ zO!>=&*l&2aJ@(-}THBBMeTjPSC%>tNnz6cZ&jt1M>pp#U+K@V15^B!potKU&r_Fb% zN2ODmO;=Q%boIPtzV{v07f!4<7rS@qOZ(qc-K|ynhpp>WPko{8E%U0r>I{9^GfVwg z9H*}oq?`WCbops^thpK=D_3t$G}r9^eyx4j{M_FszjU;jfiK$R`te>h*xaMd-c#zv zwv&2jX|1|7Tq?J(ddx_tM}Ge@!T4Gd#Q%PTk=+pzP&;Twy*wy^Yr|Dg$rv4+dtKf2 z#DES-{ziqo5wAldKT@Fw-jy)(wi?s3Lx*=AG!Z8%^w?wD&A9#BC9hD=-asd+HX%B)Qtlyz&~GwY+;r97wBl=}vBWm=P}>^`G6MAxVdt%r2g@Jh9@ zeuv)FnWZ*YSLjz-5><6^fqr%OST!oZ{saa&c)jya@ZWrY=fCZ~4gQBe`&a*(-~ZuP zB7Xm|g8@N){|5~++P#On&qzLH!k^#I%l68XbL_LwJ_Yv4^~zlP&IPzn@cxJO`Rx@4 z`WlcGB1=Tph%6FWC9+JXT_>_oWTnVbk+mX=b=uV;%SG0UEEriavSeh<$fA)|Bg;nC zjVv5lIkI$Q?a1PtcJ;{eop$|50gwtHB|vI`6alFMQU;_BNFk6)Af-TRfvy<5Pz}zO zgQFfuK{zUclmw{>QWT^rPFohFE>2q*j>;gVL282(2dNHH9*+7T1>&d>QX-BTAw}Y- z5>h6PIw6JPsFc%|3aJ%RETmdUxsZAx1>>j~QZkO3Aw}b;8d5fnx;bs(kjf#YLu%)= z#p9@+)0U5;eok9JjtU|rL~4i>5vd|lMx>5NA(2WVr9^7!w8ccK>9pnKsHf8wl%t|Z zNjYkY6qTc@NLe}RiWC;9EK*vewn%Z2>N;(Ck@`AqfsqP3ZHbW@BSq$@GE!!aIwOVV zs5DY)j#?wd=BT#QmK&+J(-s`5xYL##sX0<~r0Pi7k-8&=$NyL5!|X4CS@xGfV)kQ6 RGn0}Nvr|%%Qj(Ix{s3RXO|k$0 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Indiana/Marengo b/libs/pytz/zoneinfo/America/Indiana/Marengo new file mode 100644 index 0000000000000000000000000000000000000000..1abf75e7e864625b975feebdd0b232d0de624b27 GIT binary patch literal 1722 zcmdVaUucbC0LSt7*lf0p5t>?4b|E$U$0SXWtu-8mgBj)-o6~G~Ff;$=T==^ZN}Hsa z$lo;oLt03qB-tYQvpvQ}7|+ZF$$35Br*PrQrT6W9KX2#aT>ZX}FRyq>s`J+sZhqn6 z@|%b6*noM}9!m%uy7o=hZR-;_eBhb9w<8#6ivJ>;3L^CLmYTqelY3-a<+#AB?9uXd z{*XZX@EF;VmF~RhKT5wH7U#VEJV?JY|Mu?TSN*=D&G~TdsqSpN?R?yOU4N=qf#8)` z?H+fPQxnvl?Jrf2wMvJ`pa>N|WX~KW!p67C@Z?(}eAi$Z5q(}poY|)%^)``_R4y|! zCW_4N6FO_eLY38ArL&_ZsO$@+dQxY+ntX7lobq_Q@NO)TQ!ft{)8>0+PIai5o}MIU ztmzOlWBWVZy<5&sJ0)hf_tm*^jVkwcm!2Cuq4JJ4>v=6zYW|i>dO<^}$}gzmbzSwSs#Us5a6lwP%>My(!rOP5Vsr^?O(rKw?4{o zT=(i(PpIpju5)_X@80$u&D$B^x_54PVy1X~&cqD!%rws&^W^xPO!J*-e&h1kH~9Wx zg08vpLxOe46p=Y1lSF2TOcR->)l3wbDKb@LuE=DO*&@?L=8H@inX%PO8JV-yOd6Rr zGHqnu$i$JEqn$b*%$>_j9+*8cePsSf0!Ri(3P=uClLV55)ue&sfh2-tVl}BCxmZmy zNH$iJ4w4U&5Rws+5|R^=6p|H^7Lpf|7?PRQq=w{XHOV2_SxtIKen^5yhDeG?j!2S7 zmPndNo=Bodrbwztu2z#QlC9OGi{xuH2_qRJDI+-}Nh4V!X(M?fi6faKsUx{tP4Y__Ap*4#5^wY1K!@B6uk#4G8p=kPnP-!^vl zyg#Ytwufh%t4^N&hKJLo5AVf+e)Ydz7VP}+4;k1rHF)*q@8q-RGQp`C7v)rIzWXtK zdw6zckqkA|nxC$}#SNF1nBfaIxpVs8=T1(zpND=hzr6je8##W=oPY5nH@Ytj+|EyA zY|GK$!p7HRymNPOaaphY+PEqB?T$A2y>eA>X>!_Knn;_=!wL82>4f>~#4MLNILkzP z;?C@dn^^CtoAkt}$y!q=*{xH8oTl@VJ9i|=tNK#%UMLOnr@bTjdv}=vw@s#mTZ6)H z_Ph9zwZYUwFS)wPZmF+ZAob%Pn1=Gzu3>PqOT_OC6YqAoyGLe(_q_7F%=>z-O|Ea3 z`S0Is8aF?g(2}YuOvV zc4fYo?pyn8_nD8Sr>MvF9Ns1CCYtS&{m;r%r)%x{4QYA$V2Rz(l8}u%jGYwIPgb^v z*MEOcD&jT(h$-S(h~Zf zc%dndcEzDBq%RJQA)O(uA-y5Zd9^#FJ+Jo1p+TfWq(!7hq)DVpq)o5($)QoNc8av> z)n1Wik#3Q8k$#bek&cm;k)DyJk*<-pz1la@xK}$zTK8)2Nb^YdNc%|t$Oa%gfNTM> z2goKMyMSy1vJc2ccy%X`t?=qzAe-UU-9WYj*$-qxkR3s`1lbd0Q;=OjwguT2WMjO# zGsxC>b#IW(@#^j%+k@;6vO&lWAzOs(5&oYxsX(WyMyIMQIj3TFMO9g{y1JseqN?C; Daf7s4 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Indiana/Tell_City b/libs/pytz/zoneinfo/America/Indiana/Tell_City new file mode 100644 index 0000000000000000000000000000000000000000..4ce95c152a3d8974fdc67072f944eae97b5997ec GIT binary patch literal 1726 zcmdtiOGs5g7{Kwly_JlJ3Kyb_782b|Ev1D*kRn=Fd)(C2?4_xdIhIO3vRQ<-a}n5s zxEYucQ5Jz&3k$PaL_{SL1#;E0(9B5<4C0>VKM;hi-1c40`Hcsc-+#P0C5K|gKNDoW z@Gynu;T)#S%h%>sS04V9Ee%W5)k`1bi?adsX4RB@vp-0`t(+9Sn?|+Ym#YR!ymGKU zQ4OWW%a28g{!gnz<#0-Z_`Ga`{t_83zD%stBMXHXc|EO1hX=*iyYKbbvmWv7(SRIp z_bXlQ)8CuA)x_y*a)zKQbCrpfgN6*B!q&AJDGG0e{lEDw&+LT_#W77b$B? zb;{d|A~hsKr{1d-X=5>Z%jNTGYhSwb)K#nWj%b;Ym#ellFO%C764j2%DY-K&LV0)f zN$)S0%8cujnV-T%c6hJOek#Q7K!?t09Td6!7j)j49d&sf8*cJNs|7`Vm#pWdQ>@-i7dA4~)vNn{~WxtSVYH6{@g$dvXID9 zB8!PEC$gZ(l3LB8BFl;_EV8u7;v&n7EHJXf$RZ=lj4U*=)K;_D$Z}iFf+I_gEIP96 z$igE_k1Rg2{73*u1V{)-3}}PkKopD#1Be3&1c}6ILScx-YJy>i#%jW0hzAJ>i3kY^ zi3te`i3$k|i3}ytzx`wALV<_HLms;5`X_`4149?FT>s>1wwaElEvDsEwykBhhsyHO zZH)*sijpD{%CHVn5fwxPN)ZG$1$h%j>`+Aen(tgY1znoQywBsEzrRqpYk#rx$603n za5?qn^6iV8*XYf>_|?ZhMdHab20;rYdKLbyen8Rdv5dZ(GP!+pk=Z)$hwh zaG+b(JX|koTY|DK;T83z1#-u+dC}m@lA*>|BJ^`fHkI5KO$ z8?$=%+^}jnGpYAX_o&v65#2UgqeSq5Y#-_td-D@=UnD3xGGnr{vOsh$gk@wymWaG5 zm0jseqU&ysJTU)GbSF0Jp0|tYVAQ7%-J4atjY%ypPO8HtN+0PRQAgK3(|xu5sxSFT zMmHrybndB)WyeHp`nv4@9u_A?&&rb@O2xonM4p<;5reH&^7JJ~qP<;Y|3{DO zPLw&0>pq(BcwP6-n6un|ue9$qyq&eK`|^n=yE+(h$}7xmFn6c9bs;BUz60hT$7A0R z`im%Bb6QiAV@MoGAV?%gC`c?wFjf-{5)RsU_#hyTi3kV@i3te`i3$k|i3v*sK{UBFU

CW46GxWPD@y@$16ZL!ZukRmz*B|;`IUg@P*ZrNholn<$ zcAo9eWqhYvd3!*wikw#Y$Gdbv_kLBlyG^g|C|5=6oAsK|LM8l{ zWN~AeSUasrt_%9b`cbvABs)oz^ykaqlo%1bpDs&>ghgpIr769+vRQ4Ja7$O`ZBx~em$GJ7lc?#wDQjbEMQz71x$S$t*dE#= zcRWiMJL`gSS9`puE6S1e2OTlUXRfGd3$MRlgNK;Q{AlI5TWhJ2!l>x&DANFVoyh&Ar0hivmuTdCxMxaeVd(fxj4@XHHR6qy-ru zGD2jC$QY49BBQjLVIt#128xUn87eYXWU$C+k>MiaMFxzF*lLE1jM-`ijf@%@HZpEx z;K<04p(A5Q29Jy$89p+8v;pu!1RN6r5Cakf5{1=-!66Q-2?UA6YC=I`L4rY|LBc`e zK>|V|LPA1fLV`k~vYN1vxU42HBr>ZB4T%j24v7v44~Y*65Qz{85s48A5{VKC6N%Gm z0!1RVnoyBgttMC`S|nT~UL;^7VkBfFW+Z4NY9wqVZmS6#iQH;JM`E{{;F0K&@R9hD n4*>EJKt2TcUw;fnm~TXy`9>u8rzfT+CMWn)QW8@VlSljl+LDCq literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Indiana/Winamac b/libs/pytz/zoneinfo/America/Indiana/Winamac new file mode 100644 index 0000000000000000000000000000000000000000..630935c1e1a8c1a87e728fc1dcc4c930e81b30e9 GIT binary patch literal 1778 zcmdtiNo-9~7{KxKsHrhEK`fd_SlCpnq=-BT(WWV$@zqe&F*eoW&>^j%?!vsmrbVPG z#28AbBGMp&L`0~X)iIT*a;g?Y@~-ngTQ)46n|ppYZ}k@6|BGbhE*&au>T$ND5X|B z7S-lS?>*v)-esOfYrJPy3e5Ay3h%|SovN{})O)#YwbGSyyjQsq^}1}d_aK4KlBMl*zx_s+SkXn-$gddSzzRs2w$WRf;yNS61kP-q%dwh7N0qL=UAyb|a;F&q)&Qtdn4zBBDRB_h)7 zcbff;6L2>~{$ebBd$QX{tB~jVFT6%uPV ZON!kr3E^RhLlcK2gp-pKlM{!;{sIlvy2bzi literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Indianapolis b/libs/pytz/zoneinfo/America/Indianapolis new file mode 100644 index 0000000000000000000000000000000000000000..09511ccdcf97a5baa8e1b0eb75e040eee6b6e0c4 GIT binary patch literal 1666 zcmdVaT};kV0LStFL*AOuCUenZx^UBrvdmhxOs$2dygYfS)X7^*(Gj&G`CoXy!A)V7 zj1gwph`4Am!&tLPDN)B;GiE#Fg4v$G^F7?Tvbk}do&V?GbJNZ5`vh`JHYPfMoH6Db zE@z#&yhpm`(ReP#J$385Y}z-$J$<5IK3qA&eb}2J9~}s~PolrdCq?6QSLLwtH1(tI z&gph~rg!RRNjIEcr$zTg9C!NEQT;sF>h^bR(=P@Z+?N-Q$bt46ckp0^RE>G=tCE0x zT{q8tlQ~DeEtuxM|1w2?F#kQ+7OB1So^l$3+PD9eN{g?O>1hi@`f#((h%HnZU59jL z*nE|FwM;Mk6s;DWJSZ3UqzZp+sm!`QLuBXs<&ydku{0%KE~^|8%Ok^OAm@Py{1}!i zk}irB?+0V$3-!H%Z{Pi3)V$|q=@bSEsWXJKmn^$}xo_DFq8EfCi+vg;n z&ScNK-{G6O*dK5fq?x7iA@!2N?{$g&PIRztwO~~w!=^^t&CWy?? zYNm+H5t*db%o3RxtNr@M&Gc~^E)*%@{&bQG;3LJ%3`l9*YZ@K ztJpnPm&Lca<;{~?8Gg{+UGaskm|5bg3a(1kPdB>iYoANanP1&KU!Ibc$3nL1)FD|t z_=~MMxL?+GePZ|a9n||8kJ*qIJKIxp<^U;^&XJ`n-0jKQZ9eU0N#* zuf1gtecGmpo_<^Z{%UP(OIy=$kv_b#!J0EUx}h*)$&KG@@{ifpQh7#OekgUV3%`@r zu~}}@)R=63^Rjz%;)t~Mjl0K22c*5@q}wvGLmp3@vnTrdb!+6fJ()^tN8XTi#v8Qr zQpQr@h^EfAT6*SeO}|rP+kd>GJBA-|PoJA5&t$6IvqvvWS4)B0IXEuQRsQLAb)A&m z`4`-t#$nkrb((cg@?fJ=!zA$pw_I}o?J-r8P-v>3?+umvWUoF&} zX*t(T{|`PqGLdZd?boMG&t?Zzab0V>rG}YE7rD;P_5`6vdzc2YO9&pwl zj~IE#$YbVs(8!}k9yZ70MjklFBS#)O^4O6FALt)F^6-(zj|6~3fP{d=fCPa=frP;k z2NDQJBuFTMJ{BaHKpzbf4o5slKuAPLNJvabP)JlrSV&w*U`S+0Xh>{GaDhHLBs`Az zkN`O%L_*|<5ebqbN+e7qP9#tyQY6$sA1e}UppO;_H_*q61k4dJ5;77q5;PJu5;hVy z5;zh$5_+JI9SJ_rM~{Rb=;KEQfMW!ZA>bGTWDqz;0T~94aXCZ3euR)A pLdFOgBxID3VM4|U|F;7b@|(57Z&qnyS+pV=D@~M_N6VwJ&|mGSy~+Rp literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Iqaluit b/libs/pytz/zoneinfo/America/Iqaluit new file mode 100644 index 0000000000000000000000000000000000000000..c8138bdbb3cf141ad42ace037e144bee98d81481 GIT binary patch literal 2032 zcmd_qZ)jC@9LMo<*HXDUV$^EtT)9>2&h6g2+0|*wv~66jcHPoD(`EYAW|!+$`=xf- z%BF!K`9#E+K{1m4AVthS8AUMi4;ErM9Y$2FUqO2iEq=BjX>d=!_b>EGPuAnkIOlaZ zJ8VzR`;%(RJXP!c>j>H>oE$y&h&RaBb-Yh`*H|DwF2*N!8MqFOSwo)T-x3WpzoRO0|9=sfls5 zCib4J`S}*Jw)C7{d-keXmph`{-X1oOkL=Uy_nb6O40h@b?T5_9{*_viAF54zJ7jZp zpL#N#l=gydwIv>rEn{seT~R3MFY8ri%D7}cC{-z zu0_88{+r%RejAu`{W)(|N26X-OUi3nZO_I=F7|vZO0-Ux2qy(e}{tHDQRUl;`bs&Wxl^~@cwK#1tNHs_~NIghFNJU6VNKHslNL5H# zNL@%_NM%l28d96n7Kc=al!w%Z6o^!al!(-b6p2)cl!?@d6pB=el#0~qw8e5zEmAI0 zFH$g4F;X&8Gg35CHBvTGH&QrKxzm=8)b6y!Bh@=?`AGf90w61ZECI3x$RZ%CfGh*D z4#+|vE8(x3*6vQo%WA#3Hdi-oL~(=HdXUQWAU$ciCL nhO8N~XvnG|%ZC52bqm@pjM*8iO3n}0hND%(@dH literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Jamaica b/libs/pytz/zoneinfo/America/Jamaica new file mode 100644 index 0000000000000000000000000000000000000000..162306f88a80d69cfa5053103c285e2e8b221c3c GIT binary patch literal 498 zcmb`Dy-vbV7(g$T1ZXQ1e^3~8DkF&lJ4B5z(M6{&V!Cx@F(&>H1}9lu4U2;b0|RVq zK7-qR0h9X#UcUpQE+&4>cTdydrsqT#Nxz|fOjf?IOhuOW;6{$8((EhuSWOGTBrd#- zjcXoaPv58h$BW)vUZuswoi4rJn&7#w%cD!PH8|1R$+6ivuj}2@&{Uef-U~gme-Osi z{HLioUYv0@etE2&J4&t2thI}&%3J%s%=n#dq|Rj9J=sdkRuGLY4k833 z2trGvT}98K8ro{l#+urp1X2;vA*i@nL}G~ldj6i<#6>6Hm&@lRyu%CMCo_EFcz5u} zwO0P&aP`UIdE+JdxqUsS9-JT454Suw#cerVTt2O)HeIk&lM`mTd0)@mc&Q$}dT$?} zi<#N!&Dsv<%#*P(J-4rDGpsKN{Bas|i&;n6l;CJ+*K-uNTv|%;L%wyOefn z$$YfcuB2J+KW6LuqpCjMuwNo4Riitu8z06^b3?yvmaFP(eMEn&?l#{`N&92wi28YF zP_K?AmAlrf*G(KU(e_h??fFU59?us#QlCx7r?Z95#E|WL``YYSxvIO&-a?{QRXcAE zwWS)*%%0hV85mh_2Hs@bvd+2CnwG!3BVlfN8C&E;oSRz+0_RGVAnaV(22tl;R6?!J zy{m=dJO{0Z)0v=O&Mws}r$^53OJx3G8~I#!X7p|m*$CMQ*$UaqE1My^d1X6fKV(B> zM`TN6Ph?YMS7cjcUu0usXRmCH?Cq7!k=>E)y|O>jfLA&|T0nY0n(#^&NE=8WUTFmB z#4D{Jy&%mX-5~8C{h)7%2kA(X4`>PL326%H3TewLeIbo`r8A^8q&K8Fq&uWNq(7uV UuXKo6`IP_NVysUJJT1rW#RmjD0& literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Juneau b/libs/pytz/zoneinfo/America/Juneau new file mode 100644 index 0000000000000000000000000000000000000000..451f3490096338f40e601628ac70f04112ace51d GIT binary patch literal 2353 zcmciCZA{fw0LSrr5xCqd)GZ+qiH=Voj~=e^RFt6@Arct3n-Ezfc88UqFovQer6aeB zt8ETv(Q%Hgw$wA-O4q{DxwcG3u=Ow(rCYO{y4KTbB)9WD*m@Cf^M7~#pR@n&+uzq; z*Yu3f@t?U^9er*|PNcBz}FB9RnGrW$zbm4qC)*I0=T}<1! zFcjI4rlMAPLeV8|<&sxIu2+{Sw|6MyK6Fxee$t`o-yKo0U!U{FeY9KMa^i^h)^`pI z@4nsM+jfP-?VDS@GnzJu_}aB1q1-R-C@S#IOwSjIb8AAg+=(J7A}^Gb8ShQ{d8wX# zae+!6nXm6WBS;iZMLX#6*E&nF&v@A)r9u};0lDI8y{LHOW4-c=4pq7Jpnmwh zI#pG_Q&;aTR;!AG`jPe&Rg+w<{Q-ya|COl&c^6gS`-kM}l(1NRCPUUvTo82wvGVb; z)1rRIHTlHgfLPNwET8P}5l>ZK(G5KZ)zjHSdTns0YK+r*U0G1AyShgQ)5=xwT$^rk zWvZq_0lofGoO*U|t9))aLpT8}nz$P0hoiHDO$~RUH*=;hDO< za7eX}{i!<=wdxo=uQ&g(M{Vis6-(J2u+zjIMu!Z~YQ~3l zfLs_Mj~OB`Mr4r4D3M_z<3t9EjMQp|ij38228)aq87?wjWWdOXks%{vMh1}uVBnU_pkT4)|Kmvh80tp2Y3nUm;6AdIB zRuc~-AV@@zkRUNZf`UW^2@4VzBrr&1kkBBpL4t!s2MLeW#0Low5+Ni+NQ{smAyGoY zgv1F66cQ;UR7k9lU|CJHkZ@T|ypVudO~jCpAu&UOhC~es8xl7pa7g5k&>^uyf@d|+ zL&9e@@k0V=H4#KYh{O;HA`(R;j7S`jKq8SuLW#r@2__OvB%D?gPb8pL6Hz3jRufYs zs7O?iup)6q0*gcz2`v&^B)CX)k?>kge31ZKO@xsUTTP6SAR|#m!i>Zj2{aOEybVwUxvTJ!jRoiubN&=(eTmKJJj-<7(p4{=C5#T z+H_FXNJ~v?j?`>gjntSqTbVMQh2aAwm5DxP>z8?N&-<*LTYvRWe|7H8c|AM--0g1n z{qYwR&ChWDaYflT++2I?%{d`xAJyBFgRj2#n`~a)Ik;t0gKRDS#o5*)PL9P+2p&7* zl~0}v1y2t6Nlwm=HYXdZRQ=XyIhB9PX*gJH&NfyBzuLK88f(jf=PH(nuAUKWD)LKn z`KaL6c|+x!!hXT?N%7L+&2qjS<}u$TdYtcjI_84?_6t8Zn;%-5or|aIOzV+4=hBBw za=Atajc2|5ab~2sezdO+?N2b_H^SPf3YmyCO}fpK+O(CBid5I7-6XBrXP=X(w8JXu z`Cby;b&o{v+Gt|TYSmG%l1^W|rehBVr1OE5y35WLrt3@dbzH?fb4%F+`qm<^={9|! z?w*%qdW`6#dnR=>@%@ujuMVxIcbkr?_m45sr>RvXoVX}`4;@#Y-JeOnExXliC62lM z*$t|HezO^{phn-3S!eDX`+~l!`*xE!uvjOCK9Z!Ee4W&|O$J^VrU%uok>q;68eCl@ zDSL;i)beqXwjo}n2LdKz$?s~2H_zNX`K-!J9At)O)T&`ov1WLmFZJ-2%Vxy25A?_* zC(Jz!)jF%@l8kyksPA3(x!m{i9G$&*kBnZqT-`r$qvXt;t;VF5NbZ;%m3wuE@eZD) zz4dENUhHU{w|9{l+d4pxd!<6gpUzgkWea7(yZzO~nLe3R8L1x3N|nh^w5TZwapKQE zs{Gfk%GAs^)ztG{%rwstJ+1DFnI77q3%1sq8I5c8%$57h!?lZa;oK@St9pV~zISBy znuTi4;7WNU;8SyBOJ$xnRn2QHkU(Ob3VfO+MeVPuqFo+Y&~i#GtQ=_;9lxRsbiVohe{~LrYc@GfIDFvk zh{$mGt<6pc_uR>ScID}G3x_{G7!g0-=XY|(*n5h-AF}r(zmsdvx%M4bg!=^lzxd~e z?N!(|v>7P?QURm{NDYu8AXPxh;A-oD6auM)t1X4Atp!pHq#8&$ka{2mK`Mfj1gQy9 z6r?IhS&+IQg+VHVlm@8{QXHf@uC_c#eUJhn6+%jc)CegOQYEBJ=+=n?h2pZ60!oF{ z3Mm#+Eu>sXy^w;r+KM41bG0=?iiT7TDH~EZq;N>(kkTQwLyCt~4=Eo~Kcs+21(6aW zHAIT&YO9Ep5ve0mNTiZTDUn(t#YC!!loP2ZQc$F#NJ(97O_8Fy+NvUDMe2$a7O5;! zTBNo}agpjGtB#EZANLw@4pCz)I&7xlyvF3(8L>CoyPS5+f?W)#Y-?QI&{m!}nyuJ0U zPvnWeU8wnohwC#B?}?828h9haIr{$ZYVi35&d@7Y)aNhwoJ%Rcs!NUG`o`r7@#lqf z?K_<={KuoTI1(iS_FmV4+pmkDEs-j?alSLJ>Y55Eo_0cWE~?NMQ=RabQ!4z#F%hBr z)O`1l6Z!41jyk)|S#ai{j(+o?hKT!;`9pnI z{)D)D)lt19Zd9Z$-KA4~<4#({Mx8b};w-&drqjn>axx~GROY~TCu^isW%t%OIfs_0 z+?Fnpw>3;HE3Xyzti7u8Qwzntd0(jIp^2hk@z1(o$|qLLeWnY~kL&xc47f!nuc`Y# zaNGwDTvQLf+T<2@oKh=y@01T!_NkKQMp>HOrOHZ6WZCR-U7oqYEuYx0E237q6(j9> z)pUyc@N4_j>TimrXGfb_^Io#7T<=j;{lW5)qHMKx+mu|F7^}S17o>M?R@LOcC2M|) z*0o9J-P%uPbe-?GTmSlmer$5TyMFhmetfLmZD>BMHw>(CrRQDMxVKFGQOxQ}&bWslqG`$2UT^vkZvbE-SOTXv5fQ$4@c%V!4msAn%1$sN5d>bcWN zvbUi^?K~o6V1W4q1zULi*PoDi|JldyAMO;w?>{pf5bXEAJt)HLd!+r2@%ukL8?caf z5x?5w6(yzSS!bR{%~RzSW#)I8`OO8`Z}9$ujrq+r1o;M$ts#3uHizsE*&eb#tJxs3 zLu89+_s9#ISSWO3z79c%9nt*fxX#>&+jz%Dz;AjQX3yx+W-QZ}4 z)${{th}CoiX$jI3q$x;OkhUOwK^lW}hNCq|Z#bHRbcdroNPjpQWHlY)Xc5vQq)AAZ zkTxNGLK=m13TYM6E2LRSw~%&OO}~(aSxv`~mRU{DkftGBL)wP)4QU+GIiz(+?~vvp z-9y@EHT^>xXf+)~T4*&rM4E_n5osgRN2HNRCy`bny+oRcbQ5VO(odwJR?|_WrB>5Z zq^VZZRiv#*Uy;Tlokd!U^cHC@(p{vzNPm$ATTO?N7F$h^ktSPBmytFjeMTCMbQ)-DV@{%eY?zBUH7p6`TTcudiDF_T~hY^ zO!>=&*l&2aJ@(-}THBBMeTjPSC%>tNnz6cZ&jt1M>pp#U+K@V15^B!potKU&r_Fb% zN2ODmO;=Q%boIPtzV{v07f!4<7rS@qOZ(qc-K|ynhpp>WPko{8E%U0r>I{9^GfVwg z9H*}oq?`WCbops^thpK=D_3t$G}r9^eyx4j{M_FszjU;jfiK$R`te>h*xaMd-c#zv zwv&2jX|1|7Tq?J(ddx_tM}Ge@!T4Gd#Q%PTk=+pzP&;Twy*wy^Yr|Dg$rv4+dtKf2 z#DES-{ziqo5wAldKT@Fw-jy)(wi?s3Lx*=AG!Z8%^w?wD&A9#BC9hD=-asd+HX%B)Qtlyz&~GwY+;r97wBl=}vBWm=P}>^`G6MAxVdt%r2g@Jh9@ zeuv)FnWZ*YSLjz-5><6^fqr%OST!oZ{saa&c)jya@ZWrY=fCZ~4gQBe`&a*(-~ZuP zB7Xm|g8@N){|5~++P#On&qzLH!k^#I%l68XbL_LwJ_Yv4^~zlP&IPzn@cxJO`Rx@4 z`WlcGB1=Tph%6FWC9+JXT_>_oWTnVbk+mX=b=uV;%SG0UEEriavSeh<$fA)|Bg;nC zjVv5lIkI$Q?a1PtcJ;{eop$|50gwtHB|vI`6alFMQU;_BNFk6)Af-TRfvy<5Pz}zO zgQFfuK{zUclmw{>QWT^rPFohFE>2q*j>;gVL282(2dNHH9*+7T1>&d>QX-BTAw}Y- z5>h6PIw6JPsFc%|3aJ%RETmdUxsZAx1>>j~QZkO3Aw}b;8d5fnx;bs(kjf#YLu%)= z#p9@+)0U5;eok9JjtU|rL~4i>5vd|lMx>5NA(2WVr9^7!w8ccK>9pnKsHf8wl%t|Z zNjYkY6qTc@NLe}RiWC;9EK*vewn%Z2>N;(Ck@`AqfsqP3ZHbW@BSq$@GE!!aIwOVV zs5DY)j#?wd=BT#QmK&+J(-s`5xYL##sX0<~r0Pi7k-8&=$NyL5!|X4CS@xGfV)kQ6 RGn0}Nvr|%%Qj(Ix{s3RXO|k$0 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Kralendijk b/libs/pytz/zoneinfo/America/Kralendijk new file mode 100644 index 0000000000000000000000000000000000000000..d3b318d2d67190354d7d47fc691218577042457f GIT binary patch literal 198 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 Pzk_I6`JW3IFlJl;pAK+| literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Manaus b/libs/pytz/zoneinfo/America/Manaus new file mode 100644 index 0000000000000000000000000000000000000000..b10241e68dd415354ef12cc18fb1e61df3558104 GIT binary patch literal 616 zcmb8ry-Pw-7=ZDseIWL=v@}#!tHL22A|g&7V+Fy5pg+I}np$d;THPb4p|PCKsNry+ zLE56ILDXE@gbMl)El%%q*FfsM9G=4+?&baYo7?GW@7Hw68x9kb!@d6~ms!paZM@{a z*G%DcQD4>$Re7eU%Z-Sxj6B;)VM|rpQ@VE2P>U#;5l#`ML82=yp$}b%|Q}zxjyHob37*HI7iUq|ZsF+Y( zC^i%yiV?+$Vny+ym{HtW~TrDkL_V#0Fx~L{~tfV z!1@3G)eDTgKE5FgZf-!rF&KzlLO=$AK?n&J{s)50c9A0>8e}oZ0FdP%nrsJf0bOLm F1pu=NJQV-{ literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Matamoros b/libs/pytz/zoneinfo/America/Matamoros new file mode 100644 index 0000000000000000000000000000000000000000..5c59984def290712e183eea0655eaf8dc7a133b2 GIT binary patch literal 1402 zcmc)JPe{{Y9LMozB`A^wfe<7L6?kYZH`C0tELTHwnmYfaW>)Id{_H7rI5T@7(NlQ{ zOeiEoyM$PmVMG#Emnb9%kq$yh5`pvtMMC1Q=l#2O>JWX$kJlLN^m~5-HLcAa@yD$< zzwmHX=HdH>@#y=8Z57|d_O_?m9SjRkdz?)7Rf|1kUt~sVw#f9nmV0B9MAp{NI%{c7 z?ECdfXa9&%`=c{DXL42LKDnt63@)gHccjh>JyVDJZpgzWqbk2KB)z-))Ddr~Ji4h_ z`F5mB-^z7S5R)hi=9@&};!k<(?rl{xS1kQK5mg*blO=Vv>iE!RS(=lp$~wQx@}v|M zsF;(1Rfnp~n39#Bl0|j$J6-)!h!bm3T{HGk)GiF`y1p0ULZ@e!~jnwG||BO04 z5Rqqgjj6N2TG^P;ubOo@Z6t3baU^pjbtHFNlRT2Wtw|rr-_}e3nE^5djyWKc;Ftw64UTyr z6XBQ%G8K-w*qX^8v#~YPLFQv?CWOognG!N5WKzhikZIw6o>#nCTc%l?)1U70xYC?{ Lx7+1*rN#dSVcugw literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Mazatlan b/libs/pytz/zoneinfo/America/Mazatlan new file mode 100644 index 0000000000000000000000000000000000000000..43ee12d84a7c7e47aaa92406d01a539ccf93079d GIT binary patch literal 1550 zcmdUuNk~>v97q3_4T#_^8mK6uf+)l(hX%78(kws6PcyCjEc?u|)HKlsy-)~+a7pLC(?eR=!Pu7NIZj;A}m*VE%4>FF767<~Al!qeY;eNd!ahZY}FVU<(#q9m^h z&-|t%=C4+fVJ~#lxP@x*jIXlzoxfW0^SLbjGSMvSdMeQ!erEa2R*7l)XjZh;%gVCH zCiYN^j!Ww>@kIx8Lhy03Dxp9p22`1(d9ga_TeC{`ovV}kE7h7eWAxgdY?bn8j<`-m zsnn~!l2$WKr8mBnjKT<$S$a>hVy7B+$`#3;{oUjQHp)7AX>uoD(zye-&H67#bl#n_ zCcm##Z@7F*ZR||dn+~5*1t&tZr$np5I+tut-mJE43YMY;32JN11o2MvnBtkArFbaL zY#Z*AlHPe{`>Sr*ac!(Az57h>Y<_QcUF_6l6%R~#!%1C{_fGBh*6PZo_f=J5zTPvv zO;rciNcE4SswN;$YF?D7+E3B4_eO@=_hgprKflu)XcwtFm}csay%wKQ&Kd3F`wxy~ zosJfgC7JuG-)X4V~ms?y}Zi%;Vx_w;TA(Apg zg~$pK79uS~T!_37fgut*v_*!Wabp}>@eSp3HxLAfcklB zK(CA@l)Dnwt0(rENc&KJ%gCf@kB;O!l3z^6=hOMl4RPK1qG+}*AG2L%uZb7DZM?mPOV@7DiS^mPXb_7Wc~P$nsuUA1MH-z$+ynHF%{6qza@A zqzeT(;+0yEV!TofQVvoNQV>!RQWE-_xRIhH{(!2GvXHuv!jQ_mQW{d5SBgWb lL&`(yLkdJHL`pY=QzW@=X?P~x4 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Menominee b/libs/pytz/zoneinfo/America/Menominee new file mode 100644 index 0000000000000000000000000000000000000000..314613866de53e1457f6cbf2fb617be7e4955edf GIT binary patch literal 2274 zcmd_qT};(=9LMn=X7P|bWKxI(r6ofSN5IgdNm0TWs{+K2BHZC~97JbK>;S7m=kg6?_cw0ylItWQk4A}8u%&2Y=;PRqEcQm!u$bo|>Ki zjm(KoQ@$y`7~jRPntS~#Gw;i@>b}$OnE40Cbm50DnEN|N^aHPoDQY^VA8g+#4^_Ob z7c?|U@yvEzQoKY;F2AozbJm;E;k~LXzQUCCZ&Br=dFJ8QUe*gwEfW9MR=wy*u2j_4 z=*7Dy%98nodTGlQS(cWe1ItfIU@S^k77R$`&*`cv{iLb-LaFNT5wo)Qys8=5V`|$! zSC0&CHme#As7JeN&13#gb=~$>S)J3X>w`7Y5Z|HKloraG(X~35nIXY%%XMRPlr;8v z^ty`|dE5~vp$rqo;rBWG*!kc>HN$*UGSTFrs++yA$dqOS8O-U;ZYs(^-5^u zxZae~A)5vd>()Ql%I5Aaz2*CI+1l2kpB?Z>+o~1%xjhLoF3SFH81J}|@Bj7}iS(UO zDiS$*C~ABp^7eieF@s+`U7e~v3-`=XN7$h2ULmu=Azsi0{>!05qotT z%j%IiA(KL8g-i>X7cw!Yof$GUWNyghkl7*ASt8R!=7~%c znJLFqk-2hA7MU%_bdmXTOc?BnZh6k|L+g5l51oHcK37a@ss`BnrtCk}4!uNV1S@ zA?ZT$g(M8g7?Lul%^8w3r_CBi+MG6T9En3R$B{ZDcO1z>vd57=B!5T(kqja!L~@8E z(P^`Yq|s^fh$PZ!Gl`@U$t6cJk!*6L6Uiq>LXnJeq!h_1M^c?Ot4La%Hm^uxoi?*b pYLVO`$wjh@q!-CA{tpryXA3>smfGW=<<0lzdi;5L-aKz^++RkMdw>7{ literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Merida b/libs/pytz/zoneinfo/America/Merida new file mode 100644 index 0000000000000000000000000000000000000000..b46298e1f202ee4ec22ba3cf9f8079c83ebd6c7d GIT binary patch literal 1442 zcmd6mOGs2<97q3}ky&6AO7sAUKm;4#W>%(+I+!#0n$oO%l#WeWerk!8j}H_yp{!$fBdN_sEpKwbt9%IW4?Af z`^~n94|;oan<O;6 z&Fsl>%igjGQx%mg)oFpov$#;#tbAi?J}2sZ-^Y#jak$?9;)4=$hY+N_9mKQBQ(-^dLK<{q%{*BhtHE%qX}WLd|a z>E>tup8GGf@L59*K1C0RA`nd&sz7vMC78~t~i?t=$VncocWl?nK literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Metlakatla b/libs/pytz/zoneinfo/America/Metlakatla new file mode 100644 index 0000000000000000000000000000000000000000..26356078f8d158e6cea100856baf7620a3296b84 GIT binary patch literal 1409 zcmb``Pe>F|0LSrJU0qk#no9G}K^_H~X3J>!?-WvCVvC0BrdF1vE%wj9h*p|{VIIQK zL)amE5Jnx$p_3i5C_05=9-;`Og6Lw>p+gZlz3-H#LJ)exo6oR&{r-ZrO{a3Tzm7-! z!sV2!%XeCzdc7D*l?&Uywk*`A>nqbG!oHiOYh#(hb=9Xwbj69t)*Yt1K0`(wj5VW+ zz0zYWnVxHVrB|Oby_1hc>=(<7dp9M0Z)WxQCnIvh!zq2^z57DHF`{o8GR5ZZc701z zn@Fgs(i2OAVrx->o|Ks{lDAcwDc)p}8j)v)@;=DW$Nffink}lIXBjms??vr|&p5pJ zOdJ_lHjd6rh`NS(IZMj6T9ZDli@3}A>OhYOTx18^QaZhD3#&a4y(zt zQ#MV8tka+3<(cd4#@YESajvt*8D18x`6)(Q^So$J{9$yIKM);ulGRx_ zCp#CvTV2VP?3#INb$=U`7sel17hiYCOTD+Op6QV6t?Rcgj}=N+M5N2T<`WgYjz`m8 zrE8jP5BF(q+rDYKVw|_ndF!^_-=Zb(uPfz1AgEPTs(VP?3U&7dwc;}MLvfk{Ga7c7Wcu0ImfJlT$h)9e`kVuqBm`I#RppGh1BveNg zD-tXcEfOvgFA^{kF%mKoGZHisH4-)wHxjs`iW~{uQN@k~@2H|j!bjpq27rtJ83Hl} zWDv+GkYOO>KnCKdMuH5*QH=!|jH4P2G8|+)$bgU$AwxpOgntI*j)+?GiT14HSzXk? go&U6})nPftGPxoqmmS#c&-EMGft(zFj^Bv=1#$;!g8%>k literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Mexico_City b/libs/pytz/zoneinfo/America/Mexico_City new file mode 100644 index 0000000000000000000000000000000000000000..1434ab08804dac08e4f595967d8c325691f08aef GIT binary patch literal 1604 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 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Monterrey b/libs/pytz/zoneinfo/America/Monterrey new file mode 100644 index 0000000000000000000000000000000000000000..7dc50577749baded06400fbe5d2e8dbf2579253e GIT binary patch literal 1402 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 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Montserrat b/libs/pytz/zoneinfo/America/Montserrat new file mode 100644 index 0000000000000000000000000000000000000000..bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954 GIT binary patch literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Nassau b/libs/pytz/zoneinfo/America/Nassau new file mode 100644 index 0000000000000000000000000000000000000000..5091eb5d8d3aedf24ddf9ee26a1aefd837e3b4bb GIT binary patch literal 2270 zcmdVae@xVM9LMnkN$G%)iXuf)tOx`SuoFZ|R2ZFlVtP10(~kz;PAJ}~oktng!kGNy z8U?l*TWMu&jm#`vjfpw)pEAd4_G9?5E!JAp$5=&>cYU7UfBUQd>V5m{@!kFZdIlOp z+e=*kxM}tu9`}NMd?)vmF7N)WHX^?--KhTXG|I%?d-R{F6*8IFpeHBl&D0M|^k0|C z&9(D$bo6AViM$;-j_h>UsqAEdMlk3)MHNEn(%qTje66X%d^)rtsx9hOD z&9f>=@0aASUezh5gL1>EPM!MZGiK(C+jZK3W^-fv{raX*jk&oYPpA8G%`JV9RvCf z?^$!_%IEZ5>93odyj?mc`myA?>vZnOYm)b4sa`PHCHcbvRnQ-j!sCmSr(=~A^<=2x zV9=E8oKy>IeCD2Y-zsm;0<)-OP?gR|F=eyA)@7r=n8i~c>GE?I&602Wb;ZEXa_@&v z>H7|TE=yn9rYqatm1Uj#)biTrWku6gRpkjwb=3-0J$}^G6l~Eo!`;T0vP}Dqx0;n> z^YyBi56J2-E0uq5i>!G+Th(sz%i7)q^+1J3);%_=)@PzuAXd(|{VkLt!F!)D`1x8Brw!aO+Gsy8?FnJxWmwDP|vTf17+wt`-HDCk#BDG_O| z@u=ppMhWJmsoL}O9L_Y43=HeGx~P$ZC-kFUZFaWx z>0Rk(%}jL z;^Y2Uyf0i#2Phl~#yATmN^h{za`K_a6>hUsX>i3}7ODKb=K ztjJ)I(IUe|#)}LX88I?sWX#B*kx?VVM#k-E2ab#!89Fj{Wbnx7k>MlbM*@IE00{vS z10)DY6dY|BkT^KnKp>GoLV?5r2?i1kBpgUQkboc&K|+GW1PKZf6(lT3TpVp+kjNmR zL1Kdh2Z;_69wa_UfRG3wAwpt=1PO_fqYV=hCr2A7BvOtxR7k9lU?I^$!iB^O2^bPF zBxFd;kf0$^bF^VY;^t@rheXcNh7O4x5=bf6;u literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/New_York b/libs/pytz/zoneinfo/America/New_York new file mode 100644 index 0000000000000000000000000000000000000000..2f75480e069b60b6c58a9137c7eebd4796f74226 GIT binary patch literal 3536 zcmeI!Sx}W_9LMpa;)WucQm$lLAu8a83sO>PgnGmjT+r~zKnAt=mx@@5l_ud#S(Afp zgQ+NJDQ=jk;TkzM<%0WykET>6`Y0}>c}~ywz3nD0y6a`$^Et!dc=!AM;}TLQ^>F>; zscV13%X8Jfd~fl#{m5MvC`-5fp}tz+l4YO&q?RXNloj)S*LjoI$;$A2wQA%6lOK?+ z3VMEH3Opnbtdl%(plWh2bG+#$MfQ!leVGemFr@17u536ZLKXyRx;OQN?XeNpZyywcY2o*Ygn>_($mdA&IiTdbB#=7bOQy_ESH;Z{$eFTXIC* z*JU#8--7(j?+@S9SL)p`SMD6ue^iv2 ztH-zK%F-fpZD*OfUU)>z(js+Z(Pp_hcZsS>%aL0XW~tk;8FFX9ICVEHL8?2=)PMR% z%Do0-^}Xsb=KgQ}^yv}bEu!YeeWlHXO4au8RcW{TpbFgZvpl+N zgKD4dGLOCUiRuu4(R7?#s2>mCXPy}Rv3@dOl?m!RO$T}QO0aLd4lZ9Qov-xKT}rZ~ zYgwEM$xW5eO}$lE<`C)jNlVo|CB^i3rcvex`4m)4FfP zb<^+u4joZ?*z`Y>t0N1q$y3|k)=w`wBm=&fsH4(0$}{uls%K*t%X3LDtASzZGHBp) zYEV^yi4K{dqstbW7{6z9%%-VkaAik521wxg=IP|-eY7@k$yc~n>W&y=xG6a%=FkE*j6qh*H5C|M!1 zsuR?kx$ntaCnMGD%oLfkHBem`HU8;7i8vfMrso_7U>3{Iw{k_+_E!XApdVkne z%g5_2Uhit)d~fW0HXZ7Ya}643-;wqmZQtQ>cFSC@TFysY4K~ngpTs)mBV-GaJw!GU z*+pa4 zZ;{PKb{E-RN4vks20PjvMz$E)V`P(&T}HMU*=J;8OBau!btwef>G!yA2 z(oRR)Po$xawxdW(k)9$=MY@W#73nL|SfsN^Ymwd}%|*J4v=`|w(qKp1VWh=KkC7%L zT}IlB^ciV1(rKjCNUxD*Bi%;Y?P&XrG~Cg49BH|u?K#qPr0YoAk-j61M>>zR9_c;O ze5CtG`yFlnksH9#-T}xh;Armwa!WYcdjh#B9PM3!+!n}vf!r9#oq^mM$i0Ew9LU{)+#bmNf!rXD_6|XA5l4HE zAUBDly-SeW1i4R;8wI&jkXr@0SMdLv<=@{dzV?&}wR literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Nipigon b/libs/pytz/zoneinfo/America/Nipigon new file mode 100644 index 0000000000000000000000000000000000000000..f6a856e693420d6d989c45acff2c48b60db69186 GIT binary patch literal 2122 zcmdtie@xVM9LMo5L`4TAloTnELB$T?_+h7LRG6^q)DzOfluSPwcta@Oq1~Vib)lsH zxJJWT?OJKg+8mkLv>KCg=GMxT)%rF1)E0kK*vIG(7aP~-`T4iM`m3$)?elqjcmLc! z_j*Q~+csBu|9Gyn-*9;P?csgqPJ1Oz49F|*|EkVj4mW=KtdR>vf64_lrPJq($TiuA zl+QaTKC?$<=-ra}&1*XAR7|ct*{5f``HabaakI|Z+iI@sxnE!37BVx}mgwBD-`ub; zQ{U*%FnNW3byNC;nU$KMX8qxl**{LG{PVxb%_HYj!O_pxOP|ws5s+lYtsI)*QMl_1$ypqzm$$fRav4<0>|d7^4?`q zF_5P!V=+_J^_Q9#3Y$Auey@VXbIts!VYT4uEK@!ETU|Z=yIDB(p{ zu@19zqDU`$d9N)0s#Zm|x66w63RT0ph%^qSsRwGxW#tp&YE^!YM4Qg4=+vaF4!)&U z|2)I2DLA9ooW5k%P9N6IhepjqWBc^FzT@WM;SRmN<$&3cSfN$qUD?>*t~Qko$|JFe zYRT%B)=;@>ooJR=agK_8RwHd!O{%sd1+r!QOVvKO$aI{$q#upvo5zMmb!XGGk^RT@ z<3Vk@IuGcrxgVKr4Ly3>^bZoR9#rwM5$VqBR^7t~rRQ?9dNQ$Fp8C2*ZSRfA(-0^pPWibOD0dg?WMc-Om}B5k3_xtI(t^x^PoMA zQTvznycBmu|HTxN_UXE~s}`9AG7DrH$UKmVIPFaMA*O=N1(}S~&IXwdG9P3@$c&IF zA#*||h0F?>7BVknV#v&psUdSiCWp+EUypf3`Ge@S5%pI9LGJB_;J~DqK0Z0ar z6d*Z3l7M6ZNduAxBoRm^kW?VKaN1-b*>Ku)Ao+0GgdiC~Qi9|JNeYq`BrQl@ki;OF zK~jU{#%Ys-WXEaKgXG6)6NF?4NfDAGBuPk?kTfBALK1~!3P}}`Dl?TA^CIK1R@!9+7u!=M3RVP5lJJG gMKgp!e)-z};S{j)bs0vh+Mk*@@eShD0|-JD?=?ku03 zjJfW3&I6BP%iH zE0L7)rj;}`sglEAvXaNYR4G^5tkjYBRNC1WWcuJim2tF1F6@0yiC1iy*(60)N0!Vk z*(MfMMa!I|29cZfi(Kq{Smeb|xAHFCEAl6Qw(k2dT-<;DbE`llst1NoSP$+FRZI5o zw_L4PRN?lwWl_b)>Y;{xvUuTJYH9H%x%9^iq9lF5Dmi;fltyl}N{DtZDo@Tu>G|dnu{yzTt^OcI zR8L*7YK9`jW81WiFO*>>mq*9F~O|tRo z9@UgoDw{s(RL#y**?ice)?JE}Pw(vz&-hZT^cb0gz)n|3Y zOp4BmBUYz>rs~Q&BfG|bQ{Ayjc8{D_8?Nn<8wcN2o6dF0&AqRwp5q?byQWWV8OW0X zfztwlf^YVOggS5G<8T~naX9?`M%xkO_jl9St1wK$kB@gR*5VVStqhkWTnVbk+mB2Vv*Gv^>UH*A`3=V zj4T;hGqPx8)yT4ubt4N$R*ozkSv#_LWcA4Mk@XvO0gSo=NC}V{AVol`fRq8L15ya2 z5=beKS|G(hs)3ZlsOy0g#HcHRlmw{>QWT^rNLi4&Aca9HgOmoT4N@GWI!Jkt`XB`| z>IxwxLTZE*38@lNCZtYCp^!=;r9x_j6bq>qQZA#e7g8{zt{74>qplfJG^A=s*^s&+ zg+nTbln$vKQaq%3NcoJqen!N6c`+Gv&R`0dJCVwlie`!KZg6Ca^JXxlk|Mzo>rcnk(D9R M$}=-FGBZT@pJb_W#sB~S literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Noronha b/libs/pytz/zoneinfo/America/Noronha new file mode 100644 index 0000000000000000000000000000000000000000..95ff8a2573f4dbb03892a576c198573895a01985 GIT binary patch literal 728 zcmb8sJugF10EhA0BBX?~8q}dF76~=9jm8&1Lehw^2~A8!Hp~oXVqiF<_yXEaBoewc z!P4l9mxUTMp_(Gcd2V8|)V(?R-Q4BgJpcH@Y9i$Pxti=74%d)9Ja_CJuPi6K#DCT!%DIOGW`xX7(G9#*|bMm88sM@Sw z)-Fy&-FL3*N6+fBT$5irYpN%+CH)&2)vE`!96M8e+l#WlJ*@)sULCk!62b7J4qm53 zxSG_F2%|v6{_yh;B!+lOg7Khv zmWxp)L7~-}33GEeHqy-2R_4Oyv^Gjx|19dR++u;;&ig#=QLSfv?|%37yZ8C$?Ok8L zF-83C+~yx1uFE{UM=JBxb0|ZC>K)~ z%H*|=GQ{*yy7a#lDMWXKbahONfTyp?8C$|tVD$n$vv^9)%9_;fq%q}wAyo&-6DsKN zp!E1&SJ!>rr-LuPCqho_(Q}UXiR<6)mGb31;)c%WH`X%RF1m5%jn6|rL{b=(!NSUU2Cjz8z9gr4Vh;?WYdthG)j z?OUOe>)K?>w)tv#-bT5iWJ0CJ+%H$Ae4*}e$H}yaKSbL1!7_dFqR9C07kTHG!(!Ed zM`sQn5O?kUS>L^Xx5#SvRNvFKU)@_YptEasshnjm>fD?SD);x(GB2S;)U3 zTxYC_soZC<8RJHAQP#Y8)GX9`rk+vU_Bj#rz%`^I^^D_=Zu*6(|=X{mVX$R%Gx zX^3$4p7L!;{Z(vjIOy9J`kB~X)ZyDcJ+7M4`kkh+vua0JyR&0tP&H3%a(4FYQ_ox| za9Ua$)vlpk9Py4`?ylz6aZH z>GvNWaLwaggsm?0iozW8tT9iSd5XOv+x*Tpzd4uv2Jb&uo8MejVDJF4I%Ijs`j7=8 zD@2xPHETo`iL4S?CbCYn3+08Ca%QQ(S~(VrtQJ`=vR-7t$cn9I$;g_mX3@y1k!2(6 z=2$qga*m}VYv))zvU-l?BkSiV08#;t5+F4|ihxwXYRZ7r0VxDh38WNAEs$a$)j-OD z)PtiSNJThGg4D!nih@+dYRZDtg`+S?WsuS!wLyx5R0k;!QXiy1NQICRAvHpZgjC6D z%7oO3qfkhtI7)@oilbOawK&R!)C(yXQZb}tNX@LKXh_wprff*vtfp{C~W3& literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/North_Dakota/Center b/libs/pytz/zoneinfo/America/North_Dakota/Center new file mode 100644 index 0000000000000000000000000000000000000000..1fa0703778034551487b7b55d80d41ad220f2102 GIT binary patch literal 2380 zcmd_qUrd#C9LMqBLG%EDJ((235lRa}0Rh8+q%0r?c?9*yA0Y~Z5X<=a2NX5>l?>*E z`D`x2{t!5FwK8OGhI1p#YOQoGY|hq3YRego^{l*bf#>P>{@r%vMOS^#p7VOnx&OQa z>ziK46#qJ}{)dO_)(`JKrN4TQwN3}*9O&s zb*{B=(^>IA))ni)l21iq^cgE@ku8#fKikO>TSfBtX?xK`Kr9}4Q>9$kAX9sHsI=n+ za!GrYN8&({e+u^(Cmlx{ER} z?NQ}f!?OI>cu^VugH!pH5LLkw&ia9?qI$f?spblzG zhSUM|d}Fn&kLpwnMR~Gea-(WYN|24`N>r2ABb$z?kuP=^i;!|ks;M~bEDkVdrg?hDTwhZy;&x}w5ueb4;HS<6FxNd)#5U%@ipC{CH-?K%Sd5<*T zmg}B8=9$a8h*{|m2!DZo*6OE1KV<=tuYc$3-<-#ML*O>n=IW~rjXZ{|4p|T>M|g8Knj6W0x1Pj3#1rGHIQ;3 z_24K7QW1`lAT=@Sq99c<>arkp;V2AJ8Kg8wZII$1)j`U`Q6Hp092G)J#8D%pNE}r% z>N0WE2`LmurI1n~wL*%8R0}B=QZJ-nNX3wnAvH7VqH$EssLRGtH=`~bN9B;xA+LMalH0m;P)X}I5$x%t9lpM80ipfz;q?{b}L<)*j6e%fE zQ>3U!RgJo=NL`J(ut;T%y0l1bk>YYx7b!1CeUSolR2V5SM~#spb5z-=%Z$|7s0)o$ v+Nev7)EX%^Qf;K%NWGDQ>3JbO85tC(BY#_DMO_Wi(IYdh`DcF8G5y!9*q(Fx_NMvrj^|J41?wES zaPz0)&h#ttuEKZ3qVTgaF2NFUQ(sx}zHK6Y^o*5oEg%*Ty>2C5P&%pWMJxGaiC)rD zXQdokrc>)$W!m<+dTIVfxvXSdr^h`km#2N9@9`$cjHo|E#`mEzbKnQtd}xeMzsnxLW!5H0#x;QdCi8g)Z)xt=6o_)+LSC)!OJ-9awi!1tvYZ zG<`soej6joV?MXbKNMp9)G2#I?^RJT+G$s|ejpwl+GSVO921XqRoajH-`Cac&FYDy zUj1ZUg{lc_)3y28s&;IXu8WIRb>|9oz1O4ak0L?ITpLgsnO^d}dC$88H zrC~xHJZo=F|5a>jc*EWv{+ZZO)Nb#X8q-Y~y{c*SyxtknrgjeX>*n!IYFF1Gz57z3 z+SAggpBqS2EmdWDZ|5u((7ko|G~#`y2pfYoVU9@!H#pp5;NWRQ1@Fp z&gnkS9NtC5Dt|!ubIr5XJZ0u74u~A{JIDOyJnlCH{=wQDb5+5ieaPyNw)mMHY;#*wrin z!BG&TA{-?_YT{~&f>g!Tlm)2^M`4i4Af-WSgA@m;4pJVZK1hL(3LzyzYJ?OCsgkQH z6H+IRLLrsnC>2sGj$$Fz;wTqVFQi~d#gLLAHFGsZ0 zqMM{d) z6e%iFRaaA1q^_=}ut;THO=*$ZBE{vXE>d2O`XUA9s4!Aujv6CH=BTo(DKk=MS5s)D v(ype|NUf1#Bh^OAjno?{IR3vX?lWOuZUUd^Uz(hjoRa8IO-)WsPVxN((J^&k literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Ojinaga b/libs/pytz/zoneinfo/America/Ojinaga new file mode 100644 index 0000000000000000000000000000000000000000..37d78301bd100b7c34b183a7e355021f55cb366e GIT binary patch literal 1508 zcmd7RO-NKx9ES0uS&Eomq99NrC=fy?XUxycGOfvUvUHjzGo4BuCQGN3!OD*VNz%$w zhyjI?hgq^V^lEJ=@nZ_KL*nh_ZaDp z7cwJqlaU$!U1YwGF|rn(i|lW!)Yj3bB4;RCZF|rsa=SjM?Y-?{N5d19*U>9?mJF%< z>Q-5>`KsDgP$hTAH7V!DJn5XjViZPfkcHEY#@^ZGvgrDNDt=ZWT`iqz-)OEZsR*e3 zJ?rIxoFY{k3YTRG$;y3jR=R)0DNouX>3N%Ec-OrV-icV_@X~}RzcptZnYk@0I;M=H zv3lY<2S8 z7g^VH$T&3_D^7>9jQYVj(cpcX^FwzOSTtz+4;t+Gb7UrUf-;XKRq6A|`rAJgnl5W+P3l!!3R zJ2ym>Y2F_Si{U%W`1O0S%Pm}GZjsMhpuhF|`?>$37*ikBAmt$SAO#^6S?ZGbA8O(# z3aN^tETk@^Fr+f1G)r9@QXEHhNO>IfAq8?&h?K}tBT^(%B~m6*CsHUXGs}>PHs9u>xcX9BV)p z!LbTt8OS=2g&-?ImSU;bf-J^TuLfC;rCtxRAY?_zk~r3cEQ(`Q$g()rg)EF?WysPj z_1cidS?bjx%d^z$Ll%gv5LqI!Mr4u5D)IkcX0)DdhMsGZE7j((r6jrRcAMRn68#fq Cu8G0` literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Panama b/libs/pytz/zoneinfo/America/Panama new file mode 100644 index 0000000000000000000000000000000000000000..55b083463a8b5b19d62b6f2707665c08eca5e65b GIT binary patch literal 194 zcmWHE%1kq2zyQoZ5fBCeCLji}c^ZI3_m{*Mj7H0TYXIGHjqLt8q_ zfBd5cZZ(#}Kb);u8LrmGpfzi?)>WdlYH+H>A8oWlwB<72?R-zQ+S=OM`m_6X?&t3Q z^R~D5`zD&(AFgozb}cY(I9&V8;kjgJqVeE|&wMQ|7XBzNiW%>xP`LV1am(nU_SNe8 zEqPvcELYu7neuKdzM^spa^1xlQ))@zio0a?7m@qbl$&>9QY;-CbMs#t6*rw2a&O-I zgIe~~VYi^^6IHnDgm+8ioLau_Y46tTRT(q&*g~nWKvh`{#mSw#`JBCUy4Z4N_~69h`3{ZmafeGTvbk|oYj{< z&}+_L(06_@sH)CtgovT^>e3m zv~0VIjvv*rtQr+N+o{*h6sq+vJ}dA0v|7aXcgPLr3Pi)!xODr|#r;(w*|>LFY|L9I z6HV`n#N|1;DRNqD`u1AAIsd%c{O+vYk~*cDPmb%B$)jrPp<(^tNT=G?I-s`?Zcrlr zhTL(aL+mW;mk%Z5qBW~WwnamtZKhc!ix!IHM^&4_dMu*!?ydp#c=kJbPeZTTlbVv1T$|v8<7k%4e^1yLNqy?@@OHaG{A3yWv|NhCi=8vcUK>luysXMyj3qXO62H`%-iEcX4kUUioG{>v{p%Xkhf|Lgm*aM-DNAmP-w=4>>lu8v#6 zspps%wH)*8I05^Wzuo?PPW+2m_~v37$UKmVSj|k3sUUOVyO<0z8)Q1je2@ttGeV|> z%*kpdh0F?>7BVknV#v&psUdSiCWp)pnI1AfWP->Ht!9eI9Ia-O$Sjd*BJ)Hhip&(5 zDl%7OvdCwdyR+h$B1uKH# N(s+4!usj&f{1s}GV)+07 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Paramaribo b/libs/pytz/zoneinfo/America/Paramaribo new file mode 100644 index 0000000000000000000000000000000000000000..b95c784234126f6039477b5a53aac0e4a7439d9d GIT binary patch literal 282 zcmWHE%1kq2zyPd35fBCe7+Yu}Yu}E2THZ%4CFyLD-epSl7VV*Z@RAj0KY+Bv=o$?LWu~AR6Qh5Djt) X$RLn&Ky*D&j6x@I*#MnrXUqiveL_zj literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Phoenix b/libs/pytz/zoneinfo/America/Phoenix new file mode 100644 index 0000000000000000000000000000000000000000..4d51271a14ab85c1aac83f7145b9f7f8f19c512a GIT binary patch literal 344 zcmWHE%1kq2zyK^j5fBCeZXgD+1sZ_Fyk%As=I>^2SkNXjVd1Qo4W~PKCY%?)FLS>C z>6#0TQZm1OlnVTQ5y8O32!zZ)$jJ2n|Fm}u4FCVHUckum|Nq;uKkB@H%gRct^ z2Lo|<2+(i{2qD2q|A8Qmg=YhZ200BxgPaGVK~4nGAZLPTkW)c4$hlw|=wuKLayEzt PIUPh(=zK1qf6Tc6=)Qk) literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Port-au-Prince b/libs/pytz/zoneinfo/America/Port-au-Prince new file mode 100644 index 0000000000000000000000000000000000000000..d9590103bfbf4954025847504b9ac459ee9c1eb7 GIT binary patch literal 1446 zcmd7ROGs2v0Eh8AWkP}r7lNs2??KafXjYWLP-9XwdD9um(d<;(;3GXgvayF16hfPj zN?n8ywTK|B%qR%S2x=247PJUEZCnJ^6*o~a)A{bA0$1+3mwSGf8JNv{|6sV`Xn`^B zO4Oh5a8>Gu_mp$`xZcwCYgxw5iFC&|U;6AT4CwYqUwtton>*6!|8>rw}mGq*!IW1}J`DWr0y2So1YO=|sEx5%3ciu|E! z;TqW}+&$Zb=SrIJMygan`wvlAx>ao`{~)%PMa>;`B|g79Y?k<)zOtHtH2V|8zGAm5 zKRzQWGE-zQ^hyMO&dJK6J7WKfS*t2`TZL~;S=G~*RLzA^>%dsEs;wKa4i4>ABJfZi z>TME-^ZVtINI=vjM`eAfThxCE%ZM{YL|zxmhQvA1a5qaf&b$*%{adW&H?!(kSEki6 zJf&JgF-u+^Rc%GeYHuA-9jVW)&a!UR8T%-^HusCJ>2VoNi;C#jP1*fDEKUwxl&9Vo zi_<+3dFFAJ=&9W$&kh>Gv3Sv+J7LNE`|r04d5g!NdJIFKej_0sfBDR@G#-C)&q)6F zeNP~0n5I5T{Q9W~>Oa^p91a^JxPUa``fBLSHjza51u{WWvDLXC$spMv=^*(a2_YFF zDIqx_Ng-JwX(4$biP`GRkko8-Zb))Sc1U_ien^5yhDeG?j!2S7mPndNo=Bodrbwzt zu1K;-wn(~2zDUBhI%6bdBxfXPBx@vXByS{fBy%KnBzGivBzq)%B!6TAwt5E06m0bz zkV)9;Ss>Fu=7CHEnF%r#WG={Lkl7&9LFR)@$X3q?nUbxZ6EZ1VJu75d$h?q=Au~g! fhRhBB8}1ujos!0UB+U7n=h#F2)t literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Port_of_Spain b/libs/pytz/zoneinfo/America/Port_of_Spain new file mode 100644 index 0000000000000000000000000000000000000000..bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954 GIT binary patch literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Porto_Acre b/libs/pytz/zoneinfo/America/Porto_Acre new file mode 100644 index 0000000000000000000000000000000000000000..16b7f923bdbb7d360a851cd7a02ea480e0d0e1bf GIT binary patch literal 648 zcmbW!JxIeq7=YnxY(b*vB7%roy9@pmL_{)nP`pYead0ZQxd`GC9dvQ&B#Mh`J2^?? zbX(CWb7`xBn?(@+N@_Xp4LS)0y>ProhH&2#cWpB_Eq<+pdBbKU&F0*DTs+K|`g5Yx zURVCJlvnk9Q=X)s_%imsKpTwSOVHIg9V}(y}WiCTGn~n_+&I`-l0>6*LAEhp5Ja;z2Q?xI{HJ z6rZTZh~h-CqIglvC~g!xiXX+0;uzIfQaqy?Q;I9amRa$o7*m`n))a4wImMk~Pd)Rk R4*D_cpZ@lO`5%jE`wO!17YhIY literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Porto_Velho b/libs/pytz/zoneinfo/America/Porto_Velho new file mode 100644 index 0000000000000000000000000000000000000000..10cb02b8b9bba9ef4171080ba53fc1220e7a47c5 GIT binary patch literal 588 zcmb8rJ4*vW7)IgU^+H&0jfED*TdOdb6e1#{wkikUt@m4kbYS=jIG|gJS{>tUd(DkuS?me2}lf`f(6`9ffT|L%yZ!*?e&u#H|)?N)b zjm5HWJGNmqij1 zNs{<&F56`JB~6%|^WZcqOKFH?A@|GQ@BJ^Hc;ta6&guVuo%U??eSZp;l&ng&{`f?g zzwq$cVjg~XuQacHB6r5o;-~%-}wI-*dk-90;0?D2HAB@ zt%%(f(s6kQMYmOTI(|f%=sx3!?h#k5dJfyBd%oGMdWA35y@HEX?`KnVLQR_L^PpJv zJ&>UKUCfgGOTVcB`x0ehg+~nB;Fg2Zzly>0+U1bmZDMGerH6jLA%?yEq=&bj6eB*~ z(j#v+s!=UxbkeC4YII$d9SbzpNTpd_W!k+6k^Uk{PdfKfOuiGNGxju#%(ERjYwZOwr7Eap{#`Y7%_W`fJFlkY z9n#a|_o|%nwKC^jsmhHzD04g9BJWL^oOykNm=!FNv+FH2r)H9z+vFAV{5$mgV;7V^ zyHzhJ->()XeUSOZn^Zy6Ls^*mOcY*xrHiKRQWdssyWB31E6n}t>v5ZJr|^IJ^?Gf) zai?XO_cbfbwi}ccVcWO070uga-l2di_SauR0V{US+yX1#JY&QJ^Q4%^XHL<4T&~|1 z{KX-g|JyFv-R~Q6(8y6Ehm9OJa^T33BZrP0J96;I(IbbC9KWLp0Eqwz0g1uU1c5~1 zXu?3^KqnA>5D6a>3J?nt3=$0z4iXO%5E7B22?>b_2?~h{2@8n}2@Hu02@Q!22@Z)4 z2@i=62@r|U(S(S^=xBmOqC~<(;zR;PB1J+)Vnu>QqD8_*;za^RB6c((BQYaEBT*w^ zBXJ{vBatJaBe5gFBhe$_Bk>~xfQ$e#1jra5gMf^JqZtNd9FT!PMgkcMWGs-uKt=-@ z4rDx#0YOFt84_en9L=C0qvB|W1sNA)V33hPh6WiMWN?ttL52qzA7p@#5kiIt86#wn Y9L*>p!-N&)M73kLj7&m*mXG2{wMlfKEtyLlR7n zCfKLsnsXgG@tYGi@pQ9JI(5iSe&vu!I@)BfeWJ-sX^GhD8X{(Db)iizD>T}~Jb zn+k@^&E?1Joa8r6=G=WYb8Jwv5;ohc;a;74vBb{ne_De>nmievKn%gl) z^6H}|e`mZbD6KNLuD&P*nM=&V{4ZqD_%u^E{byTvVazPP@|j)o$vJb|x3Afy$49m3 zodfpvgTs2qvtsXTJgs-NJt=oryr9eK8l`xCo0b%>l9E5(GNswuZRyZaQw6{f9oXnMq+8SMXc#5oATBNI+FUgv;3=OY6E8)?AMhf~Q@>9C0 zO8?4MeQZqi*h#yt`v+4qe8kqaePr(K-)+~|y=3m|thF0LALz#R7TJ{Dt()s>q%Lv4 zZYeF2EhAgCJ~Kn=zbw~=@d0UgJ*e9*{3s6`E;A1f#O(IyJoC`;^R_XPXyo9B_ThqG z&5p(w?at&u(^S!Jo5n^oTG%bo;Q`&1vR`)f_h`%It+KnbL-%}LE_+*>_0hhdw63qz z$Bs;rxIkRss&U-^=@W~+TxCowcIrf6TrBqL^CsTEPxN=v@=(|;D|Tm%JC*LN47lH#n20)7(+9NY7E^V$}zO_bm~F$gD42m5TYVPM~IRPEg@<$^n@tN&=jI7Lsw6y zEJRyRr!GWah{6z!Au2<3hA0iu8lpBsZ;0Xy%^|8YbcZO<(B9Lj&(I%|07eENDS+ev zk_1Q=AZdW)0g?zvCLpPRgOLqSmkvfgJY7N<8G)n(k`qW$AX$N=1(Fv?Vj!7; zqy~~3PnR4>c065r82RyZ31Vajk|IWqAW33m36ds8o*;>WWD1fhNUk8sf@I6nr3;cT zPnR%A#ynlhAUT61jgd7-+8B9*B#x0eNa`55gCviUJx`ZDNd7!s0wEdnbSZ@75Rybl m79nYb>Hf~)63SJ7Z-K~8>7ZZMRWmy?&1oA5XN!8Dlw literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Rankin_Inlet b/libs/pytz/zoneinfo/America/Rankin_Inlet new file mode 100644 index 0000000000000000000000000000000000000000..61ff6fcb7dc92b4d41dd0854a2c9e0be939be6b9 GIT binary patch literal 1916 zcmdUveN5DK9LGOLPK}M64;Ugsnu+Lf+@a|CK$;w5pokAAf{>YFh^2gdI>ZP3x-tF7 z;t!V(tC2eDpK|J$Pjli!rCX~xhI4Ke(^}TtSl`l3myGNC`uR^=e`J69zWwfXyWQ@u z&+8RkR`*V>{A>N)H$1GtJ$z37b>DhTWj^?ibpL9UmHh*GkQ9Z7_lMh~_j^n&AF%8mf&+ z?xuk{si;CGFS@UJnbT!T?zcL1K$?U{{$|7X`efRZZ|wAIcVx!*U)Y%^dQJX?5A5qL z-DcKDYG>D9Hg7b)uXDnjpQ_&!pK4`d~jBZve((7uESECRA!6Yw@XP+$j<%v zpqY1TzLxH2H1p2{wXC|zEI2Sk7tYK#i?%$}#c3HPy5yEddwr%n@0^zZk}ehLH*Lk& zB9(n-?6S6>q^kRnt#1BG-t5?Bm)CqIZ?#t26{T0r+j|>zWpPsNqfxdqI_N5 zv&PhBW@znqC8lnGPwS5R&DwiE>$?Yv<-HqmyDm0S)}Od*>&ufwTfVd#@_v_%^`F{J z$(^#fY_Hwi*JEPgHjQ=PFk4gh=+=%?rt$F_-PXF_Y`0Fs*DoIb{D{05kDov3OX6JyKm8^<7?grYBq{}q-Q!s3 z_|HZE#|_8bxMRpIL++XXRryiZF3|~DuXBpmlbnY_zg*XiH z7~(R-XNc1fuOV(j{DwFV@!X?x9pbx3=RCuEkIsFD|BwbSIsj<_qX&>CFuDL~1Edd- zMnF0NX$7Pg9$hma-SFtz0qKWF*APfYAT43^1kw~nS0HU+^aauwMrR(rz+e($VlY@l3?_+@gylvt$c?SZ zLTe&IBu&3C2@CPjwB+1#tEH~F{F*L(-{&8jU0Ue#{+ufN3zySvFW(z>j~3(c;CLba zGpZ7={qiKYD$;|;GW}eVnU;H;gr$Qk9nRkj|E?~F}GJ<-Nay>@> zhIwOqV*lu3|Ju+7>EpDGkWNS|q?gk+L%KO_JER}d5b21tM0z4kk*-Kvq_5L9Mmjrf zYos^QocE+V(jMuLY=G>5Y=P|Iw3{HiIPEsbKFCJMPENZOvKO)$ZrEq3Xf>a|qTK>f JyWvPU@C%u!B(VSh literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Regina b/libs/pytz/zoneinfo/America/Regina new file mode 100644 index 0000000000000000000000000000000000000000..20c9c84df491e4072ec4c5d2c931a7433d9fd394 GIT binary patch literal 980 zcmc)I%S%*Y9Eb7Wq@_$lyqJMVi$W|41r?0;Dt1v+oCsXRm>A5;gMUCATqHt^7hF5K z5s4sImBW-o-ctz1OIfDJyk9wlE5VNMb9AR0SH8o0K8Ime^L)c~(HBK>;#@M{a5=^1 z@}BkTp#6HRuUB^_((Lz*Rqls^2hPW`Lbp%db>g{K-MAZa5?2bW#P?n2({6_KIet0v zwK?4#sNZr1Yc}1X`|Xk8!U=ceX0J1vy6h7SJ!;nJl3)J^^zSb%GB@9?|GbIW^Zl)Qq0P3PSX3`Y zpWA<5KGsVQOYP-n`FiEfEqk^6ky^_rk@eeoYW-iXY^}O#duCF0hLh?-;M7k_>ZxBJ z{rIBibu5c`-rKG~s(IIv?!Slpr{XD@6_sJBEH$^*+^6PNho!{4a{|ZD@EQJm&m00E z5s(l_3?v8=1qp-1@il>vNWLZ%5(^22L_@+M@sNN>L?k2<6A9{Tq9S3DxJY1M6B!AO z#72T6(UI^-d}IK=W(3F(kTD>GKt_QK0~rT05M(6CP>``8gF!}v3O6*LRG7d!-P)%6Mh literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Resolute b/libs/pytz/zoneinfo/America/Resolute new file mode 100644 index 0000000000000000000000000000000000000000..4365a5c816c5c487f7ecc155e6c06c57e5b64139 GIT binary patch literal 1916 zcmdUveMr=I9LGQBVl~0Wwrpljx7u>ndEVKqtL2_N5nW~vmucFw&1^M4TXkkN_Z6i6 zs{V)`#)j3f{%NB^)&r5XwF;sLvBjX09w;J^zI%Wo#oPDw+do48)Sr7FKVQdj-d~^B zD>}FCvs`&ZW88*aWR%~lwIF~_~nMCAHHrf_MXx=V=v2F&3kp!>_27n@{{)Mye@gC zWVaoY{Ig_^ZMK>HohB=BiOuS6H)9_Z+HoD*jlXNLX16w&Kzl@Unkr3j+ZYYi#w2&u z5FKAsA@9w6pm~{-WkT*noj5p6!Xxk7@V$PS)O*2BK6g{zzxJb@aeXplx}nibI}+5g>MApR$E!MHO1_!7<{zDvmSLi^uWGcPkyuf-W~mXN3pE99JecDTslyDGU; zR+nwEtNVLQEZnBC?#pIv%0^w=ao99IUZ(3>x0&^Sl<0=0HRkJMer=jR$9%JEs3!Ol zUU=5mpaI_;`2Jb{f57*+FOhis{Au5yc>KT~c_|)0e#n=^UwP%fPqKqSDTqX(QZUOs zj)jf`7k!Q!j=OQkkXwe_Grz}8GwvF4+l>2$+&JUTA-B%BcaQGoA$JeCeaQVo9DsNL zae?6j#0iEM5H}cpKpbIs;?cPR@x`Na2I39G9f&^=haet7T!Q!naf;y;#4Uzj5XTsv zL0n__=FvIF@DAc0!#{|F5Dy_PLVScc3Gou*Cd5yOqYzI$I#(IKdUVb*y!GhZW%vtm z7~(O+Wr)uZry*WL+=loKaU9~gN9Q`kcaP3_hW8$w`wagf4PbNt(gH>gAWdL&0n!FY zA0Ul@bOO=}NH09PWProhH&2#cWpB_Eq<+pdBbKU&F0*DTs+K|`g5Yx zURVCJlvnk9Q=X)s_%imsKpTwSOVHIg9V}(y}WiCTGn~n_+&I`-l0>6*LAEhp5Ja;z2Q?xI{HJ z6rZTZh~h-CqIglvC~g!xiXX+0;uzIfQaqy?Q;I9amRa$o7*m`n))a4wImMk~Pd)Rk R4*D_cpZ@lO`5%jE`wO!17YhIY literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Rosario b/libs/pytz/zoneinfo/America/Rosario new file mode 100644 index 0000000000000000000000000000000000000000..5df3cf6e6377be897b4d09fe438ec75eb8c15ad1 GIT binary patch literal 1100 zcmc)IKWLLd9Eb5YZK5$03@9if60xNuA{=dF#6RVg3{^2p4Je%yL=XhU!3v_$#YqTA z5QLV-c2zuwYH6cAjWyb#1X2;fA)vS^A~D3OJ%7(RiHlC=<=)Sc>4oo;9XffU$NS^A zLjK|K>zBiQ?PYn5U(c)i7Y6+Y8(!$CO?iK6@r;^YchO8wPUxA|J->A0m3sL4oq2RV zqGxB;`(`MwACHduPj;16`BK$9-PqJ~M}z*{aza)1rc9-NS3SQ{@aHqP^!&%GW+7wM zg8pD?@uXf%A2*FXVbvIGnlGVKs@W6uoA1YTYfaj;DmC@BG3+nZw(D=@r1@Stq<-8U z^p{7H%3ka9S56$z;m*^=Erm(l87&mMQlE9#r*p;b&8t;+^++-9SwF6K78CWF+IsVF zEY*Cbcg!aA!0;+P@Fo}Aw=}NzmyX49*4jW@`(Hkx;IF3*+uc26ZMo`s?j5wYV!W`m zFROtLYv0xbQSM&H!A#am%h{&-+sDF~?uDG6OoT%;(8-=iv|ETk@^Fr+f4l!nyil;V);oKhZAA5tJv dAyOhzqf?4Rszl1foP65^?DhZv literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Santa_Isabel b/libs/pytz/zoneinfo/America/Santa_Isabel new file mode 100644 index 0000000000000000000000000000000000000000..ada6bf78b2815d3d99c97d521ab9a6b35c8af8c3 GIT binary patch literal 2342 zcmdtiUrg0y9LMn=gpw%wq@u(hds2#laOA%z_Rpvz$O)7qi5Z#kXHW)-p%f81w$_^C ziw?_G^yKV9Fak&n6DH46gd6Zt(c~ zb>BpnIz#PmPhD)@EZ^sCl}lyGaycPGM!orJZ1EN~9-pMfr_}UT z)~!{`6S8#V%3`^GUZjo+&XlO>3=@5Exx@@EGqE54E-QLw%nh$z5Z$m^-+1sNSy>XU zSJiy0;xd2IH|2k*ZjSg;$0v5G_}NL55Z0m+hCern6T8*wz8;fwu33^hj~dUZU9zV6 zag%a%qoh_H(P{N@lJ4E7Gm7U*W_*dxN*kB8q1ie+W{%1pi_+`<98>GhUe!4lK2;mu ziZr);@VdIS?GJO?i-*8W6WK-d*tp#hm1F_P`op*=)90r z$s0PT^Dixt%`crY1z*>Quc^b_(_0{gJNKKSV;Nhr-n$dtfe5^u0@fPo>BD?lX_p_NwqI9&opHBOT+LLb0G4B9OxS`jWezCL}#~oa;Q?8n%m7& zr#DG+S-pAsg+vJo4hp^|IAo5!{yV=w;7Ebv1OhLM6A}otwK&)E9<;!{m3uEO@cA8I zvEM1;c{jL0C7 zQ6j@c#)%9R8L6usDl%4AJ6L42$Z(PIA_L~j88I?sWX#B*kx?VVM#hZ{92q$>bY$$v z;E~ZI!$-!C1i;ls00{vS10)DY6p%0=aX0NT85NA)!KI zg#-(U77{KbUP!=PZN!j}x!RZ^K|`X3gbj%s5;!DsNa&E*A;CkUhlJ17#t#XgtBoKM zLRT9@B#1~9kuV~0L;{IK5(y;|OC*>`G?8#3@k9dZY9oq-)YZlm3974&DiT&Cu1H{! z$ReRdVv7V9i7paeB)&+1U2TMs5WCtKBSChxQAWay#2E=R5@{sVNUZUHAM7w&^K4u5 UBwxBG&6ASkOHK8pdQ!sv0^LWd&Hw-a literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Santarem b/libs/pytz/zoneinfo/America/Santarem new file mode 100644 index 0000000000000000000000000000000000000000..8080efabff78b5b49d544f7ec784bf4a57f5099f GIT binary patch literal 618 zcmb8sJ4nMo9Dwn(jZ)%kadFW4SQQTAAR=<=(m@3wAUHWGf{QMSOVPEv2s(6BryEmo z$W{agL3Aih?cx?t!AEJ8^S-P?p%;$dks-(bi?_bLIcfb{wt2&5Wz6Q>eR6e}_50S| z{NEK-d|!~))niefzLDi9Eh_`BL8Z7Q!seK)o<-t0ypT0-UVNPP1fLx{;w#vb-@W6i zzB(i8r3cY)YC+?&Q#GT0`IFD9!P|2=bnv1wg~edl4b{lez8vj(QrV4NnbTS)6D^)p zO6xn{vb27@PuN``6Q16DwjdDa7DQQf;@WRtZuwbh!8o-9po=lfs0>Q(RB`rrM}=kC6|+wXfy z%c?3et$#eB<`-U`m(0ue*xlx67fTlJ_ndbhZ2orOkhMdr@}_~Vraalb(DI>dUtn zbh+Qoc~!pStn~K8kLaFr``x`)J-Tl&!96s+S`Kah&ilpLcKzkF(`q<#l^$;FkXL=V z@><7b?r8S>4T3cVI1uRG;aVWYpw z@WU5HME{74Z1aliU+j~UwsecACx>P{$(D7Z_YTD->IelNensGc|-cUOuR1_q6Fbt&gkJ=eO##jhmF%y+@`O z6sn93RWdVgM7ZQKIX8Tfm>1bAvx0|Jwlzs+e-bKkzE9EfkNzO;JRPZXpBfT*hsSjO z)?;Epn@`J?AFG91PUwQnH`QHBpVNzCo>7JA-LmlKTD2s)Q!W_`5KG4!WzoqRakp=+ zT-Ix;8QA`MrqHHO0~YeS(ooEQumjZ>kU;Y>H(M38;c^Qwny>lM*b5+z&w ze!nBY;dBHBOnjUH&LHy!hx|uA!G3@LyOw32fqs9VvO@j-L2X5FI?Orjbwo{^{Jy-n z)LLoYIbyDPUFMxwqQhaPW*^k3}L{6+q%Ju?Q7og!OB_KIv4*)6hNWWUIUksTvj zM)r(s+ScqE**3CoWaGAG=g8KPy(62qHM>W)kM{ogLIZeA2Y?nJJwTd(bOC7t(g&mw zwx$zEE0A6w%|N<=v;*k}(h#I0NK25OAWcEKg0uzc3(^={(;1{Swx%~obCB*J?Lqp3 zGzjSs(jufsNRyB*A#FnXgfz<5bP8z|(krA{NVkx7A^k!chI9;R8PYSPX-LD%!kv<}gL^_GI($@46X{N2|Celu% zpGZTIjv_5ZdWtj^=_=Azq_0S0k&h)kv>xO|y}1Bke}|jWitTIMQ;Y=Sb6$t|M(n`i?Xn={(YUr1!R_`AGMX z_9OjA?f~Q-K<)zMK0xjS GE$lDZUi%6F literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Santo_Domingo b/libs/pytz/zoneinfo/America/Santo_Domingo new file mode 100644 index 0000000000000000000000000000000000000000..4e5eba52b8a86c379efae694a296d51e1008141c GIT binary patch literal 482 zcmWHE%1kq2zyNGO5fBCeK_CXPr5k|6uKP|2zc{=v{91n4;s5fF7Z{wYPcXJ$zQFja z-h;W|Ljdz#77x~dO98C=J3ZJ9wgj***mHtYU|N9X?>!H!G#@4~GBYu=z#%I$>;M1z zDi|1mWC8=r|NqAiFmnF?zjFfv@BjY?b}$NpL>R<g{00Ib|A7F|k01)EKr|>UKs2qxg9{iyCR_l1#*DlG literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Sao_Paulo b/libs/pytz/zoneinfo/America/Sao_Paulo new file mode 100644 index 0000000000000000000000000000000000000000..c417ba1da757e94b88919b05df8a21b35a5bf66d GIT binary patch literal 2002 zcmc)Ke`wTo9LMp~a^x({Aj(30+p@7d&-dLeJ5G-re&R0 z77QHrYp@_b_(xbmeuBa&C`+t4B9>ymhMvnozf4Loy-w@;JU{)}AN8N#@!jJXgE4qN zUy0i0#z^2_&o%BRygW(w^7;BV-)sG_XI%ME&!6+mzH^1TKh-9KvG;ZGOh!*k_|Bf# zw^UAEzFAXmrR3DfZXHTQ|>WB&Uex%bI4@<~&>5PT%h$c5`cw*c{9u^zTStW(BgchbhmYFhHXAZ11 zvo3yWV?8g)>{G)!r!#2oJ(9I^TgICE4)4&S%HPcWJO0vnp<`zLwzMv|Q`vb)SymA@(vzR;l6 zGTS^z831_6WWvy-f<8x`NsYYOSg z>)Jy4^18;5&XCrS-jL>y?vVD7{*VTd4v`j-9=)zfq)V@B6X_Fa6zLRc73mde7U>pg z7wH#i80i>k8R^;Unnt?zy0($Ny{>VjbEI{occgiwd!&7&e`EuY9YD4K*#l$~kX`V) zZ9w+H>ox+}39s7ZflUe z@w&}Hb_dxWWPgwiLUst*B4m${O+t3b>$VBmC$HNmWT(7ttB}3&y3Im%3)wDYzmN?> zb`04vWX~`;?VldDB+uKcyKemT|FdwpbKTk%McwyEQ7|43hr%J9p}}}06y-zi-z1n& AE&u=k literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Scoresbysund b/libs/pytz/zoneinfo/America/Scoresbysund new file mode 100644 index 0000000000000000000000000000000000000000..e20e9e1c4272adc40c07562bb3d6767cb056b550 GIT binary patch literal 1916 zcmdVaT}ah;9LMp$Q#gNnQ{olndcD0+qjvQZqJe&*R z`~LW9wl(Ki|9H(Zf8oum!@PNxLmzo!BfV`a=jA1T5ta0$KRWW3#spu{n1>fNcJh?Y zzt*n{zB-_B=PyY7;E*I7J|YXdRT6i8EYG#J%kx_rWRb@wN$wI!b`(fTe5x#th?UgP zVojaS)3hH`G<_mkmwYozGlu_A$FW~Dv-hTE?YXSkJI<@K@ua$HZ%9u2u;dnwNZy7| zBtM}~3UWH7V8$;?<9c=J?Rr`EXNNAoP%JNgU#;$eIxQSou0?xS>54<~y0SS*SM6CQ zt4l+*xG_%Fr2MQU#WPYG`kj^~{UBviKS_D;B`F^tm6s-u%G#k5;u-r~y*=GhG5DFT zYipDB-Mh8&<^8&$wMjR=(5gO9nQn@y&?-l|Ry}rVbyTTTU!SY5Os7fB*+;s0B3!og z&ym{U-{n>RL#gW>m)Ghhq<+`0`ud7<(oj35jfn@Psc=Y}!_Mf|1l6tgKGl|)cHK7G zrElE!>6^zlYwNe$<*j{%+BUFJ-fl_MclPGX_DZ+3H^<1ku7$Fr(iM@A0cY5C{Z46Z~vQ=zshZ5(xa( zVp)N}aAKe!(h_V=?D#^D7;{Po-8^;wzD9P@Tr8BQmkm=~X!2g~;_F4+9D0j`*@ za>>XwBNvTaHFDXu=DLv!N3I;XbmZERi$|^=xqRgMkphqkkP?s@kRp&OkTQ@uY)v6Z zB}geqEl4p)HAp!~JxD=FMMz0VO-NBlRY+M#UACq$q%vDm8d4il98w)p9#S7tAW|Vx zB2pt#BvK_(CQ>I-C{n4dDHW;J))b3WiGvJA*NAPa%41hN#!TG*P!Kvn};4rD!$1wmE> zSrTMTkVQdO1z8qkU66%ARt8xbWNmEC;vlPIYnBIDA7p`$6+)H>StDeTkX5q%-!D_R d+bmX*%WXER$l=Y+%Fl9UI~`t^(|&S=KLvok!I1y} literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Shiprock b/libs/pytz/zoneinfo/America/Shiprock new file mode 100644 index 0000000000000000000000000000000000000000..5fbe26b1d93d1acb2561c390c1e097d07f1a262e GIT binary patch literal 2444 zcmdtjeN5F=9LMnkqQH%ZQ;8vb6-0*B?gOursm&?$b8b?d7UesOi|Njd(VTTGm*mXq%nh`_OY8GIv2h@FWtq%9yq zpPH0YHYBL9x|w=v#e|wxIIhF9MpXC}}?Nyq3Ob5vJb$tvNO`8x57 zNR>1kp=X`^LCrpNN#EFeTFvp#k~i%*sOGK=%6aQP6gTI7E^k@(wwNFHo=i^FA~|qD zr#Lo>l#!D<^^!~6I=EM-oo!U<-OuTaBb6$%*{ic&TBNeQtuklR47IRitz1+&rgD?- zlegu3q85jz%DluYBJbNMnLmDB6rB1=-u~%;Skmv%cMR+nOFMqlckbFQ3L8GsceU

gzP=p8ch855>q;fg!QFZ&W@w zvR~A+4$FrI+eK~tQMsmjy?EGpM%T5qsYlWe>qoslRUh4{JtbwzbJ?%G$?3{_+O2)z zvC4O#K(G7eXSKeoT0V9rMm+A%mrooV6%AF1vaw@WY{;FI8yk*_O>r0G=JGDFIWVsM zd54vMqPHC@P|dX-y?tkr3Jv-5Z%WwTuYY~@ z-y00>?i3;ze5)rU%)Dz6Vc(~8rXEg;xDrhw&L~3X?MMSE|QAVVWNFk9*BBexXi4+s5CQ?qMo>o&(q@q?+QlzF< zQ&gm?9A!o7%28OPvK*yFYRgevq`F9Xk@_M9Mk;JIB}Qs&HAP0MY&B&@>WmZ`sWeBa zky>*U8>u!&xsiHv6db9z)s!5mxz!XMsk+sa9jQA~c%<@3>5Bz0( zYMX;ubev<=mU^aJ=~`I2)|SZ#w_fI=bZeGV*Lqux8))@)K9yl(%-|NuSuMerXFHSr0@9k1I96RLP_{Kru z4D51l+8GizZ)kU>wX7Ej^(&mjO24?Jq{x|`UMP}M>q0YPlSFbvK`1#h!AbsMk)C;e zo=O>;t7o0?sM&|3^{xB9Q=+p(-qv$Ur3PloIcvTZa|^D@c}qVMX^CG+U&folH#wox zy)TON@h^48#Ws;Sd|YRpd0u4??$bF()~W^F&uaNnt;!85nb)4D@+-E<+v^fkfv;H> z=KZ3IJon1tlxd>)!hBgW@w2$&L$AE^>}R4>r|G-iIVSE7#ps25_lkR3FY2=GZ>vQ$ zAM3@<1FC$%LA|7WlUnlQIa!h2FDlNQl$G%tMdgtpx%6_QsCxN*z3kI2RlRw?zW=QT zRnxdb*X}7*%S(g$fzH{gE~QfY11{zNJyQn?&a1#T_sNyB!(!zJ8M1!zoM;$|lMjub z6ph=j$cKlA#H!{|`N&|ec(nSGZtC5y9?Krlj|X?C=6J1FR|M7S%e!^ZSE+)hJ9LXD zQ?(oj=rtGO)suVL;_1#6a;=UNt$`xh)^|m$E1V(Mw~mVT#0l9^b69kQr|Zt* z5!E^Vo9;@|s%!YH-tg;gwQ=xWz3KA~wYmFMz2#^?b+78t&-527cf=HTuXZ9DXI_jckf9-Cvzo!79UT{j$799^ z3=kP1GDKvI$RLqXBEz(raUuh?nvo(yMaGH@78xxvTx7h+fRPa+Lq^7o3>q0VGHhhr z$iR`2BSW{Ev0KgHk*^Ruf(%zE%@pB*I9Dkr*REMxu;_8UKoNWJJ`p-sl+5 nb$tJ>bC2(TZ}dNr{`2Cc-6d2!t2#d?FGpro=jP_*=1A`!V0=z$ literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/St_Barthelemy b/libs/pytz/zoneinfo/America/St_Barthelemy new file mode 100644 index 0000000000000000000000000000000000000000..bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954 GIT binary patch literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/St_Johns b/libs/pytz/zoneinfo/America/St_Johns new file mode 100644 index 0000000000000000000000000000000000000000..65a5b0c720dad151ffdcba3dbe91c8bd638845c6 GIT binary patch literal 3655 zcmeI!c~F#f9LMp+gDcHj5j!juuoOA8R?9L2`Du%oBL1}16dpAygY5AtBXOLLDRsuQ zV=|z!f|Lj;T{Skl>`^gCP5U`+bZv-ep}BY;${S9wkjrz@V&n5bgwR^MMy}GWhrN~q8T=CXJi%T{=?R(7`biKZ&r~8d% zEv|Lu1^1gqt?R96J$!GcYw)1IBJHpcDhebBS(ht+X4j?JF^eFFLWr`K5ro=#C;jcF|o-WQ_| z_5VqHEy9(G_(B|xZBU1gm5C#r!sLPU z?y9;w&ssg=&ZwyCyNaIrza={4jEFwfBzt|Y#8vygmREngRa{fKMPB>bTG4yn-oSN* z*~aw~D+7J*&;P3Lkmm#a#!UCebek85y%4)X z7oPPG+ffp@<;WcWtrgYg@NF6X+g28vx4)9;ACXsR-mz?~F)|~^ywgZ9QU;}(sVSX} z)YA(BX#?Z^X$K|;Mz`PL+0POn?e1WHhl02|7a`%zjkKBKx0k*mWNDGi2*y<)A zT|nA^^Z{uE(g~y$NH1)4GmviB>UJRgKpKK{1ZfG<6Qn6fSCFad z9Hcw8x;;pLkOm}Lpq1F4(T1zJfwR_`;h+G>INbmL|TaS5NRUPMWl^LACX2PokUuR z^b%<%(oLkDNI#K=+UkxXEk$~YG!^M8(pIFeNMn)CBCSPwi!>MMF4A6G-Cv}^wz|Ve zi;*59O-8zmv>E9$(rBd9NUM=vBh5y-jkFu-H_~uh-EpMlNY9a`BV9+@j`SUAJkoij z^+@lL<|Exl+Hb4-k8A*2y#tUfV5|24vI&q~fNTR~A0Qh6*$K#2K=uN%8Iaw8YzJGt zACL`Ut9JylC2aMcKsE)kE0Ar0>K%e?5nH`S zkWFH%cL}mhkbQz|6lA9$TLsxG$Yw!y3$k61{eo;5TfJkDEn}B;d)@d*Rc6BFYT;}ar(2eU89 AC;$Ke literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/St_Kitts b/libs/pytz/zoneinfo/America/St_Kitts new file mode 100644 index 0000000000000000000000000000000000000000..bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954 GIT binary patch literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/St_Lucia b/libs/pytz/zoneinfo/America/St_Lucia new file mode 100644 index 0000000000000000000000000000000000000000..bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954 GIT binary patch literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/St_Thomas b/libs/pytz/zoneinfo/America/St_Thomas new file mode 100644 index 0000000000000000000000000000000000000000..bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954 GIT binary patch literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/St_Vincent b/libs/pytz/zoneinfo/America/St_Vincent new file mode 100644 index 0000000000000000000000000000000000000000..bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954 GIT binary patch literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Swift_Current b/libs/pytz/zoneinfo/America/Swift_Current new file mode 100644 index 0000000000000000000000000000000000000000..8e9ef255eeb11515b84126d9ee5c0c6b3c72f2a0 GIT binary patch literal 560 zcmchTtxH2;6o=29*AK?PHz>Pnf?;Jz(0@Rq17TML+sa@`EoPIj_O2KNba#TXX#lUSMSY}+(~!w(sW;H($D71TY6sJU&m(9Y0B^+ zGNWokKI$VoKDZXYn6U{jG3D#}{idBe?Ta{fRr7r3&aBMEcPie7Eeo6ZQ1Tl(1)Uw8 ztx(qWCf?5u|54Lvs0yhIsSK$NsUB17Lli(XKvY0UA>oEWT literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Tegucigalpa b/libs/pytz/zoneinfo/America/Tegucigalpa new file mode 100644 index 0000000000000000000000000000000000000000..477e93950c2f09b8c338033c07a53d4ef5c01912 GIT binary patch literal 264 zcmWHE%1kq2zyQoZ5fBCeb|40^d6xKiyC}@M^FT3Xc7RgVz6Z)SwE-@%Jr7*2ow&fn z2!{Xvud`ub`2TbwKsmI@lw7BhhkMq9R8xhj!TVJX4OHmp<#V0!@rKHr&@$_Bi#jmyOuB}B zD!XY+=JYm;g9X>*q1qymn{-Cz<)(?eZ$sLhSg71{0i7R}qVgxJ^^uhrb#$a#dOkRX zH&7*yJ=`n`DqT_x{t$(kvGVw(1yQsuT>6S13Eyf^mZXh{l3m!1q{%*8vSe_M)w-WT(w@d0=+a`T}Vn#I;f6*5P##M9Ld)?C1uP(+s(5(fHs&(z1 zY)kJGZHtq#V@reRm>!g!KRu#rv|V;D*hQeHOkSRf7CqG&^2+V6VqMrjeL|rpy*67Y zG_S(eheESYZ5upZpDicX+#(+lnB)74R6^#E3;&S}k`0m$k`Iy)k`a;;k`s~?k`8PROK?Ss~Lx=7mfQnHe%QWNyghkl9(9 z=^^v8G!sN-XlbU1%n_L+GD~Ed$UKpW;(s#JNVC{fv)lx4irr~XPVhP$c85JV@;7N6 BlW70| literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Thunder_Bay b/libs/pytz/zoneinfo/America/Thunder_Bay new file mode 100644 index 0000000000000000000000000000000000000000..e504c9acf198fbd447221f120354774e46dbbcc0 GIT binary patch literal 2202 zcmdtiZ%oxy9LMn=h(;GkC@E4R1B+dR%O6A0s5D@=t6m|!n3Czyz&nKE8yZ0xnqj0p zV9f@#8mqKs|3+rET#ZRNYini7YW*7>+u}hBJ4O#$tb04}v!3;+XRUL0&g<@O_u=Oq zX=vVB=KbT$u)lCQPuR=%tUdPWo3~Sc^0<+6dB4dyHKo0Qm`Uh8uM;2CW}3uQlKNAo z*J+hJcSJ5tKdgM-A@P~VREpjwsb9aS(@sX^vJ*Xe<|~hy^k=r}jQvgK@~+$U70p32 zYkh&v4EfEKi&OPg{uGmy>sMDNPng+>DQfnwKACfVLS>)*S*{s5qjHXZCfB}jRL$-1 z%yo|(RJrv(n7nO6dS3Y{bN%us^$nRXoBV?9IzRTI_Wg{ruFg_Io3Ql-(TDQo{jEeM9pjjO*?<@xi?!m?qt=#n&3G3OgyG5(8LJoUa_ za{8>f>DxhFIdnm8es7PyrSEgO_1P_YY1><}tY^2nt@bIYYTT@CmxOvwR{?uXt|1to(AR3h!!>Rqy1g+6`f;>rYa5R2IwX2gcQ!>MP=o4bcw^~S~lvuSXZR^hj0 zb8m~SIRwj_do&wP|k~(AzUVFgt3y^p4o~(oxZ`I>tt%Gpkc|4j+=P-y76JgZpIX zSCwj4cT^sJH%E1E49V`NJ(Z9+Eh%yOf8rC5zaH_tc>J~Jy`*^j#G77nJpR$igjDyY zyLZ;gaKx)x6Y*-eciLNLZ?*lKJqdrmk$*9jxIOI`_7)9Fak&n6DH46gd6Zt(c~ zb>BpnIz#PmPhD)@EZ^sCl}lyGaycPGM!orJZ1EN~9-pMfr_}UT z)~!{`6S8#V%3`^GUZjo+&XlO>3=@5Exx@@EGqE54E-QLw%nh$z5Z$m^-+1sNSy>XU zSJiy0;xd2IH|2k*ZjSg;$0v5G_}NL55Z0m+hCern6T8*wz8;fwu33^hj~dUZU9zV6 zag%a%qoh_H(P{N@lJ4E7Gm7U*W_*dxN*kB8q1ie+W{%1pi_+`<98>GhUe!4lK2;mu ziZr);@VdIS?GJO?i-*8W6WK-d*tp#hm1F_P`op*=)90r z$s0PT^Dixt%`crY1z*>Quc^b_(_0{gJNKKSV;Nhr-n$dtfe5^u0@fPo>BD?lX_p_NwqI9&opHBOT+LLb0G4B9OxS`jWezCL}#~oa;Q?8n%m7& zr#DG+S-pAsg+vJo4hp^|IAo5!{yV=w;7Ebv1OhLM6A}otwK&)E9<;!{m3uEO@cA8I zvEM1;c{jL0C7 zQ6j@c#)%9R8L6usDl%4AJ6L42$Z(PIA_L~j88I?sWX#B*kx?VVM#hZ{92q$>bY$$v z;E~ZI!$-!C1i;ls00{vS10)DY6p%0=aX0NT85NA)!KI zg#-(U77{KbUP!=PZN!j}x!RZ^K|`X3gbj%s5;!DsNa&E*A;CkUhlJ17#t#XgtBoKM zLRT9@B#1~9kuV~0L;{IK5(y;|OC*>`G?8#3@k9dZY9oq-)YZlm3974&DiT&Cu1H{! z$ReRdVv7V9i7paeB)&+1U2TMs5WCtKBSChxQAWay#2E=R5@{sVNUZUHAM7w&^K4u5 UBwxBG&6ASkOHK8pdQ!sv0^LWd&Hw-a literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Toronto b/libs/pytz/zoneinfo/America/Toronto new file mode 100644 index 0000000000000000000000000000000000000000..6752c5b05285678b86aea170f0921fc5f5e57738 GIT binary patch literal 3494 zcmeI!Sx}W_9LMp4A`+sAikYIhq=?EQh6_?+E`)l-1x#^!H1rH&^5lY8h%GMZ)G&<( zl@6x3Vul+gX$Wd+)08OgDL$H#3+RJ;qUZE{-`j5Tu8UsgJ)bkox&D3saS2IN!)*U} z>X`rV^4u^l-@waFg%5>%F-Ky$v zfxf-JOx(#oA@%A4QJuL<-d&I_?xkeO`xEDh2eE1LVV|+$QAmP(+;Oh@%O_Gk@f@R` zJRYrUuJ=|?&qnBHM_VfA9)IoH=u)<9r*>O%S=E}WbZzMr?&6uOGfWAOs7tbL=mFu` zx7UxyZiaWYj%{~=z z__*%p@lR)ZkT1<&e`+!k(Tihwg4GV#nF#uq<~mJTgR%m{TD}`uobb z_@g4O=AIlCo+n0K^U!-;n(IH|=Rf2Q`_zK6bkuu5So=Do-N=~adC6cou^z>uZ>YY@7 zJtMzNrNle6%q&pvhATZYC0ot%JD_LB&Qr6Umt< zeE)2uNY8M{`FmQ4j0rJv!Iw5s%k6poYP&zrum4lOb-4;w*laG>kzzM@m#c7_&C~ks zZGAQzVvn;8=x^SU=6%b&!{W?%*=%msN8EFap36KlZ>LovI;YY?F2>=oSBm_tdkRTvYK*E5;!O{c* zi3Ab~Bo;_8kZ2&`K;nS}1c?X|5+o)_P>`q~VL{@81jf=t1_=!k8zeYrMTakhhsVSR z2oMq>Bt%GzkRTyZLc)Z^2?-PuDN7S7BvweUkZ2*{LgIx442c*LG9+e5(2%GhVMF4E z1P+ND5;{v0J0y5W^pNl&@k0WLL=Xuf5}O(dL1 zJduDR5k*3Z#1siC5>+IuNL-P?B9TQxYiVMO1Q&@e5?&;}NPv+DBOyj&j072pG7@Ga z&PbpwO{9@fTbfuS!L~HfM#7E68wofPaU|qO%#olYQAfg##2pE|rHMQedP@^~B>0vl z`bhYZ_#+1Zas(iU0CEf<2LW;vAcp~R93Te*awH&!f~7eYkb}X}91Y0fU}=sAM-##_e!|ATe{f01&0NPcCmNu8r(HF)a!2+Ad$WR literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Tortola b/libs/pytz/zoneinfo/America/Tortola new file mode 100644 index 0000000000000000000000000000000000000000..bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954 GIT binary patch literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Vancouver b/libs/pytz/zoneinfo/America/Vancouver new file mode 100644 index 0000000000000000000000000000000000000000..0f9f832821b6cff451b5ecccd0b6eac8e53a9190 GIT binary patch literal 2892 zcmd_rZ%oxy9LMns{y|XkWTHf9Cp8gN1QbQlOw(O45tS>68U9INn1+g7v=nvG%KmZ4 z{L?D>Mn1?@qGL9j$`IKq6rElTPK-&|e4bf{|Z_SPpeH>n@yU)QH}i~2FS zL7#SgqZ%U)>c*yh>Wu${oUJwLoUdAb+WWEb)$EX;x4mwfDvITog4O1HNw)l&HqZQ) zlPVWt$C!)m1^QB-xvDv4f^Kdbty)5&bxVDOx_r^EuN=6gT8}sB-^&}-)v8Xpw&t+9 zUgndw%}33R!dkhx_yhAtMy32Y`2}-pRH?KNt5mmp=SfG8Qq|G^yuQ<%r#esP>c766 zq5Oy3I`Cnfa_x@QK`-@E!RveKE^CIFu1jO2+uShIeM+c=BwR5)^koTE-S2d_Azh9qCr z56=8t4UIUVW8x}QjK5W4!?vhc-**z%vP=!HIUpk%O3cWL?Gj(T#EdF=MiRD9HHrCe z=%k_{X0&^q9+TPKB*$dwu}RHlTu6#eiSDLSE=B3_cP^<3$2)cE*{{^Z{gE>1@JH&Q zvJRR2_G{|l!gDgEbg!A3Q6rBmf5l82B{F^5Dl`2?gLaR6S-Bey>a_5cDy@2#p4mEE zJ^D_y%sREgq;K3Ivp=0>G8PrfoSpGz?!;`F=T#;%I#oRL+l;4kfMg|~G+7rW=mi6> zs|8;~>ui66TDZrrANL(pi%OgH6E(Y3&hle=am5C;B;6-VU)*7qjjWX?^NY>$@Jh*b zXPeyCQpt}=HTiXUQV=r06nrv6R$L62r*`J*mET9JRbID#y2`H#vtsq?vL>}=Y)`$m z@R%x!Xw~a7_NaA%Q1PbJ8n5rNtdFcT>uc&{Lwl)twxUX&JDq1XmXyn;Lo-ZCPLXWh z9cO}rg1dCJ&wukT5P0=Xmn#r>*93J91j@F!dN|*`oL9|C_qgUvvp3V;$LyWsvA=Sc zE68~~|Dp~7dvYduuOO8`N`ce@DTbr122u{B9!NouiXbIHYJwES(N+a13sM)PFi2&P z(jc`#ii1=KDGyQ~q(Df8kP;y^LW<;QtAv!v(bfqm6jCXqR7kCmVj8mhZGR0AW}l4hDZ^SDk5b>>WCE5 z(N+>EB~nYIm`F8|aw7Fa3W`(|DJfD@q^L+$k+LFnMGA{l*3p(0sV!1mq`F9Xk@_M9 zMk+Cn3hcC@8NYK;^dsWwt>q~1uuk%}WFM{14~9jQ7}cBJk| z;gQNa+R`JnM~aVBA1Oale`EoW6+o5%Sp#GdkX1mI0a*t}yAa4qINGH^*22*)2C^E+ zav*8n^23Z+LyEMq!INHTQRtH%gWPOkYLRJV_B4mw_ zMM72yStewikcC24%F!+rvR00Ev5?hrw9AF87qVc;iXlsetQoRs$f_aBhO8U1aLCFz z+NDF*&e1L&vU-ko`H=NP77$rMWC@WqL>3YM->VpA$1=r^=7{vs@k#LsBhnKS;}hc( G!u|%QT)5Hz literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Virgin b/libs/pytz/zoneinfo/America/Virgin new file mode 100644 index 0000000000000000000000000000000000000000..bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954 GIT binary patch literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Whitehorse b/libs/pytz/zoneinfo/America/Whitehorse new file mode 100644 index 0000000000000000000000000000000000000000..fb3cd71a69e3038f0d77e8ddd3290a08fb960d9c GIT binary patch literal 2084 zcmd_qUue~39LMpq`DeNuJfPFL&ap>l&du%YkJ~&t`)8BmbjMA1JbBFg*Z#1jZMk*S z(m7!i8nJJR8mPy|;f+K%8H!*H6AD5k+ae-kNKi&GA`4n6`}Mq^F1zZgi++da_j>lb z+Rf)3-PF=l>ifqt#eU)N9JGh~+;00yUcK3W_F9fHx2N@=>l^C6d3a&}P|k1dL)**r z??nk2TiB-_1h%T_ExYxM_y(0(9n~|JE>W}cDsi)HAX*00t4@}KqNt3OZ+GC$Y3`AL;KK5FvPyH(!E zXD0v09ct0wB~vimC56djCOEK7in?Ak#m81iN%K)%+A&`ihdXsy{bVW6jp>TgA7n{d zNQeBBROnu|Ui#pkTK369U3uqIwY)#eJaO$k^l_@Jz#3SY?a9V<7VxZ8mZm9$JCt& z%DUxovp%7u-d|~=8}3W=VZg-7zmV8>%k;)Mzo?BL`*nPLSZz8#uAd*dtTy-D)h`U5 zR9hNu=&i|pYFlK)Y=85(>?pi$UToeY4H=Y z_<7!;2A|LK zdb?95+Izciin~v9Z{>MsBxMG7-)wge)I_4bc$Gc%_B>}#9e>*ob@oG@l_$l$|2FzB zcHr6Pz#B(SBYQwLf$Rd=2C@%iBTl;$WGl#Ckj?P#up4AM$bOIwAv;30gzO2~l+*4C z*%q=dWMjzAkgXwmLpF!(4%r^EKV*Z*4v{T7?H-X$I_)lzZ6fJd@M)G)*{9^X)W!GDwa@=49| zWfZBSmTQI1rKpWe&AF9xh?1lcqLNd|Cv^*zINjG>Yi_xA>3$c#U(QLnTN|`ZYU?+;=gr5RKauTTsN14H?AqYeRTk=x z>*hQ4rCEA0&+9Z4CF-T*A|TusldHFSy4TFFxmMNeUTJJ)_Gfrl4q zmjgSb>#nz@+qPBm$hw8ny>OO1nwKFxrX)#Fa)R_68Yz!OhDdOqG5WZFi-fd^(2!ff z+N+_XhF0CvC%(F_VS6rU_>Pm>yP&H)`Bu61ne>bFo%5|c6<;k+r+g;Q1br?M{WeHM zYn4U@%$LZzz1r`ZSNd1Jt5MaC4k%r$(R(N8z@jM{vuUu#<`ziYf}T1kDN_bdXx8|M zVKOA{dwtd~R1$jqDhZ9PGW33}3_D&g!_Oa*5#=qKc<6n3Zhf6TU$Rn0<{#DD5}d%q+NnCm80ZX+v3rT_zEZEN|BzU#6q;iiIHqUmJmTuBz=Te;IMT>MrMU+!CZL+T1+Qn((v9Gr6 z{=E6)?Zd9^JN&n=fBeFS{fGyj`Fv$JM0~yjyFKlEzE8G`zrF8luiYMeJ~q`Wqeh!E z(VWTVq)_L`4uCL6ij15=2c9JwX&@X*2~< zm8H=YL|G7RLDU7&7erwYjX_if(V3-D8boUlwL$a-t>U;rbIhoYp*x217}|rV528Pa z0wEfNs1TwSXAXp-_fK87gJy6rxmyRxOQM zA$o-<7NS{*Y9YFXD3_sKhl`p>K%785)PEoS}1w(ivK}G-_w)ouPP!<{7Go=$@f`i1r!khv*-Y07wQbO$s16 zfF!}vWPy#*4X literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Yakutat b/libs/pytz/zoneinfo/America/Yakutat new file mode 100644 index 0000000000000000000000000000000000000000..da209f9f0a07625ec83d4ec84917216347f5687f GIT binary patch literal 2305 zcmciCZA{fw0LSqQ0v8C165>IklMf(I*TYrdQk0<(ArcVRn-Ezjc88T zNJX?1>d5*O6;+n3o$?Rme6e1~Bz&P_4xf>+Ka8mxz8=+apHGSScZc{!-^$>=3zQCv@J~7gYYxLG3!aSuN>(PRp0;RDoYfcc)t|t$bE4Ye-auS*^0j z{i`Z=-X}fjbA{)^Vp%fti@5WHSb5jfr=nD6>bu`QF7DYAt(PA-Ant9ysLOV~rB>8_ zq*u1?SLI9I&=uv|RmD%|WM%%Ks62a0R>f}QA~=&5lF*fj65} zZSyW&x35&ym-_XCfeh7;5E&sdL}ZLs zGe~5VRx?awoX9|tks?Dy#)=FU87(qgWW30Lkr5+9M#hW`8W}Y*Y-HS4GjOXJIWlx) z?8xAe(Idl0#*YL5i2xD;BnC(jkSHKwu$nj^fv}oLAfZ5Ffdm7I1`-Y=9!Nlth#(; zY9fn-)@ovl1lMY!i-Z@6FA`uR!bpgb7~?-djtGyu=Ie~Qj_=hX_n4mkFI~PGW@~I& Zb%VErZs*l3b-7(Kucn~DRp64be*u!YOXvUq literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/America/Yellowknife b/libs/pytz/zoneinfo/America/Yellowknife new file mode 100644 index 0000000000000000000000000000000000000000..e6afa390e879f97cef351e382ae62daf183e8d77 GIT binary patch literal 1966 zcmdtiZ%h<)9LMo5V5v9M-)0yJnrV7M+;N~JkeL;9Azc`rh(wqM-cBn>-OUW~*TOL# z@m)FAX0n_%AGDIxwVE5vHGEPwcW$je3s-4vmJO>-HkIr5{yptckJ|dWyO(2QJo&vp z@s{Maa{0&I>3+h8+v`63f9^HJb3={;m0Z5Y~#Qvde5`s7!wHrCyzPkpr7Hnb*n-QYs|^s0JoOl8~kg&~b^xNPIMW@%H! zIooul#56DXNt#DznoX0V(sFFvJTrD&T6@#x*^z!}Yd>u^5ABfWVi$BvU!UC?I;qbm zlD2)uLG6gv+m34~O$0+WalToTQ)k)a`_;Pr=j(RI;70Spg_-hVs>-}{Vq7|#3QX5P zT3)WWZFY8^mR)(5%@!Ui*nfKnwn&Q8wWip3VaxI(tTXO-+Q{=pp19XNbL6Qb&z(QQlSiIC$J0liKSu&c z21p7>4qlf8k_D0mk_VCqk_nOuk_(ayk_|^XNIo11AsKmHN=Qy#mlTo}M_Nc;NMcB4 zNNPxKNODMaNP0+qNPKOI|lk$UJ%7L?JVUOcgR$$Yde2g-jRzPv$G&hHa(Ww368J RaCtaV5-TeUmxUvNzX6=<+*SYp literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Antarctica/Casey b/libs/pytz/zoneinfo/Antarctica/Casey new file mode 100644 index 0000000000000000000000000000000000000000..f100f47461ac8e48a4df03bea0429e464b30a22e GIT binary patch literal 297 zcmWHE%1kq2zyK^j5fBCe4j=}xdH%_rY4Ezmrr_Ow>Vx0PIST&HXD38(SvN#2;TNd? z|NlQD6C)Ed6C)!?69^PEfb`AU05So@)-^C-&^EAO&^9y#NrTiehLB)C(2D;cH-I#Q m+ySCNZUNCC_kd`Sn?N+kT_76dHV{p&`?zd?F1FLP-~s@fiZV$6 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Antarctica/Davis b/libs/pytz/zoneinfo/Antarctica/Davis new file mode 100644 index 0000000000000000000000000000000000000000..916f2c25926bf444b7c366110a29a3fafb17fbc0 GIT binary patch literal 297 zcmWHE%1kq2zyK^j5fBCe4j=}xd7jU4VEE>KU*MnnY6h=cA_m^me>wQATxH|3Pj5(I9t# mXpmb#0OTGJ4RRBR2DuAFgWLw9>w#*?aUYir(8YGT=3D^3yE`2K literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Antarctica/DumontDUrville b/libs/pytz/zoneinfo/Antarctica/DumontDUrville new file mode 100644 index 0000000000000000000000000000000000000000..bd6563ec8fa005d0dc172a8f7850313ae7607807 GIT binary patch literal 202 zcmWHE%1kq2zyM4@5fBCe79a+(Ij-y}Yq)2Uknr4wsiFS=|No2(jEo=!AkedbfkoHA rfI-{P0L%{|!CatG|3PMhXps3J8e|8^pn4!1pFLbQKpX9J4Gp*e$FV4> literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Antarctica/Macquarie b/libs/pytz/zoneinfo/Antarctica/Macquarie new file mode 100644 index 0000000000000000000000000000000000000000..83c308addc4ca5a3f7eac89d19cd0881f7ddce9a GIT binary patch literal 1534 zcmdUuNk~<37)QUWK1FjV6EmmO2It{fnWjF|e2$BPL{iLx%5)L5sE{cAAnR>%Q$mt~ zW{9iM#D;}nX5h+bQQ4vaYY`C*i?D^m>wj*zYSp4ezsJ4j&*g3|@0_~Eww7G^V*%y~ z7i%&X=WsQ*z8CF!b0XpUfM@*3TyU!_KJJ#Ku?|_DX^@q(qv{1r3b$c}Xv`cbMwyE<_qvqCMmhHtwx+AYucE-hPUSy5rEsO42$dLTk@3mm~ zlkT2)qJ@)bS~N5-#r?tRx-=~%9ba|N@gXT~ex_w^kL-1gX?fOdb;tK;g`-v~1MW)Y z&q1mBculJ3x};|2jMP48kxsAIYx!8dmY?Gv{QUjRUuo~kfcS~r7_|adpZq?rK9|tU zoE#~2AE-08@;XU(I(gButkw7H{=y*hQQq^8NP(CHu?b=n#43nc5W66TLEAD8n8u84 z0OJ_eG0cP5$1u>Qu@GXSO=Ba(NSnq=h?x*O8HO?}Wthsam0>KyT86m{dl?4XG!`>V zX4uRy+NQCZVYW?UH^gv=$wnL0(SPwBDVn3q*Mg@!#7&S18uxYAblmV%OQ3#|G zMk$b57{x%UVUz=@2T~BDB1lP$njl5lG*v;$V$=mG3{n}SG)QfX;vm&A%7fI$C=gO1 pqeMuJkRl;fGRm}R>SPqksFYDEqgK{wl>9%64JtAfyE2{5pkF;ZQx5DFlEhUHg|Ns9pGBH7985lwm7+4q>+yWRlbPWs` lv<=LF*c2)mLW1!?Q~!gk0BHqT1ENW=ipvIQt(~qZ7Xb4-9Ekt` literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Antarctica/McMurdo b/libs/pytz/zoneinfo/Antarctica/McMurdo new file mode 100644 index 0000000000000000000000000000000000000000..60bcef686badda46f36652694d06d041c70a9d87 GIT binary patch literal 2451 zcmd_rdrXye9LMqJpwhVKWemws!@O`gTvUW2LIninl6fRN8GVz42MwVlp$lXhnPXVf zmaRD|$pO<6kpwLTF@#zdvNfreebbp|D>PSA%fg=b^Ka{q{_2mOXV3Hc?QG|tvwhyj zup&-D`o)2skG*&Q8;r!+kQV z*IOe#X_m;n;S%-sR*9}3BhH4k60_!l#Fphq+~N-h32}^XYZ-XQRM{B|_ ztvc^YkS2anuSs8C);kWC>7CtylDs2N?`r&6Qr5@m-LAkvq@E{z=P3LF2w(Yt+ zvh#qJz4WcDtJ~%bOdrXtTXTI9G8*nUdGElrMdW?;c(bkFW0}A;0^1V-3xhrKTQc>goQF_|a7(e|BUF=TwG_ zx>lil$<3D+ihdOzxm(8%?o$fREu6T$i?Rp)smh9xwK_kEjyDWlUgIh@@9`* zkuxP$R=tzS39m&;(9$WB_r(sVmYQx1Q zy>ah8C61nuX+@y{pUjRNRzYi?3_kOTocDg6d-H?X^dwg2 zoqR6xZ;a`JrfyN#KB#5rsoH$tjxGvxsp9erx+LzjDowjBOTVx<$ z^f!x&!FpNQXQ{2dxpLd%C{b12rnh%=tLmarz2nGPRpTF*p@xI1Hs+PA%MOdWo{ze| z__%6y9LHnNUfu|=Jty*?FRz#X%Ca11KwDnN8GdQ|9OvyDdoE|ooM)cQzH9kXg|JdZ zhPeag{vCmB&wPxr_Ak;fzsMmESCa^miK|Hk$puLU$p%RW$p_tpIGBw1GNgp$gd~Mz zZmR R$V3ppZz`7!(0n^%E&!2a70dtt literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Antarctica/South_Pole b/libs/pytz/zoneinfo/Antarctica/South_Pole new file mode 100644 index 0000000000000000000000000000000000000000..60bcef686badda46f36652694d06d041c70a9d87 GIT binary patch literal 2451 zcmd_rdrXye9LMqJpwhVKWemws!@O`gTvUW2LIninl6fRN8GVz42MwVlp$lXhnPXVf zmaRD|$pO<6kpwLTF@#zdvNfreebbp|D>PSA%fg=b^Ka{q{_2mOXV3Hc?QG|tvwhyj zup&-D`o)2skG*&Q8;r!+kQV z*IOe#X_m;n;S%-sR*9}3BhH4k60_!l#Fphq+~N-h32}^XYZ-XQRM{B|_ ztvc^YkS2anuSs8C);kWC>7CtylDs2N?`r&6Qr5@m-LAkvq@E{z=P3LF2w(Yt+ zvh#qJz4WcDtJ~%bOdrXtTXTI9G8*nUdGElrMdW?;c(bkFW0}A;0^1V-9ioUZkkLh=Lj(yL5fW5L2U)-OucN2v&@+B}J$nv2JfC;8b1)Wk zR$Y1K35Tn}9PZcDtqnVMp?t0HT`vt=7PYZ{MMC*+G+g>o!b=O%l>0)OGBdJk{;up! zKal3x=Ng$9mzIZjbWb87d&jS6>w)XKFW#qZJK`E`4(tBn7H#)$)AnUoJBmWm@ot?S z{JB*+pZw6SIiDQ5T`1j;zslj%cj>v2kt02Ga&+vA9^3g;j(5*$Z^@+e)uc7%o!0)1 zs{NmD>cGmd4$dU?#D}P!yx*nq*F$pZT8$>A+T`>=iJrMyB}1*%G8`+Gvw=-=uJMsFd zvMaJJvM;hRvNN(ZvNy6hvOBUpvOm%Q(t*{qfb?KBO(0z$Z6JLhjUb&MtsuQ1%^=+% z?I8Ui4Iv#NEg?NwO;bo$NLxr>NMlH6NNY%MNOMScNP9?sNP|d+NQ+31R?{TXrPZ{F w^ocZzbc(c!^olf#bZh+&?fR-s$+fQe4%U_h{gKM@s&cm?;Ex1cdspfE4Vhy68UO$Q literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Antarctica/Vostok b/libs/pytz/zoneinfo/Antarctica/Vostok new file mode 100644 index 0000000000000000000000000000000000000000..5696abf51d60ca0489d57f1545f47762378bf2e8 GIT binary patch literal 173 zcmWHE%1kq2zyM4@5fBCe7@Ol|L}x?&|Ns9P86gr33?T^&EV>2;4B7@}V4)BaOamJ9 UA7mm(BYso4Y=Gw5>6&o?06nM}IRF3v literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Arctic/Longyearbyen b/libs/pytz/zoneinfo/Arctic/Longyearbyen new file mode 100644 index 0000000000000000000000000000000000000000..c6842af88c290ac7676c84846505884bbdcf652f GIT binary patch literal 2242 zcmdtie@xVM9LMqRfk3fxZ-2m9fKZ61b@Chh#YJ=iGw(FzGExatM68SQG6w#LelX`6 zWA7Tv9JwqVv!>V|lz*VLe%NT?WhQf4t}Rwp8eJmMkFokZzs>oF{?kAG(dWDSKEC^I z_s4DbdInZ(sLQpkIdSF%@alWXGS!l5+1xZfu~z3h>p9hvfTQ>sMjMSiJt$ffd2GCX@wF1t?2i1V2I zDiIycij~pGNu3zp8JXl?Ad~a{(1i30nmFkzbw(do=kU8aW$=*R^2Hv#^}`o5>Bvz@ zKF}>Gue>T#+f-7wJ|k(tkleOvt=#SlNP1DJOmi1XMzTw$-!w&BFVPVembP2Kx`&{-X4HM8|o&DwNCvuh7(PSqL74fRN#r&scqy(9%GyQMI@uG~`=qu$yiS&(sF zOTA-K7W0Xgr++QwL*L25==Wt|xKHjK+$)Q^-xOc}d+Kj*lf?&K(LsqP<{?D7M?|uG&5zmGAA@NEr`s=)=UVQ5i%uYPROK?Ss~Lx z=7mfQnHe%QWNyghkl7*AL*|D}5Sbw|MP!c1B#~L#nrZUOnI|$)WTwbek+~w1wKcOv zri;uMnJ_YAWXi~#kx3)7My8F-8<{vVb7bns+>yy6v$r+VN9KsX}svBn!zFk}f1)wkBan#%xW>kene& zL$ZdX4apmlI3#mO>X6(a$wRV-qz}m-l0YPbwkCy04v{1xSwzx^b> literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Aden b/libs/pytz/zoneinfo/Asia/Aden new file mode 100644 index 0000000000000000000000000000000000000000..b2f9a2559a1ca4306a43c2abf074e91d51a64edc GIT binary patch literal 173 zcmWHE%1kq2zyM4@5fBCe7@Om&wAq&W|Ns9pGBPk|p8-i}88EQ;_=YfO8yJJQ3?U?# X1~lkD$V8An{HAi*0L{14HRb{Ue7_k- literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Almaty b/libs/pytz/zoneinfo/Asia/Almaty new file mode 100644 index 0000000000000000000000000000000000000000..d93201cfc49134258724c1ce280e12b5db4f67ab GIT binary patch literal 1017 zcmd6lPe@a79Ke6GRk8|3U~|rynzn3NbGfzVzps}_FdH!<#2}&)p&&vesPItUI*6=G zco-}?M3*El;^8ZJ^q?SKOz2>T4kg4cVr%|BPlS;tZ~h*?_xZiwdpvmW^GWv)J&Kw= z!((@tj8;2Ydq4SKe4EZ~L@GkBvZpGiyuR`;wZ6?Cd%83 zoV=qoZ+rCg%cRym4r|>&wbu8Z&@*lSv?08qje+la_Slk!+yx2cf6KY`U#X_&?@~?6 zi*kN`PQqWOu=OZCgDl?H~QpF_V|hu`TI(zasK*O}bzI(a~bD=sHxkbGQ$e97m3p zT$J8m?Kb9m13%?j%GmY&r>=5i()Y~q-Sh5_%kKRDOz(}f2}fdfUa>RIYmCbo&eA!h z_u<2SLJ=nrFT4OZ5I+z{4y`AMD~KyUTq!UOh4s9=xW*pjXAnicHPL literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Amman b/libs/pytz/zoneinfo/Asia/Amman new file mode 100644 index 0000000000000000000000000000000000000000..281b304e2f8bd4e9bfa58ccc1c799d3ab12f1da8 GIT binary patch literal 1863 zcmdVaYfQ~?9LMqhkz7v4-2*e1A|%JDj*?3*p%SID94#SVN-dXgRLgyYN9IA9+02Z1 zKy2pvz&-PT9{A67bN#xFS=ekCoAG=9&1SKghtAnKuhZG-+4ubk7L=9vt=}Hu<{Mr- zD$R?}9ZuEXvD<`JYFkf3pj~vbw0rZ&c84!i_w}xUh?nowbFb3waJ9d5ti5J;Iv6gU zLx=5NM{Y~+J?-s2wO^(0Mh!%U9!u2x#z4Q^OVWSpBRjhItPB`((H@w*R0c)tvtuGw zXw1_ofx*wuOKek#J><*+^)^hlhwjQyU-d~lZd0+u=O567;&kz+Hp;L`F_IXyUy`ET zGTgOAl2_G9^6Py%;^`V4dGmsdYAn>z$M;G~eXWeCtC6ua6*6vTj*KtLlhpE$Iw32h z6H|(Gk~gSnsd1X_8Lk;oT{NS)hi1BclG*e{r@ZbgQyU&=*46itz5TOHJ9tZSD(*|} z@xs9L#61$sJ)twYZ;(9y4$b?zTl2eCYyQLaI`e&j7Mz=>vu>p5?A;S}&apmPxORZf z-8EWdPFtO~y0^?vkCvi>Z&Ez4jVu`TL`uS6Nomi^Qu^YWl!ZOkvMYyW;j>G+=)g8v ze7RniY&)do^_z8R`Bq(4Q>x3Sm20RdQ&;!`T9H*El^y+B<(({5pK_$y6D!qs6J%wx zM^>HgCaas?%9$f*+E7#wiVJ_#{I*jL!&n@0twfOtbzxw4{%ZfT` z4$6v+ceS>xUXfP&pYME=l^wLw(x&qk@TsCsu$b}iU7^E7c9HbtkAfzIsB%~&!C`VHjQWjDdQW#PhQW{blQXEnpQXWztQXodmWE~vMLLe)FECsR_$YLO?fh-5I9tMjA zK~@A=5@bz~ML|}@(JTwHE{Ldq8tP}3yxGHiNxsB5 Qf1)=#A>MpUic4^R2T5_%d;kCd literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Anadyr b/libs/pytz/zoneinfo/Asia/Anadyr new file mode 100644 index 0000000000000000000000000000000000000000..6a966013d9cd9007a75318d3c8c757dfe8a72373 GIT binary patch literal 1208 zcmdVYPe_wt9KiACbz6-U6q)Hnir%D|ZiO%9pN(D?VR~dl#~q%K5UmZ>C1p zKPnafgi|(L`6e4jK1tQ_w^DszPHGa8%e2b?8Q?ZoC-Q+fG;O@bDTP8H!2tlvkQMs-$_}583W7k(S5@ zY5BP*t(8l<_5DlP@%^3NIg^&Qg%`U0VODoMzpG=HCUxidc^yA|S$AEyBfEN!>Fz`4 zq^B;ad%F+HZdar3tL>4#FA3>))Jy-XknH*Bmc-*#lAMp`2Cn8$j^^`u%TeNRTIO^u zD>|3Ei(VAJ@#o$==2B{Mn13}rtDH(@wdGdo*=x0ut7Wc*`@i>=d1gGLRu}Fk2U02+ ziJH}HR+Cu~zDg;Jqp;4D3CCaIFLY)NRUvFSZqx&NHj<|wk94VAS5CrBqSyzC?qN*EF>-@FeEZ0G$b}8 zI9n4P5}vJz4+#*75D5{95eX8B5(yKD6A2WF6bTiH6$uuJ775qZ#ES%MYa&KMMq);S WMxw@1GyDG{Y)`w%KNgBaJ-+}-2^o?A literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Aqtau b/libs/pytz/zoneinfo/Asia/Aqtau new file mode 100644 index 0000000000000000000000000000000000000000..78cbcf0ef6617bf5efd26f866fd195dfe08e3891 GIT binary patch literal 1003 zcmd7QPe_w-9LMqB)a95Gp4xP2tE{E-&zf$`nz}CGVZx7iK#+vrp@hnl6m<|hID()< zs3-9tDjh^Zp-vTa@EG+wbd&0L=+dQwbrBuw`+kNLck@I0@_{Soy2@y72=rq!_{ zEU$2J^vT6@(?;{6d$m?BoQi%(MMB&0$o|ij==Z&?=G{cR<^990*qf3)z4^elE>GIF zr(^ca%!rLo4cN0cJMFn*%%0CT+V*7GUWom$7aP9XM6f|8ejVyd-wsU2qczj<>5J~% z`KXgG-|4RPZQZ@Ns(VTcy7&IPN!^+=mnUAh^z;+gH~7r;-?-;8t9(CEW=W?;T zCigk(1`dkm%G;>By4z#&&ogH5b(0x-95KU7d!}%E-&`x5bhDL8B~VjaS0iWe#9wE9 zsQN>w{`eUTD%Bg8e}l>uRGm`SwF)Wa8P&)=AortJs?Py=_s=Jbqn=8p`sK>VmF3qc z72v2oa`*lrh5Q8-av(|gK3R}7zLEz?gk(ZeA-RxbNH!!Lk`GCUWJFRTIgzADRwONw z*H;oFnUU1Kk{d~mWJl5?`H=}AGeD*wDCU4n0+|Ie4P+k3M0{l?$W(k~F34n%*&x$F e=7UTKnGrH2WKPJWa8_FUhqDUjWpqQ`necBWYVE`IvD(rK@bW%m=t)DL_tB2;ic$e zhlmc6(jh!d$WsL#Itu*`-lV#CiJ*ga2^;HuzFh{Ly7hj%@AK|^KR)>OJo$SMXJh8C z5w>@jjJTcbFTQDh`e1LXoW0~eE?sWgcN&AcPUElB&8BZBrRF2oX*pOdMcx{4WdM(|c*P~^<5&5B=;jiij!s7n?EnUZFh3=)TLigbp z>G}9cqHp)*=Ju}iK6@p7#SQ6yRFr|41sR;))Y$y84vnp8d~#Y7tuva8jcc-;(^O%+`BPF^Q*TU8OwgEo!PmfQ>{@##Cq8>xeX{xtrxSPbCOQzeGht_vml@;p zMs@G4`G4?YKc#{jh#y{nBZw!6E05L}#2LgJ#2v&R#395Z#3jTh#3{rp#4W@x#4*G( z#5KgXN9!Eo9pc`j^$%$P(gCCeNDq)EAYDM(fb;=r1kwql6-Y0TW<1(%Ankax{XiOm jbOdP$(i5a9NLP@yAbmj^gR9ob|J+$9Z9B~LCPIGz&tTih literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Ashgabat b/libs/pytz/zoneinfo/Asia/Ashgabat new file mode 100644 index 0000000000000000000000000000000000000000..8d9e03c13ae19703ebe48105d418622e53a14b55 GIT binary patch literal 637 zcmci9Jxjwt9DwnQX|=W;T(mFoCB8;2LJ?aOigXZhsE{Ebh(o4=;3{Mj>{7+Atb+eyZiNJ^=l039Tp?67w1D)x$}*i zR;N1boCYJ=J3EuOEoMI6F0;?CLGHn^^Y@LwYM;pH^`VSi)Mfl^M<$N9#6DP)$-PCH zs#!8!Ny|*3BeT|v%%vX1iKkrW{mY$y`t%o$T7EMOL&NOr&RG1%>>n^qrOItyX|<|i zN_oDKP-@dv>E5xoW9XgkJzTAAd#X@e(W|Uii3cb{kLV;icz;mPFBl?0(cl4yP*f-~ zQMC?5h@wQ1qG(aXC~6criXKIfqDYaXXi`KesuWp@Zd5HyQKm@Krv8Ti*G^XSUszu% GCBFgayMIXl literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Ashkhabad b/libs/pytz/zoneinfo/Asia/Ashkhabad new file mode 100644 index 0000000000000000000000000000000000000000..8d9e03c13ae19703ebe48105d418622e53a14b55 GIT binary patch literal 637 zcmci9Jxjwt9DwnQX|=W;T(mFoCB8;2LJ?aOigXZhsE{Ebh(o4=;3{Mj>{7+Atb+eyZiNJ^=l039Tp?67w1D)x$}*i zR;N1boCYJ=J3EuOEoMI6F0;?CLGHn^^Y@LwYM;pH^`VSi)Mfl^M<$N9#6DP)$-PCH zs#!8!Ny|*3BeT|v%%vX1iKkrW{mY$y`t%o$T7EMOL&NOr&RG1%>>n^qrOItyX|<|i zN_oDKP-@dv>E5xoW9XgkJzTAAd#X@e(W|Uii3cb{kLV;icz;mPFBl?0(cl4yP*f-~ zQMC?5h@wQ1qG(aXC~6criXKIfqDYaXXi`KesuWp@Zd5HyQKm@Krv8Ti*G^XSUszu% GCBFgayMIXl literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Atyrau b/libs/pytz/zoneinfo/Asia/Atyrau new file mode 100644 index 0000000000000000000000000000000000000000..317466d14b4749538893135618839a4c747838b5 GIT binary patch literal 1011 zcmd6lKS-NF96&E>YDh#JN)wyf##$Rq)P5!=#5VrvA{|mVBv2?7Zm>d&lPC%ngc6+G zbSe&Fr9Y6G$z1J?yTHmD)JgD{k zonEg}PLcm3=BOH_hP3i2l{c!6-DCGZHWUE_!-pnr5e7z2}|XRj>NypIlCOhr>Yy9V`-yh8iLyzYv6j zAkU4{1&OjNe~L-{5{tqG&%Ws`Q7t!yd1~-JlTPQYlWIR^9oLjQxBT)yCq7sFTJ*ykU0&t)XDd~UU%kl3W&hNx`{wlY4O2bQ zZ_eEAH8n$NQ=5sJvu>rSYc4VMReR=K$+l?-g-058AISNi3mRSjD~;bev}wW7<`4HI z_HtA%Ouy5M6VKJT^F!jp5pD5@CE@v!j6c=Zn#X#nY)MiD^OD;6t?4aSF0Vb(D@%9v z>ij2h-@lQz7hBpswWb{p!{Ut(>b0R+>CDVXSKF#~$G&J!bx5us-<4iT>trsM3ltRo zyMm!ZTtSiOTl-cd`daLLqa(*e(q5qS;C-eqE1mW$m?hH}+(1 z6QY4%Kt-Nb2Sf=(3q%b>4@40}6GRn67epCE8$=yMA4DNUBSa-cXP#CmLn}lrLoY-z zL^DG*LpMY@L_0%0L_Z?|Mh1`+AUQCS$kS#4NrRCGBoRm^j8qu8K$3xE!$=2`4|%2+`)9WNQ>^{5S<_ZJhc;{KQiuuL&R>B^{)o z@DO?K5Eez9QelTKfeAr}Oo9iZf}}b{V8wdgzYwAh(Xr3?`JO+=U_P&951tqZ$sco@ zJ6tSjE}naC`^H+|y)4a?1#aY5mCpvfuBQp_qU~P!d&&Feqrr-qOL_m)HNAS`g037t ztJjPk(`$!%^}4~7UVkX2H>7KHRlGuPtS`~k{%?9y$p;;9`uYQ(-Tj;26z6K5FXn0= zFXV!67xLQ<&v`;;9(qDga`|x69f@>bmT2WwX$YN^hVQ2(<{we9S3T1B^|;zG9hIH) z9V&jSOEpc_tLBlg+I4fKN(_{#-52X*Px6auIl4kxtLId*<&(5ICsnHInWR3xkaqh6 zX`g*09k0h^@7-}p&)ip?SI*16iR)_rseZ|fo>N_YL#lhQPxWj+qO$2mb-Wa||F>Dr(;MA|Yomq;ujrAt4T?%2$o-(QpN z%t~D_Y_6!eBD{^*tXWU3-?IO3$mTN@IB3dY^DZ1X<>=f`6^mN$R_%GnQTyd~oP0VQYu1poj5 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Bangkok b/libs/pytz/zoneinfo/Asia/Bangkok new file mode 100644 index 0000000000000000000000000000000000000000..7249640294cd596c9445ce175455efa06b5dc6c3 GIT binary patch literal 211 zcmWHE%1kq2zyQoZ5fBCe7@Ma7$XS$?ex&~Y|No3kObiThHXwN*$-=;pRKURD;~T=@ r1jO0~<{$|m2qD3EpsD}EwzVDs(I9I;`s;ygBCX=G0a|OPYt97#+X*B% literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Barnaul b/libs/pytz/zoneinfo/Asia/Barnaul new file mode 100644 index 0000000000000000000000000000000000000000..82cc49c487a34f51dba12ca0e4cf2145bfc7965a GIT binary patch literal 1241 zcmdVZUr19?9Ki9roNmkr3CcffWtlCT=5f_rP3@E}(M^LGLS+!zAA_P1LVqYUw0emc zQ4kS?IC_dWVD%6}hYEU;9>VNFq@-Tzp`s)Rjdi|P20;+?*ty*MIlEjgY`^akdyX6m zT7N8$xx>k7GAH|jp;zbT9t=;HF7Y2vEiFo0h3?^k!XK~ui#|-GiYN1}lCixh?`V%& zc6Yb(-D*|KFGtjhvz4m!s86lzT%=aTGO8@{MXe4@t8(v4wI=_m@;g5Z|IAyl_Wc`K zaq5z+7=I$xJsS~$`)N^`zAmb+T@>pFPm1c^9#Q=~-Y*UumNomX>fniEdP9A$-neV0 z-sJ1hpB!__S$C&c)<2q&4HstR)|(Sj9-NZf2EOX; z(Z{l}dt5h_56NibL%kz+KsJ}%(9NH2>z14|y5+@r-TJOm$L@9O_}FRLb|tD4v2UW| z`$yTA$z&Wk3+4}J?r%q)EBnFi%KPi#bUB@t)jr6NoY-%feX`%-wydtCwJ>|l-f@^a z^Uri8+4D@iEnx*j$edww*76$5VV32vxArCR5A!u&DZ`ABIr9Q$jm(?H%x%rwk=Y~j zXVC!C0n!4}1JVT21=0r62hs@A3DOGE3(^eIjjd@1=?7`Z)^volg!F_og>;3qh4f`> z8bdlmT0?q6nnSun+C%!YH4P#i+L{)T9+4)IE|E5oK9NR|PLWoTUXf;zZjpA8evyWe oj%`iLNYA#WX{2kUZKQ9cainvkb?h@u{13f*>dZf(p{myN3kAm+rT_o{ literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Beirut b/libs/pytz/zoneinfo/Asia/Beirut new file mode 100644 index 0000000000000000000000000000000000000000..efb24c27f83894eb39286eeb571eb064ca27fb5f GIT binary patch literal 2166 zcmdVae@xVM9LMpmB8n(#wPqnC280239vD9|k&-SnIenWal8Jm2PlALJg%mQJX}M*t z?403juGEXlF(ks8xms&YUBi!C^pC@RR;_M|K$F&*x%xc6{n=mrSD)K`AK&lYU$@=s z8C+evG1vLWiLsyX=DcR#+$Zd`mv=+e=2+`4IlgqKI#JxCBk39Dy^J>9asLg|ajjdT znR`rh)FW3XeX6eZD}8Nn&f z(h5}E^(#6)w$8*~yj>>!JZvVPIwT3_Q`M9M7xdKkW7V|AlR9zKYi3+JqLcd9tK@Di z_w*h#Gs7`D<_an!*`t zq;MeLJa}_~%sYFRdFXPA%#Y-$qBCP47=)bH=s=wEdrk_>* zlCSh*u}4*DYDAX~oiU4J_UgrbZ=1(|sMAY2+f3OvuSj73I zHr;Nv54|HTU9G0I?`U%@5FH}(Yo#UlQ+r@Z&ePmpCG>p&KQtOQvKvKC}9u68xZa$N0tkOjHg6(LJP z)`TnySrxJ@WL^9Y7KW@0SsJo7WO1%`b;$Bu?fQ@fA}d6eh^!G=B(h3mnaDbkg(53O zmWr$uSuCf&k( zgH#474N@DVI7oGn@*wp=3WQV$DG^d5q)14WTy2?8Wq-;ptTy5cy$|0pgYKIgLsUA{3q<%;NkqROuL~4i>5vd|lMx>6e zBd6VGD#BDNU9Lxd?y9z1obJSa-g zAv{Kh1&}8=0dOUs-~9{ zs`;s_S{_|eNALA2YcisajW(&)ky>@U?}s`O*-|G%pVg^DA5}P5N`-eeQ>Wi9sxw~` zcH8^sR%Fez+n>!_9ZN;Kb2e*r%@u5W?6P%s{GA<5Pvp7>9(p}jujgaUcf5EsnU9y! zUNUseNfx@ibH#+y`{uCMw{AN9OP%h(tD-YFXSnB|6r9xU@9u?}-Tct-CwHj0U`?0H zWf?f|>kJ)nctr$-xHTp9=gHg@HGB8HC(?f+Yev!;5wTL&l34z4meS#oSkTAYF4iX57goHw3A;FMnNH`=O5)g@qghXN@ zL6N9PSR}5m4va)bLL;$};7D{NJQ5!n05Sq(2*?gp3Fo5;7)aP`-LpIIY|K7sIOV*K-@}h*$pvT$S04 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Brunei b/libs/pytz/zoneinfo/Asia/Brunei new file mode 100644 index 0000000000000000000000000000000000000000..8624c7ae182ca0bd5be20dcb0d9556f73bbe7d56 GIT binary patch literal 215 zcmWHE%1kq2zyQoZ5fBCe7@Kcx7n94bboY+>|Ns9pGBGhQWbXmVXI3zU}aNehq^5QLCm0npt4AWJ|r$Rd#bdLWx<%eZWS7Tf7sZ~*`+LnY$? literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Calcutta b/libs/pytz/zoneinfo/Asia/Calcutta new file mode 100644 index 0000000000000000000000000000000000000000..e1cfcb8d09dc8e16f828082396f5095a367881e7 GIT binary patch literal 303 zcmWHE%1kq2zyK^j5fBCeHXsJEg&KfF``kUdPTlU&IKx^fab~K~ic2LzGZ>kefslbA z=mt;$gSQ(EcI*|wL XL;;W!J#adcfq@I?c3o2|V*@S#`D;p@ literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Chita b/libs/pytz/zoneinfo/Asia/Chita new file mode 100644 index 0000000000000000000000000000000000000000..3baf7528268ac85f44ea7672ee0827901a302740 GIT binary patch literal 1243 zcmdVZO-Pe*9KiAa+I&GJewWf(=1SMHsp%@&(rL9!bd#b+Q4$EfP!=(ws30=v^)TXh zD1zu<^adeadO4W46e>ihJXr_94s|h!2qa?tzfTQDojUe^c%IL*{~iY0@4Mu#gZo3~ zuT^Y!I9V}!axb^u&GtXJJ!0kswx#BmU79Q{yf{-j`>dzz(_m`B#PsCC(bklAD4~l6 zqFR2lP8X*uwBod=@32dkbbryM@hPor8rQ1ei2A)xby@L!4S3#4;Onp~|M1*bee#g6 zdi0F(B*qJ(Bh3Ug(ClYZBY@KsWkN zNLwteoAP!_d*u;rpFXY~?k(CewnICoLK=S<(ZuLx>AK?8Wc;Ps{%y?Hm&s&Y?m52> zPhQTEUy$=6_l-aA3k%$CV|sh+&uUU~A@RLY=dJ)N5KwmbrOdgjFQD3sB?VZOoEGUzT@~D2Z3$g3Z`R583(EH9!0;cU8z&ms6f+D(u>^!arWwk0%0HhpZ+G)*z2EOUoUWn2znpG;Ifba) z;iIf5;Sxd_;;;~Frtk_;Z#qVJcIao`FBMB7nz*R%d3|3J(PV-j{&q_L@a^^A?eqQl zISr5wNDHI~(gf*(v_bkHjgU@AE2J0F4C#inL;4{N2kMSUOQa{#6zPhzMfxI*k5jBV`Xd`ac7SXF*#oi(WEaRbkbNK NG@Q{p%f;r0e*s7H(fa@Z literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Chongqing b/libs/pytz/zoneinfo/Asia/Chongqing new file mode 100644 index 0000000000000000000000000000000000000000..ce9e00a5db7c447fde613c59b214e8070f30ba2f GIT binary patch literal 545 zcmbu*KQBX37{~Fa>ThGXu?mi&+QDKF36auDBdLYEFw~^d?V4RTCt+f_yM-6v4M?P` zrdx~ZyEy4);`!cH4B|AWpQd-YzpsDXsISV8lh%K@oN2xMp0xV)a#XXeiO-1=Gd?(Kzr-Fb9xyIFa!HeGMCDIcTtpg%LP{q4}rJ{_33#$9Zp>-+h=%Q$=X zU=|7|@nYr5EKP-8Zu!*Y1~o4~Rx$Zb(Hlzr`Vl$r>6=Itr-nrWE92FDUrJ@YhdvMV z_ThGXu?mi&+QDKF36auDBdLYEFw~^d?V4RTCt+f_yM-6v4M?P` zrdx~ZyEy4);`!cH4B|AWpQd-YzpsDXsISV8lh%K@oN2xMp0xV)a#XXeiO-1=Gd?(Kzr-Fb9xyIFa!HeGMCDIcTtpg%LP{q4}rJ{_33#$9Zp>-+h=%Q$=X zU=|7|@nYr5EKP-8Zu!*Y1~o4~Rx$Zb(Hlzr`Vl$r>6=Itr-nrWE92FDUrJ@YhdvMV z_%_aH+Jx!f+XbhEXAdf{Xnt zjr#xp|1&Z%voNu;F)=YPc-;Z2XYhK$z{0@b8v!yRB!Q8Sfgw7BQ3ymb0LhR92A~K? z*2g!5!54_N4NQ#w#)$?kO%Cu($1WO|6U#xB$>~PwoH! literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Dacca b/libs/pytz/zoneinfo/Asia/Dacca new file mode 100644 index 0000000000000000000000000000000000000000..776f27dac064a5925a2ca5c3cd4c3c60b0e4af37 GIT binary patch literal 361 zcmWHE%1kq2zyNGO5fBCe4j=}xWg39QsoQNE&af6roSAC0;_iw|GrX*i2zcAvm{9-! z|9?g%W+oO^78VAEkRw2)3_%wdfb8fDkO{sK417TG1O^cXhNJ>U2_N4O1`i}q$?i=q$0+{1+IVv2o@4y7yAVl$Aij`CKx>?zg1nZXG!*i-XjL`e8&M(CmvGxYZfJ1lso z2s`gFrdT4^o$>3MnA#d#|J>};8EG(jbH zSLvj#0?V_vT_(4;sg#EIbZV7QUYVws=3G>1sgKL_s83Z!WS3qR_^P#RcgkyL(PLq?mi?R_V=sex;?Ta z?RDiZ?$xEUwyQPXU3$%EjVg=WsLQ_Vw91Elx}v|@s{GQU*B;2T)*YRx*LTEPRb5F^ ztqZZL+h)i|vVXQ}Dz3;!7f)4>Wqu_$1Yc6MbC1i~p|h$k=nGwU>K(Q5hhw_FXPE9U)sn_JnK-*%h)aWM9a}kewl0L-vMj4%r>DJ!F5#23?&UB3pEI z_K0i}*(I_~WS{76loxi&Ia}q}E3#Q+x5##p{URIY*fFwYS7*=2rd^#~BirWKH?nbL z=g8JM_Ks|xWB17RIrfh^gZq#H;(kbWQy zK{|r81nCLV6r?LiTadmWjX^qtw8qux4bmK>J4kzw{vZuPI)tp}Lpq1F4(Xk%(>$bmu1@=q z{vi!SI*7Co=^@fYq>D%!kv<}gL^_GI66qzU2_N4O1`iAKwrLZ37D+wggE4K?n&J0nPmnvJ6CnEClHXSqh>-7K1=NP>yKJ Nxom(AvD3BW0swcSF2Mi* literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Dubai b/libs/pytz/zoneinfo/Asia/Dubai new file mode 100644 index 0000000000000000000000000000000000000000..7880d5d7c92a4a5fd994630588e9d74fbd636edb GIT binary patch literal 173 zcmWHE%1kq2zyM4@5fBCe7@K3^rY5J30zG$ATnqsf`5@$-a32tpK8h%tyzTB0QOm#4^maGn0UT9m)plfuQe44kgY;L)-S?axVZ$Cu&tf{avp zWOV*h#{8y?=O1OF>s~zjBk~ xfe3;~f{231f(V01gNTF3g9wC3gouR5ga}QjQXyhtU1$F{xtRr3zc^LSd;ylKdI5|YK$W8Ab<;+SwjLrfF`|J?@? zbB?*M#@1ZAE`fk>4WB&$86P_ur_n98msd@+9Mx0pL*`@echd% zdmsJYpXkbUu>#|<>v{JMZ?2v0oA1eAew-d0K5)$O_wTop{;qLGe0Rwl*0; z-5#){d8wVecF20M(lN(GCOBim7tC>)=dJN$y3Le?BUZ}24)diupI8%qs5f8!zRn7K z({868-Q}bmjM?d(wNA$NH|&WG<<6wl+4d_HSx#p02zzoc$(a%uw5KKwIMWi7%xU-i z*7TnT%&hBot?Y}}%o!*9tYG&EGpFmgmD~QQnYU%X6{=}A^Q(HDg2+BQT%yj*oXvJ& zT9Y%YpjBoK#br*QOXu{jk)nTEbndsM^6JfZwYaBROU};Kc^wOM{^zOsS}ao+v=_<3 zMTuHk6Oh-Z{HkT8L$WCGik4^IlJdb{WO2fIS=@J7-n?@}-s=5YDz2W>$lgv_a`dn+ zZEO^?bC)h#u}9x-sMX5Z4H~T|*LPBu=<;BeE`Jcx6?WWy@BVaIdHPRXb;B>Kzetv< zlXvC)_&ura>JzK_hOF7~yM8eLjI6D?sx=u0q_(72V=tW2x-`|g-#*j&p(b5-xlPyi zNA<(6R%yefO|oHki8l6B%EtN({ivf*HZ3cbrdWz>&QF&uWq(O?vLP*bS~=NS$a5|Nryho$Qb?^5LC|NYp4RtK=hU%m{_~mCtAR9ua+tqyDdZaoqU}jy!Vg zk)w|se zBrhZ}Br_y6BsWhtIV3wIJtRLQK_o*YMI=WgNhC`oO(ahwQ6y6&RU}tWH(4ZGPd8m8 zUnF59VncO-cvdrvogB!5qL0+1O%rU01(WD<~BK&Ani z2V^3UnLwrjnG0kxklFBbrvsS}WI~V`L8b(m6J%15SwW@+nHOYYkeNZI2ALaVa*)|U zrpMErA7p}%8A7HAnImM9kXb^e37IEkqL7(FrV5!WWU`RiLZ-{poiAjXrVN=g oWYUmXL#7RxH++UB&hIW?P5~PjjD&Lwb3=LIU?e}}eVgh353H8ZHvj+t literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Gaza b/libs/pytz/zoneinfo/Asia/Gaza new file mode 100644 index 0000000000000000000000000000000000000000..cf54deb8f1d44eeb4281a3500415fdb1e4c7ff46 GIT binary patch literal 2286 zcmdtiZA?{l0LSrjeISKVBeOY22#F}ly{8ws0<;F@?hYU{@>%vmYZ)oXXk$YyLb1^ z-Ti(2gKz`g7WHtR8&EIb6?`HuT>VnNc;cS+<2R#bu(?v2owepq`-SX# z_j}_~MOWbRHfi>IHjDmIr#=vNP7GWT=8eHgYp{EVHWX~M{yK6?8}2$|Shb4+H(Rb* zw+aiik=^r+(Upe+V;kCx@u-NvZBL(ddn!kpm=|J9{CQrRoLFJqS)8rUsy`@a-R&}G zj}M5@j$||Pv%lq22X2~C7pBAlaa50f`?QGhb(>F@d@L767wfTwZm~$q)Z@a2WZci! z%*8i?a>*Cd_4vz!Vrl0BbJ?dAGU0&3bRJtJU8SA+^168GUj4P{zW#_zEYB55S-m3J zyF{eKw2Rc(P~mYj3D3b1`OK4LA}z$Lr%g4;6~n@O_K#AzGMH_qf1V|uI~8kYv@MjG z%^~LVjWcCd(OWXR#XHC$_m=%X!G@fkyu?0d&3Z9ODKNtIDnzf-=N;WIWD>9Ra(nNg9R zBrEQP8{$g1CmjV*V^<<@V*tkWQ^Qb zN8EpBZJuAt z$yv{fU-Nm@qt5o_xR3uKRy3uqhMqND$eNHvA*(``g{%u%7_u^CX~^1;#UZP+Rm(%x zXR8*7tPoiuvPNW)$SRR#BI`sJimVh_Dza8&vB+v|)pC*b+NuR3D@K;gFUOi~)uNGA zBg;nCjVv5lIkI$Q?a1Ph)g#MC){hhbser920a62`2uKx>G9Yz83V~DtDFspsq!_lU z8b~>idLRWsDuR>*sfn#B3Q`rMEVimHNMVr5Af-WSgA@m;4pJVZK1hL(3LzyzYJ?OC zsgkWK6H+InP)MbaQX#cMiiK1QDHl>Nq+m$Jkdh%aLyCq}4Jn(gsvA-`TU9xvbV%)x z;vv;T%7@esDIiioq=ZNfks=~hM9OHZ>WCE5R#g%yB~nYIm`F8|aw7Fa3W`(|DJfD@ zq^L+$k+LFnMG9-HDvOlXR@D|ME>c~jyhweK0wWd1|8I#yRid3q#5y4_*_Gn*EKf|x LOLV(ZIfecMp5^oY literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Harbin b/libs/pytz/zoneinfo/Asia/Harbin new file mode 100644 index 0000000000000000000000000000000000000000..ce9e00a5db7c447fde613c59b214e8070f30ba2f GIT binary patch literal 545 zcmbu*KQBX37{~Fa>ThGXu?mi&+QDKF36auDBdLYEFw~^d?V4RTCt+f_yM-6v4M?P` zrdx~ZyEy4);`!cH4B|AWpQd-YzpsDXsISV8lh%K@oN2xMp0xV)a#XXeiO-1=Gd?(Kzr-Fb9xyIFa!HeGMCDIcTtpg%LP{q4}rJ{_33#$9Zp>-+h=%Q$=X zU=|7|@nYr5EKP-8Zu!*Y1~o4~Rx$Zb(Hlzr`Vl$r>6=Itr-nrWE92FDUrJ@YhdvMV z_Bet)(T-xo`_79Ch_Pq72 zb+N3Y;nEgq_qjKUzL6-iKj4h$zbx$Q0~5|b=Qd-|SMU6B_@*(`aoTdK7dG5DaLu`y zpJxp3oM(-!*xxX^uEiP)p4D*6-Q(Pv%rM61`dQ<@pED-Lmpiu?rJFNq_sJP|I_#Na z{ldR3-VXZYPx<)X8+P#dNikm>F+<)sB|@`0?I()fmkUA)&9M9!vCv30!vh9o_>b4@ zMb~|D@n=)bh)V-vN&9?z>De+Fxz}Y!9bGA-i`&g*H4!pq)t7cm?*lTnG*iT-b&Gh< zVv!KqDiXu|h1=C6-1~;*laH2&BtMUtG+8H?4+;CJ--_i5U%H+AX_|ccWSE`OvOuOb z``ORbPnT&0ugmnJUioa=$BrkeO=LWL$IN{2XPMcz!&*5oSFSqiXJ&mjAy*$y7SDZf zMXo97va{bEKbDi&ZRQjN$ehM2=JS=m2y?>$`GU7gyy!V@u1#nW-mIH-t+M1eS#~$jst9^SR{T0-ZMr=sH-8=AynJ=G*wQxWZ2e+XY-{p4+gpDUJIYTv zR^u7*O6Ec5)zYJ4XZS9s(o-jPJ@k&XJL-tsJydB`1=h$aU#?X>SSVjRnPk;mNR_qC z^Q^iLL*D>4K{(tz?FhY9OKipUn3qHL^VFQD=R*?&19xdzw^NUDxWzkX<3$LiUAh4A~j7HDqtd=8)YX z+e7xJRU1Ths8w4;_K0i}*(I_~WS_`Jk)0x2MfQqp7TGPbU1YynwP9q(TD4_l&&a0v z<=C}WZ5!D)vTC0*0_gLt=lt?DMyPNbhmLy?Xm zEk$~YG!^M8(pIFeNMn)CBCSPwi!@iOx{I_|tNM#H80j$5Vx-4NlaVgt|GCZnD%w$T XE3T9Eb7Q*JhWJHgVxVyk!z8jf<%SZAD7A)q_o%R45M8rRWK#O+_RUA`%G+ ziG$OxhzJMc;;1h|yw+N@-ny%$>ZM&>T5F!agM*Ven9b~Gvd8^C@utM~rRt9pa=&mn zYux2sawyT1>mF*fJ?Z6gE4IJ=e%-dV2Rp8rhbL;~QT1ihd-}ROURh;Ri|0!!Ut!bJ z!jgV6%RU``E6?nteSW(~`p(TX{TDat7Y8QH%ah&uRYStO-g`m6SrRh?&7G2&US_hZ zIwU*3&JNB#B7><#cBrsR-q~XNzP~|+PmS0QU9Ea#-#zkMBF}a#i{dGaojL+Dr^Pw#!Kek$b8>lwl`%pZH&_S8#^~krk08ku{M; zkyVjpk#&)Uk(H69J>A;K;+}4GWO-zLqyVG>qy(e}qzI%6qzt4Eq!6SMq!dqA3sQ`y zs|G0tsRt;AI72svI|v5D z87#yi!DJ&ABL-q*lhplpHCcSk&97f-Gs`!49fh|fN~#rWWipcDqKsFo+XDWns|}LL(i(D<5re>Z&X>{i7a;= ztBRBxQSo_gRK6UGs)r+^`g%ur&$f-4!$skXEf}@Ykf>W7HtJ{UM8o8qY77m@rof|vD=9JajAJKiyy;gr|j~;MG zJmIVpJ({{KN56LU*z1VgjK|}S8 z7;+)W7_uSh81f+r88RX%8FC^?k*r8sBrlQ}$&92%awEx+>_~bfKXL-d86c;CoC9(a a$XOt#kihx3y}KI# literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Irkutsk b/libs/pytz/zoneinfo/Asia/Irkutsk new file mode 100644 index 0000000000000000000000000000000000000000..e8c53c0d63d4ab9cc7b7c95597e4cb490e782cce GIT binary patch literal 1267 zcmdVZUr19?9Ki9j+-h0i5KU`pr_9z`t2N7}&Ofzmtht9`M2rlqEQB6LL>NepOnQmv zA$yP>eq_EyPZ}XY2tE`@kVb_B{e9^r3?hiKb-s5DMm_b|y`1y8_iS9)J>Ml$y{Cif zuVb}YVQ@su;JPehraPr2k0wUvUFDwc%$myK#R})Js`&mSUHR#8X6?ey#dWiZjC(32 zRrlgjeLE~Q*Zi`+zee=g64`J}$;S8>scn8Qn|$+9=bn+xt0u%_f2TcP=k%73&jQ}Q zivjQRalLhBO#8-f>1}tfYX8kXz5PnB-f=#q1E=D8XV(P@o;xJYg@ll-aJ-iL`Pmb?Ju9yy|pDx$<*<#b#FW-(frQ>&n%c zNUE3_zQ7(c8qEmtSfy;aBX^UVlmD>Y=2vEqJhfXk}8rbk}Q%fk}i@jk}#4nk}{Grk~EUFrAZsf h+tMVCWR9ecLBrQNyn9`-!0NpX%Sf56``B z&&K{Z?(Pq;bl>gIhYsa~u-6P;bzd1lw;v-Xiq8=a#&Up9Iglja*W zr|jOMu=8fP&F+gt%)W1K*>C-_+d2N>Vq>7~hB=V#x6iMfFh3be8iRu;%nMihjnALH z?p)j!G%g*fG>2=w#^rd9^ToWK_Ls#soh!i&_K5eOIdZMW{_5s2bM#`fF*aZ}N4=x9T-JW9wH}34nH}zdRjJu!eOx@E^q1{{eVk)<^3Z#557P8Wi(}e--mTx-N=Ojfn+4pNfTh+U25-x5Wd`c8ijh{bKQk z_hsob2W8pPL$ZAJ3-ZC~Nm)_)gsiyrq>Sc2FQVhiW##00vE+lf^5M}cQPo>3mcBPb zRPQPhHC>umwmBdk=_rto#;%Fljlap|MS~(%>&RGVsk6d=-l{A7TCcnDonG(j*XxG{ z^p)Qp)mNS9)8iM;Sq=Nft;XYrt;bqhbz^V4_4vA1tkv5$S!<$a^+deRTASBsHB}$7 zntm>^)_u0fXiKNl9-sHWTp9j9FRw2%@J}z_l;CZb->+%;5%rDK^0#Oinl``0Gey%1 zW@$N^7G37Kiziy{-=F{WZ}@GzA)(c)I~H5ROF}CyDOYzH|5Y82I)A)#f6x;DVk_z+ zN;kbba0S^6vKv>m9b`YqhL9a0TSE4PYzo7Lh$7n?!brY!lh1tJ)~CQ)H{iUXjfryG6E(>=)e)^FgZ}16xM+jBFa&HL`7F z-^j*Y)y_F=-Bs-!**vm)SG9d)|40Ln4j?T+dVn;6Ll=-XAbmg@fph|Cg{$fX(hQ^< zNIQ^zAPqq}g0uwb3DOj#D@a?AzPPH!Ae}*4gY*Vz4$>W@JxG6$1|c0nT7>inX%f;U zq)kYlTvelxPPwX9A-zJHg>(yP7t$}JVMxc2mLWYunuc@@X&cfvSJgPAb4cr4Rqv4I zxvK6V?L+#9G!W?^(n6$%NE4ARB5g$ah%^%EB+^P()k~zA*rvYg|Hp1-RjH;{FD%RY E9a%VMFFT3M6xh2q$_|-$gE)CV_>M8z#ziFP|(02!N8DIz#!w}8^YiO z#M%btMg}0#7(`kEu?0i8rxVB!BoIP^TmJ)reVS(ihz7YCM1$N7qCsv4(IEGOXpkR3 SG{_$ynjU`P0(wu^oC^Rmt6tjx literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Jayapura b/libs/pytz/zoneinfo/Asia/Jayapura new file mode 100644 index 0000000000000000000000000000000000000000..a4c0829721cf845e1bf08332503e1fe742dca5fa GIT binary patch literal 237 zcmWHE%1kq2zyK^j5fBCeW*`Q!g?5P@oN*yy=Z7y_7iBOq0fn1ifaL2YfTUYzFmQm` ud_KM*4B7^kAZ%>F5bhZQQUnGeBv|$z2tXEsXpp5Knrw@?fUeNBR literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Jerusalem b/libs/pytz/zoneinfo/Asia/Jerusalem new file mode 100644 index 0000000000000000000000000000000000000000..2d14c9998e9dc54bd1ad4710332abafc8e811242 GIT binary patch literal 2256 zcmdtie@s&GJH>!w1ZFBdCNWLOC;MTB z#pblu{JCXp^uwWITXT`M>1-o7Da%yMryn3oaK|MmZc5Mlv{hSw^;cWpyK`S>_fN*> z9V}U1l`8&m;pP_}uF*WaM=SHS+n4>uiNyzXd(W4FZ$7>yI*wnpI~%LC-Mr5J_Ek%t zeDiC0s1Z6C4XtoH)(8xL5M^A6khle*V~?zRH%is-e_6g%`mp2u={`?{5Hay$tPmM&oQrJyh<^39Vs-#o==UjBZ; zf3ckrbD>Yax`Av6*%7iOWKYPZkX<3$LiUAh4A~j7HDqtDW^>5yknJJ+b2S@8c8F{d z*(0(^WS7V`k$oZ?MRtm871=AYS!B1!c3sVW`TMb9SF>Ye%gCOQO(VNTwvFr?**LOu zWb4S@k3_K!3G=>XCKqz6b7kS-u?K>C0*0_g zg>(vO71ArDSxC2#b|L*j8isVt)wB%hnX73U(lw-QNZ*jgadZx89Y^nw=5cfnX&*=b zkOp#e5NRROLs!#8q>D%!kv<}gL^_GI66qzS~(G(N$N| wR*t?RjpgVp(psdqNOST3+TBP~3uw9sorcEF2hE7#Qq57&v@< xLm0FTOhDM!03-zjAtYD;H1~gI+pGm38e|bje?5>*v}Ig2V5{wPO{|O!xB#TGCZ+%Y literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Kamchatka b/libs/pytz/zoneinfo/Asia/Kamchatka new file mode 100644 index 0000000000000000000000000000000000000000..b9ed49caca4bd81731958cc54adaa45bf84ee2f0 GIT binary patch literal 1184 zcmd7QOK1~O6oBDLn`*3rqSU^u_F)@iOifZP)wb!>ww9VyP_j}32VdZ0A>u+PQbh$p z1%)CSMHjw;h-6^}B|;ZLToma-5URofi{M7G&_z{<_n%ORxN_$mZobLPFp!)-zU$Dz zu=UqkV6L!Py=HU1%MSypx9Auca~iT$(x`$!h;iX|BB_EelVH*PT-Dw;Q@<=6cR| z($4wbj_BI#QT0Df=(>B`H89qrt>=Qe{zQWY_b=5@A|m1A)za2oFB^9Jl#O*I(jNLK z?Z0NEqh?k+KD?4mKi=!+OiDVZUuxI=taiV+qmlCuv?qB+?E@FI_v~%iGH_V?_MMjg z=9muj?Uk)%tvcA$FN0sBGE~|kLvI4I?UP5MPZmpTGMpQ}oIf_2&*xpGC56LXR&>mp zU-YH;#-HcrF}G!nW%yTY#)n){r);xbnX&u}+39JlMW%;jAs K@<<>QF8>WumKRU} literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Karachi b/libs/pytz/zoneinfo/Asia/Karachi new file mode 100644 index 0000000000000000000000000000000000000000..337e1d58620bad1d24a1e273c34d75e4071c1bdc GIT binary patch literal 403 zcmWHE%1kq2zyNGO5fBCeZXgD+WjpKsEIBjPXvL)xp&55qJXpcxzuLfNz3T?s`5Fs6 z92XdPa=S0^d~(;o>rJ%+BNHPtD+?GhFxcGznd2M5z{0=~ox#Y(z~C0ZAi%&7lE5ee zW=s0`hA?Owm>L@ZNi#5M$`Ifk90DXlKziXIgar5g2Lh0rK{UwSAR6R$5Djua$WV|U iKs3l7AR6Qs5DoGVhz9uyOauJ|qG{H(3?U?# X1~lkD$V8An{HAi*0L{14HRA#R1VtLz literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Kathmandu b/libs/pytz/zoneinfo/Asia/Kathmandu new file mode 100644 index 0000000000000000000000000000000000000000..2f810b1202a0edf2f9d0963cdc5004246369eeb8 GIT binary patch literal 224 zcmWHE%1kq2zyQoZ5fBCe7+YZBr`i@d34;~&|NsAIWMX1q@c#pn_l;m+VPFWj!@%X^ v8^WM%U}|gtB27#|l0Xnbf)zlM|AVXn=>u5>qDi!l%LZ(@ovx{si76KV$jT&Z literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Katmandu b/libs/pytz/zoneinfo/Asia/Katmandu new file mode 100644 index 0000000000000000000000000000000000000000..2f810b1202a0edf2f9d0963cdc5004246369eeb8 GIT binary patch literal 224 zcmWHE%1kq2zyQoZ5fBCe7+YZBr`i@d34;~&|NsAIWMX1q@c#pn_l;m+VPFWj!@%X^ v8^WM%U}|gtB27#|l0Xnbf)zlM|AVXn=>u5>qDi!l%LZ(@ovx{si76KV$jT&Z literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Khandyga b/libs/pytz/zoneinfo/Asia/Khandyga new file mode 100644 index 0000000000000000000000000000000000000000..2b2f5bfae4bfdbe898e256cb2d148b8bd17bdcc2 GIT binary patch literal 1297 zcmdVZPe_wt9KiAC)v0r4JcmqcnJc%JO>J8xTRN9clermDJ0*e8Un^onhk|e~^v4bo z&mjoJLs8a6!b5eK-qJ%MLFLIh2<}h>CekG$*7JQu7Z13kyofteD z6n{;nUg5=b=@-YF9(~Gvd--@)RC$l5H`fd=R@?8aRR5eBsQH>rZ&_Gg+&ULcJExOs z+eAdwjy0?8BXz3ohE%RghT74;s&*!pRDJ8ba{FeL$N5U_s(7ZnwvW>LV@B@Iy>>NR zyXb0|e=7IPJdnN@cV**~>$2(o1=)P7U+%q{l>XBZ88~@f1+T=^zDTdyf6Su})HbS? zV3}(9y-|gnZW;Q#qQbvQ<-zF>s&(OuY#aYBBiXmI{q}nq9i5QT)m-|}nQ_r^Gi!Es z4~y8TF|*4vD7s@q=HZfr=&2ttdzR0c@#0Q1{w{9zF1gLbbDx=<>kxhSD$OI8i;kue zZ$u`a&l|->Yn!d4U@I*vI4OLi#OAP<7j6~hHk%NcD*oiODfIEkL&Gk_kP;i#_v48;w_4#f|}5XBM262%k6lvVADVvFL-sy0S( zMzKcmMlnZmN3looM=?loNU=!qNHIxqX;s^#__V5xQk+t(QoK^kQruGPQv6!ghAEC2 zEK@u)n5MX9uubvJVBD&9PO)xPd#9MExTn}>@K1RLD9-_t(I)=)XTi~?pO^Ngu;UMw Cz6`_w literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Kolkata b/libs/pytz/zoneinfo/Asia/Kolkata new file mode 100644 index 0000000000000000000000000000000000000000..e1cfcb8d09dc8e16f828082396f5095a367881e7 GIT binary patch literal 303 zcmWHE%1kq2zyK^j5fBCeHXsJEg&KfF``kUdPTlU&IKx^fab~K~ic2LzGZ>kefslbA z=mt;$gSQ(EcI*|wL XL;;W!J#adcfq@I?c3o2|V*@S#`D;p@ literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Krasnoyarsk b/libs/pytz/zoneinfo/Asia/Krasnoyarsk new file mode 100644 index 0000000000000000000000000000000000000000..59efd24ca55cea04557fd8792f5e6ecd10b3d596 GIT binary patch literal 1229 zcmdVZPe_wt9Ki8sIcvBjUP^1NmS#(*^=s9%rZzP*opy*t*dL1ih>?)=hdKlbSwTcB zs!MbUM|6nzMRf^J712QoBI+Pfk_8>Ih%OSbp6@#bqfQ-rUf%cf?A^;?`+b+(ey}%U z{#ssphm+M}Pxd7@re}YDFftLG6FQulTb4GZo{^H$={G}VpI@iujeE`fu^lP@XrC^) z-J^l)ox1QsOc$N1(O`c-7w;_A@@!%eR=KKvw2XTHe#YtJNp;Dt1wp467MyVAPvscxve zEN!i~bYtOhX|Fi1?NgVuqo7|qo*&iD51Tb{XRCIN9g^;gb(&0k3=U>88CSuK?BOoV zJ7&(xdy)UfpL>tnZOpb}e%j<7WA_6?E{`#L2aGRw&E0X?JGp0!eI|F`_`152CM;2V z*4wj=4>PXpa`xFv@*k#azgUI|BU9!Rm^3nN787^0Q%5F`OrJ#oNCijmR)~~{)QA*`REd;{)QJ>|REm^})QS{~REw00)Qc4CXe&lacCzo=F)-9kV31&7 zC}?1i@$n5|2nJ$p19K2IG60drAkq?uEkH)0fDjTK4s_T5It8=#l%bS=05RcBzr literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Kuching b/libs/pytz/zoneinfo/Asia/Kuching new file mode 100644 index 0000000000000000000000000000000000000000..4878622dde6ae385b1faf198c0931fa5a6e2248e GIT binary patch literal 507 zcmWHE%1kq2zyNGO5fBCeVIT&vCDwMaP1u$0-m$-0y41Z=YZ?w-T@Y~C?g7K$ zZI>?`RS<4CI)gpnIHNnm@k*-;Cmx?+I2pbF!l@(w8&2DN4mdqAg5i=!?uYvS|Nk>G zGoe5hW@ZM4^a7B5nH3By3=E|Pj9d&11q}=W3=DM>7(^g!AKwrLZ3A;-10ZQ(1SWwj zOOQ4=2qD2sK$rXnc?(2?yau8{-UHJ>FM?>0H^DT}s~{TWT`&#wGKdCw8%zVe4x&Nc x2h+eH0MVde0MVeJ0MVe}0MVcz0n@->0nwnK0ePYx$fjZNaM=KZ&Q8~Y3jpCF|9Ki86?!R0$N(#IcMKQ62TM&(h&{S9=&A^4ADD4s%RzvjvuoMvliFDFK z6mKuDA_;lvM)@R+b_3=yBx?b!)X?Gvb+Aqs*s-Dd^?B2sw z>Q!m2N)%VBM0VOv7WCN3M4$aSbKmZh?e?4ai0Z$ZR0H?!s=?C_)!UX<^{!@>dVlnU z`cRywhU!}E)clM}m$o_S>?S$9 z^LpE?{6|gZ!iyzV;lW17@Z{)iczu7^hX1d(5c>}C&FxddD@4;3W1bMP3nHJpvEbKp z&Q;Y>QBfNep*3L<3YUnA$|x@i!*J2J=s%2hPA|rbjM!C=85uP)Ze--h*pbmA<3|!e zGC)#5a&Xm2AX&KTG>|-yM3797RFGVdWRPr-bdY?IgpiDol#rZUby7%Ht~xCwFC;M} zGbA-6HzYYEJ0v|MKO{jULnK8cMF|9Ki86?!R0$N(#IcMKQ62TM&(h&{S9=&A^4ADD4s%RzvjvuoMvliFDFK z6mKuDA_;lvM)@R+b_3=yBx?b!)X?Gvb+Aqs*s-Dd^?B2sw z>Q!m2N)%VBM0VOv7WCN3M4$aSbKmZh?e?4ai0Z$ZR0H?!s=?C_)!UX<^{!@>dVlnU z`cRywhU!}E)clM}m$o_S>?S$9 z^LpE?{6|gZ!iyzV;lW17@Z{)iczu7^hX1d(5c>}C&FxddD@4;3W1bMP3nHJpvEbKp z&Q;Y>QBfNep*3L<3YUnA$|x@i!*J2J=s%2hPA|rbjM!C=85uP)Ze--h*pbmA<3|!e zGC)#5a&Xm2AX&KTG>|-yM3797RFGVdWRPr-bdY?IgpiDol#rZUby7%Ht~xCwFC;M} zGbA-6HzYYEJ0v|MKO{jULnK8cMClhrFFjD>H5ZOp}}?GeZ!CYy^YKF(oKu=h4pjZw0kxn z8}cpU8DA?KNA0rd%8E2+zevmR53)J&M!b=ivc>;cTHQI>T60r;jv4J+nbg}pPpY=z zBdTrThTi`Avi47B^^S)pbo-r@?zk4yotHXw;7qd)o=i&UqEB}AbV%3W8rkKklW_2> zgnxaPZnsl+e_W6~KbQ61*%^r}zSYr3^SbBNQysfLt@q__>)x|>_5Q1QInXz(Ki0a?e$5`Ci_!nVs@8$~;rvSGCDh zM)`vQGec$u`7mWG4QpMxW&Yua%{MD@%*avm2^=?a559FiT9 z9+ID>Nf61<(xiywh$M+*iKL0-i6n|-ilmCO>izJL>Y-v(Pa<(){BUvM9 dBY7iV(rBvJIEQ-W4!1F$3lDKY$bgNg%tR zfq{d8p>6^L511|B;~T=@3&h$67C>yt5bhb`2vQ9NAtYG-9|%AW0MQ^vfM}3IKr~ex L!v%7jt_2qW<)b^z literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Manila b/libs/pytz/zoneinfo/Asia/Manila new file mode 100644 index 0000000000000000000000000000000000000000..2c9220c99b03e6aec1803b6c3f5683af1fd1c8ed GIT binary patch literal 350 zcmWHE%1kq2zyPd35fBCeP9O%c1sZ_F!8u1uiV2_Qbmfgl>>NDvKjD2N6*7DR&_45opO1{nr& iIEV&0o{^b}nFWZMm|?CjEC9)o>Ia|`fgaPf-~s>wP+^b& literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Muscat b/libs/pytz/zoneinfo/Asia/Muscat new file mode 100644 index 0000000000000000000000000000000000000000..7880d5d7c92a4a5fd994630588e9d74fbd636edb GIT binary patch literal 173 zcmWHE%1kq2zyM4@5fBCe7@K3^rqF(rhpu|nY}s_Jnc9QbPV-i-GEb#fYtAi8r(5m5 z$Yg}XzY-#9P-GGju7RsTPxL@E2zOwMF+w`6v5iOxDx!vL=X;=6dll@>&gI_E<)ZKY z-(P6e#&DkJUr&tl3vZr?^XB{bW1l8}H+J}I+dH(^ihWjRkGpWq7~flHPS|zFdZp86 zO6yW9Zo{ZKvC1|k1t;6D=3h4AQ!kmXP3kogqK}#h54()l@9s1w|JZ1}aiziZo$Is` zPwudj4u!4c?s_|A+d^wfQ@K5LO{O)iBEwEC8fU%fkF}@!MywgJ!**IstdaKEYo`A; zY-Id&-^{%FgE4bp(De6yV`TN5GP67P897_`nt{4jBe$mC&I|6b@{84;m9@nxNNTZX z=e5i1(TL3P_2`_TbyE0Oo6bF7B5&WS)}p>zEj~L}-|3pK^A0BJyWv!w-&rW{mBnaD zolh1_|3gblMx`v~do54BE#)J>%cAH@vS{$SEWUeGmh_*HiW?U-xVu{_Pae^w&COzT z@6cr{cj^00^;-2-lZGnFb$LRiuJC8*iYEcBjxUqypC{@EkJDw<=|{TyrdQS+j+2^! z`?5CjP-=Sy#jL$4>$cz1_4CfihMF5%mvTVri~BYF^0(TMq}uT3er+6W(T&$Tbkk5s zKRmu#o33q^kG?F{=DsTVxG_aP=_-)T%Zj8WoFH3rlVxk^Q)!L!NLx<4wmtY&+9y2G zcI&EijQpaXo$8a%2hZxZ1DADs|5y4&N3TY9NA#tr7kfpI`A=USPs&1WF*6V~#^Xtx z;u-t=lV2)=Ax~*(6(1q~Dk{qT2))2<|Lr{7H~-F!BX^G6I&$yG%_Db@+&*&uNCQX* zNDD|0NE1jGNE@zBA4nreCrB$uFGw>;H%L23KS)DJM@UOZPe@ZpS4dk(U#?DLNM}fE zNN-4UNOwqkNPkF!NQX#^NRLR9NS8>PNT04wqe!Q&POC_-NV7<{NV`bCNW)0SNXtmi zNYhByNZUx?NaIN7u1@Pn@2*bsNcTwlNdL$NAUl9;0kQ|kCLp_jYy+|n$VMPL;p%J! zvKOw-W+1zPYzML*$c7+0f@}%0C&;ECyMk;BvM4YD_`&gLMygKQ77Kgb3l zJA`ZzvPZ}!A-jZZ6S7apMj<Z91c0qO^C*L2;4Y=QCdH(?jq|NOB literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Novokuznetsk b/libs/pytz/zoneinfo/Asia/Novokuznetsk new file mode 100644 index 0000000000000000000000000000000000000000..2576a3b01642c32acde9fd4ea02df316b71ec709 GIT binary patch literal 1183 zcmd7QPe_wt9Ki8sIm?`cmvXgM%VkTa^=s8!O>N7T`7bEe!TwP6M+}6dKU5GXWCani zs4mgX5fKr;urA?YR73|Uh^T`^NfLC(I&_JM^?Y9u7M;5FjQ9OKd*641?e|UZJlq{I zf34Z}3I{7`5BBAEyn)=qq4B`n(2>kMZ`M?l43$^>cstj{!N#L$F<7$N|$+_XvqClLf=2g@`?9}>QfgI z)vq5*&C_8CKgddL_PW$vJ1;8+Ps+;UJt7CYWYwNQjU3&ps~fv@&Gw|O^>5K=B&g9} zH5&6JB=)XM*Zr)N^&^uSA9G8?Esr!l`Xo)~zQ~3f&n0o_r6kWz=*HB2X+H2wH&tDg zRP$}!Tzo=Wf)}-A@`|<=^=j*jW7_s{tG3_Up&g^A6P=e+x&C}U?<$%x^>P;%yk^cS zcvE=e&%J%jZOpcu{WDE^pRxPC0auAJ`}<7U^m+P<%U+p!hPxe|X%m*HJ!1B#=L?LB z+CcGI(V8NB~F#NC-#_NDxRA zNEk>QNFa_j5+oEP7DpQl5)Bd#5)Tp(5)l#-5)%>>5)~2_5*HE}5}Bh74T;Us28Tq4 zgonh31c*e4gown51c^k6go(t71d2q8go?!KXoE$fb+q9k@ge~u5hEdEzn%Sm8*^!c Kjl8L@zVtVQ`2r{a literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Novosibirsk b/libs/pytz/zoneinfo/Asia/Novosibirsk new file mode 100644 index 0000000000000000000000000000000000000000..95e3c73bcc7aef0eabeca2d943eedd6dc1747fb0 GIT binary patch literal 1241 zcmdVZUr19?9Ki9joNmkr2}mddms>jhoh&>D`sh3_V%9pTO=X*s6dWar7mwP{Fm&=9i_g(V9$zvh$ z$MTpvoUC?pvM(FUdutwz&r~f7oJlXQ$cS?Hcv<8q)3jXX7^_g+mFghwXTp5uY2hK_B$Z^?t=!OoRJ*_vj4Cu{!cIz$v zy*eCf)Zrfy9r5+Z$aJONI%mmk+3z}?8-+8*m&#ly0r`j+nacvp88UDTZ~FYB&%y*mD&Uw7v&$i(%SPR74l`)=m+ zV4+ZO6fK%RoW;K#B`!X=T_t}#oGzzRh(wkjIeA!^eQLLw5eAW!2n7)$6@`R(7ZG6) z5i~KniXba5MCc8=3W);gMuZ@{=pu#kCd}6NoG}=6)n(^!&hOi|!(e-#C64u-30i+0 z`DTZUquE@n-J{QB;>FlvpwM?UxxRSZUF3RNUi9Pht>Q29NzbguT{7L5^iBEm84 zyMI(S+=%MND|K3a)~}noOLcQ>tya|k(k=c)t@M7>t@-cO=Ufrr(t>Q8{~E8lcqd*p z^G>$EekK0r1f&L}2&4+645SXE z5Tp{M6r>iU7^E6oQw~xOQjo2w2q_7v2`LJx3MmVz3n>h#3@Ht%4Ji((4k^#p)Q1#k zYbr!aL~2BeM5;u}MCwEeMJh!~MQTNgMXE*0Me0Qgwlx(aCEJ>sk)n~Rk+PAxk-~A% RH1Pjap4VXh0ZrARyx(YH6yN{= literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Oral b/libs/pytz/zoneinfo/Asia/Oral new file mode 100644 index 0000000000000000000000000000000000000000..e36aec475db6fbd8c4fc79bc03332a8fca64bacf GIT binary patch literal 1025 zcmd6lF=$g!6hL3vehq1n4%#Hf+NjmEiLEhhLTb}mm*S8@kPm`bxPum2or3dj{{_0aXcICcgFWi*lldoKE_O=@sx#tGYTypu&Sy#wSxWbn2 ziisI1t`^+TW?4>rh`HgFekncA%gDQS8NC&ilaJP9?Ba%;s_t@g^?E(j)ZEhCq}$u^ zd+!Xhy$1erdY&=aZv7S}oi#1Sj8{z97+*qdoJP+-Z^kPjeOnwe?T!2Lgl{t00iC=~ zL5EMPE5vF%a{B+^ME^z|FAz8U9zPIA5KjTs6~q_B8N?gJ9mF5RA;crZCB!GhDa0$p zEyORxF~l>(bwKqEaSrhwP~AiPLmGf|0BHfz1EdK^7YaigkUk)dKstf60_i27HUsG< optb|)2htFvBS=e-o*+#@x`MO?=?l)OlmE4`NJ%Rk?aN1g0lS;ty#N3J literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Phnom_Penh b/libs/pytz/zoneinfo/Asia/Phnom_Penh new file mode 100644 index 0000000000000000000000000000000000000000..7249640294cd596c9445ce175455efa06b5dc6c3 GIT binary patch literal 211 zcmWHE%1kq2zyQoZ5fBCe7@Ma7$XS$?ex&~Y|No3kObiThHXwN*$-=;pRKURD;~T=@ r1jO0~<{$|m2qD3EpsD}EwzVDs(I9I;`s;ygBCX=G0a|OPYt97#+X*B% literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Pontianak b/libs/pytz/zoneinfo/Asia/Pontianak new file mode 100644 index 0000000000000000000000000000000000000000..9377d03831b60a35283cacbdee4f5a2ac6251642 GIT binary patch literal 381 zcmWHE%1kq2zyRz(5fBCe4j=}x<-7m)G3*k%&v3eO3CE?dcLmpNj&a=jug36Y>%I=f zTO|#QOw3FyOss4S3~3EO^&pakfg!U3WJ28p20jLcf(8a5Fe%2skW|1R?c*E55CFv5 z2Ij^FK++P3Ef~T*LmYvG6UYoC5JG}e{{sQYxgZ+kWDpH3P;0J|Ps^8f$< literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Pyongyang b/libs/pytz/zoneinfo/Asia/Pyongyang new file mode 100644 index 0000000000000000000000000000000000000000..dd54989fabec15b8a7f9b3fce29fc63c02b457ed GIT binary patch literal 253 zcmWHE%1kq2zyK^j5fBCeRv-qkdAhHEoHOZ}*pEy4i3OqOm_$z4qdfI`boXeX>t)jD_`PvPo}6%Gw=1)Z2~w>INFc{d+1s2PgU7#W&LXLoXWSayt|z9ow4zpi44Z<9JVvgV~p`J&bO?& z3;3|FP{9er3%|z=#1F)gN9zgV3gQdm4B`#q4&o2u5aJQy65*b>N81ae8IQIbNIQ^zAPqq} dg0uwb3DOj#D@a>#**dw5eT9;?ymWs&bOuOB+M@sf literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Rangoon b/libs/pytz/zoneinfo/Asia/Rangoon new file mode 100644 index 0000000000000000000000000000000000000000..a00282de340141690412a40fb70cb6d7631ff401 GIT binary patch literal 288 zcmWHE%1kq2zyPd35fBCe7+a_T$XWQQIPmnRKN1&brGKdZ|NlQD6EhPN14EQPNF|VD zVPJ^PVBlb2sGGpR2Vwj8hA;#Hv9^Jku>p{@1Sy4r5E85h+V($eTjxa(4RQvE1~~;p agPa30vL48$&`Df2V29f2npqheZ~*{I;66hD literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Riyadh b/libs/pytz/zoneinfo/Asia/Riyadh new file mode 100644 index 0000000000000000000000000000000000000000..b2f9a2559a1ca4306a43c2abf074e91d51a64edc GIT binary patch literal 173 zcmWHE%1kq2zyM4@5fBCe7@Om&wAq&W|Ns9pGBPk|p8-i}88EQ;_=YfO8yJJQ3?U?# X1~lkD$V8An{HAi*0L{14HRb{Ue7_k- literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Saigon b/libs/pytz/zoneinfo/Asia/Saigon new file mode 100644 index 0000000000000000000000000000000000000000..eab94fe8985949b5d98cd003d26d9c2e70e2d0d6 GIT binary patch literal 375 zcmWHE%1kq2zyNGO5fBCeE+7W6MLT+&8zwH+<~nQotl?thxq?g9b|0=9?G|{l?McI1 z{TU2gw#OOj|NsBb$i&RT#0-Q?3=AnC6Bv@eF|aT&Bo#1lGB6Z0Fz_-k)JbA->Us9Ke&C!U_p%(~x!*+Ry4pE>Fltm=b zq0Fv-Zo`XaNv7YZ=gd#e0>-Xos?|%<$u>C&CL;a^B z^2aQ-C)`Zb-aN0qVqQ$ngeJ_Iz|KTzSt{o(KJ&^uf4igX^Jrr2>`ZRmv_Ij?hSd58 zRjPbsm8uwas|^?DRAuUusyh5eZHzrr{+3Bq9lWnx50JTZZ}akKg3xYVBC zFSW0)nJrTnOz?5aY`uHL)ZL1k`pfO6;ar0Wov1Y7qkSrJHlVh()vN6Xyn07@g>DSb z>&73Vn|!6x^!B}O{{BsNWv6t@?5wnoy_2>~R@#T2%I@^2betTM&Py5H)qPc>$4B&@ zntthy4(PoFG3oIi(>-5K>t1)4?tR&-_kF0=v4=t3H{B`m8!L1&mN5svPRL+BpLe;J zEL@&~U#`NU-`*4!{q6D;c|0OR&-s4`Zi;=rmva@1q#w!h#n;6XmpvK2BP$oq`{GFn zhMFZ9j@UbF?+{-iF2;G{CHW6)wZA6MYANgG^Q@S%X3DCa+I3S_PFXu;^_2Bf2v8VM zC{Q?1NKjZ%Xi#`ih)|eNs8F~#wPYx480a{)d?+v~#NlAmNx4_E*I literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Samarkand b/libs/pytz/zoneinfo/Asia/Samarkand new file mode 100644 index 0000000000000000000000000000000000000000..a5d1e97004ff763fab160e6c8a2bcbfad7cf67fc GIT binary patch literal 605 zcmcK0y-UMD7=ZCNX{)8};G+GAAMrD45sFwppp;JHP$5G=5Qk0$!Bt#@E;{L-;2^k( zLmb>hTm^@BcW`lW&_P`UOT4EJB2EtGxO>7u!rd=e-)zpxuczDWFnJ1Q^4@cr-(R>m zY1ewagRn1m?PinbZub54IQRGx=I=bWaJv&amwVcOwygt4O&vU3)1loJb+?vucw<^e z>W+?9GCEdl>$vl*6X|>P5^3Li`|u|pp8ToCRjCz4k!8o?G24tp*N3j`<%6p2oa21SIT zLXn~9P=qK-6e)@pMU0|Gk)!BQ1SyIXNs4BtMwFsTk)X2h6@O60tK-))UcWb~c(Z&*#@q8^74O<-WqdF#tWa2-FhMadeS%W6 z(*$Kd&k2l7%#18ZkeL+-85qhrKsJ|mFt9K%)JDI0V9u(ZwP~T za0r7J5PQ3XfRw{Q2nnA04+J2OfoPEDKs3mMAR6RJ5DoGuhz5BUM1wpGqCuVp(IAh5 zX`tspG$;VTG%yfAG$F&G$>#|jt2z}hz11^hz11`h^ARUaREb6 H*OChW=SsNu literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Shanghai b/libs/pytz/zoneinfo/Asia/Shanghai new file mode 100644 index 0000000000000000000000000000000000000000..ce9e00a5db7c447fde613c59b214e8070f30ba2f GIT binary patch literal 545 zcmbu*KQBX37{~Fa>ThGXu?mi&+QDKF36auDBdLYEFw~^d?V4RTCt+f_yM-6v4M?P` zrdx~ZyEy4);`!cH4B|AWpQd-YzpsDXsISV8lh%K@oN2xMp0xV)a#XXeiO-1=Gd?(Kzr-Fb9xyIFa!HeGMCDIcTtpg%LP{q4}rJ{_33#$9Zp>-+h=%Q$=X zU=|7|@nYr5EKP-8Zu!*Y1~o4~Rx$Zb(Hlzr`Vl$r>6=Itr-nrWE92FDUrJ@YhdvMV z_rh`G9q1pkq1OF0Ljb>1|bH9x(N&t3=9Pg z3^G2xAq>GltZiTp!bS!l(ilWq0c_0pO&roTwju|?Sydndl|YuO%nD$SmZ?5Mmhe%FlluTAQmE0d~a z?1*YvxTbf#y{H4zX}$aLaou_+q1&!Rb^C>O9X!>jtrKwxo%c&ePn&d(Y>++PYU#4R zO4qON((NhN-Jcd@-_KROe|AQ~OYe2$$-M4)^IS)-PU{1iTe|o3U48I!R{93UbpOel za;SM!5A=&1E{o_v--rx;J1RrYP8s?Tmf_De5_{p5_*_UQZsa8u%Px=S^LdAJUEy?< zm7MF#OJ0<|@#os@aw&DYfuDBbp)%{KoWreD=B27C-itd9vr~LVnP-aosw$pHselzU zSIArzAEq3I!(JC|sehQR`C@q{j7*tNVA9C6k%`-ysUwp|rjHbWRDhI#)PNL$RDqO% z)PWR&RDzU()PfX)RAXz(LF(b4AX`%rQW8=VQWR1ZQWjDdQW#PhQW{blQXEnpQl71; z4=K>rREU&_)QA*`REd;{)QJ>|REm^})QS{~REw00)Qc2sYbr)cwly^)MI%)sWg~SX Vh2yws;Qy(-B4Yjl(N@c<_zfbFA+`Vj literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Taipei b/libs/pytz/zoneinfo/Asia/Taipei new file mode 100644 index 0000000000000000000000000000000000000000..f9cbe672ab1aa9b6c47f7fed0a0ccd73845a10ec GIT binary patch literal 781 zcmci9Jt)L+9LMqRc|6B}$)YeQgUDnM_Z6^k;GMK_?7 z+~DyJ=lS70|6*XcI=?qI3ya_N`@gP#x7_FLv~;wW$&Zt4-*7oa_VPVb+s8^%o!)Z% zdV92A?^Ms5-P!`#^99U)MLawFEi$Y5SU{BT7q>$l#c`dAc&b z-uN0E@isbAZ?Ct;;fLSH`NLpwdwPN<2N@0-4;c^{5g8I06B!g46&dzZJ1#OXGBPqW zGBz?eGCDFmGCmRjiGYMaVjw|~C`g!3Z5$*J5(x=~#6p50(U5RRJfBqkCR N|0Sx&wk|IBd;k)YUNHaw literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Tashkent b/libs/pytz/zoneinfo/Asia/Tashkent new file mode 100644 index 0000000000000000000000000000000000000000..e75bb365a78e6dd0a7ce4d75c8d36ccdb8f7f7c4 GIT binary patch literal 621 zcmci9Jxjwt9DwmVjasWh7j4s4t*=pn5QHkING3t3&>@3}Lvc_F4xOBo4z3P<0Ed8^ zxG1=Z+y`)Rc5$eKgLV4#I)VWh$MxD5T#Xk26n=(7v}*+1qNEyWTVT^BpsIvTlZsR?YDKf+_5}W@IyO zMoWEWZ1K~KyG=8ZdoYtdcgC?keCPGWpK3gprzAHH5T79l`U;2lU%G$p9S8~qlaer?gQ_{a^0L_Y0RN{9{rC)B~ zW{q|mnc(zRpFXp`9h}{a>GI>W8C(0L$Lm|>-0Ev><{HK{UilNtJ>MtKe_2=x!_aAq z?P`wp-G7XZcw2(OtDX0)dx>}~rc^$ZKZI5G16n1Ny5Ko`m2!>R*IHXAj+`{_xQ0D< zrY==fWm1+wJu0&(v%nXXa^z|5cGk*2Xp&zPR-dmq5M2;udqf0LSrvt5n2NZHb7a4MAzg{5w;fr_0Q!XrC!sH7#0O(FI-ThFW_-i1t7n z+SrpyL>&n68qqjZ@}wjPLP~-Q2SS2Gkc{^oCvkFc@SnW-%*>%lli&9ROE*@!tUsP; z^9zTk!W=$N>Z;kT9}dRq(KpV?F-;DCOo!i&d7t4QTU@1M=Lb}r=bessj8jR8EjnrN zq?$2sLeK1*tde`~%aqe4!qG8A&)RoLIqN;rF25;K3pZ(3&PU~Teb8xBo7L?2DKdT7 z1(E)xM0?&mSK`TGIp3|xOC^3S*Fg`LrA(ZOqSacib1sD7&p%j(pU{90X<{YfoNIV*z+ z(?#*9N4mr+RV5#W%Vn=R#PSF2az%ffD7|t^mv!f=mB&8IRR>zd>gMTsP1Pl}c3F=u z54hAi@lvi&t`r+4#_5WYovPw{o~(R-PgK3QE35ApiH&_Bz3K9PwfT%&)*S5>wQZHU zuJN6!U)LqK6eo(U^Alu)r&}~mE7DC9o~q{P1G4$sNYV1PS8p5isqF(^z2j!TYVB!| zJ5PNTyV|?;?tRH>Pu(-Qw|tG*8w!OYBBO>xMGpO^uSmmjkuf8KMn;Vc8yPn;@UUj&$k36o zBZEgqj|?9fKN0{E0TKcd0}=!h1ri1l2NDPp2@(nt3la z6%tlh6BiN~5*ZR25*rd65*-pA5+4#E5+M>I5+f2M5+xF5SQ94_C=w|WDiSLaED|je zE)p*iFcL8mG7>WqG!iuuHWGJO6F3q%5;_t)56jzBYpzV*O<5f literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Tel_Aviv b/libs/pytz/zoneinfo/Asia/Tel_Aviv new file mode 100644 index 0000000000000000000000000000000000000000..2d14c9998e9dc54bd1ad4710332abafc8e811242 GIT binary patch literal 2256 zcmdtie@s&GJH>!w1ZFBdCNWLOC;MTB z#pblu{JCXp^uwWITXT`M>1-o7Da%yMryn3oaK|MmZc5Mlv{hSw^;cWpyK`S>_fN*> z9V}U1l`8&m;pP_}uF*WaM=SHS+n4>uiNyzXd(W4FZ$7>yI*wnpI~%LC-Mr5J_Ek%t zeDiC0s1Z6C4XtoH)(8xL5M^A6khle*V~?zRH%is-e_6g%`mp2u={`?{5Hay$tPmM&oQrJyh<^39Vs-#o==UjBZ; zf3ckrbD>Yax`Av6*%7iOWKYPZkX<3$LiUAh4A~j7HDqtDW^>5yknJJ+b2S@8c8F{d z*(0(^WS7V`k$oZ?MRtm871=AYS!B1!c3sVW`TMb9SF>Ye%gCOQO(VNTwvFr?**LOu zWb4S@k3_K!3G=>XCKqz6b7kS-u?K>C0*0_g zg>(vO71ArDSxC2#b|L*j8isVt)wB%hnX73U(lw-QNZ*jgadZx89Y^nw=5cfnX&*=b zkOp#e5NRROLs!#8q>D%!kv<}gL^_GI66qzS~(G(N$N| wR*t?RjpgVp(psdqNOST3+TBP~u)1J-1zaU;O1HD54YJFKHOd_`{B;B zM<4F?{Qtnr$OM5549(0y^$a}=7=fDWCNOY7NFU!21}_&N4h{iHGlFmk36A&=1gVFX r6o6=uW56`fK_D9BC=d;D7>EWr4om|b2%-oMSj5>Afd3oQ@vv)6p?e|^k@Ts1V z@z-+eJDjX$eX=hZ`n><$$nZ@0iolu7%Cf9c>KZO7{rR@PY<4oUYRY4*9y^lpj`pfG zk2;j^UaMMrBdXS2u2JQueQJH@a#fMcDSz~<+7O&kmEJdMqvw?hm|sL-{)5=`>AkGF za9vhSycE^1pNQbotf5fC+ zS$w7|&7No4+EYeQg!LKGXB{77IP@|c_SU|n{$alQE9ICmGG{)3StIjiF>_lzcVza+ z{8==Bbbz#g^nf&hbb+*i^no;jbb_>k^nx^lbYrXALHa=&veg|SEg?N2O(9(&Z6SRj zjUk;Ots%W3%^}?(?b+)7kOpmahe(S^k4Te9m$tf1q)((#q*J6-q*tU_q+6t2q+g_A pq+?s%GSaiHZW`$tX&dPqX&mVsX&w7?6aPc+?wI~3G}P9)e*^u27jOUo literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Ujung_Pandang b/libs/pytz/zoneinfo/Asia/Ujung_Pandang new file mode 100644 index 0000000000000000000000000000000000000000..ed55442e2917b4bbccce34f3e1c531d1f3284ac4 GIT binary patch literal 274 zcmWHE%1kq2zyPd35fBCe79a+(MHhaGov=&n>V(rBvJIEQ-W4!1F$3lDKY$bgNg%tR zfq{d8p>6^L511|B;~T=@3&h$67C>yt5bhb`2vQ9NAtYG-9|%AW0MQ^vfM}3IKr~ex L!v%7jt_2qW<)b^z literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Ulaanbaatar b/libs/pytz/zoneinfo/Asia/Ulaanbaatar new file mode 100644 index 0000000000000000000000000000000000000000..82fd47609e125ee799e3d3be4489d6cfd4782abb GIT binary patch literal 907 zcmciAJ1j$C7=YowR3b`T5(%pA_bS!ay{-F12T~-|KpKe{{6nV_{-H4;XD}d!GuYS& z2BX1Xa~Z_QV3Ctxlhk>;nk>HNq9=DHeBP?M;zmM zlZ6NRRK~oV`dZVcUx(yIGMTibr~UDjE=1JFJ{BbFSl29>LP#aD$3kJ@ba$FhxA}DO zmDGg)uv+sUC0Q>&WW~sukyRt>Mpllj9a%lHek1{s0ZD=6K$0L?kTgghBvDE;6GJK_ z7eg{68$&uIA45VUBST6gCz2G&iljyIB8idANNOZEk{rp7q(|~2CxDy*atg>fASZ#G Y1#%iG&GSG`1pnzwvi#=J1YKQOKO>mJNdN!< literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Ulan_Bator b/libs/pytz/zoneinfo/Asia/Ulan_Bator new file mode 100644 index 0000000000000000000000000000000000000000..82fd47609e125ee799e3d3be4489d6cfd4782abb GIT binary patch literal 907 zcmciAJ1j$C7=YowR3b`T5(%pA_bS!ay{-F12T~-|KpKe{{6nV_{-H4;XD}d!GuYS& z2BX1Xa~Z_QV3Ctxlhk>;nk>HNq9=DHeBP?M;zmM zlZ6NRRK~oV`dZVcUx(yIGMTibr~UDjE=1JFJ{BbFSl29>LP#aD$3kJ@ba$FhxA}DO zmDGg)uv+sUC0Q>&WW~sukyRt>Mpllj9a%lHek1{s0ZD=6K$0L?kTgghBvDE;6GJK_ z7eg{68$&uIA45VUBST6gCz2G&iljyIB8idANNOZEk{rp7q(|~2CxDy*atg>fASZ#G Y1#%iG&GSG`1pnzwvi#=J1YKQOKO>mJNdN!< literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Urumqi b/libs/pytz/zoneinfo/Asia/Urumqi new file mode 100644 index 0000000000000000000000000000000000000000..0342b433180b416a02c5ff8764e7a0a57921091b GIT binary patch literal 173 zcmWHE%1kq2zyM4@5fBCe7@K3mzg;Qy|NsAIWMp6nk^xDDBrve}_=YfO8<>H(3?U?# X1~lkD$V8An{HAi*0L{14HRA#R1VtLz literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Ust-Nera b/libs/pytz/zoneinfo/Asia/Ust-Nera new file mode 100644 index 0000000000000000000000000000000000000000..c0c3767e38042e4d9d1d7c2bb102e1ffdaa2602d GIT binary patch literal 1276 zcmdVZPe_wt9Ki8sZOdsLBuZDd(v~e->b6W~)3sVEwynf=$?DV}s1f{#4pNXoe;~3y zhv+b(KU7#?(ZM8!haHLzNrXrTt5bE5ARc0uB3RG&-DTLRW6#U8@B2RQ80>vMZ+h_P zk&yapEH*1lM%+x!-S<9x%00jTN-gv4ks5a2$XfqnX{^i2xRCw#K0t3}!dDrK{$TH1r( zr2Y2~3D;HV@W&bH_%)}uOioB-_Kog*IIX*KPjvLcxZaw*relY1>TT!7WPA6Jjvu@# zI~oUdcU+{$*{OTI1Je6_uk_j5r0;D+c7Cpr#8ZzXr$Rb)r6{|O7h>r|P7fE0McV?0 z-8!5L%Z>_H*+uz{`RA2RmrJROJv?7Is~k$5xvtDg_8E`*QQlTP|J-KQNA9Yn+*Rgo z={u|>Q)%T7gw53`UA^G}OxJ7wz`Y(Iqng@IK=fx>}8 zg2IAAgTjMCgu;YEg~EkGhQfwIhr)+Kh{A|M$*SQ*A;o}|RYQxyi$aXTj6#jVjY5vX zjzW*Zk3x{bkV28d(W)UyVQJOSr0}E=r7)#XrEsN?rLd*YrSPQ?rZA>Zrf{Z^rm(hZ mXj6DwHN+{*Dby+4DdZ{aDfH>EvG5=Buj(`}N3=N@sQLp)Ml4nU literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Vientiane b/libs/pytz/zoneinfo/Asia/Vientiane new file mode 100644 index 0000000000000000000000000000000000000000..7249640294cd596c9445ce175455efa06b5dc6c3 GIT binary patch literal 211 zcmWHE%1kq2zyQoZ5fBCe7@Ma7$XS$?ex&~Y|No3kObiThHXwN*$-=;pRKURD;~T=@ r1jO0~<{$|m2qD3EpsD}EwzVDs(I9I;`s;ygBCX=G0a|OPYt97#+X*B% literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Vladivostok b/libs/pytz/zoneinfo/Asia/Vladivostok new file mode 100644 index 0000000000000000000000000000000000000000..15731abc81dfb016221260ed6ae40af24aefb89e GIT binary patch literal 1230 zcmdVZPe_w-9LMqRbkM}Z5dFc{D9WsdZdp~0^>eR8%*xrwiW1zp+QbWV%I^?g_ zm^(aK#5}onKRyvl&rQ#-Zt_Jlb@i8)Jg(7Y&)U;t^g*J_R)9oi$bVuU7Hut{K7XM3a^*+&^HTTu$d@a83^RjDY&fPYecDKE| zC%YGJiT}lA+4E>b+V7r{z1NcxxEPk;nSfa7A?>)(sr&juy8pOGI~y9c%UaW}U!vXK zI_duKNqc_&kOR2|?ak*UH1kpVvN;LgcqIoXAIPDzGtz%8tC8qU88|(qhg*jwIxwb3 zsuB`w&S>oGd5u>_H2yZOM?be{;+bCu7yBi7dxNGD+12qvq2Q>jC_bE3Wsi01%YG>T z#h=$Mr&Hw0H2>PMJ7TUUpF3P46Az`f^jf;(Fn3Dt5%ZqXeW@Kxror<^?H^eF-;3MdjN8Ym(tDkw52Iw(RY zN+?n&S}0;DYU~<03_TPSHgIEp%oJiA68MW9`y zkRp+xks^|zk|C3!lOdF$lp&R(l_8d*mLZp-mm%1$QB09+*J!4Qrl_XKX6U8}r{l)J Q|D(J*WWE96b}Lx@8|YsB0{{R3 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Yakutsk b/libs/pytz/zoneinfo/Asia/Yakutsk new file mode 100644 index 0000000000000000000000000000000000000000..1f86e77f5806a6c7a19143071de4fd72145bf037 GIT binary patch literal 1229 zcmdVZO-Pe*9KiAaa_Uft-=(ycxze?4YIBur>9kxXx=B%kC@F$o=#3arln@#8dKmFL z6hU<8JqYR2%fY;*P$5C($vOyjs17F4B_h`U`;39qsbl|#=lT5i>|wC|zDsS}zc+0D zTBY_5C#&6_?B(YBFFT&x9x-zQo6~bEE=-hr&QF*Bc($|R^I&@3_|(Mw(YCaAD5(nu zVp@5js7f>)kAN!dHj>K-2E!C!B^6H?v1ov8;~`79?9A>FSNbmio|z4&~^Uf z(h<+-`ogW!S$$YLr;ce?!A9+Ry+ykx!AB?9RN|%S&t|i(f?2u4T{zC z(N>6*h}4J_iByS{iPVV{id2e}iqwh}i&Tr0i`0u0>}V@SN_Mn0BSj-sBV{9XBZag2 SZ3F*Lp{@1Sy4r5E85h+V($eTjxa(4RQvE1~~;p agPa30vL48$&`Df2V29f2npqheZ~*{I;66hD literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Yekaterinburg b/libs/pytz/zoneinfo/Asia/Yekaterinburg new file mode 100644 index 0000000000000000000000000000000000000000..fff9f3b14bfebc4344701c7b66410602de37e6ba GIT binary patch literal 1267 zcmdVZPe_wt9Ki8s>S~)FBr2_Ct;}9^S!-=$)|{*5%$o2}gCLX!nNV~ol7a#uMg>I? zb*c`Q;i*3p>XeazN2}kVOGpLfB?1rCC2Xwc`;LLssbkN}^M2m<*?3{^^IhiP(ZQhl zYt)z(CL?Jk*Gq37a$9$oj2EAd781*>%lYN?lfF9Fi$LAa?<4hJzU5cUSiY6hr}N&4 zoUD2|gB_8Fg!jajYaO=)nvpR9)tKN}Qva{=zP9FXw zyP6*BuH=-YoY!@CWD8Hw!=b^V;onDiwXl`whKRRpvUOTuNoK4)dy0TGf`%(nGrL?JXR@3X_RhC=gdEBxA~Ptwj9}Z23yaQ*nT7dBm-NM0+Iug1d;`k z29gJo2$Bhs3X%(w43Z6!4w4U&5Rws+lC8-JNeaoz)})2xg(QY#hNOn%h9rk%hopz( zha`w(h@^<*Xls&0va~g6B6%W-BAFtoBDo^TBH1G8BKaZ-BN-zpBRL~UBU#&;w2{1R fP2x!ANa{%LNb*SbIBXjDPt*6r%pW7(7WVuGNaP;S literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Asia/Yerevan b/libs/pytz/zoneinfo/Asia/Yerevan new file mode 100644 index 0000000000000000000000000000000000000000..409c3b17125b803d499e66e78c730030a56d3443 GIT binary patch literal 1199 zcmd7QJ!n%=7=Yn(O`Ak<0*7oqJSPDbH_B7&lb;9ynad9M~qo!q>abDzUWxR88rVgJ!1 zG44pdEN0&~`*;CodHl@oT|)VWgl$rV{W zc2??d4oky@Az5=~pR7HRm30SFvOX7;#&n%*Xswo}@DJHo{ZS&mkdA!y>rHQ$JJFYO zPV=)5PRsafr}gf%({}Z#(?0Uh**tv5u@7H%V$YpYN9QGp?>#Pw`je83ts;2mD_dtUP5L0WJhmV_Z<2xJDVnTZ_f+q z^WE0j#&OAhnvj0ab?KkECA()&%fN%8 zO(NGGtdXXk^&Pz>%RNgGYvs1b{?gn`6? z1cF3@gyL#qL4rY|LBc`eK>|V|LPA1fLV`k~Lc&7gLIOh~b2XtMvALSykm!)`kob@Q zkqD6xkrD7CR`+5Bw!?BBxF|;GZHiw&FugCsQqb^ JdskcB{|l@V5AFZ} literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Atlantic/Azores b/libs/pytz/zoneinfo/Atlantic/Azores new file mode 100644 index 0000000000000000000000000000000000000000..56593dbfff9bc50bb32e64ff282d0502e75526c0 GIT binary patch literal 3484 zcmeI!X>d(<7{~FGS}KsVys<5xVlt+OlfKbXh&7m99xyq-(qTbX{b! zu7B{9uJ;$|h97ro&hCSWak8`J z8rhYwTX!9~qj>?HHScP)et9xg@;@6b`IlU)AT8M{IF>7W-s>TIwhd1!Ov$whcO2Ed za-p7X7Ji#O?4y@TYd^dxJ4r~OjY;s^Wd&=dBdYJ2U& zfv$b{_A2|x`33f|4ddTpX8X$I753E^ z9Q&u|^4)8_a@_0fmb*8i=D9zI%yMs5PIGUS80+4;Hq@<<5h@S7{fIm`HCAO@Q>~a- zPbs8i)yfm5|Oe`U1;+0G-|GM(U@d|<IldZm zmirz}pXaOj$}FF6WSXzmz_GsCU55HXqI>xs3v2JIQ@x3=Zuz>tdZl_e^=`Cv>Yt8s zLW>@A9^Vnm@7>q zCrZ=1snR@nwl+WhthBf@L0fKWCr=(t(C8&YG-hK)p9k}d_g8aFdtx-Ofq-A0X) z?&+^P!F6uj_!KvNF)A zD1+8slEJChWXSA7d1gqFJp1Ye9ol-GJU6I76Kj4X!(y^Esmy0OyoT!Vb028(-4uO( z_auGcc)X7IWPpy`IaXer9;2g{^perZHFeCiCNj21w4@|ek(VMKm2t7RWqd_TCNvJx z30F?a#0PHb#C_Xk(&ZyMdF2v$xp1St^3hg(HG8hUHffQj&P>%*zuzBF`o0n+Oa0Bq z{pNdreEzM!7kGb}zkHM}SN^|xl=u73Ua>5{|8#w;q~Cw_NLbgK ztUpo!qyk6@JWUOdA|O>j%7D}XDFjjpq!dUkkYXU!K+1vC11Shn5l>SRq$WsFkg6bM zLF$4O2B{2E8l*NzaggdDh3kOh}!OLLrqxN`=%4DHc*K zq+Fh+UP!@^iXkOKYK9aIsTxu?q;5#zkjf#YLu!W<52+qfKBRu0rhrHVkrE;`M2d)1 z5h){5N2HKQC6Q7hwM2@ER1+yDQcq7)P^6-srld$sk)k41MaqiQ6)7xIS){Z`ZIR+4 z)kVsS)E6l*QejV1Vx-2NrpQQ@kuoE7MhcBo8YwkWYoypnwUKfo^+pPgRNT{)9I3gd zDLPVhr0huDk-{UDM@o;>9w|OjeWd(I{gDd*xdM<&0J#P{%|(D*1;}N9TnEU7fLsa4 zrGQ)u$i;wM4antyTo1?vfm{*DC4pQMp5~%Jt_n|cSs>R1a$z7>26Aa2*9LNNAXkT{ y`Q`rq^7#E0;osxlh4JrQ9%ZA=mC`CA+T19u!s4PDHE9&yI6N#aBHViyQT8_<^E?9p literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Atlantic/Bermuda b/libs/pytz/zoneinfo/Atlantic/Bermuda new file mode 100644 index 0000000000000000000000000000000000000000..3a5c6dbf7a9ad95ce3a2d7c1bf26e203b6fac30d GIT binary patch literal 1990 zcmdVaUrg0y9LMoPG)E^fEJ+hEu|J5x5e}Y0(9pupFiw1xgK<>oTY&^tBnd(i&1EH5 zwi#OH*jz5xMOnj~<;GAlZ%Q9Co2?BW=is8{X1cXDlhgBlZoBHL^*j6Rb#}IM_kDlj zYg;#j1OIqZ-7ma6+uY0dsfR~%Cer2(>1`RiB^Vgc;MH!q-EPsTKfhit1ffL zC!IFqgL*T)|7m+?Pno%^b+Mh1$Tpc(S@!PINi%c)O*?B|mfVwe(auh}DYJk7OS3Lr zlv%N~U>o-&U>uDJ-$x27T(t45?XP zp)&A?tXh(*t2-}9ZFZ{0>%Nxwjd5KQ*(YnpQ*8a5qo)3w%Qi8&*Q`Bo(yklpHS4z@ zv<<_Jrm?BtJ~Oz?XzXKs_MHZKu5gDw-%=q>>22B^&6VaW)!LGmDlOlKv~|k3w0@eQ zZ4;+t`{ z8`ADsZPGnFpgn(9%WH#MbnAtX^!6q6^`jZm*I2G^?71c>xBSPSWOB4D5J)CZjRbB@ zCeNFcwCY$qu)Nm2m2uA1J@J2W)JgYn*!+*(%;zI&NB1A$&Vnl*OqC~<(;zR;PB1J;=x>%85k!X={k$91Sk%*Cyk(iO7 zk*JZdk+_k-y)JSjbgzpY3Eu0XN5V(qM+N{H0b~e}F+c_Z83kk*ka0i;!s|u?849l( z3uG|7ZZweLK*j?Z5M)GL64gWX8 a7IZte$n9J~tRxf;6&1vai$lesqTt^>F$c;3 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Atlantic/Canary b/libs/pytz/zoneinfo/Atlantic/Canary new file mode 100644 index 0000000000000000000000000000000000000000..f3192156ff043a529461aa9004a8de9dda326f7d GIT binary patch literal 1897 zcmdVaUrd#C9LMqJ2qGUvpA-5QdEdkL@gpEV~`qiI@cUy zpEjm*<+A0Nb4p%NT_Cmo&X%&aWKPYs<;woe(acdgM!)ydO`BKVwE3{Z>ltU`>ihmg z*KTdh_wIVeyT9<^X>}jo6MJH7hcA?l%$yP_@}?HtR#L`qnl|M-CC8js^39Jl{n~qa z;M=2m@Uu6Ra%R9%Pxe~cTW{NpPFeb{JvOtc#b(uRwAocr%P20lhk`|xnVMyDCQi4k zxH4tkny2g^GnF$mO%H!DL67wPrQoq&G`IV*a`%0$yd7s0YB;5E-6hL!>9c~8ew(-Q zpcSSav7-DoD;n*v`6=C+e|5brxYMeI17-Hul^PZI)T^X_p%(2g)#5i(wWKjarTZ4x z;}vl#Ye=ytGOw$=Y}6{^zEWkz_f~o1CtDixi7g#GYfoN#*PiMV%qfLrdRqC1KWm*-?(W;S+q-A-Frl@wI5SQSBC=QVq)X|_n)z`KjeAt_ples znR)S^H^~AM|NCAQiF$KGVQ+PL)P1U>d>04={v~=3r#t2z&KEgh{sU*s!zm-@jGQ!b z*1qnvk@H4Q96593)RA*XP98aXgfR3U~c7%9jt%njv* LgOPBEw}gKHM@+Br literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Atlantic/Cape_Verde b/libs/pytz/zoneinfo/Atlantic/Cape_Verde new file mode 100644 index 0000000000000000000000000000000000000000..e2a49d248de086306d2dd16a2b2d497a008c8f07 GIT binary patch literal 270 zcmWHE%1kq2zyPd35fBCe7@KF(vsDYuOr4`}sia1LTl~92{r~^}8JU<_SpNTi`GtYu z|NqAi7=Y}L9~e0hYz7V=-w*~}10x_dWME(fnFu06NU#`a&wr5RAR6QV5Djt!$SjaU TKy*D&jBLkn*#I49XUGKr1p`uu literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Atlantic/Faeroe b/libs/pytz/zoneinfo/Atlantic/Faeroe new file mode 100644 index 0000000000000000000000000000000000000000..4dab7ef0859c244b916d61b7489d7371881e0ca2 GIT binary patch literal 1815 zcmdVaT};h!9LMpFG}f>$R-qD-ila_ZNC~Ni%0ov*lJr0%6s?eE%#7B)w#E#TY0WH$ zi*S*Lc^s2wvt}NeP4jHcM#HS-_x`(d<;LdU{(Jp*F1q@>zs?oKMUifQJpIitygcRR z<$LhKjg47efgja-_zU%Mf2clRuIY%b^E&czgO0j&NPVwd6~AVe_#Zzhqia`L6cH_d2=$ znTG9spy6AusH5PVM&vw|$g&oh64xqImmZcV{}U1&St-%IH8S0|UZ;2F$&8;B8gn&4 zW7vq7SzNnEmt-E$r6q-$KCMKZDapFbCrvZ# zp_=i{p;=x@lJ#VmF7FAE6_>thc88~|Y#1szEuUmn%@@h7Zfz|8hBCH`m3&ecSP6qmTB?5DqY{{)D35{wdC#=*|Zc-8Mr4^rFN#&4lTKVX-%wrJ(>AlNvTtPL$j*_iTbjKin@4t!Y#-S_ z(g4x{(gM;0(ge~4(gxB8(g@NC(u$?&1!>08bc3{m^n)~nbcD2o^n^5pbcM8q^o2Br zbcVEs^oBHNX}Uw&vo!r74I&*PEh0T4O(I<)Z6bXljUt^Qts=c5&03mnk#;RjzevMK z$4JXa&q&iq*GSt)-$>&~=Sb^F?@04V_elGerhnuHAa?+{1;{->ZUS-_klTRV2joT| zcLKQ;$h|;r268u$+hJ+$2XaF!%^g8*335-6n}XaG$R-qD-ila_ZNC~Ni%0ov*lJr0%6s?eE%#7B)w#E#TY0WH$ zi*S*Lc^s2wvt}NeP4jHcM#HS-_x`(d<;LdU{(Jp*F1q@>zs?oKMUifQJpIitygcRR z<$LhKjg47efgja-_zU%Mf2clRuIY%b^E&czgO0j&NPVwd6~AVe_#Zzhqia`L6cH_d2=$ znTG9spy6AusH5PVM&vw|$g&oh64xqImmZcV{}U1&St-%IH8S0|UZ;2F$&8;B8gn&4 zW7vq7SzNnEmt-E$r6q-$KCMKZDapFbCrvZ# zp_=i{p;=x@lJ#VmF7FAE6_>thc88~|Y#1szEuUmn%@@h7Zfz|8hBCH`m3&ecSP6qmTB?5DqY{{)D35{wdC#=*|Zc-8Mr4^rFN#&4lTKVX-%wrJ(>AlNvTtPL$j*_iTbjKin@4t!Y#-S_ z(g4x{(gM;0(ge~4(gxB8(g@NC(u$?&1!>08bc3{m^n)~nbcD2o^n^5pbcM8q^o2Br zbcVEs^oBHNX}Uw&vo!r74I&*PEh0T4O(I<)Z6bXljUt^Qts=c5&03mnk#;RjzevMK z$4JXa&q&iq*GSt)-$>&~=Sb^F?@04V_elGerhnuHAa?+{1;{->ZUS-_klTRV2joT| zcLKQ;$h|;r268u$+hJ+$2XaF!%^g8*335-6n}XaGV|lz*VLe%NT?WhQf4t}Rwp8eJmMkFokZzs>oF{?kAG(dWDSKEC^I z_s4DbdInZ(sLQpkIdSF%@alWXGS!l5+1xZfu~z3h>p9hvfTQ>sMjMSiJt$ffd2GCX@wF1t?2i1V2I zDiIycij~pGNu3zp8JXl?Ad~a{(1i30nmFkzbw(do=kU8aW$=*R^2Hv#^}`o5>Bvz@ zKF}>Gue>T#+f-7wJ|k(tkleOvt=#SlNP1DJOmi1XMzTw$-!w&BFVPVembP2Kx`&{-X4HM8|o&DwNCvuh7(PSqL74fRN#r&scqy(9%GyQMI@uG~`=qu$yiS&(sF zOTA-K7W0Xgr++QwL*L25==Wt|xKHjK+$)Q^-xOc}d+Kj*lf?&K(LsqP<{?D7M?|uG&5zmGAA@NEr`s=)=UVQ5i%uYPROK?Ss~Lx z=7mfQnHe%QWNyghkl7*AL*|D}5Sbw|MP!c1B#~L#nrZUOnI|$)WTwbek+~w1wKcOv zri;uMnJ_YAWXi~#kx3)7My8F-8<{vVb7bns+>yy6v$r+VN9KsX}svBn!zFk}f1)wkBan#%xW>kene& zL$ZdX4apmlI3#mO>X6(a$wRV-qz}m-l0YPbwkCy04v{1xSwzx^b> literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Atlantic/Madeira b/libs/pytz/zoneinfo/Atlantic/Madeira new file mode 100644 index 0000000000000000000000000000000000000000..5213761f891303f95e1962570568ae62ed387950 GIT binary patch literal 3475 zcmeI!dvwor9LMp`uUVK&A4bfKvSe!U-Gq@P7CUmO<`<)8xnEjhjpLH`sdQtPYu?Q?W^w*@m6tlqESY`HEbVsE zEE_+=ERPIVD;`~~@`JCN{L72f2Sp#Ng7;$8hj}T+eZ7HN{pK{aW`birN}p%eK33nX zj~#3_-1d#xP_@&1a$$qnv}=bd+^}AKw)k1I`JMUd^95CE%arNri!o_xYjTR()-}{@ zkL#*-bY7u$CVy&n9z17?eDS8}VjJ`2(TQsJx}j=!#p*z@85Af!yjtyfD?#nqI%q;k zx*I6jzQ=qusipdA)ucdaQg)zp#fN6^6V=t;tbu`ju^EAVug*352klp1_u8h)IvsJo zX_xQol~`nsdcb!XRBx6SNm zbVih^HzLf`_cxZ%gi6z(-EYz`@~qq*dRQ6-?U6>8K9k1B3iHDD=Sh>Tb0vIJp>DdW zKu6>**3Gg+H-F(JeMeflzVoT!x$2PBp6%7;-nG{xdiiO&FXxc7nYmlqW^58?#0L_Sa!|*nuhs41H|qP7=IQnki*$$B zDZ0b;OdS`VBXQ+Jb*IY7(z!4}Kk!|$bXl1y@f$nKgRgayt_z#WL!%<4+ssb7d(U7= z7!j@?Zh2CABwW`$gNr0F@`z46e?s>P+M;`v?9z`MTc&##P$`EAsfjS(21CQu=pH6MvsXd7@E284%rC23&O{xnWP8{B3<1bR|j;{`j&ySr($7 z%B!bS)>h~tnU{2GPKkaxwM-A4aYlx9U9X3y6w8R_^YzI10vT0fos5nU8GY()8FM{d z#_pObjhZ*SYSsP^UsY9sD5bp5YIa+cuSQkX#ek}Pq%=rv zkm4ZKLCS;F2PqIzA*4i}wnqGiqDVYciH9;FbwUb-R0=5-QY)lbNVSl1dD?m*1w$%^ zlnkjEQZ%G$NZF9OA%#OKhm;Pf9a21`dPw<@`gz&{A{9hRh|~}%B2q=9j7S}kLL!w! zN{Q4GDJD`)q?|}SJ#9geihA0TA~i*dic}RTD^gdaut;T*(jv7*ii=biDKAoAq`*jp zJ#C4R8hhFzBUMJqjMNz^G*W4#)JUz7Vk6Z?%8k?;DL7JbPg`=N=AO3bNY# literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Atlantic/Reykjavik b/libs/pytz/zoneinfo/Atlantic/Reykjavik new file mode 100644 index 0000000000000000000000000000000000000000..ac6bd69731d25fc20724798abd4f683f1f2ccbd6 GIT binary patch literal 1174 zcmd7QPe_w-9LMozO-ES7AW$-}ZK$jMYysKwL@$Wb#X)mML|W;t;7K0hbxLRLV(p4wm-~Bt`XJUIiH&J}SaVgoOWO6(&NJFm zmXO|x1NwO0O-UApH92`!Qgil8>d6s#I*_M*EnDST*GlPcJgm>J<;sidE&8%9Bd@lX z>Fa$dc@vzk1EILQUHC%>OOu+Ol`liNw{&QDN`@z5I?~=R?|P5w`^&fGLvvI=o@$iQ zb3q;3b3#5HtCaDu>gURG`Ld!~C)O;IuXA^3WN{*VTd x4v`j-9+4)IE|E5oK9NR|PLWoTUXf;zZjpAareCCCq+_II{9k&`F@XniegRAxR~rBT literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Atlantic/South_Georgia b/libs/pytz/zoneinfo/Atlantic/South_Georgia new file mode 100644 index 0000000000000000000000000000000000000000..b3311b6331471833893fcb776ac88e2a90a607a8 GIT binary patch literal 172 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34$+|NnOnFfjc8|M&p|i;r&zgRTL@j1Uq` X0~+){v+Xa?973jY*#OPAGvWdOF=!;J literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Atlantic/St_Helena b/libs/pytz/zoneinfo/Atlantic/St_Helena new file mode 100644 index 0000000000000000000000000000000000000000..65d19ec2651aeb46c42ce7a74ae6ecbf3001edbb GIT binary patch literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVL@u*62on{3kL6CLt zP$5x4|GIQ&$RnrAm9&bWEMg=b3WBW~cmlVx}=*&#wO<(j`MOtzGKTw@x|tZc$}kmCMIdGwR8G zE$98GguD2sbiYWd(yu8`+08>@!?Q_G`T3BjxUpYV9`Q@hc$caQm&lDHm1>iBNp9|U zsp?g4Wld9ssQEZ0Yl|&W`+ULnet9qI?j`Ll*_5cibj5B^lVavM|w*y#DhIgBf+I(@le&>NN8bCyuWBZ+dq9YxAW7TZ1~z> zZdYa^J8-5cm(W_f@>VRn3-Zmq{NV523ktNJOIeol%-8y5*0oaWv~8`?dNJo(%ZX)9 zI3L&@wf3255I!?4W`18|^dDAhzCe$jD^4OSM%Ik18d*28a%Am}X7$MWkpz$okQ9&{ zkR(h=7Fjr&G>|-yM3797RFGVdWRPr-bdY?IgpiDol#rYpO;Si!jwUT6FC;M}GbA-6 zHzYYEJ0v|MKO{jULnK8cM@N$+lBJ_b6Uh@v6v-4x70DGz7ReS#7s(e%7|9q(8Ohnv lB#mV4XwpXVb~K42nIowqxg*JA!tC-t%)TgKeu-{h(J%cobp8MU literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Australia/ACT b/libs/pytz/zoneinfo/Australia/ACT new file mode 100644 index 0000000000000000000000000000000000000000..4ed4467ff0f7a47e2951d3674fcc542fa3f2129e GIT binary patch literal 2214 zcmds%TTGU99LK+J!U$5)avYUVGr^F9ib0`y0E84nc{!FwfGBqG1yZpRf2obyJfvO7 zj2Y65E-H-HG{+*A8!H4>7ad4*bkndl>ZW35_WwNVqOGg$TAydn|MTqGyS+Eh_v5dq zt6ijj5bWM?!x4A$Jk{LMw*HN_wx0N(2K?`7puaS5>0F5oc4h{K+8Z_85*wHl$h4p- zGZkDIYr(%{SV+nZ3;80(Lc_0G==;B0*m$4a_FA+ifAy`VG+)=$^B*aE^A$~N?a=h) zy}G^Wpzeq}tr=ytn(@a0MP%eDa`H5`k%KLU#`V;R$J_7g3WBnu~~g#7PqI( z?mW@0`0{3(Z9_I^)vKCY_k-@5vsd%-2Q_c}n&u~VY5wpfC59eTV%IxbFxIGhT0ga< z&yU)|rnfA)yWUdDj#%pPB1_v;VT&5lZSk@!Te30I(r3ll(zFR%dP9~G8=;KL-&tno znBCWN(e57^QP!DJExXX8SiY;ff^1uaKmD8ft)oqrY{JQ0=ZnoUuwOTW` z($@Uip}g;3QT~?)^u5B#5*NRpom_Iez zisQqqB>cLS-2Bl>$FEq~)gdb%?zM{Ev-a@$%X;L^L2YXNP@7-u(xXl7s@!%=kCp9L zRbHd2Gip?mP^>LcxvHIBZ(Ds;w)N*C+cuhQb)Ti%_P)FA@pmKbiPN{*lP^!$Q+CaE z1irDr_7K&V|E7lgF*Rn6=;_1(?TqfxuF$u&YvPo4j~!Lh*QPz6)a#iKs`PC4VSBEl z+4dgaVa@w1ZC}F%yT$j{H7V#na7l+Rx(`_=&-#L-L+uLv`}4X2zgFgCa}@dcna{^5 z|G(JDO-EzIzAVgMJiEEG6Pnb;syYlR|h1r*9w=vAlJiD!7 z_J-LUusdLT!2W;@0y_k@2<#EqB(O`*Zkxb9fsJBz>e+1-vsceX2X~r z16#)I8MA3%*TA-ceS3Br2X^k+Z5`OVXSaD^_rUfs`^Pi@(*aBiFg?ID0n-Ic8!&yq zGy>BJ&#o1iUU+uRz;pxC4op9Qh5#J_S_1S0XbR93Ok04yU>bwz45l?eZ#=u^0NnxF z1M~-I5YQo@ML>^$CIMZ-v8YI0g~YEr=xl(IPNPxDR! Aa{vGU literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Australia/Adelaide b/libs/pytz/zoneinfo/Australia/Adelaide new file mode 100644 index 0000000000000000000000000000000000000000..190b0e33fabb8dd6c01f6d939df0f09e288c562a GIT binary patch literal 2233 zcmd7S|4&tQ9LMo!ihfgxT+Qj#l#HvF(&$TX>($Sv$TgpQEi-mGCFadsY2v8rY)3$6547r>iCn$Dw?dOf z;xu{NVZCAhaY?D|(bVqqnzrN!zB%{c~TXs*E`n+w7RN9 zSNn4{=u6SNQsQ*YOsB4y`cdz`bU|yrIIp$ir?u|QVZGew!Zz73uUk z8A~7NzN`<1zSK}_j6763B^~A8%jTj9*^+ry9=`6Bbb8;A&R_dw>*PKOf7&bC-U&<`%>vO{Ex$R3eRBD+MkiR{zXY!un4 zt=THFS6j1LWVg0vyU2c#4I?{7wv6l<*)+0iWZTHTk&PoeN4Ada-PUX#*}bjVKC*wL z0Z0ds79c%9nt*fxX#>&+q!CCbkX9hQur3h5NmDx_CPvyg7tnsy=m zLK=p23~3qCGo)!q*O0a$eM1_DbPj19(mSMiNcWKTA^o#84MaMKv=Heb(nO?-NE?wp zB8@~kiL?^wCDKf!n@Bs6e%hLbA{|9qiu4p|D$-S?tw>*y#v+|{xSZ3c+j9k+e}{JG z)0PRX$*l>k$?dp~g%LW!+83t9)vka>5Uoog_u4hm`e}+Mmb0UN0`gIIUj8MqT0)Y$+{S`oS z8D?!@WMN?FS-`-F%=YmOVQ_SH0TIC=AZ3h5ch2jKbI%wXpSQQ7 zc4LO~za!ZEgp=cWb8;WnYAzis-*~m-Y~mjS-ZL`LU+w#-w@N?m%JY44q(uhX<9vaQ zc^Wh=UV=;FH2Akg8sZMpkk4W@H2en*?FrN=Bk${tFUQE#E8oksT>%nys$asZugY}) zaf!%1FEf05WoC4@%qnY;S^qpEv$Kljrm6chGPYhL`&H+-JUXYVPUjA#=`HO=8r2t} z(c6yct%puaOnIBewqDk_B}XN`_7}M=ZjU4sUXp~7QAtedk;K7IB`MS|NhjWsJBC~2 zPX7f>zIaemd~a#$NuQ>by`=5~6`EePRx_G%G;?9T&MS@9yP^_weuks-{|?ctxlxj} z`NWm1uZA`IuQ6G0c0lj`a!7JIf0Bh~&Pi_DhqCB!r{ryTT^8@|(E?2 zOG|QfRbHBU-7$K9OoXnU9-ynoe$@v?u4>uWm$iKGyjHy1s}G*~PAa=ENtORSsn#BO z$k!!nHu+_3+0*jy@)lW_RWIvruapgu9@!Y-(;8Q;){Iu@rlA#Ddm%^b`ckz1&1l{H zO1L)ccl43gQGK-WTWzchlgG-(q^a;{*^)gZk0)J}CvH9`&7p5g^Piovb@-t8zG#8`uie}*IAsul*=kFUvs&*I^K!L5@H^4a_l!J_cO=Lv1PWd$L65UZ*cpJz(!<5 zRx>1IOja`}WK_tokZ~ac}uVBnU_pkT4)|Kmvh80tp2Y3nUmwG>~v0@vxeJAQ7>ekXTJjkf0z@ zLBfK>1qloi86-4FY>?m}(Lut4#0Low5+Ni+Rudy6NJx~BFd=b50)<2h2^A76Bv?qa zkZ>XKLIQ?F3<;Ul#0&`<5;Y`jNZgRXA(2Buhr|vE9uhqyd`SF|03s1YLWsl=38K|R z5eXv_MF(cNJNp4A~8jRYBfB(z9uk>DcH zMZ)U}_^*G#H%#&qIN28`dhc3#pFQH3^zPg@p5CzQGCY27GhAQ_Y%^?w%wKh_#-?LD aNXcH1>E>awC*7Tzp6X8a%!9PdDgOd<7ad4*bkndl>ZW35_WwNVqOGg$TAydn|MTqGyS+Eh_v5dq zt6ijj5bWM?!x4A$Jk{LMw*HN_wx0N(2K?`7puaS5>0F5oc4h{K+8Z_85*wHl$h4p- zGZkDIYr(%{SV+nZ3;80(Lc_0G==;B0*m$4a_FA+ifAy`VG+)=$^B*aE^A$~N?a=h) zy}G^Wpzeq}tr=ytn(@a0MP%eDa`H5`k%KLU#`V;R$J_7g3WBnu~~g#7PqI( z?mW@0`0{3(Z9_I^)vKCY_k-@5vsd%-2Q_c}n&u~VY5wpfC59eTV%IxbFxIGhT0ga< z&yU)|rnfA)yWUdDj#%pPB1_v;VT&5lZSk@!Te30I(r3ll(zFR%dP9~G8=;KL-&tno znBCWN(e57^QP!DJExXX8SiY;ff^1uaKmD8ft)oqrY{JQ0=ZnoUuwOTW` z($@Uip}g;3QT~?)^u5B#5*NRpom_Iez zisQqqB>cLS-2Bl>$FEq~)gdb%?zM{Ev-a@$%X;L^L2YXNP@7-u(xXl7s@!%=kCp9L zRbHd2Gip?mP^>LcxvHIBZ(Ds;w)N*C+cuhQb)Ti%_P)FA@pmKbiPN{*lP^!$Q+CaE z1irDr_7K&V|E7lgF*Rn6=;_1(?TqfxuF$u&YvPo4j~!Lh*QPz6)a#iKs`PC4VSBEl z+4dgaVa@w1ZC}F%yT$j{H7V#na7l+Rx(`_=&-#L-L+uLv`}4X2zgFgCa}@dcna{^5 z|G(JDO-EzIzAVgMJiEEG6Pnb;syYlR|h1r*9w=vAlJiD!7 z_J-LUusdLT!2W;@0y_k@2<#EqB(O`*Zkxb9fsJBz>e+1-vsceX2X~r z16#)I8MA3%*TA-ceS3Br2X^k+Z5`OVXSaD^_rUfs`^Pi@(*aBiFg?ID0n-Ic8!&yq zGy>BJ&#o1iUU+uRz;pxC4op9Qh5#J_S_1S0XbR93Ok04yU>bwz45l?eZ#=u^0NnxF z1M~-I5YQo@ML>^$CIMZ-v8YI0g~YEr=xl(IPNPxDR! Aa{vGU literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Australia/Currie b/libs/pytz/zoneinfo/Australia/Currie new file mode 100644 index 0000000000000000000000000000000000000000..865801e5e0befe4e6d1b17a10ed15f721c5ac335 GIT binary patch literal 2214 zcmds%YfP1O9LK*$U<9dTIpv~+R&ogs2QSDeB{e`$NsPlaMMC6anFm27%hPXWo?fJO zag$|Bq%nHqRJv#zi;MZoS8kJXZ5OAy=#4*J^#i2 z?dF4hN~sRn3>&eTOAqOmmhW`y%$>Tecv!Q>ty60HF{S?Uy3&3awe$};r4$-5a*7_B{DEwOIDr8k3|rVUszry#Tiz3=byw&|TfCu1OKRVR`yn^YAU?{Z+eE zU(}}cxeaPaE7ykjLN!imwI)x!HT_&>&7=9&^8S3=I5^85I5E{8JaV0Fdit_GWZ&B6 z;O7?H7_HXo-?XK8Ol|(p)Sf=1tqJ|w7IReFF2AVlWBb+dsp;W&TD9YidOgy&*B^odDC5Wv@H3Bx zQ-Oc6lWPv`-pCZpUND=%?8ddT9n5|(8^Y`evnBqQJz+Kl?8>#XEzG`LI~&97%(b&M z%-%4Y19k^&57-~DL12f#7J)qin*?_0+Sw+sPhg|KPF*`&1@`LN*(_$anC)Wri`g({ z$H0~`d&X=U*fp?iVBf&Tft|Z{whrtavw2|mnC%1m$20)w0Hy^%4=_!@bOC4srVl_P zFr9Giv;xx$pc$BMVA_G{2c{uFM=&h`dV*;R&=pKufWBZFgXs*WH9&7%JIw*Q1GER| z56~c>LqLmw9sx}Px`b&HrcangVLF9r6{c6Non~RWg=rV2UzmnrItH{1=o!#7pld+e zfW85Z13Cw^4(J`wJWTgoJMF{t57R(Q2Qe+g^bpfTOcyb2#PktpB+yBql|V0nW&+)G y?X(lS+edaYOR|05Y_Bh~WFE?z8~Z2nWBGsp literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Australia/Darwin b/libs/pytz/zoneinfo/Australia/Darwin new file mode 100644 index 0000000000000000000000000000000000000000..cf42d1d878b364a3d720997b1511ae71da458b06 GIT binary patch literal 318 zcmWHE%1kq2zyQoZ5fBCeP9O%c`5J)49KW?o=Il}baXMrd$LZs76=$}`cAQ=AP;qWS z703B@r3xlSC}d!$S^-qgFnI+d3j;&z3@G{~7C8st<)W+o_v mxwUQr$R?nRfnEl>m<8fupku+#1~~=BG^AwcTRWH&% z@OR#D!}HF~^9*y2V;?u$i~Ui*3>Unr;h|#h`=^U+q&v+!+R>nMtzq7YciMEduSJtX zrMlMCYg66JrJ}>;X^oK2svh}z)68#rxcu|N%>Qz#1td;dz$c*=IP;tckXpkM=35w8iGxsLfw{Ot;kipj+o3&}}&*THv!y z(Xl;>{_PFL{4{2zdEA{->mexIPcl5t&%Ln3YMc0^C9-n6EkDb-3mY;3)jxMb! z|G?H}w(8FGKFdgW*)k)KSeE|=tqVA%b-#Bi`@83q^XVb2AKI%8r>nKGyI8qLvvpT% znl|l=Q=YfbHdkcX-FeF_e|3x%B!yUERFD}@&Plh``yq@Zi zpY%enC-Co+Hyd*cv?ep3Bcp)NJRVLJ{EIzJIW#8dTg3uqY7F`#8Y&w!=@T?5(% z^bONEOy@AI!}Jc*JWTgoJMF{t57R(Q2Qe+g^blww&_$q)Kp%ld0-Xd}3G@Qb{dN5D5j;Do?@Dc=_;nJn7(2fi|H)TTA;T;bAj#x?FIVl+G#M*VW7o8kAWrw nUBG-el4q+|cRqdrbP- zWBSMTWoAg@b(PV&TZYO}6}+%iek!X~?twz_Dhk!G>Y!xi1l918t}>!eS2famFC#y_ zSE1w|Oz7DomDTi0MqR(GMn7pa*{51{&b3P>cjqNN=ExzFxA>4AyW^sa%NISqvOy*U z8&v-I8kyKBTTdELE|WeS)RW)rGX;OG(^Fbjn4hoA)P+q`P0`^jU0mmxsWr)ZnktrG z=7&_tG*?RVl2q93F4I#!sTp1Vl^L<;YUaDgQugAeDu2){v;I7(W;Z`Fb58%ED;h7G zxx0_+dD3X+N4Dz)WgE@H$%}MlXpUJlc%ojMm}{!KRjMU{*|OxHd=+WSk?6xTwe(hs zEIS{smY)d7iv4fZ%G%emO5Il~8Z)cQ+jVt8n^}`}Pp?hCZPxWVqu0luGV4G7rfb^k zjQqV?Z@3#Z8?Tn?O&4~_=95ZoIS`Sp+e_58>M61<7K;VqI(&V0jQd8@)VV325#aFBSAfRKoMZAeH=zBVW%DkLl3&0sB^YI dOSwZmcT}cp|M2WbFQdqFgI>_hC>jo(zW~-0_U8Zq literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Australia/Lindeman b/libs/pytz/zoneinfo/Australia/Lindeman new file mode 100644 index 0000000000000000000000000000000000000000..8ee1a6f548b0518bf38c99632a6cdbac5d23dc32 GIT binary patch literal 513 zcmWHE%1kq2zyPd35fBCeF(3x9`5J)49KU6A=Il}Ua5`i&!|CJU1!uO0HJn{;S#WMa zF~j+G=>p~g%LW!+83t9)vka>5Uoog_u4hm`e}+Mmb0UN0`gIIi0T~apUKTTG&p6ef zbE&^Uw_;5L6C)Hdvp^sdh+<&qUIVm{Vb%slAiHM)11B=u$2Ww*(bWY+1c!i>F*1Tk zh7c0`^B)L`-Y&WVqCx%w(?Gw0XpsNFG|-PA8stw94e~392Kg66gZvD#7vygc4e~n( zfcy`pfnfjwpl|@wz_0)TPdaC|tlaFl;~o6h0st6hG-el4q+|cRqdrbP- zWBSMTWoAg@b(PV&TZYO}6}+%iek!X~?twz_Dhk!G>Y!xi1l918t}>!eS2famFC#y_ zSE1w|Oz7DomDTi0MqR(GMn7pa*{51{&b3P>cjqNN=ExzFxA>4AyW^sa%NISqvOy*U z8&v-I8kyKBTTdELE|WeS)RW)rGX;OG(^Fbjn4hoA)P+q`P0`^jU0mmxsWr)ZnktrG z=7&_tG*?RVl2q93F4I#!sTp1Vl^L<;YUaDgQugAeDu2){v;I7(W;Z`Fb58%ED;h7G zxx0_+dD3X+N4Dz)WgE@H$%}MlXpUJlc%ojMm}{!KRjMU{*|OxHd=+WSk?6xTwe(hs zEIS{smY)d7iv4fZ%G%emO5Il~8Z)cQ+jVt8n^}`}Pp?hCZPxWVqu0luGV4G7rfb^k zjQqV?Z@3#Z8?Tn?O&4~_=95ZoIS`Sp+e_58>M61<7K;VqI(&V0jQd8@)VV325#aFBSAfRKoMZAeH=zBVW%DkLl3&0sB^YI dOSwZmcT}cp|M2WbFQdqFgI>_hC>jo(zW~-0_U8Zq literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Australia/Melbourne b/libs/pytz/zoneinfo/Australia/Melbourne new file mode 100644 index 0000000000000000000000000000000000000000..3f2d3d7f176b9e461f9730117bbf4771528da823 GIT binary patch literal 2214 zcmds%TTGU99LK+}!U#&qa>zjmwG<5DMa7_$JODz9p}ZVRB|sEA_yQ_eNk31ci^{Y% znK4D0(M5&Pn&w!Y@W_j&6)&vy+u4;=LO|TJgH7{P7%aARmASUQ@E$8#mS_Qg zj1?zDSV`nHE4lHbm0rDMWtT5l`EZ|A^qsbc&R*2RuMcTM+xy!1Y_~Qwcc^mfVLei| zPgQx1s?MlUO=7V&N9U?`O1*9IRoRxGi)`y?w$*)_Zrl3j*rRVxv&T-}WRJf%Zco_v zwmtZ@1-FH%zWg^eN_}`z`6a=(9C!3=vz|VX> zP6htOPHs3FqYg%6_Tt%X2D2N_ZabL$U^ax=5oSyLFMGml3fPrrw=K-RJiCoycIMe_ z4YN1Q=78M++XMCoY!KKXuti{xz$SrRdUo3c_UYMe6th#$ZmXERdUl(|>=v_K%ziN& z#_Sl_GG@=frh#1p+XnUxY#i9RXSa1=@1EV}f!zb!$Lt@|089rkEdY9eX#%DTm^NVg zfN2D#6P{fwFum~Xnt|yCrX84m01W{;0<;9^3D6XvE10$beZe#a(-}-_fZlj^%>lXt zv)~gK#PDL0ZjtBglQ9|PnbqwI)!N!rdOU_voPJlvW2tnm;8i#h+ZT2&FEJ_!Glc B{)PYm literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Australia/NSW b/libs/pytz/zoneinfo/Australia/NSW new file mode 100644 index 0000000000000000000000000000000000000000..4ed4467ff0f7a47e2951d3674fcc542fa3f2129e GIT binary patch literal 2214 zcmds%TTGU99LK+J!U$5)avYUVGr^F9ib0`y0E84nc{!FwfGBqG1yZpRf2obyJfvO7 zj2Y65E-H-HG{+*A8!H4>7ad4*bkndl>ZW35_WwNVqOGg$TAydn|MTqGyS+Eh_v5dq zt6ijj5bWM?!x4A$Jk{LMw*HN_wx0N(2K?`7puaS5>0F5oc4h{K+8Z_85*wHl$h4p- zGZkDIYr(%{SV+nZ3;80(Lc_0G==;B0*m$4a_FA+ifAy`VG+)=$^B*aE^A$~N?a=h) zy}G^Wpzeq}tr=ytn(@a0MP%eDa`H5`k%KLU#`V;R$J_7g3WBnu~~g#7PqI( z?mW@0`0{3(Z9_I^)vKCY_k-@5vsd%-2Q_c}n&u~VY5wpfC59eTV%IxbFxIGhT0ga< z&yU)|rnfA)yWUdDj#%pPB1_v;VT&5lZSk@!Te30I(r3ll(zFR%dP9~G8=;KL-&tno znBCWN(e57^QP!DJExXX8SiY;ff^1uaKmD8ft)oqrY{JQ0=ZnoUuwOTW` z($@Uip}g;3QT~?)^u5B#5*NRpom_Iez zisQqqB>cLS-2Bl>$FEq~)gdb%?zM{Ev-a@$%X;L^L2YXNP@7-u(xXl7s@!%=kCp9L zRbHd2Gip?mP^>LcxvHIBZ(Ds;w)N*C+cuhQb)Ti%_P)FA@pmKbiPN{*lP^!$Q+CaE z1irDr_7K&V|E7lgF*Rn6=;_1(?TqfxuF$u&YvPo4j~!Lh*QPz6)a#iKs`PC4VSBEl z+4dgaVa@w1ZC}F%yT$j{H7V#na7l+Rx(`_=&-#L-L+uLv`}4X2zgFgCa}@dcna{^5 z|G(JDO-EzIzAVgMJiEEG6Pnb;syYlR|h1r*9w=vAlJiD!7 z_J-LUusdLT!2W;@0y_k@2<#EqB(O`*Zkxb9fsJBz>e+1-vsceX2X~r z16#)I8MA3%*TA-ceS3Br2X^k+Z5`OVXSaD^_rUfs`^Pi@(*aBiFg?ID0n-Ic8!&yq zGy>BJ&#o1iUU+uRz;pxC4op9Qh5#J_S_1S0XbR93Ok04yU>bwz45l?eZ#=u^0NnxF z1M~-I5YQo@ML>^$CIMZ-v8YI0g~YEr=xl(IPNPxDR! Aa{vGU literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Australia/North b/libs/pytz/zoneinfo/Australia/North new file mode 100644 index 0000000000000000000000000000000000000000..cf42d1d878b364a3d720997b1511ae71da458b06 GIT binary patch literal 318 zcmWHE%1kq2zyQoZ5fBCeP9O%c`5J)49KW?o=Il}baXMrd$LZs76=$}`cAQ=AP;qWS z703B@r3xlSC}d!$S^-qgFnI+d3j;&z3@G{~7C8st<)W+o_v mxwUQr$R?nRfnEl>m<8fupku+#1~~=w131_w!EjYV8bOI;; z?**LOBo;_9CM}R!UcW$7HD-b4`cn$7h5HrUSko2Um1`EbziVFL$sD)9^IYlzCPpx1 zWO#lD@ literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Australia/Queensland b/libs/pytz/zoneinfo/Australia/Queensland new file mode 100644 index 0000000000000000000000000000000000000000..26ffd9acb76d06cf1d6569e7bcf545da8c61a6ba GIT binary patch literal 443 zcmWHE%1kq2zyPd35fBCeK_CXP`5J)49KU6A=Il}Ua5`i&!|CJU1!uO0HJn{;S#WMa zF~j+G=>p~g%LW!+83t9)vka>5Uoog_u4hm`e}+Mmb0UN0`gIIUj8MqT0)Y$+{S`oS z8D?!@WMN?FS-`-F%=YmOVQ_SH0TIC=AZ3h!ihfgxT+Qj#l#HvF(&$TX>($Sv$TgpQEi-mGCFadsY2v8rY)3$6547r>iCn$Dw?dOf z;xu{NVZCAhaY?D|(bVqqnzrN!zB%{c~TXs*E`n+w7RN9 zSNn4{=u6SNQsQ*YOsB4y`cdz`bU|yrIIp$ir?u|QVZGew!Zz73uUk z8A~7NzN`<1zSK}_j6763B^~A8%jTj9*^+ry9=`6Bbb8;A&R_dw>*PKOf7&bC-U&<`%>vO{Ex$R3eRBD+MkiR{zXY!un4 zt=THFS6j1LWVg0vyU2c#4I?{7wv6l<*)+0iWZTHTk&PoeN4Ada-PUX#*}bjVKC*wL z0Z0ds79c%9nt*fxX#>&+q!CCbkX9hQur3h5NmDx_CPvyg7tnsy=m zLK=p23~3qCGo)!q*O0a$eM1_DbPj19(mSMiNcWKTA^o#84MaMKv=Heb(nO?-NE?wp zB8@~kiL?^wCDKf!n@Bs6e%hLbA{|9qiu4p|D$-S?tw>*y#v+|{xSZ3c+j9k+e}{JG z)0PRX$*l>k$?d7ad4*bkndl>ZW35_WwNVqOGg$TAydn|MTqGyS+Eh_v5dq zt6ijj5bWM?!x4A$Jk{LMw*HN_wx0N(2K?`7puaS5>0F5oc4h{K+8Z_85*wHl$h4p- zGZkDIYr(%{SV+nZ3;80(Lc_0G==;B0*m$4a_FA+ifAy`VG+)=$^B*aE^A$~N?a=h) zy}G^Wpzeq}tr=ytn(@a0MP%eDa`H5`k%KLU#`V;R$J_7g3WBnu~~g#7PqI( z?mW@0`0{3(Z9_I^)vKCY_k-@5vsd%-2Q_c}n&u~VY5wpfC59eTV%IxbFxIGhT0ga< z&yU)|rnfA)yWUdDj#%pPB1_v;VT&5lZSk@!Te30I(r3ll(zFR%dP9~G8=;KL-&tno znBCWN(e57^QP!DJExXX8SiY;ff^1uaKmD8ft)oqrY{JQ0=ZnoUuwOTW` z($@Uip}g;3QT~?)^u5B#5*NRpom_Iez zisQqqB>cLS-2Bl>$FEq~)gdb%?zM{Ev-a@$%X;L^L2YXNP@7-u(xXl7s@!%=kCp9L zRbHd2Gip?mP^>LcxvHIBZ(Ds;w)N*C+cuhQb)Ti%_P)FA@pmKbiPN{*lP^!$Q+CaE z1irDr_7K&V|E7lgF*Rn6=;_1(?TqfxuF$u&YvPo4j~!Lh*QPz6)a#iKs`PC4VSBEl z+4dgaVa@w1ZC}F%yT$j{H7V#na7l+Rx(`_=&-#L-L+uLv`}4X2zgFgCa}@dcna{^5 z|G(JDO-EzIzAVgMJiEEG6Pnb;syYlR|h1r*9w=vAlJiD!7 z_J-LUusdLT!2W;@0y_k@2<#EqB(O`*Zkxb9fsJBz>e+1-vsceX2X~r z16#)I8MA3%*TA-ceS3Br2X^k+Z5`OVXSaD^_rUfs`^Pi@(*aBiFg?ID0n-Ic8!&yq zGy>BJ&#o1iUU+uRz;pxC4op9Qh5#J_S_1S0XbR93Ok04yU>bwz45l?eZ#=u^0NnxF z1M~-I5YQo@ML>^$CIMZ-v8YI0g~YEr=xl(IPNPxDR! Aa{vGU literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Australia/Tasmania b/libs/pytz/zoneinfo/Australia/Tasmania new file mode 100644 index 0000000000000000000000000000000000000000..92d1215d60f929545276892330917df09eb8d1e4 GIT binary patch literal 2326 zcmds%YfM*l9EU%DE+fcGmQ(Jbm0UtDUJz385+JA~#+?)ik&9*i5k#`2@64jLSY<9* zwnQ4EHx8tWwy}t8V~xPtx~Dm+tBG^AwcTRWH&% z@OR#D!}HF~^9*y2V;?u$i~Ui*3>Unr;h|#h`=^U+q&v+!+R>nMtzq7YciMEduSJtX zrMlMCYg66JrJ}>;X^oK2svh}z)68#rxcu|N%>Qz#1td;dz$c*=IP;tckXpkM=35w8iGxsLfw{Ot;kipj+o3&}}&*THv!y z(Xl;>{_PFL{4{2zdEA{->mexIPcl5t&%Ln3YMc0^C9-n6EkDb-3mY;3)jxMb! z|G?H}w(8FGKFdgW*)k)KSeE|=tqVA%b-#Bi`@83q^XVb2AKI%8r>nKGyI8qLvvpT% znl|l=Q=YfbHdkcX-FeF_e|3x%B!yUERFD}@&Plh``yq@Zi zpY%enC-Co+Hyd*cv?ep3Bcp)NJRVLJ{EIzJIW#8dTg3uqY7F`#8Y&w!=@T?5(% z^bONEOy@AI!}Jc*JWTgoJMF{t57R(Q2Qe+g^blww&_$q)Kp%ld0-Xd}3G@Qb{dN5D5j;Do?@Dc=_;nJn7(2fi|H)TTA;T;bAj#x?FIVl+G#M*VW7o8kAWrw nUBzjmwG<5DMa7_$JODz9p}ZVRB|sEA_yQ_eNk31ci^{Y% znK4D0(M5&Pn&w!Y@W_j&6)&vy+u4;=LO|TJgH7{P7%aARmASUQ@E$8#mS_Qg zj1?zDSV`nHE4lHbm0rDMWtT5l`EZ|A^qsbc&R*2RuMcTM+xy!1Y_~Qwcc^mfVLei| zPgQx1s?MlUO=7V&N9U?`O1*9IRoRxGi)`y?w$*)_Zrl3j*rRVxv&T-}WRJf%Zco_v zwmtZ@1-FH%zWg^eN_}`z`6a=(9C!3=vz|VX> zP6htOPHs3FqYg%6_Tt%X2D2N_ZabL$U^ax=5oSyLFMGml3fPrrw=K-RJiCoycIMe_ z4YN1Q=78M++XMCoY!KKXuti{xz$SrRdUo3c_UYMe6th#$ZmXERdUl(|>=v_K%ziN& z#_Sl_GG@=frh#1p+XnUxY#i9RXSa1=@1EV}f!zb!$Lt@|089rkEdY9eX#%DTm^NVg zfN2D#6P{fwFum~Xnt|yCrX84m01W{;0<;9^3D6XvE10$beZe#a(-}-_fZlj^%>lXt zv)~gK#PDL0ZjtBglQ9|PnbqwI)!N!rdOU_voPJlvW2tnm;8i#h+ZT2&FEJ_!Glc B{)PYm literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Australia/West b/libs/pytz/zoneinfo/Australia/West new file mode 100644 index 0000000000000000000000000000000000000000..d38b67e2f953dcdfe942ab3a5123f63d24313515 GIT binary patch literal 470 zcmWHE%1kq2zyPd35fBCeVIT&v`5J)49KS<*=IpT*I303c;q>w131_w!EjYV8bOI;; z?**LOBo;_9CM}R!UcW$7HD-b4`cn$7h5HrUSko2Um1`EbziVFL$sD)9^IYlzCPpx1 zWO#lD@ literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Australia/Yancowinna b/libs/pytz/zoneinfo/Australia/Yancowinna new file mode 100644 index 0000000000000000000000000000000000000000..874c86505c896406516a16dd21b9ddd8d0ba2d95 GIT binary patch literal 2269 zcmbW%eN0t#9LMo5ch2jKbI%wXpSQQ7 zc4LO~za!ZEgp=cWb8;WnYAzis-*~m-Y~mjS-ZL`LU+w#-w@N?m%JY44q(uhX<9vaQ zc^Wh=UV=;FH2Akg8sZMpkk4W@H2en*?FrN=Bk${tFUQE#E8oksT>%nys$asZugY}) zaf!%1FEf05WoC4@%qnY;S^qpEv$Kljrm6chGPYhL`&H+-JUXYVPUjA#=`HO=8r2t} z(c6yct%puaOnIBewqDk_B}XN`_7}M=ZjU4sUXp~7QAtedk;K7IB`MS|NhjWsJBC~2 zPX7f>zIaemd~a#$NuQ>by`=5~6`EePRx_G%G;?9T&MS@9yP^_weuks-{|?ctxlxj} z`NWm1uZA`IuQ6G0c0lj`a!7JIf0Bh~&Pi_DhqCB!r{ryTT^8@|(E?2 zOG|QfRbHBU-7$K9OoXnU9-ynoe$@v?u4>uWm$iKGyjHy1s}G*~PAa=ENtORSsn#BO z$k!!nHu+_3+0*jy@)lW_RWIvruapgu9@!Y-(;8Q;){Iu@rlA#Ddm%^b`ckz1&1l{H zO1L)ccl43gQGK-WTWzchlgG-(q^a;{*^)gZk0)J}CvH9`&7p5g^Piovb@-t8zG#8`uie}*IAsul*=kFUvs&*I^K!L5@H^4a_l!J_cO=Lv1PWd$L65UZ*cpJz(!<5 zRx>1IOja`}WK_tokZ~ac}uVBnU_pkT4)|Kmvh80tp2Y3nUmwG>~v0@vxeJAQ7>ekXTJjkf0z@ zLBfK>1qloi86-4FY>?m}(Lut4#0Low5+Ni+Rudy6NJx~BFd=b50)<2h2^A76Bv?qa zkZ>XKLIQ?F3<;Ul#0&`<5;Y`jNZgRXA(2Buhr|vE9uhqyd`SF|03s1YLWsl=38K|R z5eXv_MF(cNJNp4A~8jRYBfB(z9uk>DcH zMZ)U}_^*G#H%#&qIN28`dhc3#pFQH3^zPg@p5CzQGCY27GhAQ_Y%^?w%wKh_#-?LD aNXcH1>E>awC*7Tzp6X8a%!9PdDgOd<ProhH&2#cWpB_Eq<+pdBbKU&F0*DTs+K|`g5Yx zURVCJlvnk9Q=X)s_%imsKpTwSOVHIg9V}(y}WiCTGn~n_+&I`-l0>6*LAEhp5Ja;z2Q?xI{HJ z6rZTZh~h-CqIglvC~g!xiXX+0;uzIfQaqy?Q;I9amRa$o7*m`n))a4wImMk~Pd)Rk R4*D_cpZ@lO`5%jE`wO!17YhIY literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Brazil/DeNoronha b/libs/pytz/zoneinfo/Brazil/DeNoronha new file mode 100644 index 0000000000000000000000000000000000000000..95ff8a2573f4dbb03892a576c198573895a01985 GIT binary patch literal 728 zcmb8sJugF10EhA0BBX?~8q}dF76~=9jm8&1Lehw^2~A8!Hp~oXVqiF<_yXEaBoewc z!P4l9mxUTMp_(Gcd2V8|)V(?R-Q4BgJpcH@Y9i$Pxti=74%d)9Ja_CJuPi6K#DCT!%DIOGW`xX7(G9#*|bMm88sM@Sw z)-Fy&-FL3*N6+fBT$5irYpN%+CH)&2)vE`!96M8e+l#WlJ*@)sULCk!62b7J4qm53 zxSG_F5G-re&R0 z77QHrYp@_b_(xbmeuBa&C`+t4B9>ymhMvnozf4Loy-w@;JU{)}AN8N#@!jJXgE4qN zUy0i0#z^2_&o%BRygW(w^7;BV-)sG_XI%ME&!6+mzH^1TKh-9KvG;ZGOh!*k_|Bf# zw^UAEzFAXmrR3DfZXHTQ|>WB&Uex%bI4@<~&>5PT%h$c5`cw*c{9u^zTStW(BgchbhmYFhHXAZ11 zvo3yWV?8g)>{G)!r!#2oJ(9I^TgICE4)4&S%HPcWJO0vnp<`zLwzMv|Q`vb)SymA@(vzR;l6 zGTS^z831_6WWvy-f<8x`NsYYOSg z>)Jy4^18;5&XCrS-jL>y?vVD7{*VTd4v`j-9=)zfq)V@B6X_Fa6zLRc73mde7U>pg z7wH#i80i>k8R^;Unnt?zy0($Ny{>VjbEI{occgiwd!&7&e`EuY9YD4K*#l$~kX`V) zZ9w+H>ox+}39s7ZflUe z@w&}Hb_dxWWPgwiLUst*B4m${O+t3b>$VBmC$HNmWT(7ttB}3&y3Im%3)wDYzmN?> zb`04vWX~`;?VldDB+uKcyKemT|FdwpbKTk%McwyEQ7|43hr%J9p}}}06y-zi-z1n& AE&u=k literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Brazil/West b/libs/pytz/zoneinfo/Brazil/West new file mode 100644 index 0000000000000000000000000000000000000000..b10241e68dd415354ef12cc18fb1e61df3558104 GIT binary patch literal 616 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 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Canada/Eastern b/libs/pytz/zoneinfo/Canada/Eastern new file mode 100644 index 0000000000000000000000000000000000000000..6752c5b05285678b86aea170f0921fc5f5e57738 GIT binary patch literal 3494 zcmeI!Sx}W_9LMp4A`+sAikYIhq=?EQh6_?+E`)l-1x#^!H1rH&^5lY8h%GMZ)G&<( zl@6x3Vul+gX$Wd+)08OgDL$H#3+RJ;qUZE{-`j5Tu8UsgJ)bkox&D3saS2IN!)*U} z>X`rV^4u^l-@waFg%5>%F-Ky$v zfxf-JOx(#oA@%A4QJuL<-d&I_?xkeO`xEDh2eE1LVV|+$QAmP(+;Oh@%O_Gk@f@R` zJRYrUuJ=|?&qnBHM_VfA9)IoH=u)<9r*>O%S=E}WbZzMr?&6uOGfWAOs7tbL=mFu` zx7UxyZiaWYj%{~=z z__*%p@lR)ZkT1<&e`+!k(Tihwg4GV#nF#uq<~mJTgR%m{TD}`uobb z_@g4O=AIlCo+n0K^U!-;n(IH|=Rf2Q`_zK6bkuu5So=Do-N=~adC6cou^z>uZ>YY@7 zJtMzNrNle6%q&pvhATZYC0ot%JD_LB&Qr6Umt< zeE)2uNY8M{`FmQ4j0rJv!Iw5s%k6poYP&zrum4lOb-4;w*laG>kzzM@m#c7_&C~ks zZGAQzVvn;8=x^SU=6%b&!{W?%*=%msN8EFap36KlZ>LovI;YY?F2>=oSBm_tdkRTvYK*E5;!O{c* zi3Ab~Bo;_8kZ2&`K;nS}1c?X|5+o)_P>`q~VL{@81jf=t1_=!k8zeYrMTakhhsVSR z2oMq>Bt%GzkRTyZLc)Z^2?-PuDN7S7BvweUkZ2*{LgIx442c*LG9+e5(2%GhVMF4E z1P+ND5;{v0J0y5W^pNl&@k0WLL=Xuf5}O(dL1 zJduDR5k*3Z#1siC5>+IuNL-P?B9TQxYiVMO1Q&@e5?&;}NPv+DBOyj&j072pG7@Ga z&PbpwO{9@fTbfuS!L~HfM#7E68wofPaU|qO%#olYQAfg##2pE|rHMQedP@^~B>0vl z`bhYZ_#+1Zas(iU0CEf<2LW;vAcp~R93Te*awH&!f~7eYkb}X}91Y0fU}=sAM-##_e!|ATe{f01&0NPcCmNu8r(HF)a!2+Ad$WR literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Canada/Mountain b/libs/pytz/zoneinfo/Canada/Mountain new file mode 100644 index 0000000000000000000000000000000000000000..3fa0579891a9762b7c131ec5ece5d6d02495bfc0 GIT binary patch literal 2388 zcmdtiZ%oxy9LMnkp}>tlrxHU!BP|I6Mi>RMixs4Hr-gV79MKa` zWy~@ORD#wDQ`cy0pgG+7qjT5gvJx#Ton@mxTP*C}&ig!K>rs!|`riHS>v!St=j~eM zUXw2VansCSc(~Wi!~2XE#!j5?8XVAX4h5_3oiFKb?>4pP#Y=i`+jOz7;S=4Pc~res zc2V|4^{W1ik7d8_Bk^fRnD);9y~$e>Ej};5AWz4AE&iN%MowO;6u!Z1>F@KdQO! zU)DGE99MCkIr8SM18QEmU(Rp%Ox%+Bjl6Z)dtyP4dFNL{V#$7ozH4Z=Si1cuefO>{BDe8`zNc-My0>`0zOQz(%3Jud z&d*z|@_!qZ18x9bN#SgMMv+`6PQPCc}w zNSAs7RatDZc9nmpTvsD?MdmS8@qLo4oO?l3jz-9pzEQDi-?)5utWQ+6dF3O+9iqDS zkX+rhRy^uFscYKX)nmyA^yBqzRU5uT*A*10x+@-CAD^u1k5_7UaHMj-o1+_k_(iSl zTp^!086lqZWXq=p#zkXAjBMKO6;EgWCD%0`66>SR$qmJwVuNo|d$JBF&)8YLF?xsE zI6R^^O?cF^T|N4_FDg}YORL^In4?;%>-3hLu_`cN%IBJ(DLpT6eGjwWa=FtboO$LcGtUb1l(@`ngb1)-u79yKzd6>1EDl*6vOKFsS#2nq)JGc zkUAlSvYJXErQ)a+QY?;YA?4zz7g8{eiXkOKYK9aIsTxu?tEn4OIIF1~QaY=t9a21w z>LKMr>W35%sUT89q=rZlkt!l(w3<31g|wPVBBivNS|Y{ds3uZQj(Q>m<)|o9QjVG; zMMbKLlohEfQdq00EK*vlsV!1mtEnzhUZlQAfjKISl$fK&NRc_JjFg$9&PbuHrqW2M zt)|vUv8|@sNV$=EBLzn)j+7j!IsSi(?l7TWY=WQU%t%R3NlkL5rKO~$q&ofvy_=8y literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Canada/Newfoundland b/libs/pytz/zoneinfo/Canada/Newfoundland new file mode 100644 index 0000000000000000000000000000000000000000..65a5b0c720dad151ffdcba3dbe91c8bd638845c6 GIT binary patch literal 3655 zcmeI!c~F#f9LMp+gDcHj5j!juuoOA8R?9L2`Du%oBL1}16dpAygY5AtBXOLLDRsuQ zV=|z!f|Lj;T{Skl>`^gCP5U`+bZv-ep}BY;${S9wkjrz@V&n5bgwR^MMy}GWhrN~q8T=CXJi%T{=?R(7`biKZ&r~8d% zEv|Lu1^1gqt?R96J$!GcYw)1IBJHpcDhebBS(ht+X4j?JF^eFFLWr`K5ro=#C;jcF|o-WQ_| z_5VqHEy9(G_(B|xZBU1gm5C#r!sLPU z?y9;w&ssg=&ZwyCyNaIrza={4jEFwfBzt|Y#8vygmREngRa{fKMPB>bTG4yn-oSN* z*~aw~D+7J*&;P3Lkmm#a#!UCebek85y%4)X z7oPPG+ffp@<;WcWtrgYg@NF6X+g28vx4)9;ACXsR-mz?~F)|~^ywgZ9QU;}(sVSX} z)YA(BX#?Z^X$K|;Mz`PL+0POn?e1WHhl02|7a`%zjkKBKx0k*mWNDGi2*y<)A zT|nA^^Z{uE(g~y$NH1)4GmviB>UJRgKpKK{1ZfG<6Qn6fSCFad z9Hcw8x;;pLkOm}Lpq1F4(T1zJfwR_`;h+G>INbmL|TaS5NRUPMWl^LACX2PokUuR z^b%<%(oLkDNI#K=+UkxXEk$~YG!^M8(pIFeNMn)CBCSPwi!>MMF4A6G-Cv}^wz|Ve zi;*59O-8zmv>E9$(rBd9NUM=vBh5y-jkFu-H_~uh-EpMlNY9a`BV9+@j`SUAJkoij z^+@lL<|Exl+Hb4-k8A*2y#tUfV5|24vI&q~fNTR~A0Qh6*$K#2K=uN%8Iaw8YzJGt zACL`Ut9JylC2aMcKsE)kE0Ar0>K%e?5nH`S zkWFH%cL}mhkbQz|6lA9$TLsxG$Yw!y3$k61{eo;5TfJkDEn}B;d)@d*Rc6BFYT;}ar(2eU89 AC;$Ke literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Canada/Pacific b/libs/pytz/zoneinfo/Canada/Pacific new file mode 100644 index 0000000000000000000000000000000000000000..0f9f832821b6cff451b5ecccd0b6eac8e53a9190 GIT binary patch literal 2892 zcmd_rZ%oxy9LMns{y|XkWTHf9Cp8gN1QbQlOw(O45tS>68U9INn1+g7v=nvG%KmZ4 z{L?D>Mn1?@qGL9j$`IKq6rElTPK-&|e4bf{|Z_SPpeH>n@yU)QH}i~2FS zL7#SgqZ%U)>c*yh>Wu${oUJwLoUdAb+WWEb)$EX;x4mwfDvITog4O1HNw)l&HqZQ) zlPVWt$C!)m1^QB-xvDv4f^Kdbty)5&bxVDOx_r^EuN=6gT8}sB-^&}-)v8Xpw&t+9 zUgndw%}33R!dkhx_yhAtMy32Y`2}-pRH?KNt5mmp=SfG8Qq|G^yuQ<%r#esP>c766 zq5Oy3I`Cnfa_x@QK`-@E!RveKE^CIFu1jO2+uShIeM+c=BwR5)^koTE-S2d_Azh9qCr z56=8t4UIUVW8x}QjK5W4!?vhc-**z%vP=!HIUpk%O3cWL?Gj(T#EdF=MiRD9HHrCe z=%k_{X0&^q9+TPKB*$dwu}RHlTu6#eiSDLSE=B3_cP^<3$2)cE*{{^Z{gE>1@JH&Q zvJRR2_G{|l!gDgEbg!A3Q6rBmf5l82B{F^5Dl`2?gLaR6S-Bey>a_5cDy@2#p4mEE zJ^D_y%sREgq;K3Ivp=0>G8PrfoSpGz?!;`F=T#;%I#oRL+l;4kfMg|~G+7rW=mi6> zs|8;~>ui66TDZrrANL(pi%OgH6E(Y3&hle=am5C;B;6-VU)*7qjjWX?^NY>$@Jh*b zXPeyCQpt}=HTiXUQV=r06nrv6R$L62r*`J*mET9JRbID#y2`H#vtsq?vL>}=Y)`$m z@R%x!Xw~a7_NaA%Q1PbJ8n5rNtdFcT>uc&{Lwl)twxUX&JDq1XmXyn;Lo-ZCPLXWh z9cO}rg1dCJ&wukT5P0=Xmn#r>*93J91j@F!dN|*`oL9|C_qgUvvp3V;$LyWsvA=Sc zE68~~|Dp~7dvYduuOO8`N`ce@DTbr122u{B9!NouiXbIHYJwES(N+a13sM)PFi2&P z(jc`#ii1=KDGyQ~q(Df8kP;y^LW<;QtAv!v(bfqm6jCXqR7kCmVj8mhZGR0AW}l4hDZ^SDk5b>>WCE5 z(N+>EB~nYIm`F8|aw7Fa3W`(|DJfD@q^L+$k+LFnMGA{l*3p(0sV!1mq`F9Xk@_M9 zMk+Cn3hcC@8NYK;^dsWwt>q~1uuk%}WFM{14~9jQ7}cBJk| z;gQNa+R`JnM~aVBA1Oale`EoW6+o5%Sp#GdkX1mI0a*t}yAa4qINGH^*22*)2C^E+ zav*8n^23Z+LyEMq!INHTQRtH%gWPOkYLRJV_B4mw_ zMM72yStewikcC24%F!+rvR00Ev5?hrw9AF87qVc;iXlsetQoRs$f_aBhO8U1aLCFz z+NDF*&e1L&vU-ko`H=NP77$rMWC@WqL>3YM->VpA$1=r^=7{vs@k#LsBhnKS;}hc( G!u|%QT)5Hz literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Canada/Saskatchewan b/libs/pytz/zoneinfo/Canada/Saskatchewan new file mode 100644 index 0000000000000000000000000000000000000000..20c9c84df491e4072ec4c5d2c931a7433d9fd394 GIT binary patch literal 980 zcmc)I%S%*Y9Eb7Wq@_$lyqJMVi$W|41r?0;Dt1v+oCsXRm>A5;gMUCATqHt^7hF5K z5s4sImBW-o-ctz1OIfDJyk9wlE5VNMb9AR0SH8o0K8Ime^L)c~(HBK>;#@M{a5=^1 z@}BkTp#6HRuUB^_((Lz*Rqls^2hPW`Lbp%db>g{K-MAZa5?2bW#P?n2({6_KIet0v zwK?4#sNZr1Yc}1X`|Xk8!U=ceX0J1vy6h7SJ!;nJl3)J^^zSb%GB@9?|GbIW^Zl)Qq0P3PSX3`Y zpWA<5KGsVQOYP-n`FiEfEqk^6ky^_rk@eeoYW-iXY^}O#duCF0hLh?-;M7k_>ZxBJ z{rIBibu5c`-rKG~s(IIv?!Slpr{XD@6_sJBEH$^*+^6PNho!{4a{|ZD@EQJm&m00E z5s(l_3?v8=1qp-1@il>vNWLZ%5(^22L_@+M@sNN>L?k2<6A9{Tq9S3DxJY1M6B!AO z#72T6(UI^-d}IK=W(3F(kTD>GKt_QK0~rT05M(6CP>``8gF!}v3O6*LRG7d!-P)%6Mh literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Canada/Yukon b/libs/pytz/zoneinfo/Canada/Yukon new file mode 100644 index 0000000000000000000000000000000000000000..fb3cd71a69e3038f0d77e8ddd3290a08fb960d9c GIT binary patch literal 2084 zcmd_qUue~39LMpq`DeNuJfPFL&ap>l&du%YkJ~&t`)8BmbjMA1JbBFg*Z#1jZMk*S z(m7!i8nJJR8mPy|;f+K%8H!*H6AD5k+ae-kNKi&GA`4n6`}Mq^F1zZgi++da_j>lb z+Rf)3-PF=l>ifqt#eU)N9JGh~+;00yUcK3W_F9fHx2N@=>l^C6d3a&}P|k1dL)**r z??nk2TiB-_1h%T_ExYxM_y(0(9n~|JE>W}cDsi)HAX*00t4@}KqNt3OZ+GC$Y3`AL;KK5FvPyH(!E zXD0v09ct0wB~vimC56djCOEK7in?Ak#m81iN%K)%+A&`ihdXsy{bVW6jp>TgA7n{d zNQeBBROnu|Ui#pkTK369U3uqIwY)#eJaO$k^l_@Jz#3SY?a9V<7VxZ8mZm9$JCt& z%DUxovp%7u-d|~=8}3W=VZg-7zmV8>%k;)Mzo?BL`*nPLSZz8#uAd*dtTy-D)h`U5 zR9hNu=&i|pYFlK)Y=85(>?pi$UToeY4H=Y z_<7!;2A|LK zdb?95+Izciin~v9Z{>MsBxMG7-)wge)I_4bc$Gc%_B>}#9e>*ob@oG@l_$l$|2FzB zcHr6Pz#B(SBYQwLf$Rd=2C@%iBTl;$WGl#Ckj?P#up4AM$bOIwAv;30gzO2~l+*4C z*%q=dWMjzAkgXwmLpF!(4%r^EKV*Z*4v{T7?H-X$I_)lzZ6f``6Q16DwjdDa7DQQf;@WRtZuwbh!8o-9po=lfs0>Q(RB`rrM}=kC6|+wXfy z%c?3et$#eB<`-U`m(0ue*xlx67fTlJ_ndbhZ2orOkhMdr@}_~Vraalb(DI>dUtn zbh+Qoc~!pStn~K8kLaFr``x`)J-Tl&!96s+S`Kah&ilpLcKzkF(`q<#l^$;FkXL=V z@><7b?r8S>4T3cVI1uRG;aVWYpw z@WU5HME{74Z1aliU+j~UwsecACx>P{$(D7Z_YTD->IelNensGc|-cUOuR1_q6Fbt&gkJ=eO##jhmF%y+@`O z6sn93RWdVgM7ZQKIX8Tfm>1bAvx0|Jwlzs+e-bKkzE9EfkNzO;JRPZXpBfT*hsSjO z)?;Epn@`J?AFG91PUwQnH`QHBpVNzCo>7JA-LmlKTD2s)Q!W_`5KG4!WzoqRakp=+ zT-Ix;8QA`MrqHHO0~YeS(ooEQumjZ>kU;Y>H(M38;c^Qwny>lM*b5+z&w ze!nBY;dBHBOnjUH&LHy!hx|uA!G3@LyOw32fqs9VvO@j-L2X5FI?Orjbwo{^{Jy-n z)LLoYIbyDPUFMxwqQhaPW*^k3}L{6+q%Ju?Q7og!OB_KIv4*)6hNWWUIUksTvj zM)r(s+ScqE**3CoWaGAG=g8KPy(62qHM>W)kM{ogLIZeA2Y?nJJwTd(bOC7t(g&mw zwx$zEE0A6w%|N<=v;*k}(h#I0NK25OAWcEKg0uzc3(^={(;1{Swx%~obCB*J?Lqp3 zGzjSs(jufsNRyB*A#FnXgfz<5bP8z|(krA{NVkx7A^k!chI9;R8PYSPX-LD%!kv<}gL^_GI($@46X{N2|Celu% zpGZTIjv_5ZdWtj^=_=Azq_0S0k&h)kv>xO|y}1Bke}|jWitTIMQ;Y=Sb6$t|M(n`i?Xn={(YUr1!R_`AGMX z_9OjA?f~Q-K<)zMK0xjS GE$lDZUi%6F literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Chile/EasterIsland b/libs/pytz/zoneinfo/Chile/EasterIsland new file mode 100644 index 0000000000000000000000000000000000000000..cae3744096402e8a452336544edf96ca9ae5ad8d GIT binary patch literal 2233 zcmdtiT};(=9LMqh;W^+W2$|3mS{MbwkLQQ*_=_YVJxF=dBs5cpfF(pgrWD3jVzpF8 zYi_!%R{7BM3tcFi%-?#l2P@BoBU`MSbgMNPJy}}*`@R3wRqLXgF8ZJI`@jA>7w6*a zeBPmkmZn1IZ&$4Sgv0f$Jv^swwzrYvy8pLurM@(9LEIA`8>iz7@paXk2wf|YcNdtb zjBJSxEYdNKUt$xE>ew$QB<@m*zU)|7;>Ul~3470}#L+SB??0(7-#wzIG!Lt!r%svV znn5+S>99%3>Q(n4;#JsL%Fs2O;c6)hTK;3yqTBs zoK)uz>+0{@Wq$IYo<9+xY9_mN?a?-MNBADS;D{p&hbnaNy;xOO-)9!>Iw4h&J

rD5BGI`|PpxN+wx;*-7 zp4s?zsoL~pvgvsxO+B_gS3ll&QT5g(>0Z}$eNhpS|M-fI`7d8FuDf%C<9PQd*FCVu z7w5XWw>yb{-4E<>>?b4QOIjEVIo0;eRwee7+EZ-*_dXx*KM4Jc&Dfv8ZP`*~zuSJh z-43!J^ftr;JL0li0``P#3fUF1Eo5KF#*m$P+N~jbLpF!(4%r^EKV*Z*4v{S)dqg&g z>=M}~vQK2A$WA@&R*}7W+RY-nMYfCV7uhhfV`R(7o{>!>yGFK+>>JrQvU5+nb!6|z z=8@ea+eh|~Gyv%U(gLIhNE47QAZ<YUfHVT>1kwtm7f3UZZg|>uApJlZf^-CF3DOg! zDM(k4wjg~$8iRBOX${gFPum=%JD#>ZNPmz9Ass?mg!Bk$64E84O-P@RMj@R-T7~oq zX_lw$7Sb+H+b^VHNXL+tAw5HyhI9>S8`3wVaY*No)_L0AA<gr&-9y@k^bctu(m|w! zNDq-FB3(q<i1ZO@B+^Nwl}Im<W_sFgBJD)_i8K`HDAH1-r$|$gt|Dzk`s!)Z@qcY> ee5LJgpv2yb13AI+-2B{<yn=$9V9}pX@xKE%a7T*( literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Cuba b/libs/pytz/zoneinfo/Cuba new file mode 100644 index 0000000000000000000000000000000000000000..8186060a4b2a81934eff93a2733b581bf60f299a GIT binary patch literal 2428 zcmdtjZA_JA9LMpS#~5l+K1UKJiIp5Y&~X-u_vQl_Ab;X$3SwqF2t=5XkO;Y!lC6zi zlw>kqRL0yYjBH>vyO;PdNAsWLw9%M>`J&CO<&2EvbbYU_t*uwBUUl8O?$5pR{`Wn> zqRR3#=Wi$4{KDn5o6C3HF7tYS^Ow6m8hBm0>q^|y#pQZ>pujzok*#Mwukrem%A~(N z-}}06f}G2^?hU+iRlbS8;EisI($P?pdt=FRy)jbg{Wh4PW1V~4-%lLUn=M=1@m@zm zog<#pHmqSSC%o|bK8@Hq>_%?-UZMh1ylD5h+%hfOjY&KxF{6!MtkWW~KUKLCE>+6J zZ})hUyd1gp=oas`?zbhb>41BC!H;@Jd5<^Q->*|v?)Rn^zo^sZHhR-DN_9qbi8nKT zrOv#v)Vp(Rp2nZu;NCSDtFyW?-Gm*5a(8Q@n^+W(*|p*BJ<AGo&g#o<(wua?*LTvL zJM|S!o<8g)k9W$v(Q|s=&|bO!!V!JoShdXW*{3NTdE#qp(A4HsSx{f3{)!w;du*|$ zXQk+a?s^H#Ixh>;4$H!uJ+dgiUl&~&(1*r8)Q3-gq8Wp)>Ef<)vgEUEn%R0pmL3SI zTVAXymIq|TwO2JOX}V;6cSu%6+>liti#{?kC^_vllG{J3dAs-O>MvGne*GntH-3?V z#gpaH=PpWN{B;Sg`BZ{q7i4XqUDjT{rt1=VbzR?iT|fSo7QNe}#X~!F!%O?M<k&{t zSlXzMceuJK?@f84r9?KT?2sobmP+ZQ4N{ghTgt9xN=0&nRD6{vmC-*)<p<$Xb>);) z?@Q39&W>o!c1NG?I-#{|hIMOer#=(t(`~hT_1UTKX<dG`){P&R?TcQP?L!BpVMdiS z9BYy1f6bL09hK5}(I-2bbEK&^PMSBS$O~;hOISqszkMRZ|MEmd{&!C()P34<%-eG! zL!nb%SWGB%^sqDW&o{s1<^`Q>bC)eQw=ihd<2Yeq7AN=*b{8_IvSnT`vOi>l$PSS$ zTFoAjO<K(^k!@PdK9P+gJ4LpN>=oIp)$A78uGQ=p*)XzWWXs5&(QcX#cFi%{2KJ3? z9N9Utb!6|z=8@ea+qatiBMq>c4j?T+dVn+m=>pOQqz_0VkWL`2Kze~R1L+3R4x}GQ zL#(DFNK25OAWcEKg0uzc3(^>*Ge~QY-XP6Ex`VU_>5tVk2<Z^gBBV!1laMYUZ9@8l zGz#ey(ki4^NVAY`A?-r?Wi<^$I%YL3LwbfZ4e1)vHl%Mz<B-lFtwVZeHO)i1XEp6Z z`e!u_L^_DH5a}V(M5K#I8<9RDjYK+$v=Zs1)ie|7rq#3)>8I5+6zM3^QlzIyQ<1JB zZAJQuG#2SB(psdqNOO_yT1|VA{#s3gkq%o;i;*59O-8zmv>E9$(rBd9NUM=vBh5y- zZ8hyi`fW80M>=jbEk}BeG#%+W(srcpNaOMU-uYM){($)dn4g#KOY<#AT`)h-@Avu5 Hmp}Fofim_s literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/EET b/libs/pytz/zoneinfo/EET new file mode 100644 index 0000000000000000000000000000000000000000..d2f54c9bae1f689433b7da8bc38655c7f2aad22d GIT binary patch literal 1876 zcmd7ST}+h)9LMp4#4Kj?M?*+nlL*iw99~0%h~U6fFoBbjN~F99MHEB~2x2Y9S~2!( zV>)Lpos2nW)CJiEP)n=nH0I`*^KEUpGRrYHb83yP=lk4t)m1%bXP;;1;#~c|zrePZ zrcBR2o<+_te0h4EFYgaMJXWu;4_DYDgML|OuEHCVwKNc=Wfdj%Xx5*KNc>&Pmt9e0 z@DGZ-{gtAweWu6GAJmGIA1Qiz#$t|5SnS82SlqBIzVBU2Xzj4achuX;@_;4g`|SyD zt|i4J+mlP8EIFiD$#=4q@^g|>=ay^LcZ;=p@}9h7H?(H-lF|mxD7|x9zJ^bgQG3xc zJ0>lwV9K(q_E=8r0n5$ow%mmQ%ZnaW-qoG9_OC9jn<=)ZFV`r4yiNsE>-5Y}kqY<6 z=-H-36%DSn=So9V+z@RgN!R5sUa-=T(<)2+(aPq3vGu{<*!tO1w&B`g+c<I5%FlkS zijiU4bnK9xZ)>y7!|$nb>j$c8ZB+GBtqPQv>4nHm+Tu;smb*UHM3h?1g{6A&PKs?i z{)e{Dh1!mT;Z{3&(_R|5ZFQrw_Hx~v?d<<ey9&Rw`r5N<h~H<81rus|@C)sZm3IHS zSIr9@YB|-TSFQ&1>XGeg{l48^d%r+!<JI<hbG+Ud%CYv!eCue6v^O*2tkZwr-U|0v zS9*lHZvAB4VRzMi?py1bzo_2N$L;OeDfR6;q5g>jdZ%Yh10w?p^88mHk0&O<xirrz zU(f@7f57ASR|h;n&J*|-XUZRNs>rz_CySgda=OU*A}5TTF>=btIU^_S>YO!l+Q@k$ zCytysa_Y#rBPWlXJ#zZU`6CG+86YVjIUq?OS-3iBAbB8(AekVkAh{sPAlV@4Ao(B( zAsHblAvqyQAz8UPX(4&JI*B2fA*ms`A;}@xA?YFcAqgTGA}Jy{B1s}yB55Ldx;lv> znYucuBDo^TBH1G8BKaZ-BN-zpBRL~UBUvM9BYC?zi6fc2I;kVMBgrG#Bk3dgBNKqk z05S#093Yc`%mOkE$UGnufy{)fGZn~OAd`X21~MJUd>|8o%m^|i$ebXPg3JmsEy%ne z6NAi*t1~so+_*ZEgUk*xJ;?kZ6NJnVGDY}5oug1^oYrKrPTq>Fw45|wdX~2$!{@$b GhyD%6ikYzh literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/EST b/libs/pytz/zoneinfo/EST new file mode 100644 index 0000000000000000000000000000000000000000..074a4fc76ad816447121db6cd004aa83ea41d437 GIT binary patch literal 118 rcmWHE%1kq2zyORu5fFv}5S!)y|D78c7+ixxfSeFA^>G2Un{ojFs8bC{ literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/EST5EDT b/libs/pytz/zoneinfo/EST5EDT new file mode 100644 index 0000000000000000000000000000000000000000..087b641d2cfe4a7db66b627cf3543f5a7baa3537 GIT binary patch literal 2294 zcmdtiZ%oxy9LMnslIR5zDhd^;pkg5Z?J6P_CXB9jh4f-bre6)bLnuyaHz>oJDCyQ* z)1ZH&EHi6!WM<Q9OwE~FGgDTxKf_~NtX0%8df+7Q?Yz&^9`&qs?#_ArcAtOV!G`A5 zC7yqrIQtKm^Mbv6C-?2iP_KRTi@d(YqjtS~N<M$qsIysrs<SetFJG!RV?WK&f1N8c zS58jTkwfVw`gT-%dLt&L`+|;rT$^z!B5`s>T~(`Pe8qVcUvy03pPQ^EChwMs`*s?y z*&+#gvr7Et4V`o(q^>^Pr6;|!$t1tDTBmGnGS_t6qpxkQHrFl9)v5j*bN%c@eM3%y zNz2TU$rDD*l-LBB@`qPVy)Yu_XMR=Fj-Hl`{a>gXckP#(!X9(;Gdm=+?gx{#reELU zJ8o{B|Gd5}b&tu;U8l1npQ;>hoz5A0OXdDNOHUu@R(XR#$?t1c1qWtIVdnx>v?EQ5 zLm^YL_Oi^V_M6)meJ{T3>1JlhfXs?dGNn_$)uqF~o7rQZ=&}=M%$)D~bb0@8>W+_} z)^~3IQr-2+DqYd`zM9*$QSPqUspd7Vl*+=es;ZnPRik@Nb^Z!nJ-E&IljdswfmSnr zBug)Nb*sAf>k0{MXi*D4%#@mC0ae=*C-;>Xszr|t%i{DD6|6fc!Ld=b#P_Z&`Ein| z&p4s$k6$!PBYSnj?m_dw&^EoS>!5jXpj9t#>@_R;7HSE6pjLLb$g2Dv^-w4vjY;jQ zsk%^_MjBKoJ4HgL%2o6DQEA?nq1Ft4B`rN=ruFni{ct$lJkmd?+v*}lZ9k|V^=Y%V ztyiy0J!aO|bm;Yw^D120BjKT=sy(e;+6UfL9hVy9iN4M1$#dnhp);hOI+P)u%l&HO ziyn#b#CT%I+2_CXi$)K>=kY|NpB;&bbMFNACRQyDda5ezz2Dy2AmfPP2LHu~qV_N( zWK77QkWnGSLdNB^14BlJ3=J6@GB|Xj<AdRG?D&8IA|phGh>Q^#Br-~5m`*!RWS~wv zQe>#eSdqaZqeX^`j29U&GGb)N$e58qBcn!!jf@)^I5Ki%=uSI!Wbnx7k>MlbM*@IE z00{vS10)DY6p%0=aX<p$w2?qU;k2<pf`LQ>2?r7nBp^sckdPoTL4txr1qllh7bGx9 zWRTD}ZETR>Akjg>gTx035E3CIL`aN~AR$pg!i2;L36#@D3JI0d#tI3R(?$yk7ZNWd zU`WJ}kRdTcf`&v52^$hOBydg}IV5yW8#^R;P8&TWd`SF|03s1YLWsl=2_h0jB#cNL zkw7AmL_+Dbu|$IDw9!Pu>9p}g0*XWw2`Lg&B&bMKk+33hMFNXN774A>#uf>#(?%Bw uuhYgC2{002B*aLJksu>c#{W&2y|&msTkO2RjDnJaqP#$HaY1oGk@s&Yp|6hs literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Egypt b/libs/pytz/zoneinfo/Egypt new file mode 100644 index 0000000000000000000000000000000000000000..0272fa1ba0a09ae8380be83cc8838d97d0aa6d25 GIT binary patch literal 1963 zcmdVaZ)nw39LMo<r>=RO)m6)8UR|~2n$6tOt>w*SdS&XQU)(!N8TDI<Qc)aPg1TY{ ziNZ3%_CQHc$%;z*qn5f*A%mK;+EH18LY;-^GMlHlDZ9GXc^@AJK@WP+ckI3{?txFv z`;%V$^wKi%w;SsIg<r18{qlXJav!bDpPSZ9qdt7~f@%A<OCLG%N%-B*cj@nUbebQ& z>DBFTE;ahYRr=`5p*i;2$3a*5Q(;$cNpNmNOL(p$)W4n2*z;dJCw^~lvfUec#D&jx zOS`I2{jvUx?OD7?Tx=L8duOaueYIQUr3o>0x%`;DJeU;yw`9xy&Nh+hue4VV4XCT9 z4%&hD)~G-C_sGFl_6V`L&_<U1s<N6E$szONVrczOa@gc|ME2~My)L&y<rE}kPWNyT z9k$X&+Y@TU*#Voo^D8y-aH+ihtv+$XM@#IDFBGZR>l@{$+Bz}%`M4ZY(J5|vIIuV8 zjZtH_Hruh8YLO?m*}PMYYFux^-g;1|@f}jew@ecg_H45g)iRa8;e@=cev`O;b)CFp zPQEC3a*`}8OsP8)U&~3^uZp7hC0lg%OI0iyZE;(bn%w!RynB0tC^__towDwIb<ft( zX6m!Ky0od!l+Ewf_dapLOe@xUdR2=lkM7bHML&iW7u$o%oPA;C@twi_U2lgob~gu! z))&JEnpXrfKX^Q>YIrnwaQ$jCtFA#mw7kyDPSol-kIXd5Q3Ju;(tI=5JQGyMvP|{f zn4V`(oB40F1Pe|^!kYR6x@PTTrsmyDkXmt{N$psxQz!R_>4G&uR^&hW8ItwSKiB?W zA>y^}^@-xC5%(0w=ZoRjzSk^Fi)1pzN1DG!_(=bYH$CX?r2`AMBX8U5-Z%2bk#~-~ zb>zJxZytH~$lFKWKhglw0n&ok^?)?tbzLBBAblW>Ae|tsAiW^XAl)GCApIZ>Asrzt zd0kIPQ(o5<(iYMe(iqYi`qubDZ=7om=niQQ>5rp9q(hDtksgsIy{=1+HodM-q*0_( zq*bI>j%JZ=k#>=OIT}Vf=4ct|8EM+<y5?xx>-t6-M><DZM|$UI9_b!wAL*ZC1CSly z*aBn^kWD~#fnyuIZXb}1@VcEqwgTA;j?F-J1KAE_KadSUb_CfHWKWPyL3Rb%7O&eE zWMjN;XOOKy_6FG;WOtD5LG}mPAY_M-EkgDP*(79_kZtn1eL^<M>vjs+DrB#a%|dp| c>$c15_6ylCuiG)N+cNyW?OD`~TS-~;FR%xpXaE2J literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Eire b/libs/pytz/zoneinfo/Eire new file mode 100644 index 0000000000000000000000000000000000000000..5c5a7a3b8d67f5a5c9e44a56e70a727967d972e1 GIT binary patch literal 3522 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 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/GMT b/libs/pytz/zoneinfo/Etc/GMT new file mode 100644 index 0000000000000000000000000000000000000000..2ee14295f108ab15ee013cd912e7688407fa3cde GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<UarX@YGD67I#|6}Gzy$zy_6BJH literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/GMT+0 b/libs/pytz/zoneinfo/Etc/GMT+0 new file mode 100644 index 0000000000000000000000000000000000000000..2ee14295f108ab15ee013cd912e7688407fa3cde GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<UarX@YGD67I#|6}Gzy$zy_6BJH literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/GMT+1 b/libs/pytz/zoneinfo/Etc/GMT+1 new file mode 100644 index 0000000000000000000000000000000000000000..087d1f92576a312c68393936662125d9e21e232a GIT binary patch literal 120 tcmWHE%1kq2zyORu5fFv}5S!)y|BoLS7<3H`ft(OB^>Nt%_1hV80RYaP4U_-? literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/GMT+10 b/libs/pytz/zoneinfo/Etc/GMT+10 new file mode 100644 index 0000000000000000000000000000000000000000..6437c684f8d14b58dbdcc75322149fc60b08bd82 GIT binary patch literal 121 tcmWHE%1kq2zyORu5fFv}5S!)y|KbD&23<n~ASZ-OeOxv`{dR^1TmX893#b49 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/GMT+11 b/libs/pytz/zoneinfo/Etc/GMT+11 new file mode 100644 index 0000000000000000000000000000000000000000..72a912e050e1af97d12a541f69ee01584c52f509 GIT binary patch literal 121 tcmWHE%1kq2zyORu5fFv}5S!)y|I`2m23<o#ASZ-OeOxv`{dR_iTmWzU3vd7c literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/GMT+12 b/libs/pytz/zoneinfo/Etc/GMT+12 new file mode 100644 index 0000000000000000000000000000000000000000..6938a1aff2b9a786015ebc0d0ea4d95bcf146f64 GIT binary patch literal 121 tcmWHE%1kq2zyORu5fFv}5S!)y|8NHe23<pt`VcbpaoGU%+Zh^h0RUxj3pfA( literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/GMT+2 b/libs/pytz/zoneinfo/Etc/GMT+2 new file mode 100644 index 0000000000000000000000000000000000000000..a3155777077f680d885a3e820fe0a84d5b238d58 GIT binary patch literal 120 scmWHE%1kq2zyORu5fFv}5S!)y|Hls)7<3Il>O;uX$7KW5Z)d~>0K`lUTmS$7 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/GMT+3 b/libs/pytz/zoneinfo/Etc/GMT+3 new file mode 100644 index 0000000000000000000000000000000000000000..ee776199ab76fcb46bcbb3d82e99d0b88cb36e57 GIT binary patch literal 120 tcmWHE%1kq2zyORu5fFv}5S!)y|Em`m7<3Jcft(OB^>Nt%_1hV90RXxB4I}^n literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/GMT+4 b/libs/pytz/zoneinfo/Etc/GMT+4 new file mode 100644 index 0000000000000000000000000000000000000000..1ea7da29dc7d975896c4cac40ee5724b5b037147 GIT binary patch literal 120 tcmWHE%1kq2zyORu5fFv}5S!)y|KkT37<3IxfSeFA^>Nt%_1l?n0RXS44D0{^ literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/GMT+5 b/libs/pytz/zoneinfo/Etc/GMT+5 new file mode 100644 index 0000000000000000000000000000000000000000..dda1a9e11ef7dc78cbec231e4e0463bc41f999ab GIT binary patch literal 120 tcmWHE%1kq2zyORu5fFv}5S!)y|D78c7<3Ixft(OB^>Nt%_1l?p0RW{|4730M literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/GMT+6 b/libs/pytz/zoneinfo/Etc/GMT+6 new file mode 100644 index 0000000000000000000000000000000000000000..f4a0385567fe2a6998abccd61bfffb4291b9d814 GIT binary patch literal 120 tcmWHE%1kq2zyORu5fFv}5S!)y|J4f^7<3KHfSeFA^>Nt%_1l?o0RWo>4153p literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/GMT+7 b/libs/pytz/zoneinfo/Etc/GMT+7 new file mode 100644 index 0000000000000000000000000000000000000000..2d2ccd005b9256ece18b7c48860feb77f08a2e54 GIT binary patch literal 120 tcmWHE%1kq2zyORu5fFv}5S!)y|G5(w7<3KHft(OB^>Nt%_1l?q0RWJ)3`76` literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/GMT+8 b/libs/pytz/zoneinfo/Etc/GMT+8 new file mode 100644 index 0000000000000000000000000000000000000000..826c77001b6645680d430fd46e04699e003e34bf GIT binary patch literal 120 scmWHE%1kq2zyORu5fFv}5S!)y|NaIB23-RSASZ-OeOxv`{dN{y0ES=;3;+NC literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/GMT+9 b/libs/pytz/zoneinfo/Etc/GMT+9 new file mode 100644 index 0000000000000000000000000000000000000000..b125ad2bcf93ad431545d6390ac76ea2e8aafe07 GIT binary patch literal 120 scmWHE%1kq2zyORu5fFv}5S!)y|Hc9a23-S7ASZ-OeOxv`{dSgI0DKM$)Bpeg literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/GMT-0 b/libs/pytz/zoneinfo/Etc/GMT-0 new file mode 100644 index 0000000000000000000000000000000000000000..2ee14295f108ab15ee013cd912e7688407fa3cde GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<UarX@YGD67I#|6}Gzy$zy_6BJH literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/GMT-1 b/libs/pytz/zoneinfo/Etc/GMT-1 new file mode 100644 index 0000000000000000000000000000000000000000..dde682d83c0910e3aaa4de1fc4a56846483f5b93 GIT binary patch literal 121 tcmWHE%1kq2zyORu5fFv}5SxX8flq*eLEFF($O$1+AD0bKzn!ij7XWsh1~~u# literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/GMT-10 b/libs/pytz/zoneinfo/Etc/GMT-10 new file mode 100644 index 0000000000000000000000000000000000000000..352ec08a14a107fbcff0844659e60e8bf3952a92 GIT binary patch literal 122 ucmWHE%1kq2zyORu5fFv}5SxX8p=SXDgSMdokP||tJ}w)eemh-511<pJPY8to literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/GMT-11 b/libs/pytz/zoneinfo/Etc/GMT-11 new file mode 100644 index 0000000000000000000000000000000000000000..dfa27fec76820a74da31e40ff8698fcfea3fbc8b GIT binary patch literal 122 ucmWHE%1kq2zyORu5fFv}5SxX8Vb%r)25mz_ASZ-OeOxv`{dT&BhFk#b%Lv5) literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/GMT-12 b/libs/pytz/zoneinfo/Etc/GMT-12 new file mode 100644 index 0000000000000000000000000000000000000000..eef949df27f2d63a9d508ee691637b8326829e04 GIT binary patch literal 122 ucmWHE%1kq2zyORu5fFv}5SxX8VZ{Lk25m!-`VcbpaoGU%+vyq_aRC7MM+pT0 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/GMT-13 b/libs/pytz/zoneinfo/Etc/GMT-13 new file mode 100644 index 0000000000000000000000000000000000000000..f9363b24f0a4c737afbead84994debb5fa2c9bda GIT binary patch literal 122 ucmWHE%1kq2zyORu5fFv}5SxX8VcP`;25m!QASZ-OeOxv`{dT&B##{gc(g{TX literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/GMT-14 b/libs/pytz/zoneinfo/Etc/GMT-14 new file mode 100644 index 0000000000000000000000000000000000000000..35add05a605a4ed9d7f353c78ee3db8b014e8141 GIT binary patch literal 122 ucmWHE%1kq2zyORu5fFv}5SxX8;m89925mzVASZ-OeOxv`{dT&BCR_jyP6>qo literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/GMT-2 b/libs/pytz/zoneinfo/Etc/GMT-2 new file mode 100644 index 0000000000000000000000000000000000000000..315cae4f9e535ee35b868a0e3709e2a7fab6a606 GIT binary patch literal 121 scmWHE%1kq2zyORu5fFv}5SxX8K}LarLE8YNK7>qtTsA=ccDhDf0D;;Dc>n+a literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/GMT-3 b/libs/pytz/zoneinfo/Etc/GMT-3 new file mode 100644 index 0000000000000000000000000000000000000000..7489a153dbb61b904090d6bb484c6f29770f44a1 GIT binary patch literal 121 tcmWHE%1kq2zyORu5fFv}5SxX8LCb)FLEFF>$O$1+AD0bKzn!iz7XXbH2DtzL literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/GMT-4 b/libs/pytz/zoneinfo/Etc/GMT-4 new file mode 100644 index 0000000000000000000000000000000000000000..560243e841ff12e0554eb328e40dbcbdd65b1327 GIT binary patch literal 121 tcmWHE%1kq2zyORu5fFv}5SxX8!NP%oLEFFt$O$1+AD0bKzn!iL7XX-42KfL0 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/GMT-5 b/libs/pytz/zoneinfo/Etc/GMT-5 new file mode 100644 index 0000000000000000000000000000000000000000..b2bbe977df886770874563aaf86d7a358814ac00 GIT binary patch literal 121 tcmWHE%1kq2zyORu5fFv}5SxX8!7YG+LEFF-$O$1+AD0bKzn!ir7XYJ?2RQ%$ literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/GMT-6 b/libs/pytz/zoneinfo/Etc/GMT-6 new file mode 100644 index 0000000000000000000000000000000000000000..b979dbbc5c86789f34638aebc9644e7af0fb83bc GIT binary patch literal 121 tcmWHE%1kq2zyORu5fFv}5SxX8AtZr;LEFF#$O$1+AD0bKzn!ib7XYr#2YCPh literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/GMT-7 b/libs/pytz/zoneinfo/Etc/GMT-7 new file mode 100644 index 0000000000000000000000000000000000000000..365ab1f64683d2b1867072378c1adcdf289e432a GIT binary patch literal 121 tcmWHE%1kq2zyORu5fFv}5SxX8A*q0YLEFF_$O$1+AD0bKzn!i*7XZ2o2e|+M literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/GMT-8 b/libs/pytz/zoneinfo/Etc/GMT-8 new file mode 100644 index 0000000000000000000000000000000000000000..742082fcd4fcf8bc9c1a8b8f35af7533d74b0a3a GIT binary patch literal 121 tcmWHE%1kq2zyORu5fFv}5SxX8p`d|*LEFFr$O$1+AD0bKzn!iH7XZab2l)U1 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/GMT-9 b/libs/pytz/zoneinfo/Etc/GMT-9 new file mode 100644 index 0000000000000000000000000000000000000000..abc0b2758a3b5fffe3acff6d42973ce9632acc33 GIT binary patch literal 121 tcmWHE%1kq2zyORu5fFv}5SxX8p>6^LgSLSskP||tJ}w)eemh-DE&$Ml2sr=% literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/GMT0 b/libs/pytz/zoneinfo/Etc/GMT0 new file mode 100644 index 0000000000000000000000000000000000000000..2ee14295f108ab15ee013cd912e7688407fa3cde GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<UarX@YGD67I#|6}Gzy$zy_6BJH literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/Greenwich b/libs/pytz/zoneinfo/Etc/Greenwich new file mode 100644 index 0000000000000000000000000000000000000000..2ee14295f108ab15ee013cd912e7688407fa3cde GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<UarX@YGD67I#|6}Gzy$zy_6BJH literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/UCT b/libs/pytz/zoneinfo/Etc/UCT new file mode 100644 index 0000000000000000000000000000000000000000..a88c4b665b3ec94711c735fc7593f460668958cd GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<U33UzuGD67I#|6}Gzy$z!Xa;ov literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/UTC b/libs/pytz/zoneinfo/Etc/UTC new file mode 100644 index 0000000000000000000000000000000000000000..5583f5b0c6e6949372648a7d75502e4d01b44931 GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<U2@P=uGD67I#|6}Gzy$z!n+A0N literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/Universal b/libs/pytz/zoneinfo/Etc/Universal new file mode 100644 index 0000000000000000000000000000000000000000..5583f5b0c6e6949372648a7d75502e4d01b44931 GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<U2@P=uGD67I#|6}Gzy$z!n+A0N literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Etc/Zulu b/libs/pytz/zoneinfo/Etc/Zulu new file mode 100644 index 0000000000000000000000000000000000000000..5583f5b0c6e6949372648a7d75502e4d01b44931 GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<U2@P=uGD67I#|6}Gzy$z!n+A0N literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Amsterdam b/libs/pytz/zoneinfo/Europe/Amsterdam new file mode 100644 index 0000000000000000000000000000000000000000..ed064ed4ac9d86707173d480921c6ec27bb69916 GIT binary patch literal 2940 zcmeIzdrVe!9LMnog)}<iS4<$qTSRz4d;ldiNeR(RAk-%!1uu!HMTSTWOT$X7)o<>b zW0_O)hM*1+4Q~Uy-<nERqttY5y0VLrn>n?{>i0gaE!RJ_{^+01;rw3DbNCnDAK!#o zd2yb<TtW5?57$on@VVzgd#rR~uC8qQEPK_tJ<jU#{m%Q9k<OasA<kM&GV6+OIqM4| z%!bVKPI*Rv*_cr8R76fRl~D)Hrb(C0<~AG5=H>~`mMe=*Rn#S?>i7(^^@lvCx^lGH z*0{pdywTTe-`>saD0s~IVA(jQHlvTTbM9iZ%j-D1eS4if?VFvw!%NKGAI~^-VRKB~ z*{i1h_C}{+Povp){;b*mUX3}>kYf&(EHj5HPnts)a<UI+6%`yQJyLLVP-elgDL1bj z?^$^5#E2akr>=~V)0;!JaecCUv}}N!c`Z^tE)0=R(uZriJ}m|9%BN}2+nuHTq7;>) z1nrO;rNO>1?U*(~?v4LhJ4Jl2AssJhXzSA&dVQaUUEZemeY#dVAAU!>)Hg_YRkd_o zwMM!bmG1Ljl^*$p(ldLS+@I=`h@mO+Ky-pchDXVRPMAamBx}@-0ov=UNbTJeqJ56G z(Z02}G`jp-?N@eQV-}s%{zdib&E2Z8Ip-v<uvX#|>tw*VcV%GL)shfbA_*;}GN?<L z4!Ss12LDp5LmHCh;V-guXvJhrtQ(?_EJ@N~E5mhoUW6ts8Z3{F4AA7<E;1tWil!vD z$jE>XbyUQcGOGD&8QuDzjBY$3k6+#(W2(1E>Z$#jw$w;^)jEA*_H4;8uj|;S-qLaT z({=nq`RYp@rB8;YYi4wB&AjQ=tWF~(>+@iJ`bICAu<IwC*c2$)Ydc6z?NymndR-=$ zHOiF9O)_=Cclyk*-7+obl;(C{A=4A9HLvYXozYcw#y87#W=o;YIx$b5z39{DHciz0 zV{_#BHxqSs#dvvPW_K-EGEnA>9V&%+p)xnNn-rzomKQsCq_}@4ExvY1N`h``$(bWE zulbz5R8t`@H`eL=6^C>|^=f@(Ub&VoE!9?jty=s4&AUy&UG@%ad$)VHyT|#jZjZ;m z%>FNWg3JB<-Op|B{On)jaqOD|e!+j7LE;JXc*1-5h2MGp{+=^6&F4uU@AD*C`+B`` zUVFycGdaa)ANvb?`Ps`J{QT*%-+cd|BEQ<JRq)t+q$aMmC`eV1vLJOq3WHPzDGgE^ zq&P@*kn$k)K?;Oa2q}@Ptr1crq)M)~Oh}!OLLrqxN`=%4DHc*Kq+Ce7kb)r<LrR9! z%+(eRshX=T8&Wr<a7g8l(jm1&iicFs)s_#bA5uW1f=CIG8X`qRs)&>ksUuQIq>@M} zky`SXKrvlyHIZ^6^+XDaR1_&GQd6X;NL7)tB6W4Og+(fhloqKiQe33ENO_U^A_Yb& zjFcFuF;Zlt%1D`!IwOU4wUtInjno<`Hd1Y*+(^BVf+H12N{-YVDLPVhr0huDk;1#$ z$|I$BwY5izk5nHiKT>~W0gx3ymH=4;WD$^6K$Zbn2V^0Tl|Yul)vg7y7_N3TkmW$u z16dGcMUW*y)&yA;WL1!5LDmIX7-VITrE#@ugDj4#T^(e3ko7?p2w5RyiI6oy771A; zWSNk4LKX^HDP*aTwL%ul)vgw@T*!JM3x=#1vSi4bA&Z8r8nSH2x*-dPtQ@j*$l4)` z=W16ESw2_0e#inMD~K#1vWCbaBCCl1FQX^OT};k@wTyvwKl{ZppwVgZF#}_~(P^<> I>>nTa8?hLa1^@s6 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Andorra b/libs/pytz/zoneinfo/Europe/Andorra new file mode 100644 index 0000000000000000000000000000000000000000..5962550392fa78514061582e9371c32b9f1d929b GIT binary patch literal 1742 zcmdVaU1*H~9LMqJm=i7OKjo!thM5`1UTtPJ%yG7`jqN;aUS?kA*f_?HS(~>*){@rx zUo2WIL`&kN3>PC2B3dpi&5|OoNs5KEc)pKIS8kMNJ?C>;=koXcn=7k)DaId<)A|oD zkKcOv9<ks0c%0K`M4k^x)bSHCu305|&jA_l56Fbo8)Ra>DKS|dndC~B*vL4UJZy}_ zIo%r9KSQUyiq-g@Q9AX`5S`ZfO<iZ+>Gam8n$Y+_6Kk(&QpE*LF6)()K&PZ;cgc+6 zBa#-?F6k-tl0FcWjIphn@ob&U`cbEu*WEI^uT---$~C(yQ|C10=-gwGI?orQIgPVq zey&s96=P*V>>KsC2PD^dU-M#~OWw!VvM}tXEbP7~i(a0V#i4VO|L}$uv@}cM`BS>2 zx>}YtAJn3f!&>a$sNR`=HS_ay+2}%D?uystUz4<SWUiDx8KEosr^w1HUvyPZxU4=I zA!VH(WKHn1l(%-v+VUP*xA(oSpL<m{ls(jn=r-Ay9a7)W%epB_b<^9US~(EVs(TH( z`I)I(&aP7b-5S|?C|j#Lyt1t_TDLc)Nlj6f1bm}qNAftS^?a9|5r)(yj?}tOFQk6> zSFL|^TN*z0>aL3&vb(!W_q5&8y`gs9*KkIIEkSh*3LE^d{tUyxPIv|z#&9u)8b;)J z$FSeu^9xL)#A6z6`}Laq%;B&<%)c1mPwUy2eyJ51A`fFk28oOk874ALWT41Mk)a}E zMFxwE78$OsHC|-E$cT|4BV$Gejf@%@HZpEx;K<04p(A5Q29Jy$8NRJGek1@}D*_}0 zBnBi1Bnl)9Bn~7HBoZVPBo-tXBpM_fBpxInTPq?YBwH&cBq$^*BrGH@Brqg0Bs3&8 zBse5GBs?TOBtTm$LL@|6D@G(pBuXSqBu*qyBvK?)BvvF?Bw8d~Bwi$7Bw{3FTPtQH zXe4SRY$R?Za3pdhbR>2pcqDoxd?bG403b&IIRv)WF+dK2t#uTT!+;zI<Uk-t0yz}O hvB3Z4V1!#oWO}mO<279csR?NbNv?wABuq>V{{?7%e9iy> literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Astrakhan b/libs/pytz/zoneinfo/Europe/Astrakhan new file mode 100644 index 0000000000000000000000000000000000000000..5e069ea5b3a9c8eaaeb9c48abe3d9a4b89ee3b1a GIT binary patch literal 1183 zcmd7QJ7`l;7{Kupn>00Xa8MiDq&{jI`iRCfDN)m!nx>(eLXps+Dij|DLGVK|C<qm- zom5Z+5k&-pgYQ8ns|0Zo+fHs(IN;<1Ig1Dujpu)(lY_W<FX#L&hnqn1{flFVC*tal zYpFe9arN89^YYt)T<LMyKe94%rLrn8A8Txw%QSv-Jq&zMmDLN$Skvs8O7qM`9lU#5 zhpv@$%h^ME&B;-{_DEi@+ndts3+=i!-J~~k`E@wxGj0Aw6Y)eO@+BbcuRo1<y!a@c zkKRaEc}}92p2^0k2ePSjLt+On$mV@BCVuR)>F&L561z{BWayOXiSIEz-}jqT^KnVN z9WYxK$7Ji%HnVLZE$LfX>3z~7ebZstepN|k!Yln#L9-+GUa|*%n1S#s$z`<}^xTtt z>vNO;{L&27mCexn9kcV@wApq4oGHxSl;QJ7O|cLfAN{&QCacw|t8Pi{c6)x^_1=1~ zo9*wn+v|2K6<x-^+7VOsd2qzlpj0ZO>{)I9ed4kwwRbo#3>Q^rtlO@nT?xKGxwxDs zw&Fhw(*BMr!$bzk=NT$8SY)`4cEHGxkwGKFMh1=y9T_|_d?WxQ0we?^1|$e13M339 z4kQpq8wnB$5(^Rx5)Bd#5)Tp(5)l#-5)%>>5)~2_5|^V542jIqhK9t31cyY2gonh3 z1c*e4gown51c^k6go(t71d2rJXhTI}b+o}E(IVj@@ge~u5hEevq@DeL8?!HMBkzkQ Gd_MuxBoHP5 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Athens b/libs/pytz/zoneinfo/Europe/Athens new file mode 100644 index 0000000000000000000000000000000000000000..9f3a0678d766881389e129c93def7fffd74f14f1 GIT binary patch literal 2262 zcmd_qUrd#C9LMqJ2qYerzDWE@Kr|xMj&S(vpCF>+ftd$GI2x%42BH{|5-~`OV=>np zbKe%LoS9aPIcCQ$DCdw`R!$4Zt+8h1V7W5OIhHzW#p?HdYHO{lZo27r_B^lWJmZ3k z^LhK%)z@WO|2R(bhKsY;Ts+SXnA?HCyugX}%i||bA3JyU$C~kv<w)t!rIj+==bIRL zJ#PHd7wWf<MZH=*cHCJy?aIdL>2cSqrw<3M8H0zcnLTR)5nYvmTRRsAWZSP2S-W#$ zR$1RfRM9ilw`H9wjZXeSW1_y&*pOk3z4orgT|B0@e|A`BfAGA<pB$8gqd`f0<&ezj zQb}sxEpr>1WZs4vxx?p|<UFt3>B^OqgjBg}Mx3OEc{KG#w$A@1MHh_5=)#Yt>fQZ6 zsjKI*F6#b5(>g!V^yZW5u6<K8D$h%1Q@>>84@h>|i;|OgP;xU{C3mtzmc)1Kk_($; z=`St1Y|tb3d|9D+y;Yh&uuSjWQ>X<867;^hWG(DmD)$$Ksi!txmZw}&uV+$<!p>@O z@|YA~{Z>|lye}(8&d387-;kBTKJg8`rzQKkWYy8v^ufkPDeZbzS3k5@%NjOo`Qirk z`-*i<>?&RBTA*uhy0s#@NGd*`r4QejFYDg9uIopgvf*%~RQ6wyjUCsds(VDLt43wh zuJ84cg5y$CIi$5o`(<-}Q0v0q)-8#uTfTcqw@x-`{h2o1cEPWY9$Bvqr+3I>&*f`l zZ@E0aHA%Pc$&nqa^Q5URR-VY1BhB8Q<;h4(TGFGnW#VgTjku|;=RTCStLOEpw|Zsg z$bh!*Kc%~Z2leT;9_`rIp^lI#AydLa!$OU>Otn9}2??L}Uw<qsCeQq6xrbWTLboH_ zvJ&Q6Gc7AZtVo_6(SLvc*WdU`{8pj4yxwwde#>KD=F1<)Va}hAmc!xy7d!gRoaTa; zmm_;ZHf3veg=`Di7qT&AXUNu&y&;>kHM`@#VtdH`Y|REa?2yA2kv$@tM0Sa66WJ%S zQDmpcR*}6To3%B&MYfCV7uhhfV`R(7o{>!>yGFK+>>JrQvU6nX$lj67BfGaX+eh|~ zGyv%U(gLIhNE47QAZ<YUfHVT>1kwtm7f3UZZrGZ3ApNj44M94Bv;^r1(iEgCNL!G; zAdNvfgR}<e4bmK>J4kzw{@9uZAsw<cEkb&PGzsYv(k7%&NTZNWA+17sg)|H47Sb-H zU$&-UNXKkV%aEQSO+&hdv<>MS(m14ZNb8W^A<aX&hqMprAJRaigSMuHNDq-FB3(q< zi1ZO@B+^Nwl}Im<W+L50+KKcNX(-ZBThmgcr?#f4NLP`zB7H>~i*y!gE&gA7>oi4P Ylu4UiC0S`XY3}qaS4oE3e#&<K4n&?MSpWb4 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Belfast b/libs/pytz/zoneinfo/Europe/Belfast new file mode 100644 index 0000000000000000000000000000000000000000..a340326e837ac8dd173701dc722fe2f9a272aeb6 GIT binary patch literal 3678 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 Yx$RPlNVqq$bELatR766Qn|A?!0JySS0ssI2 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Belgrade b/libs/pytz/zoneinfo/Europe/Belgrade new file mode 100644 index 0000000000000000000000000000000000000000..32a572233d4850a4bb4b1278cc2d25480ffc6a80 GIT binary patch literal 1948 zcmdVaYfQ~?9LMqhiLi!!F^Wn^ayhz}P{?u0rE=*Ym*kS%l7v=nxim9t_GOJ3=DLOv z55j|(xy~gCYlazPt~0Y?ZfpGB|IKFe$P=6Y>6}-qwe{fp{&*(O%Z;-Bc$%A^@a8Eo zZ@zD}#(Z4&ihWZ1a+KUW?5lPAU2<z{jMT3Sk@|0rg4_Gb<xct#^>b$z_&qzW9q;bd zP8YYR|CzHAaI{JSckPkR<tjld*GiYXLg_knmUK(?NN|E%x;x_}Bp_6JwDgluZ<mIC ziqW3WL$p^z2km{ix%R34qRxY_wQt1(4J*5$;Y-hGM9wjd%(^d8h1C+BSR*mxwn=Q@ zZi$O3mbk`JiTAJ2_(wCO|MwytaMmRQA7*MoWws{P4A4Ovl63IS03DJWtVw14WoWXu zx^nzwSjbCtyBa0g`<kW%KbDktFJwfM^D?6Ds*HSgKt@#^k<{9Anzp%I(vR-b(fRo@ zrhL7Qow!NI<;~WNetGIiP0{hb={mvLODBAe(9HJ9l6kMKPWseSCZGDKQyP3^>fSbz zRsB|`m41-yiaME|-5@hoz0sM2Ps^;VTFnXCA+r;!G`Gb`ofD`!=hb$d+gPacu9oQh zM;={pXo}`tSu6`TCTf0VhAf&Jqy-ydW%1YqDa`eiC6S$Fsr#!eYhy`KczZ2+|5S=w zf7asqOH%UgzAiseDJ$w~bmfi<x~giot}Z#KrJGCD(bTJnc{$9Nce8)_vaELT=Dw`f zVm1Bs8PLVi!m@t<<hQA59?RwCo#8Qm;BfH8<8XNX;+lV$XIjGh;mB1ZmyKLEa^c98 zBbRP#t{u5}<m&kkxO`i4{YU{w1xN`<4M-746-XIK9Y`TaB}geqEl4p)HAp$OrXHjq zq#~pwq$Z>&q$;E=q%Nc|q%x#5q&B2Dq&lQLTT>rWpslG8DG{j=DH5p?DHEv^DHN#` zDHW*|DHf>~DHo|1DcIIjjFfC^YDS7isz%C2>P8AjDo093YDbDksz=I4>PHs9)~o=s z1h!@kkVQaN0a*rQ9gu}URsvZHWG#@zKvn};4rD!$1wmE>SrS{bCdi^7tAZ>GvM$KN zAS;6`4YD@K;vlPoEDy3i$O0iNge;M*StDeTY|Sbm%Y>{GvQWrMAxnj<75@K=<ztqt WZzNOZOp6YS4U2H5MMhwFw9ij!n9hj+ literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Berlin b/libs/pytz/zoneinfo/Europe/Berlin new file mode 100644 index 0000000000000000000000000000000000000000..7ddd510ec65b70cb833aba2f9500282b2c0fad98 GIT binary patch literal 2326 zcmd_qZ%kEn9LMo<`IExPeZ}CKfKWt;UHK1*L1tHA23J#VMk*nJphXy0V^A7a%(=$g z*T!HD$v-h>&8P=xYpBisjTYVnyV=!TTdv4zj?J7_WA%HVwrcB9PkPeta?b1A^N`)o z+q-5{eYW+F6Jg$ParT&t=ja{g)*Izq-y1kTxi2`Vef>xEm3LJ4cl78;M6-@gl*#GW zoa#U1mQP>Kl`}7-$e1ry#*TGc<CQ0Oj2~Pf;}>!#&(_B2XJyG6C>To$G)!pV@D}Uy zv>`d?nj>FCbW1Sglm;)iO0>Uca+YVQ+>-N)#w7osv9rEZXXt5lUOlO^FTJU^o;{#* z-ru8f$4^N7kwHn=w_oOVsw8fEUXmJpa@)FExxL&g$%Vyohbv!F;#1|$sM(Sl?$*?6 zxjOHg6io}n>iiF<>0LuN)YX4k7j&K1^o~z7W9xCvtb0SVs)Ca38<L!&VaZ+9EqMuj zlAqlw`BUw(Fs@4%UaXOVUt4t13AfyHp;8M6s<mi%k>0y=i7xJq*Zb;|bxB8o++Px| z?z%Wxnlhoq?kOn=|5!_tzmn1`-^#MkQ?krIA`e`8Rmuimm-5kf)w8=(mLEB&E1H_* z!Oj<T<-;%Os>Tgkad)G7%S-hk=W<=`O4HTXGqp0NL@Lik>m%3Z$(mz7>DoY~tUEAM zs)l})N87JTb(de(R|lkK`}g|T;<u%?YE<hI_sWK%L9L&DR2vdh8@}t&jZ;3|G}5Mz zU-aq|hu3Q3N6qr&t|Dz3sF2MY6ZNT`dD6VHP<-`H*^)I^wif>^PtUZZB_l>#Ccl=} z8P~P-%m>nTC8*E5H6YLWhjrWD_jLPUpFY>tukE|rHN+7T8usTW+!4mtn;iD5kZIHZ z`;TQs?J~b>D?Z5)W?3_YpZedQ=Vv-@wyZzyf4wjFcrABv1vf79%^Zir{5g0s$Kmk) zi)sC4&gMfkH;{?hnwcR}L*|A|4w)S?J!F2!1d$mcQ$*&7Ow!iO5}Bs0nWwFpC^A!I zs>obz&17xOY?0|A^F=0%%-GgUng0lLMkbBS8ksgSZ)D=OX6DG$k+~z2M`n*qADKUr z03-uQ3XmKiNkFoIqyfnTk_aRdwk8!wE|6p(*+9~P<O4|vk`W{&NKTNXAX!1ug5(8B z43ZgJlNuy9wkA19c98TS`9TtdWC%$Sk|QKZNS2T^A$dX)g=7jz6_P7klPn}#wkBOj zzL10=8ADQr<P1p~k~Ji4NZyddA(=x`hvd%IBoE1+tw|q}KO})j29XpZIYg3(WD!Xt zl1C(wNG6d~BDqA8iDc8(q!Y;}l29b0NJ^2MB1uKEili0EE0S0wvq)-@+#<<EvTJM7 wi{#hVBpAssl42ysNRp8(BWcF}S)L<Jx))>-ahE42Juf}e<;lv#jGV~d0abN>yZ`_I literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Bratislava b/libs/pytz/zoneinfo/Europe/Bratislava new file mode 100644 index 0000000000000000000000000000000000000000..85036de352d20683bdc6cd5e43ded7f1d5ec307a GIT binary patch literal 2329 zcmc)Me@xVM9LMqRfyj?3d!xaT{464*PJTmZkl6{01XH<;R08!xEh4y#L1|bq=NfZw ziRm1YW{g!cY71)(t@U%X5Px2V<=S#(S~)j!T8-7``HdJY|M&al?(yNvb{iY7=kP3B zUz=t9?+P(bcyVnvFU}F0&0E(LXHA#?^rhV+ecIh~Kwo}ebx+$)9Sm*Mp>qr5@as+; z-shGh9XWFJ`D8ifi;`pe-l;jhDp*czj@6T;$K~Wp{fYhnU!uP(U%pE1ms748@^$DA z8F4ho$oXcGU%d?x-V~kYiPq`m^W~=OKQuDwXN{WvtvUk_tMl>)8h!RHz4^pmo$<+b zjX8KoV)yq+-0nRR->#Cd@i|GX^T{nMR?Dqr9!V-FlG|K)k{p{Nw@-<dlpwdJT*=Xy zKO}3aKT7ZTELiXCzoxF9^E#{Zw5GLvsp%UIYKHes&8!-cEMLE57Y<0yk{yy8*DZNj z&5}3TD)}*;ntx`c%>J`U=Nxj&-QQGdL2tDd4$RSew#?JHU9oy^ZIaGwn=SVh2dUc| zBlDBbX_0$Wii5t;lBDmX<l>J~8u*cv4iC!xXJ3^CeQ!wF(1%*Stz8!Ge?=dtua`yb zFX-ZjUeqOZYqa97I`x#5=!4FMy401bORr{VWn{5bo|>i)UzsV(-u+FN`@>|#-UzAc z|3w~Yy)4z8!%|c2mzA3?=&HHz$?B>h^(O3+HHCdz8*)I`#;LCTX{W9m_38S-7Jc-L zM<07_xz>H&D35O~)cW2Ed176HHf+h2#>EBVt98ngnenor=y!Q4!jh)+NNu|Gy)=hk z)#jt0O3TF&efsTQd1iP(H}3jaH}!Svvn@T^x~)|M907ro#&3r?28}%km>hf~Zp)gw z)%;ysv5AgJmK82m=zq_a<(NA0Nm;qaau-$b=CMl5H|BCU__8mD!*l&bnUCe8?W<$# z9Ql{I;!8WOVcn4nwk(YASsAi4WNpaekkui}L)M2Z5LqFzL}ZP~B5lnok!2$5L>6jm zR*Edu)~pp-EV5c;xyX8L&4T$YSuwI?WX;H;kyRthM%Ili+}5ldSvs<IWbw%Ak>w-n zM+$&c04V`d1EdH@6_7F@bwCP%RKnJj0;vU345S)JIgolF1wkr;lmw{>QWT^rNLi4& zAca9HV{1x-)W+5n2dNHH9;7}<fshIzB|>V16bY#kQYNHMNTHBQA*DiUWowFsRLj<s z3#k`UFr;Eg$&i{MMMJ8FlntpHQaGe?Na>K;*_z@Z)w4C_L+XbV5UC(iLZpUB5s@k) zWkl+T6cVW<Qc9$jNHLLW+M04A^+XDaR1_&GQd6X;NL7)tB6URyi&PdVEmB*gxJY$v zO?i>}+L{6*6-G*o)EFr;Qe~vfNSz&lCdNVIcYrxg9(xcN9C9P>fAef2ZSrg)ZT=Gp v7wexSkDpC~BPRZoNH4lhs3(@%oWo4RXJt}zS9x|?Zd!(`JTn8+v%~%dH&JpN literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Brussels b/libs/pytz/zoneinfo/Europe/Brussels new file mode 100644 index 0000000000000000000000000000000000000000..d0d0a08a29c0743517ec537c18ef3111ece34ca5 GIT binary patch literal 2961 zcmc)Ldu&d39LMqJ(dTg~>RVQIsd=`pD^|T~hcTBbDXr@>I(3~Yrn*OGQs$a9#)y0^ zGNjBw)n$}fUF$M;GCG^v*hE6cY>S=Egl!S~y^n~5E%Bdodd}-S&1w2i`~LU`j>%1Q zJ#o}D-{IoeWiFoU&oH;eUD9V1*NO@hpS@@=y-=phO1A2!J5`q#Ez;$ccjK*LtL+uj zDgrBG>)NX(XV@jlGwszwA_JvS$@ZG)VtZ{1x4o{~RD0dcR)O``huWW=?`>~5+R5H{ ze2rbUCeq$y^$KkM&})A_XRN&?kYRmMu*E9x9cyh(AL!c_FV^<NiPnxfe^@*2U$u7r zcE;LuZmzH5yWf4gk6rQY*>T#pcljJ^-{P&lFN+)d_7^Vl9T+{^cQ9*`?@)(0>u~FA z-;uWaeMhs-Sw}C{u#R<a=sR(}hn!p&u9YiO<?D}I$*Jsp!%pQ7(QgXs%eOr{>gfd? z<xG!At=-~Ypmxa!4Vl|W>J+7^Oi0ntoM^4<Z=m%uyU24%e`{FeFIvCe4;mhHQp0cU z)dp8LX~WZ&HafUK8}HsD5gW>+$<pQW{5+MWQ)Y@UuRvZHG(uj?@JnR7G<hi|MWQ02 z<>gupB-)#*(YIS^vmc|h`Pur~;&3&6rTnhOl-$sk3omMH(N`KbVYkNTY}ACo7bLNu zT$0*XNUOe|NOF^9l9E^`DfgyJ>&6SU^`+s`=AVh$c2BCj_I;MND;=usE86PovpQ*q z;t1`S8>yX&+DPZFUQNwuEM20mYg+0(>FPbM-6Ahbx0}}_J!rqAS00n@S64}ovh|X2 zVxMNtpC>&xtk7Qh`O<sd2ij-AN7^@Ur1pC?PyHF)^o{VI+CQeb_P-OaSz%oz>wH~( z^L8^Cxb08PJ{v59tWX(T{;Ld`eoKZftdwCx&&u$rKkHi^w#$gYCp4$&5*gXPOmnMm z)lp4UNBy)&N8c;ZF~=t9+n4-0c5Sxi9T_j<X1CY;(th&J=%yN&l`P}?w3C9|aCtZ3 zd6|&*m%JD1l8JF)I?=u&g&}ve@YErhbn}8v-drm0S61khB?okB*)p9rsYIvGpRVpI z-YP+!AW!wG)t>s~{U9$lPn8<M&+2r!JdMmhDTl}93K3Uzmn*{OuJ!n{JM`f@58izA zPDZBRH6YFJN_DP&d@wh2xZUQ<9L^6qpWXidZio48E-sD=bMf5hzB&4DyL=7D-N-9N zT7vY%(KH3=3epy&FGyn?O=pnSAiY7FgLDUJ57Hl`K}d&?79l-yG)+RfgtQ6i6VfQ8 zQ%I|jULnmwx`nh0=@-&4q+>|Ske)f3rXgKJ+J^KEX&llyq;*K|kme!XL)wS*4{0FM zL8OI94;@VtkuD-_MEZy{66qw;N=MU6q?t%Jk#-{e<d1=dI+~6mEk$~YG!^M8(pIFe zNMn)CBCSPw>u8#bbQfta(qE*(NQaRYBRxi%jC2`kGty_I(MYF}RwKPen(b)1jkFu- zH_~vV<4DVqo+C|1x{kCR={wSRr1MDYk=`TCcQoBc+V5!kk8A+41IQL2dw^^LvJ1#I zAp3x91hNyzRv>$UYzDF$$aXlI{XjOv(d-DaCCHv2n}X~LvMtEIARB}146-%I-XNQU z><+R$j%I(54RSO)glrMAN6022yM$~LvQNlHAv=X^6|z^zW+A(UY!|X$$c8zZ9YeMZ z*)wF*kX=Kz4cRwj<B*+0whq}lWb=^SL$(juKV$<P%?=`4=xFv3*+gU)k!?iw5!py& zC-K2n22~C6cs<@4)v7=B$@`wF+`K`-HJ{b_a4gManz1y88Oso1uFM?D<InD}hwnUi p^U*s@X6OH#%_nw~6%97)+A<;4ENe_=QfzW;d`xCSJjNvj{|lpI#FPL4 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Bucharest b/libs/pytz/zoneinfo/Europe/Bucharest new file mode 100644 index 0000000000000000000000000000000000000000..4eb7ed0dfaed722604aa3909fffeec9abde87dd4 GIT binary patch literal 2212 zcmdtie@xVM9LMp`1y}6)u@4e~0ivRTcEACmXy7jbvN%OKja1}oBBGs?sO(7eZt0ve za_@{)mTtHlvu4B}lo=YSWVE@Hez?{g^Sf--nzJ@^t~pkp=ePc8u0PuPtM~2haXWr& zY`mU<<?9>r)omANKH<sjHBa8hN6gFdo-gB0%pS2${BqFx=<-We--R94$-%enQy;!z ze|+>syZ`kz`;$Ez?bD%E_L;^-_Su@b_P}F#_NPS??Q>ZP_GihlcKo|vSYxW~ss#Vt zL-!X9S&13Lkp~i@k))XZNYY48B>BqW$k?y;M;<)8Gm>)hJ(YT_Q>7hzS&eIdL8Z5C zR^x;9YQoB;YGP%%$|#<pCV8{e<kYe1p?f?k(_I+J4ENfZH#}C>_qVJmUthJR4h&ll z_eL%6k$x+CQm38W(PQQ8dZj9N%YMsOzqKl_w%g8c-ZoTF(x;~_c~uvt9n#bDLwfpX zSQn*qNYU_WJ@ZzJ%sN%7AGuT`#a(q$(l<+H@17@f-b|Ib4H+_T*GxUX+%2W`Df-dL zKT28Ys4jPZE)^M<b;Y$G^n#evdO>thKYryc{Y3XWy7GL#`1iHzg~txdqNXNY)xJ{} zKe<<y1lLOSjGzQ6D`aWXLRsdWBFk?2q$aUk*L*WZp1P5xmw#|wR$TPxm7Vdrw)d)D z6&}%b9Z_x7UDT_$|13|>Ij+~#o|pRc1A1*qw=~4PFYD4o*8TLFG>$gQ`oUJ&FdUF) zj;@g4xlMZGo)T&5s@Bgorpt4?3-zYO#k#p6NpH>@r?-^-rnkl`-IALqEx&%JL-*a3 z(9nQxz4om<-_xbHMf;@fz!}-zeMok+9+B|Au(;fJ#Qgmc>yEwcGrr@m56xG3+<Di3 z{ZJ}7Wd3P+ja6!@&t?9PQ^%`&|LJ|DKcMEDXIWV_F9GwTF4vz=evQi&_!o2e-CXd3 z9f!;YnU15G4>BQSM#z+qIU$omW`#@(nHMrKWM;_Jkhvj~LuSWc!1R#$ArnMqh)fZg zBQi-xGfQNe$UKpWA~WTfD#u(oCd)Bfj_GpDmt(>lGv=5w$DBDP%`t07Gi^sRZ)D=g z%#o=hb4MnR%-+#VADKUr03-uQ3XmKiNkFoIqyfo;qe%pk2}hF(Bo|0BkZd67K=Oek z1jz`J5+o-`Qjn}5X+iRWBnHWhqe%^t8%L8IBs)lYko+JCLNbJ;2+0wWBqU2nnvgsp zi9#~vXi|mb%F!eX$rh3>Bwt9vkc=TILvn^B4apjkHY9II;*iWCsY7z-Xp)Cy4@n=A zKO})j29XpZIYg3(WD!Xtl1C(wNG6d~BDr)l$wad0Xwr$~6G<qNQ6!~EPLZVI|2(T6 a)7tEOs_XR^<P_%katplvJfHJ!n&%H8T0s{8 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Budapest b/libs/pytz/zoneinfo/Europe/Budapest new file mode 100644 index 0000000000000000000000000000000000000000..dfdc6d247faa6497db386a7714a32e3ac743e922 GIT binary patch literal 2396 zcmdtiUrd#C9LMp8f0@kOmkf^v2t|Yx^6vnNL1rglB$&v_NF*>3|3rjD3`(Ptx#pPr z=9mr<MI*9i)E3$WS_##3ArVFf>M~Kl{xFK1Wn=YwKR2y))lC=uo}K6Q@SJhO_`KaK z))&WF|2R|38$O%{^WpiX7tO74nxn9>e{g)yK$|wT9M-)txn+Cbh-ltdyGxsQ_l4~L zs8df)bd`0FES6JmI<)72OFrKbFTF2^N$+)!vM(B{F7$2dC_KHiRlm&Wlm4VG`6{Aa z&dh#YzMi^M20V}Jz?CW){4rY34(G|m0|&>4vVShTwB=mcWoOuh;fim^NBoWyj=KA- z(VseXMpmMAi}Skrhkma!XPi@q*C};Oe4w+2-qG2ooAuVNS2f`1F$p}>CP91l$(%Zs z;7u<{NJ+WewsN)Hp6Qm*<W#vMGEu?;!{yFtvn1TdrQuiOb?)Ubjp(1LcYQKN?{5E9 zBU`TMy!t_ns{Kr(D~@VR(c2oEKOk}C?cz-7koaXgB_XIu662~Qak56{2h{8Q^Q$E3 z_ex!G%q91p&DG@A0!`^yp!aQEqzfAZ_5R{eT~wPS52X31t0+Jo3>(o@*QBKRoYeHt zZzO$eSTekhOGeKLd1&Z0S={!PWOje3S=;Mm$)WxFaA~P5t=pp6k8aatC2KV2o)UFu zrt2e)CAvH^LYH5Q(OmyD$vtzkK6Z7ktT^(E=Jor^%4R>wZ~s9aubGg7`W`7P=$BQS zztblczAvluyR|5|LDr<SY4MGRb#0L9+R<IQZn9k0pQzRi=iU0`!8|SbbfY}=N{W`Y z=E&3Qg0*aGf^5uAmhxhUJQF)dDpG%wXZ<XxjP}>c@rzP5{hC(wek|2v1Nz*%t@3<N zhi+=<)Xi;8`a*S!)@-j)PY+KoZ*w!3x0ku>pLyW2U-OmE4gdLNS$>zy|GyO&;^A#s z)5V(l_vgRfWM;W7S85J7H^1Rw4w?t|zgXKJ=4=>yFA!OstyvzjK4gK&3T@32ku@TV zL{^C`6Imy+P-LaZQjxXVn#CfkMV5=K7g;c}Vr0q4nvq2#tF|@EM%Ili99cQCbY$(w z;*r%O%SYC4YYM<$mI`nv0a62`2uKxdO&O3nAca6Gfs_KN1yT&88b~>idLRWsDuR>* zsR>dPq$)^RY)xH|!XTAFN`uq}DGpK{q&!G{kOCnULP~_x2q_X$C8SKYrcOwqY)z$* zQX#cMiiK1QDHl>Nq+m$Jkdh%aLyCq}4JjK^H>7a3rgBK>Y)$Qu;vv;T%7@esDIiio zq=ZNfks=~hM9PTN5h<jtsU%WLTT@G<m`F8|aw7Fa3W`(|DJfD@q^L+$k+LFnMGA{l z7AdW*sV!1mq`F9Xk@_M9Mk<Vy7^yK*WTeVSnUOjpg+?lkl-kzR8Y#A|sWwt>q~1uu kk%}WFM{17$|DyYv$j^%<_mNr7sD!AP$gJ2HjCT6|3H&I{GXMYp literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Busingen b/libs/pytz/zoneinfo/Europe/Busingen new file mode 100644 index 0000000000000000000000000000000000000000..ad6cf59281a1046d9dcd045fda521585e3e33e06 GIT binary patch literal 1909 zcmciCX-L$09LMqhq+=x|UkjT`&1!PZnmp6((5_jPcE=8#Ej!E(waeVJGVQV`Btqi5 zAVpMc%Z5ah^}y<Z9c&jJCJUQH7eUcw5kZ9=Nd4abC5WxZ{r}9ohV0?@{qfIST%2Tm z^*GJH@Zni)KK$;!(R^KTEwQfLFSD+;`>f`(xmK9_nfB^=M_mEe)b;AL_I_|g`~164 z`=0w<!%v=)h(iq$x#th*SE~}WZj<ycDVG7W7sx=LU)*UKGRTuE(GfB7L$}@%<Me9G zo8db6VYJ4!_R=92I_uEJx9ZvdREO2w(zq>GHGbtuO(;C9iTO7rsk~8=)0<>?&JIb5 z+$*U`m6F;~EhEC~bj00xGV()(jymO)(YNz7t-e6hn?~uFn(;bzcZ7~BcI)^pBV|IS zQ@w@Z@>BF<&G2?ert`99x$jBVi$^js;BT4Oa!G!E@R$73a8P{BXEb|ztxP)fr%o;{ zl_|BGb?WqOnp0Awxj&Yu-<PGox+du~PpnRBPtd%uOv$^^Lub4hEHjV4)>*B=GJ9XB z<TpN-In}SEpsq#c7PQK|^=&$T><L+r->ijEyQC<+L5sT_(}j_$3!m)NMIGh3_)?WF zx$D=Z2WDx>#WGp8HC;>VbLF>1QM$Y)Marh8NqMnLRwVY5l^O43Rj4Hu@nKr=^1f7t zv}@%*=cVe!O<i-eUe>lW>AGEKb$!EL-B7h(tG8EcCx>|h0>AfbSzXLgSyn`UN1$be zh}HGW-@a_W<;}?D%g_IEIP5R~w{JGc{E-h&rTOqX^rLwOy=>cvW!HmhkQ=r&cZ}RJ za?d>6G;-I-ZQGjrMs6IrbL7^Mdq-{_xqIaHk^4s)KsrELKzcx$K)OKMK>DyXjUb&M ztsuQ1%^=+%?I8Ui4Iv#NEg?N2O(9(&Z6STxn#PdMY)xxOZ%A`UcSw6ke@KH!he(S^ zk4Te9mq?pPpGc!fr?#e5q*q(hEYdB~F48a3Fw!y7GSV~BG}1NFHqtlJIMTVTX&vd^ z)-;cFkF<~Uk8A+41IQL2dw^^LvJ1#IAp3x91hNyzRv>#}Yc>Pf4P-lz{XjMZ*%4$* zkUc>*1=$s3TabN0HU`-lWNVPUu{E26?2fJ39%O%z4MKJZ*&<|*kWE5%$q~@Wyn)W| z{eB*%p!b#;CNocFr$WT){^f7xX~O>|>c5RL-@#_Hh9$CIp6ukfl(+;>c47j?CkKB5 Dj=i{v literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Chisinau b/libs/pytz/zoneinfo/Europe/Chisinau new file mode 100644 index 0000000000000000000000000000000000000000..5bc1bfeba62d14ffa8995c1d08ba7500956c494a GIT binary patch literal 2436 zcmeIzeN0t#9LMqRjYkpbx<)*!VFD`hD2gRYUcnTuUIdX$@RWooXrW@5fWJA%Xd0SW zvtq?sQ`wZ^Vw8eLWy*?x8e28A&ekk@n4VS6mbv=94`bE(tG52@b9e3=?wE7V?~8&p z8_QDUZ*%HTcvzEu_&P@Ex0fqk34EjDWB=0&el$*BZ!yk%@r=<uSa0<7wV7w%e9=62 ze4qK&ky^92akKe$O^NwV`3m#hqD=GrgURN5>CxtexVy}Yq26X+PqXp<lXaf{uXCIq zwuKxVc-7?`JT%)mwEUnE9D8i&?$Aq_A^sztkYDcX4gEG~C~U-)8Q$MK6w%XaMV@%p zifY+oP1?WBnp|6IO{rXE-Lp2^iq4&DO`RWS-5VcfO^Xb$V#eRHVqCdqtWTPXy%D40 zu7s%R##Lkb@b|`y^Mjt5odZVvYo|Q34tE#{dz(Fp+YcE@rPZF~;&wA->#m{HjFU2Z zWuv4;9gsOGn`F-IDoKxMQ0bTRW!|lFb>G=ExqoDtntwD;Wpu}@1r4cc;Y&d(vn*IW zuy>{`%DSmsr4wZFw9izQ>$+t7bgLy(KawTaK9!|juglVbGqUW<=Om}SMRNO3DR)!7 zEI-k#R%|iEv%gy9t*%uoD_5)hxh1L~cb-}mlBo*gBUItdXjK%PE=8YstA}oc%bKpQ z)WgHS%Gy>hDenASN_Jn7b%%S!Sa(q#sk@{eUDzh;i_fXj$@}E7j8;|Ve@tzN+M_mH zeOi^@E>#=*D%7US*{b5WTUB0IFPj?^)s~}K@_6|~^+ZFgROKbe*0OQ3E!ipCv%ZsR z=MS<Y(NFELhNULx8&xyZB|EQupq}bDBD)4os@fMiRbBf5RloCjwY#ZGIqvZB^8V|~ zcbuQUpa1xP(O2MvKVH82Cw+YX!<R^ezy6Ob;XjGDNNA1d?`9-90!1RH$i%Vt(NBV$ z63i!#u(9{_OvfaCUZTg|$37YR6LQ@J;?mC|{bXh3^QJ$rAN`Fxf3bdY_zO53qmTUs zAN+X*|KKiv(<|)i`<Zf|lpCeosa<`mlzXMzEah$~w@bNS$_-QQm~zXMd#2np<*q5W zO}TH%jZ^NNa_f|Pr`$Z{?kTrVxqtp0Fo5C!#R9w91BwY07brGRe4rRXae`t6#S4lV z6gMb#Q2d}6LUDv*iCygp#T2{R6^bnsU+ijQD9%u<p?E_vhvE*!9*RE{gD4JBEMj=X zFp1%kU2PM?C%f7xhEo))7+z7#qPRt|i{cl>Fp6Ug%P5{POryBQu#MuIU2Po0IlJ0A zhIb6}817N*WB5lgkl`T3LW+kJ6DclIY^3;TR~t!j(yq3W;w8gOikl2O8GbShWjM;P zl;J7GREDb*TN%DmjHNhhS6fT*mSQf&U5dRFe;Ec-9A;Qd@t9#U!)1oe44)ZBGn}@o zt!8*_SDVdnn_@S^Z;Ig*$0?T6|2_YAo(JgP0<%*1eGu<XO-M^figza`(Ztk%-vMds BjH>_u literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Copenhagen b/libs/pytz/zoneinfo/Europe/Copenhagen new file mode 100644 index 0000000000000000000000000000000000000000..cb2ec0671a372099fbbb7fe8f049ef0fd70889ef GIT binary patch literal 2151 zcmciCeN0t#9LMo<@8yb-vM(xJizgEatt-!v8f10_W^y&<s-z;Ch*CsyGX|y6gHdbD zeRE9b(52>BIida_{R1DCTEm4`O7EDKv*pU3W^U%}VXS`d;~1{`+uzwaud~D0_=C?o zu(YN++xpLmG+($mE#~HZ^eg7kp47UsJ$$sk*SBPS-<7#?`U8)K_WI<^)?Dd-Emgh< z#>tnHM9((8A?M;dHN2uz!k500fs#`?aO}8@32nbN)<0Xu<@~9!DL-r6*ss;&I<21T zM|J$=5A~k2yY=1^?`ZttBa(2SOA>d!D@m;?$s1pl3AI7FZ~1DOSQ?O&f+CsZ&6m`K zG`WA&cu8~nH0?&NPW~=cr-bA5flnj#!S3JHyYEMx+IB(HH-D}f8xCt`Ri|cEoR{oi zx8xM|NbbUIl9#wk^0ONyf3R7m#kc9Si>qY%Uky6rh)*8=s$2^?Dz&g@hCb3VOJ}wx z=%dvsI&1TEd92v2zN&bcoq9!!e1lT#KBXlom!#zC_cF)ziOdQ0%Hx;clet~{rL^yu z`nR;oyaRi5eqEhB(fYbBSn{?mtX-pJ57la*v_uzq=ILVZ6kU8XQ_EwErF>wFK6zuZ zEIs&}E(=G=^4&2~(fy08Xud9$Z6R4%8J1O>e$uCA9+K4+eOi^gL)H{_Y4ymDb#0>R z+8?&-y1}5<^fu|!7X$jt`^&WUvwC^<twOEqD3j;bCF}Z@JgHw$Ai-*nJfD>$8;bsr z7h)`F$cWX3Yu`#^^i6H-KPgRD&+CgHb;wJh9^JU(xNhp&r7t(_)8;MB>U20Au87+% zr+dVmzbxzSKJyQ1B}{NcSXQ)HcMUyneHs2<>JM1HqB0%<e#2o7nwP-;&tatbAvfnu zbMqeaw>dQ}XS%U|)UUb7JZ#NGkeMJ;LFR%?2AK^q9b`Vpgpe5_Q$prsYbJ%v%GOMa zKaP1J6GLW(ObwYEGC5l_J7jvu{E!JEGeo9{%n_L+GD~Ed$UKpWA~Qv%ip&+6EHYbL zGhJl9$b^v@BU47^j7%DtH8O2v-pIs}nIlt2=8jArnZ2!<J~Dq>lK><GND7b~AW1;7 zfTRJ*1Cj_N6G$qMTp-CnvVo)n$%m~;2$B(7lM*B+NK%ljAZbDJf+PmX43ZioH%M}j z>>%ku@?&cfgk;FpqzK6ok|ZQcNScs5A&EjVg`^6}6_P9@TS&T)d?5)#GG=R1hU5%M z8j>|6ZAjjb#37kOQitRYNgk3tBz;K!kOU$bv^6P2a%gLkh-48-Ba%lXkw_+yR3f=_ zxSTF$g!{J3H6rrPUqih#)ik{{bu>|n7Hjm-^VXN)?{+o+RnFmbnztyE)2Ug6)$7km P&r8qr`m-`IBPZ$~7FsAY literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Dublin b/libs/pytz/zoneinfo/Europe/Dublin new file mode 100644 index 0000000000000000000000000000000000000000..5c5a7a3b8d67f5a5c9e44a56e70a727967d972e1 GIT binary patch literal 3522 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 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Gibraltar b/libs/pytz/zoneinfo/Europe/Gibraltar new file mode 100644 index 0000000000000000000000000000000000000000..117aadb8364cd7901388098503f4538c7b445aeb GIT binary patch literal 3052 zcmeIzSx}W_9LMo<Ledz=9-ROa#K#N~Lj+uMBTGc0fLu<ByQZQhk&+R(WKCvG<2e5s ziX0#xlh851C0udI4L3v)A;nD7+#R#bQB$<%`!-!w7v6N$`_6kl&zza}?ws$>Gi-87 zl<RL-L;DR6SGj#Sw|K{X<hCs~xwYOp?_h+<FW6ze$jdj2a#|Sk{zknx<F5H~LY`hS zbB`%VT5rDUeMc7tkI_p*%Js7LVS3r+TV}bd+AOc})n8w{ri*r`>f*hJb;;5sy~1Rf zl^;atRi7L(tEVQIZ_-zr(*EIQP5dyuHbR+oQ5k0aqraLB&63TApO2W07hSsS=r4NH z@gaKi`f9yp)jhNI^ELY0+yK2TGe>WqQLlF-XX%{-3e2u<!*zL&ZF+Zdt=V1oh}q}Y zR`%r#mHkygQt?(#Ik3tlm1C0CK{+jl(nd*Dx}U1L6QvGMzNf3lg_<Mrr*utFYg5yw zUTRLBQng7%y7r!zIU2q|AHDslu4^(?*ImA%kDv87-|w%~CwAU8Ka^fFCrkYFsq6~# zWBT*v^pxrP%)s{MCy$u3QH{;H=wf|7AXHy?%wzn$4v7EtEz<nV3VFQRNQ)gGOTebh zDzK<Twai<jg64~AHS1laQ`1%J5#!Vo2_Ds^SFCz6tg8wR3{h<xH&-FvF%oj4v$Q)K zETJ`i(tcM%d8+i5gcV$r4%u}QK6k%#oW4mS#urQE$YUxhy;OCIE>oQcex|y#%vW8b zGF8|5IjUPgwsbo&R&~FVAw4$7sGi4@rB~r-i7x9Q&&+#Hdglhpvng%lxw+j{pMKsF zGd@7|4L&ciG4-mS_g;x>b5g}!J*VQmwyF5aJ?e!sOVo=c%T+?fR!LkiUnLbSmY34f zRR8(!$$%jr$-vYJGU(}4@g&5_%l=6+I4o2K-;9uC-+n6jyJj-<MmsfZ?Qb%?#z&1X zP1VTKD{9o7>uPj%r5ZE3Mva|)NnYu_PK_H`A>&&uR1>00B&E?Bnb=Zf;)NWURG%)B z_hiYdCp<D`*>FiMpQc{>Fj~?I2dUR5wUVjxx~ORbda3jjfAvOWkeVKQTfN!Tr7}AD zO2)O*DznK=$vm`EWnDccGgcO=nU!TSYvFd8U6L<vXBEht1#{#f_k-SE?!Ru{^!lgw z8+bqb-`@A{|9;p0!Cg(1y8*fyxm<zT-I#A&O`WsvpXY=`kE_ohk1G!Q+Cxk%k3G2D zZnu4$*WGT<1GMG7y@pizG(Z~TXgh<n2I&pb9Hcu)dyxJh4MIAEv<T@D(j=ryNShpO zpO8j5+D;*@LVATX3+WcpE~H;b!;p?4Ekk;SG!5w*(l(@TNaGxB=aAMRy+fLZbPs7C z(m$kuNC%M?B0WT!h;$KYBhp7l+eoC7j<%IZFOg;<-9*}n^b=_)(osj-QlzIyQ<1JB zZFRJLb+nB|I_qd#i}cpfHW%rxqirwJU!=iEhmjT|Jw}?0bQx(g(r2X6NT-ojBfUnN z%}<<eJKA<5{YDy&bR20p(sQKgNY|0JBYj62k8~bsJ<@xm`AGMX_B-1CBO8G10I~(h z9w3{5>;ke4$UYz&f$Rjb706y7n}O^GvK@|gKadS^v^#=q39={1rXah5Yzwk4$i^T$ zgKQ15H^}B7yMt^GvOmZMIocgUw#d=$5wc0hE+N~5>=Uw4$W9?!h3plwS;%f7+lA~G zvSE&P$B->^w0nkZ8nSE1wjuk5Y#g$4$kriyhio3Qd&u@7`-f~GvV+JLI@&!%HWArH zWE+uvL^cxHNn|ULy+k$>*-d0Sk^MwA6xmT^OC9Z=BAe=HcNN)IWM7euMRpe1T4ZnW g|IPjP&GoTc+#!-N4omD5-X%ODEHN?yJ9hH<16u-G00000 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Guernsey b/libs/pytz/zoneinfo/Europe/Guernsey new file mode 100644 index 0000000000000000000000000000000000000000..a340326e837ac8dd173701dc722fe2f9a272aeb6 GIT binary patch literal 3678 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 Yx$RPlNVqq$bELatR766Qn|A?!0JySS0ssI2 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Helsinki b/libs/pytz/zoneinfo/Europe/Helsinki new file mode 100644 index 0000000000000000000000000000000000000000..b4f8f9cbb57450549933f83ac90dd56a2ca75344 GIT binary patch literal 1900 zcmdVaYiP}J9LMqh%+NyL(Hv|u%xpNev#Z<YHfM9$FguvbY}n>D!>~5Db3~GszG{&W zvX;c`!B9r-BI~5Igq9-LB!!R`zxUr0<&h`KIi3IOv`%~UeSbXjSCl4Nf4n-GzwqHz zX+C@p@tH^6`ZZzq{JBLfS6>u`Mz#5R_4NB3fmeKvkBz?G&(CU~2gkJUjeQz+>9T~M zZjgw>N2OnlO5~R9(!Z=i1}t1E1G7C6mFAW~&QysGkCDM$drM4EhQ@qO*4P)(I;6Fi z4!zY`hc$gwXWbheUi(<%cHYzY4VTnad`1%r9!X+FlO&}#OY*G!k`i%5QWL8rwcRTt z!)kS8+hQ5@y;4VC&X6%r@-?l#P}7@7>)2frbljnE9bX!y6LyZ0iJ3u~Q5+_dqF<>y zqg^tC?rK)lQ^|V&Ql<o6lPUf?GWGchnbvShvRkfb&fXfCe)_o1C@+_pH9ItS?jD_0 zR-$<$%G8scrL!H=b&hk0&iUff{LoCvf7nCkeU6p+=RfI!)?it9EJO;L-pL~GM=7lJ zOHpB~EZ+K7myEk0OAA`GIP##Bq&H}3mvg!-LUq~e1G>DuLRZ|W)|G7@U3GGSmfc<_ zt9Pesd3~O&SstltccsX>+%%~ub;$aJezL*+O*V#DQW+nrl^>o-RrfDib^oSRzkj5g z8tY}Vzgf2&ysldtj_9`PI`!`LYCvEI``t0<U%oBNQDP2?XGhB#>I&#$S>gSyZohxe z&hc22&ByJ|<Kf}=RzSe7r{^zD_lJ4qT^xJ}Ibr0CkyGYBa?Z#}BWG=EP8&II<iwFP zM@}6%cjV-evqw%JIe#PpBm*P`BnKo3Bnu=BTayQp2$Bhs3X%(w43Z6!4w4U&5Rws+ z5|R^=6p|H^maWMPNzB${hNOn%h9rk%hopz(ha`w(h@^<*h$M+*iKL0-i6m-kGDT9g zHMt_mBH1G8BKaZ-BN-zpBRL~UBUvM9BY7i<+nUUg)NM`fNb*SbNcu?r$OIrWfJ^~0 z2goEKvw%zkG7rc^ATxnXg{_$jWHON1K&Atk4`f1+89}B5nG<AEkXb>d1(_FQVvw0Z zrpDIH4Kg{lW_FP2LFNaUAY_J+DMIE5|KmvtHXAiOk+pK>B*mq~x#E+YISDTNTXOJE D35>6g literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Isle_of_Man b/libs/pytz/zoneinfo/Europe/Isle_of_Man new file mode 100644 index 0000000000000000000000000000000000000000..a340326e837ac8dd173701dc722fe2f9a272aeb6 GIT binary patch literal 3678 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 Yx$RPlNVqq$bELatR766Qn|A?!0JySS0ssI2 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Istanbul b/libs/pytz/zoneinfo/Europe/Istanbul new file mode 100644 index 0000000000000000000000000000000000000000..833d4eba3985c0221daf6789c9ac30266bccacd8 GIT binary patch literal 2157 zcmd_qe`u6-0LSs??shwGZuRtI&13UN&UvR(o7-A9o0~PyF}<xPJtx?Nhn%J;<69O~ z20PjiOu<Jo94ut&TC&6-CD2N56fv8vC!OWmMo)x+2>LBrQNyn9`-!0NpX%Sf56``B z&&K{Z?(<Hp-@3U}yX{O>Pq;bl>gIhYsa~u-6P;bzd1lw;v-Xiq8=a#&Up9Iglja*W zr|jOMu=8fP&F+gt%)W1K*>C-_+d2N>Vq>7~hB=V#x6iMfFh3be8iRu;%nMihjnALH z?p)j!G%g*fG>2=w#^rd9^ToWK_Ls#soh!i&_K5eOIdZMW{_5s2bM#`fF*a<Q<0tpo z-~V{p{GsKHJ+URz{Ap&?*1FF)o^2;>Z}N4=x9T-JW9w<tU;d7f+4`yoR31wPCZE%S zq2BJ`=n^MO2JCEo$jly@W90Nyns@Z>H}34nH}zdRjJu!eOx@E^q1{{eVk)<AJb7QZ zIhE%-mz)-;PK9<KFhXMoQUxDhvZr_D8Z-9pkTYLuk+Zh1mG?KVkh9|t$-=6LoU<S# ziwd&j+^Lf?><^3Z#557P8Wi(}e--mTx-N=Ojfn+4pNfTh+U25-x5Wd`c8ijh{bKQk z_hsob2W8pPL$ZAJ3-ZC~Nm)_)gsiyrq>Sc2FQVhiW##00vE+lf^5M}cQPo>3mcBPb zRPQPhHC>umwmBdk=_rto#;%Fljlap|MS~(%>&RGVsk6d=-l{A7TCcnDonG(j*XxG{ z^p)Qp)mNS9)8iM;Sq=Nft;XYrt;bqhbz^V4_4vA1tkv5$S!<$a^+deRTASBsHB}$7 zntm>^)_u0fXiKNl9-sHWTp9j9FRw2%@J}z_l;CZb->+%;5%rDK^0#Oinl``0Gey%1 zW@$N^7G37Kiziy{-=F{WZ}@GzA)(c)I~H5ROF}CyDOYzH|5Y82I)A)#f6x;DVk_z+ zN;kbba0S^6vKv>m9b`YqhL9a0TSE4PYzo<xtJ)T_FJxoL&Ro^jki8+BLw1L357{5G zL1c%>7Lh$7n?!brY!lh1tJ)~CQ)H{iUXjfryG6E(>=)e)^FgZ}16xM+jBFa&HL`7F z-^j*Y)y_F=-Bs-!**vm)SG9d)|40Ln4j?T+dVn;6Ll=-XAbmg@fph|Cg{$fX(hQ^< zNIQ^zAPqq}g0uwb3DOj#D@a?AzPPH!Ae}*4gY*Vz4$>W@JxG6$1|c0nT7>inX%f;U zq)kYlTvelxPPwX9A-zJHg>(yP7t$}JVMxc2mLWYunuc@@X&cfvSJgPAb4cr4Rqv4I zxvK6V?L+#9G!W?^(n6$%NE4ARB5g$ah%^%EB+^P()k~zA*rvYg|Hp1-RjH;{FD%RY E9a<Tx5dZ)H literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Jersey b/libs/pytz/zoneinfo/Europe/Jersey new file mode 100644 index 0000000000000000000000000000000000000000..a340326e837ac8dd173701dc722fe2f9a272aeb6 GIT binary patch literal 3678 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 Yx$RPlNVqq$bELatR766Qn|A?!0JySS0ssI2 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Kaliningrad b/libs/pytz/zoneinfo/Europe/Kaliningrad new file mode 100644 index 0000000000000000000000000000000000000000..982d82a3ac959624e4cd5be0b33f40ed03a46e46 GIT binary patch literal 1509 zcmd^;OGs2<97q4xS2~S45jH+XGtEan%Em`iX<6fFzQ}Ug^Z*wk6+v1pBR)hVB5Ha_ z3oS#$2vUe@Q6{u#5j5E-2-y?_5rK=kn~034?_3j#Hnr||x%XT?24-N+@$Ws<;g&y^ zs86`rPJQ#9rSzlU?riIyd1(&Jjs>m`-wRy39|#QgTnRjh>k0(lH#(l(bOt7eypHD= z%N(KZ97ky0=$z{7X`4PX+A<x^eUp@TBeWv<RkbrV+~S;1e3U$w5K0Mqt5buQLTL}L zo32|I%=Ce7Gvn-WbLGi)GxNwEbJhM@GplKXnO&K0t}aS3b6hEsYqv>Wbh*m=l&kXJ zIn|n(SXJ=&iz>W3r;3Kds`zqHt?ijm>rRZRlJ-Hh{=idl9~+U<s!=K1eOk)X&r5~- zuvC2O5>HyM^1N=9jb9I{O%vs^IozNsM|P>I@glXQw^VKIPf*nzNore9p=|ekR^Ik_ zsmYmDKJQ1VjUHEZ*;7*Y;f2&k-jVvr2h#AiPa4Mtq$zk?HJ>{r{-(mt2qPjgYVnGR zj)`5eaK+hvUQ6wNf5qEHmV5NSA+Ag#N+iJ{algM8lyL6K<lIwj#ETS^$g;)f;)#)D zeJ0g9&scbGYW9oQ*UG~$&8;>1ZWJE}K7CMFQp(pF#=_Ik<L#IE|3RqldPj%$?SP17 zQOgAp3?dmsG>B{v;ULmM#DmBO5fCDwMJ*ykMu?CWwUqpP#DvHR5!9lV6e21_R*0|= zX(8f5<b?<fkr*N}L}rN45UC+zL*!-%&XC-q79AoxM0kkw5b+`MLjr(A00{vS10)DW z6pSz!aWDd5L}F2g!idG94u%m8BpgOOkboc&K|+GW1PKZf6(cN2T#UdVkugGp#AZ<k c$B53N4v!HZBS1!kkPz8_6{AfzTkN*|07r&%X8-^I literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Kiev b/libs/pytz/zoneinfo/Europe/Kiev new file mode 100644 index 0000000000000000000000000000000000000000..9337c9ea27c0a61b1082f4be37cfb0f9484cf5e2 GIT binary patch literal 2088 zcmeIye@xVM9LMqRSD@%LeCwj~0)!+L?SunF(IBz&U<RkBoJ1;d)`?m~ipHQcTshYo zxwn()T)J5?G^flqv_G)*W3H^hb${fVBUh`H%{lkos=0EkKF_bC{^+m%=)b<-eZP+{ zU&d~`@p?wrZfPm8{&C!G-f(ernTzKcyUp#S?|A%dpD*_LNMUmLYORdC<JH7lK}n39 z*qS-}c=Fz9Usrk4M<d?fCx1BYT1L`08mzi+_&_r2{4po{)P5)D$ji?3p6yQV&bX7; zy2iPGeYG>AZlRN3QQ$n__c=4OJ<hC&x13q8=Y3o7gLn6wFR#buUic#N;9w%=?|U~g zulG!>aL<8A(T?*Ph@aNtrpr>&J}9M?Lo$E$K`F~QDho<FWx-gtlxOv7`IQZ_=vId= zem^J=jW%jUf3sE&E!KzkF4ZM(WNTGRzAoLfNFJ$9(_lPHmd(7Xq2QQQr+ub1`QJ#* zjUQ!s%15$1aZw)q{%xrpI4O0*A82@AkE}TLrmk#jlSt1iy6Umlbam@StzX!x(YhL4 z<6WT*{yExkGoX!`)zbLYRDJx{0$F?R7hQMBBkPY%k*2}x@<jK~(%hSnSo0;>u<M#W zx#T^0s%co`xrb$A<$$)NpVdt{s+)c~q?^aub<4#refmmNpE<EkTR+(*&%RozZT<D~ z+~!=}y0=WWt*Vgr7O!kCo-R8=zsd7cEa@o9)DGu6>CCvPoyiN*b>p(WaHd~gObqGH z!yoIefus6TSD$w8>sEV0${nd`sVR5fKGW~|`}9n>eNLS8U!0beeZ>5KaZfe(JS*L@ z<_4@umX#rv@W#Gp{9ayV`^JAe{%q&)hC6>-7mixB<_d+PR=B>_3L1l<dVW}DUYZ9E z+jcMb>#*(UznH`y=4d$gX&jjbG7n@T$V`x_Aaijwli_2T4Kf{MKCWg$$c&IFA#*|| zh0F?>7BVknV#v&psUdSiCWp)pnI1AfS2ICmhR76=IU<upW{FG_nI|$)WTwbek+~w1 zMP`dk7n!fCnJ_YAS2JZ~&d8*ZStHX%=8a4onK?3bWbVl1k=Y~DN9K<t0Lg%>Ndb}r zSCa%J3rHG}JRpfcGJ&K5$pw-OBpXONkbEEsK{DcMQi9~f)g%SU3X&EiFGymL%pj>j za)TrX$qtepBtJ-kkPIOyLUQD4l7wUlNfVMMBvDAFkW?YLLXw4K3rQD}FC<||#*maD zIde5hL$c;-(uU*>NgR?nBy~vckmTY2*)v~)ZF@{B=atY#f4H=;tT0ei>JJwO+^6}T FKLL|r@~!{? literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Kirov b/libs/pytz/zoneinfo/Europe/Kirov new file mode 100644 index 0000000000000000000000000000000000000000..a3b5320a0bd139c07b8642c4efd7b98f57c6e8dd GIT binary patch literal 1153 zcmd7QJ7`l;9LMn!oAj2%!9^R}q&{jI`t+JKCDpV>(>AoGP$W21h2o<i2>y`{3PKCk zPAVvZiXw`^#pj@tRf4#PZ6~)X9B}f1oW;eW@qDj#au7G~&Ap#+Ng(9+Esh=;PpChR z8vBHcBWo|-ueznWr=FBTBdg<A%WJ|5Zf(tcw)UIvQTU@OuU$;Jb#rIR^|Kds<lbo= zy*{BE&K}n5PVLd_kLLA;eQCY1(5xFXb$U}<NXH^U(-c}V@jzVSU&GS;=JQy~%TLn! z_^q^+=B548bLp6VD4Qp4ihJmSY}r3+62~u_WX}!Lx%;F^MW;+xVz24?e!!&bPe}S* zpXpv2m95X3%(lghWbWjo=V^oV&ctN<H6__`M|Mm{%+CG~k~{dr^u=CFe@>f$z<tR# zzA*VOugqXo$qX*sHAC-b%&rILOkwV}44*${iiPOdWTjH^Rjv4S`UAhuYNy)qbNl=0 zcO0cUuGs%kwYbW!)WC?({;TP%TDg2*e&VxF_)KBAs9N2my;An-RLW;x_CSu}KWt}z zeue#z4f#GhB3mMRdfH8qU6E~(eUXikosq4Py^+n4-I48){gDQc4v-d*9*`zHZ5K!z zNFPWeNGC`uNH0h;NH<73NIytJNJmIZNKc-&DWoe;+ZNIn(iqYi(i+km(j3wq(jL+u w(jd|y(jw9$(j?NQr)?AI)6+JJbc(c!^olf#bc>UA^Z%{gV8)i++nx;m1PZbYN&o-= literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Lisbon b/libs/pytz/zoneinfo/Europe/Lisbon new file mode 100644 index 0000000000000000000000000000000000000000..355817b52b1b05680bbb57e4dc8de358eff27a39 GIT binary patch literal 3469 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 Y{v0ZPotiXi*2uqcfM2Hof8Le;4LU9nuK)l5 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Ljubljana b/libs/pytz/zoneinfo/Europe/Ljubljana new file mode 100644 index 0000000000000000000000000000000000000000..32a572233d4850a4bb4b1278cc2d25480ffc6a80 GIT binary patch literal 1948 zcmdVaYfQ~?9LMqhiLi!!F^Wn^ayhz}P{?u0rE=*Ym*kS%l7v=nxim9t_GOJ3=DLOv z55j|(xy~gCYlazPt~0Y?ZfpGB|IKFe$P=6Y>6}-qwe{fp{&*(O%Z;-Bc$%A^@a8Eo zZ@zD}#(Z4&ihWZ1a+KUW?5lPAU2<z{jMT3Sk@|0rg4_Gb<xct#^>b$z_&qzW9q;bd zP8YYR|CzHAaI{JSckPkR<tjld*GiYXLg_knmUK(?NN|E%x;x_}Bp_6JwDgluZ<mIC ziqW3WL$p^z2km{ix%R34qRxY_wQt1(4J*5$;Y-hGM9wjd%(^d8h1C+BSR*mxwn=Q@ zZi$O3mbk`JiTAJ2_(wCO|MwytaMmRQA7*MoWws{P4A4Ovl63IS03DJWtVw14WoWXu zx^nzwSjbCtyBa0g`<kW%KbDktFJwfM^D?6Ds*HSgKt@#^k<{9Anzp%I(vR-b(fRo@ zrhL7Qow!NI<;~WNetGIiP0{hb={mvLODBAe(9HJ9l6kMKPWseSCZGDKQyP3^>fSbz zRsB|`m41-yiaME|-5@hoz0sM2Ps^;VTFnXCA+r;!G`Gb`ofD`!=hb$d+gPacu9oQh zM;={pXo}`tSu6`TCTf0VhAf&Jqy-ydW%1YqDa`eiC6S$Fsr#!eYhy`KczZ2+|5S=w zf7asqOH%UgzAiseDJ$w~bmfi<x~giot}Z#KrJGCD(bTJnc{$9Nce8)_vaELT=Dw`f zVm1Bs8PLVi!m@t<<hQA59?RwCo#8Qm;BfH8<8XNX;+lV$XIjGh;mB1ZmyKLEa^c98 zBbRP#t{u5}<m&kkxO`i4{YU{w1xN`<4M-746-XIK9Y`TaB}geqEl4p)HAp$OrXHjq zq#~pwq$Z>&q$;E=q%Nc|q%x#5q&B2Dq&lQLTT>rWpslG8DG{j=DH5p?DHEv^DHN#` zDHW*|DHf>~DHo|1DcIIjjFfC^YDS7isz%C2>P8AjDo093YDbDksz=I4>PHs9)~o=s z1h!@kkVQaN0a*rQ9gu}URsvZHWG#@zKvn};4rD!$1wmE>SrS{bCdi^7tAZ>GvM$KN zAS;6`4YD@K;vlPoEDy3i$O0iNge;M*StDeTY|Sbm%Y>{GvQWrMAxnj<75@K=<ztqt WZzNOZOp6YS4U2H5MMhwFw9ij!n9hj+ literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/London b/libs/pytz/zoneinfo/Europe/London new file mode 100644 index 0000000000000000000000000000000000000000..a340326e837ac8dd173701dc722fe2f9a272aeb6 GIT binary patch literal 3678 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 Yx$RPlNVqq$bELatR766Qn|A?!0JySS0ssI2 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Luxembourg b/libs/pytz/zoneinfo/Europe/Luxembourg new file mode 100644 index 0000000000000000000000000000000000000000..6c194a5cdcb22da9319183df65478ec4e55555fc GIT binary patch literal 2960 zcmeIze{{`t9LMo{o5hyR+px)oTWbx?es3Xbm~mwD^TzU9X2_arX=f9b(~iiF^VV^a zM!RM<#Hfa8Lm1}7QcNf(ha;tqZpqP)v!l=Ro9cA(PyN$BeZS}XeSFXTvA=eoFXy;f zS+Tai9scGOE{+;=@f=uGwymzDOYxMFrt_Y)*D5crD_`S!bB}6i(P}NdSnqOA+2~on z=!&~6GQhK8{wyg^SnSz2DOoGR5<HdRTWp(p*gczDz9E~h$Gf&%&9S|8w#>El=m^`k zW0jt&%2?ZW_i)#am)qOkUN+nFjw|}bcXOAzsz*e+cMcin*cC1A-LZM@nl|6NYi?h5 z@A>w$`@J*E9kpk_bJR6la_p@+<=9uc%)S5hosRcQf*l9)S33^QT<SQKQQ)Xgj&dLF zHQsTg&jH8Lj5F?|=Y8A_1EL+ruMU+Hn}f8mEJZ$iHC|56*&!e02Fl07`st~a{p9q} zQ1$E4?D8w0rvA%2Nt>cnm3fKUHZxoUoE^1Y`ao$P_p^2g{aORteWpQPCp75BKJ9pU zyWW4wt(^|7)Zn_k60)^QI<GC2F2yQc7c3S>cCI`yVVXRc=9JLBsq#=nqJ)Kn%fqcZ zO1O85hTn|WZdbyzdsCqHINVYnss2$T%D>W{tIlg=(FYneuTG;gw`t78a}t|dEpbV; z5<luSN$9*z5@YivvAIxs1+UUx7p6+@U-Goi-V}N4(+urfF<Fyp`{?6K`fGAYi1y10 z)&51jWxybBP00+FfnirQHKkbwc^}ikp`Xj(>tDzauLClqu|b}=yg`OmZIQI&`!#(< zu?*X~UWe!8$cW;XbmZ7qbX4|q9sOvwI@1R0lR?9DOhk7bb1PaiIt-GGPXhF*o84sG zt{-%Kldnv0x0Q+2-^irG8!~xSqfD9HBvTh&)2EYn%e0BdHM8p)nVwXoS*>>JjLxbv zzFe&{n{#zmLxDbX!Kt%1jo0iWbLH8kNt#nJTIS5`s;(soGIwNO$;}Fq=VH3ZywsoM z`L;I6i|U|xo=cMNe@pXE)=R<lb2@)Vg}l&Ms|(f~)P+^+bWuUM7Op7Nd+g@)YH_dk z-OiS+?snei<Ll#VzT?&6p4NW<)oHVN*YiUSaoAg!pQ_k={`%}Mc0b;?1^j;h?G^J$ z^NRVFdH0X|oxhNl?zE+(j^^gHjpbpic`%3F&SmaA{CU{z&VNvn+vcDqNKq_JRgkhE zbwLV)RL0Vj2B{5F9Hcr(d64=b1wtx>lnAL2QY1@LC8SJ9osdEyl|o8|)CwsUQZ1xh zNWGAPAr(VPhSUrxnx&~4QZ}S+Na2vmA*DlVhZGN~9#TG}en<h43L+&$YKRok(o_*C zBT`4CkVqwwQX;iPiiuPcDJN1-{y!+FrKu=VQlzFxQIV=5Wku?W6c(v0Qd*?8mZrE! zb&>KS^+gJdR2V5SQe&jZNR^Q?BXvd!jZ_*bHBxJ&*p{Z+NV$=EBLzn)j+7j!IZ||_ z>PXp<x+8^0Dvy*NsXbDBOH+NM{FbKv$O0fMfGh#B2FM~HtAH#6vJS{XAS;0^1+o^% zVj!!5EQh684`e|s&59sPf~*O$D9EZH%Yv*6vM|WXAWMU+4YD}M>LANwY1Ri>AWO4C z$PyuIge(%WO2{%H>x3*6vQo%WA!~&!7P4B%av|%5ESRNPF=WY*HA5B+Sv6$Ykaa^A z4p}*5>5#QU77tlHWciTwLl)4|tRS+4mSzo+MMPE+Sw>_Xk%dH768~SyJA3)Bm(tg4 YYR?$fH6lGOG9fZLB0VM=qvCvj1?KmuQ2+n{ literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Madrid b/libs/pytz/zoneinfo/Europe/Madrid new file mode 100644 index 0000000000000000000000000000000000000000..ccc9d85750eaddf9b5eafac627bace09f4c72043 GIT binary patch literal 2628 zcmeIzT};(=9LMoLA`1BQ1x2g_sF9%s$b*m=goZ#S_{2#`B#w%xg>)ctK7-S#ImW&< zs<AS0GHj%1g}H^+Qc0aama;}=v0Ry@cA7GKV)T3eyJ=+?U3Js{zjI#af7s3i+wc8x zE?ij@XZ`IOWWM3Wwb#7(?5H$vufEgj*<F`p)v3d?XLE67qt6fS#>*wvfiwF&P5a`k zgLSjrhqjNf-gtJM^`<+f@~yHR?&djxmB-Iq^_<Afw@yBs>N&Z}(cPL<?m9I#ukut+ zwdZWl7p}G!N<Hl-X1Y4+I^7@cjdGo<JmUUn%P`l+u3Go`B|o|@OkMBp3d`v3x{~Md z9n#+Id-Hj>-?f9?{uj#KgIYqm2fyy{45?3Y4c*!68MZZDr94sPTA@3jFhT>JqjY%o zbQux<iw1>%uOo+luEE~z8hqnj9d+rT-f_N8?>zCGI*uNbko`>(`pRwzt5O-gVY7@W zE|al~mdagOP6<y=m%AeqWn4&v+%tHTMEIm>M9(B0|MfVX&^c1?Z5^ohHUFfM^;a~i z=2MN{dR8ZvAJv$`0~(v(C2?iV5}(o{ljglB38A|rF>Z|{_EyMbM~zOtxLA^YU9D4& zrO5-I<!W+6fu^)f(FeCp)6|zkbZSw!PTQI!4`ujhTA@RxkGrhtX}yx+b4D}6zm&}D z-^dK__hm-MX?f(*UYXg{C|PaqX?As$%-X+4XP1=7oT@E4cfn4bSG-Jf?k`qnR;E50 zJWJ<CPSE+cVl+1>LvlX})W>?p%fh#BYF?+mEUF8T{N}6jc*PAVsOb<_L8mO<_?<3E zJt9l<+q7_Wtt?Av(jwo(x;#{M`L{3Wirz9^d3voranY$yzLuxOAC$_f9VuGUkRwm6 z7_IJY2~s*YS;~rn<>}ZkDNp}do(ZsI^~4}u?fFX9485gm&YhIC*SmDxp$1vs(V`n_ zkL$*!UAk#)y;f9LXn(JMf4RNA`VH{8%{|a}fS><=argE8R%K3%HB`L%qvdB=A!EFT z{ONNbf3<?`?{D)tE8A%;NYCMomw8I#Da|}tmKVR{WiI>o{&;Zyi__d^u1$%DE#y=q zCmT84$O%VIxve?r$Z1DTJaX!hlaHK!qyR_-kP;v@K#G7=!Pb-ksRL37TT=<76i6+Q zVj$H(%7N4aDF{*#q$Eg9kfPX{s_-AAEViaDNMVr5Af-WSgA@m;4pJVZKDMSnNQICR zAvHpZgj5MB6H+HzQz)cTNU4xoA;m(fg_H}a7g8{!Vo1r5nju9)s)m#esT)!_TT?lt zbV%)x;vv;T%7@esDIiioq=ZNfks=~hM9PTN5h<jtsU%WLTT@G<m`F8|aw7Fa3W`(| zDJfD@q^L+$k+LFnMGA{l7AdW*sV!1mTT@-6yhweK0wWbhN{rMPDKb)Jq|8X2kwPPt zMoMjKYK;`z)>IoQH&Sn;;7G-hk|Q-oijGtrDLYbkr0_`Pk<ufzM~ZK2s*jW(sXwv+ z$O<4!fUE(s2*@fR%Ydu{vJl8hAWMO)1+o~nW;KxIur=#}EC{k9$dVvyf-DNMD)|4h d{L`}do6(7iWqu;F<D(OzV<NL-V{l@;|L;A&?*9M) literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Malta b/libs/pytz/zoneinfo/Europe/Malta new file mode 100644 index 0000000000000000000000000000000000000000..bf2452da40314be196f61e6a7cdd48eaf5c426f3 GIT binary patch literal 2620 zcmd_rZA_JQ7{~F$lLDz-_yDE{wa^en9z^gV(Uj1c2TM7bsVFL<7SSykL8%<eTs7vd zmf{$g_z=sc)D(4v)-q`{kr2bwaygw@+3eoTkSW^#dpqq#uX@v)?laEkaFmzV_vf9x zEO)r;A5UlV4L>|P%@3cu`pjF!sOwfmYvZ}f`lI&d1Fr1%-nwONsdAsI%6{M8x_Wo^ zwz(s%?Vdi_4SC6S<E5!`Y-fZvZA+7H^t?9b&(q@t;nL!1u}_Rk)NiM>NNZ%9buwm? zee$wTPlc38d(u9;{q&J@H{31OjZbRrn>TB%j`A}5*2QM~_G^1BSN$H_Z{bGEzjC$} zF!5z8@Qp${Xz06kr#?wm=g@&xa74Hjd}f3d($-T$4|nz5ck)l|vh{ag*Zrro+nVFP z`^!F6S+Lg^R#>UsCv5cfNS`A;hwO3nin^@fJ$}%LfMXhQ^)u~#;Uj(EM3wgW@*VAa z@Q}E7)ktLJ7U@^2(tqU|8IV^X56)dA4|%*2H8x!a#-_+1ceFg*rME-}rD^oF5jyzd zAdP7a*CF+tb!hE%jotB!4%=`><JKP4_!S2=A!oNHW}TMd1+|itS|=lBlu2^rW=R=d zBq<#wGOF(e9d&krjJ{E*V-BUsBj3-`vDNc6wQh`#TQ@<+SGe`j+$f!}cC<{K6r^c6 zedV!1moz=CLnZ}%t&^jEl*w0qk|_aS$dslAdHlk5nOgIqcp5*~jP<25ZP!+PB0pcA zEPY+4KmE4O$Xl$LBlFbjnXFGmOw*aMF*>s?L1*=vB(uKjuFqT>EVK9jp>tY8Wo}iN zWYzvA^GdGD{0&W#J-<~Jtol_Kjz1uavKlp~f4MA9t<l`zeYzx4b;-}0bZJL{E^8>( zXU}@|xew-O-dD@z`8QKFzdBQ1SlVB$b;+`P`dBH*jgS`;`^k#*c6lkxC57?5w9q~; zMcvx8sQHK#UpcL>d|WNBHr46M^255SX0xs?-k~MyOEkdG|K2Mg(7gQoc{i`S-ucb> zmwW%yKd94x{WAAdY3|A89^e<~a&;3|$ldol-~9c(C&TMXOV8xZ%U}4J2h9iXzqsDp z=CKZ)$U&~y(Ofce&B#R~SB+dYa^1*<BUg@GI&$sE#UodbTt0IBNCA)vASFO*fD{3# z0#XJ?QwO9FNF^LiDUez?nqnZ;K+1tmJ@`ODcuYlrk{~rfih@)HDGO2;q%cTj98GDE z+91V2s)LjVsSi>hq(VrEkQyOHLaKz6iHABNg+eOjXiA0D3Mm#+Eu>sXy^w+-6+=pf z)C?&aQZ=M(NZpXaA(cZ)=V)q&6c4E$Qa+@9NCA-wA|*s>h!hd2B2q@Ajz}SqN+P9n zG_^#E>1e8nloP2ZQc$F#NJ){JB1J{2ij)<pD^ggbvPfx>+9JhuG}T4Q>uBnW6d0*6 zQevdWNRg2$BV|VFj1(HFG*W7$)=04(O|_A7JDPeU1xG55lpLu!Qgo#1NZFCPBZWsQ zkCYy%JyLw6`bha5P5qGtKvn=*0%Q%4ML<>oSq5YskcB{20$B=VEs(`PRs&fMN3$Nt wf;gHLL6!tr6J$}4RY8^oSr`2OElj8xoneX0Pi#g~Tyk7OY(`=N#wUgT1<GOd9smFU literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Mariehamn b/libs/pytz/zoneinfo/Europe/Mariehamn new file mode 100644 index 0000000000000000000000000000000000000000..b4f8f9cbb57450549933f83ac90dd56a2ca75344 GIT binary patch literal 1900 zcmdVaYiP}J9LMqh%+NyL(Hv|u%xpNev#Z<YHfM9$FguvbY}n>D!>~5Db3~GszG{&W zvX;c`!B9r-BI~5Igq9-LB!!R`zxUr0<&h`KIi3IOv`%~UeSbXjSCl4Nf4n-GzwqHz zX+C@p@tH^6`ZZzq{JBLfS6>u`Mz#5R_4NB3fmeKvkBz?G&(CU~2gkJUjeQz+>9T~M zZjgw>N2OnlO5~R9(!Z=i1}t1E1G7C6mFAW~&QysGkCDM$drM4EhQ@qO*4P)(I;6Fi z4!zY`hc$gwXWbheUi(<%cHYzY4VTnad`1%r9!X+FlO&}#OY*G!k`i%5QWL8rwcRTt z!)kS8+hQ5@y;4VC&X6%r@-?l#P}7@7>)2frbljnE9bX!y6LyZ0iJ3u~Q5+_dqF<>y zqg^tC?rK)lQ^|V&Ql<o6lPUf?GWGchnbvShvRkfb&fXfCe)_o1C@+_pH9ItS?jD_0 zR-$<$%G8scrL!H=b&hk0&iUff{LoCvf7nCkeU6p+=RfI!)?it9EJO;L-pL~GM=7lJ zOHpB~EZ+K7myEk0OAA`GIP##Bq&H}3mvg!-LUq~e1G>DuLRZ|W)|G7@U3GGSmfc<_ zt9Pesd3~O&SstltccsX>+%%~ub;$aJezL*+O*V#DQW+nrl^>o-RrfDib^oSRzkj5g z8tY}Vzgf2&ysldtj_9`PI`!`LYCvEI``t0<U%oBNQDP2?XGhB#>I&#$S>gSyZohxe z&hc22&ByJ|<Kf}=RzSe7r{^zD_lJ4qT^xJ}Ibr0CkyGYBa?Z#}BWG=EP8&II<iwFP zM@}6%cjV-evqw%JIe#PpBm*P`BnKo3Bnu=BTayQp2$Bhs3X%(w43Z6!4w4U&5Rws+ z5|R^=6p|H^maWMPNzB${hNOn%h9rk%hopz(ha`w(h@^<*h$M+*iKL0-i6m-kGDT9g zHMt_mBH1G8BKaZ-BN-zpBRL~UBUvM9BY7i<+nUUg)NM`fNb*SbNcu?r$OIrWfJ^~0 z2goEKvw%zkG7rc^ATxnXg{_$jWHON1K&Atk4`f1+89}B5nG<AEkXb>d1(_FQVvw0Z zrpDIH4Kg{lW_FP2LFNaUAY_J+DMIE5|KmvtHXAiOk+pK>B*mq~x#E+YISDTNTXOJE D35>6g literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Minsk b/libs/pytz/zoneinfo/Europe/Minsk new file mode 100644 index 0000000000000000000000000000000000000000..801aead7d245b98f0bf90ec9d9a59d1bb53a8794 GIT binary patch literal 1361 zcmds$O-NKx7>2)boSG(`W|56IXUa6w94&LyrW$Q(9K}DdoLWSMi$N+{ShkEjgh-3% zzg1EZVk9L*KZ{Ig(IRZJX%QGxtBAlw-AzPAOy{{V5wxjo@8O*1+;h2Hxc7PEyY~0h z${)$sCrr|!C;L@OFXP5|=-#N&JH59*m3-gfd3M8lX69Pd^ZZ<$C*{iNEhxX9T2lBr z;&rCGy<ZnSDx7ntjC3@zG<iN%^zf?byLs01j~q9bog6TiA5ECW2X~k&cDI-%o$Jlg zaD}<D$}r1(F0<_NKzI44!v2bPUbXtAQ&m3xq5^m4RMlu&RbNP|HA7Qs?U4yplenVR z?RhG-hsPw?cuVTGpOAY0X=$iEAPwISNvLR8h2HeYhHv}S#;JPQlx|nyv2ChxvPv}# z2i4|FZWZY(P+NuqvNiTuMHBhbT=q)Eq93KjF{xThUr6hRS!uK1k+zu!(*E|MbWDs$ zXY#h{I(1A2GntGn$38FDk?XL}|2dosehs^8;optCJP~77e_%15NUn&lSezo2HKNbm z9`SHuTbx}(5u1^{o88;|zdcpb*%g;AJ!3J=o(_p>Mq@o(H0wjX3Dg96o!D$l9qTH) z#Q(ty{RuKV?@w1i{D3$D@dV-u#21J&5N{yv@NWEpI0W&?qIL=56T~TqR}i-#enA|A zcm{C|;v2*{h<6b8ApSudw5UCVxCrqP;v~dNh?@{UA&x>kg}4gw72+(xTY|d;e+do~ zJhrG^CirYoJ5BH!;x@r=h~p5?A+AGwhd2-Mp5Q*je?kW!Js@-e(g%yW6NFw^)ZHNT agU}H|Pas_(gZf|pk9~24b)TB5gRURG6*U|H literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Monaco b/libs/pytz/zoneinfo/Europe/Monaco new file mode 100644 index 0000000000000000000000000000000000000000..686ae8831550bb0fe033409c8b4df460fcd61a04 GIT binary patch literal 2944 zcmeIzYfu$+7{~ENMG<cgnqY!r5~7COAvFo@h-3nyo|IGs6H$|FC3Da;baG71aUL7S zGBi;UZ-`cS3%uX(Mpk2{<<#h;T^##2=GbM-_PdkQi(dJvH|@@z&+fiBug>}YyrZWS z#99A3S~zdGIQBXh_o4HgkL7t;L(9^NP1%J=dsW?Xd-cJm&3lXgwAYmPH0604%-VS^ z&AO<zX8r7RQ;|5|Y#0}2SB58=jS*#LQ+79dbF;Z-^UVZ%%k^Bd^+J}ZIx)a(JGs$R zZwxcr%ZJ)K-taZ=FP>^Xu)EqH7A>-CvYOaC2S=M-(bw$Vaq0G+Hud(NdwcD@KW(u; z`aI9nesSE?)$ca@_G~iy*W}s<-dSP}zH7{(;zDzHa*jEYooSAy_}a&M$C%@NSC|vo zRrZNX&C5>(jWMULc)vNlZHhT_eTbah9Hb5FJo52d336`n&*qb&K>0MYzqY9DW3}8G zY+6-J(AIBulr{@eRc0h<Kz@X_^>)y983W~^_}{gC*pC|6?kf#yd{%>Q?bi-hx9h{7 zm21Z%%Qd)epM+FZOQ%(9r1Mgh&^hy^OJR{bGIoM=P4`Mzzf^fNGD*TiBBWc(4ie$( z(TLj#+Wp&b?QuR(dmd}1kJbFDkrm%-uhL5zwcxZy&#2Rw{B0VWb5Y`oY9v0nRuYD< zki<@_B`L00lJ1sB@8DAHeR;g}`D3Q`-RF@fzRcEsmART++gG1lI6za%LbQKDm=0Lb zN1htwtDgK|85n+DQ$2TOknc%N3;SBqZeEkYjStD-hI)DC>UtScy+zW`9MFtqOC_^v ztqz?wO|q80uERzx(cy&?b;RR^>P=76XM-|zWMmH=c_&7*+YgfL3vKne+uddKu3vP_ zd4CyO9w0e2H)LGNEy*oyki6XUGJft4`h3c6nUHfv^Fvq4#N=u%@Y|`AI;l?j?roiX zw@9bd&(aqzdv)rjF<N+hy1ck3S*KNwke4Qhs=Y8#rVr~UMFm0fa%^Xrk@}mw5@5;9 z==M6(T#@3|ceME2QJHn~qR!q?DX%uv>YSB_b#C=)eQj2SmMkk#pQeo(KiJvC_x|bE z%&)ose|1_`(;LqJ(`poJHL<ML;^W7Y)$0CzNEe@g`){24K;B!ymi5nldWP4^a;{OS zBlzHQo;*BxoF~ij@o~T4Z@BmI@jgI5?m5TQ`qWaSBS=eJot_{~LArvp1?daY7^E{u zYmnX`%|W_@v<K-A(jcTmu1<@P9wALax`ea|=@Zf@q*F+%kX|9pLb`>t3+WfqFr;Iy zPRo#<Ax%TNhO`ao8`3zWb4cru-XYCHx`(t6=^xTSq=T+b3y~hWI!#2nh_n&uBhpBu zlSnI(ULwupmq9l<XeZK7q@hShk(MGoMVg9q6=^HdSER8>XI-7vBE3bLi*y%hFVbJ6 z!AOUZ79%}Inv8TAX*1Giq|r#Hkyg7py+)dibQ@_m(r={UNXL<uBRxl&j&vPqJJNTg z@kr;9*1I~rN1E^IbRTIy(tl(FkR3p_0NDd%6Odg%wgK4(WFwHBK(+$e3uH4~o!vmT z!`0ajWJ8c0LAC_h6J%46T|u@5*%xGEkexxc2H6{Ab6lO>LAJ-$*&k$skR3v{2-zcK zlaO6Pwh7rMWTTLsLbeLoD`c~f-9om@)!8p(!;l?AwhY-bWYdscL$(dsH)P|GokO+` z**j$OkljPJ&(+yKWCLBD9YnSe*+XO#kzGW#5!pxle<L4!BmJHE>=nz9MrOoEB}T<W LX2ix|biDsxtOUee literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Moscow b/libs/pytz/zoneinfo/Europe/Moscow new file mode 100644 index 0000000000000000000000000000000000000000..ddb3f4e99a1030f33b56fad986c8d9c16e59eb32 GIT binary patch literal 1535 zcmd_pOGs2v0Eh8&eK(rb!qglm&2)U$sA*0)IyEzjsUbNPSW%Qng3+OZ6j}@+wy8i0 zTv(<w>Y|s6YLnFvfkYNAS_B#d(ZT{b1Q9Ad&UZz$TD9&D_x_Go1;Ov{Z)$BR5`SH5 z^c!xj-TLO770{2~!?v;O6<<2~a%X1yzByZObnc(+f7>=aAe@1L@*#I{^@&i>RWve~ zaC~IY6&@P4`5GPslaD0WhbPu1O}P_eCL0pxR)vy2#ZM$pdfe;AuS}$j_ABe{Zk2lN zys}+9t=6AwR%vZ}Rr<jywV`gS$|%oP8}pM@rq!adV&|1T(k|^^lVtYC#6V8_(?HIf zIhp(Xv&_3cCG&%?WWm)Za#QC$x%o`LbToI%!b78~=v0p?cJ-+(dpcA}YCx419Z;p; zkE*hic3Jk$tDN&qa@*r9wSBT&mJfNP>yb@XbY;rQULoBr(Q-$pRqgamOV6<%%A5I8 z`aJJdRpcF6o$*Xn&%97I;XzgN`j*=Dp-a`?y`<{KZ_4`1CzZc0^@tH379J565g8R7 z6CJf8Dth5#iCy-ITe<9u<=^=89B&aK!>RufJR^iCykNxW^I6W7Jw}`mWo|?Nw{jgK zVewqmU?dA+O%tiVzt43T>5K2n+)F>t@7C4(MLl<;zP&sez51>dd5#j{^ZE6yUz(S} z(^$BcUYI8#{QuC`Pkrrs7#c%5Ls~<6Gu6!@-68EE{h8_pkq%9Di%5^Ax=Ex<q)q-* z`a~K<IyKd;BE2HbBHbeGBK;x_BON0xBRwNcBV8kHBYh){Bb_6yo9f<?=8^7Ab^A#F z$Oe!dAX`B8fNTQU1+oofAIL_KogiC5_F}3xgY3psZwJ{AvLR$g$d-^jA)7*Wg=`Di s7qT&AXUNu&y&;=Jc4w-$hwRT(ZxGobvPEQ%$R_cB-=#%wxuDqc3lb5KyZ`_I literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Nicosia b/libs/pytz/zoneinfo/Europe/Nicosia new file mode 100644 index 0000000000000000000000000000000000000000..f7f10ab7665e94ca44fd8cd98a362cd4b304eff1 GIT binary patch literal 2002 zcmdVaZAeuI9LMqNOjy@ye{0UQ>qF(rhpu|nY}s_Jnc9QbPV-i-GEb#fYtAi8r(5m5 z$Yg}XzY-#9P-GGju7RsTPxL@E2zOwMF+w`6v5iOxDx!vL=X;=6dll@>&gI_E<)ZKY z-(P6e#&DkJUr&tl3vZr?^XB{bW1l8}H+J}I+dH(^ihWjRkGpWq7~flHPS|zFdZp86 zO6yW9Zo{ZKvC1|k1t;6D=3h4AQ!kmXP3kogqK}#h54()l@9s1w|JZ1}aiziZo$Is` zPwudj4u!4c?s_|A+d^wfQ@K5LO{O)iBEwEC8fU%fkF}@!MywgJ!**IstdaKEYo`A; zY-Id&-^{%FgE4bp(De6yV`TN5GP67P897_`nt{4jBe$mC&I|6b@{84;m9@nxNNTZX z=e5i1(TL3P_2`_TbyE0Oo6bF7B5&WS)}p>zEj~L}-|3pK^A0BJyWv!w-&rW{mBnaD zolh1_|3gblMx`v~do54BE#)J>%cAH@vS{$SEWUeGmh_*HiW?U-xVu{_Pae^w&COzT z@6cr{cj^00^;-2-lZGnFb$LRiuJC8*iYEcBjxUqypC{@EkJDw<=|{TyrdQS+j+2^! z`?5CjP-=Sy#jL$4>$cz1_4CfihMF5%mvTVri~BYF^0(TMq}uT3er+6W(T&$Tbkk5s zKRmu#o33q^kG?F{=DsTVxG_aP=_-)T%Zj8WoFH3rlVxk^Q)!L!NLx<4wmtY&+9y2G zcI&EijQpaXo$8a%2hZxZ1DADs|5y4&N3TY9NA#tr7kfpI`A=USPs&1WF*6V~#^Xtx z;u-t=lV2)=Ax~*(6(1q~Dk{qT2))2<|Lr{7H~-F!BX^G6I&$yG%_Db@+&*&uNCQX* zNDD|0NE1jGNE@zBA4nreCrB$uFGw>;H%L23KS)DJM@UOZPe@ZpS4dk(U#?DLNM}fE zNN-4UNOwqkNPkF!NQX#^NRLR9NS8>PNT04wqe!Q&POC_-NV7<{NV`bCNW)0SNXtmi zNYhByNZUx?NaIN7u1@Pn@2*bsNcTwlNdL$NAUl9;0kQ|kCLp_jYy+|n$VMPL;p%J! zvKOw-W+1zPYzML*$c7+0f@}%0C&;ECyMk;BvM<QSAUlI>4YD_`&gLMygKQ77Kgb3l zJA`ZzvPZ}!A-jZZ6S7apMj<<eY!$LsuFhs5yXER^7qVZ-h9NtKY#Fj=$fn`{b=SPk a%w^><c>Z91c0qO^C*L2;4Y=QCdH(?jq|NOB literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Oslo b/libs/pytz/zoneinfo/Europe/Oslo new file mode 100644 index 0000000000000000000000000000000000000000..c6842af88c290ac7676c84846505884bbdcf652f GIT binary patch literal 2242 zcmdtie@xVM9LMqRfk3fxZ-2m9fKZ61b@Chh#YJ=iGw(FzGExatM68SQG6w#LelX`6 zWA7Tv9JwqVv!>V|lz*VLe%NT?WhQf4t}Rwp8eJmMkFokZzs>oF{?kAG(dWDSKEC^I z_s4DbdInZ(sLQpkIdSF<A5OdZ@O<;r=GN&Nv^r01sp&iHujxO(NRGeZ)bQ(G`Rv7f zIq__Ud>%@alWXGS!l5+1xZfu~z3h>p9hvfTQ>sMjMSiJt$ffd2GCX@wF1t?2i1V2I zDiIycij~pGNu3zp8JXl?Ad~a{(1i30nmFkzbw(do=kU8aW$=*R^2Hv#^}`o5>Bvz@ zKF}>Gue>T#+f-7wJ|k(tkleOvt=#SlNP1DJOmi1XMzTw$-!w&BF<y0z<m-%YGj!%a zqTX>VPVembP2Kx`&{-X4HM8|o&DwNCvuh7(PSqL74fRN#r&scqy(9%GyQMI<NeahW zWKL3t&N;VQ=Kk5J^NxCD{+E?n)K#sX-g$c0_7W}bOxC;W(zT>@uG~`=qu$yiS&(sF zOTA-K7W0Xgr++QwL*L25==Wt|xKHjK+$)Q^-xOc}d+Kj*lf?&K(<KcJa$nnXy7YnP zby;woR?H4+z*nyKI~VJ6_e@<rnyr-yWm0*1qCPk>Lsq<VSyv9k%c?ySq^jqlJk&BQ z)g57}sUDEk+kVtF#fN2WRlnAz?viz$ZmqlFZC#(Dy8io}T0a)j4Smh}@VS6KvVWxp zKi(*h?(k?sSA{%QpQ?{<FOZE(izHO%lqYhg%BIra<;e+_G-f4eW8@oY8b7K{Cq9zq zp)<PqtuEOT?$xckKG1F5yY;E&ecICAqEU`0NA$SsTv0Kx|NUiI@srIT*-B1xjI*rq zV%>P<{?D7M?|uG&<t?q?7T_BWbI?2l{>5zmGAA@NEr`s=)=UVQ5i%uYPROK?Ss~Lx z=7mfQnHe%QWNyghkl7*AL*|D}5Sbw|MP!c1B#~L#nrZUOnI|$)WTwbek+~w1wKcOv zri;uMnJ_YAWXi~#kx3)7My8F-8<{vVb7bns+>yy6v$r+VN9K<t0LcK70wf1W5|At) zX+ZLTBm&6<k_sdjNHUOY*qU@8`LHz!K{A4*1jz}K6eKH1T9CXTi9s@hqz1_ik{l#E zNP3X`*qQ_(8L~AgLUM#83CR+YCL~WtqL54>sX}svBn!zFk}f1)wkBan#%xW>kene& zL$ZdX4apmlI3#mO>X6(a$wRV-qz}m-l0YPbwkCy04v{1xSwzx^<Pk|El1U_$NG_3N zBH2XJiR2SWD3VcIlTsw7wkD}aR*|$Kc|{V7WEM#+{!eooZwfpshZej2d6@;7*=~PM JHfH6;{|(X%Sn>b> literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Paris b/libs/pytz/zoneinfo/Europe/Paris new file mode 100644 index 0000000000000000000000000000000000000000..ca854351687d88b3919ff33138f0a71994356b29 GIT binary patch literal 2962 zcmeIzYfw~m7{~ENK@briG{gkOBt#9lLuwM*ilhRfZb~YGiKt0-By-R-baKqwIFF5E z8JehwH$*E`47}fPQnDH|Eho!KyN&%DbL=u^=X-ilO|P6@^ro|O&gblzo!K|L-=Am9 z)S?8}KaSS+6Hbmg`{X`+zI|C)kh82jqtujNh_==?thCl0e%!pV<Tq=5MNd;vu-R;w zA7D1dv^AUNWSYw41!nX32&*bG*=&g_H(PVNS=)T)nQb?ctnJtH&5jE>rux(Xv-9*8 zQ?n()?5Y@M?S9$Eyt!nWdCTf*y<M`{s?G7X_6&|Sdt<Lz`w}v(x;Bkg-QE4x{_nS2 z?|f8X>OVed8XEVR19e-?!S(spq1Tq0!>=22q_o%^oswsc<z}1XX+GA8-f`w+-__<+ zZnbslQp<|Q;;1WUt`C*7+d{Nyqg&p4B}vW=`~1qeB|n(=OWMl^+5NRueIHlAj!@IO za-s$<>nLp&rmM_M(V)U8ZR_cv?Xm{SeTlzlaKyLTzTKx9()_H3+&riquI|$NKdjJ> z$5v`+!vP7au8~e_*GuQ+D&cb%NSERgd0^Z`>6+=0h<@qvV04N^hDAxYfDRJn<JPEK zN!tC(NbPaHz4koes}I%wq|udMYp+$8G-lzu8auN=;|h0beBMP#D5;gi)Otx8v09Ql zt&^05Qc1Z}CcQ&fY46Juq|dLjwC@48Jn~7d_N&U*)cU^q=%N9dRvxDPiz0Nu!anlY zARl!XhRVRm>zeMqBZGWSYevLpl5yjj3~qiz1~)azkgJ<yXw7!XJab61RxFq7>J2(< z`gF-z{*n$Ky;MgOPtuVO7po^TL!SuA)=|+tbkyxQ%?%zTxfj~%lefCdn7u#h*z^7} zt|CbCYQK~5Wj7^%Rg)CtpO*>qzR{=B_Q}M&Gg=tFMkb}!Xp!F@o!m)v@>j3wlshFl zwQ;sSec7YawvN@}lQZO*#i=^IYNR|nC0wmV$ueVjKPf2+k>}z&%gpqj<@q3&%!&=x zS>_8V4ZN+T=Z?$l8y9uX?kahqsb1%<IjZw&*6EA0E46GznR@v&YkqGpZ@(74Eqq&g zxAgb(Zx!%gy<Dzl@%CSntGCM)C|-WN<3IcNdsmRl71qTo_%GLveCE&R+-GKaTsihR zI(;M;kIU_x&Kvu&%jM<u$LZWS-^9hs%X1GYxoaQO8iy=Hl7eK#(M}7J7bG!AW{}h% zxj~YHWCuwPk{={NNQRIUAvr>l<Y;FJNfVMMBvDAFkW?YLLXw4K3rQD}FC<||#*maD zIYW}>XlD&c8<IC9aY*Kn)FHV;l80muNgt9wB!Nf<krX01M3U%eXAwywl1C(wNG6d~ zBDr+5lZj*#NhiMu^2tF$k&GfKMRJNH70D`+RwS=TVv)=usYP<@XeSrRE|Oj(zes|S z3?nH<a*QMy$ug2=B+p2qkxV10MskfL+tJQ8l5Qm5NWzhfBPmC6jwBt)I+AuI??~d2 z%p<8sa*rh6(at`Sen&h1$OIrWfJ^~02goEKvw%zkG7rc^ATxnX1u_@NWFWJFOoyXA zAIO9_+B1So2{I?hq#(0`ObaqE$iyHsgG>!FH^}55vx7{Jqdh;!1UcF>giH}KN5~{0 zvxH0&GEc}vAv1+c6*5=IWFfPKOcyd=$b>oCGlonVGH1x7A+v@|8!~Um#33_>OdT?J z$mAijhfE(bf5-$n+B1kup`$&A$Rr}Oh)g3gkH|zKGl~CCB@-I>Z>G}U-qc?4ZhK*) Svl3&HW8$K-;^Qzj(f@Z_2E5k* literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Podgorica b/libs/pytz/zoneinfo/Europe/Podgorica new file mode 100644 index 0000000000000000000000000000000000000000..32a572233d4850a4bb4b1278cc2d25480ffc6a80 GIT binary patch literal 1948 zcmdVaYfQ~?9LMqhiLi!!F^Wn^ayhz}P{?u0rE=*Ym*kS%l7v=nxim9t_GOJ3=DLOv z55j|(xy~gCYlazPt~0Y?ZfpGB|IKFe$P=6Y>6}-qwe{fp{&*(O%Z;-Bc$%A^@a8Eo zZ@zD}#(Z4&ihWZ1a+KUW?5lPAU2<z{jMT3Sk@|0rg4_Gb<xct#^>b$z_&qzW9q;bd zP8YYR|CzHAaI{JSckPkR<tjld*GiYXLg_knmUK(?NN|E%x;x_}Bp_6JwDgluZ<mIC ziqW3WL$p^z2km{ix%R34qRxY_wQt1(4J*5$;Y-hGM9wjd%(^d8h1C+BSR*mxwn=Q@ zZi$O3mbk`JiTAJ2_(wCO|MwytaMmRQA7*MoWws{P4A4Ovl63IS03DJWtVw14WoWXu zx^nzwSjbCtyBa0g`<kW%KbDktFJwfM^D?6Ds*HSgKt@#^k<{9Anzp%I(vR-b(fRo@ zrhL7Qow!NI<;~WNetGIiP0{hb={mvLODBAe(9HJ9l6kMKPWseSCZGDKQyP3^>fSbz zRsB|`m41-yiaME|-5@hoz0sM2Ps^;VTFnXCA+r;!G`Gb`ofD`!=hb$d+gPacu9oQh zM;={pXo}`tSu6`TCTf0VhAf&Jqy-ydW%1YqDa`eiC6S$Fsr#!eYhy`KczZ2+|5S=w zf7asqOH%UgzAiseDJ$w~bmfi<x~giot}Z#KrJGCD(bTJnc{$9Nce8)_vaELT=Dw`f zVm1Bs8PLVi!m@t<<hQA59?RwCo#8Qm;BfH8<8XNX;+lV$XIjGh;mB1ZmyKLEa^c98 zBbRP#t{u5}<m&kkxO`i4{YU{w1xN`<4M-746-XIK9Y`TaB}geqEl4p)HAp$OrXHjq zq#~pwq$Z>&q$;E=q%Nc|q%x#5q&B2Dq&lQLTT>rWpslG8DG{j=DH5p?DHEv^DHN#` zDHW*|DHf>~DHo|1DcIIjjFfC^YDS7isz%C2>P8AjDo093YDbDksz=I4>PHs9)~o=s z1h!@kkVQaN0a*rQ9gu}URsvZHWG#@zKvn};4rD!$1wmE>SrS{bCdi^7tAZ>GvM$KN zAS;6`4YD@K;vlPoEDy3i$O0iNge;M*StDeTY|Sbm%Y>{GvQWrMAxnj<75@K=<ztqt WZzNOZOp6YS4U2H5MMhwFw9ij!n9hj+ literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Prague b/libs/pytz/zoneinfo/Europe/Prague new file mode 100644 index 0000000000000000000000000000000000000000..85036de352d20683bdc6cd5e43ded7f1d5ec307a GIT binary patch literal 2329 zcmc)Me@xVM9LMqRfyj?3d!xaT{464*PJTmZkl6{01XH<;R08!xEh4y#L1|bq=NfZw ziRm1YW{g!cY71)(t@U%X5Px2V<=S#(S~)j!T8-7``HdJY|M&al?(yNvb{iY7=kP3B zUz=t9?+P(bcyVnvFU}F0&0E(LXHA#?^rhV+ecIh~Kwo}ebx+$)9Sm*Mp>qr5@as+; z-shGh9XWFJ`D8ifi;`pe-l;jhDp*czj@6T;$K~Wp{fYhnU!uP(U%pE1ms748@^$DA z8F4ho$oXcGU%d?x-V~kYiPq`m^W~=OKQuDwXN{WvtvUk_tMl>)8h!RHz4^pmo$<+b zjX8KoV)yq+-0nRR->#Cd@i|GX^T{nMR?Dqr9!V-FlG|K)k{p{Nw@-<dlpwdJT*=Xy zKO}3aKT7ZTELiXCzoxF9^E#{Zw5GLvsp%UIYKHes&8!-cEMLE57Y<0yk{yy8*DZNj z&5}3TD)}*;ntx`c%>J`U=Nxj&-QQGdL2tDd4$RSew#?JHU9oy^ZIaGwn=SVh2dUc| zBlDBbX_0$Wii5t;lBDmX<l>J~8u*cv4iC!xXJ3^CeQ!wF(1%*Stz8!Ge?=dtua`yb zFX-ZjUeqOZYqa97I`x#5=!4FMy401bORr{VWn{5bo|>i)UzsV(-u+FN`@>|#-UzAc z|3w~Yy)4z8!%|c2mzA3?=&HHz$?B>h^(O3+HHCdz8*)I`#;LCTX{W9m_38S-7Jc-L zM<07_xz>H&D35O~)cW2Ed176HHf+h2#>EBVt98ngnenor=y!Q4!jh)+NNu|Gy)=hk z)#jt0O3TF&efsTQd1iP(H}3jaH}!Svvn@T^x~)|M907ro#&3r?28}%km>hf~Zp)gw z)%;ysv5AgJmK82m=zq_a<(NA0Nm;qaau-$b=CMl5H|BCU__8mD!*l&bnUCe8?W<$# z9Ql{I;!8WOVcn4nwk(YASsAi4WNpaekkui}L)M2Z5LqFzL}ZP~B5lnok!2$5L>6jm zR*Edu)~pp-EV5c;xyX8L&4T$YSuwI?WX;H;kyRthM%Ili+}5ldSvs<IWbw%Ak>w-n zM+$&c04V`d1EdH@6_7F@bwCP%RKnJj0;vU345S)JIgolF1wkr;lmw{>QWT^rNLi4& zAca9HV{1x-)W+5n2dNHH9;7}<fshIzB|>V16bY#kQYNHMNTHBQA*DiUWowFsRLj<s z3#k`UFr;Eg$&i{MMMJ8FlntpHQaGe?Na>K;*_z@Z)w4C_L+XbV5UC(iLZpUB5s@k) zWkl+T6cVW<Qc9$jNHLLW+M04A^+XDaR1_&GQd6X;NL7)tB6URyi&PdVEmB*gxJY$v zO?i>}+L{6*6-G*o)EFr;Qe~vfNSz&lCdNVIcYrxg9(xcN9C9P>fAef2ZSrg)ZT=Gp v7wexSkDpC~BPRZoNH4lhs3(@%oWo4RXJt}zS9x|?Zd!(`JTn8+v%~%dH&JpN literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Riga b/libs/pytz/zoneinfo/Europe/Riga new file mode 100644 index 0000000000000000000000000000000000000000..8495c506e8cecf39abac3f8322a8723184ae6f1c GIT binary patch literal 2226 zcmeIydrXye9LMqB2`D1MuPr<fAR2PfkUL5Si5-C%978w~sfZb%7Lm6cl**Yhv&PuB zVl`JLnWM{$S~FXr>oV6Uk<LG?HM+Q5(O9$7$~oJhe(wiZYyHt$|Mq)!p4W38#>O9f z-o7<knsUtF&J6p8565pmJg4yR!n==69O>wq=s8%rx99A>iPuMe{(9hzh7F%?Y^)p{ zEthkzx^?KNM?QNhPXharC7_RQjZS?zFe~QEVt4pRrTe!TAH<AB1!6}$#WxK;9f&*C z6O2FpWH90ReZkqCJA#S38-jD1R|n^=D-GUUu{fAim=>Iu9ve)Kk2NWg5hgV(-=BIZ z#h><_+dqFe+`r(=75^<K#{B8WM*IuA2mKixr~R2b`}|oANBr4!XHAaxl*zd;c0RYL zU-GJ+k^F?ivM8rj7LB({L0q>My!X@jC8s@d+eobz_SS1r{}R3Zz%pI>T)Y-HCF!z` zTjh?@F!eOV$@1h;E%A&?Y1k)Pmh_F3UHo2FgnTF~h6d!$3ol7|-)mAa_`Z6(I%VbY z7xk`|7OCvqudD8ULaUk^wR&;0`YOtFwR@%3q|euy%UN0*T`IMo-=OzgN|QAwf7P|a z5wfmlrqrGIQP#KrBK6%vvY~!h?%gw{8<)N#_tgz*L*gN6Eb7yy@V9kSg6gIpp4H9c z+jYx8o8JGGPak+?tu}wWO&&a0q%FPG^3di)-FhHjwyi3Z?M-gkkv&^>mRymCXByd+ z8Lhj5-%4xLWo-?dk+zFp=p%3T%A-U5y8F;ax~K23?rl4!?OpBay2@T5q1S|6ZLiSq z$?MvPsq4DPzg{8NM{!Lt`Q^XejhS`S{tI#sHD=yhu5e@G=a@)i7GxPQCQ8hWym7@$ z-wTRe3DZBFes<0M^S7p-E4)5aWj__wK2uWSGv4ZQ<FR^5><e%8a{JOgILGBO#^s#J z?^Ab|%l9vq^t(MZC(o@%7KN+|S(c++7qT#9WysQywIPc`R);JPS)Zd_Ab%t)M3#uG z(a|mvStYVeWSz)Dk(DA#Mb?Te7FjK_Tx7k-f{_&?OXjd<4vTiQt45ZMtQ%Q4vT|hU z$l8&`BdbT2kF1|V0UT`wkP;v@K#G7=!O@lhse_{}1X2m46i6+QVj$H(%7N4aDF{*# zq$Eg9kfI<}LCWH2>w*-<(N+d24N@DVI7oGn@*wp=3WQV$DG^d5q)14WkTN;iIw6H} zw3R|ih13cu7E&#wTu8l;f*}<{N`}-7DH>8Wq-;ptkit3I$|0pgYKIgLsUA{3q<%;N zkqROuL~4i>5vd|lMx>5NAsuZcky1L^S|Y_ns)>{nsV7oUq@wtLN&l&&5jMdKb4YHw UH#Z|cBP%mE-J6}|Jmp3F0Y>aD<p2Nx literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Rome b/libs/pytz/zoneinfo/Europe/Rome new file mode 100644 index 0000000000000000000000000000000000000000..78a131b9fff014c3ea895a9cc7bf4197462b6602 GIT binary patch literal 2683 zcmciDdrVe!9LMqJ!Xt=EUo^!8#3EA@5kwSBG(}`4Sjv-{SG=JX(UFj}RJ`TNG50M^ z$JE42u8UAB%r(?jsk4b_7?zcjWoAWlXEUb+XutPij9LBdIXvg}a2S97-XH(8#W{mq z|G5Iq8$MhI%!lutR-4E6q+8bZ+N!!8$4}Wi54p16e*Lz!t2CmnH2WQU_o}k&Ju`+{ zdoy}upUh3PtFDfh)9;08_1c2E>OHA)=FP!!=JJWUvw5@hoBVE4lTl-z8xgPHj;oQ{ z$eY&re%tKx^{e&!_FJVP;h^1c;aFW`M2*$>S%uxyRADujY_)G+Icwip`-$Z`{;}<z z`<CU|G0pOhe#r`Wwb1tU-)y(b+iV4PSZW8Q4YPylr`W-<Q>@@q$#%z(WUJ!`;dZBA z>g<q>&Gvn#n=L8mVTG1mvN~rqS)Hr5*<CWn$o&a_YuD&{?bhWd4fCGXu<J*)`{fVy zfpewW<Eu9`{O}QpI8ZK;J9bIWVwI>BYou3hzC1W%o;;M{m+0YX@^H)$=^fEW9_iR! z`uI|{&yB&__eyW=SKCb=Jszn2D{g7bzTb7gmLD{B{V5$-a9HE!mudXW3o<CbLK0Fc zW$>h}k{G#Dh74LJLz;^uDSV42U7RDyjfFbwNUA*c<5V5Kf3~Jn4$~1EM(N1y5&C#e zw2oSzEThNzG<ANsjOl$<(^8vdtnX`0kG>@7*M608-Y;Za^+|c+@?IHV{=Q^XeXg0C zie<uq-TGu+o;+2&PA5*;sFQLRXx7kN^=G8((_s^Ia!fy+d^1j`b{#8Izw4~e+~_OQ zKK)Cl*9Oas(omUM@u$oxx-PS~R7>{kTA8!*51l*mkj$G|rSqdoWI;-~<^&zog^{WY zf7_;un)7w>$>sX&MZZ4x?sUyPu|%GKEk*P8XUPkTqSV@uC`%>|m;9VCc`?4H6r?rC z(omNa4(zIh_Rq2`<fbk=drX#JyPz+Bv|nDSuGAGJU+T*8ow{oIJ}uf*q#k$M?G14A z<Msr2ox9bR-|l*PeC_W2?Q(UfFvrUk(aRm+a)pSiee1L3P22m7Ous8NEsKYr|8ScN z%@_awALbzQM?PF-=EL{UJLXC`+OrC+!)q+$a66g<jvR92pd*JJIq=A#M-D!6_>ll0 z5kNwK!~h9`qlp3%21gSIBoIg>kWe78K!Slp0||$ti3bu8BqEL`BuGpgO;C`iAYnl# zE_@*{TqZI=Xpq<-!9k*fga?Ta5+Ec(jwVD%j2umnkSIBtFd=b50)<2h2^A76Bv_6n zT1dE%cp(8pB8G&_(Zmc18WJ@mY)IUYz#)-CLWjf-2_6zXBz#ExkN_ePL_&zf(9r}D zi6Rn4B#uZRkw_w;L}G~q6Nx4gP9&a4K#_<dAw^>9Xo8AF)zO3%i7OIVB(g|ok=P=^ zMWTy@7l|(tU?jpwh>;j0K}MqNXu^!d+0g_Vi8K;wB-Ti<k!T~~M&gYG9Ems*awO(R z(2=M+ny@2rcQk=VB9DX~i9Hg0B>G7Bk@zD6fQ$e#1jra5gMf?zG7QK#IGTY#MgkcM zWGs-uKt=-@4rDx#0YOFt84_enkU>F41sN7(TpZ27AS2^wh6WiMWN?ttL52qzA7p@# z5psJyZFhjr!;i=73vljMTYkIi>1`Ky@9)+XGFxO;$ZU?8iV$&iYJIl6X?xEWP5Rd! fwGEQ_7HmdpKs<95lbH~k7#kOp86SrO6N3K%dd(YT literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Samara b/libs/pytz/zoneinfo/Europe/Samara new file mode 100644 index 0000000000000000000000000000000000000000..97d5dd9e6ed7fc924c9bcb514f991cc8b52061b3 GIT binary patch literal 1215 zcmdVYPe_wt9Ki8sx_@jDUTVuf{WDvey4KvrtZ6gTVQnBWIz+((WzZjJh=&fr1T6?6 zLLw-Nkfc+H2RoUxL(s)`kZwcxL3D|T5p^hu^?cvir6B0o^X}pE?B(T!?f1=}x^O<K z{#agfhs_!=n{(5w>YaQ(=N;V=xL?}pFGqatH)-E@+k*dtDs8L8Bh4$<OD!*Er1ja9 zv^|`V?YG8c$F-BP^KwRZoleT`Y*5-$&9bM<D;=$#>R#`9HQ)#o0$=@weeZpfLG@Y% z-+t7gS8KX+v8=o1Uh3|<3pzYKtM^aL=*YP#ec;TzM8|JRPv0Ghowy|NwsA>BbCURx zmt@ODom@*u?|N1rT=vVMN?50!#&zFPlkUIa(}y2?*6FctdSH6992u(U!LwC4+Oe#M z23KX+@mOct7bWv)Nk$s)$w>K;9D8?Fj?Wh*yYi%vyM3ivtkr6^hQ|73cWhivm(%5T zHT?SeH=QoKU8(RF^Jl71M459kt=vitkJ>i<ezuwW^=Cp6oAo4jcs`rUtIkM|*)g-@ zO4-b(zBq2I{67rV{H_|qMFz|(7&0<wWZ0Hw;K<OC!6U;*0ze`_LO^0bf<U4`!a(9c z0zo1{LP26dg0VEwAmJeKSek&4h>(zwn2?~5sF1LbxD1NGkjRkGkl2vmkmxK;cu0Jf zCO{-YBt#@eBuFGmBupeuBv2$$Bvd3;Bv>R`BwQq3OA{~>v84$ai5UqRi5dwTi)Qx! OP28T8iNC))=J^Tns}05g literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/San_Marino b/libs/pytz/zoneinfo/Europe/San_Marino new file mode 100644 index 0000000000000000000000000000000000000000..78a131b9fff014c3ea895a9cc7bf4197462b6602 GIT binary patch literal 2683 zcmciDdrVe!9LMqJ!Xt=EUo^!8#3EA@5kwSBG(}`4Sjv-{SG=JX(UFj}RJ`TNG50M^ z$JE42u8UAB%r(?jsk4b_7?zcjWoAWlXEUb+XutPij9LBdIXvg}a2S97-XH(8#W{mq z|G5Iq8$MhI%!lutR-4E6q+8bZ+N!!8$4}Wi54p16e*Lz!t2CmnH2WQU_o}k&Ju`+{ zdoy}upUh3PtFDfh)9;08_1c2E>OHA)=FP!!=JJWUvw5@hoBVE4lTl-z8xgPHj;oQ{ z$eY&re%tKx^{e&!_FJVP;h^1c;aFW`M2*$>S%uxyRADujY_)G+Icwip`-$Z`{;}<z z`<CU|G0pOhe#r`Wwb1tU-)y(b+iV4PSZW8Q4YPylr`W-<Q>@@q$#%z(WUJ!`;dZBA z>g<q>&Gvn#n=L8mVTG1mvN~rqS)Hr5*<CWn$o&a_YuD&{?bhWd4fCGXu<J*)`{fVy zfpewW<Eu9`{O}QpI8ZK;J9bIWVwI>BYou3hzC1W%o;;M{m+0YX@^H)$=^fEW9_iR! z`uI|{&yB&__eyW=SKCb=Jszn2D{g7bzTb7gmLD{B{V5$-a9HE!mudXW3o<CbLK0Fc zW$>h}k{G#Dh74LJLz;^uDSV42U7RDyjfFbwNUA*c<5V5Kf3~Jn4$~1EM(N1y5&C#e zw2oSzEThNzG<ANsjOl$<(^8vdtnX`0kG>@7*M608-Y;Za^+|c+@?IHV{=Q^XeXg0C zie<uq-TGu+o;+2&PA5*;sFQLRXx7kN^=G8((_s^Ia!fy+d^1j`b{#8Izw4~e+~_OQ zKK)Cl*9Oas(omUM@u$oxx-PS~R7>{kTA8!*51l*mkj$G|rSqdoWI;-~<^&zog^{WY zf7_;un)7w>$>sX&MZZ4x?sUyPu|%GKEk*P8XUPkTqSV@uC`%>|m;9VCc`?4H6r?rC z(omNa4(zIh_Rq2`<fbk=drX#JyPz+Bv|nDSuGAGJU+T*8ow{oIJ}uf*q#k$M?G14A z<Msr2ox9bR-|l*PeC_W2?Q(UfFvrUk(aRm+a)pSiee1L3P22m7Ous8NEsKYr|8ScN z%@_awALbzQM?PF-=EL{UJLXC`+OrC+!)q+$a66g<jvR92pd*JJIq=A#M-D!6_>ll0 z5kNwK!~h9`qlp3%21gSIBoIg>kWe78K!Slp0||$ti3bu8BqEL`BuGpgO;C`iAYnl# zE_@*{TqZI=Xpq<-!9k*fga?Ta5+Ec(jwVD%j2umnkSIBtFd=b50)<2h2^A76Bv_6n zT1dE%cp(8pB8G&_(Zmc18WJ@mY)IUYz#)-CLWjf-2_6zXBz#ExkN_ePL_&zf(9r}D zi6Rn4B#uZRkw_w;L}G~q6Nx4gP9&a4K#_<dAw^>9Xo8AF)zO3%i7OIVB(g|ok=P=^ zMWTy@7l|(tU?jpwh>;j0K}MqNXu^!d+0g_Vi8K;wB-Ti<k!T~~M&gYG9Ems*awO(R z(2=M+ny@2rcQk=VB9DX~i9Hg0B>G7Bk@zD6fQ$e#1jra5gMf?zG7QK#IGTY#MgkcM zWGs-uKt=-@4rDx#0YOFt84_enkU>F41sN7(TpZ27AS2^wh6WiMWN?ttL52qzA7p@# z5psJyZFhjr!;i=73vljMTYkIi>1`Ky@9)+XGFxO;$ZU?8iV$&iYJIl6X?xEWP5Rd! fwGEQ_7HmdpKs<95lbH~k7#kOp86SrO6N3K%dd(YT literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Sarajevo b/libs/pytz/zoneinfo/Europe/Sarajevo new file mode 100644 index 0000000000000000000000000000000000000000..32a572233d4850a4bb4b1278cc2d25480ffc6a80 GIT binary patch literal 1948 zcmdVaYfQ~?9LMqhiLi!!F^Wn^ayhz}P{?u0rE=*Ym*kS%l7v=nxim9t_GOJ3=DLOv z55j|(xy~gCYlazPt~0Y?ZfpGB|IKFe$P=6Y>6}-qwe{fp{&*(O%Z;-Bc$%A^@a8Eo zZ@zD}#(Z4&ihWZ1a+KUW?5lPAU2<z{jMT3Sk@|0rg4_Gb<xct#^>b$z_&qzW9q;bd zP8YYR|CzHAaI{JSckPkR<tjld*GiYXLg_knmUK(?NN|E%x;x_}Bp_6JwDgluZ<mIC ziqW3WL$p^z2km{ix%R34qRxY_wQt1(4J*5$;Y-hGM9wjd%(^d8h1C+BSR*mxwn=Q@ zZi$O3mbk`JiTAJ2_(wCO|MwytaMmRQA7*MoWws{P4A4Ovl63IS03DJWtVw14WoWXu zx^nzwSjbCtyBa0g`<kW%KbDktFJwfM^D?6Ds*HSgKt@#^k<{9Anzp%I(vR-b(fRo@ zrhL7Qow!NI<;~WNetGIiP0{hb={mvLODBAe(9HJ9l6kMKPWseSCZGDKQyP3^>fSbz zRsB|`m41-yiaME|-5@hoz0sM2Ps^;VTFnXCA+r;!G`Gb`ofD`!=hb$d+gPacu9oQh zM;={pXo}`tSu6`TCTf0VhAf&Jqy-ydW%1YqDa`eiC6S$Fsr#!eYhy`KczZ2+|5S=w zf7asqOH%UgzAiseDJ$w~bmfi<x~giot}Z#KrJGCD(bTJnc{$9Nce8)_vaELT=Dw`f zVm1Bs8PLVi!m@t<<hQA59?RwCo#8Qm;BfH8<8XNX;+lV$XIjGh;mB1ZmyKLEa^c98 zBbRP#t{u5}<m&kkxO`i4{YU{w1xN`<4M-746-XIK9Y`TaB}geqEl4p)HAp$OrXHjq zq#~pwq$Z>&q$;E=q%Nc|q%x#5q&B2Dq&lQLTT>rWpslG8DG{j=DH5p?DHEv^DHN#` zDHW*|DHf>~DHo|1DcIIjjFfC^YDS7isz%C2>P8AjDo093YDbDksz=I4>PHs9)~o=s z1h!@kkVQaN0a*rQ9gu}URsvZHWG#@zKvn};4rD!$1wmE>SrS{bCdi^7tAZ>GvM$KN zAS;6`4YD@K;vlPoEDy3i$O0iNge;M*StDeTY|Sbm%Y>{GvQWrMAxnj<75@K=<ztqt WZzNOZOp6YS4U2H5MMhwFw9ij!n9hj+ literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Saratov b/libs/pytz/zoneinfo/Europe/Saratov new file mode 100644 index 0000000000000000000000000000000000000000..8fd5f6d4b881457d13fdcdd35abb6fc5429d7084 GIT binary patch literal 1183 zcmd7QO-R#W9Ki8sxiy;|x|B0Fd$GB6T5E1HYuaqSV9k&i5mq3*2tm+~@K6vaWS%N0 zf`}rDMwea>b;@K!mq<Nzv)~V%dLe$7E=jHD`xj3gqFc{@&;Rr1*%)lUZ(-=fNW%QF zR@f6ZtIKYlSKT%3<Ijs#gR7%AN^631@#@OiZ1oS%)8J=Qs+mv4*Unrh)lOY?LJ!Y7 z;aj6l-Nob1x^w%T^(XtB4TsXs#(bkwpV_RNnrk!?3TQ*sf<}E&iGB}C<GZiJO|QR5 z?Ad#1F3w8JwQ1Qh@kF+c-jVpRE3)nIlqODJ*Vc~Pn%s9*Q{i!KOB~d;pGP!Zdq&b9 zy0v{_NVdOh&>iy`$=uIL$BR1YoQ%lMn?|xDe(9PB>8_qnk~{iKyCZL<C+BFd?~(M? zztX;MZ?wOnsQq&fboa+e-Sha8=4bB7z~xg~$cKjy<o3!~xm@;CEL*(1KKEMg=khM{ zx4YNx^%@g%|L>-_vCqAOo=RiVS+jEKzI5WTCySrq-TXko#Nw@Xr|eD|<FPLm5AG`b z!yxVNC^JlCpnL&CMFxuu*VPUf88R|xWZ1~Sk)b1lM~06CfJA_VfW&|Vfkc6Xfy99X z;%Xy7LP26df<dA|!a?Fe0zx7}LPBCff<mG~!b0M5wSgg#x!TZ>*pT3m=#cP`_>cgR z2$2wx7?B{6D3LIcIFUe+NL_8HNUW|lSR`5`TqIs3U?gHBWE``z|8HXsWNhS}Ey=)d D1dR{C literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Simferopol b/libs/pytz/zoneinfo/Europe/Simferopol new file mode 100644 index 0000000000000000000000000000000000000000..e82dbbc78647086170f0d291ee449122ed18e875 GIT binary patch literal 1481 zcmd_pTS!xJ0LStF+N`-u{|}Wew=~Tzre@Q0n$~PiF}GT7U5G9V%m~DXjOZas%6bTm ze<i(ivl1hbD5zd6x*(Bcx`*B@5`u(?IFC_C{l7=_rHAObb2#U7JYypG{Y7^jYVwFb zjuicdizB2jp0nEYt<Tw$`KZSkdt>X24^Py~w|7(3$SuDdy;v+qZ+OQSEWZ<9nD*Y6 zY99-y{z!SA_9Z#)9P|4Y4PT0<Kj}7H{TB@P)l<gej#eY%c%zZoyv<m$yT-^0mKxa= zD~zRiP9w+VFmkT9hL?2~7%Sg>jpYuGhF3itiRJY?2<La*kLAC76)$K%<6C{CJzUs$ z(6?q!cieNhS9pd_#EU8iWO2h~xz>G6mUxcIlF5_Oo8G0oANI?#pGQ^sQ@>m{wo_H~ z?o*Wm<!b%eDz)K?OZl3zRaJYL+*p&K{Eg|dI%h%!{FAaKVMx_xf0VV~KFd1m3t2bv zT5kGuTh`y}lfmI<Ds;X>ZtlOSwzRa!aK~x2b;mi?(0o8eO82%!gYKARmUXr*!Iog1 zGj-Y%XHM%Z2PYpS+O2bw{&vo@3z6|%{|`jQxUdP~$`txdZlOpLB3TN_8_WFZyVqxN zPJcT6Y;p63`_y;6KEBIu2!^5}qOU+8DngNZ;n(^D5q_xFFZBb5#bRC>{V;RzHu?`4 z{nE$w$AfkxCnPB(D<mx>FC;M}GgF-!e*n25$syU9>hzHOkOYwokra^}ktC5Uku;G! zkwlS9kyMdfkz`GEwn(~2zDUAI#z@LY&ZatPBx@vXByS{fBy&@pI+8n*Jd!<<zNyY1 znSiOD0Wt+-4#*^sSs>Fu=7CHEnF%r#WG={Lkl7&9LFR)@$W+e=nUblV6EZ1eR>-uF cc_9-+W`;}+nHw@WWOn!;P0ykCnqTDj4a}fXaR2}S literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Skopje b/libs/pytz/zoneinfo/Europe/Skopje new file mode 100644 index 0000000000000000000000000000000000000000..32a572233d4850a4bb4b1278cc2d25480ffc6a80 GIT binary patch literal 1948 zcmdVaYfQ~?9LMqhiLi!!F^Wn^ayhz}P{?u0rE=*Ym*kS%l7v=nxim9t_GOJ3=DLOv z55j|(xy~gCYlazPt~0Y?ZfpGB|IKFe$P=6Y>6}-qwe{fp{&*(O%Z;-Bc$%A^@a8Eo zZ@zD}#(Z4&ihWZ1a+KUW?5lPAU2<z{jMT3Sk@|0rg4_Gb<xct#^>b$z_&qzW9q;bd zP8YYR|CzHAaI{JSckPkR<tjld*GiYXLg_knmUK(?NN|E%x;x_}Bp_6JwDgluZ<mIC ziqW3WL$p^z2km{ix%R34qRxY_wQt1(4J*5$;Y-hGM9wjd%(^d8h1C+BSR*mxwn=Q@ zZi$O3mbk`JiTAJ2_(wCO|MwytaMmRQA7*MoWws{P4A4Ovl63IS03DJWtVw14WoWXu zx^nzwSjbCtyBa0g`<kW%KbDktFJwfM^D?6Ds*HSgKt@#^k<{9Anzp%I(vR-b(fRo@ zrhL7Qow!NI<;~WNetGIiP0{hb={mvLODBAe(9HJ9l6kMKPWseSCZGDKQyP3^>fSbz zRsB|`m41-yiaME|-5@hoz0sM2Ps^;VTFnXCA+r;!G`Gb`ofD`!=hb$d+gPacu9oQh zM;={pXo}`tSu6`TCTf0VhAf&Jqy-ydW%1YqDa`eiC6S$Fsr#!eYhy`KczZ2+|5S=w zf7asqOH%UgzAiseDJ$w~bmfi<x~giot}Z#KrJGCD(bTJnc{$9Nce8)_vaELT=Dw`f zVm1Bs8PLVi!m@t<<hQA59?RwCo#8Qm;BfH8<8XNX;+lV$XIjGh;mB1ZmyKLEa^c98 zBbRP#t{u5}<m&kkxO`i4{YU{w1xN`<4M-746-XIK9Y`TaB}geqEl4p)HAp$OrXHjq zq#~pwq$Z>&q$;E=q%Nc|q%x#5q&B2Dq&lQLTT>rWpslG8DG{j=DH5p?DHEv^DHN#` zDHW*|DHf>~DHo|1DcIIjjFfC^YDS7isz%C2>P8AjDo093YDbDksz=I4>PHs9)~o=s z1h!@kkVQaN0a*rQ9gu}URsvZHWG#@zKvn};4rD!$1wmE>SrS{bCdi^7tAZ>GvM$KN zAS;6`4YD@K;vlPoEDy3i$O0iNge;M*StDeTY|Sbm%Y>{GvQWrMAxnj<75@K=<ztqt WZzNOZOp6YS4U2H5MMhwFw9ij!n9hj+ literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Sofia b/libs/pytz/zoneinfo/Europe/Sofia new file mode 100644 index 0000000000000000000000000000000000000000..dcfdd0822defde60d3949775489f4edfaf5cb2f7 GIT binary patch literal 2121 zcmb`{Z%kEn9LMqB6;Qk~_C-TfKrkTCuJYf1L1YAs1iQ+GNF}a`h(&~`3{vAxopX)3 zZ^UY@+_W64=7=`32h{qbYqrtlwsNhNi$%-U)Y{CsYOH?mLw9ak&-|U8`#PSp@yO5H zw{b^vp1JL~?Hg`RkG*-G8?Zm4m)6SXZzgE)wNe@DE0EBE3<>?|YX71mJvDCYuin|U zCF66dXNB%6Tr?i<xe}T?=cD$7n6Xg8wV}YgiIai(U-ktSoIMyweE)5ebozuze)R>j zu=jvT*|*E2wl<kXn`_P8RplnFXoX4lWSNYl`R1M&x5<nu3}p5Uk7r(Y`?G$$;$8gJ zW&e_MW8Qm*gMQDccf3pchy2S9z39!}-RI9~+T+b_7#z>**c-|(9+83#FG*qYaao?% zCCev!WMyK%uDrNKR{hnft3N1}`@XH$qJc&&9$BsTA6}y+uO#UM&1t&k&?<SbJW5NO z66K+cOIlVsDdkb0X+_%iQgQVssf_qoDud_c;fdE}?ciHdHTt1eAMKTOr%&o5ZEfQ1 zJ*ev+eO@=TZqu3-t?H|)(AtD`TIX4;bvJUfKE7P)zn-m+UC)w@XMWdB7u>S>M4U7X zUzW#vu1RBmQ2dP-WXt|v^of%9WNX8yHl-YsZN-Dy9R03tPgdRj^UK;Y*`Yhmck7cE zefrcJo3!=Qo$~aNVr?6!k!M;`wEb|Q>|9?Y9nA@{D|eynF8f3F#2M+#j@QoLevq!% z8`>2*C*4=S(PxJSWN&ap_Z|C0_YWS|=ekd6&(R)rO^dKsq$_Ibnm*%}EBwJRdgh(J zjEVow{_itMsV35xSTWH&yJEu6OLAOs;jdHAH{VoM`%GDx&y+fM4gat|#<*PE{7xO0 z%lF@7m}S3na{~6}dBNZI)SU^YW5?NvdB`}BfgmG6hJuX6(GCU~js07O;UMEd284_V z84@xkWKfQFRLHQ9aUlakM#f=i9LC0Ba2!U*VR#(I$6<gRM#y1^9LC6DkQ_$oXou-& z$B7IS87VSUWUR<wk<lW<MaGK^7#T4#WMs_9ppj7{!*;aeMh5O^M~(~~89Op~Wc0}J zk?|t|Kq7#I0Eqz-1SASb7?3z1fpD~uKtkbYV}S$%i3So5BpygWkcc25L1Kag1&In0 z79=i6U>t2^kkB~V*dW0{qJxA7i4PJWBtl4tkQgCBLZXC(35gRDC?rxys2pvqkYFLv zLc)c_3ket!F(hP2%#ffVQA5Ip#0?1?5;-Jvjy85k@EmRQknkb#Ljs6I5D6g?L;N?0 zkr9y*QPZYRT{EI@xxybDXS(n76)s|83q`bDv_*^+V~c3JDB)*Y!T9jkspp$-=wjvn iwDGn$+81sc(WQCB(^H+ltZ-RQcD|=NH^+G@aQ_2r6zRYK literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Stockholm b/libs/pytz/zoneinfo/Europe/Stockholm new file mode 100644 index 0000000000000000000000000000000000000000..f3e0c7f0f25f0a7290e56281c91190e3611498a7 GIT binary patch literal 1909 zcmciCYfQ~?9LMqhQ3q?ZZ%F8dB$uPBax1r^8ai$r<dR&HTS7<?G0n`HeQk|d3}elV zcn}`sGS@LQo1K~4Y|P!vhPkcrd;c3A@Yrep|EryP_<eu8(-##aT7P|<<{KV9Ys|y% zZ8w@%O+?k~8sGi*?LDKUL((@5j(VdV+dtG0zgrse;hc7QdR#l-*{@wL?a<IOXC>@t zorLe%ClOUDk>#7DYhkf;n>kOqXL%(mHC=kRQY1PoMtZjCBr#66#(e6py`DvDZ(m34 zbETE`t^cB~L$9=7^?i-4yrFTc&S-r8F-^$5CyB-Nl9bjU{U_~|<nX<cl2|G!O%*aQ zv|0x~nj?e0m+0WLZW;0*M^kI_G_7H<4&5?Bht-7X@Pa5EQ8`FPW;oTIA1b4wUue3! zNiv+*H8bk5WWIYYqx~+(=*DX@=IKEhTX#gVZk|`q_9_{7^ni{pDv}9Rn|0#UZ91uN zzGe?7RBu+MP7WETQ(V1u%IA2^3C@t5yX|z^r(QDs)JL7+3y_)ngCw{9t<0+UAbHh| zGCR*FbJoAsxx-G&yxg0bAGurRr`2ge>yx@5Ty??AUAnNTSQlL@)5VXxy5#T-Exfuy zmTpbcqS|a(wlGqcZ%LLF6H}$QAVgLsM98Z2ud+JGl9IS!EqVV$N&`P@>Fvu>_U@jp zJy9#`8XL5H_eEV_w^uim9ny;J73yf=@bmxwKb9qL%~e@}V)<KESXW2uUvIw2@^~$G zI#0Hj|8h9&m-pW{+tU1zhfk?__&w-{`FMT%s<C|X%DKo5+nPJ(pSfk^o{^hI?i#sm zTXWyYjU#uC+&Xga$ju{nkK8_T|40K!2S^J@4@eV87f2gOAGW3uq!XkSq!*+aq#L9i zq#vXqq$8vyq$i{)q${K?q%T|37}A-oX$|QOX%6WQX%FcSX%OiUX%XoWX%guYX%p!a zX%y+y*0hTBYHONBx<%SW`b8Q>I!0PXdPbT?x<=YY`bHW@I=3~gBfZ<2=8^7^_L2UP z4M27P*#cw_kWD~#0oev*ACQeeb^_T7WG`&ZW+1zPYzML*$c7+0f@}%0C&;ECyMk;B zvM<QSAUlI>4YD`3W^<6;u{GO+><_X*$POV}gzOQrN!Ywgel7f+|NrOrFhwv-fnqfe sQyY7p%$skRr)+zk{!CQ!Mwxej8LoZ_ESJlZ6q_6y@A4$XV_Z_ePe)m?eE<Le literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Tallinn b/libs/pytz/zoneinfo/Europe/Tallinn new file mode 100644 index 0000000000000000000000000000000000000000..3a744cc6f28f3a886952ab19c8ec93f0e66cefcb GIT binary patch literal 2178 zcmeIye@xVM9LMp`fk)RR=Z7{ri8~=lLFC8rQ)`ge3CuVQ<s^R*{Xo<rR3Hb1@m(?J z8nbuB#$4r(V)SFi{6lTce$P3|i|!xR8ns-lY^=HOR?gYR>ht{8^^bqFw*KsM`+gr^ z?(UEKgV!^<WmiY3y6$N64i{&@Ts$wf%<ahL!q|~_MUD=Q%5k-P&sfjy$mGe>7e6~c z+7!Fgpg(`pFH^6E^%pOc>v->li|M(q$8TBqb&cOM)8zkc!AA>ceesN$aLui;7vq`l z9kT-`p0|T9KW#4>?y<85+U@MljrMKZ>g>gl6?RTlo_%{khP@<^p_b0~s$6%4m3w8W zmG^_+S~l&m^3PqfmY<rn3dUxv!jYI&H1xhz+&f`~+K*Z#trt{j_qZynep{7qIH)Ru z&#B6k51J}V`*h{ppkA3dB30vUQhoMLx$D4cS@lvtYC3Xc_0S#q?mD-G+cWi=C6}Z& zJg4j2pGtksx4QoFk9uwL$9nD5q`v3-*L1_gTRIZ^P#O;n>vboN%e`G)x@q_sS%2TN zvY~UkG_UBCXrx{?`qxQI!7^#N8j?-vb$Zj6H_PTLd3wv4UuEmGS8qF(rdv<_tnVNE zMYoMi=?B`T^@ICo<)KyY=!aWl(w=o#Z?B$^4$o=X5fs_+)2Qs6>y}-U{qo2+QF-)@ zt<rgZuYT-cwRDX)>)kuEWY2*Ly?1?;?(XpGo{~knxAvNTJWc6+#p$xo{!aJ#u1a70 zobJE;l{|5BTt7K=Rt64#BKs#^k*E5{Wbn|SxaK7#yWA-^Bqt>&rFi~)-RS-6b<_Oo zUJFuvZeQwu^HR!RZvNf4&r>Rp?eZw)(<(J_4`iz}rSd~6J@M(v8dpZ*oy4;%$ftZW z@qOOO54nPo#;A&#D_q;mEvjnG)!5u%t~KV_ys4DS#V;^$oX5X!=I`jgSkCX}w48~> zkOeuK6(LJP)`TnySrxJ@WL?O@kd+}zL)OOcV{yppkmWg=^&tyHR){PSStGJYWR=J= zk#!;qMOKO|6<I5?SY)*vmdjzij%LBgijgHFYep80tQuK1vTkJI$jXtWb67iv#dBCa zhvjovzoRLDqp1K=0;C2=5s)e%WkBkH6auLPQVOINNHLIVAmwm0^*{>ZXexq~1gQy9 z6r?IhS&+IQg+VHVlm@8{QXHf@NO>GheUJh<nhGH$LTZE*38@lNCZtYCp^!=;r9x_j z6bq>qQZA%kNWmOU#gLLAHA9MqR1GN`Qa7Y<Nac{yA+<w_hg1(KA5uT0fR3huNC_QH v4Ur-uRYb~&)DbBpQc3*3l>bypuW4&xDN!wGEGw!g3Kf?XG?s*%r*iKf0hJHV literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Tirane b/libs/pytz/zoneinfo/Europe/Tirane new file mode 100644 index 0000000000000000000000000000000000000000..0b86017d243f1b7bbb41d6b4feefcb2b7edfc7d8 GIT binary patch literal 2084 zcmdVaZ%kEn9LMoPdKbGQeZlZ%fLMs2U4hF#MM1M0pb46CRs4e-6A=^XK*Yd5v6fkD z%zYo+S+1PA98;&{8vSwTtfmepGS|kMI?L6{XpQbx&YZIRz0V^z*Mpw4b<WOxo!#Bt zN59WIv}#jbj`h&xG2ifTy=5NW$L=|rSKqhgZKwa{Lb-Irr<cAM(&&uBNc8V>Y_F#+ z;=SB-W6aQEC#Gk<J@%W;k=XDw`>_-E9BGNM<McZxzH-<e=X~irKKrDdF#n`8e%vRv zFEAMK-5a<u!3sMQuGQF2_J15nJat-<j&)1&hx=t>r%KB9H)K*nvpltCy*ynWlGLIS znd~o+w4`*I67z(ldxDyND^D|iO4F%><8|7(NA;PWztn$dNT-LdYUa+1n$>bjvulrQ zp!$mBH1|kuaj)bp-6Q$Q`=lVJO$tUjWM*PmXI@_?g?C$Z*6E<kzE-70T{T+VJ4@&6 znx}I=NYZEPQgz<WLV2##qruulc|Pr?mIOzn)N?`0Qoon7;h$x}sIO$fK%czu<43Zv z`>2%npV5llowDfIL0#O~C@*%tsY_P8t4kX;XyuFs4V9PaGT$Oy?w_j5Z)a;&La9`J z8?P&GWyq?}{?yfjURiS>PO5u;leHaxNKJS^?3zJYx8qlRY3}E;zPev)Q})V+;%=>r z{!}+6t8V<|J*^*U)=how`ttRVZa%zP8_qY$mUoJ^v8z&EsZUX7SH3hYDU#+opS&8F zC@m#-<h3|UTC)<gHS&YB#opGo%V(v1_=;{l(IwjkdUgBWuXRWFK7GCYkaq0u(5OfL z=^i!uKf5g}{(VkrtXQKhD``?x^n>r^6(K8F!c!UIS5Z;!N9bRi{J+h`=|>iTtN>Yp zt62ko&mvsSDv)Kknsp!xK~{n+1z8KS7-Tiba**{P3qn?eED2c?vM5)xDr8x%W?jg_ zkd+}zL)L~Y4p|+tJY;>y0+AIWOGMU)ED~9zt63(pPGq6TN|B`^Yeg1|tQJ`=vR-7t z$cm9ABWp$$jjY<$EE`$3t64a*a%Abq+L6U0t4EfPtRE==QURm{NDYu8AXPxhfYia& z6auM)t0@Ii3#1rGHIQ;3^*{=OR0JsrQWK;oNL7%sAa!vyg+VIgYD$CD1}P3w9i%)+ zeUJhn6+%jc)CegOQYEBJNS%;EA(e79r9x_j6bq>qQZA%kNWqYbAtgg<h7=8{8d5f- zZb;#f%DI}-A+>Wg#Y3uxln<#NQb44FND1-(T|=)a<n#cE^jG9&=4WR6D+1Y=mFv9^ D;y&Xi literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Tiraspol b/libs/pytz/zoneinfo/Europe/Tiraspol new file mode 100644 index 0000000000000000000000000000000000000000..5bc1bfeba62d14ffa8995c1d08ba7500956c494a GIT binary patch literal 2436 zcmeIzeN0t#9LMqRjYkpbx<)*!VFD`hD2gRYUcnTuUIdX$@RWooXrW@5fWJA%Xd0SW zvtq?sQ`wZ^Vw8eLWy*?x8e28A&ekk@n4VS6mbv=94`bE(tG52@b9e3=?wE7V?~8&p z8_QDUZ*%HTcvzEu_&P@Ex0fqk34EjDWB=0&el$*BZ!yk%@r=<uSa0<7wV7w%e9=62 ze4qK&ky^92akKe$O^NwV`3m#hqD=GrgURN5>CxtexVy}Yq26X+PqXp<lXaf{uXCIq zwuKxVc-7?`JT%)mwEUnE9D8i&?$Aq_A^sztkYDcX4gEG~C~U-)8Q$MK6w%XaMV@%p zifY+oP1?WBnp|6IO{rXE-Lp2^iq4&DO`RWS-5VcfO^Xb$V#eRHVqCdqtWTPXy%D40 zu7s%R##Lkb@b|`y^Mjt5odZVvYo|Q34tE#{dz(Fp+YcE@rPZF~;&wA->#m{HjFU2Z zWuv4;9gsOGn`F-IDoKxMQ0bTRW!|lFb>G=ExqoDtntwD;Wpu}@1r4cc;Y&d(vn*IW zuy>{`%DSmsr4wZFw9izQ>$+t7bgLy(KawTaK9!|juglVbGqUW<=Om}SMRNO3DR)!7 zEI-k#R%|iEv%gy9t*%uoD_5)hxh1L~cb-}mlBo*gBUItdXjK%PE=8YstA}oc%bKpQ z)WgHS%Gy>hDenASN_Jn7b%%S!Sa(q#sk@{eUDzh;i_fXj$@}E7j8;|Ve@tzN+M_mH zeOi^@E>#=*D%7US*{b5WTUB0IFPj?^)s~}K@_6|~^+ZFgROKbe*0OQ3E!ipCv%ZsR z=MS<Y(NFELhNULx8&xyZB|EQupq}bDBD)4os@fMiRbBf5RloCjwY#ZGIqvZB^8V|~ zcbuQUpa1xP(O2MvKVH82Cw+YX!<R^ezy6Ob;XjGDNNA1d?`9-90!1RH$i%Vt(NBV$ z63i!#u(9{_OvfaCUZTg|$37YR6LQ@J;?mC|{bXh3^QJ$rAN`Fxf3bdY_zO53qmTUs zAN+X*|KKiv(<|)i`<Zf|lpCeosa<`mlzXMzEah$~w@bNS$_-QQm~zXMd#2np<*q5W zO}TH%jZ^NNa_f|Pr`$Z{?kTrVxqtp0Fo5C!#R9w91BwY07brGRe4rRXae`t6#S4lV z6gMb#Q2d}6LUDv*iCygp#T2{R6^bnsU+ijQD9%u<p?E_vhvE*!9*RE{gD4JBEMj=X zFp1%kU2PM?C%f7xhEo))7+z7#qPRt|i{cl>Fp6Ug%P5{POryBQu#MuIU2Po0IlJ0A zhIb6}817N*WB5lgkl`T3LW+kJ6DclIY^3;TR~t!j(yq3W;w8gOikl2O8GbShWjM;P zl;J7GREDb*TN%DmjHNhhS6fT*mSQf&U5dRFe;Ec-9A;Qd@t9#U!)1oe44)ZBGn}@o zt!8*_SDVdnn_@S^Z;Ig*$0?T6|2_YAo(JgP0<%*1eGu<XO-M^figza`(Ztk%-vMds BjH>_u literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Ulyanovsk b/libs/pytz/zoneinfo/Europe/Ulyanovsk new file mode 100644 index 0000000000000000000000000000000000000000..7b61bdc522b5b7f4397fdb9246185f4d972f4b6c GIT binary patch literal 1267 zcmdVZPe{{I0KoD0%gwE=LkDY1H?wT6O;^9>CbOokrVeX@#PDDR(jOrRdV~jqz(MAz zf+C10qG)vK@1agvj_4Apr*0PfAUaeK?-GGh>wSOIQ<pmSetW;qkH=%M-}|mGd}1^% z{upcY3X_r5ljpiSqO<s{<Q-fWzFuDMpErH(xr}eoc;f#e${QAvX8r8>a>Mi$EAZf) z6}&lSHC{MwZ9F?<HJ!{^n+~O{&H0GcoUXUFw0W&iz@u8cODgOP%kX!<-1^B^jEv6| zBkvYv^z~QS`t-eQE6vH+)t7SH<YT#g?6x$IU6wlzPpkOpYpT8LmP#Brqmsd*>WCjy z9Y2q#RKvJTedtl0OT%*Kix#zOAuZGQva;)WqwJmv$=x@E%#6C^p2>jP+xuB&kN#3U zp|`R(YpFixLz!!SrE=fisQ#Lg>Yu-__I;dE`yX9Y`PsX2;L<5o$OlJ;e$>f{N~L1d ztg2oP=kitSs&%<n>)YR44wu6rL~KOARuMIYe(oDI+(M)>yz1(GWyR1d)jd(u&^rT7 zVl8`EXJ>w(AX?3KJ(GGS^wbAx=+E-td1Vy-;kfm$tZ?MWvGW}qJ#zd=0=7B>Bn2b~ zBnc!7Bn>1FBoQPNBo!nVBpD<dBpoClBq1atTb&Y;6Oxpz&I(Bj$qPvg$qY#i$qh*k z$qq>m$qz{o$q-4=R_BN$X{)nD(nRt^5=AmaQblq_l0~vb(naz`5=JscQbuw{lD5@Z kBWc^}yphC_%#qZQ+>zvw>~TW3@SmpdN$WpHcP!!g4SK2@ng9R* literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Uzhgorod b/libs/pytz/zoneinfo/Europe/Uzhgorod new file mode 100644 index 0000000000000000000000000000000000000000..677f0887b0c6c965fba6fb3c8a4d47c9f6f51695 GIT binary patch literal 2094 zcmeIyZ%kEn9LMo<QWS4RUs`k_E`JKiKV9MSPthRJ>xC&?yUNu_C9Z||C!zpjU>dHR zYmMBuWI0zlEJxXl(MI(ETYu&nZFIeDx#q~#YNd0Iovoa!#_IPzW?Sn~4|>))JLh%w zdvc%L&pWzddrPtPkLzZ0!o#)8JbY*GHLtN*?K*y;Rz5%B)%a^cIrCDfB=(+&C5|2a z_KP2;oU}P_ByLaty2_h85%K<d%SY*#QW6;x!KypP4<<5C4LH7&FFIMTKI3Hfbvik_ zo1NU&b<Ules-3&)7CCtpbDi1#3@6{`aq^FLM(%#0z?pMp`M|s{FURJeJ`=fTBp&l0 ze<!k_e>hgK?_i{G=P3;|zpX`$=cKq}L`o`0rF8AfQkHc@78ZBQ!l_;<&+OOo^P6Sy zZ(X|NgP`0y(V!KBO<Fm+MDN?bOqagm)2fy{UAAwrEU!+|U~{Id$iJYW;FMG+eWo>e z-$>2mkFs*w$Fed$Cab=GU22EklDhE^HGH5?R-Zhk_qVl4r0-c>^T6}EwsnivFKX3j zU5&2uuGaPbdAj~;KpWDkrQxev^}#E1Wy5<v>&CMl*))(UjU$)kq28aQsXs2Urn9nn z&n10$>HG3X<G42G9F{GWL)w!3u5Qgz-FoqmZky`R?PEQ<<9t*feRHF>e%daNJy)r1 zgZ1+Gwj6z8f0?wesgRBquXGk=%g)d*@?@$dU4?1d<$NdIDOa^SaawvN&*`q=L3t`Z zs=E(=qI-sp=+iyNwf8`;+BZxy_jG$k(zSbL@?ZB1dzQz&Z@lThxGn3pi{?*erHD1% zvV6IA^54J9t8DX6oFA}KufMOI_;Rf@PV9{9Z%*pMQ7ahYsgGLW`W5D>wL<0~#DQhm z_Mb2JgWu({?dZSQ#P8<XF*%%$Yy;T`vJqq_$X1ZOxSGu%yWyAF4zeF)L#}2=$d-^j zA)7*Wg=`Di7qT&AXUNu&y&;=Jc86>a*`KS~AhJVbi^v|4O(MHQwu$T$*(kD8WUI(t zk<B8zMYfCV7um3@*)g(ZSF>ki)5xxoZ6o_eHjeBZ**da!Wb?@Gk?kY<M;d^10BM1% z=>gINSJMTg4M-o5Mj)L)T7mQeX$H~_q#Z~<kcJ=~L0aN!dV(~?)pP}E3(^;)F-T{S z)*!t>nuBx)X%EsLq(MlBkQO05LYm}ix`ea|=@Zf@q*F+%kX|9pLb`>t3+WfqFr;Hh z%aEQSO>;F}L)zwQ`i3+P=^WBJq<2X3knZ9C?Xzu<>EnW80_hKz6qFSN3QPRqqJaBV G>iGj{t@?!k literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Vaduz b/libs/pytz/zoneinfo/Europe/Vaduz new file mode 100644 index 0000000000000000000000000000000000000000..ad6cf59281a1046d9dcd045fda521585e3e33e06 GIT binary patch literal 1909 zcmciCX-L$09LMqhq+=x|UkjT`&1!PZnmp6((5_jPcE=8#Ej!E(waeVJGVQV`Btqi5 zAVpMc%Z5ah^}y<Z9c&jJCJUQH7eUcw5kZ9=Nd4abC5WxZ{r}9ohV0?@{qfIST%2Tm z^*GJH@Zni)KK$;!(R^KTEwQfLFSD+;`>f`(xmK9_nfB^=M_mEe)b;AL_I_|g`~164 z`=0w<!%v=)h(iq$x#th*SE~}WZj<ycDVG7W7sx=LU)*UKGRTuE(GfB7L$}@%<Me9G zo8db6VYJ4!_R=92I_uEJx9ZvdREO2w(zq>GHGbtuO(;C9iTO7rsk~8=)0<>?&JIb5 z+$*U`m6F;~EhEC~bj00xGV()(jymO)(YNz7t-e6hn?~uFn(;bzcZ7~BcI)^pBV|IS zQ@w@Z@>BF<&G2?ert`99x$jBVi$^js;BT4Oa!G!E@R$73a8P{BXEb|ztxP)fr%o;{ zl_|BGb?WqOnp0Awxj&Yu-<PGox+du~PpnRBPtd%uOv$^^Lub4hEHjV4)>*B=GJ9XB z<TpN-In}SEpsq#c7PQK|^=&$T><L+r->ijEyQC<+L5sT_(}j_$3!m)NMIGh3_)?WF zx$D=Z2WDx>#WGp8HC;>VbLF>1QM$Y)Marh8NqMnLRwVY5l^O43Rj4Hu@nKr=^1f7t zv}@%*=cVe!O<i-eUe>lW>AGEKb$!EL-B7h(tG8EcCx>|h0>AfbSzXLgSyn`UN1$be zh}HGW-@a_W<;}?D%g_IEIP5R~w{JGc{E-h&rTOqX^rLwOy=>cvW!HmhkQ=r&cZ}RJ za?d>6G;-I-ZQGjrMs6IrbL7^Mdq-{_xqIaHk^4s)KsrELKzcx$K)OKMK>DyXjUb&M ztsuQ1%^=+%?I8Ui4Iv#NEg?N2O(9(&Z6STxn#PdMY)xxOZ%A`UcSw6ke@KH!he(S^ zk4Te9mq?pPpGc!fr?#e5q*q(hEYdB~F48a3Fw!y7GSV~BG}1NFHqtlJIMTVTX&vd^ z)-;cFkF<~Uk8A+41IQL2dw^^LvJ1#IAp3x91hNyzRv>#}Yc>Pf4P-lz{XjMZ*%4$* zkUc>*1=$s3TabN0HU`-lWNVPUu{E26?2fJ39%O%z4MKJZ*&<|*kWE5%$q~@Wyn)W| z{eB*%p!b#;CNocFr$WT){^f7xX~O>|>c5RL-@#_Hh9$CIp6ukfl(+;>c47j?CkKB5 Dj=i{v literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Vatican b/libs/pytz/zoneinfo/Europe/Vatican new file mode 100644 index 0000000000000000000000000000000000000000..78a131b9fff014c3ea895a9cc7bf4197462b6602 GIT binary patch literal 2683 zcmciDdrVe!9LMqJ!Xt=EUo^!8#3EA@5kwSBG(}`4Sjv-{SG=JX(UFj}RJ`TNG50M^ z$JE42u8UAB%r(?jsk4b_7?zcjWoAWlXEUb+XutPij9LBdIXvg}a2S97-XH(8#W{mq z|G5Iq8$MhI%!lutR-4E6q+8bZ+N!!8$4}Wi54p16e*Lz!t2CmnH2WQU_o}k&Ju`+{ zdoy}upUh3PtFDfh)9;08_1c2E>OHA)=FP!!=JJWUvw5@hoBVE4lTl-z8xgPHj;oQ{ z$eY&re%tKx^{e&!_FJVP;h^1c;aFW`M2*$>S%uxyRADujY_)G+Icwip`-$Z`{;}<z z`<CU|G0pOhe#r`Wwb1tU-)y(b+iV4PSZW8Q4YPylr`W-<Q>@@q$#%z(WUJ!`;dZBA z>g<q>&Gvn#n=L8mVTG1mvN~rqS)Hr5*<CWn$o&a_YuD&{?bhWd4fCGXu<J*)`{fVy zfpewW<Eu9`{O}QpI8ZK;J9bIWVwI>BYou3hzC1W%o;;M{m+0YX@^H)$=^fEW9_iR! z`uI|{&yB&__eyW=SKCb=Jszn2D{g7bzTb7gmLD{B{V5$-a9HE!mudXW3o<CbLK0Fc zW$>h}k{G#Dh74LJLz;^uDSV42U7RDyjfFbwNUA*c<5V5Kf3~Jn4$~1EM(N1y5&C#e zw2oSzEThNzG<ANsjOl$<(^8vdtnX`0kG>@7*M608-Y;Za^+|c+@?IHV{=Q^XeXg0C zie<uq-TGu+o;+2&PA5*;sFQLRXx7kN^=G8((_s^Ia!fy+d^1j`b{#8Izw4~e+~_OQ zKK)Cl*9Oas(omUM@u$oxx-PS~R7>{kTA8!*51l*mkj$G|rSqdoWI;-~<^&zog^{WY zf7_;un)7w>$>sX&MZZ4x?sUyPu|%GKEk*P8XUPkTqSV@uC`%>|m;9VCc`?4H6r?rC z(omNa4(zIh_Rq2`<fbk=drX#JyPz+Bv|nDSuGAGJU+T*8ow{oIJ}uf*q#k$M?G14A z<Msr2ox9bR-|l*PeC_W2?Q(UfFvrUk(aRm+a)pSiee1L3P22m7Ous8NEsKYr|8ScN z%@_awALbzQM?PF-=EL{UJLXC`+OrC+!)q+$a66g<jvR92pd*JJIq=A#M-D!6_>ll0 z5kNwK!~h9`qlp3%21gSIBoIg>kWe78K!Slp0||$ti3bu8BqEL`BuGpgO;C`iAYnl# zE_@*{TqZI=Xpq<-!9k*fga?Ta5+Ec(jwVD%j2umnkSIBtFd=b50)<2h2^A76Bv_6n zT1dE%cp(8pB8G&_(Zmc18WJ@mY)IUYz#)-CLWjf-2_6zXBz#ExkN_ePL_&zf(9r}D zi6Rn4B#uZRkw_w;L}G~q6Nx4gP9&a4K#_<dAw^>9Xo8AF)zO3%i7OIVB(g|ok=P=^ zMWTy@7l|(tU?jpwh>;j0K}MqNXu^!d+0g_Vi8K;wB-Ti<k!T~~M&gYG9Ems*awO(R z(2=M+ny@2rcQk=VB9DX~i9Hg0B>G7Bk@zD6fQ$e#1jra5gMf?zG7QK#IGTY#MgkcM zWGs-uKt=-@4rDx#0YOFt84_enkU>F41sN7(TpZ27AS2^wh6WiMWN?ttL52qzA7p@# z5psJyZFhjr!;i=73vljMTYkIi>1`Ky@9)+XGFxO;$ZU?8iV$&iYJIl6X?xEWP5Rd! fwGEQ_7HmdpKs<95lbH~k7#kOp86SrO6N3K%dd(YT literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Vienna b/libs/pytz/zoneinfo/Europe/Vienna new file mode 100644 index 0000000000000000000000000000000000000000..9e2d0c94860438443e8d8307f2d5be74f6eea2af GIT binary patch literal 2228 zcmdtie@xVM9LMo5zcQHF+YC<mAr=y1C%-{4$m~2A%2~*zBoY{inuKr}gVL@a%(X`D ztz$Zeq#|S0?CKA!HT*HN8Z9K$E<<x|x%^ShG0j<PEI-fhAN|w%qksCN&+WdC&u+Uv zZoAjhyK;Sfmi4zY!F<A-^QL)ozw2f5=$h!L=?b13+cO%_?%qTC*2k)Q+jr^y>&KdU znquX^qDmc%Y}TR6OXTD`4jt}s$yYmaWaQOkIpvFyQ+~IcKA$sorZ!H$E;%j1{1fs` zYQLOy#>%&mJ7tO|W$fPUk(%hFpEYL6cj^c`sg4^*b?U|Ub=sMI8hc{9#vMK)@dpEv zuxGDK?@&qH{EEzI@X39vYUTb?uOt<?Wu`MvlH*h4fr(QkCBmgCH*<8>kI9-EjM3Sj zP0$DXe^Y1gRh`p$PSe`I)bve<HKXo5&8!-gEMLE57Y<0yvYnEf&@Fjct&%tHmw9oW zI`6`2$^WB8=O1y&L+2~Bps!jB2j=U;TNmnru6TW<K1mn0=gXr-5$dXolSRpw)$JOW zqKGfFIO%&SzII7U!j4PH@SrTdctDl}-j&j!W9r$~AxjVL*T)(g<?)W!v~0y2x~yT1 zmd|ZaZ)veU;aIB6ovFI~R)$tY7fHq0d-Tbhvt;FmziMSLN>=TgELHu#$W#6sQr$T$ zHPt~`z2zr;dcj9hTQ#J0iMwS@VL<EeJfv$ARM%eFrR&Chx_+=tpSj@G4R2R!!{^QN z?CXWv*jFwa*ClGx)?8^WD-d73L!Qf=E}PuH%kz^hX-SXPma!kCb<!<u9r;w+u8ryo zAN0wK!vnf`_b0j~(5)}E^{RiHUqeGe!@|SD?FW~?t|6fjcl_5c%euSF{D-ah86n}8 zHA$?<f4Y}?yq3#d&cn+$Ld-#P@&1d&{Atd{p6YaDIksj!$byg+AxlEmge(eK6|yX3 zUC6?al_5(*)`lz&S)Hv}9<n}UfyfGxB_eA?7KyBqU(Pa-bs`HzR*Edu)~pp-EV5c; zxyX8v1tTj)mW-?!Sv0b0WZB5Nk%c2GN0yGP-PSA~Sv|6RWc^41kP09rKx%*#0jUB~ z2BZ#1A&^QSr9f(7Yl?wX!`74osRvRJq#{U3keVPxL8^k31*r>C7^E^tX^`3=#X+iL zYs!Pv$JP`GsSr{kq((@QkSZZ%Lh6JR3aJ!QDx_9Ov5;!nnsOoavNZ)mDu$E{sToo< zq-sdnkh&p-Ln?=q4yhedJfwO^`H=eAngSvfL`sO%5Gf*3MWl>K9g#vJl|)L3)DkHs zQca|sNIh*$L6M5unvx<lMT&}46)7uHSER7`|Ez433GbXt672M3r{$()I6aven4TSV E8yebJwEzGB literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Vilnius b/libs/pytz/zoneinfo/Europe/Vilnius new file mode 100644 index 0000000000000000000000000000000000000000..46ce484fb415aed15a6484e34a757c1b30a60eec GIT binary patch literal 2190 zcmeIyZ%kEn9LMqB4NxM&FBo135QzjNgv&p*O%&tb6i}2a{w1jpv4|ASfoYtTIoFu` zR&3?c<-bumVm>f?kS%MjQQh!9VXaxK)yl@2JFT3vjn(gcfLis)t%v>24zIIwf$@OP zJGgOsTao$KNw9CYIeYERbCzSj99@;(aN@ZYublij;GSyhZW$iDyyKG%x#7^&TKViv zzmA-&kjpQYNH|dTO=9-z;aN#vmiwcxH28m;aWQEuE}VR&qWtdAOW~9Y$KBMy7u>X? z&$zSucDd>MTHQGvYu${^)$ToY3*F4cIqtoA$?n|LWRn#eW3r=4o$TvbPR{p!XWnSE zlY8l=bKki!C-3wXXMTUk$v^O(Q?Pr;3ADcE6gGcqin<0&aoJm@WX&N{a^_-7Y1%Pa zP}D68#(QN^O1~~inAx)A;4)qMYO0pEW$Lm6_sav-QCiWOB9(KmYE{L!R7ZWRHJM*a z&9xt7dBg{@d}LTwe0NG}ht5b{=zXm}+$SpsPw1-7PHE_SR#&fkUe|PN)y9P#8mz0) zwf>dbls8YCZUl6FVzsRQJV7^H&ykJif7MN+F|zr1yfmNtNgnL|MYi;hNXwQ{d1(Kb zKD_iDd89d{t?5T(YuS*tMZc}v(p0zo__DT-cj@-w9)0wypg#7-ChhoWr#ybBOgje} z<%#xm-Epu~cCKD5U2T5ZRXAIASKX8+<BjYoNYp*<x6&PVL%YM5r03cfy7%mWJT>yJ z?mP0K?jJg)PxqYG-ow4>yDcIzA}V^?^l8(hZlAbf?wGt{@BHT#88;(NBID)1xQy{% zu>WLc=2;VI%&c}3ZA_e)SYuM>7%?U{VD9?sjW3D!d|g!TOPl(1>e-hu^-~ky^Y`E5 z%c!dlnsxTBwRc5T&{WyGzOmLmH}X(vU)YC<<MSEgn>>@R{`|z}3*N$pez&LTe5exH z60#>`Q^>B6Z6W(YHiqmB*&4DpWOMvk><-x;vOiC|L1c%>7Lh$7n?!brY!lfhvQcEG z$X1cPBAZ2ai)@#}emQK|)9x7AGO}l6)5xxoZ6o_eHjeBZ**da!4x8t&dk)+8wEIUI zfONpqwgBk?(gaW21*8o~ACN{Moj_WF^a5!H(hZ~?p0*!ILp*Iqkd`1lL7IYe1!)V? z7o;&rXOPw)y+N9TbO&jVr|l2YAWz#Nq(w-NkR~BrLfVA%327A4DWp|MuaIUT-9p-h z^b2X2r|lThGNfln(~zzqZA1EoG!E$;(mJGfNb`{HA?-u@hcwXBb`WWyr|luqM5K#I p8<9RDjYK+$|F`niTNz_BJHLpu=G7PHm*xiwiu39V1Kv|f%pWP&2jTz# literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Volgograd b/libs/pytz/zoneinfo/Europe/Volgograd new file mode 100644 index 0000000000000000000000000000000000000000..8f170dd97adfbb9a93d3ce6b727cec5d40e64e13 GIT binary patch literal 1183 zcmd7QJ!lhQ9LMqJ?8Vg7!9|<eq`uTNrZ0M?Nr{@))U*lJ6p93gR-yP(5Ck8jgMv`O z+DQdP5K%-ixcEBgWR)N;V%w=(D;zla%75u%(fE9?gP?=BdB@%72uUFMeT##~M&jy^ zV}*Hzi=*3IJg>U5keh#2su@@vy<T1uoN;Qar!%$RtS7;bs$4ghbk<ItFV|0AaYGNz zx#63mZo`G6?z%Jk-Sx-w?uG*?cVi*uHl{brrq()%gaXo3vnWwtR7V$rIyU=xxcT)b z-STu^x0a@L+trtP%lKoxb@aA&j$GE;4o*t^<TYvUyd{Z!rzIIaD;@D6>G*y~QuU{F z>Rpd)UmVmsUNp(hxwKB-%j(YO4Z3S0qIca;Iy2(e-QywI-TOgj5C4#!$Xnf;btUI} zsPm1lB>(k|^i`FlZ|1)2c|Re0A6=Bf)Lq?w>4X#u;o+g#FEUoCRIFuHOQ-Gg&gFhi zdx^i@ZNF_R6)W+tHap5Zr!uNqsoa3I^7m)Uy#DJwh5n*yaoWw5FjtZ<P!@;x#I5)b zLz>@FVN7IDKF_Ggu*kTcW?*DwWN2h;WN>73WO!tJBmg7=Bm^V|BnTu5Bn%`DBoI#% z2@(nt3la<x4H6C#4-ya(5fTy-6A}~>6%rN_m!}B~iOkc4hQx*hheU^jhs1{jh(w5l zh{T8liA0HniNuKnibU#ZLPcWrG{GX#BH<$OA^{^2BO&9M+5W$cIgmD?ceNz~KLI3! B7OnsQ literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Warsaw b/libs/pytz/zoneinfo/Europe/Warsaw new file mode 100644 index 0000000000000000000000000000000000000000..d6bb1561db672f94bfd5bccc95fe1a8d18a158d6 GIT binary patch literal 2696 zcmeIzdrXye9LMn=97!a#FWyiDFA=G9R1if)$c&E81oLvd;RQ5AEi$}BXcEtoxn|CN zbBOa+P{hzFxplN9I%l`hGQtZQHf!Zdab%d9wZ`oCeq3wK*47{W*YDYRUe7r@|32@J zKXX~`Fmu<r*Z#tXQ*A#yM>_Vly*jR8XUB-_osH*PcQw`M?#hGu+Iy<6mu%DW9fwTC z;-jXjXkB()!B=wP(j@t8PlVRLktUyS87>XZp6rH_!{+4HE%~Q5)@GkxbUXjdq!?{n zuwTv&3dlKcq<qn#Oqzm^Yg2QfT=t(bm#+n!=5O9|uAD4$TDp~)mc#FuANE$7t2?%u zAJ1f(*0s-@Yk?H=Q|26Vy|j<HkvzuSEJ}8Mj*K>);@a{%RnBlaztYj%S2EI()dQXI zoL){Bf0)xXBgu42Y;n5BTyT1Ht#=|k$DD}k2b`W4E1X`Zw>Xg>tao}JdD}$oD>u=* zUNwC-y=3~XTV?v?<(U5SW|;ox&$iy5?w6PppFH4AlGvyL@?giFG9V;P2izR41HX&a zL5)2$?xXhlP~aE!RyOP4((^i`<Wn8G`iREo?AL_(O)_j{KoV1HW%#r*84<l(l7<yZ zQd_Z%>Rqa%E-aMMzZGcm(KH$J<!nu<%F@)@WPNzUI32q)N*~FM(QzfC<<apWnwHaB z9*e!CzO*(OAM%M#i1}J3T>V}qdXCG)`Z{_1;+rz5X0N25IHnn!H_7CE75c>T<uYZ{ zdYw9JqfX0PtkXy4sXu*!&WM<-Grfa!=B;?0-F>{wKG#L(+#D#Ghi>TH#xR*z9xn3( zEwZ5ax@48sOLkVHEG)XBi^jeyPtHG~IeoXw;?x?=4Lzt!qE(k%-lj|2R_e04HTu*A zzdl_(SMxqzA<w*=s>`dU%d<=SYW{{1vSMnAtjvv&RSA7$weMGXF5F1L(C%8$`mGdp zzNLi?AIh4mO}h3#mAp`2tLwJEuSGSx^~E)nTD-YfgFL~Wb|LLT?`iJ|4zUlxcfRv@ z*To<I=JIq1`|mGfx*o8v68Cn-MD+^_HKwzePJexliw_Ft7t`a<`yc;I&+waB_LJtD z&dqOpJoxMbC&(UqbD!^g_y3Ex{I)$a4>e3d-ge}TceQUl^5!FNKT-gs0!Rsv8X!eL zs(_RMsRL37q!LIeTx~6oVj$H(%7N4aDF{*#q$Eg9kfONSsvu=?wRJ%XgH#47jjOE< zQXHf@NO_R@AO%7ygp>%W5mF?iN*v1MYU_j)ibJK4QX#cMiiK2*L%EQ8Aq7J!=4wlZ z)C?&aQZ=M(9O}lQa2zVfp>!N-$Dw$xwt7hUkoqA7L@J1s5UHW7Eh17yq>M-%kwPMs zL`sR&5-BE9O{APiJ&}SU6-7#l)YR1$6{)JLEh|!2q_9Y3k<ucyMT(157b!1NU!=fD zg^>~?HAaezRN2**8L6|YEi_VTq|`{Qkzym&M#_!U8!0$aairu(&5@!bRd=;zN9yis z3y)MDDLqnqr1(hnk@6$;M-~8C0b~h~H9!^tSp{Snkacji3xTWzvJ}W#Ad7*l2C^K; zdLRpetO&9s$eJLFf~*R%EXcaJ+J!+@#?>wjvNp)#AghBc53)YU0^$EF^iL}kW|wMk W0-NQ{NE|X^NW3>AAs&Y&hW!qIlp4DL literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Zagreb b/libs/pytz/zoneinfo/Europe/Zagreb new file mode 100644 index 0000000000000000000000000000000000000000..32a572233d4850a4bb4b1278cc2d25480ffc6a80 GIT binary patch literal 1948 zcmdVaYfQ~?9LMqhiLi!!F^Wn^ayhz}P{?u0rE=*Ym*kS%l7v=nxim9t_GOJ3=DLOv z55j|(xy~gCYlazPt~0Y?ZfpGB|IKFe$P=6Y>6}-qwe{fp{&*(O%Z;-Bc$%A^@a8Eo zZ@zD}#(Z4&ihWZ1a+KUW?5lPAU2<z{jMT3Sk@|0rg4_Gb<xct#^>b$z_&qzW9q;bd zP8YYR|CzHAaI{JSckPkR<tjld*GiYXLg_knmUK(?NN|E%x;x_}Bp_6JwDgluZ<mIC ziqW3WL$p^z2km{ix%R34qRxY_wQt1(4J*5$;Y-hGM9wjd%(^d8h1C+BSR*mxwn=Q@ zZi$O3mbk`JiTAJ2_(wCO|MwytaMmRQA7*MoWws{P4A4Ovl63IS03DJWtVw14WoWXu zx^nzwSjbCtyBa0g`<kW%KbDktFJwfM^D?6Ds*HSgKt@#^k<{9Anzp%I(vR-b(fRo@ zrhL7Qow!NI<;~WNetGIiP0{hb={mvLODBAe(9HJ9l6kMKPWseSCZGDKQyP3^>fSbz zRsB|`m41-yiaME|-5@hoz0sM2Ps^;VTFnXCA+r;!G`Gb`ofD`!=hb$d+gPacu9oQh zM;={pXo}`tSu6`TCTf0VhAf&Jqy-ydW%1YqDa`eiC6S$Fsr#!eYhy`KczZ2+|5S=w zf7asqOH%UgzAiseDJ$w~bmfi<x~giot}Z#KrJGCD(bTJnc{$9Nce8)_vaELT=Dw`f zVm1Bs8PLVi!m@t<<hQA59?RwCo#8Qm;BfH8<8XNX;+lV$XIjGh;mB1ZmyKLEa^c98 zBbRP#t{u5}<m&kkxO`i4{YU{w1xN`<4M-746-XIK9Y`TaB}geqEl4p)HAp$OrXHjq zq#~pwq$Z>&q$;E=q%Nc|q%x#5q&B2Dq&lQLTT>rWpslG8DG{j=DH5p?DHEv^DHN#` zDHW*|DHf>~DHo|1DcIIjjFfC^YDS7isz%C2>P8AjDo093YDbDksz=I4>PHs9)~o=s z1h!@kkVQaN0a*rQ9gu}URsvZHWG#@zKvn};4rD!$1wmE>SrS{bCdi^7tAZ>GvM$KN zAS;6`4YD@K;vlPoEDy3i$O0iNge;M*StDeTY|Sbm%Y>{GvQWrMAxnj<75@K=<ztqt WZzNOZOp6YS4U2H5MMhwFw9ij!n9hj+ literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Zaporozhye b/libs/pytz/zoneinfo/Europe/Zaporozhye new file mode 100644 index 0000000000000000000000000000000000000000..e42edfc8506b9b99362b36d90c8b8c4db67d50d8 GIT binary patch literal 2106 zcmeIye@xVM9LMp`2^5`$Z{6@9Ku9!D5spI?4H7#KW`Lb?5~;*lh+0I5#=taMIp-R4 z?<T9ca<gPq%$RLxf1vA^Yu4zxKeFb?wN;De%zd|VR*u!@`E|6m{_2nZ>-*jJ`}p!@ z?6w=PXJq4!)`0oPaff}w#j(d;JkNX9-iFeT`%ev|M?W2!h>uOw$Y*c)H1>K>VrReX zNX>gAK0EE}N?-DL*!TO4_tP$?#M8%vm3NLEj%S=X=476D(aC!CIcHAaE+>0$i<8r~ z!MSU5l{2??nUh<d@60Pmcjjk$ox7*saPpG!I`Xcib>x5lQ+UA_SE38geI8yk5{niL zyc1sBe==IQ|8Tfy_ZjuKysgDe7bVa+A|(~0vSj^BQkr#CmIk_I>13~zW%O&=r7g1j zMwhNQ8<cy-8?}6}St~|Y=)DJ4>B?6!wX!u=SM6Ue_f;inuq8uQ&!5mxa8jz0KGEvj zZ>0L_53(lV16dP0FZX|UTxy1gq;~8*tvl2wYfrqU545*SxbJyg_uvb<zHO`4FKg3C zZMAOjt<{Et1=?`kuZ^iy()iUZedt=gY&`voZo1%=&Bta+)5sNhxc6si?vF{d`GRcO zcUd1<`K~<LG^Q=tM`UZou(l?j(rsC)+kSjmw@-HJj`KbG*rkX*{^lla`*^23aj-($ z2kYg@?b+IKpj39QE0@kzpX@50BfCSt$x}0pbQPs)m-DT3r(D<W_&MphdQtbB9F(VH zqq_IVhq`b0s6Nv(puLBB)iW*Omc*pQgj;W($+!LO^iI2ZPQU%XIE~5q)&7&2oVZCe zCNsx)jale7DaNFTnZ+B=?5TTMr6*(Rw^PraY~FC^Z)@u!W|2P-@S9L5V(RK^Owbw( z)$_w@`_evecs%X}e;poA<X<e~4|_D6{wNt)2(l7nDacxo#UQJ3waejSSr4)xWJRua zNywUzMIoy~mW8YfSs1c1WNFCSki{XZLzaiE4_P3xLS%`qc8$m)kyRqgMAnHc6j>>< zRAjBlVv*G%%SG0UEEriavSe4gW@OQ>cGbwTk#!>rM^=t39a%fFcx3g+@{#o;1wbl* zlmMv#QUq691*8nFwhl-kkV+t>Kx%;$1E~g54x}DPL6C|dB|&O}6vfq61u2WGtqW2Z zq%ufpklG-{L8^n42dNKIAf!S_iI5s0MMA2Cl*!fB2`Lm(DWp_Lt&n0N)k4aJ)C(yX zQZb}tNX?L<Ayq@l=4$JP6wcLF4k;Z{JEV9>^^o!*^~3)Q$hSZdy*8VR17xzGuB5QE Q&|g$iP*?1CpO$$41Tp&eZ~y=R literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Europe/Zurich b/libs/pytz/zoneinfo/Europe/Zurich new file mode 100644 index 0000000000000000000000000000000000000000..ad6cf59281a1046d9dcd045fda521585e3e33e06 GIT binary patch literal 1909 zcmciCX-L$09LMqhq+=x|UkjT`&1!PZnmp6((5_jPcE=8#Ej!E(waeVJGVQV`Btqi5 zAVpMc%Z5ah^}y<Z9c&jJCJUQH7eUcw5kZ9=Nd4abC5WxZ{r}9ohV0?@{qfIST%2Tm z^*GJH@Zni)KK$;!(R^KTEwQfLFSD+;`>f`(xmK9_nfB^=M_mEe)b;AL_I_|g`~164 z`=0w<!%v=)h(iq$x#th*SE~}WZj<ycDVG7W7sx=LU)*UKGRTuE(GfB7L$}@%<Me9G zo8db6VYJ4!_R=92I_uEJx9ZvdREO2w(zq>GHGbtuO(;C9iTO7rsk~8=)0<>?&JIb5 z+$*U`m6F;~EhEC~bj00xGV()(jymO)(YNz7t-e6hn?~uFn(;bzcZ7~BcI)^pBV|IS zQ@w@Z@>BF<&G2?ert`99x$jBVi$^js;BT4Oa!G!E@R$73a8P{BXEb|ztxP)fr%o;{ zl_|BGb?WqOnp0Awxj&Yu-<PGox+du~PpnRBPtd%uOv$^^Lub4hEHjV4)>*B=GJ9XB z<TpN-In}SEpsq#c7PQK|^=&$T><L+r->ijEyQC<+L5sT_(}j_$3!m)NMIGh3_)?WF zx$D=Z2WDx>#WGp8HC;>VbLF>1QM$Y)Marh8NqMnLRwVY5l^O43Rj4Hu@nKr=^1f7t zv}@%*=cVe!O<i-eUe>lW>AGEKb$!EL-B7h(tG8EcCx>|h0>AfbSzXLgSyn`UN1$be zh}HGW-@a_W<;}?D%g_IEIP5R~w{JGc{E-h&rTOqX^rLwOy=>cvW!HmhkQ=r&cZ}RJ za?d>6G;-I-ZQGjrMs6IrbL7^Mdq-{_xqIaHk^4s)KsrELKzcx$K)OKMK>DyXjUb&M ztsuQ1%^=+%?I8Ui4Iv#NEg?N2O(9(&Z6STxn#PdMY)xxOZ%A`UcSw6ke@KH!he(S^ zk4Te9mq?pPpGc!fr?#e5q*q(hEYdB~F48a3Fw!y7GSV~BG}1NFHqtlJIMTVTX&vd^ z)-;cFkF<~Uk8A+41IQL2dw^^LvJ1#IAp3x91hNyzRv>#}Yc>Pf4P-lz{XjMZ*%4$* zkUc>*1=$s3TabN0HU`-lWNVPUu{E26?2fJ39%O%z4MKJZ*&<|*kWE5%$q~@Wyn)W| z{eB*%p!b#;CNocFr$WT){^f7xX~O>|>c5RL-@#_Hh9$CIp6ukfl(+;>c47j?CkKB5 Dj=i{v literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Factory b/libs/pytz/zoneinfo/Factory new file mode 100644 index 0000000000000000000000000000000000000000..95bc3a3b1a0c21d0314dd63c80d4e2e8ac0513fb GIT binary patch literal 120 ocmWHE%1kq2zyORu5fFv}5Ss<U(KRptGD67I$7KW5Z)d;-0A>3H(f|Me literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/GB b/libs/pytz/zoneinfo/GB new file mode 100644 index 0000000000000000000000000000000000000000..a340326e837ac8dd173701dc722fe2f9a272aeb6 GIT binary patch literal 3678 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 Yx$RPlNVqq$bELatR766Qn|A?!0JySS0ssI2 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/GB-Eire b/libs/pytz/zoneinfo/GB-Eire new file mode 100644 index 0000000000000000000000000000000000000000..a340326e837ac8dd173701dc722fe2f9a272aeb6 GIT binary patch literal 3678 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 Yx$RPlNVqq$bELatR766Qn|A?!0JySS0ssI2 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/GMT b/libs/pytz/zoneinfo/GMT new file mode 100644 index 0000000000000000000000000000000000000000..2ee14295f108ab15ee013cd912e7688407fa3cde GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<UarX@YGD67I#|6}Gzy$zy_6BJH literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/GMT+0 b/libs/pytz/zoneinfo/GMT+0 new file mode 100644 index 0000000000000000000000000000000000000000..2ee14295f108ab15ee013cd912e7688407fa3cde GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<UarX@YGD67I#|6}Gzy$zy_6BJH literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/GMT-0 b/libs/pytz/zoneinfo/GMT-0 new file mode 100644 index 0000000000000000000000000000000000000000..2ee14295f108ab15ee013cd912e7688407fa3cde GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<UarX@YGD67I#|6}Gzy$zy_6BJH literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/GMT0 b/libs/pytz/zoneinfo/GMT0 new file mode 100644 index 0000000000000000000000000000000000000000..2ee14295f108ab15ee013cd912e7688407fa3cde GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<UarX@YGD67I#|6}Gzy$zy_6BJH literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Greenwich b/libs/pytz/zoneinfo/Greenwich new file mode 100644 index 0000000000000000000000000000000000000000..2ee14295f108ab15ee013cd912e7688407fa3cde GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<UarX@YGD67I#|6}Gzy$zy_6BJH literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/HST b/libs/pytz/zoneinfo/HST new file mode 100644 index 0000000000000000000000000000000000000000..616c31bc5ea69cbc5ba90dba190a0f500fe8cc35 GIT binary patch literal 119 rcmWHE%1kq2zyORu5fFv}5S!)y|KbD&29MwnASZ-OeOy58h6Y>!g3%07 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Hongkong b/libs/pytz/zoneinfo/Hongkong new file mode 100644 index 0000000000000000000000000000000000000000..8e5c5813666a4d023bc9f7f7bd0e53ca1a61dd85 GIT binary patch literal 1175 zcmc)IO>E3T9Eb7Q*JhWJHgVxVyk!z8jf<%SZAD7A)q_o%R45M8rRWK#O+_RUA`%G+ ziG$OxhzJMc;;1h|yw+N@-ny%$>ZM&>T5F!agM*Ven9b~Gvd8^C@utM~rRt9pa=&mn zYux2sawyT1>mF*fJ?Z6gE4IJ=e%-dV2Rp8rhbL;~QT1ihd-}ROURh;Ri|0!!Ut!bJ z!jgV6%RU``E6?nteSW(~`p(TX{TDat7Y8QH%ah&uRYStO-g`m6SrRh?&7G2&US_hZ zIwU*3&JNB#B7><#cBrsR-q~XNzP~|+PmS0QU9Ea#-#z<L*6UA=SMBG+<@!tIT{GHJ zs>kMBF}a#i{dGaojL+Dr^Pw#!Kek$b8>lwl`<i9q&SpD#qe^~Us<ef(^F$q+YkkR( z_;&|wU{h9t^%)zg3F`2&fGJv-(M1PWm`J!wM{=DenmMnh^mdr3H;?L)GZm(EPpkNS z{(!sx_EBnQlz%#T+!s`;tzAWUtKwhpy85_U8{5EFT-7>%pZH&_S8#^~krk08ku{M; zkyVjpk#&)Uk(H69J>A;K;+}4GWO-zLqyVG>qy(e}qzI%6qzt4Eq!6SMq!dqA3sQ`y zs|G0tsRt<tsR$_vsR=0xsR}6zsS7C#sSGI%sSPO(sm{}tht!7@h*XG_h+d66D3ar< r1j<C}L<&VJMM_0#MT+%w)gtA3x_Xg<k&2O$k(%-U7aetxmzn5Kw|Ne+ literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Iceland b/libs/pytz/zoneinfo/Iceland new file mode 100644 index 0000000000000000000000000000000000000000..ac6bd69731d25fc20724798abd4f683f1f2ccbd6 GIT binary patch literal 1174 zcmd7QPe_w-9LMozO-ES7AW$-}Z<HEHu>K$jMYysKwL@$Wb#X)mML|W;t;7<dl=M)i zRo}!@^bZ2T*dYiKHLYdZT4sOEsr}I`n`J%y-aii=I@PJ)!}fZ3o}Ko2N4D+WwcPpR z_{<YNoOR~Iz5jIdxW*a^ob!p3^%o9quDaOc^=r7sxzt=*-?XZ@s;T9W|86MXX<c0` zt*N{lZAELf?M{(&_zJb-N>K0hbxLRLV(p4wm-~Bt`XJUIiH&J}SaVgoOWO6(&NJFm zmXO|x1NwO0O-UApH92`!Qgil8>d6s#I*_M*EnDST*GlPcJgm>J<;sidE&8%9Bd@lX z>Fa$dc@vzk1EILQUHC%>OOu+Ol`liNw{&QDN`@z5I?~=R?|P5w`^&fGLvvI=o@$iQ zb3q;3b3#5HtCaDu>gURG`Ld!~C)O;IuXA^3W<j=O#@FlQ&q4Xty+psKy*d@IkQtue zmpAL5u58yGiJJe`98Z?(j*U7qr@yD4*cY=mg(6N#AmA(wEOR!Pdw%Tk*mq9kFZOfI zVMAm`WJ_dEWK(2UWLsoktJxUY8QI!u_C_|hn%$A@t!96u0i*+@1*8Y038V|84WtjG z5u_8O6{HuW8LR0AX~$~%K^j6jLRvz4LYhLlLfS(5LK;IlLs~<6Lz+XnL)x>N{*VTd x4v`j-9+4)IE|E5oK9NR|PLWoTUXf;zZjpAareCCCq+_II{9k&`F@XniegRAxR~rBT literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Indian/Antananarivo b/libs/pytz/zoneinfo/Indian/Antananarivo new file mode 100644 index 0000000000000000000000000000000000000000..6e19601f7d3a420c1d4832178352c1eafbd08146 GIT binary patch literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J<Z5g*?W23N-r25kc)V*?OrVhU0W1tBEZ{T~QG_Je4U8$dM39Uz(- MZs7ttQ`eXa0Kl#|w*UYD literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Indian/Chagos b/libs/pytz/zoneinfo/Indian/Chagos new file mode 100644 index 0000000000000000000000000000000000000000..f609611c8fc76edae40cfcfede2767156b11429d GIT binary patch literal 211 zcmWHE%1kq2zyQoZ5fBCe7@McF?)w~rXLmQ$|NsA=k%@_c!TAYD-YtNEg@GX?fq}!v pH-tgkz!ZqhKoUR@LW1!?Q~!gk0O<o+1ENW=ipvIQt(~qJ7XZtCBcA{O literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Indian/Christmas b/libs/pytz/zoneinfo/Indian/Christmas new file mode 100644 index 0000000000000000000000000000000000000000..6babdeead509d64c1317373a537b4bd7391af8e3 GIT binary patch literal 173 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m14FV5NGhp-fyKu+ghAWD9K>Y^A;C1D XLH~=zRz3mg!*42=4bXf$U2`q~*SHut literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Indian/Cocos b/libs/pytz/zoneinfo/Indian/Cocos new file mode 100644 index 0000000000000000000000000000000000000000..58f80514e13d1929df97047c7a3de221640d93f1 GIT binary patch literal 182 zcmWHE%1kq2zyM4@5fBCe7@MmB$f^JT|34!m14GmukW_RA1B;Ju2!pnPnXv&#fFXng c^MFSEuL)IK0@6pwTrL~19d^2AR>lTg02q%N)Bpeg literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Indian/Comoro b/libs/pytz/zoneinfo/Indian/Comoro new file mode 100644 index 0000000000000000000000000000000000000000..6e19601f7d3a420c1d4832178352c1eafbd08146 GIT binary patch literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J<Z5g*?W23N-r25kc)V*?OrVhU0W1tBEZ{T~QG_Je4U8$dM39Uz(- MZs7ttQ`eXa0Kl#|w*UYD literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Indian/Kerguelen b/libs/pytz/zoneinfo/Indian/Kerguelen new file mode 100644 index 0000000000000000000000000000000000000000..2cb6f3e357b6ea6832fa9a5d56ad915a110b23f0 GIT binary patch literal 173 zcmWHE%1kq2zyM4@5fBCe7@Ol(Vp2o>|Ns9P86gr33~m7oEV>2;4B7^!V4)BaOamJ9 UA7mm(BYso4Y=Gw5>6&r@01o#Tp8x;= literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Indian/Mahe b/libs/pytz/zoneinfo/Indian/Mahe new file mode 100644 index 0000000000000000000000000000000000000000..49e23e5a0a8d5e90a9da0838a08c17c20eefaac0 GIT binary patch literal 173 zcmWHE%1kq2zyM4@5fBCe7@MOb<ylMp|Ns9P85tOi|A3?{92i)9d_x$t4NO2>h7b}= X0~+)nWFkl(ep9(@facrjns5OC04Eyn literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Indian/Maldives b/libs/pytz/zoneinfo/Indian/Maldives new file mode 100644 index 0000000000000000000000000000000000000000..ffa33658444a473605f9a04a78f3ec345e0fbe7d GIT binary patch literal 211 zcmWHE%1kq2zyQoZ5fBCe7@Ma7$a$-Oct-vI|Nj}8m>3vbUV!9*BntzBTL1%xk8cQr rFA!@Rn1UpLAcO?tfu{Zs+csYUM1!mW>8}T}iL{E#257CFt|=D)o|z^D literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Indian/Mauritius b/libs/pytz/zoneinfo/Indian/Mauritius new file mode 100644 index 0000000000000000000000000000000000000000..b23e2cee1f1bca4abedd105b04824431f40e8392 GIT binary patch literal 253 zcmWHE%1kq2zyQoZ5fBCeHXsJEc{=M^XGpNVb&$ASWZ=m>?SbdH{tNa0|Nm!V1VSbT z2GbWH<!%9tEDQ`54h$SVz99_S2Btu40+IlN5E5(y8vGw*BS;^}Rxk~;8AO9@2h%_o RfN0WO!DRz<k)5sy7XT^`DjNU* literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Indian/Mayotte b/libs/pytz/zoneinfo/Indian/Mayotte new file mode 100644 index 0000000000000000000000000000000000000000..6e19601f7d3a420c1d4832178352c1eafbd08146 GIT binary patch literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J<Z5g*?W23N-r25kc)V*?OrVhU0W1tBEZ{T~QG_Je4U8$dM39Uz(- MZs7ttQ`eXa0Kl#|w*UYD literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Indian/Reunion b/libs/pytz/zoneinfo/Indian/Reunion new file mode 100644 index 0000000000000000000000000000000000000000..11c6002e2ec443e44f07313dd8d169195b5c74fc GIT binary patch literal 173 zcmWHE%1kq2zyM4@5fBCe7@K3_8Ow(H|Ns9pGBPljfTb)P7+8FKLm0FTAp8&#OamJ9 UA7moPApE9s*#OPA(>37&07mc_c>n+a literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Iran b/libs/pytz/zoneinfo/Iran new file mode 100644 index 0000000000000000000000000000000000000000..ad9058b4937b8786f58e2cbd61adff5ffb6f0657 GIT binary patch literal 1704 zcmdVaNo>qf0LSrvt5n2NZHb7a4MAzg{5w;fr_0Q!XrC!sH7#0O(FI-ThFW_-i1t7n z+SrpyL>&n68qqjZ@}wjPLP~-Q2SS2Gkc{^oCvkFc@SnW-%*>%lli&9ROE*@!tUsP; z^9zTk!W=$N>Z;kT9}dRq(KpV?F-;DCOo!i&d7t4QTU@1M=Lb}r=bessj8jR8EjnrN zq?$2sLeK1*tde`~%aqe4!qG8A&)RoLIqN;rF25;K3pZ(3&PU~Teb8xBo7L?2DKdT7 z1(E)xM0?&mSK`TGIp<D{$mks-GcO(&SzS3g`-oQFoh{N=`$PCwUeI$3oGQmROV3Na zs`6gnk@9hYSa5r>3|xOC^3S*Fg`LrA(ZOqSacib1sD7&p%j(pU{90X<{YfoNIV*z+ z(?#*9N4mr+RV5#W%Vn=R#PSF2az%ffD7|t^mv!f=mB&8IRR>zd>gMTsP1Pl}c3F=u z54hAi@lvi&t`r+4#_5WYovPw{o~(R-PgK3QE35ApiH&_Bz3K9PwfT%&)*S5>wQZHU zuJN6!U)LqK6eo(U^Alu)r&}~mE7DC9o~q{P1G4$sNYV1PS8p5isqF(^z2j!TYVB!| zJ5PNTyV|?;?tRH>Pu(-Qw|tG*8w!OYBBO>xMGpO^uSm<X1xqmRQI?e|tl^fGTNp9g zvV`A?wJe{E82{_{g^Pk#(41u3?Y7PPY;)2$=G*U2@GpjE{?8EOLk7rSVuZ*Lkuiof zgG5G&3=<hAGEiisVa-sHu_A*-MvDv=880$mWW>mjkuf8KMn;Vc8yPn;@UUj&$k36o zBZEgqj|?9fKN0{E0TKcd0}=!h1ri1l2NDPp2@(nt3la<x4H6C#4-ya(5fTy-6A}~> z6%tlh6BiN~5*ZR25*rd65*-pA5+4#E5+M>I5+f2M5+xF5SQ94_C=w|WDiSLaED|je zE)p*iFcL8mG7>WqG!iuuHWGJO6F3q%5;_t)5<C(;5<U_?aseQh0CEw8HEsO2m%)gv e@P*-ZxHH_g`E0HWZ%RPePCF&wN>6jzBYpzV*O<5f literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Israel b/libs/pytz/zoneinfo/Israel new file mode 100644 index 0000000000000000000000000000000000000000..2d14c9998e9dc54bd1ad4710332abafc8e811242 GIT binary patch literal 2256 zcmdtie@s<n9LMqJCS!=^WQnzgfMptrmkV5wO43je2;>&GJH>!w1ZFBdCNWLOC;MTB z#pblu{JCXp^uwWITXT`M>1-o7Da%yMryn3oaK|MmZc5Mlv{hSw^;cWpyK`S>_fN*> z9V}U1l`8&m;pP_}uF*WaM=SHS+n4>uiNyzXd(W4FZ$7>yI*wnpI~%LC-Mr5J_Ek%t zeDiC0s<K6Ot<d)Ak_p!73voxkPdY5m#OCO;<NKVmmk#N3t$xw{S-d{4-x5ExcBl*c zzEHZMR{R*M7eBS-TR*4!?O%3Y6Fn0r+PyWW^u?%?s&Cqe=<6%8BkCG-#LX6a^w6(X zWK*gg({^1?+I+>1Z6C4XtoH)(8xL5M^A6khl<n5{#+L@Bl=!Us#GJs?F#~$)cfIzs zOP%`uqoV=|UH#Vd<~V!CkxHGo*<;V#u|Rvvn*&Mf6SOb)n4O&aj!v0f9Y{?+WclyD zWv4}Lx6-aO1!jdss9BvJnK4wZ9_Sv{584Crq5Vs&+3)X_nR^df55KZe&Z*m@=dMYY zj|BEvSp|(^UT%?`pSW6N#|}9;V~a&je}%K4KTYIb9F%$OcZ&SBoz9~l4U2*;qn*O- z(Q4tQ9kQtAnhKO<IgbU;sK;04$zuNz6)cz`pO~;qg%WR<p{pCzqUZ!?QP)zncyNqU z(mGEq=^AjJ++~T<)=sCaK1-BuJK$KQQ^eBx4*67OxTq*?kx$3;iOS5avMM}GEt^y$ zm-Y0jXKse%@?$5|v*$D9iVr?iD~|`ARj+MP)lKQnbCuOfzJ8apdSQcjzVe2%#=A<? zWOq9+j4Kkg-eFmLIa#b7cTTSDxI?VF{JDJblR>e*V~?zRH%is-e_6g%`<vSEc7^ne zy6w+T*k7;z)teBL-GA@+>mp2u={`?{5Hay$tPmM<J>&oQrJyh<^39Vs-#o==UjBZ; zf3ckrbD>Yax`Av6*%7iOWKYPZkX<3$LiUAh4A~j7HDqtDW^>5yknJJ+b2S@8c8F{d z*(0(^WS7V`k$oZ?MRtm871=AYS!B1!c3sVW`TMb9SF>Ye%gCOQO(VNTwvFr?**LOu zWb4S@k<BB!N4D>3_K!3G=>XCKqz6b7kS-u?K>C0*0_g<O3ZxfEGmvf|?Qk{yKpNs| zI)bzW=?T&lq$@~UkiH;|K{|u92I&pb9FFcF?cwMT(jZsUA*4kdJwlqq(Iuo!9DPC> zg>(vO71ArDSxC2#b|L*j8isVt)wB%hnX73U(lw-QNZ*jgadZx89Y^nw=5cfnX&*=b zkOp#e5NRROLs!#8q>D%!kv<}gL^_GI66qz<Or)DgJ30D^G?b&GNJ}|->S~(G(N$N| wR*t?RjpgVp(psdqNOST3+TBP~<C!TY%ZY`lUcc9$l#-rUnC$bWd3}+;1NQXO$p8QV literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Jamaica b/libs/pytz/zoneinfo/Jamaica new file mode 100644 index 0000000000000000000000000000000000000000..162306f88a80d69cfa5053103c285e2e8b221c3c GIT binary patch literal 498 zcmb`Dy-vbV7(g$T1ZXQ1e^3~8DkF&lJ4B5z(M6{&V!Cx@F(&>H1}9lu4U2;b0|RVq zK7-qR0h9X#UcUpQE+&4>cTdydrsqT#Nxz|fOjf?IOhuOW;6{$8((EhuSWOGTBrd#- zjcXoaPv58h$BW)vUZuswoi4rJn&7#w%cD!PH8|1R$+6ivuj}2@&{Uef-U~gme-Osi z{HLioUYv0@etE2&J4&t2thI}&%3J%s%=n#dq|Rj9J=s<y|FoXy4<=S786I9kjJN?S vh}nu_2Qh?LLQEmH5Mzin#2jJ|DFCSeDFLYgDFUeiDFdkk|F4iM$&TD_ya{zn literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Japan b/libs/pytz/zoneinfo/Japan new file mode 100644 index 0000000000000000000000000000000000000000..26f4d34d67b46513491f26c2e661c6e653cc130d GIT binary patch literal 309 zcmWHE%1kq2zyK^j5fBCeP9O%cc^ZJkbvvel>u)1J-1zaU;O1HD54YJFKHOd_`{B;B zM<4F?{Qtnr$OM5549(0y^$a}=7=fDWCNOY7NFU!21}_&N4h{iHGlFmk36A&=1gVFX r6o6=uW56`fK_D9BC=d;D7>EWr4om|b2%<rb1kq$Wlndx;T}v(iZ^UHp literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Kwajalein b/libs/pytz/zoneinfo/Kwajalein new file mode 100644 index 0000000000000000000000000000000000000000..54bd71ff2dc7d7fc6f3ddb6e3e5ceed4c92b72f5 GIT binary patch literal 250 zcmWHE%1kq2zyK^j5fBCe7+atL$obzU9iUUP=Rp1c|Nj}8n3)+E<~#rjGtAn+!1Dip zxB~+R1H*~~3_L!*Aq?7vh77ufMnD>2LkJ1>0j>C7XC|QlqCxh8>;c&gqU(V|<k-(; L19XiY&{bRj+D$OG literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Libya b/libs/pytz/zoneinfo/Libya new file mode 100644 index 0000000000000000000000000000000000000000..bd885315f84f8615da866553c9c0086ad430ad01 GIT binary patch literal 641 zcmcK1y)Oe{9Ki9Xt$}*US)7;RXlo!MImJk57onkU5{bt^QqwbvK^lJolR?C25Ro)7 zh{0lNBC&~(r-?>FBF7@J@O%%G$>4YQ-1qL5yZf9spI>psuc<P3Sd3#9=Z*WX=ZV|X zW9u${D9dYCR{3FBR|ZB^<)EaWvKM~S*0{8*-<18{r<)&p{g#_W*;+dC+s^J~thnyC z@7cOzyH<5>K1)YUs;;FC-JQEs@pMEdQei)t9FaX^C%&6~k%Q@Bl^R;rGrK!t*1Im` z^2I_p6l{_2eqC`ici4rfTQKh_Vou1sZ-XUjI2ZL()1H{f%yIBU#;l+5{_yc1W&ofd zP#`E6K@A86C8&X+;P6a<C`dsK6a|X{MnR*%QSc~$6hsOn1(O0wL8ZV_a4EnPWC}C| Un*vTjXMm^wf*&=1qTh{v0>JjEDgXcg literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/MET b/libs/pytz/zoneinfo/MET new file mode 100644 index 0000000000000000000000000000000000000000..388dd7442a540317163ddc7d83fae62ad9bc9155 GIT binary patch literal 2102 zcmdVae@xVM9LMn^VkI+rqrs7Yn1pDZ{D#yZwew&mM=6()N@yTz5yeFeN@EpsPMLev zSk577#h5j_`U7hXt@U%XkgUcUmTSwE{hD(#XFta3^ZfQ_fAwE|Z{K^|zIT7zAD`DV zvTj>*k?$YZ<?abL*DiPSd0PK;|Hl4_Q|Av%#xyW^RL{LB9qies(P?cOJG;tGyyVyT z3qkwjnG!qsWR86rO|{Qzmg%a&MkVc;WmorvWbI{2Zpzh+NSbDbSJ*YhzbhsCC#BB( zO8)p0@?UsMX=h&5wVxl-toNQ)`my7daU^D$2M*foURl=mCu~kj)UMmmXmhJ0mR%mQ z>jPz$laXulrl(nMLQuIEOEmx694(kg)eRq9p&N&PRbcQ3E$lm`yq=Gh-+oL5O|Pi1 ze$tAf!&Y1|VkK*ywbIN1D=X@>vZ-!cl-{RBr#IQ+KRUGJc+hVCvQFhg4XPMfqFZ(@ z*V6tBEo;u!@}9+ZYh{9hP3g8G=d41(DXUEQNLATiTh+PmY-RjAwsL&bZaed$t%|*D z)njifytmhCjy$j1TU+gp-lw$suH9PGvRSn^wJ1_ur91sKS{qoPwU-K1mr`kUU(C?m z7w6l$*MHIaiA39QDB0?Vf3|zNFIYq0xNU5huuVID)V)jJu*Ui^HD&F$%@r{<UwKqp zGNmow@6*<)sJ4xE>Auqu-G6w!T0U&E2X<Acb*R=J+?u6_c9vS(>T-)V`|aVv+14KV z%^peiSx0_~I?jJ%ok^F}dGdYhIyb3DUmLQ=#z(Y$|GU}|8_?rjgX-SftvKIh@&ARF z&zEEz+>AMK?%e+U&XaH`;_ljr`zkJuA4LAe;s4WJ48YTk02u-@24oP(D3D<w<3I+2 zj0719G8SYo$Y}U~3<ntxG9Y9`$dHgRA%j9jg$xTB7cwwUH!@^s$k>p<A)`Zvhl~#y zATmN^h{za`K_a6>hKY<587MMRPd8L#tjJ)I(IUe|#)}LX88I?sWX#B*kx?VVM#hZ{ z92vQ%8#*#}Pd9jE^vLj$@go61B7lSdi2)J>Bnn6vkT@WLKq7&J0*QsE3kDJmPZtg( z9!Nlth#(<BVuAz(i3$=HBrZr`kjNmRL1N?Sf`dfI(}f3#4-z0GLP&^^7$HGIqJ)GA zi4zhiBvMGIkXRwXLZapA!iB^O2^bPFBxFd;kf0$^L&Ao{4GA0)IV5yQ?2zCg(erfS vL*nP@0*FKq2_X_gB#1~9kuc)_GLDHZmkSH&WguLfSDIH42p1M$esSVoBHs-X literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/MST b/libs/pytz/zoneinfo/MST new file mode 100644 index 0000000000000000000000000000000000000000..da3e926d23e76bc4ded05861c01c1f494b9c8d27 GIT binary patch literal 118 rcmWHE%1kq2zyORu5fFv}5S!)y|G5(w7<_|6fSeFA^>G2Un{xpGmY)pj literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/MST7MDT b/libs/pytz/zoneinfo/MST7MDT new file mode 100644 index 0000000000000000000000000000000000000000..ddca8d1967d93336bb0432f4df79da3efa7ac689 GIT binary patch literal 2294 zcmdtidrZ}39LMo5NFgUKPZtaU&9o#45Kt0MW>nA#=>*{s&<LYIcC-R7owN|2f@1v9 zd{)LRlc1ZmW?t89Y^ZCvwz9e7*0dIOzgRZ9Y_YhUp7--_fAwGM%bwrsjQ>CHz^dSy z0{O?q*#Gdj=k4RS>U+a$ULK6q{ZFTxffs+&ANItX@Vr0T!i_2N*VrR!{D(T3I8&=8 zKk&-bkzy5jGhJk7nu_Y4lITY#)wK1gI;L@^xu|MFUtBV7V)I6H>@$TXP94*6dk-kj z=qvh?F9uBf*`tzhtkWdEHy}xG^(*y4r(D|eq`GWNvs@l*Qdg|>Npkt^YDRIUT$y>D zN|{@xXU2I{YIKH9{cXNU`zlVSho_ra$A8kZ51cnw4V=_-LbKG>PwdlkS47mjwI9hf zg<q;`m%StNliybvSw=FZ&Y4Wl2FV;bZnDk?<ocmkP4?+FozwS}$vsr97j(3kyyq6_ z{FW|NuyKZ7=v$)}RgdYyj62i~1)u80v017p?N2HCK3)}%pB3+kU(}7C4a$=J9&^*+ zZdtnhXLIweZBo+uvALydm%g=fzqzext1eyetSKv9t;>ENR=%8e@txkI$`cx-{7{ct zcA-QnUV7c!`ANO@w|ASn-d(CI>w~7McaFY$NrkBnMeF71IVP~;8y%QPGBt%Kbj`Qf zs&4kzQa7BaR!$Aes@F!<JtKRhzALQm9qN&Wro(D=-#WR^f66p=ckBCe4w?sAg1RYT zhiUdz=;jL{(~^;+TTa%QU~G~O?)93r-~FoB_13BfPbJDjp<?y$zENqdNmFgx!?M0` zQf+8GEE|*0sZEu=vT17Ego+O8(8%XzbIK0AdFZIwG8WR0^$nQE&(!Jmj!v`nh*x(s zG@ETZ({+?YN%S=P`7ixNBD;-9B=UBDRE+yhci(vb@__hD?W?t~D!@3Rc!7U0qKG|) zgp3Ip6f!DgSjf1Xc3{ZJkf9-CLk5R#bo^j=oE;x9K#mb2Lqx`i3=$b7GEAo(Co)i{ z9Vs$YWUR<wIYx^Nmt(xhfH_8t44Grh$e=k!jSQP(+{nO@kt0KQ+OZ>pM@Ekf9~nOq z03-rP2#^>cLEwl25(bVqAc1h&NFbqb+E^gL;D`ni4kR8(K#+(aAwgn-1O<r-5*8#b zNMMl2Afa*E*dW2-hz=4Sj`$z};)oCuB90g#K|-R0gb9fg5-6vQ6cQ?@jTI6sr;Qd8 zE{=F10Yf5&gbaxp5;P=gNZ63LA%Sz+$RVL~+Snn%bK2-3;p2!O5<rd!A|d36AreH6 zC?a7*;)nzii6jzAr;Q~NOs9<|5>BU$ClXL3qDV+NVu}QnBdSPPIpT^0mLsxAXq`5; zNN}Atx=47PHoi!Jkq9FpMq-Qv8HqCfZ^G=c#a?WSo$X(kTacTV?a$B8&CkvA{0**M BL=gZ0 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Mexico/BajaNorte b/libs/pytz/zoneinfo/Mexico/BajaNorte new file mode 100644 index 0000000000000000000000000000000000000000..ada6bf78b2815d3d99c97d521ab9a6b35c8af8c3 GIT binary patch literal 2342 zcmdtiUrg0y9LMn=gpw%wq@u(hds2#laOA%z_Rpvz$O)7qi5Z#kXHW)-p%f81w$_^C ziw?_G^yKV<wIXJb{bS^oTWhSsR+x=3Q(zcHtQD2x^t^vvcGX?$clJE5-_G6d;`8?J zsIE+N{_)JU|8RIZ?BPA~wccM_x*7}Xx~H3_dMnH8-i=ny>9Fak&n6DH46gd6Zt(c~ zb>BpnIz#PmPhD)@EZ^sCl}lyGaycPGM!orJZ1EN~9-pMfr_<F$=t4Cy7@@9=PN^Sy zep8cY2i1@5=hgg?ZnNP0fC}$#Hw)kER*Smc)arP<y6#!giyQ0JlIp#BY3Vi<k>}UT z)~!{`6S8#V%3`^GUZjo+&XlO>3=@5Exx@@EGqE54E-QLw%nh$z5Z$m^-+1sNSy>XU zSJiy0;xd2IH|2k*ZjSg;$0v5G_}NL55Z0m+hCern6T8*wz8;fwu33^hj~dUZU9zV6 zag%a%qoh_H(P{N@lJ4E7Gm7U*W_*dxN*kB8q1ie+W{%1pi_+`<98>GhUe!4lK2;mu ziZr);@VdIS?GJO?i-*<iwcnXLTDxRpVV}9P{5i>8W6WK-d*tp#hm1F_P`op*=)90r z$s0PT^Dixt%`crY1z*>Quc^b_(_0{gJNKKSV;<SEq10?`P*NO|WBl8u#eX%{lw^J- zC70Lh?JIs(+dqlXrL*VMj+3+czTtP&&ejoqf8X<}to)3AptDi!@(r5@pXrd@$^GV` zs{K+Pe!^6EOQmA6)l|jjNYy~4sSb^m>Nhr-n$dtfe5^u0@<oi=)8N&QcF(HXk_27X zHliNOny>fPo>BD?lX_p_NwqI9&opHBOT+LLb0G4B9OxS`jWezCL}#~oa;Q?8n%m7& zr#DG+S-pAsg+vJo4hp^|IAo5!{yV=w;7Ebv1OhLM6A}otwK&)E9<;!{m3uEO@cA8I zvEM1;<l1wuJw<*7<2XTo-~N9wu7G_Q7&0<sXvo-*!6BnVhKG#L)eaCDAu>c{jL0C7 zQ6j@c#)%9R8L6usDl%4AJ6L42$Z(PIA_L~j88I?sWX#B*kx?VVM#hZ{92q$>bY$$v z;E~ZI!$-!C1i;ls00{vS10)DY6p%0=aX<orL;?u~5(^|4NHmaexY~Fi0dchvK|+GW z1PKZf6(lT3T#&#ZkwHR(#0Cit5*;KwNPLh0x!MRJAwpt=1PO@}5+)>0NT85NA)!KI zg#-(U77{KbUP!=PZN!j}x!RZ^K|`X3gbj%s5;!DsNa&E*A;CkUhlJ17#t#XgtBoKM zLRT9@B#1~9kuV~0L;{IK5(y;|OC*>`G?8#3@k9dZY9oq-)YZlm3974&DiT&Cu1H{! z$ReRdVv7V9i7paeB)&+1U2TMs5WCtKBSChxQAWay#2E=R5@{sVNUZUHAM7w&^K4u5 UBwxBG&6ASkOHK8pdQ!sv0^LWd&Hw-a literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Mexico/BajaSur b/libs/pytz/zoneinfo/Mexico/BajaSur new file mode 100644 index 0000000000000000000000000000000000000000..43ee12d84a7c7e47aaa92406d01a539ccf93079d GIT binary patch literal 1550 zcmdUuNk~>v97q3_4T#_^8mK6uf+)l(hX%78(kws6PcyCjEc?u|)HKlsy-)~+<u!-` zvw|op2_<r%0a`@^6&ZpRfkae9kQmW~yxw_2n^rAa^t;@59&htD=eYAqykYXk#@b)F zY@>a7pLC(?eR=!Pu7NIZj;A}m*VE%4>FF767<~Al!qeY;eNd!ahZY}FVU<(#q9m^h z&-|t%=C4+fVJ~#lxP@x*jIXlzoxfW0^SLbjGSMvSdMeQ!erEa2R*7l)XjZh;%gVCH zCiYN^j!Ww>@kIx8Lhy03Dxp9p22`1(d9ga_TeC{`ovV}kE7h7eWAxgdY?bn8j<`-m zsnn~!l2$WKr8mBnjKT<$S$a>hVy7B+$`#3;{oUjQHp)7AX>uoD(zye-&H67#bl#n_ zCcm##Z@7F*ZR||dn+~5*1t&tZr$np5I+tut-mJE43YMY;32JN11o2MvnBtkArFbaL zY#Z*AlHPe{`>Sr*ac!(Az57h>Y<_QcUF_6l6%R~#!%1C{_fGBh*6PZo_f=J5zTPvv zO;rciNcE4SswN;$YF?D7+E3B4_eO@=_hgprKflu)XcwtFm}csay%wKQ&Kd3F`wxy~ zosJf<tX3nwmeqDn##>gC7JuG-)X4V~ms?y}Zi%;Vx_w;<Zlw4<_g@HP*+U|TND!GI zLP4Z*Xp04r3yuiJZ_71LM1#l%5e_09L_COm5CI_)I<!TE$jA^9A|*pih@1>TA(Apg zg~$pK79uS~T!_37fgut*v_*!<?9dh(A~i&8h};apA(Ashhse$l9wI$Me2Dyv03Z=C zLV(2J&<+9;1tbhe9FRaDkw8L$!~zKh5)C69NIZ;yAQ3S_g2d#|4hj+#BP>W<jKCm~ zK|+JX1_=%l9V9$Re2@Sk5kf+Q#OTlt5)!3DJ4{HNj6flgGD3yK$_N(z7t#9JMMv2s V2fD(8LW4pAU7;aC5kVn-zW_OOkC6ZX literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Mexico/General b/libs/pytz/zoneinfo/Mexico/General new file mode 100644 index 0000000000000000000000000000000000000000..1434ab08804dac08e4f595967d8c325691f08aef GIT binary patch literal 1604 zcmd6mU1*JA0LLG5WLQm@3m+vZt>*0OI2dMg#+vQehhv*zJDWLk7<=a9Gz?EoN-^7o ze5{o$U(q%fX3T|@Q)Eq(FwrTJxbRSme7&CEn>$y^h5ys@{@&iJ_u}{Sl?4MS;*X6o zU%0HwT;3<0>v=1?K5dKi1d9FFJ%j$<7`MOo$02?9Ww$?k!c}l@^~cD)cP|PCqQa4Z z>%|2_r{W^dLro8pYeFJrN3=}ME)}k#cXICRG~rHpAm@#qCgx9ltLMM@DHeRYuhWJd zYGKbEy{PNETHM#H(~rGWOWLH)sJpE)4<40E^Uo?zb)C#gJgAms70TtY0hK)~Rc3!_ z5;=}Ine%j`Sn=w!%<at<dHoZ$_hh_Sd1YAVH;fYnN1o`y%Gs)@_J&@SIY#;N+Vtuf z?^SWaK3yXERcYL5SsLk5Yla(T+3i-f_Hnsfcd1jXzm_4(54VVl<1?i{xLs84PuEpB zKCz*EhOUlxi;d|~IxwL~)l7e_Yd*QurXOJ)9Gt8+zqqKkT>YYI!*}J@)*-d+`~_K8 ze@n?jhh=@)GqF9eMea!J6FZlC<*q61B9s`^p|1x-Lu{^Y7^)PzKg`j4ZhFMtfmq$x zQK9yAe$@M$GSz|RM|wmQXQVj}`^nqCJ(krGBZOtOw+M%2T|OhCE$c@2h#31hKF{kD z-c>%~;bxgz;=_~Q^ZkWUmKjz-%!1ejF$`jvO=B9wHi&T$>uehHAokfb20|=^n8+_; zBg06Dl?*c>b}|fwSjsTfrm>Y_EW}!fxe$9H216`{m<+KQVl>2Rh}jUkA%;UNhnQ~D z*v>HCrm>!3KE!@T0gwt9B|vIm6alFMQU;_BNFk6)Af-TRffQrYR0AmoQV*mcNJWg2 zAT=?Hf>gyQ3sM)OFi2&L(jc`#inD2|gOmrU4^kkcLP&{_8X-kOs)UpYsgqGCq*6wy zkXjkV+BDTN%C%|gWfTmlm{BsMW=PSHs^R}%_E;0V+XSEBbvcurNeSNMB<Eab(%4`7 C-Qw5) literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/NZ b/libs/pytz/zoneinfo/NZ new file mode 100644 index 0000000000000000000000000000000000000000..60bcef686badda46f36652694d06d041c70a9d87 GIT binary patch literal 2451 zcmd_rdrXye9LMqJpwhVKWemws!@O`gTvUW2LIninl6fRN8GVz42MwVlp$lXhnPXVf zmaRD|$pO<6kpwLTF@#zdvNfreebbp|D>PSA%fg=b^Ka{q{_2mOXV3Hc?QG|tvwhyj z<t{CbH~x0rWPQTJwaa=qkKbs$+B(`j2bOLrXs<qR9$a`{Itu#Dcf<STP-3guIjU8< zLh8+~pZfIhwKDU_$IbfQP@ehzIgcKF=ZfdpiI22<+mNTHX`dcncf`}Xd7GZd-R1e9 zs6zXkwVspN4bmSdo`I-x8Ms<wp8EQ=RG*MP)o0%x^}V!5{2KpI|Dod=P<uuLyP7np zut#rxwNA&T?ACGBB|1LrIh|0Dr4vSqH8?R+gD-5Bkg1sx(!W|l9T5`Ryhv{O-d`qd znI*UOT$Hd9Kbic-piIdamZ=-t<+fNK4KMvvrv?3}w>up&-D`o)2skG*&Q8;r!+kQV z*IOe#X_m;n;S%-sR*9}3BhH4k60_!l#Fphq+~N-<KEG6FN9>h32}^XYZ-XQRM{B|_ ztvc^YkS2anuSs8C);kWC>7CtylDs2N?`r&6Qr5@m-L<DAb!D#1FYJ&7$+5C9<rPVr zG)~gPswMsUuaYt1mPJ?VH1kZdWSuV2#mB;ANoSU3HyV<&Gg5PF&PrZYfZkI)qDv)0 z?#)nLmg+CA>Akvq@<qw_eoOOj49a~!Jg)`cwabc=rn<XdmizbD$;y^Cec;6sDSTnO zK3JY5Vpi%yd6BXzGhd5h0_5SDiMl%Qk`#|!F2&dUwB+(UF;5R`>E{z=P3LF2w(Yt+ zvh#qJz4WcDtJ<OE72Q%HZSv@fZ}hR$?Xo`Us8&v?l*cE&t{aSe+3?%5TBV6n{Z)}Z z(Gx099!}S%S`+l?-K(T#YlzlvN|R^I-_^_EHR_*k@6lua)7vnbhO9Xl`v)AO4dcx& z!^bdMdN>~%bOdrXtTXTI9G8*nUdGElrMdW?;c(bkFW0}A;0^1V-<jQlOc9wQGD&2X z$TX38A`?YsicHnknkzC{WVXn3k@+GMMrMpm8JROOX=K*Ow2^ru6Gvu_OdXlKtu=XM z_WU+X-`1Kxk^m$FND7b~AW1;7fTRJ*1Cj_N6G$p-tz00<u(h&*q{G(A2a*t5D<eos zkenb%L9&9R1<4DN7$h@DYLMI@$w9J%q{r6E50W4xLr98{93e?UvV^1w$rF+&BvVML zkX#|jLb8RV%ht*lk}xD=NXn3$AxT5BhNKP28<IFAb4cot+#$(BvWKM4*2*7}KqP}m z3XvQlNkp=Uq!Gy@l1L<zNGg$BBFRLuiKG+BCz4QGE2BtCk(?q)MY4*d70D}-SR}JZ zYLVO`$wjh@q!-CAl3*mmwpNOf93x3avW%n|$up8@B-2Q$kz6CmMzW2h8_744a3teM z%5ANjBS}ZHj-(yQJCb-L^GNEE+~fZ!`M&%iM90PFy3<@yIZ4jB&e*7&InFp|Y|L!m FzW~Tp3}^rV literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/NZ-CHAT b/libs/pytz/zoneinfo/NZ-CHAT new file mode 100644 index 0000000000000000000000000000000000000000..abe09cb9138504ee3f29cff16411e0a3b0b957ea GIT binary patch literal 2078 zcmdVaYfRO39LMqhA$8+$Q*#W-P}96{xE*eS(FqL@lp|g6q(t;dlLH!QNkSLVR4V6O zO`G<Jn&r%4i?of}D$uQ34=S@YyDa-=TRv!LuGUS1e(!$|d(@-WIXl1C|NOV};B5cT zyRoUgwb1*=YrMO|hu3!Z;W~YrJ5GPO|E<J~KP~a-Aq!qSq@=zpmi+k%OX)dlsc*b! zX)VJx@wr}`6gg;<yW@6s)=rzUvd*TAwpw~w$kNa6P{zz!WxT&unSpF&9$unrzD?G& zC+F+h;S0)&Cu#Z%r!=ExL^HP?({%-jHmm(3o1ON9U7r`woP@<TH|0CcJvZCtjl8S* zCns3;hldsFpQW5vb}4uFc;)R`ul#lARnQStc<Bc!jI~=)cE5^Cmsv@0k4n>Xt@NiO zw&0gEEBkb}m49~079Q`g8-`L<v9G~y?E6uXO<}vK=d>!DmupeW>snk<pqnH6RW)t0 zs<XOPefekAj5cY>uf0}#wnBAh;<ogiEG-+Xv-&<y(E}k{zU7=6x>D?x)=|4v*}AR9 z?DooJHO@I{E2dviY{E+x8#|>tzJJ12esfH#-fOd_p=WjHtG#M|dBE;^I<A(d_S)SW z>ZG<#yQd+f)wMBOlb@n{=at#o)Qf7Jv_h@FC0YE^0=1nPvGz}<YTe++wtnEU?mO_B zbv*O6?(f=X8#WGUqXzWAs;}(9%DviD{<d|_=+r|~pSR6kOq+jy+`6nx-CwM+hlexu z$eY#n=#f%;?BHr`*_B~E+pDy-?GM{JHZ~R*mvGg8`cCk?{wwZb^o~|}<2>&~b0Fw> z$Lqa`f1mZyMlTdD441gGsF<_Rot_s6@Egx-{1?x>`@3U{2CH@=&pyBB`S*1jKz4v^ z0oenx31k<@HjsTF8$ou0Yz5hiuiFf=8(+5_WIw)cL&%PN-IkC&A)7*Wg=`Di7qT&A zXUNu&y&;=Jc86>a*`Kf5AhJVbi^v|4O(MHQwu$T$*(kD8WUI(tk<B8zMYfCV*Vk<r z*)g(ZWY5T^kzFI(M)r+t9N9Utb!6|z=8@ea+eh~A>l%P`0BHfz1EdK^7mzj}eLxz4 zbOLDw(hH;+NH>snApJlZf^@{!wFK!2(iEgCNL!G;AdNvfgR}<e4bmK>J4kzw{vZuP zI)t>y*YyZ#64E84O-P@RMj@R-T7~oqX%^Bgq+LkAkcJ^0Lt2LP%-1yy=^D~Dq;E*$ zkj^2kLwbia59yw-Yry}#eZdO9hLPNG;lkn)_r(P=k`pb@E6FR!=T=U%I4|t3i`;ea EFB%CJV*mgE literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Navajo b/libs/pytz/zoneinfo/Navajo new file mode 100644 index 0000000000000000000000000000000000000000..5fbe26b1d93d1acb2561c390c1e097d07f1a262e GIT binary patch literal 2444 zcmdtjeN5F=9LMnkqQH%ZQ;8v<nU)CgtR#>b6-0<P2(NH8!YHnHS1a(Ln-=0RD8?U+ zvtrCL36!+fOng|gv7xTv+REl|Yg!BKxhxw!Y?8peo%dPwPk;4STVM9OuOIjS&-=Po z`_|@&f812_4G-6C9^R)b{@GWcUmFNlJ<liU-dDa?dprTXw{@E6E54}vI`*j#+N1RF zyx$s!>*B?gOursm&?$b8b?d7UesOi|Njd(VTTGm*mXq%nh`_OY8GIv2h@FWtq%9yq zpPH0YHYBL9x|w=v#e|wxIIhF9MpXC<xjIswP>}}?Nyq3Ob<M?I9d-V=h(6JxW8Uo* zv2XTB`ErZ6w*6Uo-Bypd-d8WDuPPC7rT5Ai`6=Rtlm#+=Zn2sf>5vJb$tvNO`8x57 zNR>1kp=X`^LCrpNN#EFeTFvp#k~i%*sOGK=%6aQP6gTI7E^k@(wwNFHo=i^FA~|qD zr#Lo>l#!D<^^!~6I=EM-oo!U<-OuTaBb6$%*{ic&TBNeQtuklR47IRitz1+&rgD?- zlegu3q85jz%DluYBJbNMnLmDB6rB1=-u~%;Skmv%cMR+nOFMqlckbFQ3L8GsceU<P zcbE6;d+N8TqRba{anTx8{Ogb`NpBJ*XZOp}=vq;Fq+Kq%Tqw$3eO)jAxJEgf+VuVJ zELG(-K3&l@M?J8lOjr6t)rzEa?OOSja!thQs@zkm>gzP=p8ch855>q;fg!QFZ&W@w zvR~A+4$FrI+eK~tQMsmjy?EGpM%T5qsYlWe>qoslRUh4{JtbwzbJ?%G$?3{_+O2)z zvC4O#K(G7eXSKeoT0V9rMm+A%mrooV6%AF1vaw@WY{;FI8yk*_O>r0G=JGDFIWVsM zd54vM<TJe`zEf=(Jg&En`PI|iz51DRZq?M>qPHC@P|dX-y?tkr3Jv-5Z%WwTuYY~@ z-y00>?i3;ze5)rU%)Dz6Vc(<dr(EuI31^XcR+y*SJQXgpA|XQThwERgFKDhdEUF(_ zA+khdjmRRARU*qo)@d~hMOKO|)oRv?EEZWUvRq`nR<mGa#mJJ8HKScLFRYp~%LdlX zv2bMN$kLIuBa25?Z#BzD)^9ZhKq`Qg0I2~-5s)fylmV#&M<I|(aFhb61xGQEYH*YT zsRvRJq#{;R5~L<bQIM)2WkKqK6b7jbQW~T-9K}JZ!%-fjK2}p8q(W9xBBVwfMMA2C zlnJR5QYfTSNU4xoA;m(fg_H}a7g8{!VpdZ!q-GpNL#oD6Hl%JGg+nUGQ97h{Nb!*B zA>~8rXEg;xDrhw&L~3X?MMSE|QAVVWNFk9*BBexXi4+s5CQ?qMo>o&(q@q?+QlzF< zQ&gm?9A!o7%28OPvK*yFYRgevq`F9Xk@_M9Mk;JIB}Qs&HAP0MY&B&@>WmZ`sWeBa zky>*U8>u!&xsiHv6db9z)s!5mxz!XMsk+sa9jQA~c%<@3>5<wa#mE15^&RHNV6pj8 VNOLaC$jQh`b7p5}WM^bK{s0D?lEVN1 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/PRC b/libs/pytz/zoneinfo/PRC new file mode 100644 index 0000000000000000000000000000000000000000..ce9e00a5db7c447fde613c59b214e8070f30ba2f GIT binary patch literal 545 zcmbu*KQBX37{~Fa>ThGXu?mi&+QDKF36auDBdLYEFw~^d?V4RTCt+f_yM-6v4M?P` zrdx~ZyEy4);`!cH4B|AWpQd-YzpsDXsISV8lh%K@oN2xMp0xV)a#XXeiO-<beU|pf zjcbQR>1=Gd?(Kzr-Fb9xyIFa!HeGMCDIcTtpg%LP{q4}rJ{_33#$9Zp>-+h=%Q$=X zU=|7|@nYr5EKP-8Zu!*Y1~o4~Rx$Zb(Hlzr`Vl$r>6=Itr-nrWE92FDUrJ@YhdvMV z_<xx7r6*b|6_9zz#6+EmOik3e$Yf+TG98(ZBtSACDUckAnuPZx3z7!OgCs&SA*qmD WNHQc_qNYRgCH_BQMr*FDXTAZK`l!MH literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/PST8PDT b/libs/pytz/zoneinfo/PST8PDT new file mode 100644 index 0000000000000000000000000000000000000000..d773e28f1e7743936188263954598b2b521c15b4 GIT binary patch literal 2294 zcmdtieN5F=9LMnsqI7wv(*sHbc2bk>6|R7SDE6S@E|>_$mBh@%co?LiLI`DKi&fSf zUvj8D(b2h<Yle>5Y!;DQt=8xPTC;47wfV4$Rn`_M-P?JefBUQdTIcTkUccSHe*b*l zp>_4OIi7!<82b;G(`_&Bs^|40^V+E-F;Dx=!I%D!Pj{!7p_#vL9jcnGE{{K@uUxEE z6K87lUmq8#@X4T#yx~`#13n$~Y=Vkzjn|X5-6k<r>1OikIGM7<Yhts0lGr`Vj8}ao z-lOm9xL=M*{AjO9_-0Tg4s@BT-|1D?9C=Ht7rWH8yG38u)}*el->z?{T%(eTLwf4M z1!|fvPbbePmm9swIwd++QZCImH+?@%QirCRv=9E2>Bq;-%?HnlZkeNRdGbA(QIV); zHhimQ<^HU1UGc7(o%E$n_xGsu@R;$%?NYvx&yD|wO=?d6ag#CJE}1=hO`vb1%x!<f zWbIul*>x}IoTgbaFW9W-mrs%0^a`Dqb5Rz==Ii{_$twRsie7l-f?D*^gf2MyiCTOt z+1!5WO?5}-Wpn4td(>Tx-<c&HM`USfzgbrOycEtgW_jTjS^nFw4rV^5g2N|tapGoG z+_zt^7+a<8ex=W>{JKF(c6OUpZ?BZn^*c=2zJRP=TxsrUQBs~-U_xsyNoXR?ROEgo z73UV|wbOr9Yd=iYmEmEv?r>P&H*!L)?-<qh_wQF5s!!_&dfL>+k`Yt&Ot);x954^o z?U3rkL#8HJDK%pqrY^le>IQ2~eQcW4A1yKs=Ogmaz8byxi&V9xC8!_n4XefqpWfOz zs<!3D>+OxFRa4TKZZ18nnj>D*l0P6VBR`tf<U`WhKWN&<JLJ)xUh~+QTG`p&Wgb6S zB<&lT%o7Lv66J~VL{GAh|I#lK>D=Y<L?W+BRE&FG<(>&;<sna5p}qIoTNbjLu%B&j z=wFN|Vh=+?#)J$C85J@tWL!==Fl1!N(2%hqgF`nuUKk$7jt>|hGD2jC$QY49BBMlx z>9pfS2I{mUMTUxu6&WltT4cD$c##1kBSwadj2Rg;GHPVl$heV#BO^zK?zCe^29Jy$ z89p+8BmhVRkPsj-K!Sio0SN;V2P6<q8wn&7P8$m(7)Ugba3JwO0)j*Y2?-JtBq&H! zkgy<eK>~wB1__PR#s&!v5*;KwNPLh0ArV4Cgv1C55)vgOOh}xNKsjxskWe{otdL+i zZM2YZA@M>2hC~br84@!jXh_tMupx0n0_U`mLqg}Yu|tCAw9!Mthr|yFAQC|&gh&jL zAR<vj!idBX2_zCpB$Q4YOC*?18%-pfP8&}oph!fKkRmZff{H{H2`ds;B(O+ik<dDA zY?0tPZFG_FI&FNB03#7bLX5;12{IC8{NIE*&K5i07CWP4ULYrsl~IzN9mo!3#r+Mb C_f!G^ literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Apia b/libs/pytz/zoneinfo/Pacific/Apia new file mode 100644 index 0000000000000000000000000000000000000000..fd03ff765eace5b92fbe13e3f151a1bc49c88d03 GIT binary patch literal 1125 zcmd7QPi%{E9LMort*fML2?w!VI3c=qd$j11S$04sS+}7p8@8{`y3U_%tXBVKNC<Jj zNOqiv11Ap$vV%-SBu?fei1^0A4RQ1T=lAYO;>5|*JkRS%lm5x`d8a!2vn$LW$87fw zCr7<IxvxCoE|*Hbo0pkjqIq(upPYzj%jjEa`EmYgYoScrvOjgBESF8|RW~=zmDG}l z+P>(6q(j#=ZD(Z5yED4=%^m4@oYT(fE3)m@Y1w|m*Bux7r0eXsb`Ne4$>nutYeX_l zY3->glU>Ua+FSlnvh&&{`+1J`eOfKK=kK-uS%vJr_f+@z-(>H^ZQXbAwG0$4>)_C| z42duMJ6`F5#&H?0yRZ4hc{x}ysUs#WBVSHxK@)Q5MUNhSP$@^I*6GoktMu5ljEtUJ zsK-yOm9gAc9kbR3W(7-vC85%Pc!iAd-Q%*h+dVUDyP8bU+F=QVt?g|y<;L8dG4qXi zq=5y^$&{(~yjZQfa(X7>#oQTppZxtu{l!x5&)b1DPuwqC%VNlCd>_jp>mdskbt@uE zB5NXxBC8_HBI_axBP$nmOCxI|izBNe%OmR}1t1k5B_K5*MIcomWgvARg&>t6r4)6w zAjKfnAmt$SAO#^6AtfO-Aw?loA!Q+TA%!88A*CU;6?MfS)gk2}^&tf!6(S`fH6leK mRTgzy{$FK=>s_%8)n3dQZ>UPvMOQ{^YhqQ&c+_(@@$fIA@G;;3 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Auckland b/libs/pytz/zoneinfo/Pacific/Auckland new file mode 100644 index 0000000000000000000000000000000000000000..60bcef686badda46f36652694d06d041c70a9d87 GIT binary patch literal 2451 zcmd_rdrXye9LMqJpwhVKWemws!@O`gTvUW2LIninl6fRN8GVz42MwVlp$lXhnPXVf zmaRD|$pO<6kpwLTF@#zdvNfreebbp|D>PSA%fg=b^Ka{q{_2mOXV3Hc?QG|tvwhyj z<t{CbH~x0rWPQTJwaa=qkKbs$+B(`j2bOLrXs<qR9$a`{Itu#Dcf<STP-3guIjU8< zLh8+~pZfIhwKDU_$IbfQP@ehzIgcKF=ZfdpiI22<+mNTHX`dcncf`}Xd7GZd-R1e9 zs6zXkwVspN4bmSdo`I-x8Ms<wp8EQ=RG*MP)o0%x^}V!5{2KpI|Dod=P<uuLyP7np zut#rxwNA&T?ACGBB|1LrIh|0Dr4vSqH8?R+gD-5Bkg1sx(!W|l9T5`Ryhv{O-d`qd znI*UOT$Hd9Kbic-piIdamZ=-t<+fNK4KMvvrv?3}w>up&-D`o)2skG*&Q8;r!+kQV z*IOe#X_m;n;S%-sR*9}3BhH4k60_!l#Fphq+~N-<KEG6FN9>h32}^XYZ-XQRM{B|_ ztvc^YkS2anuSs8C);kWC>7CtylDs2N?`r&6Qr5@m-L<DAb!D#1FYJ&7$+5C9<rPVr zG)~gPswMsUuaYt1mPJ?VH1kZdWSuV2#mB;ANoSU3HyV<&Gg5PF&PrZYfZkI)qDv)0 z?#)nLmg+CA>Akvq@<qw_eoOOj49a~!Jg)`cwabc=rn<XdmizbD$;y^Cec;6sDSTnO zK3JY5Vpi%yd6BXzGhd5h0_5SDiMl%Qk`#|!F2&dUwB+(UF;5R`>E{z=P3LF2w(Yt+ zvh#qJz4WcDtJ<OE72Q%HZSv@fZ}hR$?Xo`Us8&v?l*cE&t{aSe+3?%5TBV6n{Z)}Z z(Gx099!}S%S`+l?-K(T#YlzlvN|R^I-_^_EHR_*k@6lua)7vnbhO9Xl`v)AO4dcx& z!^bdMdN>~%bOdrXtTXTI9G8*nUdGElrMdW?;c(bkFW0}A;0^1V-<jQlOc9wQGD&2X z$TX38A`?YsicHnknkzC{WVXn3k@+GMMrMpm8JROOX=K*Ow2^ru6Gvu_OdXlKtu=XM z_WU+X-`1Kxk^m$FND7b~AW1;7fTRJ*1Cj_N6G$p-tz00<u(h&*q{G(A2a*t5D<eos zkenb%L9&9R1<4DN7$h@DYLMI@$w9J%q{r6E50W4xLr98{93e?UvV^1w$rF+&BvVML zkX#|jLb8RV%ht*lk}xD=NXn3$AxT5BhNKP28<IFAb4cot+#$(BvWKM4*2*7}KqP}m z3XvQlNkp=Uq!Gy@l1L<zNGg$BBFRLuiKG+BCz4QGE2BtCk(?q)MY4*d70D}-SR}JZ zYLVO`$wjh@q!-CAl3*mmwpNOf93x3avW%n|$up8@B-2Q$kz6CmMzW2h8_744a3teM z%5ANjBS}ZHj-(yQJCb-L^GNEE+~fZ!`M&%iM90PFy3<@yIZ4jB&e*7&InFp|Y|L!m FzW~Tp3}^rV literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Bougainville b/libs/pytz/zoneinfo/Pacific/Bougainville new file mode 100644 index 0000000000000000000000000000000000000000..6a6c2da28faa75b344004f55ad2a90caf9046091 GIT binary patch literal 286 zcmWHE%1kq2zyK^j5fBCeRv-qk1sZ_F8E3PEOWHXfLgrm>sQ>@}KO++(GcyCj#2Y|4 zhMol=g>@4cI2agaZD8Q>@eN_nHZ)++Hn0TJh9D(i5Q5!OkVPO20&GAGVv7Rp_#d_{ zNCQL{y<IW^M1vdwqCpM;DF!(PMArkw7@3%vSb(m8xTy03$VEVRaf02&3wD<v$Xx-x SI2^~oz-0q;tevi*Ar}CQuRnzV literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Chatham b/libs/pytz/zoneinfo/Pacific/Chatham new file mode 100644 index 0000000000000000000000000000000000000000..abe09cb9138504ee3f29cff16411e0a3b0b957ea GIT binary patch literal 2078 zcmdVaYfRO39LMqhA$8+$Q*#W-P}96{xE*eS(FqL@lp|g6q(t;dlLH!QNkSLVR4V6O zO`G<Jn&r%4i?of}D$uQ34=S@YyDa-=TRv!LuGUS1e(!$|d(@-WIXl1C|NOV};B5cT zyRoUgwb1*=YrMO|hu3!Z;W~YrJ5GPO|E<J~KP~a-Aq!qSq@=zpmi+k%OX)dlsc*b! zX)VJx@wr}`6gg;<yW@6s)=rzUvd*TAwpw~w$kNa6P{zz!WxT&unSpF&9$unrzD?G& zC+F+h;S0)&Cu#Z%r!=ExL^HP?({%-jHmm(3o1ON9U7r`woP@<TH|0CcJvZCtjl8S* zCns3;hldsFpQW5vb}4uFc;)R`ul#lARnQStc<Bc!jI~=)cE5^Cmsv@0k4n>Xt@NiO zw&0gEEBkb}m49~079Q`g8-`L<v9G~y?E6uXO<}vK=d>!DmupeW>snk<pqnH6RW)t0 zs<XOPefekAj5cY>uf0}#wnBAh;<ogiEG-+Xv-&<y(E}k{zU7=6x>D?x)=|4v*}AR9 z?DooJHO@I{E2dviY{E+x8#|>tzJJ12esfH#-fOd_p=WjHtG#M|dBE;^I<A(d_S)SW z>ZG<#yQd+f)wMBOlb@n{=at#o)Qf7Jv_h@FC0YE^0=1nPvGz}<YTe++wtnEU?mO_B zbv*O6?(f=X8#WGUqXzWAs;}(9%DviD{<d|_=+r|~pSR6kOq+jy+`6nx-CwM+hlexu z$eY#n=#f%;?BHr`*_B~E+pDy-?GM{JHZ~R*mvGg8`cCk?{wwZb^o~|}<2>&~b0Fw> z$Lqa`f1mZyMlTdD441gGsF<_Rot_s6@Egx-{1?x>`@3U{2CH@=&pyBB`S*1jKz4v^ z0oenx31k<@HjsTF8$ou0Yz5hiuiFf=8(+5_WIw)cL&%PN-IkC&A)7*Wg=`Di7qT&A zXUNu&y&;=Jc86>a*`Kf5AhJVbi^v|4O(MHQwu$T$*(kD8WUI(tk<B8zMYfCV*Vk<r z*)g(ZWY5T^kzFI(M)r+t9N9Utb!6|z=8@ea+eh~A>l%P`0BHfz1EdK^7mzj}eLxz4 zbOLDw(hH;+NH>snApJlZf^@{!wFK!2(iEgCNL!G;AdNvfgR}<e4bmK>J4kzw{vZuP zI)t>y*YyZ#64E84O-P@RMj@R-T7~oqX%^Bgq+LkAkcJ^0Lt2LP%-1yy=^D~Dq;E*$ zkj^2kLwbia59yw-Yry}#eZdO9hLPNG;lkn)_r(P=k`pb@E6FR!=T=U%I4|t3i`;ea EFB%CJV*mgE literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Chuuk b/libs/pytz/zoneinfo/Pacific/Chuuk new file mode 100644 index 0000000000000000000000000000000000000000..e79bca2dafa7e914a9baa568edec1b428a2ac63d GIT binary patch literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m14Ew=NUCQ61B;Ju2!pnv0f@^GLV{^P YgZ|f<scix2!*42=4bXf$T|)yd06J?KGXMYp literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Easter b/libs/pytz/zoneinfo/Pacific/Easter new file mode 100644 index 0000000000000000000000000000000000000000..cae3744096402e8a452336544edf96ca9ae5ad8d GIT binary patch literal 2233 zcmdtiT};(=9LMqh;W^+W2$|3mS{MbwkLQQ*_=_YVJxF=dBs5cpfF(pgrWD3jVzpF8 zYi_!%R{7BM3tcFi%-?#l2P@BoBU`MSbgMNPJy}}*`@R3wRqLXgF8ZJI`@jA>7w6*a zeBPmkmZn1IZ&$4Sgv0f$Jv^swwzrYvy8pLurM@(9LEIA`8>iz7@paXk2wf|YcNdtb zjBJSxEYdNKUt$xE>ew$QB<@m*zU)|7;>Ul~3470}#L+SB??0(7-#wzIG!Lt!r%svV znn5+S>99%3>Q<?@?=)8=56HAxo6NMyPMIFF+)NKIk+idOP5MxoT=i+AzIsQxTyrR( zuWkQTuG^NOGkPP{jJ60pv;3mEzV0i1L)y5?EOSieFUQoZ?|wEno_<MXoqxyN^wy}{ zJocK&e)&boIoxk%_dOxGFSMGxRjWm9-lFrXs-<9Mi!Piqri%0eU7RpamH3b7(wI|H z=1kFLAH}Kiud_|X{%_PRANWn>(<juNy%Q$TdQi>n4;#JsL%Fs2O;c6)hTK;3yqTBs zoK)uz>+0{@Wq$IYo<9+xY9_mN?a?-MNBADS;D{p&hbnaNy;xOO-)9!>Iw<v3r_G%` z+vTq8pY-C!4hbcErk9qURZ9<jYnEO4zFM~J6Vq^hzq+?gOyj<_vb?j$tk_yB_k~uN zl`YwFe~~t;YW=c0b*5R9H6d$$h%!x66IIjr483;poN6A8)GgtYs&&^Hy>4h&J<xMp zKe%I1t#90?+ct`{S3aX3Y8a4?%-7As6`j%<z14K3FOjY@>rD5BGI`|PpxN+wx;*-7 zp4s?zsoL~pvgvsxO+B_gS3ll&QT5g(>0Z}$eNhpS|M-fI`7d8FuDf%C<9PQd*FCVu z7w5XWw>yb{-4E<>>?b4QOIjEVIo0;eRwee7+EZ-*_dXx*KM4Jc&Dfv8ZP`*~zuSJh z-43!J^ftr;JL0li0``P#3fUF1Eo5KF#*m$P+N~jbLpF!(4%r^EKV*Z*4v{S)dqg&g z>=M}~vQK2A$WA@&R*}7W+RY-nMYfCV7uhhfV`R(7o{>!>yGFK+>>JrQvU5+nb!6|z z=8@ea+eh|~Gyv%U(gLIhNE47QAZ<YUfHVT>1kwtm7f3UZZg|>uApJlZf^-CF3DOg! zDM(k4wjg~$8iRBOX${gFPum=%JD#>ZNPmz9Ass?mg!Bk$64E84O-P@RMj@R-T7~oq zX_lw$7Sb+H+b^VHNXL+tAw5HyhI9>S8`3wVaY*No)_L0AA<gr&-9y@k^bctu(m|w! zNDq-FB3(q<i1ZO@B+^Nwl}Im<W_sFgBJD)_i8K`HDAH1-r$|$gt|Dzk`s!)Z@qcY> ee5LJgpv2yb13AI+-2B{<yn=$9V9}pX@xKE%a7T*( literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Efate b/libs/pytz/zoneinfo/Pacific/Efate new file mode 100644 index 0000000000000000000000000000000000000000..d650a056d9e7c734f886bb4a82d2256898140458 GIT binary patch literal 478 zcmWHE%1kq2zyQoZ5fBCeF(3x9c_w{5v_<mL_X|>oZXJ-mm3Bd9(Vhdcv%dw%HO)I9 zUwtD$A$zxjV)U*6CGWWo%GPrNRJ7|IRHf?z)VLEJ)P5%fsK0i0(0EdFL9;Q?LF=W* z1?@y*hx-5j|1&XSflLexbI*XRUvYqug@Iw#1_llv-w+0ELn9zI1W5ov2nntSTJ#^} z1CTzDFF-WNCm<T+8xRfh5r_u)3Pgi^2BJZ}1JNKKf@qL0K{UvxAR6Rb5DoG%hz9u@ tM1y<|rh&c((V!3j(V$QO(V&n3(V)-((V!3k(UgS>mklt4>~swcxd1L<X6XO` literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Enderbury b/libs/pytz/zoneinfo/Pacific/Enderbury new file mode 100644 index 0000000000000000000000000000000000000000..80873503faeb389853e1bc3f04b71f5156b48a14 GIT binary patch literal 250 zcmWHE%1kq2zyK^j5fBCe7+atL$Po%-IiSyKxuO35|No3k%*_A)$IoG4`2RoLfq~`! z|I`2m4hDv87Z`Yad_x#?4UK@<kU`tf7-S9@gpgn#(2D<c=AkJdjUanLG{|lcO|Jc1 NHbB?d=^7ey0RWqlFUJ4? literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Fakaofo b/libs/pytz/zoneinfo/Pacific/Fakaofo new file mode 100644 index 0000000000000000000000000000000000000000..4fa169f3cc6cfbe9414982b9eef32eb7f6718251 GIT binary patch literal 212 zcmWHE%1kq2zyQoZ5fBCe7@Ma7$npC-b3^_A|Nj}8nEwBduV7&K|35W=frWu#+XV&= vAKwrLT|+|#Z9`*_Mj!|w!FZsl|Le>{J3#tC)_`adtm3i(T5G3kXv_rwq0K43 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Fiji b/libs/pytz/zoneinfo/Pacific/Fiji new file mode 100644 index 0000000000000000000000000000000000000000..61a669535f14d380929c8fed6708b5ca9f8d5bae GIT binary patch literal 1090 zcmciAO-R#m9LMp$D<Uk=>L7&Dq3EJ5w~1Mn)TPTF68j@Md-#f|%&Ex|)Acau&>^8q z#6svYqKC^MN`)X8BZwkMND!jnOCUj=k|2<3ec!)prwICuAFr|Thq2GwPo{<<-XCX! zeZu7&wafdhb2}cDTHEWib!A=J_OwXb;(Lj1Ytm@3Mq=g963-mdMB%lZ*!f&LuNI|q z{hoHEUuoC7?2_+)694IlcDKBg?zx!uG^}b*HmJQnUuZH}t9>6H>ZyZkdivR{p4nX^ zXYY)u1iniDwd>OV@vRIDjmkiIUIvdRWU!Ez;l0f=Jo{Kvo3=|TeM?7HzezfJK}X-z z>R4m1ju+ST+?IBoxUsAgUm7$szMz@qLo%7Tt&>mcW$M7ZOx^q?({*{7KCd$KeMYh; z%W`q0PcB97%H_o)a;3hcx%@84{U~UzQmF*0{^=F)y!$2lAH9X8s*RrapyX|i#eJ_a z6tNrT3p@T_>|sBt!X`Y&E>7D9*~e)cAv-y3D`YQZGh{bpJ7hnnZHVmXv@MZ6owg~m ztJAhc_I29E$j->t$ll22$nMDY$o@zJNC!@90qMbMO(0!3tqr6Pr!|6f;<Q$fUXW&x zZjg46evpQcj-1vK(v#DgLb`HVTS#9{YYgekX{{l>A<ZG(A?+dkAq^rOI;}<Lf9kRN eu(jE-FBCQ-9Zm62C>RPho564}ygw9<RR0E(xg)Ls literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Funafuti b/libs/pytz/zoneinfo/Pacific/Funafuti new file mode 100644 index 0000000000000000000000000000000000000000..e6a154474bd8d6619556bbab19b0921f81a13d2b GIT binary patch literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m1H%dykkpC;3@kprAq?7v5HmtZFb!zX X|2i|CKOl4Po62PaG~Z6w(1;5FNf#M~ literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Galapagos b/libs/pytz/zoneinfo/Pacific/Galapagos new file mode 100644 index 0000000000000000000000000000000000000000..859b76d94d52a9979e757fa3d5869436458aec8a GIT binary patch literal 254 zcmWHE%1kq2zyK^j5fBCeRv-qkdA2R_X^@jR5}+;4^+3DuOF;eq|Nj}8nV6aX|6c=? z|NnpI1_l-o$p|D@FJR#C@eN_nH82HYGoU;Kgpgn%(ER@(OF=q87K3Pz<zO1<01!=< NBe-mUPO>xO0stI?I=lb? literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Gambier b/libs/pytz/zoneinfo/Pacific/Gambier new file mode 100644 index 0000000000000000000000000000000000000000..4e9e36c5a7edb3ebbe08ccc56308ac262df9f58f GIT binary patch literal 172 zcmWHE%1kq2zyM4@5fBCe7@K2CfCo$c|Ns9P8UO!ptYKgPk_8MbKE5Fgx(1eDr6DAk W1~lkD$V8Av{HAi*0L{0v<N^SLeIXVA literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Guadalcanal b/libs/pytz/zoneinfo/Pacific/Guadalcanal new file mode 100644 index 0000000000000000000000000000000000000000..908ccc144ef9e69da0c108dde21f8dade3203ff2 GIT binary patch literal 174 zcmWHE%1kq2zyM4@5fBCe7@K2?zj06f|Ns9P85tO+egR3%+Q7i#;~T=DZD<JMGK7#| Y8qlEsAQM6Q@SDnI12o@G*U*p)07Whw2LJ#7 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Guam b/libs/pytz/zoneinfo/Pacific/Guam new file mode 100644 index 0000000000000000000000000000000000000000..ffdf8c24b86220d1bc67908fa080a37f250544db GIT binary patch literal 216 zcmWHE%1kq2zyQoZ5fBCeCLji}c^iO)m2+GIBh&x?W+p%mL(c*R7BI=-;~T=@9vs5p zoB<>tAOyS7Kn);GU;r`}#OD1E1R@WQo&nKyX1YEgS%_84FuU5ffCTZ{$iTn_vR~KG GfC~UJHYHvF literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Honolulu b/libs/pytz/zoneinfo/Pacific/Honolulu new file mode 100644 index 0000000000000000000000000000000000000000..c7cd060159bd22fc5e6f10ac5a2089afb2c19c6a GIT binary patch literal 329 zcmWHE%1kq2zyNGO5fBCeb|40^MH+y_ZdPZH-HL?~r#o#=TvGm0a4FH#;%aZP2O|?B zGYcc@|Nl8m3=BXrf`R4#|Edf|4lv0BCI$ZgFHT@!@$n5|@CXKC7a$G?;(!pK!3+$H uP%?xBC;bP4k_QF*Ks3l{U>fK=5Dju7hz2<mOaq+?qN(g$E}&lw4Y&a7X=UL6 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Johnston b/libs/pytz/zoneinfo/Pacific/Johnston new file mode 100644 index 0000000000000000000000000000000000000000..c7cd060159bd22fc5e6f10ac5a2089afb2c19c6a GIT binary patch literal 329 zcmWHE%1kq2zyNGO5fBCeb|40^MH+y_ZdPZH-HL?~r#o#=TvGm0a4FH#;%aZP2O|?B zGYcc@|Nl8m3=BXrf`R4#|Edf|4lv0BCI$ZgFHT@!@$n5|@CXKC7a$G?;(!pK!3+$H uP%?xBC;bP4k_QF*Ks3l{U>fK=5Dju7hz2<mOaq+?qN(g$E}&lw4Y&a7X=UL6 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Kiritimati b/libs/pytz/zoneinfo/Pacific/Kiritimati new file mode 100644 index 0000000000000000000000000000000000000000..cf5b3bd348fcc7d6a39dd6b64b1658046444f71f GIT binary patch literal 254 zcmWHE%1kq2zyK^j5fBCe7+a_T$Po(t#Gucry`cX8|No3k%*_A)=KzKO|IY%d`~SZ< zfq{#G;m899J|Eu@23<n~69W**plxUZ)C&P2B-jYF<bR#HM*~PF$Yu}#*$$$~cLA3T L&^>m#h9+D9eoZjO literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Kosrae b/libs/pytz/zoneinfo/Pacific/Kosrae new file mode 100644 index 0000000000000000000000000000000000000000..b6bd4b089416a12b02a37eba8a1082dfa28b7797 GIT binary patch literal 242 zcmWHE%1kq2zyK^j5fBCe7@Ma7$obzU9bnd-?oj{#|9?g%Mn(pP8D~I>W^DkeTXBGa z1H$(44PnqWGz4OV=^-T81vLJDotcaYhz8jSvIAr<h^_|;k!m-W4bU}qx`u{a0Gwtk AUjP6A literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Kwajalein b/libs/pytz/zoneinfo/Pacific/Kwajalein new file mode 100644 index 0000000000000000000000000000000000000000..54bd71ff2dc7d7fc6f3ddb6e3e5ceed4c92b72f5 GIT binary patch literal 250 zcmWHE%1kq2zyK^j5fBCe7+atL$obzU9iUUP=Rp1c|Nj}8n3)+E<~#rjGtAn+!1Dip zxB~+R1H*~~3_L!*Aq?7vh77ufMnD>2LkJ1>0j>C7XC|QlqCxh8>;c&gqU(V|<k-(; L19XiY&{bRj+D$OG literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Majuro b/libs/pytz/zoneinfo/Pacific/Majuro new file mode 100644 index 0000000000000000000000000000000000000000..53f32886d08156820aaf860bcaedd3009c35f601 GIT binary patch literal 212 zcmWHE%1kq2zyQoZ5fBCe7@Ma7$obzU9Z>)O|9?g%CI*HDAQ6UH8yHv^7*-r$;PCMc rVbC@-1Y($xAtV?NH1&U-nMebO23Z3#yB^3S(kd<+ptW|ohDKZfj`Jm{ literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Marquesas b/libs/pytz/zoneinfo/Pacific/Marquesas new file mode 100644 index 0000000000000000000000000000000000000000..5fad0e1b201fb0cf49f3d2c27b117e9a029501a4 GIT binary patch literal 181 zcmWHE%1kq2zyM4@5fBCe7@KQKfR9K0|Ns9P8UO#UwP0ZQ|Gz4OfyKu+ghAK9(%1m3 cID`cAfJXfXnF-QJ$XqTPupM@mR>lTg0Jwo7!T<mO literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Midway b/libs/pytz/zoneinfo/Pacific/Midway new file mode 100644 index 0000000000000000000000000000000000000000..72707b5e15cccac9888e5ba22bb1b4e92170df5b GIT binary patch literal 187 zcmWHE%1kq2zyQoZ5fBCeCLji}IU0b(MAqLNj6ji%6$}jj|HuCTk*NU;EIz&=48g%6 cKouYmLV~IPfgsQJ1P6#F&U7xIMTUl40GV|pSO5S3 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Nauru b/libs/pytz/zoneinfo/Pacific/Nauru new file mode 100644 index 0000000000000000000000000000000000000000..7e7d920e11396d3b210f28c1d69389bc68d33548 GIT binary patch literal 268 zcmWHE%1kq2zyK^j5fBCeRv-qkg%&^8W;wlOzsCiqfC9m8mJ{{=|Nm!XVq|7!V3<<_ zQn_#kNMYRs1}+AM6$cpje0)O~v<(f74H&cyEP*V9MIj_u3$*1w$Z8M`vK~Z(oB*;B X<O~p94-_NQDO@%{huP^G8gT&t{~b3+ literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Niue b/libs/pytz/zoneinfo/Pacific/Niue new file mode 100644 index 0000000000000000000000000000000000000000..1d58fe36f47433fcde7743c626a8073f65dd331f GIT binary patch literal 257 zcmWHE%1kq2zyK^j5fBCe7+a(P$hqTenjlbe%A)@N|No3k%*_A)Cv0J00FnzBSpNTy z^I+im|35W=LBPj1ghAKP(8vHp8iPrYSzr)Cf}KEX{@0m@_<(4T-5>)%_JinppcuJs N;IaX_%g)e{3jo^+IynFU literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Norfolk b/libs/pytz/zoneinfo/Pacific/Norfolk new file mode 100644 index 0000000000000000000000000000000000000000..f630a65d5778d651b9d6842159d593c3cc18a996 GIT binary patch literal 314 zcmWHE%1kq2zyPd35fBCeHXsJEr5b?59mgLHocyOUIJe%62;+a2QUCw{e?}%|CKeV3 zhPf3$1q^c=7=Y}BI~cea7}lR)6kuSOwShs*$2WvQ+tAR^2t*ni07)Y-X$aB+1tBE3 t1!(R6Iy3PO5Djt@$Ow?Tz%<ZpAR6R85M2*cLoGLQ*#O;dr)y})1pov^I^+NV literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Noumea b/libs/pytz/zoneinfo/Pacific/Noumea new file mode 100644 index 0000000000000000000000000000000000000000..99f6bca20d23a1ea93182d86b066cabb764abc0b GIT binary patch literal 314 zcmWHE%1kq2zyPd35fBCe4j=}xc_w{5Qo{FaR{($5?gIkH<^%|uzjrWN;Qhf^!lj`8 z|Ns9?j6lfD!ot8XhX<r_#Q{bjd)5Y!2?(~2ZwQ07p%D-pf)p__f=GrC65Ii_>OaUW pAblYBfM}4LKs3l*AR6R05Dju4hz7Y4L{sWcE*qe`?Q{(dxd2h3I*kAT literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Pago_Pago b/libs/pytz/zoneinfo/Pacific/Pago_Pago new file mode 100644 index 0000000000000000000000000000000000000000..72707b5e15cccac9888e5ba22bb1b4e92170df5b GIT binary patch literal 187 zcmWHE%1kq2zyQoZ5fBCeCLji}IU0b(MAqLNj6ji%6$}jj|HuCTk*NU;EIz&=48g%6 cKouYmLV~IPfgsQJ1P6#F&U7xIMTUl40GV|pSO5S3 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Palau b/libs/pytz/zoneinfo/Pacific/Palau new file mode 100644 index 0000000000000000000000000000000000000000..968f1956c8eefeb78d96651051befba0b1a3ab82 GIT binary patch literal 173 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m14Eq%NUClE1B;Ju2!pnPC5X!qLV{^P XgZ|f<nY{t&!*42=4bXf$T}v(i_(&L^ literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Pitcairn b/libs/pytz/zoneinfo/Pacific/Pitcairn new file mode 100644 index 0000000000000000000000000000000000000000..9092e481678c18c41823cd24d370ad9623398b5c GIT binary patch literal 214 zcmWHE%1kq2zyQoZ5fBCe7@MyF$T4+s;;8@s|34!W)Bpc%JPZu~|94a{u>Aku-@w4- s;~T=DYhYn)03tzJfgpqg3xMYSuQS*C0@4Sv2t<=)8J7*vVmk{i0FU=74*&oF literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Pohnpei b/libs/pytz/zoneinfo/Pacific/Pohnpei new file mode 100644 index 0000000000000000000000000000000000000000..d3393a20d85209df94887e0de0c88e08442e4009 GIT binary patch literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m1H+UMkkqUV3@kprAq?7vh9E9O2nnVE Y4f<barmzO255K8gHbC?3bPWx;08pJ7fdBvi literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Ponape b/libs/pytz/zoneinfo/Pacific/Ponape new file mode 100644 index 0000000000000000000000000000000000000000..d3393a20d85209df94887e0de0c88e08442e4009 GIT binary patch literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m1H+UMkkqUV3@kprAq?7vh9E9O2nnVE Y4f<barmzO255K8gHbC?3bPWx;08pJ7fdBvi literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Port_Moresby b/libs/pytz/zoneinfo/Pacific/Port_Moresby new file mode 100644 index 0000000000000000000000000000000000000000..f6fd51cb93f23872e8809dd4b2e99ca444fac10e GIT binary patch literal 196 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m14HKzkW|kC1{NRR5C&~S0}z)X1iNX> zKq(Lg0T`PXXwd(#ZBZN`y6Eka2_VUOAe)g1Xd%S-&JPSMU`sebmIU}B*~GxWWdpR; JPS?<Y3jkR5BfJ0r literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Rarotonga b/libs/pytz/zoneinfo/Pacific/Rarotonga new file mode 100644 index 0000000000000000000000000000000000000000..9708b8707230d12731657b936499626858049654 GIT binary patch literal 593 zcmcK0u}Z^09LMo%EC?B-YAAo&)SlH)5ekV8S`-~bgrZvp>!b)$i<^VAgWAm(5N|v; zhpxUrr=p9GAWn7j3H<y29XN@DgBR{TM<9g%H$6B#SyDe%R^DJ^g|cxEuI0$}iwl#R zk2KZk>FM$v1<hj}aEJ2sZJi(88mb&w8eW)!w`B_tO;db}Y<RO~N`rAb(<+(r{kzpW zY-W4UwvvQ2*B<Hl+$TkiCmnsfQgyMft1kn(V&=@4_uJz!w^x;7KHFDW=JP}4Gk05= ziQT<a)slEQ#;Iy?U2fki{ll#Cd%j$04Dl^yhL{^-c8K{Q8aSl`L<^_%fN0{BE)Z>; u(g&guL??(=5WOIpL3D#?2hk6rAw)-rmQLvj(bOqj;eWLC*QNJH68m5DID0_= literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Saipan b/libs/pytz/zoneinfo/Pacific/Saipan new file mode 100644 index 0000000000000000000000000000000000000000..ffdf8c24b86220d1bc67908fa080a37f250544db GIT binary patch literal 216 zcmWHE%1kq2zyQoZ5fBCeCLji}c^iO)m2+GIBh&x?W+p%mL(c*R7BI=-;~T=@9vs5p zoB<>tAOyS7Kn);GU;r`}#OD1E1R@WQo&nKyX1YEgS%_84FuU5ffCTZ{$iTn_vR~KG GfC~UJHYHvF literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Samoa b/libs/pytz/zoneinfo/Pacific/Samoa new file mode 100644 index 0000000000000000000000000000000000000000..72707b5e15cccac9888e5ba22bb1b4e92170df5b GIT binary patch literal 187 zcmWHE%1kq2zyQoZ5fBCeCLji}IU0b(MAqLNj6ji%6$}jj|HuCTk*NU;EIz&=48g%6 cKouYmLV~IPfgsQJ1P6#F&U7xIMTUl40GV|pSO5S3 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Tahiti b/libs/pytz/zoneinfo/Pacific/Tahiti new file mode 100644 index 0000000000000000000000000000000000000000..37e4e883368265e85b7db406348aa4b25350f100 GIT binary patch literal 173 zcmWHE%1kq2zyM4@5fBCe7@K2CK<JM8|Ns9pGXDQxe1d@iNG33_`1pn}=o%V;m4=XD X8qlEsAQM3v@tewJ12o^x(0~g7G|wXF literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Tarawa b/libs/pytz/zoneinfo/Pacific/Tarawa new file mode 100644 index 0000000000000000000000000000000000000000..e23c0cd2cb4a1542809e253e0980646caae226d5 GIT binary patch literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m1H&Q{kkpC;3@kprAq?7v5HmtZFb!zX X|2i|FGaz&Do62PaG~Z6w(1;5FS@#*Q literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Tongatapu b/libs/pytz/zoneinfo/Pacific/Tongatapu new file mode 100644 index 0000000000000000000000000000000000000000..35c9e2c64642a3f2e17341e2d7f704b2bf5f156b GIT binary patch literal 384 zcmWHE%1kq2zyNGO5fBCeZXgD+g&Kgw$zrDo=D*!9SX{2XV8y}_U{$ok!TRy50Gps$ z4iU1~FGN%+T&Vy5|34!WGYcyd2r@A+tn~ouWLR6kz{0?=?E(WA1H+LAjC{y!AKwrL zZ9^j?10ZP(#3mpOjEo=>2tr73B+%~vb!O~4Ks3m)ApIZ*gJ_VWK{UwWU>fLn5DoGG ghz5BAM1wp6rh%RT(e*&HsOlju8=yDsbPbKU0LCg#(f|Me literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Truk b/libs/pytz/zoneinfo/Pacific/Truk new file mode 100644 index 0000000000000000000000000000000000000000..e79bca2dafa7e914a9baa568edec1b428a2ac63d GIT binary patch literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m14Ew=NUCQ61B;Ju2!pnv0f@^GLV{^P YgZ|f<scix2!*42=4bXf$T|)yd06J?KGXMYp literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Wake b/libs/pytz/zoneinfo/Pacific/Wake new file mode 100644 index 0000000000000000000000000000000000000000..837ce1f5c7f7ad25955108003ce9cd44ec9f2f1e GIT binary patch literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m1H&8>kkpC;3@kprAq?7v5HmtZFb!zX X|2i{?Gaz&Do62PaG~Z6w(1;5FSBDv? literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Wallis b/libs/pytz/zoneinfo/Pacific/Wallis new file mode 100644 index 0000000000000000000000000000000000000000..8be9ac4d3bbe8f54267e85eebc8b11e1d612c9a4 GIT binary patch literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m1H+mKkkpC;3@kprAq?7v5HmtZFb!zX X|2i{{6(Do)o62PaG~Z6w(1;5FY=ary literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Pacific/Yap b/libs/pytz/zoneinfo/Pacific/Yap new file mode 100644 index 0000000000000000000000000000000000000000..e79bca2dafa7e914a9baa568edec1b428a2ac63d GIT binary patch literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m14Ew=NUCQ61B;Ju2!pnv0f@^GLV{^P YgZ|f<scix2!*42=4bXf$T|)yd06J?KGXMYp literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Poland b/libs/pytz/zoneinfo/Poland new file mode 100644 index 0000000000000000000000000000000000000000..d6bb1561db672f94bfd5bccc95fe1a8d18a158d6 GIT binary patch literal 2696 zcmeIzdrXye9LMn=97!a#FWyiDFA=G9R1if)$c&E81oLvd;RQ5AEi$}BXcEtoxn|CN zbBOa+P{hzFxplN9I%l`hGQtZQHf!Zdab%d9wZ`oCeq3wK*47{W*YDYRUe7r@|32@J zKXX~`Fmu<r*Z#tXQ*A#yM>_Vly*jR8XUB-_osH*PcQw`M?#hGu+Iy<6mu%DW9fwTC z;-jXjXkB()!B=wP(j@t8PlVRLktUyS87>XZp6rH_!{+4HE%~Q5)@GkxbUXjdq!?{n zuwTv&3dlKcq<qn#Oqzm^Yg2QfT=t(bm#+n!=5O9|uAD4$TDp~)mc#FuANE$7t2?%u zAJ1f(*0s-@Yk?H=Q|26Vy|j<HkvzuSEJ}8Mj*K>);@a{%RnBlaztYj%S2EI()dQXI zoL){Bf0)xXBgu42Y;n5BTyT1Ht#=|k$DD}k2b`W4E1X`Zw>Xg>tao}JdD}$oD>u=* zUNwC-y=3~XTV?v?<(U5SW|;ox&$iy5?w6PppFH4AlGvyL@?giFG9V;P2izR41HX&a zL5)2$?xXhlP~aE!RyOP4((^i`<Wn8G`iREo?AL_(O)_j{KoV1HW%#r*84<l(l7<yZ zQd_Z%>Rqa%E-aMMzZGcm(KH$J<!nu<%F@)@WPNzUI32q)N*~FM(QzfC<<apWnwHaB z9*e!CzO*(OAM%M#i1}J3T>V}qdXCG)`Z{_1;+rz5X0N25IHnn!H_7CE75c>T<uYZ{ zdYw9JqfX0PtkXy4sXu*!&WM<-Grfa!=B;?0-F>{wKG#L(+#D#Ghi>TH#xR*z9xn3( zEwZ5ax@48sOLkVHEG)XBi^jeyPtHG~IeoXw;?x?=4Lzt!qE(k%-lj|2R_e04HTu*A zzdl_(SMxqzA<w*=s>`dU%d<=SYW{{1vSMnAtjvv&RSA7$weMGXF5F1L(C%8$`mGdp zzNLi?AIh4mO}h3#mAp`2tLwJEuSGSx^~E)nTD-YfgFL~Wb|LLT?`iJ|4zUlxcfRv@ z*To<I=JIq1`|mGfx*o8v68Cn-MD+^_HKwzePJexliw_Ft7t`a<`yc;I&+waB_LJtD z&dqOpJoxMbC&(UqbD!^g_y3Ex{I)$a4>e3d-ge}TceQUl^5!FNKT-gs0!Rsv8X!eL zs(_RMsRL37q!LIeTx~6oVj$H(%7N4aDF{*#q$Eg9kfONSsvu=?wRJ%XgH#47jjOE< zQXHf@NO_R@AO%7ygp>%W5mF?iN*v1MYU_j)ibJK4QX#cMiiK2*L%EQ8Aq7J!=4wlZ z)C?&aQZ=M(9O}lQa2zVfp>!N-$Dw$xwt7hUkoqA7L@J1s5UHW7Eh17yq>M-%kwPMs zL`sR&5-BE9O{APiJ&}SU6-7#l)YR1$6{)JLEh|!2q_9Y3k<ucyMT(157b!1NU!=fD zg^>~?HAaezRN2**8L6|YEi_VTq|`{Qkzym&M#_!U8!0$aairu(&5@!bRd=;zN9yis z3y)MDDLqnqr1(hnk@6$;M-~8C0b~h~H9!^tSp{Snkacji3xTWzvJ}W#Ad7*l2C^K; zdLRpetO&9s$eJLFf~*R%EXcaJ+J!+@#?>wjvNp)#AghBc53)YU0^$EF^iL}kW|wMk W0-NQ{NE|X^NW3>AAs&Y&hW!qIlp4DL literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Portugal b/libs/pytz/zoneinfo/Portugal new file mode 100644 index 0000000000000000000000000000000000000000..355817b52b1b05680bbb57e4dc8de358eff27a39 GIT binary patch literal 3469 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 Y{v0ZPotiXi*2uqcfM2Hof8Le;4LU9nuK)l5 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/ROC b/libs/pytz/zoneinfo/ROC new file mode 100644 index 0000000000000000000000000000000000000000..f9cbe672ab1aa9b6c47f7fed0a0ccd73845a10ec GIT binary patch literal 781 zcmci9Jt)L+9LMqRc|6B}$)YeQgUDnM<rwHp{?sX(l<nuCB+_p&U@>_Z6^k;GMK_?7 z+~DyJ=lS70|6*XcI=?qI3ya_N`@gP#x7_FLv~;wW$&Zt4-*7oa_VPVb+s8^%o!)Z% zdV92A?^Ms5-P!`#^99U)ML<F+?JAu6qQf^+<{(}uhwGc_=+v(xb7GFWy5(e{O`Q%5 z$=UdXId5Ik7rh(mvhqQ$O7qlpey7}I&#Gv`jE=sB%<V-;?shZO{aU(ySgbOSZ!vkA zTs6<(5^eh4RcfqR+>awFEi$Y<!DW+mIIXjth;k*k5`Xm(>5SU{BT7q>$l#c`dAc&b z-uN0E@isbAZ?Ct;;fLSH`NLpwdwPN<2N@0-4;c^{5g8I06B!g46&dzZJ1#OXGBPqW zGBz?eGCDFmGCmRjiGYMaVjw|~C`g!3Z5$*J5(x=~#6p50(U5RRJf<Wdhlof>BqkCR N|0Sx&wk|IBd;k)YUNHaw literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/ROK b/libs/pytz/zoneinfo/ROK new file mode 100644 index 0000000000000000000000000000000000000000..fa1cbd3952c362552f524061301b11f03770f335 GIT binary patch literal 517 zcmWHE%1kq2zyNGO5fBCeQ6L7f1-h?)sF?Ij?8hZ%^$(ByH9tJb-u~mMutdhQB<70e zf<7-^=oeJHc>X2h6@O60tK-))UcWb~c(Z&*#@q8^74O<-WqdF#tWa2-FhMadeS%W6 z(*$Kd&k2l7%#18ZkeL+-85qhrKsJ|mFt9K%)J<Rj@_;0e-8zGjhk>DI0V9u(ZwP~T za0r7J5PQ3XfRw{Q2nnA04+J2OfoPEDKs3mMAR6RJ5DoGuhz5BUM1wpGqCuVp(IAh5 zX`tspG$;VTG%yfAG$<fIG$=4YG$=qoG$>F&G$>#|jt2z}hz11^hz11`h^ARUaREb6 H*OChW=SsNu literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Singapore b/libs/pytz/zoneinfo/Singapore new file mode 100644 index 0000000000000000000000000000000000000000..ebc4b0d9d2ade7381506bc35d054f361f8a942d9 GIT binary patch literal 415 zcmWHE%1kq2zyKUT5fBCeP9O%c6&ip<TXXB;UFm)k4sbn5IJNm{!s&_e9G5(DKZs5I z%2EIS|9?g%W)@a9R(5s<hQwH)dJxINz>rh`G9q1pkq1OF0Ljb>1|bH9x(N&t3=9Pg z3^G2xAq>GltZiTp!bS!l(ilWq0<i_iC=?Jvg2RFC`d?=jyck4-JOH9Wo&eDxkAP^9 hXFxQ_Lm(RDDUgLAkAdiVpcvge$7KWbvYoC47XV%lV9Nji literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Turkey b/libs/pytz/zoneinfo/Turkey new file mode 100644 index 0000000000000000000000000000000000000000..833d4eba3985c0221daf6789c9ac30266bccacd8 GIT binary patch literal 2157 zcmd_qe`u6-0LSs??shwGZuRtI&13UN&UvR(o7-A9o0~PyF}<xPJtx?Nhn%J;<69O~ z20PjiOu<Jo94ut&TC&6-CD2N56fv8vC!OWmMo)x+2>LBrQNyn9`-!0NpX%Sf56``B z&&K{Z?(<Hp-@3U}yX{O>Pq;bl>gIhYsa~u-6P;bzd1lw;v-Xiq8=a#&Up9Iglja*W zr|jOMu=8fP&F+gt%)W1K*>C-_+d2N>Vq>7~hB=V#x6iMfFh3be8iRu;%nMihjnALH z?p)j!G%g*fG>2=w#^rd9^ToWK_Ls#soh!i&_K5eOIdZMW{_5s2bM#`fF*a<Q<0tpo z-~V{p{GsKHJ+URz{Ap&?*1FF)o^2;>Z}N4=x9T-JW9w<tU;d7f+4`yoR31wPCZE%S zq2BJ`=n^MO2JCEo$jly@W90Nyns@Z>H}34nH}zdRjJu!eOx@E^q1{{eVk)<AJb7QZ zIhE%-mz)-;PK9<KFhXMoQUxDhvZr_D8Z-9pkTYLuk+Zh1mG?KVkh9|t$-=6LoU<S# ziwd&j+^Lf?><^3Z#557P8Wi(}e--mTx-N=Ojfn+4pNfTh+U25-x5Wd`c8ijh{bKQk z_hsob2W8pPL$ZAJ3-ZC~Nm)_)gsiyrq>Sc2FQVhiW##00vE+lf^5M}cQPo>3mcBPb zRPQPhHC>umwmBdk=_rto#;%Fljlap|MS~(%>&RGVsk6d=-l{A7TCcnDonG(j*XxG{ z^p)Qp)mNS9)8iM;Sq=Nft;XYrt;bqhbz^V4_4vA1tkv5$S!<$a^+deRTASBsHB}$7 zntm>^)_u0fXiKNl9-sHWTp9j9FRw2%@J}z_l;CZb->+%;5%rDK^0#Oinl``0Gey%1 zW@$N^7G37Kiziy{-=F{WZ}@GzA)(c)I~H5ROF}CyDOYzH|5Y82I)A)#f6x;DVk_z+ zN;kbba0S^6vKv>m9b`YqhL9a0TSE4PYzo<xtJ)T_FJxoL&Ro^jki8+BLw1L357{5G zL1c%>7Lh$7n?!brY!lh1tJ)~CQ)H{iUXjfryG6E(>=)e)^FgZ}16xM+jBFa&HL`7F z-^j*Y)y_F=-Bs-!**vm)SG9d)|40Ln4j?T+dVn;6Ll=-XAbmg@fph|Cg{$fX(hQ^< zNIQ^zAPqq}g0uwb3DOj#D@a?AzPPH!Ae}*4gY*Vz4$>W@JxG6$1|c0nT7>inX%f;U zq)kYlTvelxPPwX9A-zJHg>(yP7t$}JVMxc2mLWYunuc@@X&cfvSJgPAb4cr4Rqv4I zxvK6V?L+#9G!W?^(n6$%NE4ARB5g$ah%^%EB+^P()k~zA*rvYg|Hp1-RjH;{FD%RY E9a<Tx5dZ)H literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/UCT b/libs/pytz/zoneinfo/UCT new file mode 100644 index 0000000000000000000000000000000000000000..a88c4b665b3ec94711c735fc7593f460668958cd GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<U33UzuGD67I#|6}Gzy$z!Xa;ov literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/US/Alaska b/libs/pytz/zoneinfo/US/Alaska new file mode 100644 index 0000000000000000000000000000000000000000..9bbb2fd3b361ea8aa4c126d14df5fa370343a63f GIT binary patch literal 2371 zcmciCZA{fw0LSqQ0v8C15>k=qB=Y>=0R^EbF9-r60dl(ukxF8BSP2AU_(W1Vaw{?0 z9L`3^IkwuQo#|G(7TvVgCgupY9>$_{YbHzAdYVOYJKvM57d7AI|G)G99PW7g`??!i zp3HIl>j^WzaCr8a!#!oE`Hb$#^NlC`+&11+EPo#_Q!^(vxcqOdkdA>;SHO!YGO#<@ zHLJZu2Q@AC1=l9&kfKDNGdol}UtZ@6i<;75!xOIXAI|FAz8UpJe0f<$`i6bCpB$BU zym`hIb#PeTx#y_st}Xp?cFSH@bbY&wsc3WET~H_Iq^@?&UC^rMg)MQ#2G;7>^ysMA zAB*+;i-{_3e4)PQlvBkY3(@x;zN|!7fxNGGR4wq#mkFD`6AN>%%fyvuL{iMxGCA$2 zNS>M2so{G?>f~2CZK_SAkG!ul&cCEG2M_D4<D1o@o)@%ywMJ!omCWhLQH#r-mrLrR zRc>;#%***zEp@Jt`Ej#F{-qRIF#U_T|Ko7^z{KaGP$%gJ-#sZF+83&q9Xcdjty8*a z*E_1X`mA2wd{C7vdP|p<Y*VE_U65s&1ETEwX;~4uRa6`wk}Iz?iptkM(5pV{R#n@N z=!f5KP}PmQb<Kf7Ra@xQtGnV=U0j8BdmPIBN4oapUR0iM%jKGQzgY88nyjC>AR2}u z<YSYkMdPlk^6`-&v9@_kt{dzV>#M%kO?^ky6Pf4q2Jddw9I5rjGOyZrWxw_&S19i% zow~)Du3CmYdefyy_0)k5`Se(tc&6(SxmibuR?kw|)_+yB=gpJPwvLI8m}%KreN1%v z=jg8dbE<3dH{Cr~tL~8rz2(||wRP}4z3q!mwY}$cz2k&O^{nmH&kf|OfWTP+LBThB zLqeUm@O3yoyykHD{T=HaL4JR4TR^D&M%Z7X>^+9BBi8Tl-x&~Z?+L4_+>W9;a~?IP z#+-8gC@*n4>bX>!OHrk{nJ0h`&tDh!e{U_^`~!#Q6?3?!_|3EI)b&qsM_*AnvOQ#f zR<l85hiJFRg+20^O#-__wu$T$*(kD8WUI(tt!A^xZmnj!$bOLxBRfX6jO-cNG_q@C z+sM9=jUzipwvOx_**vm)Wc$eet)>B1(*dLfNDq)EAYDM(fb;=r1kwql6-Y0TW+2@_ z+F>>QKpJ8-9YI=x^aN=N(iNmFNMDe~Ae}*4gY*Vz4$>W@JxG6$23bvqkQO05LYjnh z32773C!|qGr;t`5y+WFWbPH*h)$|K#nALO)X_?jZ3~3tDHKc7w-;l;3okLoO^bTnr z(mkYoR?|PEfmYK&q=i<~L!^mF7m+q1eMB0GbP{PL(o3Y7NH>voBK<@fYBe22T52^t zMVe|gT}9f8^c86=(pjXnNN<tmBHcyWi}V+1u+?-JX|dJx7-_QAbQx(g(r2X6NT-oj zBfZ8O%?=6-4!POu3=6%5@88kx{$JDmPrGm2!ijnTdC#a?oRyO$Gpe$)v$C^f_@5Pu BZASnA literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/US/Aleutian b/libs/pytz/zoneinfo/US/Aleutian new file mode 100644 index 0000000000000000000000000000000000000000..43236498f681cc06f64ca2afa613880331fe6fbb GIT binary patch literal 2356 zcmciCZ%ma{0LSsm-|Iy&Dj-Bm!Hl7RhrdBt9tc86<UwwrWC#iolmVg)3ox8hdC|nN zoE0&9(I3~SV{FZ&u`@U43+KAv*1#M!H|MM|UA1J6yq)jK){C0&@;rN<&)MC5`}=yU zn_f<L{p)zlFT9+7^Ky@W%Y4rF75FBW|JFKD=g8X=FQ_}G+8qC<Ug<hk;RGDYmVupF zPEgxM9b8xL3n|akp?MiTcUrV|zrDlfiI~-%;p<M=%}aXzk5j${Q@3Qe9`!B!dP+WU zV$z9tcT_&uciMSq&j<41ra>oi^IjQM+~Y*&*2zbbYMq#bZoSBp@5Baf)v>D*mc{<! z=*3quRNO?mUUDW%J^E#&Ui#rJwXCB^#`jLCgvunjy!m(WSoVCmqGVD$9yKEqSDqG$ zeveKH8x%>?KkJo0^@vqt7j*K)_f*Qz7dmyMORerXqQyXsN^AUFrngI#QPeLpD-u*z z;!c^J5v-nYdu2{syvVthEpz9B#FOV@<Wt{Y6>C(cetPtrc&0yEuYLc7kS()1Z~s}9 zUv^19TmOkFSpAJIEa+2(zuu5VDIbfXi{r95{E#Rf8IdJ3&EomNZ}s}`4ye+ulX}Bf zuc)#u1KK%SqRQ9o)*CyLRYhEt_Es)b-nm>|nRQcDUagdymWGQ>XLID{J2yo2N3rt7 z>2a}T|D1ejY(&)5Ps^=C?}*yc+q&-HNwqEIvfkb}pz6cNbVJc@)i85hHzro8#tZv& zlRH;64cF`DYm3#ZM|<UKz8tZmW4nA^#fp~7LfLwFPPAnw%AGCKqCMIpca>?e%fCW* z<Xl!AKe%;g%$VvNyRP@l9#?M+o!4(p?o(Yo!@B!az3QnstoI&!P6Y%81q6rO>j|Cb zzK@T~_1P7d%kOV+T)}>Sdu_lx`(0pviLm!bzOER*zqd7DiM=mcU+Q&js4#Dpc^$7S z-`w*Hyso@;=CaOQ%n9Jb`Rn5S?~#R>Kk#ynn3sFJ-<-8){usyZgVi<2=#b%A&G?W3 zq8%X@hR88v1O|zW5*a2kPGq3SNRgph%~+AaTFq#Y;UeQj28@gt88R|vWYEZ{kzpg_ zMh1?I92q(?c4Y9#=#k-D&G@Y*07wLo5Fjx?f`CK;2?G)bBoIg>kWe78K!Slp!)n5T z#KUR=f<y!f2@(?|C`eS0upn_k0)s>b2@Mh(BsfTPknkY!v6=uO5kf+Q#0Uuz5+x)| zNSu&BA(28tg~SR877{J12^SJCs|gqqF{=p~5;G)dNYs$9A#p<jheQqu9TGbvcu4fD zCVWWztR{d+1g$27NDPr6B2h%bh{O>IBoav^lt?U*U?R~(!imJwY66Nx)M`SC#MEkn zibNF&D-u^Eut;Q)&?2!#f{R2K2`>^~s|hd?VXFx-5@V|gG7@DZ%t)M(KqHYxLXCH0 z9UK@EdauXrnRg$bziVB+?f+@^KheH>3o}7a6Q=0Nr5UN|sUo>FEiE-IRfPQs4Q6_I literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/US/Arizona b/libs/pytz/zoneinfo/US/Arizona new file mode 100644 index 0000000000000000000000000000000000000000..4d51271a14ab85c1aac83f7145b9f7f8f19c512a GIT binary patch literal 344 zcmWHE%1kq2zyK^j5fBCeZXgD+1sZ_Fyk%As=I>^2SkNXjVd1Qo4W~PKCY%?)FLS>C z>6#0TQZm1OlnVTQ5y8O32!zZ)$jJ2n|Fm}u4FCVHUckum|Nq<x3>;uKkB@H%gRct^ z2Lo|<2+(i{2qD2q|A8Qmg=YhZ200BxgPaGVK~4nGAZLPTkW)c4$hlw|=wuKLayEzt PIUPh(=zK1qf6Tc6=)Qk) literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/US/Central b/libs/pytz/zoneinfo/US/Central new file mode 100644 index 0000000000000000000000000000000000000000..a5b1617c7f70bfc77b7d504aaa3f23603082c3cb GIT binary patch literal 3576 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 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/US/East-Indiana b/libs/pytz/zoneinfo/US/East-Indiana new file mode 100644 index 0000000000000000000000000000000000000000..09511ccdcf97a5baa8e1b0eb75e040eee6b6e0c4 GIT binary patch literal 1666 zcmdVaT};kV0LStFL*AOuCUenZx^UBrvdmhxOs$2dygYfS)X7^*(Gj&G`CoXy!A)V7 zj1gwph`4Am!&tLPDN)B;GiE#Fg4v$G^F7?Tvbk}do&V?GbJNZ5`vh`JHYPfMoH6Db zE@z#&yhpm`(ReP#J$385Y}z-$J$<5IK3qA&eb}2J9~}s~PolrdCq?6QSLLwtH1(tI z&gph~rg!RRNjIEcr$zTg9C!NEQT;sF>h^bR(=P@Z+?N-Q$bt46ckp0^RE>G=tCE0x zT{q8tlQ~DeEtuxM|1w2?F#kQ+7OB1So^l$3+PD9eN{g?O>1hi@`f#((h%HnZU59jL z*nE|FwM;Mk6s;DWJSZ3UqzZp+sm!`QLuBXs<&ydku{0%KE~^|8%Ok^OAm@Py{1}!i zk}irB?<VS1QTNoUyPx&yV6)0S+okgc4ypV-t$Iy+nJQS{pbHzbl<;4ZMf*#|+Sq!z zuGlZuhgHiB8S!Gnr(9V)Gh7sRrpS`f!=mJJl-xAbElTT?b=l+3YI9Yj-qO;g%5#ER z9&S}zla#I~Z&2GJ?&$5=HEMfsP*%;Y7gYndW%bl*QQdw<)_ltqI~w=OoxLfdwys$2 zYKsze1(|a9F-MH>+0V$3-!H%Z{Pi3)V$|q=@bSEsWXJKmn^$}xo_DFq8EfCi+vg;n z&ScNK-{G6O*dK5fq?x<i+?D1o2{`HIJ>7iA@!2N?{$g&PIRztwO~~w!=^^t&CWy?? zYNm+H5t*db%o3R<GEZcp$V`!`B6CG1Yc;b)ri;uMnJ_YAWXi~#kx3)7My8F-8<{vV zb7bmh=gte0=a|_8(?{lyBw#feASqZ)4oDJKlLe9nk_VCqk_nOuk_(ayk`0m$k`I!Q z)ntUEWHmV<Nm)%+NLol<NMcB4NNPxKNODMaNP0+qNP<X)NQzdIBa)=mWQn9{HF+Y5 zBAFtoBDo^TBH1G8BKaZ-BN-zpTTRYL(pHl-lD5_4jU<j_j--y{jwFv{kN<J{q2?DM Y$^0V3_-Dr@#?6ZHCnUrr#LWu*39tolUH||9 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/US/Eastern b/libs/pytz/zoneinfo/US/Eastern new file mode 100644 index 0000000000000000000000000000000000000000..2f75480e069b60b6c58a9137c7eebd4796f74226 GIT binary patch literal 3536 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 EZ-WKA#Q*>R literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/US/Hawaii b/libs/pytz/zoneinfo/US/Hawaii new file mode 100644 index 0000000000000000000000000000000000000000..c7cd060159bd22fc5e6f10ac5a2089afb2c19c6a GIT binary patch literal 329 zcmWHE%1kq2zyNGO5fBCeb|40^MH+y_ZdPZH-HL?~r#o#=TvGm0a4FH#;%aZP2O|?B zGYcc@|Nl8m3=BXrf`R4#|Edf|4lv0BCI$ZgFHT@!@$n5|@CXKC7a$G?;(!pK!3+$H uP%?xBC;bP4k_QF*Ks3l{U>fK=5Dju7hz2<mOaq+?qN(g$E}&lw4Y&a7X=UL6 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/US/Indiana-Starke b/libs/pytz/zoneinfo/US/Indiana-Starke new file mode 100644 index 0000000000000000000000000000000000000000..fcd408d74df43310a9a85c475f83d545f6d75911 GIT binary patch literal 2428 zcmd_rQB2ik7{~Dkfe-{G6GJjEt(buXHk3Bl+S0K@BA5qIA&k@z%Y0QJQKQ$bL0&XV zn~G})i(IZ5T2rwtG^N&R&d?-CCBP(SA+N>-DV@{%eY?zBUH7p6`TTcudiDF_T~hY^ zO!>=&*l&2aJ@(-}THBBMeTjPSC%>tNnz6cZ&jt1M>pp#U+K@V15^B!potKU&r_Fb% zN2ODmO;=Q%boIPtzV{v07f!4<7rS@qOZ(qc-K|ynhpp>WPko{8E%U0r>I{9^GfVwg z9H*}oq?`WCbops^thpK=D_3t$G}r9^eyx4j{M_FszjU;jfiK$R`te>h*xaMd-c#zv zwv&2jX|1|7Tq?J(ddx_tM}Ge@!T4Gd#Q%PTk=+pzP&;Twy*wy^Yr|Dg$rv4+dtKf2 z#DES-{ziqo5wAldKT@Fw-jy)(wi?s3Lx*=AG!Z8%^w?wD&A9#BC9<yE+`YA2##iN= zd&=@<!s0X&<w=u?kH?sMr^iV2)Y)p%=n;t-HA%(XjMn${-d2;_Z|VC#yQE?dUDR=n z$JLa|aq_^HMm06>hD=-asd+H<oII4Z*E}3`SmGbqV&Z-6dV1J0Gw0DtHFwSeHTTz} zk~w3w$vjslo`@Xd`FN9L4WyW--r1$+b<9`Uo2&HvBgrbKs8Hwb9IqCnXXvLZhSb8z zaoU^Lp}ZpjIzP2V<zI=FMX}$SMW2f-_8l=xn);-$d$%citxcY3-DrxJ?~|qVMdsP; zle(m~N<BBDNiQocRLdi3^oq<3wPIkUE{%^<rKhuWSxA5?JCLYX^<P#m?DWWsXZ&V$ zWrDoa+-uh4M~K>X%B)Qtlyz&~GwY+;r97wBl=}vBWm=P}>^`G6MAxVdt%r2g@Jh9@ zeuv)FnWZ*YSLjz-5><6^fqr%OST!oZ{saa&c)jya@ZWrY=fCZ~4gQBe`&a*(-~ZuP zB7Xm|g8@N){|5~++P#On&qzLH!k^#I%l68XbL_LwJ_Yv4^~zlP&IPzn@cxJO`Rx@4 z`WlcGB1=Tph%6FWC9+JXT_>_oWTnVbk+mX=b=uV;%SG0UEEriavSeh<$fA)|Bg;nC zjVv5lIkI$Q?a1PtcJ;{eop$|50gwtHB|vI`6alFMQU;_BNFk6)Af-TRfvy<5Pz}zO zgQFfuK{zUclmw{>QWT^rPFohFE>2q*j>;gVL282(2dNHH9*+7T1>&d>QX-BTAw}Y- z5>h6PIw6JPsFc%|3aJ%RETmdUxsZAx1>>j~QZkO3Aw}b;8d5fnx;bs(kjf#YLu%)= z#p9@+)0U5;eok9JjtU|rL~4i>5vd|lMx>5NA(2WVr9^7!w8ccK>9pnKsHf8wl%t|Z zNjYkY6qTc@NLe}RiWC;9EK*vewn%Z2>N;(Ck@`AqfsqP3ZHbW@BSq$@GE!!aIwOVV zs5DY)j#?wd=BT#QmK&+J(-s`5xYL##sX0<~r0Pi7k-8&=$NyL5!|X4CS@xGfV)kQ6 RGn0}Nvr|%%Qj(Ix{s3RXO|k$0 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/US/Michigan b/libs/pytz/zoneinfo/US/Michigan new file mode 100644 index 0000000000000000000000000000000000000000..5e0226057ac5e154901b0debb90547c096f9083a GIT binary patch literal 2174 zcmdtiUrg0y9LMn=L|_L<C@EB;f{cOux1(rO7_ie(kC+}z(e%;4vm+GW)Xty`Ye9xv zb4`c28q2gswMJ^TT*cI!wK+3omHin$wZ%n?dW<ewY@D9=v+la;uJt>6p4ZvA`Q4n) zJKPlARO$WWNw&XmczW&Odv?!9d29Ap@Ab|;XXIl3?{ZO1=&$?(=8|_nC)Zq-l=4$5 z<@xDyO~xVR^A3v7JgZW5kEDJ5s!l%<k!z24>#1)%V>0${(wV(2=DN=N^!3qznYOw} zX9Ww*4fE6VjfJTuJFieppE71<B&Mnvzxib5_hTyO!q0Nk$@41r@Mm)Kfy3&Sm}hQ% zdXLI${K4dJ9@Mw_Pn%hbUeLE^y>1GMw(5e(kEPJps0&A4lcJyI>Dfa&rFb~3O8TQx zdUUQT>sl=3d$LtUBw{MJ{Hf*yg659p-zk5=Y%{lVNX<)0H&rvg(N&|rn)wqS>IG*m zm^;7i*VTi+$Xy>irSIPTx!m*8MqSf>L>6}MQ1>?MmD=VFs;(?1^>wwXetf_LO4jSZ z@GcWfU#Npe+svY|e7*SPURm;GjS6jVm8I|HsfM*7S=N`N?yoMB<&TZ36*-v_Zv0e* zC&p!^|4p^>$Ejvj?is!6^cAyuazHm78a8W2cIma<$IOF6ZF*hvKC`}msaBzPWy8)^ zwXvj69*Trib9#rg1j<y)Sd&BwGF9YUwM3K0RrKv#**yA%YVBKK+Rk6m565!MBZI@b zy>ZgW?qm8<zcyRi_vx)!r_8p7PQ7jNvc#(TRBYs=bYyp^j-i9n`A3s_yuU}DxKypS zcSYpM_j6U(x}fZM(NhVDS0yE0{U7+m<40zBUOfKRD_&AOe*7J8N<99_iG(zFXSjRX zl2F*IT@m)`IS<&g%$~Y1e|j(B?>qc21`@XqBSD6Oj0G8t(~bri4l*8I#ek3zIqi^; zF(HF;+EF3HLdJy*3>g_RG-PbZ;E>TF!$Zc03=kP1GDKvI$RLqXI_)r#aUugnMv4p- z87neaWVFa|k?|q}Mn;Sb85y(F4jLJ?(+(RMH!^T!<jByGu_J>=Mvn|189x#LBmzhX zkQg9AK%(HZVL;-51OkZ!5(*?1NHCCSAmKpbfdmAJ2oe$`CQcg^Bq~lD79=iC8yF-q zNNAAQAi+VRgM<f(4-z0GLP&^^7&&c_kSIBAn2<O*ZJ>}yA)!KIg#-(U77{KbUP!=@ zh#?_EVul0_iJH@f4T+o61`dgw(}oU-9TGewdPw+?_#pvAB8Y?#i6Ih1B#KTOMkJ0- y8%QLQP8&)jmPjy>Xd>Z6;)w(l|CbT<*~0p5S&Kt+N-Imti$fI^r4^;+zP|v~6Kewi literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/US/Mountain b/libs/pytz/zoneinfo/US/Mountain new file mode 100644 index 0000000000000000000000000000000000000000..5fbe26b1d93d1acb2561c390c1e097d07f1a262e GIT binary patch literal 2444 zcmdtjeN5F=9LMnkqQH%ZQ;8v<nU)CgtR#>b6-0<P2(NH8!YHnHS1a(Ln-=0RD8?U+ zvtrCL36!+fOng|gv7xTv+REl|Yg!BKxhxw!Y?8peo%dPwPk;4STVM9OuOIjS&-=Po z`_|@&f812_4G-6C9^R)b{@GWcUmFNlJ<liU-dDa?dprTXw{@E6E54}vI`*j#+N1RF zyx$s!>*B?gOursm&?$b8b?d7UesOi|Njd(VTTGm*mXq%nh`_OY8GIv2h@FWtq%9yq zpPH0YHYBL9x|w=v#e|wxIIhF9MpXC<xjIswP>}}?Nyq3Ob<M?I9d-V=h(6JxW8Uo* zv2XTB`ErZ6w*6Uo-Bypd-d8WDuPPC7rT5Ai`6=Rtlm#+=Zn2sf>5vJb$tvNO`8x57 zNR>1kp=X`^LCrpNN#EFeTFvp#k~i%*sOGK=%6aQP6gTI7E^k@(wwNFHo=i^FA~|qD zr#Lo>l#!D<^^!~6I=EM-oo!U<-OuTaBb6$%*{ic&TBNeQtuklR47IRitz1+&rgD?- zlegu3q85jz%DluYBJbNMnLmDB6rB1=-u~%;Skmv%cMR+nOFMqlckbFQ3L8GsceU<P zcbE6;d+N8TqRba{anTx8{Ogb`NpBJ*XZOp}=vq;Fq+Kq%Tqw$3eO)jAxJEgf+VuVJ zELG(-K3&l@M?J8lOjr6t)rzEa?OOSja!thQs@zkm>gzP=p8ch855>q;fg!QFZ&W@w zvR~A+4$FrI+eK~tQMsmjy?EGpM%T5qsYlWe>qoslRUh4{JtbwzbJ?%G$?3{_+O2)z zvC4O#K(G7eXSKeoT0V9rMm+A%mrooV6%AF1vaw@WY{;FI8yk*_O>r0G=JGDFIWVsM zd54vM<TJe`zEf=(Jg&En`PI|iz51DRZq?M>qPHC@P|dX-y?tkr3Jv-5Z%WwTuYY~@ z-y00>?i3;ze5)rU%)Dz6Vc(<dr(EuI31^XcR+y*SJQXgpA|XQThwERgFKDhdEUF(_ zA+khdjmRRARU*qo)@d~hMOKO|)oRv?EEZWUvRq`nR<mGa#mJJ8HKScLFRYp~%LdlX zv2bMN$kLIuBa25?Z#BzD)^9ZhKq`Qg0I2~-5s)fylmV#&M<I|(aFhb61xGQEYH*YT zsRvRJq#{;R5~L<bQIM)2WkKqK6b7jbQW~T-9K}JZ!%-fjK2}p8q(W9xBBVwfMMA2C zlnJR5QYfTSNU4xoA;m(fg_H}a7g8{!VpdZ!q-GpNL#oD6Hl%JGg+nUGQ97h{Nb!*B zA>~8rXEg;xDrhw&L~3X?MMSE|QAVVWNFk9*BBexXi4+s5CQ?qMo>o&(q@q?+QlzF< zQ&gm?9A!o7%28OPvK*yFYRgevq`F9Xk@_M9Mk;JIB}Qs&HAP0MY&B&@>WmZ`sWeBa zky>*U8>u!&xsiHv6db9z)s!5mxz!XMsk+sa9jQA~c%<@3>5<wa#mE15^&RHNV6pj8 VNOLaC$jQh`b7p5}WM^bK{s0D?lEVN1 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/US/Pacific b/libs/pytz/zoneinfo/US/Pacific new file mode 100644 index 0000000000000000000000000000000000000000..9dad4f4c75b373635ccbe634798f8d9e587e36c1 GIT binary patch literal 2836 zcmd_rX;76_9LMpCq6mtfOq2-iq$YxZfTFmRxecHqDoA36O9F#ws1Rxy(nOgx#-Gfk zjgDqbP8phGV_AeYIW>)C&^T@pSt6t2f|j^+Z|8g7_Ntdn&ok%woVoAs_m?@lATPo5 zkEetEg~RiiJ=}Yg*-zDbDdz3{A!447GFxB2F5j&SGj;v0Ev=hBKppiK&pB4MQ%-ol zl9RQfPBpwMKkxWZ8fw<cFY8{G#;OAOwP2~7E}bmDrOuGwb7JI7<WOl!o}|uppRSrC zqE&P25Opq~t2$Q~qRuy6Ru^_(S1pI?)Wyo<>QePZxx8$@x>9jOTGt$qtA!uSwYl%e zAL*~kpJSer>w`<AZQwR_quVUG*{NLJY<pJUYR*%)kLBvWzDZHueaYJQew6ZTiPU~C zbW!bAcGm5e4HW<R5vIfRAn7<Z&;-O?kbw2$O`!T-0(X9?gD&rq&W+Wk%kjf1xVF-C z{j^$j+wqZBuT`o$)`{-Esz}{guw3`Zo~c4oGj-1q!&R@yVLG&LhTIhxs>9kPN?7Yq zbNA_95?<HS^geJy`s{8q_iQ~Wx@3^P_n9xGZ&tAGx9EiGpLj{%H|cXVAmm3K5mluk zye%d&s7ysR{9vNaEl`7McAMz>Qi-YBU}E>olfk7=n79q&BtHKYolw+Yh9np3p&1<| zF(OM3OK6ti0ZBS3yn{+Q8>UCxI;%z=x~)f@{8o+L6>9F^|ABg-;-(q%#(MQ&;VCn= ze20unuQB5nz9bU{8#8gj5}A0lUMI)AsFLgV>eS%HDs|6hJ*j1?n*8P-Gv(+aNn5?q zO#Nhvq|aGlrfrIq>7%pFj1nao;iF9E%vQ;~-P>d({v=svM(SC8uBcgGhwE%_y_&t< zs~>LItLBt9>PKoetDJ=g_1vmeYF=7{nZI_UEQqN!kLItCg~8iQZgRHdwv?Ovh*6S% zIL{OW^p=91DP~cVPafNps}~;$S4&Eg_2boERhSj2msT{YWy3n_<%I`TQAmp}PT#JI zeSxMVsa8rF&YP8?+hk?UVY8~OT%N3|HcuVPlhvh_=IMPYQkqj_)@+HAc7FD4@9*IH z-+6t$$^jma&-a%2`TKkoWu8v%-o<^@l(bCGv<dcP*z=G*(=zQp+T-zapUi(z0-t?y z{KIOIA|O>j%7D}XDFjjpr!56i3#1rGHIQ;3^*{=OR0JsrQWK;oNL7%sAay|sgH#47 z4N@DYEe=v0r!5asAEZD?g^&^<HA0GnR0$~)QYWNPNTrZcA+<t^g;WbEm($h@DHu{Q zq-5x7#)YEs*s1|#L+XYU4yhbcI;3_;@tn4LNco($en<h43L+&$YKRmOsUlKFq>e}- zkxC+^L~4l?6R9RrPNbelL7lduNJ){JB1J{2ij)<pD^ggbvPfx>+9Jh8s*9A@Y3qv= z*l8<_lo+WoQe>pcNSTp3BZWpPjg%UxHBxM(+DN&PdLspQ+KMA3M{14~9jQ7}cBJk| z;gQNCrAKOy6d$QRQhukcKe7N$y8_4(IPDrBi-4>GvJA*NAPa%41hN#!S|E#otOl|i zPP-n+f;jDpAWP!3Yl18avMR{3AnSrG46-uF(jaStEDo|d$nqfTgDjBKt`M?BPP<0P zB023UA<KlU6S7dqN+C;ytQE3Y$Z8?Wg{&8{U{1SY$dWnjnjwqkw5x_J8?tW5!XYb% jEFH3T`2StJAUlLfb`Yb}hQubs#zm*a$H&IU#s&Qiukn{d literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/US/Samoa b/libs/pytz/zoneinfo/US/Samoa new file mode 100644 index 0000000000000000000000000000000000000000..72707b5e15cccac9888e5ba22bb1b4e92170df5b GIT binary patch literal 187 zcmWHE%1kq2zyQoZ5fBCeCLji}IU0b(MAqLNj6ji%6$}jj|HuCTk*NU;EIz&=48g%6 cKouYmLV~IPfgsQJ1P6#F&U7xIMTUl40GV|pSO5S3 literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/UTC b/libs/pytz/zoneinfo/UTC new file mode 100644 index 0000000000000000000000000000000000000000..5583f5b0c6e6949372648a7d75502e4d01b44931 GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<U2@P=uGD67I#|6}Gzy$z!n+A0N literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Universal b/libs/pytz/zoneinfo/Universal new file mode 100644 index 0000000000000000000000000000000000000000..5583f5b0c6e6949372648a7d75502e4d01b44931 GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<U2@P=uGD67I#|6}Gzy$z!n+A0N literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/W-SU b/libs/pytz/zoneinfo/W-SU new file mode 100644 index 0000000000000000000000000000000000000000..ddb3f4e99a1030f33b56fad986c8d9c16e59eb32 GIT binary patch literal 1535 zcmd_pOGs2v0Eh8&eK(rb!qglm&2)U$sA*0)IyEzjsUbNPSW%Qng3+OZ6j}@+wy8i0 zTv(<w>Y|s6YLnFvfkYNAS_B#d(ZT{b1Q9Ad&UZz$TD9&D_x_Go1;Ov{Z)$BR5`SH5 z^c!xj-TLO770{2~!?v;O6<<2~a%X1yzByZObnc(+f7>=aAe@1L@*#I{^@&i>RWve~ zaC~IY6&@P4`5GPslaD0WhbPu1O}P_eCL0pxR)vy2#ZM$pdfe;AuS}$j_ABe{Zk2lN zys}+9t=6AwR%vZ}Rr<jywV`gS$|%oP8}pM@rq!adV&|1T(k|^^lVtYC#6V8_(?HIf zIhp(Xv&_3cCG&%?WWm)Za#QC$x%o`LbToI%!b78~=v0p?cJ-+(dpcA}YCx419Z;p; zkE*hic3Jk$tDN&qa@*r9wSBT&mJfNP>yb@XbY;rQULoBr(Q-$pRqgamOV6<%%A5I8 z`aJJdRpcF6o$*Xn&%97I;XzgN`j*=Dp-a`?y`<{KZ_4`1CzZc0^@tH379J565g8R7 z6CJf8Dth5#iCy-ITe<9u<=^=89B&aK!>RufJR^iCykNxW^I6W7Jw}`mWo|?Nw{jgK zVewqmU?dA+O%tiVzt43T>5K2n+)F>t@7C4(MLl<;zP&sez51>dd5#j{^ZE6yUz(S} z(^$BcUYI8#{QuC`Pkrrs7#c%5Ls~<6Gu6!@-68EE{h8_pkq%9Di%5^Ax=Ex<q)q-* z`a~K<IyKd;BE2HbBHbeGBK;x_BON0xBRwNcBV8kHBYh){Bb_6yo9f<?=8^7Ab^A#F z$Oe!dAX`B8fNTQU1+oofAIL_KogiC5_F}3xgY3psZwJ{AvLR$g$d-^jA)7*Wg=`Di s7qT&AXUNu&y&;=Jc4w-$hwRT(ZxGobvPEQ%$R_cB-=#%wxuDqc3lb5KyZ`_I literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/WET b/libs/pytz/zoneinfo/WET new file mode 100644 index 0000000000000000000000000000000000000000..9b03a17f41153b8673c144b42dd3cb0adc3f4ba8 GIT binary patch literal 1873 zcmd7ST}+h)9LMoP#448b2jeAqONl@|!T|(@ATvBL3ozs;gd!=3Scrniz_ha#bFG;B zxiOndCoRTWv!fSS7hulU;Uwnfn6q?kx!%;dHgi^v&FA~vcGXorXJ?;h=i*%bzrR5J z=9XN~zn;gOU-<IubiTYlHDjnrQ6JB;XZrlIt^!3jXDB9+pqZ7+?Ag436q|NSaWk(e zKKwhy-~CnzH;(GL%Y&MAW}gzrPFT{BkR=~HY$^S+)Lrk}^KG3rdu@}=sR&qFvEN?s z7Fc>xhQ0W7f@MUMD&yWlW&WJ5xl?hPckT(zANfn(q2IM&;Cp5DT~v10n0(D&DW~D8 z<#vu(UdgB}T)E%!lMh)zZjTjA2W@fUfEHh0Z-o!KRdk}%UizU<#lwv%87<PwdzNa+ zr%8IHB~45F3hmXh2$eP`+OqUt<u9GKvWN>RPy5lz@BCuR!%x}r$#Hw_#^?5W=u4}Z zIH}6L{Z@74Gp%TEw>SDfP<8D`TG_TyHH+F5s3_N}_$saT&eiJsKGnsRS>2Twy?HOw z>c9R|Yo;P??O?PujNG<$!MoNtFllc!PTBh1H??8OH`dfJq2|;Bwy`9nmM4#EQ?j(_ zw@=hM-Kovvy?Xn4K<|99Ms4RiY|DowY9Fq#t*xorwkO{@s*A0&CEm8@q*#~#Z`%>= zvF_|xb^q~`^+erQ&!w~0d*`Zl9vilICr7pGz-jFc9nyQfLkjK<D$MhLeK<FsC<}YM z)*tZH`fCE7Fy{&Uhco35I92zLoO4A^7CBqwbdmE#P8d03<dl(fMo!w*Icwy!k@H4Q z96593)RA*XP98aX<n)pAM-o6XKvF<*K$1YRaCOo^@<0+nGC@*7azT<ovO&^8@<9?p zGD1>9azc_qvT}9OLh^ET5<@aWQbTe>l0&jX(nIn?5=1gYQbck@l0>pZ(nRufbrMA~ zb#+ojaz&CwvPIHG@<kFxGDcEHaz>IyvPRNI@^*C+M>2PHQb%$}l1H*f(ns<~CIFcM zWD1ZuKqdj11!NkKc|axtnF&{CDv-HACIgubWIB-fKqds45oAh`IYA}`nH6MOka<BS z2ALUGXKIkSadjpKnH^+$koiF-2$>;ditrJgqey3*e2kN~GA}DX%a@(wt<3T97Woif Cu$Y+u literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/Zulu b/libs/pytz/zoneinfo/Zulu new file mode 100644 index 0000000000000000000000000000000000000000..5583f5b0c6e6949372648a7d75502e4d01b44931 GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<U2@P=uGD67I#|6}Gzy$z!n+A0N literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/iso3166.tab b/libs/pytz/zoneinfo/iso3166.tab new file mode 100644 index 00000000..c2e0f8ea --- /dev/null +++ b/libs/pytz/zoneinfo/iso3166.tab @@ -0,0 +1,274 @@ +# ISO 3166 alpha-2 country codes +# +# This file is in the public domain, so clarified as of +# 2009-05-17 by Arthur David Olson. +# +# From Paul Eggert (2015-05-02): +# This file contains a table of two-letter country codes. Columns are +# separated by a single tab. Lines beginning with '#' are comments. +# All text uses UTF-8 encoding. The columns of the table are as follows: +# +# 1. ISO 3166-1 alpha-2 country code, current as of +# ISO 3166-1 N905 (2016-11-15). See: Updates on ISO 3166-1 +# http://isotc.iso.org/livelink/livelink/Open/16944257 +# 2. The usual English name for the coded region, +# chosen so that alphabetic sorting of subsets produces helpful lists. +# This is not the same as the English name in the ISO 3166 tables. +# +# The table is sorted by country code. +# +# This table is intended as an aid for users, to help them select time +# zone data appropriate for their practical needs. It is not intended +# to take or endorse any position on legal or territorial claims. +# +#country- +#code name of country, territory, area, or subdivision +AD Andorra +AE United Arab Emirates +AF Afghanistan +AG Antigua & Barbuda +AI Anguilla +AL Albania +AM Armenia +AO Angola +AQ Antarctica +AR Argentina +AS Samoa (American) +AT Austria +AU Australia +AW Aruba +AX Åland Islands +AZ Azerbaijan +BA Bosnia & Herzegovina +BB Barbados +BD Bangladesh +BE Belgium +BF Burkina Faso +BG Bulgaria +BH Bahrain +BI Burundi +BJ Benin +BL St Barthelemy +BM Bermuda +BN Brunei +BO Bolivia +BQ Caribbean NL +BR Brazil +BS Bahamas +BT Bhutan +BV Bouvet Island +BW Botswana +BY Belarus +BZ Belize +CA Canada +CC Cocos (Keeling) Islands +CD Congo (Dem. Rep.) +CF Central African Rep. +CG Congo (Rep.) +CH Switzerland +CI Côte d'Ivoire +CK Cook Islands +CL Chile +CM Cameroon +CN China +CO Colombia +CR Costa Rica +CU Cuba +CV Cape Verde +CW Curaçao +CX Christmas Island +CY Cyprus +CZ Czech Republic +DE Germany +DJ Djibouti +DK Denmark +DM Dominica +DO Dominican Republic +DZ Algeria +EC Ecuador +EE Estonia +EG Egypt +EH Western Sahara +ER Eritrea +ES Spain +ET Ethiopia +FI Finland +FJ Fiji +FK Falkland Islands +FM Micronesia +FO Faroe Islands +FR France +GA Gabon +GB Britain (UK) +GD Grenada +GE Georgia +GF French Guiana +GG Guernsey +GH Ghana +GI Gibraltar +GL Greenland +GM Gambia +GN Guinea +GP Guadeloupe +GQ Equatorial Guinea +GR Greece +GS South Georgia & the South Sandwich Islands +GT Guatemala +GU Guam +GW Guinea-Bissau +GY Guyana +HK Hong Kong +HM Heard Island & McDonald Islands +HN Honduras +HR Croatia +HT Haiti +HU Hungary +ID Indonesia +IE Ireland +IL Israel +IM Isle of Man +IN India +IO British Indian Ocean Territory +IQ Iraq +IR Iran +IS Iceland +IT Italy +JE Jersey +JM Jamaica +JO Jordan +JP Japan +KE Kenya +KG Kyrgyzstan +KH Cambodia +KI Kiribati +KM Comoros +KN St Kitts & Nevis +KP Korea (North) +KR Korea (South) +KW Kuwait +KY Cayman Islands +KZ Kazakhstan +LA Laos +LB Lebanon +LC St Lucia +LI Liechtenstein +LK Sri Lanka +LR Liberia +LS Lesotho +LT Lithuania +LU Luxembourg +LV Latvia +LY Libya +MA Morocco +MC Monaco +MD Moldova +ME Montenegro +MF St Martin (French) +MG Madagascar +MH Marshall Islands +MK Macedonia +ML Mali +MM Myanmar (Burma) +MN Mongolia +MO Macau +MP Northern Mariana Islands +MQ Martinique +MR Mauritania +MS Montserrat +MT Malta +MU Mauritius +MV Maldives +MW Malawi +MX Mexico +MY Malaysia +MZ Mozambique +NA Namibia +NC New Caledonia +NE Niger +NF Norfolk Island +NG Nigeria +NI Nicaragua +NL Netherlands +NO Norway +NP Nepal +NR Nauru +NU Niue +NZ New Zealand +OM Oman +PA Panama +PE Peru +PF French Polynesia +PG Papua New Guinea +PH Philippines +PK Pakistan +PL Poland +PM St Pierre & Miquelon +PN Pitcairn +PR Puerto Rico +PS Palestine +PT Portugal +PW Palau +PY Paraguay +QA Qatar +RE Réunion +RO Romania +RS Serbia +RU Russia +RW Rwanda +SA Saudi Arabia +SB Solomon Islands +SC Seychelles +SD Sudan +SE Sweden +SG Singapore +SH St Helena +SI Slovenia +SJ Svalbard & Jan Mayen +SK Slovakia +SL Sierra Leone +SM San Marino +SN Senegal +SO Somalia +SR Suriname +SS South Sudan +ST Sao Tome & Principe +SV El Salvador +SX St Maarten (Dutch) +SY Syria +SZ Swaziland +TC Turks & Caicos Is +TD Chad +TF French Southern & Antarctic Lands +TG Togo +TH Thailand +TJ Tajikistan +TK Tokelau +TL East Timor +TM Turkmenistan +TN Tunisia +TO Tonga +TR Turkey +TT Trinidad & Tobago +TV Tuvalu +TW Taiwan +TZ Tanzania +UA Ukraine +UG Uganda +UM US minor outlying islands +US United States +UY Uruguay +UZ Uzbekistan +VA Vatican City +VC St Vincent +VE Venezuela +VG Virgin Islands (UK) +VI Virgin Islands (US) +VN Vietnam +VU Vanuatu +WF Wallis & Futuna +WS Samoa (western) +YE Yemen +YT Mayotte +ZA South Africa +ZM Zambia +ZW Zimbabwe diff --git a/libs/pytz/zoneinfo/leapseconds b/libs/pytz/zoneinfo/leapseconds new file mode 100644 index 00000000..148aa8ee --- /dev/null +++ b/libs/pytz/zoneinfo/leapseconds @@ -0,0 +1,66 @@ +# Allowance for leap seconds added to each time zone file. + +# This file is in the public domain. + +# This file is generated automatically from the data in the public-domain +# leap-seconds.list file, which can be copied from +# <ftp://ftp.nist.gov/pub/time/leap-seconds.list> +# or <ftp://ftp.boulder.nist.gov/pub/time/leap-seconds.list> +# or <ftp://tycho.usno.navy.mil/pub/ntp/leap-seconds.list>. +# For more about leap-seconds.list, please see +# The NTP Timescale and Leap Seconds +# <https://www.eecis.udel.edu/~mills/leap.html>. + +# The International Earth Rotation and Reference Systems Service +# periodically uses leap seconds to keep UTC to within 0.9 s of UT1 +# (which measures the true angular orientation of the earth in space) +# and publishes leap second data in a copyrighted file +# <https://hpiers.obspm.fr/iers/bul/bulc/Leap_Second.dat>. +# See: Levine J. Coordinated Universal Time and the leap second. +# URSI Radio Sci Bull. 2016;89(4):30-6. doi:10.23919/URSIRSB.2016.7909995 +# <https://ieeexplore.ieee.org/document/7909995>. +# There were no leap seconds before 1972, because the official mechanism +# accounting for the discrepancy between atomic time and the earth's rotation +# did not exist. + +# The correction (+ or -) is made at the given time, so lines +# will typically look like: +# Leap YEAR MON DAY 23:59:60 + R/S +# or +# Leap YEAR MON DAY 23:59:59 - R/S + +# If the leap second is Rolling (R) the given time is local time (unused here). +Leap 1972 Jun 30 23:59:60 + S +Leap 1972 Dec 31 23:59:60 + S +Leap 1973 Dec 31 23:59:60 + S +Leap 1974 Dec 31 23:59:60 + S +Leap 1975 Dec 31 23:59:60 + S +Leap 1976 Dec 31 23:59:60 + S +Leap 1977 Dec 31 23:59:60 + S +Leap 1978 Dec 31 23:59:60 + S +Leap 1979 Dec 31 23:59:60 + S +Leap 1981 Jun 30 23:59:60 + S +Leap 1982 Jun 30 23:59:60 + S +Leap 1983 Jun 30 23:59:60 + S +Leap 1985 Jun 30 23:59:60 + S +Leap 1987 Dec 31 23:59:60 + S +Leap 1989 Dec 31 23:59:60 + S +Leap 1990 Dec 31 23:59:60 + S +Leap 1992 Jun 30 23:59:60 + S +Leap 1993 Jun 30 23:59:60 + S +Leap 1994 Jun 30 23:59:60 + S +Leap 1995 Dec 31 23:59:60 + S +Leap 1997 Jun 30 23:59:60 + S +Leap 1998 Dec 31 23:59:60 + S +Leap 2005 Dec 31 23:59:60 + S +Leap 2008 Dec 31 23:59:60 + S +Leap 2012 Jun 30 23:59:60 + S +Leap 2015 Jun 30 23:59:60 + S +Leap 2016 Dec 31 23:59:60 + S + +# POSIX timestamps for the data in this file: +#updated 1467936000 +#expires 1561680000 + +# Updated through IERS Bulletin C56 +# File expires on: 28 June 2019 diff --git a/libs/pytz/zoneinfo/posixrules b/libs/pytz/zoneinfo/posixrules new file mode 100644 index 0000000000000000000000000000000000000000..2f75480e069b60b6c58a9137c7eebd4796f74226 GIT binary patch literal 3536 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 EZ-WKA#Q*>R literal 0 HcmV?d00001 diff --git a/libs/pytz/zoneinfo/tzdata.zi b/libs/pytz/zoneinfo/tzdata.zi new file mode 100644 index 00000000..7eb8193b --- /dev/null +++ b/libs/pytz/zoneinfo/tzdata.zi @@ -0,0 +1,4177 @@ +# version unknown +# This zic input file is in the public domain. +R d 1916 o - Jun 14 23s 1 S +R d 1916 1919 - O Sun>=1 23s 0 - +R d 1917 o - Mar 24 23s 1 S +R d 1918 o - Mar 9 23s 1 S +R d 1919 o - Mar 1 23s 1 S +R d 1920 o - F 14 23s 1 S +R d 1920 o - O 23 23s 0 - +R d 1921 o - Mar 14 23s 1 S +R d 1921 o - Jun 21 23s 0 - +R d 1939 o - S 11 23s 1 S +R d 1939 o - N 19 1 0 - +R d 1944 1945 - Ap M>=1 2 1 S +R d 1944 o - O 8 2 0 - +R d 1945 o - S 16 1 0 - +R d 1971 o - Ap 25 23s 1 S +R d 1971 o - S 26 23s 0 - +R d 1977 o - May 6 0 1 S +R d 1977 o - O 21 0 0 - +R d 1978 o - Mar 24 1 1 S +R d 1978 o - S 22 3 0 - +R d 1980 o - Ap 25 0 1 S +R d 1980 o - O 31 2 0 - +Z Africa/Algiers 0:12:12 - LMT 1891 Mar 15 0:1 +0:9:21 - PMT 1911 Mar 11 +0 d WE%sT 1940 F 25 2 +1 d CE%sT 1946 O 7 +0 - WET 1956 Ja 29 +1 - CET 1963 Ap 14 +0 d WE%sT 1977 O 21 +1 d CE%sT 1979 O 26 +0 d WE%sT 1981 May +1 - CET +Z Atlantic/Cape_Verde -1:34:4 - LMT 1912 Ja 1 2u +-2 - -02 1942 S +-2 1 -01 1945 O 15 +-2 - -02 1975 N 25 2 +-1 - -01 +Z Africa/Ndjamena 1:0:12 - LMT 1912 +1 - WAT 1979 O 14 +1 1 WAST 1980 Mar 8 +1 - WAT +Z Africa/Abidjan -0:16:8 - LMT 1912 +0 - GMT +Li Africa/Abidjan Africa/Bamako +Li Africa/Abidjan Africa/Banjul +Li Africa/Abidjan Africa/Conakry +Li Africa/Abidjan Africa/Dakar +Li Africa/Abidjan Africa/Freetown +Li Africa/Abidjan Africa/Lome +Li Africa/Abidjan Africa/Nouakchott +Li Africa/Abidjan Africa/Ouagadougou +Li Africa/Abidjan Atlantic/St_Helena +R K 1940 o - Jul 15 0 1 S +R K 1940 o - O 1 0 0 - +R K 1941 o - Ap 15 0 1 S +R K 1941 o - S 16 0 0 - +R K 1942 1944 - Ap 1 0 1 S +R K 1942 o - O 27 0 0 - +R K 1943 1945 - N 1 0 0 - +R K 1945 o - Ap 16 0 1 S +R K 1957 o - May 10 0 1 S +R K 1957 1958 - O 1 0 0 - +R K 1958 o - May 1 0 1 S +R K 1959 1981 - May 1 1 1 S +R K 1959 1965 - S 30 3 0 - +R K 1966 1994 - O 1 3 0 - +R K 1982 o - Jul 25 1 1 S +R K 1983 o - Jul 12 1 1 S +R K 1984 1988 - May 1 1 1 S +R K 1989 o - May 6 1 1 S +R K 1990 1994 - May 1 1 1 S +R K 1995 2010 - Ap lastF 0s 1 S +R K 1995 2005 - S lastTh 24 0 - +R K 2006 o - S 21 24 0 - +R K 2007 o - S Th>=1 24 0 - +R K 2008 o - Au lastTh 24 0 - +R K 2009 o - Au 20 24 0 - +R K 2010 o - Au 10 24 0 - +R K 2010 o - S 9 24 1 S +R K 2010 o - S lastTh 24 0 - +R K 2014 o - May 15 24 1 S +R K 2014 o - Jun 26 24 0 - +R K 2014 o - Jul 31 24 1 S +R K 2014 o - S lastTh 24 0 - +Z Africa/Cairo 2:5:9 - LMT 1900 O +2 K EE%sT +R GH 1920 1942 - S 1 0 0:20 - +R GH 1920 1942 - D 31 0 0 - +Z Africa/Accra -0:0:52 - LMT 1918 +0 GH GMT/+0020 +Z Africa/Bissau -1:2:20 - LMT 1912 Ja 1 1u +-1 - -01 1975 +0 - GMT +Z Africa/Nairobi 2:27:16 - LMT 1928 Jul +3 - EAT 1930 +2:30 - +0230 1940 +2:45 - +0245 1960 +3 - EAT +Li Africa/Nairobi Africa/Addis_Ababa +Li Africa/Nairobi Africa/Asmara +Li Africa/Nairobi Africa/Dar_es_Salaam +Li Africa/Nairobi Africa/Djibouti +Li Africa/Nairobi Africa/Kampala +Li Africa/Nairobi Africa/Mogadishu +Li Africa/Nairobi Indian/Antananarivo +Li Africa/Nairobi Indian/Comoro +Li Africa/Nairobi Indian/Mayotte +Z Africa/Monrovia -0:43:8 - LMT 1882 +-0:43:8 - MMT 1919 Mar +-0:44:30 - MMT 1972 Ja 7 +0 - GMT +R L 1951 o - O 14 2 1 S +R L 1952 o - Ja 1 0 0 - +R L 1953 o - O 9 2 1 S +R L 1954 o - Ja 1 0 0 - +R L 1955 o - S 30 0 1 S +R L 1956 o - Ja 1 0 0 - +R L 1982 1984 - Ap 1 0 1 S +R L 1982 1985 - O 1 0 0 - +R L 1985 o - Ap 6 0 1 S +R L 1986 o - Ap 4 0 1 S +R L 1986 o - O 3 0 0 - +R L 1987 1989 - Ap 1 0 1 S +R L 1987 1989 - O 1 0 0 - +R L 1997 o - Ap 4 0 1 S +R L 1997 o - O 4 0 0 - +R L 2013 o - Mar lastF 1 1 S +R L 2013 o - O lastF 2 0 - +Z Africa/Tripoli 0:52:44 - LMT 1920 +1 L CE%sT 1959 +2 - EET 1982 +1 L CE%sT 1990 May 4 +2 - EET 1996 S 30 +1 L CE%sT 1997 O 4 +2 - EET 2012 N 10 2 +1 L CE%sT 2013 O 25 2 +2 - EET +R MU 1982 o - O 10 0 1 - +R MU 1983 o - Mar 21 0 0 - +R MU 2008 o - O lastSun 2 1 - +R MU 2009 o - Mar lastSun 2 0 - +Z Indian/Mauritius 3:50 - LMT 1907 +4 MU +04/+05 +R M 1939 o - S 12 0 1 - +R M 1939 o - N 19 0 0 - +R M 1940 o - F 25 0 1 - +R M 1945 o - N 18 0 0 - +R M 1950 o - Jun 11 0 1 - +R M 1950 o - O 29 0 0 - +R M 1967 o - Jun 3 12 1 - +R M 1967 o - O 1 0 0 - +R M 1974 o - Jun 24 0 1 - +R M 1974 o - S 1 0 0 - +R M 1976 1977 - May 1 0 1 - +R M 1976 o - Au 1 0 0 - +R M 1977 o - S 28 0 0 - +R M 1978 o - Jun 1 0 1 - +R M 1978 o - Au 4 0 0 - +R M 2008 o - Jun 1 0 1 - +R M 2008 o - S 1 0 0 - +R M 2009 o - Jun 1 0 1 - +R M 2009 o - Au 21 0 0 - +R M 2010 o - May 2 0 1 - +R M 2010 o - Au 8 0 0 - +R M 2011 o - Ap 3 0 1 - +R M 2011 o - Jul 31 0 0 - +R M 2012 2013 - Ap lastSun 2 1 - +R M 2012 o - Jul 20 3 0 - +R M 2012 o - Au 20 2 1 - +R M 2012 o - S 30 3 0 - +R M 2013 o - Jul 7 3 0 - +R M 2013 o - Au 10 2 1 - +R M 2013 2018 - O lastSun 3 0 - +R M 2014 2018 - Mar lastSun 2 1 - +R M 2014 o - Jun 28 3 0 - +R M 2014 o - Au 2 2 1 - +R M 2015 o - Jun 14 3 0 - +R M 2015 o - Jul 19 2 1 - +R M 2016 o - Jun 5 3 0 - +R M 2016 o - Jul 10 2 1 - +R M 2017 o - May 21 3 0 - +R M 2017 o - Jul 2 2 1 - +R M 2018 o - May 13 3 0 - +R M 2018 o - Jun 17 2 1 - +Z Africa/Casablanca -0:30:20 - LMT 1913 O 26 +0 M +00/+01 1984 Mar 16 +1 - +01 1986 +0 M +00/+01 2018 O 27 +1 - +01 +Z Africa/El_Aaiun -0:52:48 - LMT 1934 +-1 - -01 1976 Ap 14 +0 M +00/+01 2018 O 27 +1 - +01 +Z Africa/Maputo 2:10:20 - LMT 1903 Mar +2 - CAT +Li Africa/Maputo Africa/Blantyre +Li Africa/Maputo Africa/Bujumbura +Li Africa/Maputo Africa/Gaborone +Li Africa/Maputo Africa/Harare +Li Africa/Maputo Africa/Kigali +Li Africa/Maputo Africa/Lubumbashi +Li Africa/Maputo Africa/Lusaka +R NA 1994 o - Mar 21 0 -1 WAT +R NA 1994 2017 - S Sun>=1 2 0 CAT +R NA 1995 2017 - Ap Sun>=1 2 -1 WAT +Z Africa/Windhoek 1:8:24 - LMT 1892 F 8 +1:30 - +0130 1903 Mar +2 - SAST 1942 S 20 2 +2 1 SAST 1943 Mar 21 2 +2 - SAST 1990 Mar 21 +2 NA %s +Z Africa/Lagos 0:13:36 - LMT 1919 S +1 - WAT +Li Africa/Lagos Africa/Bangui +Li Africa/Lagos Africa/Brazzaville +Li Africa/Lagos Africa/Douala +Li Africa/Lagos Africa/Kinshasa +Li Africa/Lagos Africa/Libreville +Li Africa/Lagos Africa/Luanda +Li Africa/Lagos Africa/Malabo +Li Africa/Lagos Africa/Niamey +Li Africa/Lagos Africa/Porto-Novo +Z Indian/Reunion 3:41:52 - LMT 1911 Jun +4 - +04 +Z Africa/Sao_Tome 0:26:56 - LMT 1884 +-0:36:45 - LMT 1912 Ja 1 0u +0 - GMT 2018 Ja 1 1 +1 - WAT +Z Indian/Mahe 3:41:48 - LMT 1906 Jun +4 - +04 +R SA 1942 1943 - S Sun>=15 2 1 - +R SA 1943 1944 - Mar Sun>=15 2 0 - +Z Africa/Johannesburg 1:52 - LMT 1892 F 8 +1:30 - SAST 1903 Mar +2 SA SAST +Li Africa/Johannesburg Africa/Maseru +Li Africa/Johannesburg Africa/Mbabane +R SD 1970 o - May 1 0 1 S +R SD 1970 1985 - O 15 0 0 - +R SD 1971 o - Ap 30 0 1 S +R SD 1972 1985 - Ap lastSun 0 1 S +Z Africa/Khartoum 2:10:8 - LMT 1931 +2 SD CA%sT 2000 Ja 15 12 +3 - EAT 2017 N +2 - CAT +Z Africa/Juba 2:6:28 - LMT 1931 +2 SD CA%sT 2000 Ja 15 12 +3 - EAT +R n 1939 o - Ap 15 23s 1 S +R n 1939 o - N 18 23s 0 - +R n 1940 o - F 25 23s 1 S +R n 1941 o - O 6 0 0 - +R n 1942 o - Mar 9 0 1 S +R n 1942 o - N 2 3 0 - +R n 1943 o - Mar 29 2 1 S +R n 1943 o - Ap 17 2 0 - +R n 1943 o - Ap 25 2 1 S +R n 1943 o - O 4 2 0 - +R n 1944 1945 - Ap M>=1 2 1 S +R n 1944 o - O 8 0 0 - +R n 1945 o - S 16 0 0 - +R n 1977 o - Ap 30 0s 1 S +R n 1977 o - S 24 0s 0 - +R n 1978 o - May 1 0s 1 S +R n 1978 o - O 1 0s 0 - +R n 1988 o - Jun 1 0s 1 S +R n 1988 1990 - S lastSun 0s 0 - +R n 1989 o - Mar 26 0s 1 S +R n 1990 o - May 1 0s 1 S +R n 2005 o - May 1 0s 1 S +R n 2005 o - S 30 1s 0 - +R n 2006 2008 - Mar lastSun 2s 1 S +R n 2006 2008 - O lastSun 2s 0 - +Z Africa/Tunis 0:40:44 - LMT 1881 May 12 +0:9:21 - PMT 1911 Mar 11 +1 n CE%sT +Z Antarctica/Casey 0 - -00 1969 +8 - +08 2009 O 18 2 +11 - +11 2010 Mar 5 2 +8 - +08 2011 O 28 2 +11 - +11 2012 F 21 17u +8 - +08 2016 O 22 +11 - +11 2018 Mar 11 4 +8 - +08 +Z Antarctica/Davis 0 - -00 1957 Ja 13 +7 - +07 1964 N +0 - -00 1969 F +7 - +07 2009 O 18 2 +5 - +05 2010 Mar 10 20u +7 - +07 2011 O 28 2 +5 - +05 2012 F 21 20u +7 - +07 +Z Antarctica/Mawson 0 - -00 1954 F 13 +6 - +06 2009 O 18 2 +5 - +05 +Z Indian/Kerguelen 0 - -00 1950 +5 - +05 +Z Antarctica/DumontDUrville 0 - -00 1947 +10 - +10 1952 Ja 14 +0 - -00 1956 N +10 - +10 +Z Antarctica/Syowa 0 - -00 1957 Ja 29 +3 - +03 +R Tr 2005 ma - Mar lastSun 1u 2 +02 +R Tr 2004 ma - O lastSun 1u 0 +00 +Z Antarctica/Troll 0 - -00 2005 F 12 +0 Tr %s +Z Antarctica/Vostok 0 - -00 1957 D 16 +6 - +06 +Z Antarctica/Rothera 0 - -00 1976 D +-3 - -03 +Z Asia/Kabul 4:36:48 - LMT 1890 +4 - +04 1945 +4:30 - +0430 +R AM 2011 o - Mar lastSun 2s 1 - +R AM 2011 o - O lastSun 2s 0 - +Z Asia/Yerevan 2:58 - LMT 1924 May 2 +3 - +03 1957 Mar +4 R +04/+05 1991 Mar 31 2s +3 R +03/+04 1995 S 24 2s +4 - +04 1997 +4 R +04/+05 2011 +4 AM +04/+05 +R AZ 1997 2015 - Mar lastSun 4 1 - +R AZ 1997 2015 - O lastSun 5 0 - +Z Asia/Baku 3:19:24 - LMT 1924 May 2 +3 - +03 1957 Mar +4 R +04/+05 1991 Mar 31 2s +3 R +03/+04 1992 S lastSun 2s +4 - +04 1996 +4 E +04/+05 1997 +4 AZ +04/+05 +R BD 2009 o - Jun 19 23 1 - +R BD 2009 o - D 31 24 0 - +Z Asia/Dhaka 6:1:40 - LMT 1890 +5:53:20 - HMT 1941 O +6:30 - +0630 1942 May 15 +5:30 - +0530 1942 S +6:30 - +0630 1951 S 30 +6 - +06 2009 +6 BD +06/+07 +Z Asia/Thimphu 5:58:36 - LMT 1947 Au 15 +5:30 - +0530 1987 O +6 - +06 +Z Indian/Chagos 4:49:40 - LMT 1907 +5 - +05 1996 +6 - +06 +Z Asia/Brunei 7:39:40 - LMT 1926 Mar +7:30 - +0730 1933 +8 - +08 +Z Asia/Yangon 6:24:47 - LMT 1880 +6:24:47 - RMT 1920 +6:30 - +0630 1942 May +9 - +09 1945 May 3 +6:30 - +0630 +R Sh 1940 o - Jun 1 0 1 D +R Sh 1940 o - O 12 24 0 S +R Sh 1941 o - Mar 15 0 1 D +R Sh 1941 o - N 1 24 0 S +R Sh 1942 o - Ja 31 0 1 D +R Sh 1945 o - S 1 24 0 S +R Sh 1946 o - May 15 0 1 D +R Sh 1946 o - S 30 24 0 S +R Sh 1947 o - Ap 15 0 1 D +R Sh 1947 o - O 31 24 0 S +R Sh 1948 1949 - May 1 0 1 D +R Sh 1948 1949 - S 30 24 0 S +R CN 1986 o - May 4 2 1 D +R CN 1986 1991 - S Sun>=11 2 0 S +R CN 1987 1991 - Ap Sun>=11 2 1 D +Z Asia/Shanghai 8:5:43 - LMT 1901 +8 Sh C%sT 1949 May 28 +8 CN C%sT +Z Asia/Urumqi 5:50:20 - LMT 1928 +6 - +06 +R HK 1941 o - Ap 1 3:30 1 S +R HK 1941 o - S 30 3:30 0 - +R HK 1946 o - Ap 20 3:30 1 S +R HK 1946 o - D 1 3:30 0 - +R HK 1947 o - Ap 13 3:30 1 S +R HK 1947 o - D 30 3:30 0 - +R HK 1948 o - May 2 3:30 1 S +R HK 1948 1951 - O lastSun 3:30 0 - +R HK 1952 o - O 25 3:30 0 - +R HK 1949 1953 - Ap Sun>=1 3:30 1 S +R HK 1953 o - N 1 3:30 0 - +R HK 1954 1964 - Mar Sun>=18 3:30 1 S +R HK 1954 o - O 31 3:30 0 - +R HK 1955 1964 - N Sun>=1 3:30 0 - +R HK 1965 1976 - Ap Sun>=16 3:30 1 S +R HK 1965 1976 - O Sun>=16 3:30 0 - +R HK 1973 o - D 30 3:30 1 S +R HK 1979 o - May Sun>=8 3:30 1 S +R HK 1979 o - O Sun>=16 3:30 0 - +Z Asia/Hong_Kong 7:36:42 - LMT 1904 O 30 +8 HK HK%sT 1941 D 25 +9 - JST 1945 S 15 +8 HK HK%sT +R f 1946 o - May 15 0 1 D +R f 1946 o - O 1 0 0 S +R f 1947 o - Ap 15 0 1 D +R f 1947 o - N 1 0 0 S +R f 1948 1951 - May 1 0 1 D +R f 1948 1951 - O 1 0 0 S +R f 1952 o - Mar 1 0 1 D +R f 1952 1954 - N 1 0 0 S +R f 1953 1959 - Ap 1 0 1 D +R f 1955 1961 - O 1 0 0 S +R f 1960 1961 - Jun 1 0 1 D +R f 1974 1975 - Ap 1 0 1 D +R f 1974 1975 - O 1 0 0 S +R f 1979 o - Jul 1 0 1 D +R f 1979 o - O 1 0 0 S +Z Asia/Taipei 8:6 - LMT 1896 +8 - CST 1937 O +9 - JST 1945 S 21 1 +8 f C%sT +R _ 1942 1943 - Ap 30 23 1 - +R _ 1942 o - N 17 23 0 - +R _ 1943 o - S 30 23 0 S +R _ 1946 o - Ap 30 23s 1 D +R _ 1946 o - S 30 23s 0 S +R _ 1947 o - Ap 19 23s 1 D +R _ 1947 o - N 30 23s 0 S +R _ 1948 o - May 2 23s 1 D +R _ 1948 o - O 31 23s 0 S +R _ 1949 1950 - Ap Sat>=1 23s 1 D +R _ 1949 1950 - O lastSat 23s 0 S +R _ 1951 o - Mar 31 23s 1 D +R _ 1951 o - O 28 23s 0 S +R _ 1952 1953 - Ap Sat>=1 23s 1 D +R _ 1952 o - N 1 23s 0 S +R _ 1953 1954 - O lastSat 23s 0 S +R _ 1954 1956 - Mar Sat>=17 23s 1 D +R _ 1955 o - N 5 23s 0 S +R _ 1956 1964 - N Sun>=1 3:30 0 S +R _ 1957 1964 - Mar Sun>=18 3:30 1 D +R _ 1965 1973 - Ap Sun>=16 3:30 1 D +R _ 1965 1966 - O Sun>=16 2:30 0 S +R _ 1967 1976 - O Sun>=16 3:30 0 S +R _ 1973 o - D 30 3:30 1 D +R _ 1975 1976 - Ap Sun>=16 3:30 1 D +R _ 1979 o - May 13 3:30 1 D +R _ 1979 o - O Sun>=16 3:30 0 S +Z Asia/Macau 7:34:10 - LMT 1904 O 30 +8 - CST 1941 D 21 23 +9 _ +09/+10 1945 S 30 24 +8 _ C%sT +R CY 1975 o - Ap 13 0 1 S +R CY 1975 o - O 12 0 0 - +R CY 1976 o - May 15 0 1 S +R CY 1976 o - O 11 0 0 - +R CY 1977 1980 - Ap Sun>=1 0 1 S +R CY 1977 o - S 25 0 0 - +R CY 1978 o - O 2 0 0 - +R CY 1979 1997 - S lastSun 0 0 - +R CY 1981 1998 - Mar lastSun 0 1 S +Z Asia/Nicosia 2:13:28 - LMT 1921 N 14 +2 CY EE%sT 1998 S +2 E EE%sT +Z Asia/Famagusta 2:15:48 - LMT 1921 N 14 +2 CY EE%sT 1998 S +2 E EE%sT 2016 S 8 +3 - +03 2017 O 29 1u +2 E EE%sT +Li Asia/Nicosia Europe/Nicosia +Z Asia/Tbilisi 2:59:11 - LMT 1880 +2:59:11 - TBMT 1924 May 2 +3 - +03 1957 Mar +4 R +04/+05 1991 Mar 31 2s +3 R +03/+04 1992 +3 e +03/+04 1994 S lastSun +4 e +04/+05 1996 O lastSun +4 1 +05 1997 Mar lastSun +4 e +04/+05 2004 Jun 27 +3 R +03/+04 2005 Mar lastSun 2 +4 - +04 +Z Asia/Dili 8:22:20 - LMT 1912 +8 - +08 1942 F 21 23 +9 - +09 1976 May 3 +8 - +08 2000 S 17 +9 - +09 +Z Asia/Kolkata 5:53:28 - LMT 1854 Jun 28 +5:53:20 - HMT 1870 +5:21:10 - MMT 1906 +5:30 - IST 1941 O +5:30 1 +0630 1942 May 15 +5:30 - IST 1942 S +5:30 1 +0630 1945 O 15 +5:30 - IST +Z Asia/Jakarta 7:7:12 - LMT 1867 Au 10 +7:7:12 - BMT 1923 D 31 23:47:12 +7:20 - +0720 1932 N +7:30 - +0730 1942 Mar 23 +9 - +09 1945 S 23 +7:30 - +0730 1948 May +8 - +08 1950 May +7:30 - +0730 1964 +7 - WIB +Z Asia/Pontianak 7:17:20 - LMT 1908 May +7:17:20 - PMT 1932 N +7:30 - +0730 1942 Ja 29 +9 - +09 1945 S 23 +7:30 - +0730 1948 May +8 - +08 1950 May +7:30 - +0730 1964 +8 - WITA 1988 +7 - WIB +Z Asia/Makassar 7:57:36 - LMT 1920 +7:57:36 - MMT 1932 N +8 - +08 1942 F 9 +9 - +09 1945 S 23 +8 - WITA +Z Asia/Jayapura 9:22:48 - LMT 1932 N +9 - +09 1944 S +9:30 - +0930 1964 +9 - WIT +R i 1978 1980 - Mar 21 0 1 - +R i 1978 o - O 21 0 0 - +R i 1979 o - S 19 0 0 - +R i 1980 o - S 23 0 0 - +R i 1991 o - May 3 0 1 - +R i 1992 1995 - Mar 22 0 1 - +R i 1991 1995 - S 22 0 0 - +R i 1996 o - Mar 21 0 1 - +R i 1996 o - S 21 0 0 - +R i 1997 1999 - Mar 22 0 1 - +R i 1997 1999 - S 22 0 0 - +R i 2000 o - Mar 21 0 1 - +R i 2000 o - S 21 0 0 - +R i 2001 2003 - Mar 22 0 1 - +R i 2001 2003 - S 22 0 0 - +R i 2004 o - Mar 21 0 1 - +R i 2004 o - S 21 0 0 - +R i 2005 o - Mar 22 0 1 - +R i 2005 o - S 22 0 0 - +R i 2008 o - Mar 21 0 1 - +R i 2008 o - S 21 0 0 - +R i 2009 2011 - Mar 22 0 1 - +R i 2009 2011 - S 22 0 0 - +R i 2012 o - Mar 21 0 1 - +R i 2012 o - S 21 0 0 - +R i 2013 2015 - Mar 22 0 1 - +R i 2013 2015 - S 22 0 0 - +R i 2016 o - Mar 21 0 1 - +R i 2016 o - S 21 0 0 - +R i 2017 2019 - Mar 22 0 1 - +R i 2017 2019 - S 22 0 0 - +R i 2020 o - Mar 21 0 1 - +R i 2020 o - S 21 0 0 - +R i 2021 2023 - Mar 22 0 1 - +R i 2021 2023 - S 22 0 0 - +R i 2024 o - Mar 21 0 1 - +R i 2024 o - S 21 0 0 - +R i 2025 2027 - Mar 22 0 1 - +R i 2025 2027 - S 22 0 0 - +R i 2028 2029 - Mar 21 0 1 - +R i 2028 2029 - S 21 0 0 - +R i 2030 2031 - Mar 22 0 1 - +R i 2030 2031 - S 22 0 0 - +R i 2032 2033 - Mar 21 0 1 - +R i 2032 2033 - S 21 0 0 - +R i 2034 2035 - Mar 22 0 1 - +R i 2034 2035 - S 22 0 0 - +R i 2036 ma - Mar 21 0 1 - +R i 2036 ma - S 21 0 0 - +Z Asia/Tehran 3:25:44 - LMT 1916 +3:25:44 - TMT 1946 +3:30 - +0330 1977 N +4 i +04/+05 1979 +3:30 i +0330/+0430 +R IQ 1982 o - May 1 0 1 - +R IQ 1982 1984 - O 1 0 0 - +R IQ 1983 o - Mar 31 0 1 - +R IQ 1984 1985 - Ap 1 0 1 - +R IQ 1985 1990 - S lastSun 1s 0 - +R IQ 1986 1990 - Mar lastSun 1s 1 - +R IQ 1991 2007 - Ap 1 3s 1 - +R IQ 1991 2007 - O 1 3s 0 - +Z Asia/Baghdad 2:57:40 - LMT 1890 +2:57:36 - BMT 1918 +3 - +03 1982 May +3 IQ +03/+04 +R Z 1940 o - Jun 1 0 1 D +R Z 1942 1944 - N 1 0 0 S +R Z 1943 o - Ap 1 2 1 D +R Z 1944 o - Ap 1 0 1 D +R Z 1945 o - Ap 16 0 1 D +R Z 1945 o - N 1 2 0 S +R Z 1946 o - Ap 16 2 1 D +R Z 1946 o - N 1 0 0 S +R Z 1948 o - May 23 0 2 DD +R Z 1948 o - S 1 0 1 D +R Z 1948 1949 - N 1 2 0 S +R Z 1949 o - May 1 0 1 D +R Z 1950 o - Ap 16 0 1 D +R Z 1950 o - S 15 3 0 S +R Z 1951 o - Ap 1 0 1 D +R Z 1951 o - N 11 3 0 S +R Z 1952 o - Ap 20 2 1 D +R Z 1952 o - O 19 3 0 S +R Z 1953 o - Ap 12 2 1 D +R Z 1953 o - S 13 3 0 S +R Z 1954 o - Jun 13 0 1 D +R Z 1954 o - S 12 0 0 S +R Z 1955 o - Jun 11 2 1 D +R Z 1955 o - S 11 0 0 S +R Z 1956 o - Jun 3 0 1 D +R Z 1956 o - S 30 3 0 S +R Z 1957 o - Ap 29 2 1 D +R Z 1957 o - S 22 0 0 S +R Z 1974 o - Jul 7 0 1 D +R Z 1974 o - O 13 0 0 S +R Z 1975 o - Ap 20 0 1 D +R Z 1975 o - Au 31 0 0 S +R Z 1985 o - Ap 14 0 1 D +R Z 1985 o - S 15 0 0 S +R Z 1986 o - May 18 0 1 D +R Z 1986 o - S 7 0 0 S +R Z 1987 o - Ap 15 0 1 D +R Z 1987 o - S 13 0 0 S +R Z 1988 o - Ap 10 0 1 D +R Z 1988 o - S 4 0 0 S +R Z 1989 o - Ap 30 0 1 D +R Z 1989 o - S 3 0 0 S +R Z 1990 o - Mar 25 0 1 D +R Z 1990 o - Au 26 0 0 S +R Z 1991 o - Mar 24 0 1 D +R Z 1991 o - S 1 0 0 S +R Z 1992 o - Mar 29 0 1 D +R Z 1992 o - S 6 0 0 S +R Z 1993 o - Ap 2 0 1 D +R Z 1993 o - S 5 0 0 S +R Z 1994 o - Ap 1 0 1 D +R Z 1994 o - Au 28 0 0 S +R Z 1995 o - Mar 31 0 1 D +R Z 1995 o - S 3 0 0 S +R Z 1996 o - Mar 15 0 1 D +R Z 1996 o - S 16 0 0 S +R Z 1997 o - Mar 21 0 1 D +R Z 1997 o - S 14 0 0 S +R Z 1998 o - Mar 20 0 1 D +R Z 1998 o - S 6 0 0 S +R Z 1999 o - Ap 2 2 1 D +R Z 1999 o - S 3 2 0 S +R Z 2000 o - Ap 14 2 1 D +R Z 2000 o - O 6 1 0 S +R Z 2001 o - Ap 9 1 1 D +R Z 2001 o - S 24 1 0 S +R Z 2002 o - Mar 29 1 1 D +R Z 2002 o - O 7 1 0 S +R Z 2003 o - Mar 28 1 1 D +R Z 2003 o - O 3 1 0 S +R Z 2004 o - Ap 7 1 1 D +R Z 2004 o - S 22 1 0 S +R Z 2005 o - Ap 1 2 1 D +R Z 2005 o - O 9 2 0 S +R Z 2006 2010 - Mar F>=26 2 1 D +R Z 2006 o - O 1 2 0 S +R Z 2007 o - S 16 2 0 S +R Z 2008 o - O 5 2 0 S +R Z 2009 o - S 27 2 0 S +R Z 2010 o - S 12 2 0 S +R Z 2011 o - Ap 1 2 1 D +R Z 2011 o - O 2 2 0 S +R Z 2012 o - Mar F>=26 2 1 D +R Z 2012 o - S 23 2 0 S +R Z 2013 ma - Mar F>=23 2 1 D +R Z 2013 ma - O lastSun 2 0 S +Z Asia/Jerusalem 2:20:54 - LMT 1880 +2:20:40 - JMT 1918 +2 Z I%sT +R JP 1948 o - May Sat>=1 24 1 D +R JP 1948 1951 - S Sat>=8 25 0 S +R JP 1949 o - Ap Sat>=1 24 1 D +R JP 1950 1951 - May Sat>=1 24 1 D +Z Asia/Tokyo 9:18:59 - LMT 1887 D 31 15u +9 JP J%sT +R J 1973 o - Jun 6 0 1 S +R J 1973 1975 - O 1 0 0 - +R J 1974 1977 - May 1 0 1 S +R J 1976 o - N 1 0 0 - +R J 1977 o - O 1 0 0 - +R J 1978 o - Ap 30 0 1 S +R J 1978 o - S 30 0 0 - +R J 1985 o - Ap 1 0 1 S +R J 1985 o - O 1 0 0 - +R J 1986 1988 - Ap F>=1 0 1 S +R J 1986 1990 - O F>=1 0 0 - +R J 1989 o - May 8 0 1 S +R J 1990 o - Ap 27 0 1 S +R J 1991 o - Ap 17 0 1 S +R J 1991 o - S 27 0 0 - +R J 1992 o - Ap 10 0 1 S +R J 1992 1993 - O F>=1 0 0 - +R J 1993 1998 - Ap F>=1 0 1 S +R J 1994 o - S F>=15 0 0 - +R J 1995 1998 - S F>=15 0s 0 - +R J 1999 o - Jul 1 0s 1 S +R J 1999 2002 - S lastF 0s 0 - +R J 2000 2001 - Mar lastTh 0s 1 S +R J 2002 2012 - Mar lastTh 24 1 S +R J 2003 o - O 24 0s 0 - +R J 2004 o - O 15 0s 0 - +R J 2005 o - S lastF 0s 0 - +R J 2006 2011 - O lastF 0s 0 - +R J 2013 o - D 20 0 0 - +R J 2014 ma - Mar lastTh 24 1 S +R J 2014 ma - O lastF 0s 0 - +Z Asia/Amman 2:23:44 - LMT 1931 +2 J EE%sT +Z Asia/Almaty 5:7:48 - LMT 1924 May 2 +5 - +05 1930 Jun 21 +6 R +06/+07 1991 Mar 31 2s +5 R +05/+06 1992 Ja 19 2s +6 R +06/+07 2004 O 31 2s +6 - +06 +Z Asia/Qyzylorda 4:21:52 - LMT 1924 May 2 +4 - +04 1930 Jun 21 +5 - +05 1981 Ap +5 1 +06 1981 O +6 - +06 1982 Ap +5 R +05/+06 1991 Mar 31 2s +4 R +04/+05 1991 S 29 2s +5 R +05/+06 1992 Ja 19 2s +6 R +06/+07 1992 Mar 29 2s +5 R +05/+06 2004 O 31 2s +6 - +06 +Z Asia/Aqtobe 3:48:40 - LMT 1924 May 2 +4 - +04 1930 Jun 21 +5 - +05 1981 Ap +5 1 +06 1981 O +6 - +06 1982 Ap +5 R +05/+06 1991 Mar 31 2s +4 R +04/+05 1992 Ja 19 2s +5 R +05/+06 2004 O 31 2s +5 - +05 +Z Asia/Aqtau 3:21:4 - LMT 1924 May 2 +4 - +04 1930 Jun 21 +5 - +05 1981 O +6 - +06 1982 Ap +5 R +05/+06 1991 Mar 31 2s +4 R +04/+05 1992 Ja 19 2s +5 R +05/+06 1994 S 25 2s +4 R +04/+05 2004 O 31 2s +5 - +05 +Z Asia/Atyrau 3:27:44 - LMT 1924 May 2 +3 - +03 1930 Jun 21 +5 - +05 1981 O +6 - +06 1982 Ap +5 R +05/+06 1991 Mar 31 2s +4 R +04/+05 1992 Ja 19 2s +5 R +05/+06 1999 Mar 28 2s +4 R +04/+05 2004 O 31 2s +5 - +05 +Z Asia/Oral 3:25:24 - LMT 1924 May 2 +3 - +03 1930 Jun 21 +5 - +05 1981 Ap +5 1 +06 1981 O +6 - +06 1982 Ap +5 R +05/+06 1989 Mar 26 2s +4 R +04/+05 1992 Ja 19 2s +5 R +05/+06 1992 Mar 29 2s +4 R +04/+05 2004 O 31 2s +5 - +05 +R KG 1992 1996 - Ap Sun>=7 0s 1 - +R KG 1992 1996 - S lastSun 0 0 - +R KG 1997 2005 - Mar lastSun 2:30 1 - +R KG 1997 2004 - O lastSun 2:30 0 - +Z Asia/Bishkek 4:58:24 - LMT 1924 May 2 +5 - +05 1930 Jun 21 +6 R +06/+07 1991 Mar 31 2s +5 R +05/+06 1991 Au 31 2 +5 KG +05/+06 2005 Au 12 +6 - +06 +R KR 1948 o - Jun 1 0 1 D +R KR 1948 o - S 13 0 0 S +R KR 1949 o - Ap 3 0 1 D +R KR 1949 1951 - S Sun>=8 0 0 S +R KR 1950 o - Ap 1 0 1 D +R KR 1951 o - May 6 0 1 D +R KR 1955 o - May 5 0 1 D +R KR 1955 o - S 9 0 0 S +R KR 1956 o - May 20 0 1 D +R KR 1956 o - S 30 0 0 S +R KR 1957 1960 - May Sun>=1 0 1 D +R KR 1957 1960 - S Sun>=18 0 0 S +R KR 1987 1988 - May Sun>=8 2 1 D +R KR 1987 1988 - O Sun>=8 3 0 S +Z Asia/Seoul 8:27:52 - LMT 1908 Ap +8:30 - KST 1912 +9 - JST 1945 S 8 +9 - KST 1954 Mar 21 +8:30 KR K%sT 1961 Au 10 +9 KR K%sT +Z Asia/Pyongyang 8:23 - LMT 1908 Ap +8:30 - KST 1912 +9 - JST 1945 Au 24 +9 - KST 2015 Au 15 +8:30 - KST 2018 May 4 23:30 +9 - KST +R l 1920 o - Mar 28 0 1 S +R l 1920 o - O 25 0 0 - +R l 1921 o - Ap 3 0 1 S +R l 1921 o - O 3 0 0 - +R l 1922 o - Mar 26 0 1 S +R l 1922 o - O 8 0 0 - +R l 1923 o - Ap 22 0 1 S +R l 1923 o - S 16 0 0 - +R l 1957 1961 - May 1 0 1 S +R l 1957 1961 - O 1 0 0 - +R l 1972 o - Jun 22 0 1 S +R l 1972 1977 - O 1 0 0 - +R l 1973 1977 - May 1 0 1 S +R l 1978 o - Ap 30 0 1 S +R l 1978 o - S 30 0 0 - +R l 1984 1987 - May 1 0 1 S +R l 1984 1991 - O 16 0 0 - +R l 1988 o - Jun 1 0 1 S +R l 1989 o - May 10 0 1 S +R l 1990 1992 - May 1 0 1 S +R l 1992 o - O 4 0 0 - +R l 1993 ma - Mar lastSun 0 1 S +R l 1993 1998 - S lastSun 0 0 - +R l 1999 ma - O lastSun 0 0 - +Z Asia/Beirut 2:22 - LMT 1880 +2 l EE%sT +R NB 1935 1941 - S 14 0 0:20 - +R NB 1935 1941 - D 14 0 0 - +Z Asia/Kuala_Lumpur 6:46:46 - LMT 1901 +6:55:25 - SMT 1905 Jun +7 - +07 1933 +7 0:20 +0720 1936 +7:20 - +0720 1941 S +7:30 - +0730 1942 F 16 +9 - +09 1945 S 12 +7:30 - +0730 1982 +8 - +08 +Z Asia/Kuching 7:21:20 - LMT 1926 Mar +7:30 - +0730 1933 +8 NB +08/+0820 1942 F 16 +9 - +09 1945 S 12 +8 - +08 +Z Indian/Maldives 4:54 - LMT 1880 +4:54 - MMT 1960 +5 - +05 +R X 1983 1984 - Ap 1 0 1 - +R X 1983 o - O 1 0 0 - +R X 1985 1998 - Mar lastSun 0 1 - +R X 1984 1998 - S lastSun 0 0 - +R X 2001 o - Ap lastSat 2 1 - +R X 2001 2006 - S lastSat 2 0 - +R X 2002 2006 - Mar lastSat 2 1 - +R X 2015 2016 - Mar lastSat 2 1 - +R X 2015 2016 - S lastSat 0 0 - +Z Asia/Hovd 6:6:36 - LMT 1905 Au +6 - +06 1978 +7 X +07/+08 +Z Asia/Ulaanbaatar 7:7:32 - LMT 1905 Au +7 - +07 1978 +8 X +08/+09 +Z Asia/Choibalsan 7:38 - LMT 1905 Au +7 - +07 1978 +8 - +08 1983 Ap +9 X +09/+10 2008 Mar 31 +8 X +08/+09 +Z Asia/Kathmandu 5:41:16 - LMT 1920 +5:30 - +0530 1986 +5:45 - +0545 +R PK 2002 o - Ap Sun>=2 0 1 S +R PK 2002 o - O Sun>=2 0 0 - +R PK 2008 o - Jun 1 0 1 S +R PK 2008 2009 - N 1 0 0 - +R PK 2009 o - Ap 15 0 1 S +Z Asia/Karachi 4:28:12 - LMT 1907 +5:30 - +0530 1942 S +5:30 1 +0630 1945 O 15 +5:30 - +0530 1951 S 30 +5 - +05 1971 Mar 26 +5 PK PK%sT +R P 1999 2005 - Ap F>=15 0 1 S +R P 1999 2003 - O F>=15 0 0 - +R P 2004 o - O 1 1 0 - +R P 2005 o - O 4 2 0 - +R P 2006 2007 - Ap 1 0 1 S +R P 2006 o - S 22 0 0 - +R P 2007 o - S Th>=8 2 0 - +R P 2008 2009 - Mar lastF 0 1 S +R P 2008 o - S 1 0 0 - +R P 2009 o - S F>=1 1 0 - +R P 2010 o - Mar 26 0 1 S +R P 2010 o - Au 11 0 0 - +R P 2011 o - Ap 1 0:1 1 S +R P 2011 o - Au 1 0 0 - +R P 2011 o - Au 30 0 1 S +R P 2011 o - S 30 0 0 - +R P 2012 2014 - Mar lastTh 24 1 S +R P 2012 o - S 21 1 0 - +R P 2013 o - S F>=21 0 0 - +R P 2014 2015 - O F>=21 0 0 - +R P 2015 o - Mar lastF 24 1 S +R P 2016 ma - Mar Sat>=22 1 1 S +R P 2016 ma - O lastSat 1 0 - +Z Asia/Gaza 2:17:52 - LMT 1900 O +2 Z EET/EEST 1948 May 15 +2 K EE%sT 1967 Jun 5 +2 Z I%sT 1996 +2 J EE%sT 1999 +2 P EE%sT 2008 Au 29 +2 - EET 2008 S +2 P EE%sT 2010 +2 - EET 2010 Mar 27 0:1 +2 P EE%sT 2011 Au +2 - EET 2012 +2 P EE%sT +Z Asia/Hebron 2:20:23 - LMT 1900 O +2 Z EET/EEST 1948 May 15 +2 K EE%sT 1967 Jun 5 +2 Z I%sT 1996 +2 J EE%sT 1999 +2 P EE%sT +R PH 1936 o - N 1 0 1 D +R PH 1937 o - F 1 0 0 S +R PH 1954 o - Ap 12 0 1 D +R PH 1954 o - Jul 1 0 0 S +R PH 1978 o - Mar 22 0 1 D +R PH 1978 o - S 21 0 0 S +Z Asia/Manila -15:56 - LMT 1844 D 31 +8:4 - LMT 1899 May 11 +8 PH P%sT 1942 May +9 - JST 1944 N +8 PH P%sT +Z Asia/Qatar 3:26:8 - LMT 1920 +4 - +04 1972 Jun +3 - +03 +Li Asia/Qatar Asia/Bahrain +Z Asia/Riyadh 3:6:52 - LMT 1947 Mar 14 +3 - +03 +Li Asia/Riyadh Asia/Aden +Li Asia/Riyadh Asia/Kuwait +Z Asia/Singapore 6:55:25 - LMT 1901 +6:55:25 - SMT 1905 Jun +7 - +07 1933 +7 0:20 +0720 1936 +7:20 - +0720 1941 S +7:30 - +0730 1942 F 16 +9 - +09 1945 S 12 +7:30 - +0730 1982 +8 - +08 +Z Asia/Colombo 5:19:24 - LMT 1880 +5:19:32 - MMT 1906 +5:30 - +0530 1942 Ja 5 +5:30 0:30 +06 1942 S +5:30 1 +0630 1945 O 16 2 +5:30 - +0530 1996 May 25 +6:30 - +0630 1996 O 26 0:30 +6 - +06 2006 Ap 15 0:30 +5:30 - +0530 +R S 1920 1923 - Ap Sun>=15 2 1 S +R S 1920 1923 - O Sun>=1 2 0 - +R S 1962 o - Ap 29 2 1 S +R S 1962 o - O 1 2 0 - +R S 1963 1965 - May 1 2 1 S +R S 1963 o - S 30 2 0 - +R S 1964 o - O 1 2 0 - +R S 1965 o - S 30 2 0 - +R S 1966 o - Ap 24 2 1 S +R S 1966 1976 - O 1 2 0 - +R S 1967 1978 - May 1 2 1 S +R S 1977 1978 - S 1 2 0 - +R S 1983 1984 - Ap 9 2 1 S +R S 1983 1984 - O 1 2 0 - +R S 1986 o - F 16 2 1 S +R S 1986 o - O 9 2 0 - +R S 1987 o - Mar 1 2 1 S +R S 1987 1988 - O 31 2 0 - +R S 1988 o - Mar 15 2 1 S +R S 1989 o - Mar 31 2 1 S +R S 1989 o - O 1 2 0 - +R S 1990 o - Ap 1 2 1 S +R S 1990 o - S 30 2 0 - +R S 1991 o - Ap 1 0 1 S +R S 1991 1992 - O 1 0 0 - +R S 1992 o - Ap 8 0 1 S +R S 1993 o - Mar 26 0 1 S +R S 1993 o - S 25 0 0 - +R S 1994 1996 - Ap 1 0 1 S +R S 1994 2005 - O 1 0 0 - +R S 1997 1998 - Mar lastM 0 1 S +R S 1999 2006 - Ap 1 0 1 S +R S 2006 o - S 22 0 0 - +R S 2007 o - Mar lastF 0 1 S +R S 2007 o - N F>=1 0 0 - +R S 2008 o - Ap F>=1 0 1 S +R S 2008 o - N 1 0 0 - +R S 2009 o - Mar lastF 0 1 S +R S 2010 2011 - Ap F>=1 0 1 S +R S 2012 ma - Mar lastF 0 1 S +R S 2009 ma - O lastF 0 0 - +Z Asia/Damascus 2:25:12 - LMT 1920 +2 S EE%sT +Z Asia/Dushanbe 4:35:12 - LMT 1924 May 2 +5 - +05 1930 Jun 21 +6 R +06/+07 1991 Mar 31 2s +5 1 +05/+06 1991 S 9 2s +5 - +05 +Z Asia/Bangkok 6:42:4 - LMT 1880 +6:42:4 - BMT 1920 Ap +7 - +07 +Li Asia/Bangkok Asia/Phnom_Penh +Li Asia/Bangkok Asia/Vientiane +Z Asia/Ashgabat 3:53:32 - LMT 1924 May 2 +4 - +04 1930 Jun 21 +5 R +05/+06 1991 Mar 31 2 +4 R +04/+05 1992 Ja 19 2 +5 - +05 +Z Asia/Dubai 3:41:12 - LMT 1920 +4 - +04 +Li Asia/Dubai Asia/Muscat +Z Asia/Samarkand 4:27:53 - LMT 1924 May 2 +4 - +04 1930 Jun 21 +5 - +05 1981 Ap +5 1 +06 1981 O +6 - +06 1982 Ap +5 R +05/+06 1992 +5 - +05 +Z Asia/Tashkent 4:37:11 - LMT 1924 May 2 +5 - +05 1930 Jun 21 +6 R +06/+07 1991 Mar 31 2 +5 R +05/+06 1992 +5 - +05 +Z Asia/Ho_Chi_Minh 7:6:40 - LMT 1906 Jul +7:6:30 - PLMT 1911 May +7 - +07 1942 D 31 23 +8 - +08 1945 Mar 14 23 +9 - +09 1945 S 2 +7 - +07 1947 Ap +8 - +08 1955 Jul +7 - +07 1959 D 31 23 +8 - +08 1975 Jun 13 +7 - +07 +R AU 1917 o - Ja 1 0:1 1 D +R AU 1917 o - Mar 25 2 0 S +R AU 1942 o - Ja 1 2 1 D +R AU 1942 o - Mar 29 2 0 S +R AU 1942 o - S 27 2 1 D +R AU 1943 1944 - Mar lastSun 2 0 S +R AU 1943 o - O 3 2 1 D +Z Australia/Darwin 8:43:20 - LMT 1895 F +9 - ACST 1899 May +9:30 AU AC%sT +R AW 1974 o - O lastSun 2s 1 D +R AW 1975 o - Mar Sun>=1 2s 0 S +R AW 1983 o - O lastSun 2s 1 D +R AW 1984 o - Mar Sun>=1 2s 0 S +R AW 1991 o - N 17 2s 1 D +R AW 1992 o - Mar Sun>=1 2s 0 S +R AW 2006 o - D 3 2s 1 D +R AW 2007 2009 - Mar lastSun 2s 0 S +R AW 2007 2008 - O lastSun 2s 1 D +Z Australia/Perth 7:43:24 - LMT 1895 D +8 AU AW%sT 1943 Jul +8 AW AW%sT +Z Australia/Eucla 8:35:28 - LMT 1895 D +8:45 AU +0845/+0945 1943 Jul +8:45 AW +0845/+0945 +R AQ 1971 o - O lastSun 2s 1 D +R AQ 1972 o - F lastSun 2s 0 S +R AQ 1989 1991 - O lastSun 2s 1 D +R AQ 1990 1992 - Mar Sun>=1 2s 0 S +R Ho 1992 1993 - O lastSun 2s 1 D +R Ho 1993 1994 - Mar Sun>=1 2s 0 S +Z Australia/Brisbane 10:12:8 - LMT 1895 +10 AU AE%sT 1971 +10 AQ AE%sT +Z Australia/Lindeman 9:55:56 - LMT 1895 +10 AU AE%sT 1971 +10 AQ AE%sT 1992 Jul +10 Ho AE%sT +R AS 1971 1985 - O lastSun 2s 1 D +R AS 1986 o - O 19 2s 1 D +R AS 1987 2007 - O lastSun 2s 1 D +R AS 1972 o - F 27 2s 0 S +R AS 1973 1985 - Mar Sun>=1 2s 0 S +R AS 1986 1990 - Mar Sun>=15 2s 0 S +R AS 1991 o - Mar 3 2s 0 S +R AS 1992 o - Mar 22 2s 0 S +R AS 1993 o - Mar 7 2s 0 S +R AS 1994 o - Mar 20 2s 0 S +R AS 1995 2005 - Mar lastSun 2s 0 S +R AS 2006 o - Ap 2 2s 0 S +R AS 2007 o - Mar lastSun 2s 0 S +R AS 2008 ma - Ap Sun>=1 2s 0 S +R AS 2008 ma - O Sun>=1 2s 1 D +Z Australia/Adelaide 9:14:20 - LMT 1895 F +9 - ACST 1899 May +9:30 AU AC%sT 1971 +9:30 AS AC%sT +R AT 1967 o - O Sun>=1 2s 1 D +R AT 1968 o - Mar lastSun 2s 0 S +R AT 1968 1985 - O lastSun 2s 1 D +R AT 1969 1971 - Mar Sun>=8 2s 0 S +R AT 1972 o - F lastSun 2s 0 S +R AT 1973 1981 - Mar Sun>=1 2s 0 S +R AT 1982 1983 - Mar lastSun 2s 0 S +R AT 1984 1986 - Mar Sun>=1 2s 0 S +R AT 1986 o - O Sun>=15 2s 1 D +R AT 1987 1990 - Mar Sun>=15 2s 0 S +R AT 1987 o - O Sun>=22 2s 1 D +R AT 1988 1990 - O lastSun 2s 1 D +R AT 1991 1999 - O Sun>=1 2s 1 D +R AT 1991 2005 - Mar lastSun 2s 0 S +R AT 2000 o - Au lastSun 2s 1 D +R AT 2001 ma - O Sun>=1 2s 1 D +R AT 2006 o - Ap Sun>=1 2s 0 S +R AT 2007 o - Mar lastSun 2s 0 S +R AT 2008 ma - Ap Sun>=1 2s 0 S +Z Australia/Hobart 9:49:16 - LMT 1895 S +10 - AEST 1916 O 1 2 +10 1 AEDT 1917 F +10 AU AE%sT 1967 +10 AT AE%sT +Z Australia/Currie 9:35:28 - LMT 1895 S +10 - AEST 1916 O 1 2 +10 1 AEDT 1917 F +10 AU AE%sT 1971 Jul +10 AT AE%sT +R AV 1971 1985 - O lastSun 2s 1 D +R AV 1972 o - F lastSun 2s 0 S +R AV 1973 1985 - Mar Sun>=1 2s 0 S +R AV 1986 1990 - Mar Sun>=15 2s 0 S +R AV 1986 1987 - O Sun>=15 2s 1 D +R AV 1988 1999 - O lastSun 2s 1 D +R AV 1991 1994 - Mar Sun>=1 2s 0 S +R AV 1995 2005 - Mar lastSun 2s 0 S +R AV 2000 o - Au lastSun 2s 1 D +R AV 2001 2007 - O lastSun 2s 1 D +R AV 2006 o - Ap Sun>=1 2s 0 S +R AV 2007 o - Mar lastSun 2s 0 S +R AV 2008 ma - Ap Sun>=1 2s 0 S +R AV 2008 ma - O Sun>=1 2s 1 D +Z Australia/Melbourne 9:39:52 - LMT 1895 F +10 AU AE%sT 1971 +10 AV AE%sT +R AN 1971 1985 - O lastSun 2s 1 D +R AN 1972 o - F 27 2s 0 S +R AN 1973 1981 - Mar Sun>=1 2s 0 S +R AN 1982 o - Ap Sun>=1 2s 0 S +R AN 1983 1985 - Mar Sun>=1 2s 0 S +R AN 1986 1989 - Mar Sun>=15 2s 0 S +R AN 1986 o - O 19 2s 1 D +R AN 1987 1999 - O lastSun 2s 1 D +R AN 1990 1995 - Mar Sun>=1 2s 0 S +R AN 1996 2005 - Mar lastSun 2s 0 S +R AN 2000 o - Au lastSun 2s 1 D +R AN 2001 2007 - O lastSun 2s 1 D +R AN 2006 o - Ap Sun>=1 2s 0 S +R AN 2007 o - Mar lastSun 2s 0 S +R AN 2008 ma - Ap Sun>=1 2s 0 S +R AN 2008 ma - O Sun>=1 2s 1 D +Z Australia/Sydney 10:4:52 - LMT 1895 F +10 AU AE%sT 1971 +10 AN AE%sT +Z Australia/Broken_Hill 9:25:48 - LMT 1895 F +10 - AEST 1896 Au 23 +9 - ACST 1899 May +9:30 AU AC%sT 1971 +9:30 AN AC%sT 2000 +9:30 AS AC%sT +R LH 1981 1984 - O lastSun 2 1 - +R LH 1982 1985 - Mar Sun>=1 2 0 - +R LH 1985 o - O lastSun 2 0:30 - +R LH 1986 1989 - Mar Sun>=15 2 0 - +R LH 1986 o - O 19 2 0:30 - +R LH 1987 1999 - O lastSun 2 0:30 - +R LH 1990 1995 - Mar Sun>=1 2 0 - +R LH 1996 2005 - Mar lastSun 2 0 - +R LH 2000 o - Au lastSun 2 0:30 - +R LH 2001 2007 - O lastSun 2 0:30 - +R LH 2006 o - Ap Sun>=1 2 0 - +R LH 2007 o - Mar lastSun 2 0 - +R LH 2008 ma - Ap Sun>=1 2 0 - +R LH 2008 ma - O Sun>=1 2 0:30 - +Z Australia/Lord_Howe 10:36:20 - LMT 1895 F +10 - AEST 1981 Mar +10:30 LH +1030/+1130 1985 Jul +10:30 LH +1030/+11 +Z Antarctica/Macquarie 0 - -00 1899 N +10 - AEST 1916 O 1 2 +10 1 AEDT 1917 F +10 AU AE%sT 1919 Ap 1 0s +0 - -00 1948 Mar 25 +10 AU AE%sT 1967 +10 AT AE%sT 2010 Ap 4 3 +11 - +11 +Z Indian/Christmas 7:2:52 - LMT 1895 F +7 - +07 +Z Indian/Cocos 6:27:40 - LMT 1900 +6:30 - +0630 +R FJ 1998 1999 - N Sun>=1 2 1 - +R FJ 1999 2000 - F lastSun 3 0 - +R FJ 2009 o - N 29 2 1 - +R FJ 2010 o - Mar lastSun 3 0 - +R FJ 2010 2013 - O Sun>=21 2 1 - +R FJ 2011 o - Mar Sun>=1 3 0 - +R FJ 2012 2013 - Ja Sun>=18 3 0 - +R FJ 2014 o - Ja Sun>=18 2 0 - +R FJ 2014 ma - N Sun>=1 2 1 - +R FJ 2015 ma - Ja Sun>=13 3 0 - +Z Pacific/Fiji 11:55:44 - LMT 1915 O 26 +12 FJ +12/+13 +Z Pacific/Gambier -8:59:48 - LMT 1912 O +-9 - -09 +Z Pacific/Marquesas -9:18 - LMT 1912 O +-9:30 - -0930 +Z Pacific/Tahiti -9:58:16 - LMT 1912 O +-10 - -10 +Z Pacific/Guam -14:21 - LMT 1844 D 31 +9:39 - LMT 1901 +10 - GST 2000 D 23 +10 - ChST +Li Pacific/Guam Pacific/Saipan +Z Pacific/Tarawa 11:32:4 - LMT 1901 +12 - +12 +Z Pacific/Enderbury -11:24:20 - LMT 1901 +-12 - -12 1979 O +-11 - -11 1994 D 31 +13 - +13 +Z Pacific/Kiritimati -10:29:20 - LMT 1901 +-10:40 - -1040 1979 O +-10 - -10 1994 D 31 +14 - +14 +Z Pacific/Majuro 11:24:48 - LMT 1901 +11 - +11 1969 O +12 - +12 +Z Pacific/Kwajalein 11:9:20 - LMT 1901 +11 - +11 1969 O +-12 - -12 1993 Au 20 +12 - +12 +Z Pacific/Chuuk 10:7:8 - LMT 1901 +10 - +10 +Z Pacific/Pohnpei 10:32:52 - LMT 1901 +11 - +11 +Z Pacific/Kosrae 10:51:56 - LMT 1901 +11 - +11 1969 O +12 - +12 1999 +11 - +11 +Z Pacific/Nauru 11:7:40 - LMT 1921 Ja 15 +11:30 - +1130 1942 Mar 15 +9 - +09 1944 Au 15 +11:30 - +1130 1979 May +12 - +12 +R NC 1977 1978 - D Sun>=1 0 1 - +R NC 1978 1979 - F 27 0 0 - +R NC 1996 o - D 1 2s 1 - +R NC 1997 o - Mar 2 2s 0 - +Z Pacific/Noumea 11:5:48 - LMT 1912 Ja 13 +11 NC +11/+12 +R NZ 1927 o - N 6 2 1 S +R NZ 1928 o - Mar 4 2 0 M +R NZ 1928 1933 - O Sun>=8 2 0:30 S +R NZ 1929 1933 - Mar Sun>=15 2 0 M +R NZ 1934 1940 - Ap lastSun 2 0 M +R NZ 1934 1940 - S lastSun 2 0:30 S +R NZ 1946 o - Ja 1 0 0 S +R NZ 1974 o - N Sun>=1 2s 1 D +R k 1974 o - N Sun>=1 2:45s 1 - +R NZ 1975 o - F lastSun 2s 0 S +R k 1975 o - F lastSun 2:45s 0 - +R NZ 1975 1988 - O lastSun 2s 1 D +R k 1975 1988 - O lastSun 2:45s 1 - +R NZ 1976 1989 - Mar Sun>=1 2s 0 S +R k 1976 1989 - Mar Sun>=1 2:45s 0 - +R NZ 1989 o - O Sun>=8 2s 1 D +R k 1989 o - O Sun>=8 2:45s 1 - +R NZ 1990 2006 - O Sun>=1 2s 1 D +R k 1990 2006 - O Sun>=1 2:45s 1 - +R NZ 1990 2007 - Mar Sun>=15 2s 0 S +R k 1990 2007 - Mar Sun>=15 2:45s 0 - +R NZ 2007 ma - S lastSun 2s 1 D +R k 2007 ma - S lastSun 2:45s 1 - +R NZ 2008 ma - Ap Sun>=1 2s 0 S +R k 2008 ma - Ap Sun>=1 2:45s 0 - +Z Pacific/Auckland 11:39:4 - LMT 1868 N 2 +11:30 NZ NZ%sT 1946 +12 NZ NZ%sT +Z Pacific/Chatham 12:13:48 - LMT 1868 N 2 +12:15 - +1215 1946 +12:45 k +1245/+1345 +Li Pacific/Auckland Antarctica/McMurdo +R CK 1978 o - N 12 0 0:30 - +R CK 1979 1991 - Mar Sun>=1 0 0 - +R CK 1979 1990 - O lastSun 0 0:30 - +Z Pacific/Rarotonga -10:39:4 - LMT 1901 +-10:30 - -1030 1978 N 12 +-10 CK -10/-0930 +Z Pacific/Niue -11:19:40 - LMT 1901 +-11:20 - -1120 1951 +-11:30 - -1130 1978 O +-11 - -11 +Z Pacific/Norfolk 11:11:52 - LMT 1901 +11:12 - +1112 1951 +11:30 - +1130 1974 O 27 2 +11:30 1 +1230 1975 Mar 2 2 +11:30 - +1130 2015 O 4 2 +11 - +11 +Z Pacific/Palau 8:57:56 - LMT 1901 +9 - +09 +Z Pacific/Port_Moresby 9:48:40 - LMT 1880 +9:48:32 - PMMT 1895 +10 - +10 +Z Pacific/Bougainville 10:22:16 - LMT 1880 +9:48:32 - PMMT 1895 +10 - +10 1942 Jul +9 - +09 1945 Au 21 +10 - +10 2014 D 28 2 +11 - +11 +Z Pacific/Pitcairn -8:40:20 - LMT 1901 +-8:30 - -0830 1998 Ap 27 +-8 - -08 +Z Pacific/Pago_Pago 12:37:12 - LMT 1892 Jul 5 +-11:22:48 - LMT 1911 +-11 - SST +Li Pacific/Pago_Pago Pacific/Midway +R WS 2010 o - S lastSun 0 1 - +R WS 2011 o - Ap Sat>=1 4 0 - +R WS 2011 o - S lastSat 3 1 - +R WS 2012 ma - Ap Sun>=1 4 0 - +R WS 2012 ma - S lastSun 3 1 - +Z Pacific/Apia 12:33:4 - LMT 1892 Jul 5 +-11:26:56 - LMT 1911 +-11:30 - -1130 1950 +-11 WS -11/-10 2011 D 29 24 +13 WS +13/+14 +Z Pacific/Guadalcanal 10:39:48 - LMT 1912 O +11 - +11 +Z Pacific/Fakaofo -11:24:56 - LMT 1901 +-11 - -11 2011 D 30 +13 - +13 +R TO 1999 o - O 7 2s 1 - +R TO 2000 o - Mar 19 2s 0 - +R TO 2000 2001 - N Sun>=1 2 1 - +R TO 2001 2002 - Ja lastSun 2 0 - +R TO 2016 o - N Sun>=1 2 1 - +R TO 2017 o - Ja Sun>=15 3 0 - +Z Pacific/Tongatapu 12:19:20 - LMT 1901 +12:20 - +1220 1941 +13 - +13 1999 +13 TO +13/+14 +Z Pacific/Funafuti 11:56:52 - LMT 1901 +12 - +12 +Z Pacific/Wake 11:6:28 - LMT 1901 +12 - +12 +R VU 1983 o - S 25 0 1 - +R VU 1984 1991 - Mar Sun>=23 0 0 - +R VU 1984 o - O 23 0 1 - +R VU 1985 1991 - S Sun>=23 0 1 - +R VU 1992 1993 - Ja Sun>=23 0 0 - +R VU 1992 o - O Sun>=23 0 1 - +Z Pacific/Efate 11:13:16 - LMT 1912 Ja 13 +11 VU +11/+12 +Z Pacific/Wallis 12:15:20 - LMT 1901 +12 - +12 +R G 1916 o - May 21 2s 1 BST +R G 1916 o - O 1 2s 0 GMT +R G 1917 o - Ap 8 2s 1 BST +R G 1917 o - S 17 2s 0 GMT +R G 1918 o - Mar 24 2s 1 BST +R G 1918 o - S 30 2s 0 GMT +R G 1919 o - Mar 30 2s 1 BST +R G 1919 o - S 29 2s 0 GMT +R G 1920 o - Mar 28 2s 1 BST +R G 1920 o - O 25 2s 0 GMT +R G 1921 o - Ap 3 2s 1 BST +R G 1921 o - O 3 2s 0 GMT +R G 1922 o - Mar 26 2s 1 BST +R G 1922 o - O 8 2s 0 GMT +R G 1923 o - Ap Sun>=16 2s 1 BST +R G 1923 1924 - S Sun>=16 2s 0 GMT +R G 1924 o - Ap Sun>=9 2s 1 BST +R G 1925 1926 - Ap Sun>=16 2s 1 BST +R G 1925 1938 - O Sun>=2 2s 0 GMT +R G 1927 o - Ap Sun>=9 2s 1 BST +R G 1928 1929 - Ap Sun>=16 2s 1 BST +R G 1930 o - Ap Sun>=9 2s 1 BST +R G 1931 1932 - Ap Sun>=16 2s 1 BST +R G 1933 o - Ap Sun>=9 2s 1 BST +R G 1934 o - Ap Sun>=16 2s 1 BST +R G 1935 o - Ap Sun>=9 2s 1 BST +R G 1936 1937 - Ap Sun>=16 2s 1 BST +R G 1938 o - Ap Sun>=9 2s 1 BST +R G 1939 o - Ap Sun>=16 2s 1 BST +R G 1939 o - N Sun>=16 2s 0 GMT +R G 1940 o - F Sun>=23 2s 1 BST +R G 1941 o - May Sun>=2 1s 2 BDST +R G 1941 1943 - Au Sun>=9 1s 1 BST +R G 1942 1944 - Ap Sun>=2 1s 2 BDST +R G 1944 o - S Sun>=16 1s 1 BST +R G 1945 o - Ap M>=2 1s 2 BDST +R G 1945 o - Jul Sun>=9 1s 1 BST +R G 1945 1946 - O Sun>=2 2s 0 GMT +R G 1946 o - Ap Sun>=9 2s 1 BST +R G 1947 o - Mar 16 2s 1 BST +R G 1947 o - Ap 13 1s 2 BDST +R G 1947 o - Au 10 1s 1 BST +R G 1947 o - N 2 2s 0 GMT +R G 1948 o - Mar 14 2s 1 BST +R G 1948 o - O 31 2s 0 GMT +R G 1949 o - Ap 3 2s 1 BST +R G 1949 o - O 30 2s 0 GMT +R G 1950 1952 - Ap Sun>=14 2s 1 BST +R G 1950 1952 - O Sun>=21 2s 0 GMT +R G 1953 o - Ap Sun>=16 2s 1 BST +R G 1953 1960 - O Sun>=2 2s 0 GMT +R G 1954 o - Ap Sun>=9 2s 1 BST +R G 1955 1956 - Ap Sun>=16 2s 1 BST +R G 1957 o - Ap Sun>=9 2s 1 BST +R G 1958 1959 - Ap Sun>=16 2s 1 BST +R G 1960 o - Ap Sun>=9 2s 1 BST +R G 1961 1963 - Mar lastSun 2s 1 BST +R G 1961 1968 - O Sun>=23 2s 0 GMT +R G 1964 1967 - Mar Sun>=19 2s 1 BST +R G 1968 o - F 18 2s 1 BST +R G 1972 1980 - Mar Sun>=16 2s 1 BST +R G 1972 1980 - O Sun>=23 2s 0 GMT +R G 1981 1995 - Mar lastSun 1u 1 BST +R G 1981 1989 - O Sun>=23 1u 0 GMT +R G 1990 1995 - O Sun>=22 1u 0 GMT +Z Europe/London -0:1:15 - LMT 1847 D 1 0s +0 G %s 1968 O 27 +1 - BST 1971 O 31 2u +0 G %s 1996 +0 E GMT/BST +Li Europe/London Europe/Jersey +Li Europe/London Europe/Guernsey +Li Europe/London Europe/Isle_of_Man +R IE 1971 o - O 31 2u -1 - +R IE 1972 1980 - Mar Sun>=16 2u 0 - +R IE 1972 1980 - O Sun>=23 2u -1 - +R IE 1981 ma - Mar lastSun 1u 0 - +R IE 1981 1989 - O Sun>=23 1u -1 - +R IE 1990 1995 - O Sun>=22 1u -1 - +R IE 1996 ma - O lastSun 1u -1 - +Z Europe/Dublin -0:25 - LMT 1880 Au 2 +-0:25:21 - DMT 1916 May 21 2s +-0:25:21 1 IST 1916 O 1 2s +0 G %s 1921 D 6 +0 G GMT/IST 1940 F 25 2s +0 1 IST 1946 O 6 2s +0 - GMT 1947 Mar 16 2s +0 1 IST 1947 N 2 2s +0 - GMT 1948 Ap 18 2s +0 G GMT/IST 1968 O 27 +1 IE IST/GMT +R E 1977 1980 - Ap Sun>=1 1u 1 S +R E 1977 o - S lastSun 1u 0 - +R E 1978 o - O 1 1u 0 - +R E 1979 1995 - S lastSun 1u 0 - +R E 1981 ma - Mar lastSun 1u 1 S +R E 1996 ma - O lastSun 1u 0 - +R W- 1977 1980 - Ap Sun>=1 1s 1 S +R W- 1977 o - S lastSun 1s 0 - +R W- 1978 o - O 1 1s 0 - +R W- 1979 1995 - S lastSun 1s 0 - +R W- 1981 ma - Mar lastSun 1s 1 S +R W- 1996 ma - O lastSun 1s 0 - +R c 1916 o - Ap 30 23 1 S +R c 1916 o - O 1 1 0 - +R c 1917 1918 - Ap M>=15 2s 1 S +R c 1917 1918 - S M>=15 2s 0 - +R c 1940 o - Ap 1 2s 1 S +R c 1942 o - N 2 2s 0 - +R c 1943 o - Mar 29 2s 1 S +R c 1943 o - O 4 2s 0 - +R c 1944 1945 - Ap M>=1 2s 1 S +R c 1944 o - O 2 2s 0 - +R c 1945 o - S 16 2s 0 - +R c 1977 1980 - Ap Sun>=1 2s 1 S +R c 1977 o - S lastSun 2s 0 - +R c 1978 o - O 1 2s 0 - +R c 1979 1995 - S lastSun 2s 0 - +R c 1981 ma - Mar lastSun 2s 1 S +R c 1996 ma - O lastSun 2s 0 - +R e 1977 1980 - Ap Sun>=1 0 1 S +R e 1977 o - S lastSun 0 0 - +R e 1978 o - O 1 0 0 - +R e 1979 1995 - S lastSun 0 0 - +R e 1981 ma - Mar lastSun 0 1 S +R e 1996 ma - O lastSun 0 0 - +R R 1917 o - Jul 1 23 1 MST +R R 1917 o - D 28 0 0 MMT +R R 1918 o - May 31 22 2 MDST +R R 1918 o - S 16 1 1 MST +R R 1919 o - May 31 23 2 MDST +R R 1919 o - Jul 1 0u 1 MSD +R R 1919 o - Au 16 0 0 MSK +R R 1921 o - F 14 23 1 MSD +R R 1921 o - Mar 20 23 2 +05 +R R 1921 o - S 1 0 1 MSD +R R 1921 o - O 1 0 0 - +R R 1981 1984 - Ap 1 0 1 S +R R 1981 1983 - O 1 0 0 - +R R 1984 1995 - S lastSun 2s 0 - +R R 1985 2010 - Mar lastSun 2s 1 S +R R 1996 2010 - O lastSun 2s 0 - +Z WET 0 E WE%sT +Z CET 1 c CE%sT +Z MET 1 c ME%sT +Z EET 2 E EE%sT +R q 1940 o - Jun 16 0 1 S +R q 1942 o - N 2 3 0 - +R q 1943 o - Mar 29 2 1 S +R q 1943 o - Ap 10 3 0 - +R q 1974 o - May 4 0 1 S +R q 1974 o - O 2 0 0 - +R q 1975 o - May 1 0 1 S +R q 1975 o - O 2 0 0 - +R q 1976 o - May 2 0 1 S +R q 1976 o - O 3 0 0 - +R q 1977 o - May 8 0 1 S +R q 1977 o - O 2 0 0 - +R q 1978 o - May 6 0 1 S +R q 1978 o - O 1 0 0 - +R q 1979 o - May 5 0 1 S +R q 1979 o - S 30 0 0 - +R q 1980 o - May 3 0 1 S +R q 1980 o - O 4 0 0 - +R q 1981 o - Ap 26 0 1 S +R q 1981 o - S 27 0 0 - +R q 1982 o - May 2 0 1 S +R q 1982 o - O 3 0 0 - +R q 1983 o - Ap 18 0 1 S +R q 1983 o - O 1 0 0 - +R q 1984 o - Ap 1 0 1 S +Z Europe/Tirane 1:19:20 - LMT 1914 +1 - CET 1940 Jun 16 +1 q CE%sT 1984 Jul +1 E CE%sT +Z Europe/Andorra 0:6:4 - LMT 1901 +0 - WET 1946 S 30 +1 - CET 1985 Mar 31 2 +1 E CE%sT +R a 1920 o - Ap 5 2s 1 S +R a 1920 o - S 13 2s 0 - +R a 1946 o - Ap 14 2s 1 S +R a 1946 1948 - O Sun>=1 2s 0 - +R a 1947 o - Ap 6 2s 1 S +R a 1948 o - Ap 18 2s 1 S +R a 1980 o - Ap 6 0 1 S +R a 1980 o - S 28 0 0 - +Z Europe/Vienna 1:5:21 - LMT 1893 Ap +1 c CE%sT 1920 +1 a CE%sT 1940 Ap 1 2s +1 c CE%sT 1945 Ap 2 2s +1 1 CEST 1945 Ap 12 2s +1 - CET 1946 +1 a CE%sT 1981 +1 E CE%sT +Z Europe/Minsk 1:50:16 - LMT 1880 +1:50 - MMT 1924 May 2 +2 - EET 1930 Jun 21 +3 - MSK 1941 Jun 28 +1 c CE%sT 1944 Jul 3 +3 R MSK/MSD 1990 +3 - MSK 1991 Mar 31 2s +2 R EE%sT 2011 Mar 27 2s +3 - +03 +R b 1918 o - Mar 9 0s 1 S +R b 1918 1919 - O Sat>=1 23s 0 - +R b 1919 o - Mar 1 23s 1 S +R b 1920 o - F 14 23s 1 S +R b 1920 o - O 23 23s 0 - +R b 1921 o - Mar 14 23s 1 S +R b 1921 o - O 25 23s 0 - +R b 1922 o - Mar 25 23s 1 S +R b 1922 1927 - O Sat>=1 23s 0 - +R b 1923 o - Ap 21 23s 1 S +R b 1924 o - Mar 29 23s 1 S +R b 1925 o - Ap 4 23s 1 S +R b 1926 o - Ap 17 23s 1 S +R b 1927 o - Ap 9 23s 1 S +R b 1928 o - Ap 14 23s 1 S +R b 1928 1938 - O Sun>=2 2s 0 - +R b 1929 o - Ap 21 2s 1 S +R b 1930 o - Ap 13 2s 1 S +R b 1931 o - Ap 19 2s 1 S +R b 1932 o - Ap 3 2s 1 S +R b 1933 o - Mar 26 2s 1 S +R b 1934 o - Ap 8 2s 1 S +R b 1935 o - Mar 31 2s 1 S +R b 1936 o - Ap 19 2s 1 S +R b 1937 o - Ap 4 2s 1 S +R b 1938 o - Mar 27 2s 1 S +R b 1939 o - Ap 16 2s 1 S +R b 1939 o - N 19 2s 0 - +R b 1940 o - F 25 2s 1 S +R b 1944 o - S 17 2s 0 - +R b 1945 o - Ap 2 2s 1 S +R b 1945 o - S 16 2s 0 - +R b 1946 o - May 19 2s 1 S +R b 1946 o - O 7 2s 0 - +Z Europe/Brussels 0:17:30 - LMT 1880 +0:17:30 - BMT 1892 May 1 12 +0 - WET 1914 N 8 +1 - CET 1916 May +1 c CE%sT 1918 N 11 11u +0 b WE%sT 1940 May 20 2s +1 c CE%sT 1944 S 3 +1 b CE%sT 1977 +1 E CE%sT +R BG 1979 o - Mar 31 23 1 S +R BG 1979 o - O 1 1 0 - +R BG 1980 1982 - Ap Sat>=1 23 1 S +R BG 1980 o - S 29 1 0 - +R BG 1981 o - S 27 2 0 - +Z Europe/Sofia 1:33:16 - LMT 1880 +1:56:56 - IMT 1894 N 30 +2 - EET 1942 N 2 3 +1 c CE%sT 1945 +1 - CET 1945 Ap 2 3 +2 - EET 1979 Mar 31 23 +2 BG EE%sT 1982 S 26 3 +2 c EE%sT 1991 +2 e EE%sT 1997 +2 E EE%sT +R CZ 1945 o - Ap M>=1 2s 1 S +R CZ 1945 o - O 1 2s 0 - +R CZ 1946 o - May 6 2s 1 S +R CZ 1946 1949 - O Sun>=1 2s 0 - +R CZ 1947 1948 - Ap Sun>=15 2s 1 S +R CZ 1949 o - Ap 9 2s 1 S +Z Europe/Prague 0:57:44 - LMT 1850 +0:57:44 - PMT 1891 O +1 c CE%sT 1945 May 9 +1 CZ CE%sT 1946 D 1 3 +1 -1 GMT 1947 F 23 2 +1 CZ CE%sT 1979 +1 E CE%sT +R D 1916 o - May 14 23 1 S +R D 1916 o - S 30 23 0 - +R D 1940 o - May 15 0 1 S +R D 1945 o - Ap 2 2s 1 S +R D 1945 o - Au 15 2s 0 - +R D 1946 o - May 1 2s 1 S +R D 1946 o - S 1 2s 0 - +R D 1947 o - May 4 2s 1 S +R D 1947 o - Au 10 2s 0 - +R D 1948 o - May 9 2s 1 S +R D 1948 o - Au 8 2s 0 - +Z Europe/Copenhagen 0:50:20 - LMT 1890 +0:50:20 - CMT 1894 +1 D CE%sT 1942 N 2 2s +1 c CE%sT 1945 Ap 2 2 +1 D CE%sT 1980 +1 E CE%sT +Z Atlantic/Faroe -0:27:4 - LMT 1908 Ja 11 +0 - WET 1981 +0 E WE%sT +R Th 1991 1992 - Mar lastSun 2 1 D +R Th 1991 1992 - S lastSun 2 0 S +R Th 1993 2006 - Ap Sun>=1 2 1 D +R Th 1993 2006 - O lastSun 2 0 S +R Th 2007 ma - Mar Sun>=8 2 1 D +R Th 2007 ma - N Sun>=1 2 0 S +Z America/Danmarkshavn -1:14:40 - LMT 1916 Jul 28 +-3 - -03 1980 Ap 6 2 +-3 E -03/-02 1996 +0 - GMT +Z America/Scoresbysund -1:27:52 - LMT 1916 Jul 28 +-2 - -02 1980 Ap 6 2 +-2 c -02/-01 1981 Mar 29 +-1 E -01/+00 +Z America/Godthab -3:26:56 - LMT 1916 Jul 28 +-3 - -03 1980 Ap 6 2 +-3 E -03/-02 +Z America/Thule -4:35:8 - LMT 1916 Jul 28 +-4 Th A%sT +Z Europe/Tallinn 1:39 - LMT 1880 +1:39 - TMT 1918 F +1 c CE%sT 1919 Jul +1:39 - TMT 1921 May +2 - EET 1940 Au 6 +3 - MSK 1941 S 15 +1 c CE%sT 1944 S 22 +3 R MSK/MSD 1989 Mar 26 2s +2 1 EEST 1989 S 24 2s +2 c EE%sT 1998 S 22 +2 E EE%sT 1999 O 31 4 +2 - EET 2002 F 21 +2 E EE%sT +R FI 1942 o - Ap 2 24 1 S +R FI 1942 o - O 4 1 0 - +R FI 1981 1982 - Mar lastSun 2 1 S +R FI 1981 1982 - S lastSun 3 0 - +Z Europe/Helsinki 1:39:49 - LMT 1878 May 31 +1:39:49 - HMT 1921 May +2 FI EE%sT 1983 +2 E EE%sT +Li Europe/Helsinki Europe/Mariehamn +R F 1916 o - Jun 14 23s 1 S +R F 1916 1919 - O Sun>=1 23s 0 - +R F 1917 o - Mar 24 23s 1 S +R F 1918 o - Mar 9 23s 1 S +R F 1919 o - Mar 1 23s 1 S +R F 1920 o - F 14 23s 1 S +R F 1920 o - O 23 23s 0 - +R F 1921 o - Mar 14 23s 1 S +R F 1921 o - O 25 23s 0 - +R F 1922 o - Mar 25 23s 1 S +R F 1922 1938 - O Sat>=1 23s 0 - +R F 1923 o - May 26 23s 1 S +R F 1924 o - Mar 29 23s 1 S +R F 1925 o - Ap 4 23s 1 S +R F 1926 o - Ap 17 23s 1 S +R F 1927 o - Ap 9 23s 1 S +R F 1928 o - Ap 14 23s 1 S +R F 1929 o - Ap 20 23s 1 S +R F 1930 o - Ap 12 23s 1 S +R F 1931 o - Ap 18 23s 1 S +R F 1932 o - Ap 2 23s 1 S +R F 1933 o - Mar 25 23s 1 S +R F 1934 o - Ap 7 23s 1 S +R F 1935 o - Mar 30 23s 1 S +R F 1936 o - Ap 18 23s 1 S +R F 1937 o - Ap 3 23s 1 S +R F 1938 o - Mar 26 23s 1 S +R F 1939 o - Ap 15 23s 1 S +R F 1939 o - N 18 23s 0 - +R F 1940 o - F 25 2 1 S +R F 1941 o - May 5 0 2 M +R F 1941 o - O 6 0 1 S +R F 1942 o - Mar 9 0 2 M +R F 1942 o - N 2 3 1 S +R F 1943 o - Mar 29 2 2 M +R F 1943 o - O 4 3 1 S +R F 1944 o - Ap 3 2 2 M +R F 1944 o - O 8 1 1 S +R F 1945 o - Ap 2 2 2 M +R F 1945 o - S 16 3 0 - +R F 1976 o - Mar 28 1 1 S +R F 1976 o - S 26 1 0 - +Z Europe/Paris 0:9:21 - LMT 1891 Mar 15 0:1 +0:9:21 - PMT 1911 Mar 11 0:1 +0 F WE%sT 1940 Jun 14 23 +1 c CE%sT 1944 Au 25 +0 F WE%sT 1945 S 16 3 +1 F CE%sT 1977 +1 E CE%sT +R DE 1946 o - Ap 14 2s 1 S +R DE 1946 o - O 7 2s 0 - +R DE 1947 1949 - O Sun>=1 2s 0 - +R DE 1947 o - Ap 6 3s 1 S +R DE 1947 o - May 11 2s 2 M +R DE 1947 o - Jun 29 3 1 S +R DE 1948 o - Ap 18 2s 1 S +R DE 1949 o - Ap 10 2s 1 S +R So 1945 o - May 24 2 2 M +R So 1945 o - S 24 3 1 S +R So 1945 o - N 18 2s 0 - +Z Europe/Berlin 0:53:28 - LMT 1893 Ap +1 c CE%sT 1945 May 24 2 +1 So CE%sT 1946 +1 DE CE%sT 1980 +1 E CE%sT +Li Europe/Zurich Europe/Busingen +Z Europe/Gibraltar -0:21:24 - LMT 1880 Au 2 0s +0 G %s 1957 Ap 14 2 +1 - CET 1982 +1 E CE%sT +R g 1932 o - Jul 7 0 1 S +R g 1932 o - S 1 0 0 - +R g 1941 o - Ap 7 0 1 S +R g 1942 o - N 2 3 0 - +R g 1943 o - Mar 30 0 1 S +R g 1943 o - O 4 0 0 - +R g 1952 o - Jul 1 0 1 S +R g 1952 o - N 2 0 0 - +R g 1975 o - Ap 12 0s 1 S +R g 1975 o - N 26 0s 0 - +R g 1976 o - Ap 11 2s 1 S +R g 1976 o - O 10 2s 0 - +R g 1977 1978 - Ap Sun>=1 2s 1 S +R g 1977 o - S 26 2s 0 - +R g 1978 o - S 24 4 0 - +R g 1979 o - Ap 1 9 1 S +R g 1979 o - S 29 2 0 - +R g 1980 o - Ap 1 0 1 S +R g 1980 o - S 28 0 0 - +Z Europe/Athens 1:34:52 - LMT 1895 S 14 +1:34:52 - AMT 1916 Jul 28 0:1 +2 g EE%sT 1941 Ap 30 +1 g CE%sT 1944 Ap 4 +2 g EE%sT 1981 +2 E EE%sT +R h 1918 o - Ap 1 3 1 S +R h 1918 o - S 16 3 0 - +R h 1919 o - Ap 15 3 1 S +R h 1919 o - N 24 3 0 - +R h 1945 o - May 1 23 1 S +R h 1945 o - N 1 0 0 - +R h 1946 o - Mar 31 2s 1 S +R h 1946 1949 - O Sun>=1 2s 0 - +R h 1947 1949 - Ap Sun>=4 2s 1 S +R h 1950 o - Ap 17 2s 1 S +R h 1950 o - O 23 2s 0 - +R h 1954 1955 - May 23 0 1 S +R h 1954 1955 - O 3 0 0 - +R h 1956 o - Jun Sun>=1 0 1 S +R h 1956 o - S lastSun 0 0 - +R h 1957 o - Jun Sun>=1 1 1 S +R h 1957 o - S lastSun 3 0 - +R h 1980 o - Ap 6 1 1 S +Z Europe/Budapest 1:16:20 - LMT 1890 O +1 c CE%sT 1918 +1 h CE%sT 1941 Ap 8 +1 c CE%sT 1945 +1 h CE%sT 1980 S 28 2s +1 E CE%sT +R w 1917 1919 - F 19 23 1 - +R w 1917 o - O 21 1 0 - +R w 1918 1919 - N 16 1 0 - +R w 1921 o - Mar 19 23 1 - +R w 1921 o - Jun 23 1 0 - +R w 1939 o - Ap 29 23 1 - +R w 1939 o - O 29 2 0 - +R w 1940 o - F 25 2 1 - +R w 1940 1941 - N Sun>=2 1s 0 - +R w 1941 1942 - Mar Sun>=2 1s 1 - +R w 1943 1946 - Mar Sun>=1 1s 1 - +R w 1942 1948 - O Sun>=22 1s 0 - +R w 1947 1967 - Ap Sun>=1 1s 1 - +R w 1949 o - O 30 1s 0 - +R w 1950 1966 - O Sun>=22 1s 0 - +R w 1967 o - O 29 1s 0 - +Z Atlantic/Reykjavik -1:28 - LMT 1908 +-1 w -01/+00 1968 Ap 7 1s +0 - GMT +R I 1916 o - Jun 3 24 1 S +R I 1916 1917 - S 30 24 0 - +R I 1917 o - Mar 31 24 1 S +R I 1918 o - Mar 9 24 1 S +R I 1918 o - O 6 24 0 - +R I 1919 o - Mar 1 24 1 S +R I 1919 o - O 4 24 0 - +R I 1920 o - Mar 20 24 1 S +R I 1920 o - S 18 24 0 - +R I 1940 o - Jun 14 24 1 S +R I 1942 o - N 2 2s 0 - +R I 1943 o - Mar 29 2s 1 S +R I 1943 o - O 4 2s 0 - +R I 1944 o - Ap 2 2s 1 S +R I 1944 o - S 17 2s 0 - +R I 1945 o - Ap 2 2 1 S +R I 1945 o - S 15 1 0 - +R I 1946 o - Mar 17 2s 1 S +R I 1946 o - O 6 2s 0 - +R I 1947 o - Mar 16 0s 1 S +R I 1947 o - O 5 0s 0 - +R I 1948 o - F 29 2s 1 S +R I 1948 o - O 3 2s 0 - +R I 1966 1968 - May Sun>=22 0s 1 S +R I 1966 o - S 24 24 0 - +R I 1967 1969 - S Sun>=22 0s 0 - +R I 1969 o - Jun 1 0s 1 S +R I 1970 o - May 31 0s 1 S +R I 1970 o - S lastSun 0s 0 - +R I 1971 1972 - May Sun>=22 0s 1 S +R I 1971 o - S lastSun 0s 0 - +R I 1972 o - O 1 0s 0 - +R I 1973 o - Jun 3 0s 1 S +R I 1973 1974 - S lastSun 0s 0 - +R I 1974 o - May 26 0s 1 S +R I 1975 o - Jun 1 0s 1 S +R I 1975 1977 - S lastSun 0s 0 - +R I 1976 o - May 30 0s 1 S +R I 1977 1979 - May Sun>=22 0s 1 S +R I 1978 o - O 1 0s 0 - +R I 1979 o - S 30 0s 0 - +Z Europe/Rome 0:49:56 - LMT 1866 S 22 +0:49:56 - RMT 1893 O 31 23:49:56 +1 I CE%sT 1943 S 10 +1 c CE%sT 1944 Jun 4 +1 I CE%sT 1980 +1 E CE%sT +Li Europe/Rome Europe/Vatican +Li Europe/Rome Europe/San_Marino +R LV 1989 1996 - Mar lastSun 2s 1 S +R LV 1989 1996 - S lastSun 2s 0 - +Z Europe/Riga 1:36:34 - LMT 1880 +1:36:34 - RMT 1918 Ap 15 2 +1:36:34 1 LST 1918 S 16 3 +1:36:34 - RMT 1919 Ap 1 2 +1:36:34 1 LST 1919 May 22 3 +1:36:34 - RMT 1926 May 11 +2 - EET 1940 Au 5 +3 - MSK 1941 Jul +1 c CE%sT 1944 O 13 +3 R MSK/MSD 1989 Mar lastSun 2s +2 1 EEST 1989 S lastSun 2s +2 LV EE%sT 1997 Ja 21 +2 E EE%sT 2000 F 29 +2 - EET 2001 Ja 2 +2 E EE%sT +Li Europe/Zurich Europe/Vaduz +Z Europe/Vilnius 1:41:16 - LMT 1880 +1:24 - WMT 1917 +1:35:36 - KMT 1919 O 10 +1 - CET 1920 Jul 12 +2 - EET 1920 O 9 +1 - CET 1940 Au 3 +3 - MSK 1941 Jun 24 +1 c CE%sT 1944 Au +3 R MSK/MSD 1989 Mar 26 2s +2 R EE%sT 1991 S 29 2s +2 c EE%sT 1998 +2 - EET 1998 Mar 29 1u +1 E CE%sT 1999 O 31 1u +2 - EET 2003 +2 E EE%sT +R LX 1916 o - May 14 23 1 S +R LX 1916 o - O 1 1 0 - +R LX 1917 o - Ap 28 23 1 S +R LX 1917 o - S 17 1 0 - +R LX 1918 o - Ap M>=15 2s 1 S +R LX 1918 o - S M>=15 2s 0 - +R LX 1919 o - Mar 1 23 1 S +R LX 1919 o - O 5 3 0 - +R LX 1920 o - F 14 23 1 S +R LX 1920 o - O 24 2 0 - +R LX 1921 o - Mar 14 23 1 S +R LX 1921 o - O 26 2 0 - +R LX 1922 o - Mar 25 23 1 S +R LX 1922 o - O Sun>=2 1 0 - +R LX 1923 o - Ap 21 23 1 S +R LX 1923 o - O Sun>=2 2 0 - +R LX 1924 o - Mar 29 23 1 S +R LX 1924 1928 - O Sun>=2 1 0 - +R LX 1925 o - Ap 5 23 1 S +R LX 1926 o - Ap 17 23 1 S +R LX 1927 o - Ap 9 23 1 S +R LX 1928 o - Ap 14 23 1 S +R LX 1929 o - Ap 20 23 1 S +Z Europe/Luxembourg 0:24:36 - LMT 1904 Jun +1 LX CE%sT 1918 N 25 +0 LX WE%sT 1929 O 6 2s +0 b WE%sT 1940 May 14 3 +1 c WE%sT 1944 S 18 3 +1 b CE%sT 1977 +1 E CE%sT +R MT 1973 o - Mar 31 0s 1 S +R MT 1973 o - S 29 0s 0 - +R MT 1974 o - Ap 21 0s 1 S +R MT 1974 o - S 16 0s 0 - +R MT 1975 1979 - Ap Sun>=15 2 1 S +R MT 1975 1980 - S Sun>=15 2 0 - +R MT 1980 o - Mar 31 2 1 S +Z Europe/Malta 0:58:4 - LMT 1893 N 2 0s +1 I CE%sT 1973 Mar 31 +1 MT CE%sT 1981 +1 E CE%sT +R MD 1997 ma - Mar lastSun 2 1 S +R MD 1997 ma - O lastSun 3 0 - +Z Europe/Chisinau 1:55:20 - LMT 1880 +1:55 - CMT 1918 F 15 +1:44:24 - BMT 1931 Jul 24 +2 z EE%sT 1940 Au 15 +2 1 EEST 1941 Jul 17 +1 c CE%sT 1944 Au 24 +3 R MSK/MSD 1990 May 6 2 +2 R EE%sT 1992 +2 e EE%sT 1997 +2 MD EE%sT +Z Europe/Monaco 0:29:32 - LMT 1891 Mar 15 +0:9:21 - PMT 1911 Mar 11 +0 F WE%sT 1945 S 16 3 +1 F CE%sT 1977 +1 E CE%sT +R N 1916 o - May 1 0 1 NST +R N 1916 o - O 1 0 0 AMT +R N 1917 o - Ap 16 2s 1 NST +R N 1917 o - S 17 2s 0 AMT +R N 1918 1921 - Ap M>=1 2s 1 NST +R N 1918 1921 - S lastM 2s 0 AMT +R N 1922 o - Mar lastSun 2s 1 NST +R N 1922 1936 - O Sun>=2 2s 0 AMT +R N 1923 o - Jun F>=1 2s 1 NST +R N 1924 o - Mar lastSun 2s 1 NST +R N 1925 o - Jun F>=1 2s 1 NST +R N 1926 1931 - May 15 2s 1 NST +R N 1932 o - May 22 2s 1 NST +R N 1933 1936 - May 15 2s 1 NST +R N 1937 o - May 22 2s 1 NST +R N 1937 o - Jul 1 0 1 S +R N 1937 1939 - O Sun>=2 2s 0 - +R N 1938 1939 - May 15 2s 1 S +R N 1945 o - Ap 2 2s 1 S +R N 1945 o - S 16 2s 0 - +Z Europe/Amsterdam 0:19:32 - LMT 1835 +0:19:32 N %s 1937 Jul +0:20 N +0020/+0120 1940 May 16 +1 c CE%sT 1945 Ap 2 2 +1 N CE%sT 1977 +1 E CE%sT +R NO 1916 o - May 22 1 1 S +R NO 1916 o - S 30 0 0 - +R NO 1945 o - Ap 2 2s 1 S +R NO 1945 o - O 1 2s 0 - +R NO 1959 1964 - Mar Sun>=15 2s 1 S +R NO 1959 1965 - S Sun>=15 2s 0 - +R NO 1965 o - Ap 25 2s 1 S +Z Europe/Oslo 0:43 - LMT 1895 +1 NO CE%sT 1940 Au 10 23 +1 c CE%sT 1945 Ap 2 2 +1 NO CE%sT 1980 +1 E CE%sT +Li Europe/Oslo Arctic/Longyearbyen +R O 1918 1919 - S 16 2s 0 - +R O 1919 o - Ap 15 2s 1 S +R O 1944 o - Ap 3 2s 1 S +R O 1944 o - O 4 2 0 - +R O 1945 o - Ap 29 0 1 S +R O 1945 o - N 1 0 0 - +R O 1946 o - Ap 14 0s 1 S +R O 1946 o - O 7 2s 0 - +R O 1947 o - May 4 2s 1 S +R O 1947 1949 - O Sun>=1 2s 0 - +R O 1948 o - Ap 18 2s 1 S +R O 1949 o - Ap 10 2s 1 S +R O 1957 o - Jun 2 1s 1 S +R O 1957 1958 - S lastSun 1s 0 - +R O 1958 o - Mar 30 1s 1 S +R O 1959 o - May 31 1s 1 S +R O 1959 1961 - O Sun>=1 1s 0 - +R O 1960 o - Ap 3 1s 1 S +R O 1961 1964 - May lastSun 1s 1 S +R O 1962 1964 - S lastSun 1s 0 - +Z Europe/Warsaw 1:24 - LMT 1880 +1:24 - WMT 1915 Au 5 +1 c CE%sT 1918 S 16 3 +2 O EE%sT 1922 Jun +1 O CE%sT 1940 Jun 23 2 +1 c CE%sT 1944 O +1 O CE%sT 1977 +1 W- CE%sT 1988 +1 E CE%sT +R p 1916 o - Jun 17 23 1 S +R p 1916 o - N 1 1 0 - +R p 1917 o - F 28 23s 1 S +R p 1917 1921 - O 14 23s 0 - +R p 1918 o - Mar 1 23s 1 S +R p 1919 o - F 28 23s 1 S +R p 1920 o - F 29 23s 1 S +R p 1921 o - F 28 23s 1 S +R p 1924 o - Ap 16 23s 1 S +R p 1924 o - O 14 23s 0 - +R p 1926 o - Ap 17 23s 1 S +R p 1926 1929 - O Sat>=1 23s 0 - +R p 1927 o - Ap 9 23s 1 S +R p 1928 o - Ap 14 23s 1 S +R p 1929 o - Ap 20 23s 1 S +R p 1931 o - Ap 18 23s 1 S +R p 1931 1932 - O Sat>=1 23s 0 - +R p 1932 o - Ap 2 23s 1 S +R p 1934 o - Ap 7 23s 1 S +R p 1934 1938 - O Sat>=1 23s 0 - +R p 1935 o - Mar 30 23s 1 S +R p 1936 o - Ap 18 23s 1 S +R p 1937 o - Ap 3 23s 1 S +R p 1938 o - Mar 26 23s 1 S +R p 1939 o - Ap 15 23s 1 S +R p 1939 o - N 18 23s 0 - +R p 1940 o - F 24 23s 1 S +R p 1940 1941 - O 5 23s 0 - +R p 1941 o - Ap 5 23s 1 S +R p 1942 1945 - Mar Sat>=8 23s 1 S +R p 1942 o - Ap 25 22s 2 M +R p 1942 o - Au 15 22s 1 S +R p 1942 1945 - O Sat>=24 23s 0 - +R p 1943 o - Ap 17 22s 2 M +R p 1943 1945 - Au Sat>=25 22s 1 S +R p 1944 1945 - Ap Sat>=21 22s 2 M +R p 1946 o - Ap Sat>=1 23s 1 S +R p 1946 o - O Sat>=1 23s 0 - +R p 1947 1949 - Ap Sun>=1 2s 1 S +R p 1947 1949 - O Sun>=1 2s 0 - +R p 1951 1965 - Ap Sun>=1 2s 1 S +R p 1951 1965 - O Sun>=1 2s 0 - +R p 1977 o - Mar 27 0s 1 S +R p 1977 o - S 25 0s 0 - +R p 1978 1979 - Ap Sun>=1 0s 1 S +R p 1978 o - O 1 0s 0 - +R p 1979 1982 - S lastSun 1s 0 - +R p 1980 o - Mar lastSun 0s 1 S +R p 1981 1982 - Mar lastSun 1s 1 S +R p 1983 o - Mar lastSun 2s 1 S +Z Europe/Lisbon -0:36:45 - LMT 1884 +-0:36:45 - LMT 1912 Ja 1 0u +0 p WE%sT 1966 Ap 3 2 +1 - CET 1976 S 26 1 +0 p WE%sT 1983 S 25 1s +0 W- WE%sT 1992 S 27 1s +1 E CE%sT 1996 Mar 31 1u +0 E WE%sT +Z Atlantic/Azores -1:42:40 - LMT 1884 +-1:54:32 - HMT 1912 Ja 1 2u +-2 p -02/-01 1942 Ap 25 22s +-2 p +00 1942 Au 15 22s +-2 p -02/-01 1943 Ap 17 22s +-2 p +00 1943 Au 28 22s +-2 p -02/-01 1944 Ap 22 22s +-2 p +00 1944 Au 26 22s +-2 p -02/-01 1945 Ap 21 22s +-2 p +00 1945 Au 25 22s +-2 p -02/-01 1966 Ap 3 2 +-1 p -01/+00 1983 S 25 1s +-1 W- -01/+00 1992 S 27 1s +0 E WE%sT 1993 Mar 28 1u +-1 E -01/+00 +Z Atlantic/Madeira -1:7:36 - LMT 1884 +-1:7:36 - FMT 1912 Ja 1 1u +-1 p -01/+00 1942 Ap 25 22s +-1 p +01 1942 Au 15 22s +-1 p -01/+00 1943 Ap 17 22s +-1 p +01 1943 Au 28 22s +-1 p -01/+00 1944 Ap 22 22s +-1 p +01 1944 Au 26 22s +-1 p -01/+00 1945 Ap 21 22s +-1 p +01 1945 Au 25 22s +-1 p -01/+00 1966 Ap 3 2 +0 p WE%sT 1983 S 25 1s +0 E WE%sT +R z 1932 o - May 21 0s 1 S +R z 1932 1939 - O Sun>=1 0s 0 - +R z 1933 1939 - Ap Sun>=2 0s 1 S +R z 1979 o - May 27 0 1 S +R z 1979 o - S lastSun 0 0 - +R z 1980 o - Ap 5 23 1 S +R z 1980 o - S lastSun 1 0 - +R z 1991 1993 - Mar lastSun 0s 1 S +R z 1991 1993 - S lastSun 0s 0 - +Z Europe/Bucharest 1:44:24 - LMT 1891 O +1:44:24 - BMT 1931 Jul 24 +2 z EE%sT 1981 Mar 29 2s +2 c EE%sT 1991 +2 z EE%sT 1994 +2 e EE%sT 1997 +2 E EE%sT +Z Europe/Kaliningrad 1:22 - LMT 1893 Ap +1 c CE%sT 1945 +2 O CE%sT 1946 +3 R MSK/MSD 1989 Mar 26 2s +2 R EE%sT 2011 Mar 27 2s +3 - +03 2014 O 26 2s +2 - EET +Z Europe/Moscow 2:30:17 - LMT 1880 +2:30:17 - MMT 1916 Jul 3 +2:31:19 R %s 1919 Jul 1 0u +3 R %s 1921 O +3 R MSK/MSD 1922 O +2 - EET 1930 Jun 21 +3 R MSK/MSD 1991 Mar 31 2s +2 R EE%sT 1992 Ja 19 2s +3 R MSK/MSD 2011 Mar 27 2s +4 - MSK 2014 O 26 2s +3 - MSK +Z Europe/Simferopol 2:16:24 - LMT 1880 +2:16 - SMT 1924 May 2 +2 - EET 1930 Jun 21 +3 - MSK 1941 N +1 c CE%sT 1944 Ap 13 +3 R MSK/MSD 1990 +3 - MSK 1990 Jul 1 2 +2 - EET 1992 +2 e EE%sT 1994 May +3 e MSK/MSD 1996 Mar 31 0s +3 1 MSD 1996 O 27 3s +3 R MSK/MSD 1997 +3 - MSK 1997 Mar lastSun 1u +2 E EE%sT 2014 Mar 30 2 +4 - MSK 2014 O 26 2s +3 - MSK +Z Europe/Astrakhan 3:12:12 - LMT 1924 May +3 - +03 1930 Jun 21 +4 R +04/+05 1989 Mar 26 2s +3 R +03/+04 1991 Mar 31 2s +4 - +04 1992 Mar 29 2s +3 R +03/+04 2011 Mar 27 2s +4 - +04 2014 O 26 2s +3 - +03 2016 Mar 27 2s +4 - +04 +Z Europe/Volgograd 2:57:40 - LMT 1920 Ja 3 +3 - +03 1930 Jun 21 +4 - +04 1961 N 11 +4 R +04/+05 1988 Mar 27 2s +3 R +03/+04 1991 Mar 31 2s +4 - +04 1992 Mar 29 2s +3 R +03/+04 2011 Mar 27 2s +4 - +04 2014 O 26 2s +3 - +03 2018 O 28 2s +4 - +04 +Z Europe/Saratov 3:4:18 - LMT 1919 Jul 1 0u +3 - +03 1930 Jun 21 +4 R +04/+05 1988 Mar 27 2s +3 R +03/+04 1991 Mar 31 2s +4 - +04 1992 Mar 29 2s +3 R +03/+04 2011 Mar 27 2s +4 - +04 2014 O 26 2s +3 - +03 2016 D 4 2s +4 - +04 +Z Europe/Kirov 3:18:48 - LMT 1919 Jul 1 0u +3 - +03 1930 Jun 21 +4 R +04/+05 1989 Mar 26 2s +3 R +03/+04 1991 Mar 31 2s +4 - +04 1992 Mar 29 2s +3 R +03/+04 2011 Mar 27 2s +4 - +04 2014 O 26 2s +3 - +03 +Z Europe/Samara 3:20:20 - LMT 1919 Jul 1 0u +3 - +03 1930 Jun 21 +4 - +04 1935 Ja 27 +4 R +04/+05 1989 Mar 26 2s +3 R +03/+04 1991 Mar 31 2s +2 R +02/+03 1991 S 29 2s +3 - +03 1991 O 20 3 +4 R +04/+05 2010 Mar 28 2s +3 R +03/+04 2011 Mar 27 2s +4 - +04 +Z Europe/Ulyanovsk 3:13:36 - LMT 1919 Jul 1 0u +3 - +03 1930 Jun 21 +4 R +04/+05 1989 Mar 26 2s +3 R +03/+04 1991 Mar 31 2s +2 R +02/+03 1992 Ja 19 2s +3 R +03/+04 2011 Mar 27 2s +4 - +04 2014 O 26 2s +3 - +03 2016 Mar 27 2s +4 - +04 +Z Asia/Yekaterinburg 4:2:33 - LMT 1916 Jul 3 +3:45:5 - PMT 1919 Jul 15 4 +4 - +04 1930 Jun 21 +5 R +05/+06 1991 Mar 31 2s +4 R +04/+05 1992 Ja 19 2s +5 R +05/+06 2011 Mar 27 2s +6 - +06 2014 O 26 2s +5 - +05 +Z Asia/Omsk 4:53:30 - LMT 1919 N 14 +5 - +05 1930 Jun 21 +6 R +06/+07 1991 Mar 31 2s +5 R +05/+06 1992 Ja 19 2s +6 R +06/+07 2011 Mar 27 2s +7 - +07 2014 O 26 2s +6 - +06 +Z Asia/Barnaul 5:35 - LMT 1919 D 10 +6 - +06 1930 Jun 21 +7 R +07/+08 1991 Mar 31 2s +6 R +06/+07 1992 Ja 19 2s +7 R +07/+08 1995 May 28 +6 R +06/+07 2011 Mar 27 2s +7 - +07 2014 O 26 2s +6 - +06 2016 Mar 27 2s +7 - +07 +Z Asia/Novosibirsk 5:31:40 - LMT 1919 D 14 6 +6 - +06 1930 Jun 21 +7 R +07/+08 1991 Mar 31 2s +6 R +06/+07 1992 Ja 19 2s +7 R +07/+08 1993 May 23 +6 R +06/+07 2011 Mar 27 2s +7 - +07 2014 O 26 2s +6 - +06 2016 Jul 24 2s +7 - +07 +Z Asia/Tomsk 5:39:51 - LMT 1919 D 22 +6 - +06 1930 Jun 21 +7 R +07/+08 1991 Mar 31 2s +6 R +06/+07 1992 Ja 19 2s +7 R +07/+08 2002 May 1 3 +6 R +06/+07 2011 Mar 27 2s +7 - +07 2014 O 26 2s +6 - +06 2016 May 29 2s +7 - +07 +Z Asia/Novokuznetsk 5:48:48 - LMT 1924 May +6 - +06 1930 Jun 21 +7 R +07/+08 1991 Mar 31 2s +6 R +06/+07 1992 Ja 19 2s +7 R +07/+08 2010 Mar 28 2s +6 R +06/+07 2011 Mar 27 2s +7 - +07 +Z Asia/Krasnoyarsk 6:11:26 - LMT 1920 Ja 6 +6 - +06 1930 Jun 21 +7 R +07/+08 1991 Mar 31 2s +6 R +06/+07 1992 Ja 19 2s +7 R +07/+08 2011 Mar 27 2s +8 - +08 2014 O 26 2s +7 - +07 +Z Asia/Irkutsk 6:57:5 - LMT 1880 +6:57:5 - IMT 1920 Ja 25 +7 - +07 1930 Jun 21 +8 R +08/+09 1991 Mar 31 2s +7 R +07/+08 1992 Ja 19 2s +8 R +08/+09 2011 Mar 27 2s +9 - +09 2014 O 26 2s +8 - +08 +Z Asia/Chita 7:33:52 - LMT 1919 D 15 +8 - +08 1930 Jun 21 +9 R +09/+10 1991 Mar 31 2s +8 R +08/+09 1992 Ja 19 2s +9 R +09/+10 2011 Mar 27 2s +10 - +10 2014 O 26 2s +8 - +08 2016 Mar 27 2 +9 - +09 +Z Asia/Yakutsk 8:38:58 - LMT 1919 D 15 +8 - +08 1930 Jun 21 +9 R +09/+10 1991 Mar 31 2s +8 R +08/+09 1992 Ja 19 2s +9 R +09/+10 2011 Mar 27 2s +10 - +10 2014 O 26 2s +9 - +09 +Z Asia/Vladivostok 8:47:31 - LMT 1922 N 15 +9 - +09 1930 Jun 21 +10 R +10/+11 1991 Mar 31 2s +9 R +09/+10 1992 Ja 19 2s +10 R +10/+11 2011 Mar 27 2s +11 - +11 2014 O 26 2s +10 - +10 +Z Asia/Khandyga 9:2:13 - LMT 1919 D 15 +8 - +08 1930 Jun 21 +9 R +09/+10 1991 Mar 31 2s +8 R +08/+09 1992 Ja 19 2s +9 R +09/+10 2004 +10 R +10/+11 2011 Mar 27 2s +11 - +11 2011 S 13 0s +10 - +10 2014 O 26 2s +9 - +09 +Z Asia/Sakhalin 9:30:48 - LMT 1905 Au 23 +9 - +09 1945 Au 25 +11 R +11/+12 1991 Mar 31 2s +10 R +10/+11 1992 Ja 19 2s +11 R +11/+12 1997 Mar lastSun 2s +10 R +10/+11 2011 Mar 27 2s +11 - +11 2014 O 26 2s +10 - +10 2016 Mar 27 2s +11 - +11 +Z Asia/Magadan 10:3:12 - LMT 1924 May 2 +10 - +10 1930 Jun 21 +11 R +11/+12 1991 Mar 31 2s +10 R +10/+11 1992 Ja 19 2s +11 R +11/+12 2011 Mar 27 2s +12 - +12 2014 O 26 2s +10 - +10 2016 Ap 24 2s +11 - +11 +Z Asia/Srednekolymsk 10:14:52 - LMT 1924 May 2 +10 - +10 1930 Jun 21 +11 R +11/+12 1991 Mar 31 2s +10 R +10/+11 1992 Ja 19 2s +11 R +11/+12 2011 Mar 27 2s +12 - +12 2014 O 26 2s +11 - +11 +Z Asia/Ust-Nera 9:32:54 - LMT 1919 D 15 +8 - +08 1930 Jun 21 +9 R +09/+10 1981 Ap +11 R +11/+12 1991 Mar 31 2s +10 R +10/+11 1992 Ja 19 2s +11 R +11/+12 2011 Mar 27 2s +12 - +12 2011 S 13 0s +11 - +11 2014 O 26 2s +10 - +10 +Z Asia/Kamchatka 10:34:36 - LMT 1922 N 10 +11 - +11 1930 Jun 21 +12 R +12/+13 1991 Mar 31 2s +11 R +11/+12 1992 Ja 19 2s +12 R +12/+13 2010 Mar 28 2s +11 R +11/+12 2011 Mar 27 2s +12 - +12 +Z Asia/Anadyr 11:49:56 - LMT 1924 May 2 +12 - +12 1930 Jun 21 +13 R +13/+14 1982 Ap 1 0s +12 R +12/+13 1991 Mar 31 2s +11 R +11/+12 1992 Ja 19 2s +12 R +12/+13 2010 Mar 28 2s +11 R +11/+12 2011 Mar 27 2s +12 - +12 +Z Europe/Belgrade 1:22 - LMT 1884 +1 - CET 1941 Ap 18 23 +1 c CE%sT 1945 +1 - CET 1945 May 8 2s +1 1 CEST 1945 S 16 2s +1 - CET 1982 N 27 +1 E CE%sT +Li Europe/Belgrade Europe/Ljubljana +Li Europe/Belgrade Europe/Podgorica +Li Europe/Belgrade Europe/Sarajevo +Li Europe/Belgrade Europe/Skopje +Li Europe/Belgrade Europe/Zagreb +Li Europe/Prague Europe/Bratislava +R s 1918 o - Ap 15 23 1 S +R s 1918 1919 - O 6 24s 0 - +R s 1919 o - Ap 6 23 1 S +R s 1924 o - Ap 16 23 1 S +R s 1924 o - O 4 24s 0 - +R s 1926 o - Ap 17 23 1 S +R s 1926 1929 - O Sat>=1 24s 0 - +R s 1927 o - Ap 9 23 1 S +R s 1928 o - Ap 15 0 1 S +R s 1929 o - Ap 20 23 1 S +R s 1937 o - Jun 16 23 1 S +R s 1937 o - O 2 24s 0 - +R s 1938 o - Ap 2 23 1 S +R s 1938 o - Ap 30 23 2 M +R s 1938 o - O 2 24 1 S +R s 1939 o - O 7 24s 0 - +R s 1942 o - May 2 23 1 S +R s 1942 o - S 1 1 0 - +R s 1943 1946 - Ap Sat>=13 23 1 S +R s 1943 1944 - O Sun>=1 1 0 - +R s 1945 1946 - S lastSun 1 0 - +R s 1949 o - Ap 30 23 1 S +R s 1949 o - O 2 1 0 - +R s 1974 1975 - Ap Sat>=12 23 1 S +R s 1974 1975 - O Sun>=1 1 0 - +R s 1976 o - Mar 27 23 1 S +R s 1976 1977 - S lastSun 1 0 - +R s 1977 o - Ap 2 23 1 S +R s 1978 o - Ap 2 2s 1 S +R s 1978 o - O 1 2s 0 - +R Sp 1967 o - Jun 3 12 1 S +R Sp 1967 o - O 1 0 0 - +R Sp 1974 o - Jun 24 0 1 S +R Sp 1974 o - S 1 0 0 - +R Sp 1976 1977 - May 1 0 1 S +R Sp 1976 o - Au 1 0 0 - +R Sp 1977 o - S 28 0 0 - +R Sp 1978 o - Jun 1 0 1 S +R Sp 1978 o - Au 4 0 0 - +Z Europe/Madrid -0:14:44 - LMT 1900 D 31 23:45:16 +0 s WE%sT 1940 Mar 16 23 +1 s CE%sT 1979 +1 E CE%sT +Z Africa/Ceuta -0:21:16 - LMT 1900 D 31 23:38:44 +0 - WET 1918 May 6 23 +0 1 WEST 1918 O 7 23 +0 - WET 1924 +0 s WE%sT 1929 +0 - WET 1967 +0 Sp WE%sT 1984 Mar 16 +1 - CET 1986 +1 E CE%sT +Z Atlantic/Canary -1:1:36 - LMT 1922 Mar +-1 - -01 1946 S 30 1 +0 - WET 1980 Ap 6 0s +0 1 WEST 1980 S 28 1u +0 E WE%sT +Z Europe/Stockholm 1:12:12 - LMT 1879 +1:0:14 - SET 1900 +1 - CET 1916 May 14 23 +1 1 CEST 1916 O 1 1 +1 - CET 1980 +1 E CE%sT +R CH 1941 1942 - May M>=1 1 1 S +R CH 1941 1942 - O M>=1 2 0 - +Z Europe/Zurich 0:34:8 - LMT 1853 Jul 16 +0:29:46 - BMT 1894 Jun +1 CH CE%sT 1981 +1 E CE%sT +R T 1916 o - May 1 0 1 S +R T 1916 o - O 1 0 0 - +R T 1920 o - Mar 28 0 1 S +R T 1920 o - O 25 0 0 - +R T 1921 o - Ap 3 0 1 S +R T 1921 o - O 3 0 0 - +R T 1922 o - Mar 26 0 1 S +R T 1922 o - O 8 0 0 - +R T 1924 o - May 13 0 1 S +R T 1924 1925 - O 1 0 0 - +R T 1925 o - May 1 0 1 S +R T 1940 o - Jun 30 0 1 S +R T 1940 o - O 5 0 0 - +R T 1940 o - D 1 0 1 S +R T 1941 o - S 21 0 0 - +R T 1942 o - Ap 1 0 1 S +R T 1942 o - N 1 0 0 - +R T 1945 o - Ap 2 0 1 S +R T 1945 o - O 8 0 0 - +R T 1946 o - Jun 1 0 1 S +R T 1946 o - O 1 0 0 - +R T 1947 1948 - Ap Sun>=16 0 1 S +R T 1947 1950 - O Sun>=2 0 0 - +R T 1949 o - Ap 10 0 1 S +R T 1950 o - Ap 19 0 1 S +R T 1951 o - Ap 22 0 1 S +R T 1951 o - O 8 0 0 - +R T 1962 o - Jul 15 0 1 S +R T 1962 o - O 8 0 0 - +R T 1964 o - May 15 0 1 S +R T 1964 o - O 1 0 0 - +R T 1970 1972 - May Sun>=2 0 1 S +R T 1970 1972 - O Sun>=2 0 0 - +R T 1973 o - Jun 3 1 1 S +R T 1973 o - N 4 3 0 - +R T 1974 o - Mar 31 2 1 S +R T 1974 o - N 3 5 0 - +R T 1975 o - Mar 30 0 1 S +R T 1975 1976 - O lastSun 0 0 - +R T 1976 o - Jun 1 0 1 S +R T 1977 1978 - Ap Sun>=1 0 1 S +R T 1977 o - O 16 0 0 - +R T 1979 1980 - Ap Sun>=1 3 1 S +R T 1979 1982 - O M>=11 0 0 - +R T 1981 1982 - Mar lastSun 3 1 S +R T 1983 o - Jul 31 0 1 S +R T 1983 o - O 2 0 0 - +R T 1985 o - Ap 20 0 1 S +R T 1985 o - S 28 0 0 - +R T 1986 1993 - Mar lastSun 1s 1 S +R T 1986 1995 - S lastSun 1s 0 - +R T 1994 o - Mar 20 1s 1 S +R T 1995 2006 - Mar lastSun 1s 1 S +R T 1996 2006 - O lastSun 1s 0 - +Z Europe/Istanbul 1:55:52 - LMT 1880 +1:56:56 - IMT 1910 O +2 T EE%sT 1978 O 15 +3 T +03/+04 1985 Ap 20 +2 T EE%sT 2007 +2 E EE%sT 2011 Mar 27 1u +2 - EET 2011 Mar 28 1u +2 E EE%sT 2014 Mar 30 1u +2 - EET 2014 Mar 31 1u +2 E EE%sT 2015 O 25 1u +2 1 EEST 2015 N 8 1u +2 E EE%sT 2016 S 7 +3 - +03 +Li Europe/Istanbul Asia/Istanbul +Z Europe/Kiev 2:2:4 - LMT 1880 +2:2:4 - KMT 1924 May 2 +2 - EET 1930 Jun 21 +3 - MSK 1941 S 20 +1 c CE%sT 1943 N 6 +3 R MSK/MSD 1990 Jul 1 2 +2 1 EEST 1991 S 29 3 +2 e EE%sT 1995 +2 E EE%sT +Z Europe/Uzhgorod 1:29:12 - LMT 1890 O +1 - CET 1940 +1 c CE%sT 1944 O +1 1 CEST 1944 O 26 +1 - CET 1945 Jun 29 +3 R MSK/MSD 1990 +3 - MSK 1990 Jul 1 2 +1 - CET 1991 Mar 31 3 +2 - EET 1992 +2 e EE%sT 1995 +2 E EE%sT +Z Europe/Zaporozhye 2:20:40 - LMT 1880 +2:20 - +0220 1924 May 2 +2 - EET 1930 Jun 21 +3 - MSK 1941 Au 25 +1 c CE%sT 1943 O 25 +3 R MSK/MSD 1991 Mar 31 2 +2 e EE%sT 1995 +2 E EE%sT +R u 1918 1919 - Mar lastSun 2 1 D +R u 1918 1919 - O lastSun 2 0 S +R u 1942 o - F 9 2 1 W +R u 1945 o - Au 14 23u 1 P +R u 1945 o - S lastSun 2 0 S +R u 1967 2006 - O lastSun 2 0 S +R u 1967 1973 - Ap lastSun 2 1 D +R u 1974 o - Ja 6 2 1 D +R u 1975 o - F 23 2 1 D +R u 1976 1986 - Ap lastSun 2 1 D +R u 1987 2006 - Ap Sun>=1 2 1 D +R u 2007 ma - Mar Sun>=8 2 1 D +R u 2007 ma - N Sun>=1 2 0 S +Z EST -5 - EST +Z MST -7 - MST +Z HST -10 - HST +Z EST5EDT -5 u E%sT +Z CST6CDT -6 u C%sT +Z MST7MDT -7 u M%sT +Z PST8PDT -8 u P%sT +R NY 1920 o - Mar lastSun 2 1 D +R NY 1920 o - O lastSun 2 0 S +R NY 1921 1966 - Ap lastSun 2 1 D +R NY 1921 1954 - S lastSun 2 0 S +R NY 1955 1966 - O lastSun 2 0 S +Z America/New_York -4:56:2 - LMT 1883 N 18 12:3:58 +-5 u E%sT 1920 +-5 NY E%sT 1942 +-5 u E%sT 1946 +-5 NY E%sT 1967 +-5 u E%sT +R Ch 1920 o - Jun 13 2 1 D +R Ch 1920 1921 - O lastSun 2 0 S +R Ch 1921 o - Mar lastSun 2 1 D +R Ch 1922 1966 - Ap lastSun 2 1 D +R Ch 1922 1954 - S lastSun 2 0 S +R Ch 1955 1966 - O lastSun 2 0 S +Z America/Chicago -5:50:36 - LMT 1883 N 18 12:9:24 +-6 u C%sT 1920 +-6 Ch C%sT 1936 Mar 1 2 +-5 - EST 1936 N 15 2 +-6 Ch C%sT 1942 +-6 u C%sT 1946 +-6 Ch C%sT 1967 +-6 u C%sT +Z America/North_Dakota/Center -6:45:12 - LMT 1883 N 18 12:14:48 +-7 u M%sT 1992 O 25 2 +-6 u C%sT +Z America/North_Dakota/New_Salem -6:45:39 - LMT 1883 N 18 12:14:21 +-7 u M%sT 2003 O 26 2 +-6 u C%sT +Z America/North_Dakota/Beulah -6:47:7 - LMT 1883 N 18 12:12:53 +-7 u M%sT 2010 N 7 2 +-6 u C%sT +R De 1920 1921 - Mar lastSun 2 1 D +R De 1920 o - O lastSun 2 0 S +R De 1921 o - May 22 2 0 S +R De 1965 1966 - Ap lastSun 2 1 D +R De 1965 1966 - O lastSun 2 0 S +Z America/Denver -6:59:56 - LMT 1883 N 18 12:0:4 +-7 u M%sT 1920 +-7 De M%sT 1942 +-7 u M%sT 1946 +-7 De M%sT 1967 +-7 u M%sT +R CA 1948 o - Mar 14 2:1 1 D +R CA 1949 o - Ja 1 2 0 S +R CA 1950 1966 - Ap lastSun 1 1 D +R CA 1950 1961 - S lastSun 2 0 S +R CA 1962 1966 - O lastSun 2 0 S +Z America/Los_Angeles -7:52:58 - LMT 1883 N 18 12:7:2 +-8 u P%sT 1946 +-8 CA P%sT 1967 +-8 u P%sT +Z America/Juneau 15:2:19 - LMT 1867 O 19 15:33:32 +-8:57:41 - LMT 1900 Au 20 12 +-8 - PST 1942 +-8 u P%sT 1946 +-8 - PST 1969 +-8 u P%sT 1980 Ap 27 2 +-9 u Y%sT 1980 O 26 2 +-8 u P%sT 1983 O 30 2 +-9 u Y%sT 1983 N 30 +-9 u AK%sT +Z America/Sitka 14:58:47 - LMT 1867 O 19 15:30 +-9:1:13 - LMT 1900 Au 20 12 +-8 - PST 1942 +-8 u P%sT 1946 +-8 - PST 1969 +-8 u P%sT 1983 O 30 2 +-9 u Y%sT 1983 N 30 +-9 u AK%sT +Z America/Metlakatla 15:13:42 - LMT 1867 O 19 15:44:55 +-8:46:18 - LMT 1900 Au 20 12 +-8 - PST 1942 +-8 u P%sT 1946 +-8 - PST 1969 +-8 u P%sT 1983 O 30 2 +-8 - PST 2015 N 1 2 +-9 u AK%sT +Z America/Yakutat 14:41:5 - LMT 1867 O 19 15:12:18 +-9:18:55 - LMT 1900 Au 20 12 +-9 - YST 1942 +-9 u Y%sT 1946 +-9 - YST 1969 +-9 u Y%sT 1983 N 30 +-9 u AK%sT +Z America/Anchorage 14:0:24 - LMT 1867 O 19 14:31:37 +-9:59:36 - LMT 1900 Au 20 12 +-10 - AST 1942 +-10 u A%sT 1967 Ap +-10 - AHST 1969 +-10 u AH%sT 1983 O 30 2 +-9 u Y%sT 1983 N 30 +-9 u AK%sT +Z America/Nome 12:58:22 - LMT 1867 O 19 13:29:35 +-11:1:38 - LMT 1900 Au 20 12 +-11 - NST 1942 +-11 u N%sT 1946 +-11 - NST 1967 Ap +-11 - BST 1969 +-11 u B%sT 1983 O 30 2 +-9 u Y%sT 1983 N 30 +-9 u AK%sT +Z America/Adak 12:13:22 - LMT 1867 O 19 12:44:35 +-11:46:38 - LMT 1900 Au 20 12 +-11 - NST 1942 +-11 u N%sT 1946 +-11 - NST 1967 Ap +-11 - BST 1969 +-11 u B%sT 1983 O 30 2 +-10 u AH%sT 1983 N 30 +-10 u H%sT +Z Pacific/Honolulu -10:31:26 - LMT 1896 Ja 13 12 +-10:30 - HST 1933 Ap 30 2 +-10:30 1 HDT 1933 May 21 12 +-10:30 u H%sT 1947 Jun 8 2 +-10 - HST +Z America/Phoenix -7:28:18 - LMT 1883 N 18 11:31:42 +-7 u M%sT 1944 Ja 1 0:1 +-7 - MST 1944 Ap 1 0:1 +-7 u M%sT 1944 O 1 0:1 +-7 - MST 1967 +-7 u M%sT 1968 Mar 21 +-7 - MST +Z America/Boise -7:44:49 - LMT 1883 N 18 12:15:11 +-8 u P%sT 1923 May 13 2 +-7 u M%sT 1974 +-7 - MST 1974 F 3 2 +-7 u M%sT +R In 1941 o - Jun 22 2 1 D +R In 1941 1954 - S lastSun 2 0 S +R In 1946 1954 - Ap lastSun 2 1 D +Z America/Indiana/Indianapolis -5:44:38 - LMT 1883 N 18 12:15:22 +-6 u C%sT 1920 +-6 In C%sT 1942 +-6 u C%sT 1946 +-6 In C%sT 1955 Ap 24 2 +-5 - EST 1957 S 29 2 +-6 - CST 1958 Ap 27 2 +-5 - EST 1969 +-5 u E%sT 1971 +-5 - EST 2006 +-5 u E%sT +R Ma 1951 o - Ap lastSun 2 1 D +R Ma 1951 o - S lastSun 2 0 S +R Ma 1954 1960 - Ap lastSun 2 1 D +R Ma 1954 1960 - S lastSun 2 0 S +Z America/Indiana/Marengo -5:45:23 - LMT 1883 N 18 12:14:37 +-6 u C%sT 1951 +-6 Ma C%sT 1961 Ap 30 2 +-5 - EST 1969 +-5 u E%sT 1974 Ja 6 2 +-6 1 CDT 1974 O 27 2 +-5 u E%sT 1976 +-5 - EST 2006 +-5 u E%sT +R V 1946 o - Ap lastSun 2 1 D +R V 1946 o - S lastSun 2 0 S +R V 1953 1954 - Ap lastSun 2 1 D +R V 1953 1959 - S lastSun 2 0 S +R V 1955 o - May 1 0 1 D +R V 1956 1963 - Ap lastSun 2 1 D +R V 1960 o - O lastSun 2 0 S +R V 1961 o - S lastSun 2 0 S +R V 1962 1963 - O lastSun 2 0 S +Z America/Indiana/Vincennes -5:50:7 - LMT 1883 N 18 12:9:53 +-6 u C%sT 1946 +-6 V C%sT 1964 Ap 26 2 +-5 - EST 1969 +-5 u E%sT 1971 +-5 - EST 2006 Ap 2 2 +-6 u C%sT 2007 N 4 2 +-5 u E%sT +R Pe 1946 o - Ap lastSun 2 1 D +R Pe 1946 o - S lastSun 2 0 S +R Pe 1953 1954 - Ap lastSun 2 1 D +R Pe 1953 1959 - S lastSun 2 0 S +R Pe 1955 o - May 1 0 1 D +R Pe 1956 1963 - Ap lastSun 2 1 D +R Pe 1960 o - O lastSun 2 0 S +R Pe 1961 o - S lastSun 2 0 S +R Pe 1962 1963 - O lastSun 2 0 S +Z America/Indiana/Tell_City -5:47:3 - LMT 1883 N 18 12:12:57 +-6 u C%sT 1946 +-6 Pe C%sT 1964 Ap 26 2 +-5 - EST 1969 +-5 u E%sT 1971 +-5 - EST 2006 Ap 2 2 +-6 u C%sT +R Pi 1955 o - May 1 0 1 D +R Pi 1955 1960 - S lastSun 2 0 S +R Pi 1956 1964 - Ap lastSun 2 1 D +R Pi 1961 1964 - O lastSun 2 0 S +Z America/Indiana/Petersburg -5:49:7 - LMT 1883 N 18 12:10:53 +-6 u C%sT 1955 +-6 Pi C%sT 1965 Ap 25 2 +-5 - EST 1966 O 30 2 +-6 u C%sT 1977 O 30 2 +-5 - EST 2006 Ap 2 2 +-6 u C%sT 2007 N 4 2 +-5 u E%sT +R St 1947 1961 - Ap lastSun 2 1 D +R St 1947 1954 - S lastSun 2 0 S +R St 1955 1956 - O lastSun 2 0 S +R St 1957 1958 - S lastSun 2 0 S +R St 1959 1961 - O lastSun 2 0 S +Z America/Indiana/Knox -5:46:30 - LMT 1883 N 18 12:13:30 +-6 u C%sT 1947 +-6 St C%sT 1962 Ap 29 2 +-5 - EST 1963 O 27 2 +-6 u C%sT 1991 O 27 2 +-5 - EST 2006 Ap 2 2 +-6 u C%sT +R Pu 1946 1960 - Ap lastSun 2 1 D +R Pu 1946 1954 - S lastSun 2 0 S +R Pu 1955 1956 - O lastSun 2 0 S +R Pu 1957 1960 - S lastSun 2 0 S +Z America/Indiana/Winamac -5:46:25 - LMT 1883 N 18 12:13:35 +-6 u C%sT 1946 +-6 Pu C%sT 1961 Ap 30 2 +-5 - EST 1969 +-5 u E%sT 1971 +-5 - EST 2006 Ap 2 2 +-6 u C%sT 2007 Mar 11 2 +-5 u E%sT +Z America/Indiana/Vevay -5:40:16 - LMT 1883 N 18 12:19:44 +-6 u C%sT 1954 Ap 25 2 +-5 - EST 1969 +-5 u E%sT 1973 +-5 - EST 2006 +-5 u E%sT +R v 1921 o - May 1 2 1 D +R v 1921 o - S 1 2 0 S +R v 1941 1961 - Ap lastSun 2 1 D +R v 1941 o - S lastSun 2 0 S +R v 1946 o - Jun 2 2 0 S +R v 1950 1955 - S lastSun 2 0 S +R v 1956 1960 - O lastSun 2 0 S +Z America/Kentucky/Louisville -5:43:2 - LMT 1883 N 18 12:16:58 +-6 u C%sT 1921 +-6 v C%sT 1942 +-6 u C%sT 1946 +-6 v C%sT 1961 Jul 23 2 +-5 - EST 1968 +-5 u E%sT 1974 Ja 6 2 +-6 1 CDT 1974 O 27 2 +-5 u E%sT +Z America/Kentucky/Monticello -5:39:24 - LMT 1883 N 18 12:20:36 +-6 u C%sT 1946 +-6 - CST 1968 +-6 u C%sT 2000 O 29 2 +-5 u E%sT +R Dt 1948 o - Ap lastSun 2 1 D +R Dt 1948 o - S lastSun 2 0 S +Z America/Detroit -5:32:11 - LMT 1905 +-6 - CST 1915 May 15 2 +-5 - EST 1942 +-5 u E%sT 1946 +-5 Dt E%sT 1973 +-5 u E%sT 1975 +-5 - EST 1975 Ap 27 2 +-5 u E%sT +R Me 1946 o - Ap lastSun 2 1 D +R Me 1946 o - S lastSun 2 0 S +R Me 1966 o - Ap lastSun 2 1 D +R Me 1966 o - O lastSun 2 0 S +Z America/Menominee -5:50:27 - LMT 1885 S 18 12 +-6 u C%sT 1946 +-6 Me C%sT 1969 Ap 27 2 +-5 - EST 1973 Ap 29 2 +-6 u C%sT +R C 1918 o - Ap 14 2 1 D +R C 1918 o - O 27 2 0 S +R C 1942 o - F 9 2 1 W +R C 1945 o - Au 14 23u 1 P +R C 1945 o - S 30 2 0 S +R C 1974 1986 - Ap lastSun 2 1 D +R C 1974 2006 - O lastSun 2 0 S +R C 1987 2006 - Ap Sun>=1 2 1 D +R C 2007 ma - Mar Sun>=8 2 1 D +R C 2007 ma - N Sun>=1 2 0 S +R j 1917 o - Ap 8 2 1 D +R j 1917 o - S 17 2 0 S +R j 1919 o - May 5 23 1 D +R j 1919 o - Au 12 23 0 S +R j 1920 1935 - May Sun>=1 23 1 D +R j 1920 1935 - O lastSun 23 0 S +R j 1936 1941 - May M>=9 0 1 D +R j 1936 1941 - O M>=2 0 0 S +R j 1946 1950 - May Sun>=8 2 1 D +R j 1946 1950 - O Sun>=2 2 0 S +R j 1951 1986 - Ap lastSun 2 1 D +R j 1951 1959 - S lastSun 2 0 S +R j 1960 1986 - O lastSun 2 0 S +R j 1987 o - Ap Sun>=1 0:1 1 D +R j 1987 2006 - O lastSun 0:1 0 S +R j 1988 o - Ap Sun>=1 0:1 2 DD +R j 1989 2006 - Ap Sun>=1 0:1 1 D +R j 2007 2011 - Mar Sun>=8 0:1 1 D +R j 2007 2010 - N Sun>=1 0:1 0 S +Z America/St_Johns -3:30:52 - LMT 1884 +-3:30:52 j N%sT 1918 +-3:30:52 C N%sT 1919 +-3:30:52 j N%sT 1935 Mar 30 +-3:30 j N%sT 1942 May 11 +-3:30 C N%sT 1946 +-3:30 j N%sT 2011 N +-3:30 C N%sT +Z America/Goose_Bay -4:1:40 - LMT 1884 +-3:30:52 - NST 1918 +-3:30:52 C N%sT 1919 +-3:30:52 - NST 1935 Mar 30 +-3:30 - NST 1936 +-3:30 j N%sT 1942 May 11 +-3:30 C N%sT 1946 +-3:30 j N%sT 1966 Mar 15 2 +-4 j A%sT 2011 N +-4 C A%sT +R H 1916 o - Ap 1 0 1 D +R H 1916 o - O 1 0 0 S +R H 1920 o - May 9 0 1 D +R H 1920 o - Au 29 0 0 S +R H 1921 o - May 6 0 1 D +R H 1921 1922 - S 5 0 0 S +R H 1922 o - Ap 30 0 1 D +R H 1923 1925 - May Sun>=1 0 1 D +R H 1923 o - S 4 0 0 S +R H 1924 o - S 15 0 0 S +R H 1925 o - S 28 0 0 S +R H 1926 o - May 16 0 1 D +R H 1926 o - S 13 0 0 S +R H 1927 o - May 1 0 1 D +R H 1927 o - S 26 0 0 S +R H 1928 1931 - May Sun>=8 0 1 D +R H 1928 o - S 9 0 0 S +R H 1929 o - S 3 0 0 S +R H 1930 o - S 15 0 0 S +R H 1931 1932 - S M>=24 0 0 S +R H 1932 o - May 1 0 1 D +R H 1933 o - Ap 30 0 1 D +R H 1933 o - O 2 0 0 S +R H 1934 o - May 20 0 1 D +R H 1934 o - S 16 0 0 S +R H 1935 o - Jun 2 0 1 D +R H 1935 o - S 30 0 0 S +R H 1936 o - Jun 1 0 1 D +R H 1936 o - S 14 0 0 S +R H 1937 1938 - May Sun>=1 0 1 D +R H 1937 1941 - S M>=24 0 0 S +R H 1939 o - May 28 0 1 D +R H 1940 1941 - May Sun>=1 0 1 D +R H 1946 1949 - Ap lastSun 2 1 D +R H 1946 1949 - S lastSun 2 0 S +R H 1951 1954 - Ap lastSun 2 1 D +R H 1951 1954 - S lastSun 2 0 S +R H 1956 1959 - Ap lastSun 2 1 D +R H 1956 1959 - S lastSun 2 0 S +R H 1962 1973 - Ap lastSun 2 1 D +R H 1962 1973 - O lastSun 2 0 S +Z America/Halifax -4:14:24 - LMT 1902 Jun 15 +-4 H A%sT 1918 +-4 C A%sT 1919 +-4 H A%sT 1942 F 9 2s +-4 C A%sT 1946 +-4 H A%sT 1974 +-4 C A%sT +Z America/Glace_Bay -3:59:48 - LMT 1902 Jun 15 +-4 C A%sT 1953 +-4 H A%sT 1954 +-4 - AST 1972 +-4 H A%sT 1974 +-4 C A%sT +R o 1933 1935 - Jun Sun>=8 1 1 D +R o 1933 1935 - S Sun>=8 1 0 S +R o 1936 1938 - Jun Sun>=1 1 1 D +R o 1936 1938 - S Sun>=1 1 0 S +R o 1939 o - May 27 1 1 D +R o 1939 1941 - S Sat>=21 1 0 S +R o 1940 o - May 19 1 1 D +R o 1941 o - May 4 1 1 D +R o 1946 1972 - Ap lastSun 2 1 D +R o 1946 1956 - S lastSun 2 0 S +R o 1957 1972 - O lastSun 2 0 S +R o 1993 2006 - Ap Sun>=1 0:1 1 D +R o 1993 2006 - O lastSun 0:1 0 S +Z America/Moncton -4:19:8 - LMT 1883 D 9 +-5 - EST 1902 Jun 15 +-4 C A%sT 1933 +-4 o A%sT 1942 +-4 C A%sT 1946 +-4 o A%sT 1973 +-4 C A%sT 1993 +-4 o A%sT 2007 +-4 C A%sT +Z America/Blanc-Sablon -3:48:28 - LMT 1884 +-4 C A%sT 1970 +-4 - AST +R t 1919 o - Mar 30 23:30 1 D +R t 1919 o - O 26 0 0 S +R t 1920 o - May 2 2 1 D +R t 1920 o - S 26 0 0 S +R t 1921 o - May 15 2 1 D +R t 1921 o - S 15 2 0 S +R t 1922 1923 - May Sun>=8 2 1 D +R t 1922 1926 - S Sun>=15 2 0 S +R t 1924 1927 - May Sun>=1 2 1 D +R t 1927 1932 - S lastSun 2 0 S +R t 1928 1931 - Ap lastSun 2 1 D +R t 1932 o - May 1 2 1 D +R t 1933 1940 - Ap lastSun 2 1 D +R t 1933 o - O 1 2 0 S +R t 1934 1939 - S lastSun 2 0 S +R t 1945 1946 - S lastSun 2 0 S +R t 1946 o - Ap lastSun 2 1 D +R t 1947 1949 - Ap lastSun 0 1 D +R t 1947 1948 - S lastSun 0 0 S +R t 1949 o - N lastSun 0 0 S +R t 1950 1973 - Ap lastSun 2 1 D +R t 1950 o - N lastSun 2 0 S +R t 1951 1956 - S lastSun 2 0 S +R t 1957 1973 - O lastSun 2 0 S +Z America/Toronto -5:17:32 - LMT 1895 +-5 C E%sT 1919 +-5 t E%sT 1942 F 9 2s +-5 C E%sT 1946 +-5 t E%sT 1974 +-5 C E%sT +Z America/Thunder_Bay -5:57 - LMT 1895 +-6 - CST 1910 +-5 - EST 1942 +-5 C E%sT 1970 +-5 t E%sT 1973 +-5 - EST 1974 +-5 C E%sT +Z America/Nipigon -5:53:4 - LMT 1895 +-5 C E%sT 1940 S 29 +-5 1 EDT 1942 F 9 2s +-5 C E%sT +Z America/Rainy_River -6:18:16 - LMT 1895 +-6 C C%sT 1940 S 29 +-6 1 CDT 1942 F 9 2s +-6 C C%sT +Z America/Atikokan -6:6:28 - LMT 1895 +-6 C C%sT 1940 S 29 +-6 1 CDT 1942 F 9 2s +-6 C C%sT 1945 S 30 2 +-5 - EST +R W 1916 o - Ap 23 0 1 D +R W 1916 o - S 17 0 0 S +R W 1918 o - Ap 14 2 1 D +R W 1918 o - O 27 2 0 S +R W 1937 o - May 16 2 1 D +R W 1937 o - S 26 2 0 S +R W 1942 o - F 9 2 1 W +R W 1945 o - Au 14 23u 1 P +R W 1945 o - S lastSun 2 0 S +R W 1946 o - May 12 2 1 D +R W 1946 o - O 13 2 0 S +R W 1947 1949 - Ap lastSun 2 1 D +R W 1947 1949 - S lastSun 2 0 S +R W 1950 o - May 1 2 1 D +R W 1950 o - S 30 2 0 S +R W 1951 1960 - Ap lastSun 2 1 D +R W 1951 1958 - S lastSun 2 0 S +R W 1959 o - O lastSun 2 0 S +R W 1960 o - S lastSun 2 0 S +R W 1963 o - Ap lastSun 2 1 D +R W 1963 o - S 22 2 0 S +R W 1966 1986 - Ap lastSun 2s 1 D +R W 1966 2005 - O lastSun 2s 0 S +R W 1987 2005 - Ap Sun>=1 2s 1 D +Z America/Winnipeg -6:28:36 - LMT 1887 Jul 16 +-6 W C%sT 2006 +-6 C C%sT +R r 1918 o - Ap 14 2 1 D +R r 1918 o - O 27 2 0 S +R r 1930 1934 - May Sun>=1 0 1 D +R r 1930 1934 - O Sun>=1 0 0 S +R r 1937 1941 - Ap Sun>=8 0 1 D +R r 1937 o - O Sun>=8 0 0 S +R r 1938 o - O Sun>=1 0 0 S +R r 1939 1941 - O Sun>=8 0 0 S +R r 1942 o - F 9 2 1 W +R r 1945 o - Au 14 23u 1 P +R r 1945 o - S lastSun 2 0 S +R r 1946 o - Ap Sun>=8 2 1 D +R r 1946 o - O Sun>=8 2 0 S +R r 1947 1957 - Ap lastSun 2 1 D +R r 1947 1957 - S lastSun 2 0 S +R r 1959 o - Ap lastSun 2 1 D +R r 1959 o - O lastSun 2 0 S +R Sw 1957 o - Ap lastSun 2 1 D +R Sw 1957 o - O lastSun 2 0 S +R Sw 1959 1961 - Ap lastSun 2 1 D +R Sw 1959 o - O lastSun 2 0 S +R Sw 1960 1961 - S lastSun 2 0 S +Z America/Regina -6:58:36 - LMT 1905 S +-7 r M%sT 1960 Ap lastSun 2 +-6 - CST +Z America/Swift_Current -7:11:20 - LMT 1905 S +-7 C M%sT 1946 Ap lastSun 2 +-7 r M%sT 1950 +-7 Sw M%sT 1972 Ap lastSun 2 +-6 - CST +R Ed 1918 1919 - Ap Sun>=8 2 1 D +R Ed 1918 o - O 27 2 0 S +R Ed 1919 o - May 27 2 0 S +R Ed 1920 1923 - Ap lastSun 2 1 D +R Ed 1920 o - O lastSun 2 0 S +R Ed 1921 1923 - S lastSun 2 0 S +R Ed 1942 o - F 9 2 1 W +R Ed 1945 o - Au 14 23u 1 P +R Ed 1945 o - S lastSun 2 0 S +R Ed 1947 o - Ap lastSun 2 1 D +R Ed 1947 o - S lastSun 2 0 S +R Ed 1967 o - Ap lastSun 2 1 D +R Ed 1967 o - O lastSun 2 0 S +R Ed 1969 o - Ap lastSun 2 1 D +R Ed 1969 o - O lastSun 2 0 S +R Ed 1972 1986 - Ap lastSun 2 1 D +R Ed 1972 2006 - O lastSun 2 0 S +Z America/Edmonton -7:33:52 - LMT 1906 S +-7 Ed M%sT 1987 +-7 C M%sT +R Va 1918 o - Ap 14 2 1 D +R Va 1918 o - O 27 2 0 S +R Va 1942 o - F 9 2 1 W +R Va 1945 o - Au 14 23u 1 P +R Va 1945 o - S 30 2 0 S +R Va 1946 1986 - Ap lastSun 2 1 D +R Va 1946 o - O 13 2 0 S +R Va 1947 1961 - S lastSun 2 0 S +R Va 1962 2006 - O lastSun 2 0 S +Z America/Vancouver -8:12:28 - LMT 1884 +-8 Va P%sT 1987 +-8 C P%sT +Z America/Dawson_Creek -8:0:56 - LMT 1884 +-8 C P%sT 1947 +-8 Va P%sT 1972 Au 30 2 +-7 - MST +Z America/Fort_Nelson -8:10:47 - LMT 1884 +-8 Va P%sT 1946 +-8 - PST 1947 +-8 Va P%sT 1987 +-8 C P%sT 2015 Mar 8 2 +-7 - MST +Z America/Creston -7:46:4 - LMT 1884 +-7 - MST 1916 O +-8 - PST 1918 Jun 2 +-7 - MST +R Y 1918 o - Ap 14 2 1 D +R Y 1918 o - O 27 2 0 S +R Y 1919 o - May 25 2 1 D +R Y 1919 o - N 1 0 0 S +R Y 1942 o - F 9 2 1 W +R Y 1945 o - Au 14 23u 1 P +R Y 1945 o - S 30 2 0 S +R Y 1965 o - Ap lastSun 0 2 DD +R Y 1965 o - O lastSun 2 0 S +R Y 1980 1986 - Ap lastSun 2 1 D +R Y 1980 2006 - O lastSun 2 0 S +R Y 1987 2006 - Ap Sun>=1 2 1 D +Z America/Pangnirtung 0 - -00 1921 +-4 Y A%sT 1995 Ap Sun>=1 2 +-5 C E%sT 1999 O 31 2 +-6 C C%sT 2000 O 29 2 +-5 C E%sT +Z America/Iqaluit 0 - -00 1942 Au +-5 Y E%sT 1999 O 31 2 +-6 C C%sT 2000 O 29 2 +-5 C E%sT +Z America/Resolute 0 - -00 1947 Au 31 +-6 Y C%sT 2000 O 29 2 +-5 - EST 2001 Ap 1 3 +-6 C C%sT 2006 O 29 2 +-5 - EST 2007 Mar 11 3 +-6 C C%sT +Z America/Rankin_Inlet 0 - -00 1957 +-6 Y C%sT 2000 O 29 2 +-5 - EST 2001 Ap 1 3 +-6 C C%sT +Z America/Cambridge_Bay 0 - -00 1920 +-7 Y M%sT 1999 O 31 2 +-6 C C%sT 2000 O 29 2 +-5 - EST 2000 N 5 +-6 - CST 2001 Ap 1 3 +-7 C M%sT +Z America/Yellowknife 0 - -00 1935 +-7 Y M%sT 1980 +-7 C M%sT +Z America/Inuvik 0 - -00 1953 +-8 Y P%sT 1979 Ap lastSun 2 +-7 Y M%sT 1980 +-7 C M%sT +Z America/Whitehorse -9:0:12 - LMT 1900 Au 20 +-9 Y Y%sT 1967 May 28 +-8 Y P%sT 1980 +-8 C P%sT +Z America/Dawson -9:17:40 - LMT 1900 Au 20 +-9 Y Y%sT 1973 O 28 +-8 Y P%sT 1980 +-8 C P%sT +R m 1939 o - F 5 0 1 D +R m 1939 o - Jun 25 0 0 S +R m 1940 o - D 9 0 1 D +R m 1941 o - Ap 1 0 0 S +R m 1943 o - D 16 0 1 W +R m 1944 o - May 1 0 0 S +R m 1950 o - F 12 0 1 D +R m 1950 o - Jul 30 0 0 S +R m 1996 2000 - Ap Sun>=1 2 1 D +R m 1996 2000 - O lastSun 2 0 S +R m 2001 o - May Sun>=1 2 1 D +R m 2001 o - S lastSun 2 0 S +R m 2002 ma - Ap Sun>=1 2 1 D +R m 2002 ma - O lastSun 2 0 S +Z America/Cancun -5:47:4 - LMT 1922 Ja 1 0:12:56 +-6 - CST 1981 D 23 +-5 m E%sT 1998 Au 2 2 +-6 m C%sT 2015 F 1 2 +-5 - EST +Z America/Merida -5:58:28 - LMT 1922 Ja 1 0:1:32 +-6 - CST 1981 D 23 +-5 - EST 1982 D 2 +-6 m C%sT +Z America/Matamoros -6:40 - LMT 1921 D 31 23:20 +-6 - CST 1988 +-6 u C%sT 1989 +-6 m C%sT 2010 +-6 u C%sT +Z America/Monterrey -6:41:16 - LMT 1921 D 31 23:18:44 +-6 - CST 1988 +-6 u C%sT 1989 +-6 m C%sT +Z America/Mexico_City -6:36:36 - LMT 1922 Ja 1 0:23:24 +-7 - MST 1927 Jun 10 23 +-6 - CST 1930 N 15 +-7 - MST 1931 May 1 23 +-6 - CST 1931 O +-7 - MST 1932 Ap +-6 m C%sT 2001 S 30 2 +-6 - CST 2002 F 20 +-6 m C%sT +Z America/Ojinaga -6:57:40 - LMT 1922 Ja 1 0:2:20 +-7 - MST 1927 Jun 10 23 +-6 - CST 1930 N 15 +-7 - MST 1931 May 1 23 +-6 - CST 1931 O +-7 - MST 1932 Ap +-6 - CST 1996 +-6 m C%sT 1998 +-6 - CST 1998 Ap Sun>=1 3 +-7 m M%sT 2010 +-7 u M%sT +Z America/Chihuahua -7:4:20 - LMT 1921 D 31 23:55:40 +-7 - MST 1927 Jun 10 23 +-6 - CST 1930 N 15 +-7 - MST 1931 May 1 23 +-6 - CST 1931 O +-7 - MST 1932 Ap +-6 - CST 1996 +-6 m C%sT 1998 +-6 - CST 1998 Ap Sun>=1 3 +-7 m M%sT +Z America/Hermosillo -7:23:52 - LMT 1921 D 31 23:36:8 +-7 - MST 1927 Jun 10 23 +-6 - CST 1930 N 15 +-7 - MST 1931 May 1 23 +-6 - CST 1931 O +-7 - MST 1932 Ap +-6 - CST 1942 Ap 24 +-7 - MST 1949 Ja 14 +-8 - PST 1970 +-7 m M%sT 1999 +-7 - MST +Z America/Mazatlan -7:5:40 - LMT 1921 D 31 23:54:20 +-7 - MST 1927 Jun 10 23 +-6 - CST 1930 N 15 +-7 - MST 1931 May 1 23 +-6 - CST 1931 O +-7 - MST 1932 Ap +-6 - CST 1942 Ap 24 +-7 - MST 1949 Ja 14 +-8 - PST 1970 +-7 m M%sT +Z America/Bahia_Banderas -7:1 - LMT 1921 D 31 23:59 +-7 - MST 1927 Jun 10 23 +-6 - CST 1930 N 15 +-7 - MST 1931 May 1 23 +-6 - CST 1931 O +-7 - MST 1932 Ap +-6 - CST 1942 Ap 24 +-7 - MST 1949 Ja 14 +-8 - PST 1970 +-7 m M%sT 2010 Ap 4 2 +-6 m C%sT +Z America/Tijuana -7:48:4 - LMT 1922 Ja 1 0:11:56 +-7 - MST 1924 +-8 - PST 1927 Jun 10 23 +-7 - MST 1930 N 15 +-8 - PST 1931 Ap +-8 1 PDT 1931 S 30 +-8 - PST 1942 Ap 24 +-8 1 PWT 1945 Au 14 23u +-8 1 PPT 1945 N 12 +-8 - PST 1948 Ap 5 +-8 1 PDT 1949 Ja 14 +-8 - PST 1954 +-8 CA P%sT 1961 +-8 - PST 1976 +-8 u P%sT 1996 +-8 m P%sT 2001 +-8 u P%sT 2002 F 20 +-8 m P%sT 2010 +-8 u P%sT +R BS 1964 1975 - O lastSun 2 0 S +R BS 1964 1975 - Ap lastSun 2 1 D +Z America/Nassau -5:9:30 - LMT 1912 Mar 2 +-5 BS E%sT 1976 +-5 u E%sT +R BB 1977 o - Jun 12 2 1 D +R BB 1977 1978 - O Sun>=1 2 0 S +R BB 1978 1980 - Ap Sun>=15 2 1 D +R BB 1979 o - S 30 2 0 S +R BB 1980 o - S 25 2 0 S +Z America/Barbados -3:58:29 - LMT 1924 +-3:58:29 - BMT 1932 +-4 BB A%sT +R BZ 1918 1942 - O Sun>=2 0 0:30 -0530 +R BZ 1919 1943 - F Sun>=9 0 0 CST +R BZ 1973 o - D 5 0 1 CDT +R BZ 1974 o - F 9 0 0 CST +R BZ 1982 o - D 18 0 1 CDT +R BZ 1983 o - F 12 0 0 CST +Z America/Belize -5:52:48 - LMT 1912 Ap +-6 BZ %s +Z Atlantic/Bermuda -4:19:18 - LMT 1930 Ja 1 2 +-4 - AST 1974 Ap 28 2 +-4 C A%sT 1976 +-4 u A%sT +R CR 1979 1980 - F lastSun 0 1 D +R CR 1979 1980 - Jun Sun>=1 0 0 S +R CR 1991 1992 - Ja Sat>=15 0 1 D +R CR 1991 o - Jul 1 0 0 S +R CR 1992 o - Mar 15 0 0 S +Z America/Costa_Rica -5:36:13 - LMT 1890 +-5:36:13 - SJMT 1921 Ja 15 +-6 CR C%sT +R Q 1928 o - Jun 10 0 1 D +R Q 1928 o - O 10 0 0 S +R Q 1940 1942 - Jun Sun>=1 0 1 D +R Q 1940 1942 - S Sun>=1 0 0 S +R Q 1945 1946 - Jun Sun>=1 0 1 D +R Q 1945 1946 - S Sun>=1 0 0 S +R Q 1965 o - Jun 1 0 1 D +R Q 1965 o - S 30 0 0 S +R Q 1966 o - May 29 0 1 D +R Q 1966 o - O 2 0 0 S +R Q 1967 o - Ap 8 0 1 D +R Q 1967 1968 - S Sun>=8 0 0 S +R Q 1968 o - Ap 14 0 1 D +R Q 1969 1977 - Ap lastSun 0 1 D +R Q 1969 1971 - O lastSun 0 0 S +R Q 1972 1974 - O 8 0 0 S +R Q 1975 1977 - O lastSun 0 0 S +R Q 1978 o - May 7 0 1 D +R Q 1978 1990 - O Sun>=8 0 0 S +R Q 1979 1980 - Mar Sun>=15 0 1 D +R Q 1981 1985 - May Sun>=5 0 1 D +R Q 1986 1989 - Mar Sun>=14 0 1 D +R Q 1990 1997 - Ap Sun>=1 0 1 D +R Q 1991 1995 - O Sun>=8 0s 0 S +R Q 1996 o - O 6 0s 0 S +R Q 1997 o - O 12 0s 0 S +R Q 1998 1999 - Mar lastSun 0s 1 D +R Q 1998 2003 - O lastSun 0s 0 S +R Q 2000 2003 - Ap Sun>=1 0s 1 D +R Q 2004 o - Mar lastSun 0s 1 D +R Q 2006 2010 - O lastSun 0s 0 S +R Q 2007 o - Mar Sun>=8 0s 1 D +R Q 2008 o - Mar Sun>=15 0s 1 D +R Q 2009 2010 - Mar Sun>=8 0s 1 D +R Q 2011 o - Mar Sun>=15 0s 1 D +R Q 2011 o - N 13 0s 0 S +R Q 2012 o - Ap 1 0s 1 D +R Q 2012 ma - N Sun>=1 0s 0 S +R Q 2013 ma - Mar Sun>=8 0s 1 D +Z America/Havana -5:29:28 - LMT 1890 +-5:29:36 - HMT 1925 Jul 19 12 +-5 Q C%sT +R DO 1966 o - O 30 0 1 EDT +R DO 1967 o - F 28 0 0 EST +R DO 1969 1973 - O lastSun 0 0:30 -0430 +R DO 1970 o - F 21 0 0 EST +R DO 1971 o - Ja 20 0 0 EST +R DO 1972 1974 - Ja 21 0 0 EST +Z America/Santo_Domingo -4:39:36 - LMT 1890 +-4:40 - SDMT 1933 Ap 1 12 +-5 DO %s 1974 O 27 +-4 - AST 2000 O 29 2 +-5 u E%sT 2000 D 3 1 +-4 - AST +R SV 1987 1988 - May Sun>=1 0 1 D +R SV 1987 1988 - S lastSun 0 0 S +Z America/El_Salvador -5:56:48 - LMT 1921 +-6 SV C%sT +R GT 1973 o - N 25 0 1 D +R GT 1974 o - F 24 0 0 S +R GT 1983 o - May 21 0 1 D +R GT 1983 o - S 22 0 0 S +R GT 1991 o - Mar 23 0 1 D +R GT 1991 o - S 7 0 0 S +R GT 2006 o - Ap 30 0 1 D +R GT 2006 o - O 1 0 0 S +Z America/Guatemala -6:2:4 - LMT 1918 O 5 +-6 GT C%sT +R HT 1983 o - May 8 0 1 D +R HT 1984 1987 - Ap lastSun 0 1 D +R HT 1983 1987 - O lastSun 0 0 S +R HT 1988 1997 - Ap Sun>=1 1s 1 D +R HT 1988 1997 - O lastSun 1s 0 S +R HT 2005 2006 - Ap Sun>=1 0 1 D +R HT 2005 2006 - O lastSun 0 0 S +R HT 2012 2015 - Mar Sun>=8 2 1 D +R HT 2012 2015 - N Sun>=1 2 0 S +R HT 2017 ma - Mar Sun>=8 2 1 D +R HT 2017 ma - N Sun>=1 2 0 S +Z America/Port-au-Prince -4:49:20 - LMT 1890 +-4:49 - PPMT 1917 Ja 24 12 +-5 HT E%sT +R HN 1987 1988 - May Sun>=1 0 1 D +R HN 1987 1988 - S lastSun 0 0 S +R HN 2006 o - May Sun>=1 0 1 D +R HN 2006 o - Au M>=1 0 0 S +Z America/Tegucigalpa -5:48:52 - LMT 1921 Ap +-6 HN C%sT +Z America/Jamaica -5:7:10 - LMT 1890 +-5:7:10 - KMT 1912 F +-5 - EST 1974 +-5 u E%sT 1984 +-5 - EST +Z America/Martinique -4:4:20 - LMT 1890 +-4:4:20 - FFMT 1911 May +-4 - AST 1980 Ap 6 +-4 1 ADT 1980 S 28 +-4 - AST +R NI 1979 1980 - Mar Sun>=16 0 1 D +R NI 1979 1980 - Jun M>=23 0 0 S +R NI 2005 o - Ap 10 0 1 D +R NI 2005 o - O Sun>=1 0 0 S +R NI 2006 o - Ap 30 2 1 D +R NI 2006 o - O Sun>=1 1 0 S +Z America/Managua -5:45:8 - LMT 1890 +-5:45:12 - MMT 1934 Jun 23 +-6 - CST 1973 May +-5 - EST 1975 F 16 +-6 NI C%sT 1992 Ja 1 4 +-5 - EST 1992 S 24 +-6 - CST 1993 +-5 - EST 1997 +-6 NI C%sT +Z America/Panama -5:18:8 - LMT 1890 +-5:19:36 - CMT 1908 Ap 22 +-5 - EST +Li America/Panama America/Cayman +Z America/Puerto_Rico -4:24:25 - LMT 1899 Mar 28 12 +-4 - AST 1942 May 3 +-4 u A%sT 1946 +-4 - AST +Z America/Miquelon -3:44:40 - LMT 1911 May 15 +-4 - AST 1980 May +-3 - -03 1987 +-3 C -03/-02 +Z America/Grand_Turk -4:44:32 - LMT 1890 +-5:7:10 - KMT 1912 F +-5 - EST 1979 +-5 u E%sT 2015 N Sun>=1 2 +-4 - AST 2018 Mar 11 3 +-5 u E%sT +R A 1930 o - D 1 0 1 - +R A 1931 o - Ap 1 0 0 - +R A 1931 o - O 15 0 1 - +R A 1932 1940 - Mar 1 0 0 - +R A 1932 1939 - N 1 0 1 - +R A 1940 o - Jul 1 0 1 - +R A 1941 o - Jun 15 0 0 - +R A 1941 o - O 15 0 1 - +R A 1943 o - Au 1 0 0 - +R A 1943 o - O 15 0 1 - +R A 1946 o - Mar 1 0 0 - +R A 1946 o - O 1 0 1 - +R A 1963 o - O 1 0 0 - +R A 1963 o - D 15 0 1 - +R A 1964 1966 - Mar 1 0 0 - +R A 1964 1966 - O 15 0 1 - +R A 1967 o - Ap 2 0 0 - +R A 1967 1968 - O Sun>=1 0 1 - +R A 1968 1969 - Ap Sun>=1 0 0 - +R A 1974 o - Ja 23 0 1 - +R A 1974 o - May 1 0 0 - +R A 1988 o - D 1 0 1 - +R A 1989 1993 - Mar Sun>=1 0 0 - +R A 1989 1992 - O Sun>=15 0 1 - +R A 1999 o - O Sun>=1 0 1 - +R A 2000 o - Mar 3 0 0 - +R A 2007 o - D 30 0 1 - +R A 2008 2009 - Mar Sun>=15 0 0 - +R A 2008 o - O Sun>=15 0 1 - +Z America/Argentina/Buenos_Aires -3:53:48 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 A -03/-02 +Z America/Argentina/Cordoba -4:16:48 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1991 Mar 3 +-4 - -04 1991 O 20 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 A -03/-02 +Z America/Argentina/Salta -4:21:40 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1991 Mar 3 +-4 - -04 1991 O 20 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 A -03/-02 2008 O 18 +-3 - -03 +Z America/Argentina/Tucuman -4:20:52 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1991 Mar 3 +-4 - -04 1991 O 20 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 - -03 2004 Jun +-4 - -04 2004 Jun 13 +-3 A -03/-02 +Z America/Argentina/La_Rioja -4:27:24 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1991 Mar +-4 - -04 1991 May 7 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 - -03 2004 Jun +-4 - -04 2004 Jun 20 +-3 A -03/-02 2008 O 18 +-3 - -03 +Z America/Argentina/San_Juan -4:34:4 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1991 Mar +-4 - -04 1991 May 7 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 - -03 2004 May 31 +-4 - -04 2004 Jul 25 +-3 A -03/-02 2008 O 18 +-3 - -03 +Z America/Argentina/Jujuy -4:21:12 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1990 Mar 4 +-4 - -04 1990 O 28 +-4 1 -03 1991 Mar 17 +-4 - -04 1991 O 6 +-3 1 -02 1992 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 A -03/-02 2008 O 18 +-3 - -03 +Z America/Argentina/Catamarca -4:23:8 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1991 Mar 3 +-4 - -04 1991 O 20 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 - -03 2004 Jun +-4 - -04 2004 Jun 20 +-3 A -03/-02 2008 O 18 +-3 - -03 +Z America/Argentina/Mendoza -4:35:16 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1990 Mar 4 +-4 - -04 1990 O 15 +-4 1 -03 1991 Mar +-4 - -04 1991 O 15 +-4 1 -03 1992 Mar +-4 - -04 1992 O 18 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 - -03 2004 May 23 +-4 - -04 2004 S 26 +-3 A -03/-02 2008 O 18 +-3 - -03 +R Sa 2008 2009 - Mar Sun>=8 0 0 - +R Sa 2007 2008 - O Sun>=8 0 1 - +Z America/Argentina/San_Luis -4:25:24 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1990 +-3 1 -02 1990 Mar 14 +-4 - -04 1990 O 15 +-4 1 -03 1991 Mar +-4 - -04 1991 Jun +-3 - -03 1999 O 3 +-4 1 -03 2000 Mar 3 +-3 - -03 2004 May 31 +-4 - -04 2004 Jul 25 +-3 A -03/-02 2008 Ja 21 +-4 Sa -04/-03 2009 O 11 +-3 - -03 +Z America/Argentina/Rio_Gallegos -4:36:52 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 - -03 2004 Jun +-4 - -04 2004 Jun 20 +-3 A -03/-02 2008 O 18 +-3 - -03 +Z America/Argentina/Ushuaia -4:33:12 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 - -03 2004 May 30 +-4 - -04 2004 Jun 20 +-3 A -03/-02 2008 O 18 +-3 - -03 +Li America/Curacao America/Aruba +Z America/La_Paz -4:32:36 - LMT 1890 +-4:32:36 - CMT 1931 O 15 +-4:32:36 1 BST 1932 Mar 21 +-4 - -04 +R B 1931 o - O 3 11 1 - +R B 1932 1933 - Ap 1 0 0 - +R B 1932 o - O 3 0 1 - +R B 1949 1952 - D 1 0 1 - +R B 1950 o - Ap 16 1 0 - +R B 1951 1952 - Ap 1 0 0 - +R B 1953 o - Mar 1 0 0 - +R B 1963 o - D 9 0 1 - +R B 1964 o - Mar 1 0 0 - +R B 1965 o - Ja 31 0 1 - +R B 1965 o - Mar 31 0 0 - +R B 1965 o - D 1 0 1 - +R B 1966 1968 - Mar 1 0 0 - +R B 1966 1967 - N 1 0 1 - +R B 1985 o - N 2 0 1 - +R B 1986 o - Mar 15 0 0 - +R B 1986 o - O 25 0 1 - +R B 1987 o - F 14 0 0 - +R B 1987 o - O 25 0 1 - +R B 1988 o - F 7 0 0 - +R B 1988 o - O 16 0 1 - +R B 1989 o - Ja 29 0 0 - +R B 1989 o - O 15 0 1 - +R B 1990 o - F 11 0 0 - +R B 1990 o - O 21 0 1 - +R B 1991 o - F 17 0 0 - +R B 1991 o - O 20 0 1 - +R B 1992 o - F 9 0 0 - +R B 1992 o - O 25 0 1 - +R B 1993 o - Ja 31 0 0 - +R B 1993 1995 - O Sun>=11 0 1 - +R B 1994 1995 - F Sun>=15 0 0 - +R B 1996 o - F 11 0 0 - +R B 1996 o - O 6 0 1 - +R B 1997 o - F 16 0 0 - +R B 1997 o - O 6 0 1 - +R B 1998 o - Mar 1 0 0 - +R B 1998 o - O 11 0 1 - +R B 1999 o - F 21 0 0 - +R B 1999 o - O 3 0 1 - +R B 2000 o - F 27 0 0 - +R B 2000 2001 - O Sun>=8 0 1 - +R B 2001 2006 - F Sun>=15 0 0 - +R B 2002 o - N 3 0 1 - +R B 2003 o - O 19 0 1 - +R B 2004 o - N 2 0 1 - +R B 2005 o - O 16 0 1 - +R B 2006 o - N 5 0 1 - +R B 2007 o - F 25 0 0 - +R B 2007 o - O Sun>=8 0 1 - +R B 2008 2017 - O Sun>=15 0 1 - +R B 2008 2011 - F Sun>=15 0 0 - +R B 2012 o - F Sun>=22 0 0 - +R B 2013 2014 - F Sun>=15 0 0 - +R B 2015 o - F Sun>=22 0 0 - +R B 2016 2022 - F Sun>=15 0 0 - +R B 2018 ma - N Sun>=1 0 1 - +R B 2023 o - F Sun>=22 0 0 - +R B 2024 2025 - F Sun>=15 0 0 - +R B 2026 o - F Sun>=22 0 0 - +R B 2027 2033 - F Sun>=15 0 0 - +R B 2034 o - F Sun>=22 0 0 - +R B 2035 2036 - F Sun>=15 0 0 - +R B 2037 o - F Sun>=22 0 0 - +R B 2038 ma - F Sun>=15 0 0 - +Z America/Noronha -2:9:40 - LMT 1914 +-2 B -02/-01 1990 S 17 +-2 - -02 1999 S 30 +-2 B -02/-01 2000 O 15 +-2 - -02 2001 S 13 +-2 B -02/-01 2002 O +-2 - -02 +Z America/Belem -3:13:56 - LMT 1914 +-3 B -03/-02 1988 S 12 +-3 - -03 +Z America/Santarem -3:38:48 - LMT 1914 +-4 B -04/-03 1988 S 12 +-4 - -04 2008 Jun 24 +-3 - -03 +Z America/Fortaleza -2:34 - LMT 1914 +-3 B -03/-02 1990 S 17 +-3 - -03 1999 S 30 +-3 B -03/-02 2000 O 22 +-3 - -03 2001 S 13 +-3 B -03/-02 2002 O +-3 - -03 +Z America/Recife -2:19:36 - LMT 1914 +-3 B -03/-02 1990 S 17 +-3 - -03 1999 S 30 +-3 B -03/-02 2000 O 15 +-3 - -03 2001 S 13 +-3 B -03/-02 2002 O +-3 - -03 +Z America/Araguaina -3:12:48 - LMT 1914 +-3 B -03/-02 1990 S 17 +-3 - -03 1995 S 14 +-3 B -03/-02 2003 S 24 +-3 - -03 2012 O 21 +-3 B -03/-02 2013 S +-3 - -03 +Z America/Maceio -2:22:52 - LMT 1914 +-3 B -03/-02 1990 S 17 +-3 - -03 1995 O 13 +-3 B -03/-02 1996 S 4 +-3 - -03 1999 S 30 +-3 B -03/-02 2000 O 22 +-3 - -03 2001 S 13 +-3 B -03/-02 2002 O +-3 - -03 +Z America/Bahia -2:34:4 - LMT 1914 +-3 B -03/-02 2003 S 24 +-3 - -03 2011 O 16 +-3 B -03/-02 2012 O 21 +-3 - -03 +Z America/Sao_Paulo -3:6:28 - LMT 1914 +-3 B -03/-02 1963 O 23 +-3 1 -02 1964 +-3 B -03/-02 +Z America/Campo_Grande -3:38:28 - LMT 1914 +-4 B -04/-03 +Z America/Cuiaba -3:44:20 - LMT 1914 +-4 B -04/-03 2003 S 24 +-4 - -04 2004 O +-4 B -04/-03 +Z America/Porto_Velho -4:15:36 - LMT 1914 +-4 B -04/-03 1988 S 12 +-4 - -04 +Z America/Boa_Vista -4:2:40 - LMT 1914 +-4 B -04/-03 1988 S 12 +-4 - -04 1999 S 30 +-4 B -04/-03 2000 O 15 +-4 - -04 +Z America/Manaus -4:0:4 - LMT 1914 +-4 B -04/-03 1988 S 12 +-4 - -04 1993 S 28 +-4 B -04/-03 1994 S 22 +-4 - -04 +Z America/Eirunepe -4:39:28 - LMT 1914 +-5 B -05/-04 1988 S 12 +-5 - -05 1993 S 28 +-5 B -05/-04 1994 S 22 +-5 - -05 2008 Jun 24 +-4 - -04 2013 N 10 +-5 - -05 +Z America/Rio_Branco -4:31:12 - LMT 1914 +-5 B -05/-04 1988 S 12 +-5 - -05 2008 Jun 24 +-4 - -04 2013 N 10 +-5 - -05 +R x 1927 1931 - S 1 0 1 - +R x 1928 1932 - Ap 1 0 0 - +R x 1968 o - N 3 4u 1 - +R x 1969 o - Mar 30 3u 0 - +R x 1969 o - N 23 4u 1 - +R x 1970 o - Mar 29 3u 0 - +R x 1971 o - Mar 14 3u 0 - +R x 1970 1972 - O Sun>=9 4u 1 - +R x 1972 1986 - Mar Sun>=9 3u 0 - +R x 1973 o - S 30 4u 1 - +R x 1974 1987 - O Sun>=9 4u 1 - +R x 1987 o - Ap 12 3u 0 - +R x 1988 1990 - Mar Sun>=9 3u 0 - +R x 1988 1989 - O Sun>=9 4u 1 - +R x 1990 o - S 16 4u 1 - +R x 1991 1996 - Mar Sun>=9 3u 0 - +R x 1991 1997 - O Sun>=9 4u 1 - +R x 1997 o - Mar 30 3u 0 - +R x 1998 o - Mar Sun>=9 3u 0 - +R x 1998 o - S 27 4u 1 - +R x 1999 o - Ap 4 3u 0 - +R x 1999 2010 - O Sun>=9 4u 1 - +R x 2000 2007 - Mar Sun>=9 3u 0 - +R x 2008 o - Mar 30 3u 0 - +R x 2009 o - Mar Sun>=9 3u 0 - +R x 2010 o - Ap Sun>=1 3u 0 - +R x 2011 o - May Sun>=2 3u 0 - +R x 2011 o - Au Sun>=16 4u 1 - +R x 2012 2014 - Ap Sun>=23 3u 0 - +R x 2012 2014 - S Sun>=2 4u 1 - +R x 2016 2018 - May Sun>=9 3u 0 - +R x 2016 2018 - Au Sun>=9 4u 1 - +R x 2019 ma - Ap Sun>=2 3u 0 - +R x 2019 ma - S Sun>=2 4u 1 - +Z America/Santiago -4:42:46 - LMT 1890 +-4:42:46 - SMT 1910 Ja 10 +-5 - -05 1916 Jul +-4:42:46 - SMT 1918 S 10 +-4 - -04 1919 Jul +-4:42:46 - SMT 1927 S +-5 x -05/-04 1932 S +-4 - -04 1942 Jun +-5 - -05 1942 Au +-4 - -04 1946 Jul 15 +-4 1 -03 1946 S +-4 - -04 1947 Ap +-5 - -05 1947 May 21 23 +-4 x -04/-03 +Z America/Punta_Arenas -4:43:40 - LMT 1890 +-4:42:46 - SMT 1910 Ja 10 +-5 - -05 1916 Jul +-4:42:46 - SMT 1918 S 10 +-4 - -04 1919 Jul +-4:42:46 - SMT 1927 S +-5 x -05/-04 1932 S +-4 - -04 1942 Jun +-5 - -05 1942 Au +-4 - -04 1947 Ap +-5 - -05 1947 May 21 23 +-4 x -04/-03 2016 D 4 +-3 - -03 +Z Pacific/Easter -7:17:28 - LMT 1890 +-7:17:28 - EMT 1932 S +-7 x -07/-06 1982 Mar 14 3u +-6 x -06/-05 +Z Antarctica/Palmer 0 - -00 1965 +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1982 May +-4 x -04/-03 2016 D 4 +-3 - -03 +R CO 1992 o - May 3 0 1 - +R CO 1993 o - Ap 4 0 0 - +Z America/Bogota -4:56:16 - LMT 1884 Mar 13 +-4:56:16 - BMT 1914 N 23 +-5 CO -05/-04 +Z America/Curacao -4:35:47 - LMT 1912 F 12 +-4:30 - -0430 1965 +-4 - AST +Li America/Curacao America/Lower_Princes +Li America/Curacao America/Kralendijk +R EC 1992 o - N 28 0 1 - +R EC 1993 o - F 5 0 0 - +Z America/Guayaquil -5:19:20 - LMT 1890 +-5:14 - QMT 1931 +-5 EC -05/-04 +Z Pacific/Galapagos -5:58:24 - LMT 1931 +-5 - -05 1986 +-6 EC -06/-05 +R FK 1937 1938 - S lastSun 0 1 - +R FK 1938 1942 - Mar Sun>=19 0 0 - +R FK 1939 o - O 1 0 1 - +R FK 1940 1942 - S lastSun 0 1 - +R FK 1943 o - Ja 1 0 0 - +R FK 1983 o - S lastSun 0 1 - +R FK 1984 1985 - Ap lastSun 0 0 - +R FK 1984 o - S 16 0 1 - +R FK 1985 2000 - S Sun>=9 0 1 - +R FK 1986 2000 - Ap Sun>=16 0 0 - +R FK 2001 2010 - Ap Sun>=15 2 0 - +R FK 2001 2010 - S Sun>=1 2 1 - +Z Atlantic/Stanley -3:51:24 - LMT 1890 +-3:51:24 - SMT 1912 Mar 12 +-4 FK -04/-03 1983 May +-3 FK -03/-02 1985 S 15 +-4 FK -04/-03 2010 S 5 2 +-3 - -03 +Z America/Cayenne -3:29:20 - LMT 1911 Jul +-4 - -04 1967 O +-3 - -03 +Z America/Guyana -3:52:40 - LMT 1915 Mar +-3:45 - -0345 1975 Jul 31 +-3 - -03 1991 +-4 - -04 +R y 1975 1988 - O 1 0 1 - +R y 1975 1978 - Mar 1 0 0 - +R y 1979 1991 - Ap 1 0 0 - +R y 1989 o - O 22 0 1 - +R y 1990 o - O 1 0 1 - +R y 1991 o - O 6 0 1 - +R y 1992 o - Mar 1 0 0 - +R y 1992 o - O 5 0 1 - +R y 1993 o - Mar 31 0 0 - +R y 1993 1995 - O 1 0 1 - +R y 1994 1995 - F lastSun 0 0 - +R y 1996 o - Mar 1 0 0 - +R y 1996 2001 - O Sun>=1 0 1 - +R y 1997 o - F lastSun 0 0 - +R y 1998 2001 - Mar Sun>=1 0 0 - +R y 2002 2004 - Ap Sun>=1 0 0 - +R y 2002 2003 - S Sun>=1 0 1 - +R y 2004 2009 - O Sun>=15 0 1 - +R y 2005 2009 - Mar Sun>=8 0 0 - +R y 2010 ma - O Sun>=1 0 1 - +R y 2010 2012 - Ap Sun>=8 0 0 - +R y 2013 ma - Mar Sun>=22 0 0 - +Z America/Asuncion -3:50:40 - LMT 1890 +-3:50:40 - AMT 1931 O 10 +-4 - -04 1972 O +-3 - -03 1974 Ap +-4 y -04/-03 +R PE 1938 o - Ja 1 0 1 - +R PE 1938 o - Ap 1 0 0 - +R PE 1938 1939 - S lastSun 0 1 - +R PE 1939 1940 - Mar Sun>=24 0 0 - +R PE 1986 1987 - Ja 1 0 1 - +R PE 1986 1987 - Ap 1 0 0 - +R PE 1990 o - Ja 1 0 1 - +R PE 1990 o - Ap 1 0 0 - +R PE 1994 o - Ja 1 0 1 - +R PE 1994 o - Ap 1 0 0 - +Z America/Lima -5:8:12 - LMT 1890 +-5:8:36 - LMT 1908 Jul 28 +-5 PE -05/-04 +Z Atlantic/South_Georgia -2:26:8 - LMT 1890 +-2 - -02 +Z America/Paramaribo -3:40:40 - LMT 1911 +-3:40:52 - PMT 1935 +-3:40:36 - PMT 1945 O +-3:30 - -0330 1984 O +-3 - -03 +Z America/Port_of_Spain -4:6:4 - LMT 1912 Mar 2 +-4 - AST +Li America/Port_of_Spain America/Anguilla +Li America/Port_of_Spain America/Antigua +Li America/Port_of_Spain America/Dominica +Li America/Port_of_Spain America/Grenada +Li America/Port_of_Spain America/Guadeloupe +Li America/Port_of_Spain America/Marigot +Li America/Port_of_Spain America/Montserrat +Li America/Port_of_Spain America/St_Barthelemy +Li America/Port_of_Spain America/St_Kitts +Li America/Port_of_Spain America/St_Lucia +Li America/Port_of_Spain America/St_Thomas +Li America/Port_of_Spain America/St_Vincent +Li America/Port_of_Spain America/Tortola +R U 1923 1925 - O 1 0 0:30 - +R U 1924 1926 - Ap 1 0 0 - +R U 1933 1938 - O lastSun 0 0:30 - +R U 1934 1941 - Mar lastSat 24 0 - +R U 1939 o - O 1 0 0:30 - +R U 1940 o - O 27 0 0:30 - +R U 1941 o - Au 1 0 0:30 - +R U 1942 o - D 14 0 0:30 - +R U 1943 o - Mar 14 0 0 - +R U 1959 o - May 24 0 0:30 - +R U 1959 o - N 15 0 0 - +R U 1960 o - Ja 17 0 1 - +R U 1960 o - Mar 6 0 0 - +R U 1965 o - Ap 4 0 1 - +R U 1965 o - S 26 0 0 - +R U 1968 o - May 27 0 0:30 - +R U 1968 o - D 1 0 0 - +R U 1970 o - Ap 25 0 1 - +R U 1970 o - Jun 14 0 0 - +R U 1972 o - Ap 23 0 1 - +R U 1972 o - Jul 16 0 0 - +R U 1974 o - Ja 13 0 1:30 - +R U 1974 o - Mar 10 0 0:30 - +R U 1974 o - S 1 0 0 - +R U 1974 o - D 22 0 1 - +R U 1975 o - Mar 30 0 0 - +R U 1976 o - D 19 0 1 - +R U 1977 o - Mar 6 0 0 - +R U 1977 o - D 4 0 1 - +R U 1978 1979 - Mar Sun>=1 0 0 - +R U 1978 o - D 17 0 1 - +R U 1979 o - Ap 29 0 1 - +R U 1980 o - Mar 16 0 0 - +R U 1987 o - D 14 0 1 - +R U 1988 o - F 28 0 0 - +R U 1988 o - D 11 0 1 - +R U 1989 o - Mar 5 0 0 - +R U 1989 o - O 29 0 1 - +R U 1990 o - F 25 0 0 - +R U 1990 1991 - O Sun>=21 0 1 - +R U 1991 1992 - Mar Sun>=1 0 0 - +R U 1992 o - O 18 0 1 - +R U 1993 o - F 28 0 0 - +R U 2004 o - S 19 0 1 - +R U 2005 o - Mar 27 2 0 - +R U 2005 o - O 9 2 1 - +R U 2006 2015 - Mar Sun>=8 2 0 - +R U 2006 2014 - O Sun>=1 2 1 - +Z America/Montevideo -3:44:51 - LMT 1908 Jun 10 +-3:44:51 - MMT 1920 May +-4 - -04 1923 O +-3:30 U -0330/-03 1942 D 14 +-3 U -03/-0230 1960 +-3 U -03/-02 1968 +-3 U -03/-0230 1970 +-3 U -03/-02 1974 +-3 U -03/-0130 1974 Mar 10 +-3 U -03/-0230 1974 D 22 +-3 U -03/-02 +Z America/Caracas -4:27:44 - LMT 1890 +-4:27:40 - CMT 1912 F 12 +-4:30 - -0430 1965 +-4 - -04 2007 D 9 3 +-4:30 - -0430 2016 May 1 2:30 +-4 - -04 +Z Etc/GMT 0 - GMT +Z Etc/UTC 0 - UTC +Z Etc/UCT 0 - UCT +Li Etc/GMT GMT +Li Etc/UTC Etc/Universal +Li Etc/UTC Etc/Zulu +Li Etc/GMT Etc/Greenwich +Li Etc/GMT Etc/GMT-0 +Li Etc/GMT Etc/GMT+0 +Li Etc/GMT Etc/GMT0 +Z Etc/GMT-14 14 - +14 +Z Etc/GMT-13 13 - +13 +Z Etc/GMT-12 12 - +12 +Z Etc/GMT-11 11 - +11 +Z Etc/GMT-10 10 - +10 +Z Etc/GMT-9 9 - +09 +Z Etc/GMT-8 8 - +08 +Z Etc/GMT-7 7 - +07 +Z Etc/GMT-6 6 - +06 +Z Etc/GMT-5 5 - +05 +Z Etc/GMT-4 4 - +04 +Z Etc/GMT-3 3 - +03 +Z Etc/GMT-2 2 - +02 +Z Etc/GMT-1 1 - +01 +Z Etc/GMT+1 -1 - -01 +Z Etc/GMT+2 -2 - -02 +Z Etc/GMT+3 -3 - -03 +Z Etc/GMT+4 -4 - -04 +Z Etc/GMT+5 -5 - -05 +Z Etc/GMT+6 -6 - -06 +Z Etc/GMT+7 -7 - -07 +Z Etc/GMT+8 -8 - -08 +Z Etc/GMT+9 -9 - -09 +Z Etc/GMT+10 -10 - -10 +Z Etc/GMT+11 -11 - -11 +Z Etc/GMT+12 -12 - -12 +Z Factory 0 - -00 +Li Africa/Nairobi Africa/Asmera +Li Africa/Abidjan Africa/Timbuktu +Li America/Argentina/Catamarca America/Argentina/ComodRivadavia +Li America/Adak America/Atka +Li America/Argentina/Buenos_Aires America/Buenos_Aires +Li America/Argentina/Catamarca America/Catamarca +Li America/Atikokan America/Coral_Harbour +Li America/Argentina/Cordoba America/Cordoba +Li America/Tijuana America/Ensenada +Li America/Indiana/Indianapolis America/Fort_Wayne +Li America/Indiana/Indianapolis America/Indianapolis +Li America/Argentina/Jujuy America/Jujuy +Li America/Indiana/Knox America/Knox_IN +Li America/Kentucky/Louisville America/Louisville +Li America/Argentina/Mendoza America/Mendoza +Li America/Toronto America/Montreal +Li America/Rio_Branco America/Porto_Acre +Li America/Argentina/Cordoba America/Rosario +Li America/Tijuana America/Santa_Isabel +Li America/Denver America/Shiprock +Li America/Port_of_Spain America/Virgin +Li Pacific/Auckland Antarctica/South_Pole +Li Asia/Ashgabat Asia/Ashkhabad +Li Asia/Kolkata Asia/Calcutta +Li Asia/Shanghai Asia/Chongqing +Li Asia/Shanghai Asia/Chungking +Li Asia/Dhaka Asia/Dacca +Li Asia/Shanghai Asia/Harbin +Li Asia/Urumqi Asia/Kashgar +Li Asia/Kathmandu Asia/Katmandu +Li Asia/Macau Asia/Macao +Li Asia/Yangon Asia/Rangoon +Li Asia/Ho_Chi_Minh Asia/Saigon +Li Asia/Jerusalem Asia/Tel_Aviv +Li Asia/Thimphu Asia/Thimbu +Li Asia/Makassar Asia/Ujung_Pandang +Li Asia/Ulaanbaatar Asia/Ulan_Bator +Li Atlantic/Faroe Atlantic/Faeroe +Li Europe/Oslo Atlantic/Jan_Mayen +Li Australia/Sydney Australia/ACT +Li Australia/Sydney Australia/Canberra +Li Australia/Lord_Howe Australia/LHI +Li Australia/Sydney Australia/NSW +Li Australia/Darwin Australia/North +Li Australia/Brisbane Australia/Queensland +Li Australia/Adelaide Australia/South +Li Australia/Hobart Australia/Tasmania +Li Australia/Melbourne Australia/Victoria +Li Australia/Perth Australia/West +Li Australia/Broken_Hill Australia/Yancowinna +Li America/Rio_Branco Brazil/Acre +Li America/Noronha Brazil/DeNoronha +Li America/Sao_Paulo Brazil/East +Li America/Manaus Brazil/West +Li America/Halifax Canada/Atlantic +Li America/Winnipeg Canada/Central +Li America/Toronto Canada/Eastern +Li America/Edmonton Canada/Mountain +Li America/St_Johns Canada/Newfoundland +Li America/Vancouver Canada/Pacific +Li America/Regina Canada/Saskatchewan +Li America/Whitehorse Canada/Yukon +Li America/Santiago Chile/Continental +Li Pacific/Easter Chile/EasterIsland +Li America/Havana Cuba +Li Africa/Cairo Egypt +Li Europe/Dublin Eire +Li Europe/London Europe/Belfast +Li Europe/Chisinau Europe/Tiraspol +Li Europe/London GB +Li Europe/London GB-Eire +Li Etc/GMT GMT+0 +Li Etc/GMT GMT-0 +Li Etc/GMT GMT0 +Li Etc/GMT Greenwich +Li Asia/Hong_Kong Hongkong +Li Atlantic/Reykjavik Iceland +Li Asia/Tehran Iran +Li Asia/Jerusalem Israel +Li America/Jamaica Jamaica +Li Asia/Tokyo Japan +Li Pacific/Kwajalein Kwajalein +Li Africa/Tripoli Libya +Li America/Tijuana Mexico/BajaNorte +Li America/Mazatlan Mexico/BajaSur +Li America/Mexico_City Mexico/General +Li Pacific/Auckland NZ +Li Pacific/Chatham NZ-CHAT +Li America/Denver Navajo +Li Asia/Shanghai PRC +Li Pacific/Honolulu Pacific/Johnston +Li Pacific/Pohnpei Pacific/Ponape +Li Pacific/Pago_Pago Pacific/Samoa +Li Pacific/Chuuk Pacific/Truk +Li Pacific/Chuuk Pacific/Yap +Li Europe/Warsaw Poland +Li Europe/Lisbon Portugal +Li Asia/Taipei ROC +Li Asia/Seoul ROK +Li Asia/Singapore Singapore +Li Europe/Istanbul Turkey +Li Etc/UCT UCT +Li America/Anchorage US/Alaska +Li America/Adak US/Aleutian +Li America/Phoenix US/Arizona +Li America/Chicago US/Central +Li America/Indiana/Indianapolis US/East-Indiana +Li America/New_York US/Eastern +Li Pacific/Honolulu US/Hawaii +Li America/Indiana/Knox US/Indiana-Starke +Li America/Detroit US/Michigan +Li America/Denver US/Mountain +Li America/Los_Angeles US/Pacific +Li Pacific/Pago_Pago US/Samoa +Li Etc/UTC UTC +Li Etc/UTC Universal +Li Europe/Moscow W-SU +Li Etc/UTC Zulu diff --git a/libs/pytz/zoneinfo/zone.tab b/libs/pytz/zoneinfo/zone.tab new file mode 100644 index 00000000..dcb6e1da --- /dev/null +++ b/libs/pytz/zoneinfo/zone.tab @@ -0,0 +1,448 @@ +# tzdb timezone descriptions (deprecated version) +# +# This file is in the public domain, so clarified as of +# 2009-05-17 by Arthur David Olson. +# +# From Paul Eggert (2018-06-27): +# This file is intended as a backward-compatibility aid for older programs. +# New programs should use zone1970.tab. This file is like zone1970.tab (see +# zone1970.tab's comments), but with the following additional restrictions: +# +# 1. This file contains only ASCII characters. +# 2. The first data column contains exactly one country code. +# +# Because of (2), each row stands for an area that is the intersection +# of a region identified by a country code and of a timezone where civil +# clocks have agreed since 1970; this is a narrower definition than +# that of zone1970.tab. +# +# This table is intended as an aid for users, to help them select timezones +# appropriate for their practical needs. It is not intended to take or +# endorse any position on legal or territorial claims. +# +#country- +#code coordinates TZ comments +AD +4230+00131 Europe/Andorra +AE +2518+05518 Asia/Dubai +AF +3431+06912 Asia/Kabul +AG +1703-06148 America/Antigua +AI +1812-06304 America/Anguilla +AL +4120+01950 Europe/Tirane +AM +4011+04430 Asia/Yerevan +AO -0848+01314 Africa/Luanda +AQ -7750+16636 Antarctica/McMurdo New Zealand time - McMurdo, South Pole +AQ -6617+11031 Antarctica/Casey Casey +AQ -6835+07758 Antarctica/Davis Davis +AQ -6640+14001 Antarctica/DumontDUrville Dumont-d'Urville +AQ -6736+06253 Antarctica/Mawson Mawson +AQ -6448-06406 Antarctica/Palmer Palmer +AQ -6734-06808 Antarctica/Rothera Rothera +AQ -690022+0393524 Antarctica/Syowa Syowa +AQ -720041+0023206 Antarctica/Troll Troll +AQ -7824+10654 Antarctica/Vostok Vostok +AR -3436-05827 America/Argentina/Buenos_Aires Buenos Aires (BA, CF) +AR -3124-06411 America/Argentina/Cordoba Argentina (most areas: CB, CC, CN, ER, FM, MN, SE, SF) +AR -2447-06525 America/Argentina/Salta Salta (SA, LP, NQ, RN) +AR -2411-06518 America/Argentina/Jujuy Jujuy (JY) +AR -2649-06513 America/Argentina/Tucuman Tucuman (TM) +AR -2828-06547 America/Argentina/Catamarca Catamarca (CT); Chubut (CH) +AR -2926-06651 America/Argentina/La_Rioja La Rioja (LR) +AR -3132-06831 America/Argentina/San_Juan San Juan (SJ) +AR -3253-06849 America/Argentina/Mendoza Mendoza (MZ) +AR -3319-06621 America/Argentina/San_Luis San Luis (SL) +AR -5138-06913 America/Argentina/Rio_Gallegos Santa Cruz (SC) +AR -5448-06818 America/Argentina/Ushuaia Tierra del Fuego (TF) +AS -1416-17042 Pacific/Pago_Pago +AT +4813+01620 Europe/Vienna +AU -3133+15905 Australia/Lord_Howe Lord Howe Island +AU -5430+15857 Antarctica/Macquarie Macquarie Island +AU -4253+14719 Australia/Hobart Tasmania (most areas) +AU -3956+14352 Australia/Currie Tasmania (King Island) +AU -3749+14458 Australia/Melbourne Victoria +AU -3352+15113 Australia/Sydney New South Wales (most areas) +AU -3157+14127 Australia/Broken_Hill New South Wales (Yancowinna) +AU -2728+15302 Australia/Brisbane Queensland (most areas) +AU -2016+14900 Australia/Lindeman Queensland (Whitsunday Islands) +AU -3455+13835 Australia/Adelaide South Australia +AU -1228+13050 Australia/Darwin Northern Territory +AU -3157+11551 Australia/Perth Western Australia (most areas) +AU -3143+12852 Australia/Eucla Western Australia (Eucla) +AW +1230-06958 America/Aruba +AX +6006+01957 Europe/Mariehamn +AZ +4023+04951 Asia/Baku +BA +4352+01825 Europe/Sarajevo +BB +1306-05937 America/Barbados +BD +2343+09025 Asia/Dhaka +BE +5050+00420 Europe/Brussels +BF +1222-00131 Africa/Ouagadougou +BG +4241+02319 Europe/Sofia +BH +2623+05035 Asia/Bahrain +BI -0323+02922 Africa/Bujumbura +BJ +0629+00237 Africa/Porto-Novo +BL +1753-06251 America/St_Barthelemy +BM +3217-06446 Atlantic/Bermuda +BN +0456+11455 Asia/Brunei +BO -1630-06809 America/La_Paz +BQ +120903-0681636 America/Kralendijk +BR -0351-03225 America/Noronha Atlantic islands +BR -0127-04829 America/Belem Para (east); Amapa +BR -0343-03830 America/Fortaleza Brazil (northeast: MA, PI, CE, RN, PB) +BR -0803-03454 America/Recife Pernambuco +BR -0712-04812 America/Araguaina Tocantins +BR -0940-03543 America/Maceio Alagoas, Sergipe +BR -1259-03831 America/Bahia Bahia +BR -2332-04637 America/Sao_Paulo Brazil (southeast: GO, DF, MG, ES, RJ, SP, PR, SC, RS) +BR -2027-05437 America/Campo_Grande Mato Grosso do Sul +BR -1535-05605 America/Cuiaba Mato Grosso +BR -0226-05452 America/Santarem Para (west) +BR -0846-06354 America/Porto_Velho Rondonia +BR +0249-06040 America/Boa_Vista Roraima +BR -0308-06001 America/Manaus Amazonas (east) +BR -0640-06952 America/Eirunepe Amazonas (west) +BR -0958-06748 America/Rio_Branco Acre +BS +2505-07721 America/Nassau +BT +2728+08939 Asia/Thimphu +BW -2439+02555 Africa/Gaborone +BY +5354+02734 Europe/Minsk +BZ +1730-08812 America/Belize +CA +4734-05243 America/St_Johns Newfoundland; Labrador (southeast) +CA +4439-06336 America/Halifax Atlantic - NS (most areas); PE +CA +4612-05957 America/Glace_Bay Atlantic - NS (Cape Breton) +CA +4606-06447 America/Moncton Atlantic - New Brunswick +CA +5320-06025 America/Goose_Bay Atlantic - Labrador (most areas) +CA +5125-05707 America/Blanc-Sablon AST - QC (Lower North Shore) +CA +4339-07923 America/Toronto Eastern - ON, QC (most areas) +CA +4901-08816 America/Nipigon Eastern - ON, QC (no DST 1967-73) +CA +4823-08915 America/Thunder_Bay Eastern - ON (Thunder Bay) +CA +6344-06828 America/Iqaluit Eastern - NU (most east areas) +CA +6608-06544 America/Pangnirtung Eastern - NU (Pangnirtung) +CA +484531-0913718 America/Atikokan EST - ON (Atikokan); NU (Coral H) +CA +4953-09709 America/Winnipeg Central - ON (west); Manitoba +CA +4843-09434 America/Rainy_River Central - ON (Rainy R, Ft Frances) +CA +744144-0944945 America/Resolute Central - NU (Resolute) +CA +624900-0920459 America/Rankin_Inlet Central - NU (central) +CA +5024-10439 America/Regina CST - SK (most areas) +CA +5017-10750 America/Swift_Current CST - SK (midwest) +CA +5333-11328 America/Edmonton Mountain - AB; BC (E); SK (W) +CA +690650-1050310 America/Cambridge_Bay Mountain - NU (west) +CA +6227-11421 America/Yellowknife Mountain - NT (central) +CA +682059-1334300 America/Inuvik Mountain - NT (west) +CA +4906-11631 America/Creston MST - BC (Creston) +CA +5946-12014 America/Dawson_Creek MST - BC (Dawson Cr, Ft St John) +CA +5848-12242 America/Fort_Nelson MST - BC (Ft Nelson) +CA +4916-12307 America/Vancouver Pacific - BC (most areas) +CA +6043-13503 America/Whitehorse Pacific - Yukon (south) +CA +6404-13925 America/Dawson Pacific - Yukon (north) +CC -1210+09655 Indian/Cocos +CD -0418+01518 Africa/Kinshasa Dem. Rep. of Congo (west) +CD -1140+02728 Africa/Lubumbashi Dem. Rep. of Congo (east) +CF +0422+01835 Africa/Bangui +CG -0416+01517 Africa/Brazzaville +CH +4723+00832 Europe/Zurich +CI +0519-00402 Africa/Abidjan +CK -2114-15946 Pacific/Rarotonga +CL -3327-07040 America/Santiago Chile (most areas) +CL -5309-07055 America/Punta_Arenas Region of Magallanes +CL -2709-10926 Pacific/Easter Easter Island +CM +0403+00942 Africa/Douala +CN +3114+12128 Asia/Shanghai Beijing Time +CN +4348+08735 Asia/Urumqi Xinjiang Time +CO +0436-07405 America/Bogota +CR +0956-08405 America/Costa_Rica +CU +2308-08222 America/Havana +CV +1455-02331 Atlantic/Cape_Verde +CW +1211-06900 America/Curacao +CX -1025+10543 Indian/Christmas +CY +3510+03322 Asia/Nicosia Cyprus (most areas) +CY +3507+03357 Asia/Famagusta Northern Cyprus +CZ +5005+01426 Europe/Prague +DE +5230+01322 Europe/Berlin Germany (most areas) +DE +4742+00841 Europe/Busingen Busingen +DJ +1136+04309 Africa/Djibouti +DK +5540+01235 Europe/Copenhagen +DM +1518-06124 America/Dominica +DO +1828-06954 America/Santo_Domingo +DZ +3647+00303 Africa/Algiers +EC -0210-07950 America/Guayaquil Ecuador (mainland) +EC -0054-08936 Pacific/Galapagos Galapagos Islands +EE +5925+02445 Europe/Tallinn +EG +3003+03115 Africa/Cairo +EH +2709-01312 Africa/El_Aaiun +ER +1520+03853 Africa/Asmara +ES +4024-00341 Europe/Madrid Spain (mainland) +ES +3553-00519 Africa/Ceuta Ceuta, Melilla +ES +2806-01524 Atlantic/Canary Canary Islands +ET +0902+03842 Africa/Addis_Ababa +FI +6010+02458 Europe/Helsinki +FJ -1808+17825 Pacific/Fiji +FK -5142-05751 Atlantic/Stanley +FM +0725+15147 Pacific/Chuuk Chuuk/Truk, Yap +FM +0658+15813 Pacific/Pohnpei Pohnpei/Ponape +FM +0519+16259 Pacific/Kosrae Kosrae +FO +6201-00646 Atlantic/Faroe +FR +4852+00220 Europe/Paris +GA +0023+00927 Africa/Libreville +GB +513030-0000731 Europe/London +GD +1203-06145 America/Grenada +GE +4143+04449 Asia/Tbilisi +GF +0456-05220 America/Cayenne +GG +492717-0023210 Europe/Guernsey +GH +0533-00013 Africa/Accra +GI +3608-00521 Europe/Gibraltar +GL +6411-05144 America/Godthab Greenland (most areas) +GL +7646-01840 America/Danmarkshavn National Park (east coast) +GL +7029-02158 America/Scoresbysund Scoresbysund/Ittoqqortoormiit +GL +7634-06847 America/Thule Thule/Pituffik +GM +1328-01639 Africa/Banjul +GN +0931-01343 Africa/Conakry +GP +1614-06132 America/Guadeloupe +GQ +0345+00847 Africa/Malabo +GR +3758+02343 Europe/Athens +GS -5416-03632 Atlantic/South_Georgia +GT +1438-09031 America/Guatemala +GU +1328+14445 Pacific/Guam +GW +1151-01535 Africa/Bissau +GY +0648-05810 America/Guyana +HK +2217+11409 Asia/Hong_Kong +HN +1406-08713 America/Tegucigalpa +HR +4548+01558 Europe/Zagreb +HT +1832-07220 America/Port-au-Prince +HU +4730+01905 Europe/Budapest +ID -0610+10648 Asia/Jakarta Java, Sumatra +ID -0002+10920 Asia/Pontianak Borneo (west, central) +ID -0507+11924 Asia/Makassar Borneo (east, south); Sulawesi/Celebes, Bali, Nusa Tengarra; Timor (west) +ID -0232+14042 Asia/Jayapura New Guinea (West Papua / Irian Jaya); Malukus/Moluccas +IE +5320-00615 Europe/Dublin +IL +314650+0351326 Asia/Jerusalem +IM +5409-00428 Europe/Isle_of_Man +IN +2232+08822 Asia/Kolkata +IO -0720+07225 Indian/Chagos +IQ +3321+04425 Asia/Baghdad +IR +3540+05126 Asia/Tehran +IS +6409-02151 Atlantic/Reykjavik +IT +4154+01229 Europe/Rome +JE +491101-0020624 Europe/Jersey +JM +175805-0764736 America/Jamaica +JO +3157+03556 Asia/Amman +JP +353916+1394441 Asia/Tokyo +KE -0117+03649 Africa/Nairobi +KG +4254+07436 Asia/Bishkek +KH +1133+10455 Asia/Phnom_Penh +KI +0125+17300 Pacific/Tarawa Gilbert Islands +KI -0308-17105 Pacific/Enderbury Phoenix Islands +KI +0152-15720 Pacific/Kiritimati Line Islands +KM -1141+04316 Indian/Comoro +KN +1718-06243 America/St_Kitts +KP +3901+12545 Asia/Pyongyang +KR +3733+12658 Asia/Seoul +KW +2920+04759 Asia/Kuwait +KY +1918-08123 America/Cayman +KZ +4315+07657 Asia/Almaty Kazakhstan (most areas) +KZ +4448+06528 Asia/Qyzylorda Qyzylorda/Kyzylorda/Kzyl-Orda +KZ +5017+05710 Asia/Aqtobe Aqtobe/Aktobe +KZ +4431+05016 Asia/Aqtau Mangghystau/Mankistau +KZ +4707+05156 Asia/Atyrau Atyrau/Atirau/Gur'yev +KZ +5113+05121 Asia/Oral West Kazakhstan +LA +1758+10236 Asia/Vientiane +LB +3353+03530 Asia/Beirut +LC +1401-06100 America/St_Lucia +LI +4709+00931 Europe/Vaduz +LK +0656+07951 Asia/Colombo +LR +0618-01047 Africa/Monrovia +LS -2928+02730 Africa/Maseru +LT +5441+02519 Europe/Vilnius +LU +4936+00609 Europe/Luxembourg +LV +5657+02406 Europe/Riga +LY +3254+01311 Africa/Tripoli +MA +3339-00735 Africa/Casablanca +MC +4342+00723 Europe/Monaco +MD +4700+02850 Europe/Chisinau +ME +4226+01916 Europe/Podgorica +MF +1804-06305 America/Marigot +MG -1855+04731 Indian/Antananarivo +MH +0709+17112 Pacific/Majuro Marshall Islands (most areas) +MH +0905+16720 Pacific/Kwajalein Kwajalein +MK +4159+02126 Europe/Skopje +ML +1239-00800 Africa/Bamako +MM +1647+09610 Asia/Yangon +MN +4755+10653 Asia/Ulaanbaatar Mongolia (most areas) +MN +4801+09139 Asia/Hovd Bayan-Olgiy, Govi-Altai, Hovd, Uvs, Zavkhan +MN +4804+11430 Asia/Choibalsan Dornod, Sukhbaatar +MO +221150+1133230 Asia/Macau +MP +1512+14545 Pacific/Saipan +MQ +1436-06105 America/Martinique +MR +1806-01557 Africa/Nouakchott +MS +1643-06213 America/Montserrat +MT +3554+01431 Europe/Malta +MU -2010+05730 Indian/Mauritius +MV +0410+07330 Indian/Maldives +MW -1547+03500 Africa/Blantyre +MX +1924-09909 America/Mexico_City Central Time +MX +2105-08646 America/Cancun Eastern Standard Time - Quintana Roo +MX +2058-08937 America/Merida Central Time - Campeche, Yucatan +MX +2540-10019 America/Monterrey Central Time - Durango; Coahuila, Nuevo Leon, Tamaulipas (most areas) +MX +2550-09730 America/Matamoros Central Time US - Coahuila, Nuevo Leon, Tamaulipas (US border) +MX +2313-10625 America/Mazatlan Mountain Time - Baja California Sur, Nayarit, Sinaloa +MX +2838-10605 America/Chihuahua Mountain Time - Chihuahua (most areas) +MX +2934-10425 America/Ojinaga Mountain Time US - Chihuahua (US border) +MX +2904-11058 America/Hermosillo Mountain Standard Time - Sonora +MX +3232-11701 America/Tijuana Pacific Time US - Baja California +MX +2048-10515 America/Bahia_Banderas Central Time - Bahia de Banderas +MY +0310+10142 Asia/Kuala_Lumpur Malaysia (peninsula) +MY +0133+11020 Asia/Kuching Sabah, Sarawak +MZ -2558+03235 Africa/Maputo +NA -2234+01706 Africa/Windhoek +NC -2216+16627 Pacific/Noumea +NE +1331+00207 Africa/Niamey +NF -2903+16758 Pacific/Norfolk +NG +0627+00324 Africa/Lagos +NI +1209-08617 America/Managua +NL +5222+00454 Europe/Amsterdam +NO +5955+01045 Europe/Oslo +NP +2743+08519 Asia/Kathmandu +NR -0031+16655 Pacific/Nauru +NU -1901-16955 Pacific/Niue +NZ -3652+17446 Pacific/Auckland New Zealand (most areas) +NZ -4357-17633 Pacific/Chatham Chatham Islands +OM +2336+05835 Asia/Muscat +PA +0858-07932 America/Panama +PE -1203-07703 America/Lima +PF -1732-14934 Pacific/Tahiti Society Islands +PF -0900-13930 Pacific/Marquesas Marquesas Islands +PF -2308-13457 Pacific/Gambier Gambier Islands +PG -0930+14710 Pacific/Port_Moresby Papua New Guinea (most areas) +PG -0613+15534 Pacific/Bougainville Bougainville +PH +1435+12100 Asia/Manila +PK +2452+06703 Asia/Karachi +PL +5215+02100 Europe/Warsaw +PM +4703-05620 America/Miquelon +PN -2504-13005 Pacific/Pitcairn +PR +182806-0660622 America/Puerto_Rico +PS +3130+03428 Asia/Gaza Gaza Strip +PS +313200+0350542 Asia/Hebron West Bank +PT +3843-00908 Europe/Lisbon Portugal (mainland) +PT +3238-01654 Atlantic/Madeira Madeira Islands +PT +3744-02540 Atlantic/Azores Azores +PW +0720+13429 Pacific/Palau +PY -2516-05740 America/Asuncion +QA +2517+05132 Asia/Qatar +RE -2052+05528 Indian/Reunion +RO +4426+02606 Europe/Bucharest +RS +4450+02030 Europe/Belgrade +RU +5443+02030 Europe/Kaliningrad MSK-01 - Kaliningrad +RU +554521+0373704 Europe/Moscow MSK+00 - Moscow area +RU +4457+03406 Europe/Simferopol MSK+00 - Crimea +RU +4844+04425 Europe/Volgograd MSK+00 - Volgograd +RU +5836+04939 Europe/Kirov MSK+00 - Kirov +RU +4621+04803 Europe/Astrakhan MSK+01 - Astrakhan +RU +5134+04602 Europe/Saratov MSK+01 - Saratov +RU +5420+04824 Europe/Ulyanovsk MSK+01 - Ulyanovsk +RU +5312+05009 Europe/Samara MSK+01 - Samara, Udmurtia +RU +5651+06036 Asia/Yekaterinburg MSK+02 - Urals +RU +5500+07324 Asia/Omsk MSK+03 - Omsk +RU +5502+08255 Asia/Novosibirsk MSK+04 - Novosibirsk +RU +5322+08345 Asia/Barnaul MSK+04 - Altai +RU +5630+08458 Asia/Tomsk MSK+04 - Tomsk +RU +5345+08707 Asia/Novokuznetsk MSK+04 - Kemerovo +RU +5601+09250 Asia/Krasnoyarsk MSK+04 - Krasnoyarsk area +RU +5216+10420 Asia/Irkutsk MSK+05 - Irkutsk, Buryatia +RU +5203+11328 Asia/Chita MSK+06 - Zabaykalsky +RU +6200+12940 Asia/Yakutsk MSK+06 - Lena River +RU +623923+1353314 Asia/Khandyga MSK+06 - Tomponsky, Ust-Maysky +RU +4310+13156 Asia/Vladivostok MSK+07 - Amur River +RU +643337+1431336 Asia/Ust-Nera MSK+07 - Oymyakonsky +RU +5934+15048 Asia/Magadan MSK+08 - Magadan +RU +4658+14242 Asia/Sakhalin MSK+08 - Sakhalin Island +RU +6728+15343 Asia/Srednekolymsk MSK+08 - Sakha (E); North Kuril Is +RU +5301+15839 Asia/Kamchatka MSK+09 - Kamchatka +RU +6445+17729 Asia/Anadyr MSK+09 - Bering Sea +RW -0157+03004 Africa/Kigali +SA +2438+04643 Asia/Riyadh +SB -0932+16012 Pacific/Guadalcanal +SC -0440+05528 Indian/Mahe +SD +1536+03232 Africa/Khartoum +SE +5920+01803 Europe/Stockholm +SG +0117+10351 Asia/Singapore +SH -1555-00542 Atlantic/St_Helena +SI +4603+01431 Europe/Ljubljana +SJ +7800+01600 Arctic/Longyearbyen +SK +4809+01707 Europe/Bratislava +SL +0830-01315 Africa/Freetown +SM +4355+01228 Europe/San_Marino +SN +1440-01726 Africa/Dakar +SO +0204+04522 Africa/Mogadishu +SR +0550-05510 America/Paramaribo +SS +0451+03137 Africa/Juba +ST +0020+00644 Africa/Sao_Tome +SV +1342-08912 America/El_Salvador +SX +180305-0630250 America/Lower_Princes +SY +3330+03618 Asia/Damascus +SZ -2618+03106 Africa/Mbabane +TC +2128-07108 America/Grand_Turk +TD +1207+01503 Africa/Ndjamena +TF -492110+0701303 Indian/Kerguelen +TG +0608+00113 Africa/Lome +TH +1345+10031 Asia/Bangkok +TJ +3835+06848 Asia/Dushanbe +TK -0922-17114 Pacific/Fakaofo +TL -0833+12535 Asia/Dili +TM +3757+05823 Asia/Ashgabat +TN +3648+01011 Africa/Tunis +TO -2110-17510 Pacific/Tongatapu +TR +4101+02858 Europe/Istanbul +TT +1039-06131 America/Port_of_Spain +TV -0831+17913 Pacific/Funafuti +TW +2503+12130 Asia/Taipei +TZ -0648+03917 Africa/Dar_es_Salaam +UA +5026+03031 Europe/Kiev Ukraine (most areas) +UA +4837+02218 Europe/Uzhgorod Ruthenia +UA +4750+03510 Europe/Zaporozhye Zaporozh'ye/Zaporizhia; Lugansk/Luhansk (east) +UG +0019+03225 Africa/Kampala +UM +2813-17722 Pacific/Midway Midway Islands +UM +1917+16637 Pacific/Wake Wake Island +US +404251-0740023 America/New_York Eastern (most areas) +US +421953-0830245 America/Detroit Eastern - MI (most areas) +US +381515-0854534 America/Kentucky/Louisville Eastern - KY (Louisville area) +US +364947-0845057 America/Kentucky/Monticello Eastern - KY (Wayne) +US +394606-0860929 America/Indiana/Indianapolis Eastern - IN (most areas) +US +384038-0873143 America/Indiana/Vincennes Eastern - IN (Da, Du, K, Mn) +US +410305-0863611 America/Indiana/Winamac Eastern - IN (Pulaski) +US +382232-0862041 America/Indiana/Marengo Eastern - IN (Crawford) +US +382931-0871643 America/Indiana/Petersburg Eastern - IN (Pike) +US +384452-0850402 America/Indiana/Vevay Eastern - IN (Switzerland) +US +415100-0873900 America/Chicago Central (most areas) +US +375711-0864541 America/Indiana/Tell_City Central - IN (Perry) +US +411745-0863730 America/Indiana/Knox Central - IN (Starke) +US +450628-0873651 America/Menominee Central - MI (Wisconsin border) +US +470659-1011757 America/North_Dakota/Center Central - ND (Oliver) +US +465042-1012439 America/North_Dakota/New_Salem Central - ND (Morton rural) +US +471551-1014640 America/North_Dakota/Beulah Central - ND (Mercer) +US +394421-1045903 America/Denver Mountain (most areas) +US +433649-1161209 America/Boise Mountain - ID (south); OR (east) +US +332654-1120424 America/Phoenix MST - Arizona (except Navajo) +US +340308-1181434 America/Los_Angeles Pacific +US +611305-1495401 America/Anchorage Alaska (most areas) +US +581807-1342511 America/Juneau Alaska - Juneau area +US +571035-1351807 America/Sitka Alaska - Sitka area +US +550737-1313435 America/Metlakatla Alaska - Annette Island +US +593249-1394338 America/Yakutat Alaska - Yakutat +US +643004-1652423 America/Nome Alaska (west) +US +515248-1763929 America/Adak Aleutian Islands +US +211825-1575130 Pacific/Honolulu Hawaii +UY -345433-0561245 America/Montevideo +UZ +3940+06648 Asia/Samarkand Uzbekistan (west) +UZ +4120+06918 Asia/Tashkent Uzbekistan (east) +VA +415408+0122711 Europe/Vatican +VC +1309-06114 America/St_Vincent +VE +1030-06656 America/Caracas +VG +1827-06437 America/Tortola +VI +1821-06456 America/St_Thomas +VN +1045+10640 Asia/Ho_Chi_Minh +VU -1740+16825 Pacific/Efate +WF -1318-17610 Pacific/Wallis +WS -1350-17144 Pacific/Apia +YE +1245+04512 Asia/Aden +YT -1247+04514 Indian/Mayotte +ZA -2615+02800 Africa/Johannesburg +ZM -1525+02817 Africa/Lusaka +ZW -1750+03103 Africa/Harare diff --git a/libs/pytz/zoneinfo/zone1970.tab b/libs/pytz/zoneinfo/zone1970.tab new file mode 100644 index 00000000..7c86fb69 --- /dev/null +++ b/libs/pytz/zoneinfo/zone1970.tab @@ -0,0 +1,382 @@ +# tzdb timezone descriptions +# +# This file is in the public domain. +# +# From Paul Eggert (2018-06-27): +# This file contains a table where each row stands for a timezone where +# civil timestamps have agreed since 1970. Columns are separated by +# a single tab. Lines beginning with '#' are comments. All text uses +# UTF-8 encoding. The columns of the table are as follows: +# +# 1. The countries that overlap the timezone, as a comma-separated list +# of ISO 3166 2-character country codes. See the file 'iso3166.tab'. +# 2. Latitude and longitude of the timezone's principal location +# in ISO 6709 sign-degrees-minutes-seconds format, +# either ±DDMM±DDDMM or ±DDMMSS±DDDMMSS, +# first latitude (+ is north), then longitude (+ is east). +# 3. Timezone name used in value of TZ environment variable. +# Please see the theory.html file for how these names are chosen. +# If multiple timezones overlap a country, each has a row in the +# table, with each column 1 containing the country code. +# 4. Comments; present if and only if a country has multiple timezones. +# +# If a timezone covers multiple countries, the most-populous city is used, +# and that country is listed first in column 1; any other countries +# are listed alphabetically by country code. The table is sorted +# first by country code, then (if possible) by an order within the +# country that (1) makes some geographical sense, and (2) puts the +# most populous timezones first, where that does not contradict (1). +# +# This table is intended as an aid for users, to help them select timezones +# appropriate for their practical needs. It is not intended to take or +# endorse any position on legal or territorial claims. +# +#country- +#codes coordinates TZ comments +AD +4230+00131 Europe/Andorra +AE,OM +2518+05518 Asia/Dubai +AF +3431+06912 Asia/Kabul +AL +4120+01950 Europe/Tirane +AM +4011+04430 Asia/Yerevan +AQ -6617+11031 Antarctica/Casey Casey +AQ -6835+07758 Antarctica/Davis Davis +AQ -6640+14001 Antarctica/DumontDUrville Dumont-d'Urville +AQ -6736+06253 Antarctica/Mawson Mawson +AQ -6448-06406 Antarctica/Palmer Palmer +AQ -6734-06808 Antarctica/Rothera Rothera +AQ -690022+0393524 Antarctica/Syowa Syowa +AQ -720041+0023206 Antarctica/Troll Troll +AQ -7824+10654 Antarctica/Vostok Vostok +AR -3436-05827 America/Argentina/Buenos_Aires Buenos Aires (BA, CF) +AR -3124-06411 America/Argentina/Cordoba Argentina (most areas: CB, CC, CN, ER, FM, MN, SE, SF) +AR -2447-06525 America/Argentina/Salta Salta (SA, LP, NQ, RN) +AR -2411-06518 America/Argentina/Jujuy Jujuy (JY) +AR -2649-06513 America/Argentina/Tucuman Tucumán (TM) +AR -2828-06547 America/Argentina/Catamarca Catamarca (CT); Chubut (CH) +AR -2926-06651 America/Argentina/La_Rioja La Rioja (LR) +AR -3132-06831 America/Argentina/San_Juan San Juan (SJ) +AR -3253-06849 America/Argentina/Mendoza Mendoza (MZ) +AR -3319-06621 America/Argentina/San_Luis San Luis (SL) +AR -5138-06913 America/Argentina/Rio_Gallegos Santa Cruz (SC) +AR -5448-06818 America/Argentina/Ushuaia Tierra del Fuego (TF) +AS,UM -1416-17042 Pacific/Pago_Pago Samoa, Midway +AT +4813+01620 Europe/Vienna +AU -3133+15905 Australia/Lord_Howe Lord Howe Island +AU -5430+15857 Antarctica/Macquarie Macquarie Island +AU -4253+14719 Australia/Hobart Tasmania (most areas) +AU -3956+14352 Australia/Currie Tasmania (King Island) +AU -3749+14458 Australia/Melbourne Victoria +AU -3352+15113 Australia/Sydney New South Wales (most areas) +AU -3157+14127 Australia/Broken_Hill New South Wales (Yancowinna) +AU -2728+15302 Australia/Brisbane Queensland (most areas) +AU -2016+14900 Australia/Lindeman Queensland (Whitsunday Islands) +AU -3455+13835 Australia/Adelaide South Australia +AU -1228+13050 Australia/Darwin Northern Territory +AU -3157+11551 Australia/Perth Western Australia (most areas) +AU -3143+12852 Australia/Eucla Western Australia (Eucla) +AZ +4023+04951 Asia/Baku +BB +1306-05937 America/Barbados +BD +2343+09025 Asia/Dhaka +BE +5050+00420 Europe/Brussels +BG +4241+02319 Europe/Sofia +BM +3217-06446 Atlantic/Bermuda +BN +0456+11455 Asia/Brunei +BO -1630-06809 America/La_Paz +BR -0351-03225 America/Noronha Atlantic islands +BR -0127-04829 America/Belem Pará (east); Amapá +BR -0343-03830 America/Fortaleza Brazil (northeast: MA, PI, CE, RN, PB) +BR -0803-03454 America/Recife Pernambuco +BR -0712-04812 America/Araguaina Tocantins +BR -0940-03543 America/Maceio Alagoas, Sergipe +BR -1259-03831 America/Bahia Bahia +BR -2332-04637 America/Sao_Paulo Brazil (southeast: GO, DF, MG, ES, RJ, SP, PR, SC, RS) +BR -2027-05437 America/Campo_Grande Mato Grosso do Sul +BR -1535-05605 America/Cuiaba Mato Grosso +BR -0226-05452 America/Santarem Pará (west) +BR -0846-06354 America/Porto_Velho Rondônia +BR +0249-06040 America/Boa_Vista Roraima +BR -0308-06001 America/Manaus Amazonas (east) +BR -0640-06952 America/Eirunepe Amazonas (west) +BR -0958-06748 America/Rio_Branco Acre +BS +2505-07721 America/Nassau +BT +2728+08939 Asia/Thimphu +BY +5354+02734 Europe/Minsk +BZ +1730-08812 America/Belize +CA +4734-05243 America/St_Johns Newfoundland; Labrador (southeast) +CA +4439-06336 America/Halifax Atlantic - NS (most areas); PE +CA +4612-05957 America/Glace_Bay Atlantic - NS (Cape Breton) +CA +4606-06447 America/Moncton Atlantic - New Brunswick +CA +5320-06025 America/Goose_Bay Atlantic - Labrador (most areas) +CA +5125-05707 America/Blanc-Sablon AST - QC (Lower North Shore) +CA +4339-07923 America/Toronto Eastern - ON, QC (most areas) +CA +4901-08816 America/Nipigon Eastern - ON, QC (no DST 1967-73) +CA +4823-08915 America/Thunder_Bay Eastern - ON (Thunder Bay) +CA +6344-06828 America/Iqaluit Eastern - NU (most east areas) +CA +6608-06544 America/Pangnirtung Eastern - NU (Pangnirtung) +CA +484531-0913718 America/Atikokan EST - ON (Atikokan); NU (Coral H) +CA +4953-09709 America/Winnipeg Central - ON (west); Manitoba +CA +4843-09434 America/Rainy_River Central - ON (Rainy R, Ft Frances) +CA +744144-0944945 America/Resolute Central - NU (Resolute) +CA +624900-0920459 America/Rankin_Inlet Central - NU (central) +CA +5024-10439 America/Regina CST - SK (most areas) +CA +5017-10750 America/Swift_Current CST - SK (midwest) +CA +5333-11328 America/Edmonton Mountain - AB; BC (E); SK (W) +CA +690650-1050310 America/Cambridge_Bay Mountain - NU (west) +CA +6227-11421 America/Yellowknife Mountain - NT (central) +CA +682059-1334300 America/Inuvik Mountain - NT (west) +CA +4906-11631 America/Creston MST - BC (Creston) +CA +5946-12014 America/Dawson_Creek MST - BC (Dawson Cr, Ft St John) +CA +5848-12242 America/Fort_Nelson MST - BC (Ft Nelson) +CA +4916-12307 America/Vancouver Pacific - BC (most areas) +CA +6043-13503 America/Whitehorse Pacific - Yukon (south) +CA +6404-13925 America/Dawson Pacific - Yukon (north) +CC -1210+09655 Indian/Cocos +CH,DE,LI +4723+00832 Europe/Zurich Swiss time +CI,BF,GM,GN,ML,MR,SH,SL,SN,TG +0519-00402 Africa/Abidjan +CK -2114-15946 Pacific/Rarotonga +CL -3327-07040 America/Santiago Chile (most areas) +CL -5309-07055 America/Punta_Arenas Region of Magallanes +CL -2709-10926 Pacific/Easter Easter Island +CN +3114+12128 Asia/Shanghai Beijing Time +CN +4348+08735 Asia/Urumqi Xinjiang Time +CO +0436-07405 America/Bogota +CR +0956-08405 America/Costa_Rica +CU +2308-08222 America/Havana +CV +1455-02331 Atlantic/Cape_Verde +CW,AW,BQ,SX +1211-06900 America/Curacao +CX -1025+10543 Indian/Christmas +CY +3510+03322 Asia/Nicosia Cyprus (most areas) +CY +3507+03357 Asia/Famagusta Northern Cyprus +CZ,SK +5005+01426 Europe/Prague +DE +5230+01322 Europe/Berlin Germany (most areas) +DK +5540+01235 Europe/Copenhagen +DO +1828-06954 America/Santo_Domingo +DZ +3647+00303 Africa/Algiers +EC -0210-07950 America/Guayaquil Ecuador (mainland) +EC -0054-08936 Pacific/Galapagos Galápagos Islands +EE +5925+02445 Europe/Tallinn +EG +3003+03115 Africa/Cairo +EH +2709-01312 Africa/El_Aaiun +ES +4024-00341 Europe/Madrid Spain (mainland) +ES +3553-00519 Africa/Ceuta Ceuta, Melilla +ES +2806-01524 Atlantic/Canary Canary Islands +FI,AX +6010+02458 Europe/Helsinki +FJ -1808+17825 Pacific/Fiji +FK -5142-05751 Atlantic/Stanley +FM +0725+15147 Pacific/Chuuk Chuuk/Truk, Yap +FM +0658+15813 Pacific/Pohnpei Pohnpei/Ponape +FM +0519+16259 Pacific/Kosrae Kosrae +FO +6201-00646 Atlantic/Faroe +FR +4852+00220 Europe/Paris +GB,GG,IM,JE +513030-0000731 Europe/London +GE +4143+04449 Asia/Tbilisi +GF +0456-05220 America/Cayenne +GH +0533-00013 Africa/Accra +GI +3608-00521 Europe/Gibraltar +GL +6411-05144 America/Godthab Greenland (most areas) +GL +7646-01840 America/Danmarkshavn National Park (east coast) +GL +7029-02158 America/Scoresbysund Scoresbysund/Ittoqqortoormiit +GL +7634-06847 America/Thule Thule/Pituffik +GR +3758+02343 Europe/Athens +GS -5416-03632 Atlantic/South_Georgia +GT +1438-09031 America/Guatemala +GU,MP +1328+14445 Pacific/Guam +GW +1151-01535 Africa/Bissau +GY +0648-05810 America/Guyana +HK +2217+11409 Asia/Hong_Kong +HN +1406-08713 America/Tegucigalpa +HT +1832-07220 America/Port-au-Prince +HU +4730+01905 Europe/Budapest +ID -0610+10648 Asia/Jakarta Java, Sumatra +ID -0002+10920 Asia/Pontianak Borneo (west, central) +ID -0507+11924 Asia/Makassar Borneo (east, south); Sulawesi/Celebes, Bali, Nusa Tengarra; Timor (west) +ID -0232+14042 Asia/Jayapura New Guinea (West Papua / Irian Jaya); Malukus/Moluccas +IE +5320-00615 Europe/Dublin +IL +314650+0351326 Asia/Jerusalem +IN +2232+08822 Asia/Kolkata +IO -0720+07225 Indian/Chagos +IQ +3321+04425 Asia/Baghdad +IR +3540+05126 Asia/Tehran +IS +6409-02151 Atlantic/Reykjavik +IT,SM,VA +4154+01229 Europe/Rome +JM +175805-0764736 America/Jamaica +JO +3157+03556 Asia/Amman +JP +353916+1394441 Asia/Tokyo +KE,DJ,ER,ET,KM,MG,SO,TZ,UG,YT -0117+03649 Africa/Nairobi +KG +4254+07436 Asia/Bishkek +KI +0125+17300 Pacific/Tarawa Gilbert Islands +KI -0308-17105 Pacific/Enderbury Phoenix Islands +KI +0152-15720 Pacific/Kiritimati Line Islands +KP +3901+12545 Asia/Pyongyang +KR +3733+12658 Asia/Seoul +KZ +4315+07657 Asia/Almaty Kazakhstan (most areas) +KZ +4448+06528 Asia/Qyzylorda Qyzylorda/Kyzylorda/Kzyl-Orda +KZ +5017+05710 Asia/Aqtobe Aqtöbe/Aktobe +KZ +4431+05016 Asia/Aqtau Mangghystaū/Mankistau +KZ +4707+05156 Asia/Atyrau Atyraū/Atirau/Gur'yev +KZ +5113+05121 Asia/Oral West Kazakhstan +LB +3353+03530 Asia/Beirut +LK +0656+07951 Asia/Colombo +LR +0618-01047 Africa/Monrovia +LT +5441+02519 Europe/Vilnius +LU +4936+00609 Europe/Luxembourg +LV +5657+02406 Europe/Riga +LY +3254+01311 Africa/Tripoli +MA +3339-00735 Africa/Casablanca +MC +4342+00723 Europe/Monaco +MD +4700+02850 Europe/Chisinau +MH +0709+17112 Pacific/Majuro Marshall Islands (most areas) +MH +0905+16720 Pacific/Kwajalein Kwajalein +MM +1647+09610 Asia/Yangon +MN +4755+10653 Asia/Ulaanbaatar Mongolia (most areas) +MN +4801+09139 Asia/Hovd Bayan-Ölgii, Govi-Altai, Hovd, Uvs, Zavkhan +MN +4804+11430 Asia/Choibalsan Dornod, Sükhbaatar +MO +221150+1133230 Asia/Macau +MQ +1436-06105 America/Martinique +MT +3554+01431 Europe/Malta +MU -2010+05730 Indian/Mauritius +MV +0410+07330 Indian/Maldives +MX +1924-09909 America/Mexico_City Central Time +MX +2105-08646 America/Cancun Eastern Standard Time - Quintana Roo +MX +2058-08937 America/Merida Central Time - Campeche, Yucatán +MX +2540-10019 America/Monterrey Central Time - Durango; Coahuila, Nuevo León, Tamaulipas (most areas) +MX +2550-09730 America/Matamoros Central Time US - Coahuila, Nuevo León, Tamaulipas (US border) +MX +2313-10625 America/Mazatlan Mountain Time - Baja California Sur, Nayarit, Sinaloa +MX +2838-10605 America/Chihuahua Mountain Time - Chihuahua (most areas) +MX +2934-10425 America/Ojinaga Mountain Time US - Chihuahua (US border) +MX +2904-11058 America/Hermosillo Mountain Standard Time - Sonora +MX +3232-11701 America/Tijuana Pacific Time US - Baja California +MX +2048-10515 America/Bahia_Banderas Central Time - Bahía de Banderas +MY +0310+10142 Asia/Kuala_Lumpur Malaysia (peninsula) +MY +0133+11020 Asia/Kuching Sabah, Sarawak +MZ,BI,BW,CD,MW,RW,ZM,ZW -2558+03235 Africa/Maputo Central Africa Time +NA -2234+01706 Africa/Windhoek +NC -2216+16627 Pacific/Noumea +NF -2903+16758 Pacific/Norfolk +NG,AO,BJ,CD,CF,CG,CM,GA,GQ,NE +0627+00324 Africa/Lagos West Africa Time +NI +1209-08617 America/Managua +NL +5222+00454 Europe/Amsterdam +NO,SJ +5955+01045 Europe/Oslo +NP +2743+08519 Asia/Kathmandu +NR -0031+16655 Pacific/Nauru +NU -1901-16955 Pacific/Niue +NZ,AQ -3652+17446 Pacific/Auckland New Zealand time +NZ -4357-17633 Pacific/Chatham Chatham Islands +PA,KY +0858-07932 America/Panama +PE -1203-07703 America/Lima +PF -1732-14934 Pacific/Tahiti Society Islands +PF -0900-13930 Pacific/Marquesas Marquesas Islands +PF -2308-13457 Pacific/Gambier Gambier Islands +PG -0930+14710 Pacific/Port_Moresby Papua New Guinea (most areas) +PG -0613+15534 Pacific/Bougainville Bougainville +PH +1435+12100 Asia/Manila +PK +2452+06703 Asia/Karachi +PL +5215+02100 Europe/Warsaw +PM +4703-05620 America/Miquelon +PN -2504-13005 Pacific/Pitcairn +PR +182806-0660622 America/Puerto_Rico +PS +3130+03428 Asia/Gaza Gaza Strip +PS +313200+0350542 Asia/Hebron West Bank +PT +3843-00908 Europe/Lisbon Portugal (mainland) +PT +3238-01654 Atlantic/Madeira Madeira Islands +PT +3744-02540 Atlantic/Azores Azores +PW +0720+13429 Pacific/Palau +PY -2516-05740 America/Asuncion +QA,BH +2517+05132 Asia/Qatar +RE,TF -2052+05528 Indian/Reunion Réunion, Crozet, Scattered Islands +RO +4426+02606 Europe/Bucharest +RS,BA,HR,ME,MK,SI +4450+02030 Europe/Belgrade +RU +5443+02030 Europe/Kaliningrad MSK-01 - Kaliningrad +RU +554521+0373704 Europe/Moscow MSK+00 - Moscow area +RU +4457+03406 Europe/Simferopol MSK+00 - Crimea +RU +4844+04425 Europe/Volgograd MSK+00 - Volgograd +RU +5836+04939 Europe/Kirov MSK+00 - Kirov +RU +4621+04803 Europe/Astrakhan MSK+01 - Astrakhan +RU +5134+04602 Europe/Saratov MSK+01 - Saratov +RU +5420+04824 Europe/Ulyanovsk MSK+01 - Ulyanovsk +RU +5312+05009 Europe/Samara MSK+01 - Samara, Udmurtia +RU +5651+06036 Asia/Yekaterinburg MSK+02 - Urals +RU +5500+07324 Asia/Omsk MSK+03 - Omsk +RU +5502+08255 Asia/Novosibirsk MSK+04 - Novosibirsk +RU +5322+08345 Asia/Barnaul MSK+04 - Altai +RU +5630+08458 Asia/Tomsk MSK+04 - Tomsk +RU +5345+08707 Asia/Novokuznetsk MSK+04 - Kemerovo +RU +5601+09250 Asia/Krasnoyarsk MSK+04 - Krasnoyarsk area +RU +5216+10420 Asia/Irkutsk MSK+05 - Irkutsk, Buryatia +RU +5203+11328 Asia/Chita MSK+06 - Zabaykalsky +RU +6200+12940 Asia/Yakutsk MSK+06 - Lena River +RU +623923+1353314 Asia/Khandyga MSK+06 - Tomponsky, Ust-Maysky +RU +4310+13156 Asia/Vladivostok MSK+07 - Amur River +RU +643337+1431336 Asia/Ust-Nera MSK+07 - Oymyakonsky +RU +5934+15048 Asia/Magadan MSK+08 - Magadan +RU +4658+14242 Asia/Sakhalin MSK+08 - Sakhalin Island +RU +6728+15343 Asia/Srednekolymsk MSK+08 - Sakha (E); North Kuril Is +RU +5301+15839 Asia/Kamchatka MSK+09 - Kamchatka +RU +6445+17729 Asia/Anadyr MSK+09 - Bering Sea +SA,KW,YE +2438+04643 Asia/Riyadh +SB -0932+16012 Pacific/Guadalcanal +SC -0440+05528 Indian/Mahe +SD +1536+03232 Africa/Khartoum +SE +5920+01803 Europe/Stockholm +SG +0117+10351 Asia/Singapore +SR +0550-05510 America/Paramaribo +SS +0451+03137 Africa/Juba +ST +0020+00644 Africa/Sao_Tome +SV +1342-08912 America/El_Salvador +SY +3330+03618 Asia/Damascus +TC +2128-07108 America/Grand_Turk +TD +1207+01503 Africa/Ndjamena +TF -492110+0701303 Indian/Kerguelen Kerguelen, St Paul Island, Amsterdam Island +TH,KH,LA,VN +1345+10031 Asia/Bangkok Indochina (most areas) +TJ +3835+06848 Asia/Dushanbe +TK -0922-17114 Pacific/Fakaofo +TL -0833+12535 Asia/Dili +TM +3757+05823 Asia/Ashgabat +TN +3648+01011 Africa/Tunis +TO -2110-17510 Pacific/Tongatapu +TR +4101+02858 Europe/Istanbul +TT,AG,AI,BL,DM,GD,GP,KN,LC,MF,MS,VC,VG,VI +1039-06131 America/Port_of_Spain +TV -0831+17913 Pacific/Funafuti +TW +2503+12130 Asia/Taipei +UA +5026+03031 Europe/Kiev Ukraine (most areas) +UA +4837+02218 Europe/Uzhgorod Ruthenia +UA +4750+03510 Europe/Zaporozhye Zaporozh'ye/Zaporizhia; Lugansk/Luhansk (east) +UM +1917+16637 Pacific/Wake Wake Island +US +404251-0740023 America/New_York Eastern (most areas) +US +421953-0830245 America/Detroit Eastern - MI (most areas) +US +381515-0854534 America/Kentucky/Louisville Eastern - KY (Louisville area) +US +364947-0845057 America/Kentucky/Monticello Eastern - KY (Wayne) +US +394606-0860929 America/Indiana/Indianapolis Eastern - IN (most areas) +US +384038-0873143 America/Indiana/Vincennes Eastern - IN (Da, Du, K, Mn) +US +410305-0863611 America/Indiana/Winamac Eastern - IN (Pulaski) +US +382232-0862041 America/Indiana/Marengo Eastern - IN (Crawford) +US +382931-0871643 America/Indiana/Petersburg Eastern - IN (Pike) +US +384452-0850402 America/Indiana/Vevay Eastern - IN (Switzerland) +US +415100-0873900 America/Chicago Central (most areas) +US +375711-0864541 America/Indiana/Tell_City Central - IN (Perry) +US +411745-0863730 America/Indiana/Knox Central - IN (Starke) +US +450628-0873651 America/Menominee Central - MI (Wisconsin border) +US +470659-1011757 America/North_Dakota/Center Central - ND (Oliver) +US +465042-1012439 America/North_Dakota/New_Salem Central - ND (Morton rural) +US +471551-1014640 America/North_Dakota/Beulah Central - ND (Mercer) +US +394421-1045903 America/Denver Mountain (most areas) +US +433649-1161209 America/Boise Mountain - ID (south); OR (east) +US +332654-1120424 America/Phoenix MST - Arizona (except Navajo) +US +340308-1181434 America/Los_Angeles Pacific +US +611305-1495401 America/Anchorage Alaska (most areas) +US +581807-1342511 America/Juneau Alaska - Juneau area +US +571035-1351807 America/Sitka Alaska - Sitka area +US +550737-1313435 America/Metlakatla Alaska - Annette Island +US +593249-1394338 America/Yakutat Alaska - Yakutat +US +643004-1652423 America/Nome Alaska (west) +US +515248-1763929 America/Adak Aleutian Islands +US,UM +211825-1575130 Pacific/Honolulu Hawaii +UY -345433-0561245 America/Montevideo +UZ +3940+06648 Asia/Samarkand Uzbekistan (west) +UZ +4120+06918 Asia/Tashkent Uzbekistan (east) +VE +1030-06656 America/Caracas +VN +1045+10640 Asia/Ho_Chi_Minh Vietnam (south) +VU -1740+16825 Pacific/Efate +WF -1318-17610 Pacific/Wallis +WS -1350-17144 Pacific/Apia +ZA,LS,SZ -2615+02800 Africa/Johannesburg diff --git a/libs/rarfile.py b/libs/rarfile.py index 25b61196..78148c19 100644 --- a/libs/rarfile.py +++ b/libs/rarfile.py @@ -54,74 +54,127 @@ here they are with defaults, and reason to change it:: # Set to full path of unrar.exe if it is not in PATH rarfile.UNRAR_TOOL = "unrar" - # Set to 0 if you don't look at comments and want to - # avoid wasting time for parsing them - rarfile.NEED_COMMENTS = 1 - - # Set up to 1 if you don't want to deal with decoding comments - # from unknown encoding. rarfile will try couple of common - # encodings in sequence. - rarfile.UNICODE_COMMENTS = 0 - - # Set to 1 if you prefer timestamps to be datetime objects - # instead tuples - rarfile.USE_DATETIME = 0 - - # Set to '/' to be more compatible with zipfile - rarfile.PATH_SEP = '\\' + # Set to '\\' to be more compatible with old rarfile + rarfile.PATH_SEP = '/' For more details, refer to source. """ -__version__ = '2.8' - -# export only interesting items -__all__ = ['is_rarfile', 'RarInfo', 'RarFile', 'RarExtFile'] +from __future__ import division, print_function ## ## Imports and compat - support both Python 2.x and 3.x ## -import sys, os, struct, errno +import sys +import os +import errno +import struct + from struct import pack, unpack, Struct -from binascii import crc32 +from binascii import crc32, hexlify from tempfile import mkstemp from subprocess import Popen, PIPE, STDOUT -from datetime import datetime from io import RawIOBase -from hashlib import sha1 +from hashlib import sha1, sha256 +from hmac import HMAC +from datetime import datetime, timedelta, tzinfo + +# fixed offset timezone, for UTC +try: + from datetime import timezone +except ImportError: + class timezone(tzinfo): + """Compat timezone.""" + __slots__ = ('_ofs', '_name') + _DST = timedelta(0) + + def __init__(self, offset, name): + super(timezone, self).__init__() + self._ofs, self._name = offset, name + + def utcoffset(self, dt): + return self._ofs + + def tzname(self, dt): + return self._name + + def dst(self, dt): + return self._DST # only needed for encryped headers try: try: from cryptography.hazmat.primitives.ciphers import algorithms, modes, Cipher from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.kdf import pbkdf2 + class AES_CBC_Decrypt(object): - block_size = 16 + """Decrypt API""" def __init__(self, key, iv): ciph = Cipher(algorithms.AES(key), modes.CBC(iv), default_backend()) - self.dec = ciph.decryptor() - def decrypt(self, data): - return self.dec.update(data) + self.decrypt = ciph.decryptor().update + + def pbkdf2_sha256(password, salt, iters): + """PBKDF2 with HMAC-SHA256""" + ctx = pbkdf2.PBKDF2HMAC(hashes.SHA256(), 32, salt, iters, default_backend()) + return ctx.derive(password) + except ImportError: from Crypto.Cipher import AES + from Crypto.Protocol import KDF + class AES_CBC_Decrypt(object): - block_size = 16 + """Decrypt API""" def __init__(self, key, iv): - self.dec = AES.new(key, AES.MODE_CBC, iv) - def decrypt(self, data): - return self.dec.decrypt(data) + self.decrypt = AES.new(key, AES.MODE_CBC, iv).decrypt + + def pbkdf2_sha256(password, salt, iters): + """PBKDF2 with HMAC-SHA256""" + return KDF.PBKDF2(password, salt, 32, iters, hmac_sha256) + _have_crypto = 1 except ImportError: _have_crypto = 0 +try: + try: + from hashlib import blake2s + _have_blake2 = True + except ImportError: + from pyblake2 import blake2s + _have_blake2 = True +except ImportError: + _have_blake2 = False + # compat with 2.x if sys.hexversion < 0x3000000: - # prefer 3.x behaviour - range = xrange -else: + def rar_crc32(data, prev=0): + """CRC32 with unsigned values. + """ + if (prev > 0) and (prev & 0x80000000): + prev -= (1 << 32) + res = crc32(data, prev) + if res < 0: + res += (1 << 32) + return res + tohex = hexlify + _byte_code = ord +else: # pragma: no cover + def tohex(data): + """Return hex string.""" + return hexlify(data).decode('ascii') + rar_crc32 = crc32 unicode = str + _byte_code = int # noqa + + +__version__ = '3.0' + +# export only interesting items +__all__ = ['is_rarfile', 'RarInfo', 'RarFile', 'RarExtFile'] ## ## Module configuration. Can be tuned after importing. @@ -166,36 +219,27 @@ ALT_CHECK_ARGS = ('--help',) USE_EXTRACT_HACK = 1 #: limit the filesize for tmp archive usage -HACK_SIZE_LIMIT = 20*1024*1024 - -#: whether to parse file/archive comments. -NEED_COMMENTS = 1 - -#: whether to convert comments to unicode strings -UNICODE_COMMENTS = 0 - -#: Convert RAR time tuple into datetime() object -USE_DATETIME = 0 +HACK_SIZE_LIMIT = 20 * 1024 * 1024 #: Separator for path name components. RAR internally uses '\\'. #: Use '/' to be similar with zipfile. -PATH_SEP = '\\' +PATH_SEP = '/' ## ## rar constants ## # block types -RAR_BLOCK_MARK = 0x72 # r -RAR_BLOCK_MAIN = 0x73 # s -RAR_BLOCK_FILE = 0x74 # t -RAR_BLOCK_OLD_COMMENT = 0x75 # u -RAR_BLOCK_OLD_EXTRA = 0x76 # v -RAR_BLOCK_OLD_SUB = 0x77 # w -RAR_BLOCK_OLD_RECOVERY = 0x78 # x -RAR_BLOCK_OLD_AUTH = 0x79 # y -RAR_BLOCK_SUB = 0x7a # z -RAR_BLOCK_ENDARC = 0x7b # { +RAR_BLOCK_MARK = 0x72 # r +RAR_BLOCK_MAIN = 0x73 # s +RAR_BLOCK_FILE = 0x74 # t +RAR_BLOCK_OLD_COMMENT = 0x75 # u +RAR_BLOCK_OLD_EXTRA = 0x76 # v +RAR_BLOCK_OLD_SUB = 0x77 # w +RAR_BLOCK_OLD_RECOVERY = 0x78 # x +RAR_BLOCK_OLD_AUTH = 0x79 # y +RAR_BLOCK_SUB = 0x7a # z +RAR_BLOCK_ENDARC = 0x7b # { # flags for RAR_BLOCK_MAIN RAR_MAIN_VOLUME = 0x0001 @@ -257,196 +301,335 @@ RAR_M3 = 0x33 RAR_M4 = 0x34 RAR_M5 = 0x35 +# +# RAR5 constants +# + +RAR5_BLOCK_MAIN = 1 +RAR5_BLOCK_FILE = 2 +RAR5_BLOCK_SERVICE = 3 +RAR5_BLOCK_ENCRYPTION = 4 +RAR5_BLOCK_ENDARC = 5 + +RAR5_BLOCK_FLAG_EXTRA_DATA = 0x01 +RAR5_BLOCK_FLAG_DATA_AREA = 0x02 +RAR5_BLOCK_FLAG_SKIP_IF_UNKNOWN = 0x04 +RAR5_BLOCK_FLAG_SPLIT_BEFORE = 0x08 +RAR5_BLOCK_FLAG_SPLIT_AFTER = 0x10 +RAR5_BLOCK_FLAG_DEPENDS_PREV = 0x20 +RAR5_BLOCK_FLAG_KEEP_WITH_PARENT = 0x40 + +RAR5_MAIN_FLAG_ISVOL = 0x01 +RAR5_MAIN_FLAG_HAS_VOLNR = 0x02 +RAR5_MAIN_FLAG_SOLID = 0x04 +RAR5_MAIN_FLAG_RECOVERY = 0x08 +RAR5_MAIN_FLAG_LOCKED = 0x10 + +RAR5_FILE_FLAG_ISDIR = 0x01 +RAR5_FILE_FLAG_HAS_MTIME = 0x02 +RAR5_FILE_FLAG_HAS_CRC32 = 0x04 +RAR5_FILE_FLAG_UNKNOWN_SIZE = 0x08 + +RAR5_COMPR_SOLID = 0x40 + +RAR5_ENC_FLAG_HAS_CHECKVAL = 0x01 + +RAR5_ENDARC_FLAG_NEXT_VOL = 0x01 + +RAR5_XFILE_ENCRYPTION = 1 +RAR5_XFILE_HASH = 2 +RAR5_XFILE_TIME = 3 +RAR5_XFILE_VERSION = 4 +RAR5_XFILE_REDIR = 5 +RAR5_XFILE_OWNER = 6 +RAR5_XFILE_SERVICE = 7 + +RAR5_XTIME_UNIXTIME = 0x01 +RAR5_XTIME_HAS_MTIME = 0x02 +RAR5_XTIME_HAS_CTIME = 0x04 +RAR5_XTIME_HAS_ATIME = 0x08 + +RAR5_XENC_CIPHER_AES256 = 0 + +RAR5_XENC_CHECKVAL = 0x01 +RAR5_XENC_TWEAKED = 0x02 + +RAR5_XHASH_BLAKE2SP = 0 + +RAR5_XREDIR_UNIX_SYMLINK = 1 +RAR5_XREDIR_WINDOWS_SYMLINK = 2 +RAR5_XREDIR_WINDOWS_JUNCTION = 3 +RAR5_XREDIR_HARD_LINK = 4 +RAR5_XREDIR_FILE_COPY = 5 + +RAR5_XREDIR_ISDIR = 0x01 + +RAR5_XOWNER_UNAME = 0x01 +RAR5_XOWNER_GNAME = 0x02 +RAR5_XOWNER_UID = 0x04 +RAR5_XOWNER_GID = 0x08 + +RAR5_OS_WINDOWS = 0 +RAR5_OS_UNIX = 1 + ## ## internal constants ## RAR_ID = b"Rar!\x1a\x07\x00" -ZERO = b"\0" -EMPTY = b"" +RAR5_ID = b"Rar!\x1a\x07\x01\x00" +ZERO = b'\0' +EMPTY = b'' +UTC = timezone(timedelta(0), 'UTC') +BSIZE = 32 * 1024 -S_BLK_HDR = Struct('<HBHH') -S_FILE_HDR = Struct('<LLBLLBBHL') -S_LONG = Struct('<L') -S_SHORT = Struct('<H') -S_BYTE = Struct('<B') -S_COMMENT_HDR = Struct('<HBBH') +def _get_rar_version(xfile): + """Check quickly whether file is rar archive. + """ + with XFile(xfile) as fd: + buf = fd.read(len(RAR5_ID)) + if buf.startswith(RAR_ID): + return 3 + elif buf.startswith(RAR5_ID): + return 5 + return 0 ## ## Public interface ## +def is_rarfile(xfile): + """Check quickly whether file is rar archive. + """ + return _get_rar_version(xfile) > 0 + class Error(Exception): """Base class for rarfile errors.""" + class BadRarFile(Error): """Incorrect data in archive.""" + class NotRarFile(Error): """The file is not RAR archive.""" + class BadRarName(Error): """Cannot guess multipart name components.""" + class NoRarEntry(Error): """File not found in RAR""" + class PasswordRequired(Error): """File requires password""" + class NeedFirstVolume(Error): """Need to start from first volume.""" + class NoCrypto(Error): """Cannot parse encrypted headers - no crypto available.""" + class RarExecError(Error): """Problem reported by unrar/rar.""" + class RarWarning(RarExecError): """Non-fatal error""" + class RarFatalError(RarExecError): """Fatal error""" + class RarCRCError(RarExecError): """CRC error during unpacking""" + class RarLockedArchiveError(RarExecError): """Must not modify locked archive""" + class RarWriteError(RarExecError): """Write error""" + class RarOpenError(RarExecError): """Open error""" + class RarUserError(RarExecError): """User error""" + class RarMemoryError(RarExecError): """Memory error""" + class RarCreateError(RarExecError): """Create error""" + class RarNoFilesError(RarExecError): """No files that match pattern were found""" + class RarUserBreak(RarExecError): """User stop""" + +class RarWrongPassword(RarExecError): + """Incorrect password""" + class RarUnknownError(RarExecError): """Unknown exit code""" + class RarSignalExit(RarExecError): """Unrar exited with signal""" + class RarCannotExec(RarExecError): """Executable not found.""" -def is_rarfile(xfile): - '''Check quickly whether file is rar archive.''' - fd = XFile(xfile) - buf = fd.read(len(RAR_ID)) - fd.close() - return buf == RAR_ID - - class RarInfo(object): - r'''An entry in rar archive. + r"""An entry in rar archive. + + RAR3 extended timestamps are :class:`datetime.datetime` objects without timezone. + RAR5 extended timestamps are :class:`datetime.datetime` objects with UTC timezone. + + Attributes: - :mod:`zipfile`-compatible fields: - filename File name with relative path. - Default path separator is '\\', to change set rarfile.PATH_SEP. - Always unicode string. + Path separator is '/'. Always unicode string. + date_time - Modification time, tuple of (year, month, day, hour, minute, second). - Or datetime() object if USE_DATETIME is set. + File modification timestamp. As tuple of (year, month, day, hour, minute, second). + RAR5 allows archives where it is missing, it's None then. + file_size Uncompressed size. + compress_size Compressed size. + + compress_type + Compression method: one of :data:`RAR_M0` .. :data:`RAR_M5` constants. + + extract_version + Minimal Rar version needed for decompressing. As (major*10 + minor), + so 2.9 is 29. + + RAR3: 10, 20, 29 + + RAR5 does not have such field in archive, it's simply set to 50. + + host_os + Host OS type, one of RAR_OS_* constants. + + RAR3: :data:`RAR_OS_WIN32`, :data:`RAR_OS_UNIX`, :data:`RAR_OS_MSDOS`, + :data:`RAR_OS_OS2`, :data:`RAR_OS_BEOS`. + + RAR5: :data:`RAR_OS_WIN32`, :data:`RAR_OS_UNIX`. + + mode + File attributes. May be either dos-style or unix-style, depending on host_os. + + mtime + File modification time. Same value as :attr:`date_time` + but as :class:`datetime.datetime` object with extended precision. + + ctime + Optional time field: creation time. As :class:`datetime.datetime` object. + + atime + Optional time field: last access time. As :class:`datetime.datetime` object. + + arctime + Optional time field: archival time. As :class:`datetime.datetime` object. + (RAR3-only) + CRC CRC-32 of uncompressed file, unsigned int. + + RAR5: may be None. + + blake2sp_hash + Blake2SP hash over decompressed data. (RAR5-only) + comment - File comment. Byte string or None. Use UNICODE_COMMENTS - to get automatic decoding to unicode. + Optional file comment field. Unicode string. (RAR3-only) + + file_redir + If not None, file is link of some sort. Contains tuple of (type, flags, target). + (RAR5-only) + + Type is one of constants: + + :data:`RAR5_XREDIR_UNIX_SYMLINK` + unix symlink to target. + :data:`RAR5_XREDIR_WINDOWS_SYMLINK` + windows symlink to target. + :data:`RAR5_XREDIR_WINDOWS_JUNCTION` + windows junction. + :data:`RAR5_XREDIR_HARD_LINK` + hard link to target. + :data:`RAR5_XREDIR_FILE_COPY` + current file is copy of another archive entry. + + Flags may contain :data:`RAR5_XREDIR_ISDIR` bit. + volume Volume nr, starting from 0. - RAR-specific fields: - - compress_type - Compression method: 0x30 - 0x35. - extract_version - Minimal Rar version needed for decompressing. - host_os - Host OS type, one of RAR_OS_* constants. - mode - File attributes. May be either dos-style or unix-style, depending on host_os. volume_file Volume file name, where file starts. - mtime - Optional time field: Modification time, with float seconds. - Same as .date_time but with more precision. - ctime - Optional time field: creation time, with float seconds. - atime - Optional time field: last access time, with float seconds. - arctime - Optional time field: archival time, with float seconds. - Internal fields: + """ - type - One of RAR_BLOCK_* types. Only entries with type==RAR_BLOCK_FILE are shown in .infolist(). - flags - For files, RAR_FILE_* bits. - ''' + # zipfile-compatible fields + filename = None + file_size = None + compress_size = None + date_time = None + comment = None + CRC = None + volume = None + orig_filename = None - __slots__ = ( - # zipfile-compatible fields - 'filename', - 'file_size', - 'compress_size', - 'date_time', - 'comment', - 'CRC', - 'volume', - 'orig_filename', # bytes in unknown encoding + # optional extended time fields, datetime() objects. + mtime = None + ctime = None + atime = None - # rar-specific fields - 'extract_version', - 'compress_type', - 'host_os', - 'mode', - 'type', - 'flags', + extract_version = None + mode = None + host_os = None + compress_type = None - # optional extended time fields - # tuple where the sec is float, or datetime(). - 'mtime', # same as .date_time - 'ctime', - 'atime', - 'arctime', + # rar3-only fields + comment = None + arctime = None - # RAR internals - 'name_size', - 'header_size', - 'header_crc', - 'file_offset', - 'add_size', - 'header_data', - 'header_base', - 'header_offset', - 'salt', - 'volume_file', - ) + # rar5-only fields + blake2sp_hash = None + file_redir = None + + # internal fields + flags = 0 + type = None def isdir(self): - '''Returns True if the entry is a directory.''' + """Returns True if entry is a directory. + """ if self.type == RAR_BLOCK_FILE: return (self.flags & RAR_FILE_DIRECTORY) == RAR_FILE_DIRECTORY return False def needs_password(self): - return (self.flags & RAR_FILE_PASSWORD) > 0 + """Returns True if data is stored password-protected. + """ + if self.type == RAR_BLOCK_FILE: + return (self.flags & RAR_FILE_PASSWORD) > 0 + return False class RarFile(object): - '''Parse RAR structure, provide access to files in archive. - ''' + """Parse RAR structure, provide access to files in archive. + """ - #: Archive comment. Byte string or None. Use :data:`UNICODE_COMMENTS` - #: to get automatic decoding to unicode. + #: Archive comment. Unicode string or None. comment = None def __init__(self, rarfile, mode="r", charset=None, info_callback=None, - crc_check = True, errors = "stop"): + crc_check=True, errors="stop"): """Open and parse a RAR archive. - + Parameters: rarfile @@ -463,18 +646,12 @@ class RarFile(object): Either "stop" to quietly stop parsing on errors, or "strict" to raise errors. Default is "stop". """ - self.rarfile = rarfile - self.comment = None + self._rarfile = rarfile self._charset = charset or DEFAULT_CHARSET self._info_callback = info_callback - - self._info_list = [] - self._info_map = {} - self._parse_error = None - self._needs_password = False - self._password = None self._crc_check = crc_check - self._vol_list = [] + self._password = None + self._file_parser = None if errors == "stop": self._strict = False @@ -483,69 +660,62 @@ class RarFile(object): else: raise ValueError("Invalid value for 'errors' parameter.") - self._main = None - if mode != "r": raise NotImplementedError("RarFile supports only mode=r") self._parse() def __enter__(self): + """Open context.""" return self - def __exit__(self, type, value, traceback): + def __exit__(self, typ, value, traceback): + """Exit context""" self.close() def setpassword(self, password): - '''Sets the password to use when extracting.''' + """Sets the password to use when extracting. + """ self._password = password - if not self._main: + if self._file_parser: + if self._file_parser.has_header_encryption(): + self._file_parser = None + if not self._file_parser: self._parse() + else: + self._file_parser.setpassword(self._password) def needs_password(self): - '''Returns True if any archive entries require password for extraction.''' - return self._needs_password + """Returns True if any archive entries require password for extraction. + """ + return self._file_parser.needs_password() def namelist(self): - '''Return list of filenames in archive.''' + """Return list of filenames in archive. + """ return [f.filename for f in self.infolist()] def infolist(self): - '''Return RarInfo objects for all files/directories in archive.''' - return self._info_list + """Return RarInfo objects for all files/directories in archive. + """ + return self._file_parser.infolist() def volumelist(self): - '''Returns filenames of archive volumes. + """Returns filenames of archive volumes. In case of single-volume archive, the list contains just the name of main archive file. - ''' - return self._vol_list + """ + return self._file_parser.volumelist() def getinfo(self, fname): - '''Return RarInfo for file.''' + """Return RarInfo for file. + """ + return self._file_parser.getinfo(fname) - if isinstance(fname, RarInfo): - return fname + def open(self, fname, mode='r', psw=None): + """Returns file-like object (:class:`RarExtFile`) from where the data can be read. - # accept both ways here - if PATH_SEP == '/': - fname2 = fname.replace("\\", "/") - else: - fname2 = fname.replace("/", "\\") - - try: - return self._info_map[fname] - except KeyError: - try: - return self._info_map[fname2] - except KeyError: - raise NoRarEntry("No such file: "+fname) - - def open(self, fname, mode = 'r', psw = None): - '''Returns file-like object (:class:`RarExtFile`), - from where the data can be read. - The object implements :class:`io.RawIOBase` interface, so it can be further wrapped with :class:`io.BufferedReader` and :class:`io.TextIOWrapper`. @@ -565,7 +735,7 @@ class RarFile(object): must be 'r' psw password to use for extracting. - ''' + """ if mode != 'r': raise NotImplementedError("RarFile.open() supports only mode=r") @@ -575,9 +745,6 @@ class RarFile(object): if inf.isdir(): raise TypeError("Directory does not have any data: " + inf.filename) - if inf.flags & RAR_FILE_SPLIT_BEFORE: - raise NeedFirstVolume("Partial file, please start from first volume: " + inf.filename) - # check password if inf.needs_password(): psw = psw or self._password @@ -586,34 +753,11 @@ class RarFile(object): else: psw = None - # is temp write usable? - use_hack = 1 - if not self._main: - use_hack = 0 - elif self._main.flags & (RAR_MAIN_SOLID | RAR_MAIN_PASSWORD): - use_hack = 0 - elif inf.flags & (RAR_FILE_SPLIT_BEFORE | RAR_FILE_SPLIT_AFTER): - use_hack = 0 - elif is_filelike(self.rarfile): - pass - elif inf.file_size > HACK_SIZE_LIMIT: - use_hack = 0 - elif not USE_EXTRACT_HACK: - use_hack = 0 + return self._file_parser.open(inf, psw) - # now extract - if inf.compress_type == RAR_M0 and (inf.flags & RAR_FILE_PASSWORD) == 0: - return self._open_clear(inf) - elif use_hack: - return self._open_hack(inf, psw) - elif is_filelike(self.rarfile): - return self._open_unrar_membuf(self.rarfile, inf, psw) - else: - return self._open_unrar(self.rarfile, inf, psw) - - def read(self, fname, psw = None): + def read(self, fname, psw=None): """Return uncompressed data for archive entry. - + For longer files using :meth:`RarFile.open` may be better idea. Parameters: @@ -624,11 +768,8 @@ class RarFile(object): password to use for extracting. """ - f = self.open(fname, 'r', psw) - try: + with self.open(fname, 'r', psw) as f: return f.read() - finally: - f.close() def close(self): """Release open resources.""" @@ -641,7 +782,7 @@ class RarFile(object): def extract(self, member, path=None, pwd=None): """Extract single file into current directory. - + Parameters: member @@ -659,7 +800,7 @@ class RarFile(object): def extractall(self, path=None, members=None, pwd=None): """Extract all files into current directory. - + Parameters: path @@ -684,77 +825,149 @@ class RarFile(object): cmd = [UNRAR_TOOL] + list(TEST_ARGS) add_password_arg(cmd, self._password) cmd.append('--') - - if is_filelike(self.rarfile): - tmpname = membuf_tempfile(self.rarfile) - cmd.append(tmpname) - else: - tmpname = None - cmd.append(self.rarfile) - - try: + with XTempFile(self._rarfile) as rarfile: + cmd.append(rarfile) p = custom_popen(cmd) output = p.communicate()[0] check_returncode(p, output) - finally: - if tmpname: - os.unlink(tmpname) def strerror(self): - """Return error string if parsing failed, - or None if no problems. + """Return error string if parsing failed or None if no problems. """ - return self._parse_error + if not self._file_parser: + return "Not a RAR file" + return self._file_parser.strerror() ## ## private methods ## - def _set_error(self, msg, *args): - if args: - msg = msg % args - self._parse_error = msg - if self._strict: - raise BadRarFile(msg) + def _parse(self): + ver = _get_rar_version(self._rarfile) + if ver == 3: + p3 = RAR3Parser(self._rarfile, self._password, self._crc_check, + self._charset, self._strict, self._info_callback) + self._file_parser = p3 # noqa + elif ver == 5: + p5 = RAR5Parser(self._rarfile, self._password, self._crc_check, + self._charset, self._strict, self._info_callback) + self._file_parser = p5 # noqa + else: + raise BadRarFile("Not a RAR file") - # store entry - def _process_entry(self, item): - if item.type == RAR_BLOCK_FILE: - # use only first part - if (item.flags & RAR_FILE_SPLIT_BEFORE) == 0: - self._info_map[item.filename] = item - self._info_list.append(item) - # remember if any items require password - if item.needs_password(): - self._needs_password = True - elif len(self._info_list) > 0: - # final crc is in last block - old = self._info_list[-1] - old.CRC = item.CRC - old.compress_size += item.compress_size + self._file_parser.parse() + self.comment = self._file_parser.comment - # parse new-style comment - if item.type == RAR_BLOCK_SUB and item.filename == 'CMT': - if not NEED_COMMENTS: - pass - elif item.flags & (RAR_FILE_SPLIT_BEFORE | RAR_FILE_SPLIT_AFTER): - pass - elif item.flags & RAR_FILE_SOLID: - # file comment - cmt = self._read_comment_v3(item, self._password) - if len(self._info_list) > 0: - old = self._info_list[-1] - old.comment = cmt - else: - # archive comment - cmt = self._read_comment_v3(item, self._password) - self.comment = cmt + # call unrar to extract a file + def _extract(self, fnlist, path=None, psw=None): + cmd = [UNRAR_TOOL] + list(EXTRACT_ARGS) - if self._info_callback: - self._info_callback(item) + # pasoword + psw = psw or self._password + add_password_arg(cmd, psw) + cmd.append('--') + + # rar file + with XTempFile(self._rarfile) as rarfn: + cmd.append(rarfn) + + # file list + for fn in fnlist: + if os.sep != PATH_SEP: + fn = fn.replace(PATH_SEP, os.sep) + cmd.append(fn) + + # destination path + if path is not None: + cmd.append(path + os.sep) + + # call + p = custom_popen(cmd) + output = p.communicate()[0] + check_returncode(p, output) + +# +# File format parsing +# + +class CommonParser(object): + """Shared parser parts.""" + _main = None + _hdrenc_main = None + _needs_password = False + _fd = None + _expect_sig = None + _parse_error = None + _password = None + comment = None + + def __init__(self, rarfile, password, crc_check, charset, strict, info_cb): + self._rarfile = rarfile + self._password = password + self._crc_check = crc_check + self._charset = charset + self._strict = strict + self._info_callback = info_cb + self._info_list = [] + self._info_map = {} + self._vol_list = [] + + def has_header_encryption(self): + """Returns True if headers are encrypted + """ + if self._hdrenc_main: + return True + if self._main: + if self._main.flags & RAR_MAIN_PASSWORD: + return True + return False + + def setpassword(self, psw): + """Set cached password.""" + self._password = psw + + def volumelist(self): + """Volume files""" + return self._vol_list + + def needs_password(self): + """Is password required""" + return self._needs_password + + def strerror(self): + """Last error""" + return self._parse_error + + def infolist(self): + """List of RarInfo records. + """ + return self._info_list + + def getinfo(self, member): + """Return RarInfo for filename + """ + if isinstance(member, RarInfo): + fname = member.filename + else: + fname = member + + # accept both ways here + if PATH_SEP == '/': + fname2 = fname.replace("\\", "/") + else: + fname2 = fname.replace("/", "\\") + + try: + return self._info_map[fname] + except KeyError: + try: + return self._info_map[fname2] + except KeyError: + raise NoRarEntry("No such file: %s" % fname) # read rar - def _parse(self): + def parse(self): + """Process file.""" self._fd = None try: self._parse_real() @@ -764,19 +977,19 @@ class RarFile(object): self._fd = None def _parse_real(self): - fd = XFile(self.rarfile) + fd = XFile(self._rarfile) self._fd = fd - id = fd.read(len(RAR_ID)) - if id != RAR_ID: - if isinstance(self.rarfile, (str, unicode)): - raise NotRarFile("Not a Rar archive: {}".format(self.rarfile)) + sig = fd.read(len(self._expect_sig)) + if sig != self._expect_sig: + if isinstance(self._rarfile, (str, unicode)): + raise NotRarFile("Not a Rar archive: {}".format(self._rarfile)) raise NotRarFile("Not a Rar archive") volume = 0 # first vol (.rar) is 0 - more_vols = 0 - endarc = 0 - volfile = self.rarfile - self._vol_list = [self.rarfile] + more_vols = False + endarc = False + volfile = self._rarfile + self._vol_list = [self._rarfile] while 1: if endarc: h = None # don't read past ENDARC @@ -793,8 +1006,12 @@ class RarFile(object): self._set_error("Cannot open next volume: %s", volfile) break self._fd = fd - more_vols = 0 - endarc = 0 + sig = fd.read(len(self._expect_sig)) + if sig != self._expect_sig: + self._set_error("Invalid volume sig: %s", volfile) + break + more_vols = False + endarc = False self._vol_list.append(volfile) continue break @@ -811,28 +1028,206 @@ class RarFile(object): if h.flags & RAR_MAIN_PASSWORD: self._needs_password = True if not self._password: - self._main = None break elif h.type == RAR_BLOCK_ENDARC: - more_vols = h.flags & RAR_ENDARC_NEXT_VOLUME - endarc = 1 + more_vols = (h.flags & RAR_ENDARC_NEXT_VOLUME) > 0 + endarc = True elif h.type == RAR_BLOCK_FILE: # RAR 2.x does not write RAR_BLOCK_ENDARC if h.flags & RAR_FILE_SPLIT_AFTER: - more_vols = 1 + more_vols = True # RAR 2.x does not set RAR_MAIN_FIRSTVOLUME if volume == 0 and h.flags & RAR_FILE_SPLIT_BEFORE: raise NeedFirstVolume("Need to start from first volume") + if h.needs_password(): + self._needs_password = True + # store it - self._process_entry(h) + self.process_entry(fd, h) + + if self._info_callback: + self._info_callback(h) # go to next header if h.add_size > 0: - fd.seek(h.file_offset + h.add_size, 0) + fd.seek(h.data_offset + h.add_size, 0) + + def process_entry(self, fd, item): + """Examine item, add into lookup cache.""" + raise NotImplementedError() + + def _decrypt_header(self, fd): + raise NotImplementedError('_decrypt_header') + + def _parse_block_header(self, fd): + raise NotImplementedError('_parse_block_header') + + def _open_hack(self, inf, psw): + raise NotImplementedError('_open_hack') + + # read single header + def _parse_header(self, fd): + try: + # handle encrypted headers + if (self._main and self._main.flags & RAR_MAIN_PASSWORD) or self._hdrenc_main: + if not self._password: + return + fd = self._decrypt_header(fd) + + # now read actual header + return self._parse_block_header(fd) + except struct.error: + self._set_error('Broken header in RAR file') + return None + + # given current vol name, construct next one + def _next_volname(self, volfile): + if is_filelike(volfile): + raise IOError("Working on single FD") + if self._main.flags & RAR_MAIN_NEWNUMBERING: + return _next_newvol(volfile) + return _next_oldvol(volfile) + + def _set_error(self, msg, *args): + if args: + msg = msg % args + self._parse_error = msg + if self._strict: + raise BadRarFile(msg) + + def open(self, inf, psw): + """Return stream object for file data.""" + + if inf.file_redir: + # cannot leave to unrar as it expects copied file to exist + if inf.file_redir[0] in (RAR5_XREDIR_FILE_COPY, RAR5_XREDIR_HARD_LINK): + inf = self.getinfo(inf.file_redir[2]) + if not inf: + raise BadRarFile('cannot find copied file') + + if inf.flags & RAR_FILE_SPLIT_BEFORE: + raise NeedFirstVolume("Partial file, please start from first volume: " + inf.filename) + + # is temp write usable? + use_hack = 1 + if not self._main: + use_hack = 0 + elif self._main._must_disable_hack(): + use_hack = 0 + elif inf._must_disable_hack(): + use_hack = 0 + elif is_filelike(self._rarfile): + pass + elif inf.file_size > HACK_SIZE_LIMIT: + use_hack = 0 + elif not USE_EXTRACT_HACK: + use_hack = 0 + + # now extract + if inf.compress_type == RAR_M0 and (inf.flags & RAR_FILE_PASSWORD) == 0 and inf.file_redir is None: + return self._open_clear(inf) + elif use_hack: + return self._open_hack(inf, psw) + elif is_filelike(self._rarfile): + return self._open_unrar_membuf(self._rarfile, inf, psw) + else: + return self._open_unrar(self._rarfile, inf, psw) + + def _open_clear(self, inf): + return DirectReader(self, inf) + + def _open_hack_core(self, inf, psw, prefix, suffix): + + size = inf.compress_size + inf.header_size + rf = XFile(inf.volume_file, 0) + rf.seek(inf.header_offset) + + tmpfd, tmpname = mkstemp(suffix='.rar') + tmpf = os.fdopen(tmpfd, "wb") + + try: + tmpf.write(prefix) + while size > 0: + if size > BSIZE: + buf = rf.read(BSIZE) + else: + buf = rf.read(size) + if not buf: + raise BadRarFile('read failed: ' + inf.filename) + tmpf.write(buf) + size -= len(buf) + tmpf.write(suffix) + tmpf.close() + rf.close() + except: + rf.close() + tmpf.close() + os.unlink(tmpname) + raise + + return self._open_unrar(tmpname, inf, psw, tmpname) + + # write in-memory archive to temp file - needed for solid archives + def _open_unrar_membuf(self, memfile, inf, psw): + tmpname = membuf_tempfile(memfile) + return self._open_unrar(tmpname, inf, psw, tmpname, force_file=True) + + # extract using unrar + def _open_unrar(self, rarfile, inf, psw=None, tmpfile=None, force_file=False): + cmd = [UNRAR_TOOL] + list(OPEN_ARGS) + add_password_arg(cmd, psw) + cmd.append("--") + cmd.append(rarfile) + + # not giving filename avoids encoding related problems + if not tmpfile or force_file: + fn = inf.filename + if PATH_SEP != os.sep: + fn = fn.replace(PATH_SEP, os.sep) + cmd.append(fn) + + # read from unrar pipe + return PipeReader(self, inf, cmd, tmpfile) + +# +# RAR3 format +# + +class Rar3Info(RarInfo): + """RAR3 specific fields.""" + extract_version = 15 + salt = None + add_size = 0 + header_crc = None + header_size = None + header_offset = None + data_offset = None + _md_class = None + _md_expect = None + + # make sure some rar5 fields are always present + file_redir = None + blake2sp_hash = None + + def _must_disable_hack(self): + if self.type == RAR_BLOCK_FILE: + if self.flags & RAR_FILE_PASSWORD: + return True + elif self.flags & (RAR_FILE_SPLIT_BEFORE | RAR_FILE_SPLIT_AFTER): + return True + elif self.type == RAR_BLOCK_MAIN: + if self.flags & (RAR_MAIN_SOLID | RAR_MAIN_PASSWORD): + return True + return False + + +class RAR3Parser(CommonParser): + """Parse RAR3 file format. + """ + _expect_sig = RAR_ID + _last_aes_key = (None, None, None) # (salt, key, iv) - # AES encrypted headers - _last_aes_key = (None, None, None) # (salt, key, iv) def _decrypt_header(self, fd): if not _have_crypto: raise NoCrypto('Cannot parse encrypted headers - no crypto') @@ -844,26 +1239,10 @@ class RarFile(object): self._last_aes_key = (salt, key, iv) return HeaderDecrypt(fd, key, iv) - # read single header - def _parse_header(self, fd): - try: - # handle encrypted headers - if self._main and self._main.flags & RAR_MAIN_PASSWORD: - if not self._password: - return - fd = self._decrypt_header(fd) - - # now read actual header - return self._parse_block_header(fd) - except struct.error: - self._set_error('Broken header in RAR file') - return None - # common header def _parse_block_header(self, fd): - h = RarInfo() + h = Rar3Info() h.header_offset = fd.tell() - h.comment = None # read and parse base header buf = fd.read(S_BLK_HDR.size) @@ -871,24 +1250,24 @@ class RarFile(object): return None t = S_BLK_HDR.unpack_from(buf) h.header_crc, h.type, h.flags, h.header_size = t - h.header_base = S_BLK_HDR.size - pos = S_BLK_HDR.size # read full header if h.header_size > S_BLK_HDR.size: - h.header_data = buf + fd.read(h.header_size - S_BLK_HDR.size) + hdata = buf + fd.read(h.header_size - S_BLK_HDR.size) else: - h.header_data = buf - h.file_offset = fd.tell() + hdata = buf + h.data_offset = fd.tell() # unexpected EOF? - if len(h.header_data) != h.header_size: + if len(hdata) != h.header_size: self._set_error('Unexpected EOF when reading header') return None + pos = S_BLK_HDR.size + # block has data assiciated with it? if h.flags & RAR_LONG_BLOCK: - h.add_size = S_LONG.unpack_from(h.header_data, pos)[0] + h.add_size, pos = load_le32(hdata, pos) else: h.add_size = 0 @@ -896,31 +1275,36 @@ class RarFile(object): if h.type == RAR_BLOCK_MARK: return h elif h.type == RAR_BLOCK_MAIN: - h.header_base += 6 + pos += 6 if h.flags & RAR_MAIN_ENCRYPTVER: - h.header_base += 1 + pos += 1 + crc_pos = pos if h.flags & RAR_MAIN_COMMENT: - self._parse_subblocks(h, h.header_base) - self.comment = h.comment + self._parse_subblocks(h, hdata, pos) elif h.type == RAR_BLOCK_FILE: - self._parse_file_header(h, pos) + pos = self._parse_file_header(h, hdata, pos - 4) + crc_pos = pos + if h.flags & RAR_FILE_COMMENT: + pos = self._parse_subblocks(h, hdata, pos) elif h.type == RAR_BLOCK_SUB: - self._parse_file_header(h, pos) - h.header_base = h.header_size + pos = self._parse_file_header(h, hdata, pos - 4) + crc_pos = h.header_size elif h.type == RAR_BLOCK_OLD_AUTH: - h.header_base += 8 + pos += 8 + crc_pos = pos elif h.type == RAR_BLOCK_OLD_EXTRA: - h.header_base += 7 + pos += 7 + crc_pos = pos else: - h.header_base = h.header_size + crc_pos = h.header_size # check crc if h.type == RAR_BLOCK_OLD_SUB: - crcdat = h.header_data[2:] + fd.read(h.add_size) + crcdat = hdata[2:] + fd.read(h.add_size) else: - crcdat = h.header_data[2:h.header_base] + crcdat = hdata[2:crc_pos] - calc_crc = crc32(crcdat) & 0xFFFF + calc_crc = rar_crc32(crcdat) & 0xFFFF # return good header if h.header_crc == calc_crc: @@ -928,39 +1312,42 @@ class RarFile(object): # header parsing failed. self._set_error('Header CRC error (%02x): exp=%x got=%x (xlen = %d)', - h.type, h.header_crc, calc_crc, len(crcdat)) + h.type, h.header_crc, calc_crc, len(crcdat)) # instead panicing, send eof return None # read file-specific header - def _parse_file_header(self, h, pos): - fld = S_FILE_HDR.unpack_from(h.header_data, pos) + def _parse_file_header(self, h, hdata, pos): + fld = S_FILE_HDR.unpack_from(hdata, pos) + pos += S_FILE_HDR.size + h.compress_size = fld[0] h.file_size = fld[1] h.host_os = fld[2] h.CRC = fld[3] h.date_time = parse_dos_time(fld[4]) + h.mtime = to_datetime(h.date_time) h.extract_version = fld[5] h.compress_type = fld[6] - h.name_size = fld[7] + name_size = fld[7] h.mode = fld[8] - pos += S_FILE_HDR.size + + h._md_class = CRC32Context + h._md_expect = h.CRC if h.flags & RAR_FILE_LARGE: - h1 = S_LONG.unpack_from(h.header_data, pos)[0] - h2 = S_LONG.unpack_from(h.header_data, pos + 4)[0] + h1, pos = load_le32(hdata, pos) + h2, pos = load_le32(hdata, pos) h.compress_size |= h1 << 32 h.file_size |= h2 << 32 - pos += 8 h.add_size = h.compress_size - name = h.header_data[pos : pos + h.name_size ] - pos += h.name_size + name, pos = load_bytes(hdata, name_size, pos) if h.flags & RAR_FILE_UNICODE: nul = name.find(ZERO) h.orig_filename = name[:nul] - u = UnicodeFilename(h.orig_filename, name[nul + 1 : ]) + u = UnicodeFilename(h.orig_filename, name[nul + 1:]) h.filename = u.decode() # if parsing failed fall back to simple name @@ -975,48 +1362,24 @@ class RarFile(object): h.filename = h.filename.replace('\\', PATH_SEP) if h.flags & RAR_FILE_SALT: - h.salt = h.header_data[pos : pos + 8] - pos += 8 + h.salt, pos = load_bytes(hdata, 8, pos) else: h.salt = None # optional extended time stamps if h.flags & RAR_FILE_EXTTIME: - pos = self._parse_ext_time(h, pos) + pos = _parse_ext_time(h, hdata, pos) else: h.mtime = h.atime = h.ctime = h.arctime = None - # base header end - h.header_base = pos - - if h.flags & RAR_FILE_COMMENT: - self._parse_subblocks(h, pos) - - # convert timestamps - if USE_DATETIME: - h.date_time = to_datetime(h.date_time) - h.mtime = to_datetime(h.mtime) - h.atime = to_datetime(h.atime) - h.ctime = to_datetime(h.ctime) - h.arctime = to_datetime(h.arctime) - - # .mtime is .date_time with more precision - if h.mtime: - if USE_DATETIME: - h.date_time = h.mtime - else: - # keep seconds int - h.date_time = h.mtime[:5] + (int(h.mtime[5]),) - return pos # find old-style comment subblock - def _parse_subblocks(self, h, pos): - hdata = h.header_data + def _parse_subblocks(self, h, hdata, pos): while pos < len(hdata): # ordinary block header t = S_BLK_HDR.unpack_from(hdata, pos) - scrc, stype, sflags, slen = t + ___scrc, stype, sflags, slen = t pos_next = pos + slen pos += S_BLK_HDR.size @@ -1029,168 +1392,35 @@ class RarFile(object): declen, ver, meth, crc = S_COMMENT_HDR.unpack_from(hdata, pos) pos += S_COMMENT_HDR.size data = hdata[pos : pos_next] - cmt = rar_decompress(ver, meth, data, declen, sflags, - crc, self._password) + cmt = rar3_decompress(ver, meth, data, declen, sflags, + crc, self._password) if not self._crc_check: h.comment = self._decode_comment(cmt) - elif crc32(cmt) & 0xFFFF == crc: + elif rar_crc32(cmt) & 0xFFFF == crc: h.comment = self._decode_comment(cmt) pos = pos_next - - def _parse_ext_time(self, h, pos): - data = h.header_data - - # flags and rest of data can be missing - flags = 0 - if pos + 2 <= len(data): - flags = S_SHORT.unpack_from(data, pos)[0] - pos += 2 - - h.mtime, pos = self._parse_xtime(flags >> 3*4, data, pos, h.date_time) - h.ctime, pos = self._parse_xtime(flags >> 2*4, data, pos) - h.atime, pos = self._parse_xtime(flags >> 1*4, data, pos) - h.arctime, pos = self._parse_xtime(flags >> 0*4, data, pos) return pos - def _parse_xtime(self, flag, data, pos, dostime = None): - unit = 10000000.0 # 100 ns units - if flag & 8: - if not dostime: - t = S_LONG.unpack_from(data, pos)[0] - dostime = parse_dos_time(t) - pos += 4 - rem = 0 - cnt = flag & 3 - for i in range(cnt): - b = S_BYTE.unpack_from(data, pos)[0] - rem = (b << 16) | (rem >> 8) - pos += 1 - sec = dostime[5] + rem / unit - if flag & 4: - sec += 1 - dostime = dostime[:5] + (sec,) - return dostime, pos - - # given current vol name, construct next one - def _next_volname(self, volfile): - if is_filelike(volfile): - raise IOError("Working on single FD") - if self._main.flags & RAR_MAIN_NEWNUMBERING: - return self._next_newvol(volfile) - return self._next_oldvol(volfile) - - # new-style next volume - def _next_newvol(self, volfile): - i = len(volfile) - 1 - while i >= 0: - if volfile[i] >= '0' and volfile[i] <= '9': - return self._inc_volname(volfile, i) - i -= 1 - raise BadRarName("Cannot construct volume name: "+volfile) - - # old-style next volume - def _next_oldvol(self, volfile): - # rar -> r00 - if volfile[-4:].lower() == '.rar': - return volfile[:-2] + '00' - return self._inc_volname(volfile, len(volfile) - 1) - - # increase digits with carry, otherwise just increment char - def _inc_volname(self, volfile, i): - fn = list(volfile) - while i >= 0: - if fn[i] != '9': - fn[i] = chr(ord(fn[i]) + 1) - break - fn[i] = '0' - i -= 1 - return ''.join(fn) - - def _open_clear(self, inf): - return DirectReader(self, inf) - - # put file compressed data into temporary .rar archive, and run - # unrar on that, thus avoiding unrar going over whole archive - def _open_hack(self, inf, psw = None): - BSIZE = 32*1024 - - size = inf.compress_size + inf.header_size - rf = XFile(inf.volume_file, 0) - rf.seek(inf.header_offset) - - tmpfd, tmpname = mkstemp(suffix='.rar') - tmpf = os.fdopen(tmpfd, "wb") - - try: - # create main header: crc, type, flags, size, res1, res2 - mh = S_BLK_HDR.pack(0x90CF, 0x73, 0, 13) + ZERO * (2+4) - tmpf.write(RAR_ID + mh) - while size > 0: - if size > BSIZE: - buf = rf.read(BSIZE) - else: - buf = rf.read(size) - if not buf: - raise BadRarFile('read failed: ' + inf.filename) - tmpf.write(buf) - size -= len(buf) - tmpf.close() - rf.close() - except: - rf.close() - tmpf.close() - os.unlink(tmpname) - raise - - return self._open_unrar(tmpname, inf, psw, tmpname) - def _read_comment_v3(self, inf, psw=None): # read data - rf = XFile(inf.volume_file) - rf.seek(inf.file_offset) - data = rf.read(inf.compress_size) - rf.close() + with XFile(inf.volume_file) as rf: + rf.seek(inf.data_offset) + data = rf.read(inf.compress_size) # decompress - cmt = rar_decompress(inf.extract_version, inf.compress_type, data, - inf.file_size, inf.flags, inf.CRC, psw, inf.salt) + cmt = rar3_decompress(inf.extract_version, inf.compress_type, data, + inf.file_size, inf.flags, inf.CRC, psw, inf.salt) # check crc if self._crc_check: - crc = crc32(cmt) - if crc < 0: - crc += (1 << 32) + crc = rar_crc32(cmt) if crc != inf.CRC: return None return self._decode_comment(cmt) - # write in-memory archive to temp file - needed for solid archives - def _open_unrar_membuf(self, memfile, inf, psw): - tmpname = membuf_tempfile(memfile) - return self._open_unrar(tmpname, inf, psw, tmpname) - - # extract using unrar - def _open_unrar(self, rarfile, inf, psw = None, tmpfile = None): - if is_filelike(rarfile): - raise ValueError("Cannot use unrar directly on memory buffer") - cmd = [UNRAR_TOOL] + list(OPEN_ARGS) - add_password_arg(cmd, psw) - cmd.append("--") - cmd.append(rarfile) - - # not giving filename avoids encoding related problems - if not tmpfile: - fn = inf.filename - if PATH_SEP != os.sep: - fn = fn.replace(PATH_SEP, os.sep) - cmd.append(fn) - - # read from unrar pipe - return PipeReader(self, inf, cmd, tmpfile) - def _decode(self, val): for c in TRY_ENCODINGS: try: @@ -1200,53 +1430,466 @@ class RarFile(object): return val.decode(self._charset, 'replace') def _decode_comment(self, val): - if UNICODE_COMMENTS: - return self._decode(val) - return val + return self._decode(val) - # call unrar to extract a file - def _extract(self, fnlist, path=None, psw=None): - cmd = [UNRAR_TOOL] + list(EXTRACT_ARGS) + def process_entry(self, fd, item): + if item.type == RAR_BLOCK_FILE: + # use only first part + if (item.flags & RAR_FILE_SPLIT_BEFORE) == 0: + self._info_map[item.filename] = item + self._info_list.append(item) + elif len(self._info_list) > 0: + # final crc is in last block + old = self._info_list[-1] + old.CRC = item.CRC + old._md_expect = item._md_expect + old.compress_size += item.compress_size - # pasoword - psw = psw or self._password - add_password_arg(cmd, psw) - cmd.append('--') + # parse new-style comment + if item.type == RAR_BLOCK_SUB and item.filename == 'CMT': + if item.flags & (RAR_FILE_SPLIT_BEFORE | RAR_FILE_SPLIT_AFTER): + pass + elif item.flags & RAR_FILE_SOLID: + # file comment + cmt = self._read_comment_v3(item, self._password) + if len(self._info_list) > 0: + old = self._info_list[-1] + old.comment = cmt + else: + # archive comment + cmt = self._read_comment_v3(item, self._password) + self.comment = cmt - # rar file - if is_filelike(self.rarfile): - tmpname = membuf_tempfile(self.rarfile) - cmd.append(tmpname) + if item.type == RAR_BLOCK_MAIN: + if item.flags & RAR_MAIN_COMMENT: + self.comment = item.comment + if item.flags & RAR_MAIN_PASSWORD: + self._needs_password = True + + # put file compressed data into temporary .rar archive, and run + # unrar on that, thus avoiding unrar going over whole archive + def _open_hack(self, inf, psw): + # create main header: crc, type, flags, size, res1, res2 + prefix = RAR_ID + S_BLK_HDR.pack(0x90CF, 0x73, 0, 13) + ZERO * (2 + 4) + return self._open_hack_core(inf, psw, prefix, EMPTY) + +# +# RAR5 format +# + +class Rar5Info(RarInfo): + """Shared fields for RAR5 records. + """ + extract_version = 50 + header_crc = None + header_size = None + header_offset = None + data_offset = None + + # type=all + block_type = None + block_flags = None + add_size = 0 + block_extra_size = 0 + + # type=MAIN + volume_number = None + _md_class = None + _md_expect = None + + def _must_disable_hack(self): + return False + + +class Rar5BaseFile(Rar5Info): + """Shared sturct for file & service record. + """ + type = -1 + file_flags = None + file_encryption = (0, 0, 0, EMPTY, EMPTY, EMPTY) + file_compress_flags = None + file_redir = None + file_owner = None + file_version = None + blake2sp_hash = None + + def _must_disable_hack(self): + if self.flags & RAR_FILE_PASSWORD: + return True + if self.block_flags & (RAR5_BLOCK_FLAG_SPLIT_BEFORE | RAR5_BLOCK_FLAG_SPLIT_AFTER): + return True + if self.file_compress_flags & RAR5_COMPR_SOLID: + return True + if self.file_redir: + return True + return False + + +class Rar5FileInfo(Rar5BaseFile): + """RAR5 file record. + """ + type = RAR_BLOCK_FILE + + +class Rar5ServiceInfo(Rar5BaseFile): + """RAR5 service record. + """ + type = RAR_BLOCK_SUB + + +class Rar5MainInfo(Rar5Info): + """RAR5 archive main record. + """ + type = RAR_BLOCK_MAIN + main_flags = None + main_volume_number = None + + def _must_disable_hack(self): + if self.main_flags & RAR5_MAIN_FLAG_SOLID: + return True + return False + + +class Rar5EncryptionInfo(Rar5Info): + """RAR5 archive header encryption record. + """ + type = RAR5_BLOCK_ENCRYPTION + encryption_algo = None + encryption_flags = None + encryption_kdf_count = None + encryption_salt = None + encryption_check_value = None + + def needs_password(self): + return True + + +class Rar5EndArcInfo(Rar5Info): + """RAR5 end of archive record. + """ + type = RAR_BLOCK_ENDARC + endarc_flags = None + + +class RAR5Parser(CommonParser): + """Parse RAR5 format. + """ + _expect_sig = RAR5_ID + _hdrenc_main = None + + # AES encrypted headers + _last_aes256_key = (-1, None, None) # (kdf_count, salt, key) + + def _gen_key(self, kdf_count, salt): + if self._last_aes256_key[:2] == (kdf_count, salt): + return self._last_aes256_key[2] + if kdf_count > 24: + raise BadRarFile('Too large kdf_count') + psw = self._password + if isinstance(psw, unicode): + psw = psw.encode('utf8') + key = pbkdf2_sha256(psw, salt, 1 << kdf_count) + self._last_aes256_key = (kdf_count, salt, key) + return key + + def _decrypt_header(self, fd): + if not _have_crypto: + raise NoCrypto('Cannot parse encrypted headers - no crypto') + h = self._hdrenc_main + key = self._gen_key(h.encryption_kdf_count, h.encryption_salt) + iv = fd.read(16) + return HeaderDecrypt(fd, key, iv) + + # common header + def _parse_block_header(self, fd): + header_offset = fd.tell() + + preload = 4 + 3 + start_bytes = fd.read(preload) + header_crc, pos = load_le32(start_bytes, 0) + hdrlen, pos = load_vint(start_bytes, pos) + if hdrlen > 2 * 1024 * 1024: + return None + header_size = pos + hdrlen + + # read full header, check for EOF + hdata = start_bytes + fd.read(header_size - len(start_bytes)) + if len(hdata) != header_size: + self._set_error('Unexpected EOF when reading header') + return None + data_offset = fd.tell() + + calc_crc = rar_crc32(memoryview(hdata)[4:]) + if header_crc != calc_crc: + # header parsing failed. + self._set_error('Header CRC error: exp=%x got=%x (xlen = %d)', + header_crc, calc_crc, len(hdata)) + return None + + block_type, pos = load_vint(hdata, pos) + + if block_type == RAR5_BLOCK_MAIN: + h, pos = self._parse_block_common(Rar5MainInfo(), hdata) + h = self._parse_main_block(h, hdata, pos) + elif block_type == RAR5_BLOCK_FILE: + h, pos = self._parse_block_common(Rar5FileInfo(), hdata) + h = self._parse_file_block(h, hdata, pos) + elif block_type == RAR5_BLOCK_SERVICE: + h, pos = self._parse_block_common(Rar5ServiceInfo(), hdata) + h = self._parse_file_block(h, hdata, pos) + elif block_type == RAR5_BLOCK_ENCRYPTION: + h, pos = self._parse_block_common(Rar5EncryptionInfo(), hdata) + h = self._parse_encryption_block(h, hdata, pos) + elif block_type == RAR5_BLOCK_ENDARC: + h, pos = self._parse_block_common(Rar5EndArcInfo(), hdata) + h = self._parse_endarc_block(h, hdata, pos) else: - tmpname = None - cmd.append(self.rarfile) + h = None + if h: + h.header_offset = header_offset + h.data_offset = data_offset + return h - # file list - for fn in fnlist: - if os.sep != PATH_SEP: - fn = fn.replace(PATH_SEP, os.sep) - cmd.append(fn) + def _parse_block_common(self, h, hdata): + h.header_crc, pos = load_le32(hdata, 0) + hdrlen, pos = load_vint(hdata, pos) + h.header_size = hdrlen + pos + h.block_type, pos = load_vint(hdata, pos) + h.block_flags, pos = load_vint(hdata, pos) - # destination path - if path is not None: - cmd.append(path + os.sep) + if h.block_flags & RAR5_BLOCK_FLAG_EXTRA_DATA: + h.block_extra_size, pos = load_vint(hdata, pos) + if h.block_flags & RAR5_BLOCK_FLAG_DATA_AREA: + h.add_size, pos = load_vint(hdata, pos) - # call - try: - p = custom_popen(cmd) - output = p.communicate()[0] - check_returncode(p, output) - finally: - if tmpname: - os.unlink(tmpname) + h.compress_size = h.add_size + + if h.block_flags & RAR5_BLOCK_FLAG_SKIP_IF_UNKNOWN: + h.flags |= RAR_SKIP_IF_UNKNOWN + if h.block_flags & RAR5_BLOCK_FLAG_DATA_AREA: + h.flags |= RAR_LONG_BLOCK + return h, pos + + def _parse_main_block(self, h, hdata, pos): + h.main_flags, pos = load_vint(hdata, pos) + if h.main_flags & RAR5_MAIN_FLAG_HAS_VOLNR: + h.main_volume_number = load_vint(hdata, pos) + + h.flags |= RAR_MAIN_NEWNUMBERING + if h.main_flags & RAR5_MAIN_FLAG_SOLID: + h.flags |= RAR_MAIN_SOLID + if h.main_flags & RAR5_MAIN_FLAG_ISVOL: + h.flags |= RAR_MAIN_VOLUME + if h.main_flags & RAR5_MAIN_FLAG_RECOVERY: + h.flags |= RAR_MAIN_RECOVERY + if self._hdrenc_main: + h.flags |= RAR_MAIN_PASSWORD + if h.main_flags & RAR5_MAIN_FLAG_HAS_VOLNR == 0: + h.flags |= RAR_MAIN_FIRSTVOLUME + + return h + + def _parse_file_block(self, h, hdata, pos): + h.file_flags, pos = load_vint(hdata, pos) + h.file_size, pos = load_vint(hdata, pos) + h.mode, pos = load_vint(hdata, pos) + + if h.file_flags & RAR5_FILE_FLAG_HAS_MTIME: + h.mtime, pos = load_unixtime(hdata, pos) + h.date_time = h.mtime.timetuple()[:6] + if h.file_flags & RAR5_FILE_FLAG_HAS_CRC32: + h.CRC, pos = load_le32(hdata, pos) + h._md_class = CRC32Context + h._md_expect = h.CRC + + h.file_compress_flags, pos = load_vint(hdata, pos) + h.file_host_os, pos = load_vint(hdata, pos) + h.orig_filename, pos = load_vstr(hdata, pos) + h.filename = h.orig_filename.decode('utf8', 'replace') + + # use compatible values + if h.file_host_os == RAR5_OS_WINDOWS: + h.host_os = RAR_OS_WIN32 + else: + h.host_os = RAR_OS_UNIX + h.compress_type = RAR_M0 + ((h.file_compress_flags >> 7) & 7) + + if h.block_extra_size: + # allow 1 byte of garbage + while pos < len(hdata) - 1: + xsize, pos = load_vint(hdata, pos) + xdata, pos = load_bytes(hdata, xsize, pos) + self._process_file_extra(h, xdata) + + if h.block_flags & RAR5_BLOCK_FLAG_SPLIT_BEFORE: + h.flags |= RAR_FILE_SPLIT_BEFORE + if h.block_flags & RAR5_BLOCK_FLAG_SPLIT_AFTER: + h.flags |= RAR_FILE_SPLIT_AFTER + if h.file_flags & RAR5_FILE_FLAG_ISDIR: + h.flags |= RAR_FILE_DIRECTORY + if h.file_compress_flags & RAR5_COMPR_SOLID: + h.flags |= RAR_FILE_SOLID + + return h + + def _parse_endarc_block(self, h, hdata, pos): + h.endarc_flags, pos = load_vint(hdata, pos) + if h.endarc_flags & RAR5_ENDARC_FLAG_NEXT_VOL: + h.flags |= RAR_ENDARC_NEXT_VOLUME + return h + + def _parse_encryption_block(self, h, hdata, pos): + h.encryption_algo, pos = load_vint(hdata, pos) + h.encryption_flags, pos = load_vint(hdata, pos) + h.encryption_kdf_count, pos = load_byte(hdata, pos) + h.encryption_salt, pos = load_bytes(hdata, 16, pos) + if h.encryption_flags & RAR5_ENC_FLAG_HAS_CHECKVAL: + h.encryption_check_value = load_bytes(hdata, 12, pos) + if h.encryption_algo != RAR5_XENC_CIPHER_AES256: + raise BadRarFile('Unsupported header encryption cipher') + self._hdrenc_main = h + return h + + # file extra record + def _process_file_extra(self, h, xdata): + xtype, pos = load_vint(xdata, 0) + if xtype == RAR5_XFILE_TIME: + self._parse_file_xtime(h, xdata, pos) + elif xtype == RAR5_XFILE_ENCRYPTION: + self._parse_file_encryption(h, xdata, pos) + elif xtype == RAR5_XFILE_HASH: + self._parse_file_hash(h, xdata, pos) + elif xtype == RAR5_XFILE_VERSION: + self._parse_file_version(h, xdata, pos) + elif xtype == RAR5_XFILE_REDIR: + self._parse_file_redir(h, xdata, pos) + elif xtype == RAR5_XFILE_OWNER: + self._parse_file_owner(h, xdata, pos) + elif xtype == RAR5_XFILE_SERVICE: + pass + else: + pass + + # extra block for file time record + def _parse_file_xtime(self, h, xdata, pos): + tflags, pos = load_vint(xdata, pos) + ldr = load_windowstime + if tflags & RAR5_XTIME_UNIXTIME: + ldr = load_unixtime + if tflags & RAR5_XTIME_HAS_MTIME: + h.mtime, pos = ldr(xdata, pos) + h.date_time = h.mtime.timetuple()[:6] + if tflags & RAR5_XTIME_HAS_CTIME: + h.ctime, pos = ldr(xdata, pos) + if tflags & RAR5_XTIME_HAS_ATIME: + h.atime, pos = ldr(xdata, pos) + + # just remember encryption info + def _parse_file_encryption(self, h, xdata, pos): + algo, pos = load_vint(xdata, pos) + flags, pos = load_vint(xdata, pos) + kdf_count, pos = load_byte(xdata, pos) + salt, pos = load_bytes(xdata, 16, pos) + iv, pos = load_bytes(xdata, 16, pos) + checkval = None + if flags & RAR5_XENC_CHECKVAL: + checkval, pos = load_bytes(xdata, 12, pos) + if flags & RAR5_XENC_TWEAKED: + h._md_expect = None + h._md_class = NoHashContext + + h.file_encryption = (algo, flags, kdf_count, salt, iv, checkval) + h.flags |= RAR_FILE_PASSWORD + + def _parse_file_hash(self, h, xdata, pos): + hash_type, pos = load_vint(xdata, pos) + if hash_type == RAR5_XHASH_BLAKE2SP: + h.blake2sp_hash, pos = load_bytes(xdata, 32, pos) + if _have_blake2 and (h.file_encryption[1] & RAR5_XENC_TWEAKED) == 0: + h._md_class = Blake2SP + h._md_expect = h.blake2sp_hash + + def _parse_file_version(self, h, xdata, pos): + flags, pos = load_vint(xdata, pos) + version, pos = load_vint(xdata, pos) + h.file_version = (flags, version) + + def _parse_file_redir(self, h, xdata, pos): + redir_type, pos = load_vint(xdata, pos) + redir_flags, pos = load_vint(xdata, pos) + redir_name, pos = load_vstr(xdata, pos) + redir_name = redir_name.decode('utf8', 'replace') + h.file_redir = (redir_type, redir_flags, redir_name) + + def _parse_file_owner(self, h, xdata, pos): + user_name = group_name = user_id = group_id = None + + flags, pos = load_vint(xdata, pos) + if flags & RAR5_XOWNER_UNAME: + user_name, pos = load_vstr(xdata, pos) + if flags & RAR5_XOWNER_GNAME: + group_name, pos = load_vstr(xdata, pos) + if flags & RAR5_XOWNER_UID: + user_id, pos = load_vint(xdata, pos) + if flags & RAR5_XOWNER_GID: + group_id, pos = load_vint(xdata, pos) + + h.file_owner = (user_name, group_name, user_id, group_id) + + def process_entry(self, fd, item): + if item.block_type == RAR5_BLOCK_FILE: + # use only first part + if (item.block_flags & RAR5_BLOCK_FLAG_SPLIT_BEFORE) == 0: + self._info_map[item.filename] = item + self._info_list.append(item) + elif len(self._info_list) > 0: + # final crc is in last block + old = self._info_list[-1] + old.CRC = item.CRC + old._md_expect = item._md_expect + old.blake2sp_hash = item.blake2sp_hash + old.compress_size += item.compress_size + elif item.block_type == RAR5_BLOCK_SERVICE: + if item.filename == 'CMT': + self._load_comment(fd, item) + + def _load_comment(self, fd, item): + if item.block_flags & (RAR5_BLOCK_FLAG_SPLIT_BEFORE | RAR5_BLOCK_FLAG_SPLIT_AFTER): + return None + if item.compress_type != RAR_M0: + return None + + if item.flags & RAR_FILE_PASSWORD: + algo, ___flags, kdf_count, salt, iv, ___checkval = item.file_encryption + if algo != RAR5_XENC_CIPHER_AES256: + return None + key = self._gen_key(kdf_count, salt) + f = HeaderDecrypt(fd, key, iv) + cmt = f.read(item.file_size) + else: + # archive comment + with self._open_clear(item) as cmtstream: + cmt = cmtstream.read() + + # rar bug? - appends zero to comment + cmt = cmt.split(ZERO, 1)[0] + self.comment = cmt.decode('utf8') + + def _open_hack(self, inf, psw): + # len, type, blk_flags, flags + main_hdr = b'\x03\x01\x00\x00' + endarc_hdr = b'\x03\x05\x00\x00' + main_hdr = S_LONG.pack(rar_crc32(main_hdr)) + main_hdr + endarc_hdr = S_LONG.pack(rar_crc32(endarc_hdr)) + endarc_hdr + return self._open_hack_core(inf, psw, RAR5_ID + main_hdr, endarc_hdr) ## ## Utility classes ## class UnicodeFilename(object): - """Handle unicode filename decompression""" - + """Handle RAR3 unicode filename decompression. + """ def __init__(self, name, encdata): self.std_name = bytearray(name) self.encdata = bytearray(encdata) @@ -1255,6 +1898,7 @@ class UnicodeFilename(object): self.failed = 0 def enc_byte(self): + """Copy encoded byte.""" try: c = self.encdata[self.encpos] self.encpos += 1 @@ -1264,6 +1908,7 @@ class UnicodeFilename(object): return 0 def std_byte(self): + """Copy byte from 8-bit representation.""" try: return self.std_name[self.pos] except IndexError: @@ -1271,11 +1916,13 @@ class UnicodeFilename(object): return ord('?') def put(self, lo, hi): + """Copy 16-bit value to result.""" self.buf.append(lo) self.buf.append(hi) self.pos += 1 def decode(self): + """Decompress compressed UTF16 value.""" hi = self.enc_byte() flagbits = 0 while self.encpos < len(self.encdata): @@ -1294,11 +1941,11 @@ class UnicodeFilename(object): n = self.enc_byte() if n & 0x80: c = self.enc_byte() - for i in range((n & 0x7f) + 2): + for _ in range((n & 0x7f) + 2): lo = (self.std_byte() + c) & 0xFF self.put(lo, hi) else: - for i in range(n + 2): + for _ in range(n + 2): self.put(self.std_byte(), 0) return self.buf.decode("utf-16le", "replace") @@ -1311,77 +1958,78 @@ class RarExtFile(RawIOBase): Behaviour: - no short reads - .read() and .readinfo() read as much as requested. - no internal buffer, use io.BufferedReader for that. - - If :mod:`io` module is available (Python 2.6+, 3.x), then this calls - will inherit from :class:`io.RawIOBase` class. This makes line-based - access available: :meth:`RarExtFile.readline` and ``for ln in f``. """ #: Filename of the archive entry name = None - def __init__(self, rf, inf): + def __init__(self, parser, inf): + """Open archive entry. + """ super(RarExtFile, self).__init__() # standard io.* properties self.name = inf.filename self.mode = 'rb' - self.rf = rf - self.inf = inf - self.crc_check = rf._crc_check - self.fd = None - self.CRC = 0 - self.remain = 0 - self.returncode = 0 + self._parser = parser + self._inf = inf + self._fd = None + self._remain = 0 + self._returncode = 0 + + self._md_context = None self._open() def _open(self): - if self.fd: - self.fd.close() - self.fd = None - self.CRC = 0 - self.remain = self.inf.file_size + if self._fd: + self._fd.close() + md_class = self._inf._md_class or NoHashContext + self._md_context = md_class() + self._fd = None + self._remain = self._inf.file_size - def read(self, cnt = None): + def read(self, cnt=None): """Read all or specified amount of data from archive entry.""" # sanitize cnt if cnt is None or cnt < 0: - cnt = self.remain - elif cnt > self.remain: - cnt = self.remain + cnt = self._remain + elif cnt > self._remain: + cnt = self._remain if cnt == 0: return EMPTY # actual read data = self._read(cnt) if data: - self.CRC = crc32(data, self.CRC) - self.remain -= len(data) + self._md_context.update(data) + self._remain -= len(data) if len(data) != cnt: raise BadRarFile("Failed the read enough data") # done? - if not data or self.remain == 0: - #self.close() + if not data or self._remain == 0: + # self.close() self._check() return data def _check(self): """Check final CRC.""" - if not self.crc_check: + final = self._md_context.digest() + exp = self._inf._md_expect + if exp is None: return - if self.returncode: + if final is None: + return + if self._returncode: check_returncode(self, '') - if self.remain != 0: + if self._remain != 0: raise BadRarFile("Failed the read enough data") - crc = self.CRC - if crc < 0: - crc += (1 << 32) - if crc != self.inf.CRC: - raise BadRarFile("Corrupt file - CRC check failed: " + self.inf.filename) + if final != exp: + raise BadRarFile("Corrupt file - CRC check failed: %s - exp=%r got=%r" % ( + self._inf.filename, exp, final)) def _read(self, cnt): """Actual read that gets sanitized cnt.""" @@ -1391,9 +2039,9 @@ class RarExtFile(RawIOBase): super(RarExtFile, self).close() - if self.fd: - self.fd.close() - self.fd = None + if self._fd: + self._fd.close() + self._fd = None def __del__(self): """Hook delete to make sure tempfile is removed.""" @@ -1404,25 +2052,15 @@ class RarExtFile(RawIOBase): Returns bytes read. """ - - data = self.read(len(buf)) - n = len(data) - try: - buf[:n] = data - except TypeError: - import array - if not isinstance(buf, array.array): - raise - buf[:n] = array.array(buf.typecode, data) - return n + raise NotImplementedError('readinto') def tell(self): """Return current reading position in uncompressed data.""" - return self.inf.file_size - self.remain + return self._inf.file_size - self._remain - def seek(self, ofs, whence = 0): + def seek(self, ofs, whence=0): """Seek in data. - + On uncompressed files, the seeking works by actual seeks so it's fast. On compresses files its slow - forward seeking happends by reading ahead, @@ -1430,9 +2068,9 @@ class RarExtFile(RawIOBase): """ # disable crc check when seeking - self.crc_check = 0 + self._md_context = NoHashContext() - fsize = self.inf.file_size + fsize = self._inf.file_size cur_ofs = self.tell() if whence == 0: # seek from beginning of file @@ -1454,8 +2092,6 @@ class RarExtFile(RawIOBase): if new_ofs >= cur_ofs: self._skip(new_ofs - cur_ofs) else: - # process old data ? - #self._skip(fsize - cur_ofs) # reopen and seek self._open() self._skip(new_ofs) @@ -1478,13 +2114,14 @@ class RarExtFile(RawIOBase): def writable(self): """Returns False. - - Writing is not supported.""" + + Writing is not supported. + """ return False def seekable(self): """Returns True. - + Seeking is supported, although it's slow on compressed files. """ return True @@ -1499,23 +2136,23 @@ class PipeReader(RarExtFile): """Read data from pipe, handle tempfile cleanup.""" def __init__(self, rf, inf, cmd, tempfile=None): - self.cmd = cmd - self.proc = None - self.tempfile = tempfile + self._cmd = cmd + self._proc = None + self._tempfile = tempfile super(PipeReader, self).__init__(rf, inf) def _close_proc(self): - if not self.proc: + if not self._proc: return - if self.proc.stdout: - self.proc.stdout.close() - if self.proc.stdin: - self.proc.stdin.close() - if self.proc.stderr: - self.proc.stderr.close() - self.proc.wait() - self.returncode = self.proc.returncode - self.proc = None + if self._proc.stdout: + self._proc.stdout.close() + if self._proc.stdin: + self._proc.stdin.close() + if self._proc.stderr: + self._proc.stderr.close() + self._proc.wait() + self._returncode = self._proc.returncode + self._proc = None def _open(self): super(PipeReader, self)._open() @@ -1524,19 +2161,19 @@ class PipeReader(RarExtFile): self._close_proc() # launch new process - self.returncode = 0 - self.proc = custom_popen(self.cmd) - self.fd = self.proc.stdout + self._returncode = 0 + self._proc = custom_popen(self._cmd) + self._fd = self._proc.stdout # avoid situation where unrar waits on stdin - if self.proc.stdin: - self.proc.stdin.close() + if self._proc.stdin: + self._proc.stdin.close() def _read(self, cnt): """Read from pipe.""" # normal read is usually enough - data = self.fd.read(cnt) + data = self._fd.read(cnt) if len(data) == cnt or not data: return data @@ -1544,7 +2181,7 @@ class PipeReader(RarExtFile): buf = [data] cnt -= len(data) while cnt > 0: - data = self.fd.read(cnt) + data = self._fd.read(cnt) if not data: break cnt -= len(data) @@ -1557,42 +2194,45 @@ class PipeReader(RarExtFile): self._close_proc() super(PipeReader, self).close() - if self.tempfile: + if self._tempfile: try: - os.unlink(self.tempfile) + os.unlink(self._tempfile) except OSError: pass - self.tempfile = None + self._tempfile = None def readinto(self, buf): """Zero-copy read directly into buffer.""" cnt = len(buf) - if cnt > self.remain: - cnt = self.remain + if cnt > self._remain: + cnt = self._remain vbuf = memoryview(buf) res = got = 0 while got < cnt: - res = self.fd.readinto(vbuf[got : cnt]) + res = self._fd.readinto(vbuf[got : cnt]) if not res: break - if self.crc_check: - self.CRC = crc32(vbuf[got : got + res], self.CRC) - self.remain -= res + self._md_context.update(vbuf[got : got + res]) + self._remain -= res got += res return got class DirectReader(RarExtFile): - """Read uncompressed data directly from archive.""" + """Read uncompressed data directly from archive. + """ + _cur = None + _cur_avail = None + _volfile = None def _open(self): super(DirectReader, self)._open() - self.volfile = self.inf.volume_file - self.fd = XFile(self.volfile, 0) - self.fd.seek(self.inf.header_offset, 0) - self.cur = self.rf._parse_header(self.fd) - self.cur_avail = self.cur.add_size + self._volfile = self._inf.volume_file + self._fd = XFile(self._volfile, 0) + self._fd.seek(self._inf.header_offset, 0) + self._cur = self._parser._parse_header(self._fd) + self._cur_avail = self._cur.add_size def _skip(self, cnt): """RAR Seek, skipping through rar files to get to correct position @@ -1600,19 +2240,19 @@ class DirectReader(RarExtFile): while cnt > 0: # next vol needed? - if self.cur_avail == 0: + if self._cur_avail == 0: if not self._open_next(): break # fd is in read pos, do the read - if cnt > self.cur_avail: - cnt -= self.cur_avail - self.remain -= self.cur_avail - self.cur_avail = 0 + if cnt > self._cur_avail: + cnt -= self._cur_avail + self._remain -= self._cur_avail + self._cur_avail = 0 else: - self.fd.seek(cnt, 1) - self.cur_avail -= cnt - self.remain -= cnt + self._fd.seek(cnt, 1) + self._cur_avail -= cnt + self._remain -= cnt cnt = 0 def _read(self, cnt): @@ -1621,21 +2261,21 @@ class DirectReader(RarExtFile): buf = [] while cnt > 0: # next vol needed? - if self.cur_avail == 0: + if self._cur_avail == 0: if not self._open_next(): break # fd is in read pos, do the read - if cnt > self.cur_avail: - data = self.fd.read(self.cur_avail) + if cnt > self._cur_avail: + data = self._fd.read(self._cur_avail) else: - data = self.fd.read(cnt) + data = self._fd.read(cnt) if not data: break # got some data cnt -= len(data) - self.cur_avail -= len(data) + self._cur_avail -= len(data) buf.append(data) if len(buf) == 1: @@ -1646,31 +2286,34 @@ class DirectReader(RarExtFile): """Proceed to next volume.""" # is the file split over archives? - if (self.cur.flags & RAR_FILE_SPLIT_AFTER) == 0: + if (self._cur.flags & RAR_FILE_SPLIT_AFTER) == 0: return False - if self.fd: - self.fd.close() - self.fd = None + if self._fd: + self._fd.close() + self._fd = None # open next part - self.volfile = self.rf._next_volname(self.volfile) - fd = open(self.volfile, "rb", 0) - self.fd = fd + self._volfile = self._parser._next_volname(self._volfile) + fd = open(self._volfile, "rb", 0) + self._fd = fd + sig = fd.read(len(self._parser._expect_sig)) + if sig != self._parser._expect_sig: + raise BadRarFile("Invalid signature") # loop until first file header while 1: - cur = self.rf._parse_header(fd) + cur = self._parser._parse_header(fd) if not cur: raise BadRarFile("Unexpected EOF") if cur.type in (RAR_BLOCK_MARK, RAR_BLOCK_MAIN): if cur.add_size: fd.seek(cur.add_size, 1) continue - if cur.orig_filename != self.inf.orig_filename: + if cur.orig_filename != self._inf.orig_filename: raise BadRarFile("Did not found file entry") - self.cur = cur - self.cur_avail = cur.add_size + self._cur = cur + self._cur_avail = cur.add_size return True def readinto(self, buf): @@ -1679,23 +2322,22 @@ class DirectReader(RarExtFile): vbuf = memoryview(buf) while got < len(buf): # next vol needed? - if self.cur_avail == 0: + if self._cur_avail == 0: if not self._open_next(): break # length for next read cnt = len(buf) - got - if cnt > self.cur_avail: - cnt = self.cur_avail + if cnt > self._cur_avail: + cnt = self._cur_avail # read into temp view - res = self.fd.readinto(vbuf[got : got + cnt]) + res = self._fd.readinto(vbuf[got : got + cnt]) if not res: break - if self.crc_check: - self.CRC = crc32(vbuf[got : got + res], self.CRC) - self.cur_avail -= res - self.remain -= res + self._md_context.update(vbuf[got : got + res]) + self._cur_avail -= res + self._remain -= res got += res return got @@ -1708,10 +2350,12 @@ class HeaderDecrypt(object): self.buf = EMPTY def tell(self): + """Current file pos - works only on block boundaries.""" return self.f.tell() def read(self, cnt=None): - if cnt > 8*1024: + """Read and decrypt.""" + if cnt > 8 * 1024: raise BadRarFile('Bad count to header decrypt - wrong password?') # consume old data @@ -1724,10 +2368,10 @@ class HeaderDecrypt(object): cnt -= len(res) # decrypt new data - BLK = self.ciph.block_size + blklen = 16 while cnt > 0: - enc = self.f.read(BLK) - if len(enc) < BLK: + enc = self.f.read(blklen) + if len(enc) < blklen: break dec = self.ciph.decrypt(enc) if cnt >= len(dec): @@ -1740,10 +2384,14 @@ class HeaderDecrypt(object): return res + # handle (filename|filelike) object class XFile(object): + """Input may be filename or file object. + """ __slots__ = ('_fd', '_need_close') - def __init__(self, xfile, bufsize = 1024): + + def __init__(self, xfile, bufsize=1024): if is_filelike(xfile): self._need_close = False self._fd = xfile @@ -1751,27 +2399,279 @@ class XFile(object): else: self._need_close = True self._fd = open(xfile, 'rb', bufsize) + def read(self, n=None): + """Read from file.""" return self._fd.read(n) + def tell(self): + """Return file pos.""" return self._fd.tell() + def seek(self, ofs, whence=0): + """Move file pos.""" return self._fd.seek(ofs, whence) + def readinto(self, dst): + """Read into buffer.""" return self._fd.readinto(dst) + def close(self): + """Close file object.""" if self._need_close: self._fd.close() + def __enter__(self): return self + def __exit__(self, typ, val, tb): self.close() + +class NoHashContext(object): + """No-op hash function.""" + def __init__(self, data=None): + """Initialize""" + def update(self, data): + """Update data""" + def digest(self): + """Final hash""" + def hexdigest(self): + """Hexadecimal digest.""" + + +class CRC32Context(object): + """Hash context that uses CRC32.""" + __slots__ = ['_crc'] + + def __init__(self, data=None): + self._crc = 0 + if data: + self.update(data) + + def update(self, data): + """Process data.""" + self._crc = rar_crc32(data, self._crc) + + def digest(self): + """Final hash.""" + return self._crc + + def hexdigest(self): + """Hexadecimal digest.""" + return '%08x' % self.digest() + + +class Blake2SP(object): + """Blake2sp hash context. + """ + __slots__ = ['_thread', '_buf', '_cur', '_digest'] + digest_size = 32 + block_size = 64 + parallelism = 8 + + def __init__(self, data=None): + self._buf = b'' + self._cur = 0 + self._digest = None + self._thread = [] + + for i in range(self.parallelism): + ctx = self._blake2s(i, 0, i == (self.parallelism - 1)) + self._thread.append(ctx) + + if data: + self.update(data) + + def _blake2s(self, ofs, depth, is_last): + return blake2s(node_offset=ofs, node_depth=depth, last_node=is_last, + depth=2, inner_size=32, fanout=self.parallelism) + + def _add_block(self, blk): + self._thread[self._cur].update(blk) + self._cur = (self._cur + 1) % self.parallelism + + def update(self, data): + """Hash data. + """ + view = memoryview(data) + bs = self.block_size + if self._buf: + need = bs - len(self._buf) + if len(view) < need: + self._buf += view.tobytes() + return + self._add_block(self._buf + view[:need].tobytes()) + view = view[need:] + while len(view) >= bs: + self._add_block(view[:bs]) + view = view[bs:] + self._buf = view.tobytes() + + def digest(self): + """Return final digest value. + """ + if self._digest is None: + if self._buf: + self._add_block(self._buf) + self._buf = EMPTY + ctx = self._blake2s(0, 1, True) + for t in self._thread: + ctx.update(t.digest()) + self._digest = ctx.digest() + return self._digest + + def hexdigest(self): + """Hexadecimal digest.""" + return tohex(self.digest()) + ## ## Utility functions ## +S_LONG = Struct('<L') +S_SHORT = Struct('<H') +S_BYTE = Struct('<B') + +S_BLK_HDR = Struct('<HBHH') +S_FILE_HDR = Struct('<LLBLLBBHL') +S_COMMENT_HDR = Struct('<HBBH') + +def load_vint(buf, pos): + """Load variable-size int.""" + limit = min(pos + 11, len(buf)) + res = ofs = 0 + while pos < limit: + b = _byte_code(buf[pos]) + res += ((b & 0x7F) << ofs) + pos += 1 + ofs += 7 + if b < 0x80: + return res, pos + raise BadRarFile('cannot load vint') + +def load_byte(buf, pos): + """Load single byte""" + end = pos + 1 + if end > len(buf): + raise BadRarFile('cannot load byte') + return S_BYTE.unpack_from(buf, pos)[0], end + +def load_le32(buf, pos): + """Load little-endian 32-bit integer""" + end = pos + 4 + if end > len(buf): + raise BadRarFile('cannot load le32') + return S_LONG.unpack_from(buf, pos)[0], pos + 4 + +def load_bytes(buf, num, pos): + """Load sequence of bytes""" + end = pos + num + if end > len(buf): + raise BadRarFile('cannot load bytes') + return buf[pos : end], end + +def load_vstr(buf, pos): + """Load bytes prefixed by vint length""" + slen, pos = load_vint(buf, pos) + return load_bytes(buf, slen, pos) + +def load_dostime(buf, pos): + """Load LE32 dos timestamp""" + stamp, pos = load_le32(buf, pos) + tup = parse_dos_time(stamp) + return to_datetime(tup), pos + +def load_unixtime(buf, pos): + """Load LE32 unix timestamp""" + secs, pos = load_le32(buf, pos) + dt = datetime.fromtimestamp(secs, UTC) + return dt, pos + +def load_windowstime(buf, pos): + """Load LE64 windows timestamp""" + # unix epoch (1970) in seconds from windows epoch (1601) + unix_epoch = 11644473600 + val1, pos = load_le32(buf, pos) + val2, pos = load_le32(buf, pos) + secs, n1secs = divmod((val2 << 32) | val1, 10000000) + dt = datetime.fromtimestamp(secs - unix_epoch, UTC) + dt = dt.replace(microsecond=n1secs // 10) + return dt, pos + +# new-style next volume +def _next_newvol(volfile): + i = len(volfile) - 1 + while i >= 0: + if volfile[i] >= '0' and volfile[i] <= '9': + return _inc_volname(volfile, i) + i -= 1 + raise BadRarName("Cannot construct volume name: " + volfile) + +# old-style next volume +def _next_oldvol(volfile): + # rar -> r00 + if volfile[-4:].lower() == '.rar': + return volfile[:-2] + '00' + return _inc_volname(volfile, len(volfile) - 1) + +# increase digits with carry, otherwise just increment char +def _inc_volname(volfile, i): + fn = list(volfile) + while i >= 0: + if fn[i] != '9': + fn[i] = chr(ord(fn[i]) + 1) + break + fn[i] = '0' + i -= 1 + return ''.join(fn) + +# rar3 extended time fields +def _parse_ext_time(h, data, pos): + # flags and rest of data can be missing + flags = 0 + if pos + 2 <= len(data): + flags = S_SHORT.unpack_from(data, pos)[0] + pos += 2 + + mtime, pos = _parse_xtime(flags >> 3 * 4, data, pos, h.mtime) + h.ctime, pos = _parse_xtime(flags >> 2 * 4, data, pos) + h.atime, pos = _parse_xtime(flags >> 1 * 4, data, pos) + h.arctime, pos = _parse_xtime(flags >> 0 * 4, data, pos) + if mtime: + h.mtime = mtime + h.date_time = mtime.timetuple()[:6] + return pos + +# rar3 one extended time field +def _parse_xtime(flag, data, pos, basetime=None): + res = None + if flag & 8: + if not basetime: + basetime, pos = load_dostime(data, pos) + + # load second fractions + rem = 0 + cnt = flag & 3 + for _ in range(cnt): + b, pos = load_byte(data, pos) + rem = (b << 16) | (rem >> 8) + + # convert 100ns units to microseconds + usec = rem // 10 + if usec > 1000000: + usec = 999999 + + # dostime has room for 30 seconds only, correct if needed + if flag & 4 and basetime.second < 59: + res = basetime.replace(microsecond=usec, second=basetime.second + 1) + else: + res = basetime.replace(microsecond=usec) + return res, pos + def is_filelike(obj): + """Filename or file object? + """ if isinstance(obj, str) or isinstance(obj, unicode): return False res = True @@ -1782,14 +2682,16 @@ def is_filelike(obj): return True def rar3_s2k(psw, salt): - """String-to-key hash for RAR3.""" - + """String-to-key hash for RAR3. + """ + if not isinstance(psw, unicode): + psw = psw.decode('utf8') seed = psw.encode('utf-16le') + salt iv = EMPTY h = sha1() for i in range(16): for j in range(0x4000): - cnt = S_LONG.pack(i*0x4000 + j) + cnt = S_LONG.pack(i * 0x4000 + j) h.update(seed + cnt[:3]) if j == 0: iv += h.digest()[19:20] @@ -1797,12 +2699,11 @@ def rar3_s2k(psw, salt): key_le = pack("<LLLL", *unpack(">LLLL", key_be)) return key_le, iv -def rar_decompress(vers, meth, data, declen=0, flags=0, crc=0, psw=None, salt=None): +def rar3_decompress(vers, meth, data, declen=0, flags=0, crc=0, psw=None, salt=None): """Decompress blob of compressed data. Used for data with non-standard header - eg. comments. """ - # already uncompressed? if meth == RAR_M0 and (flags & RAR_FILE_PASSWORD) == 0: return data @@ -1826,11 +2727,11 @@ def rar_decompress(vers, meth, data, declen=0, flags=0, crc=0, psw=None, salt=No # full header hlen = S_BLK_HDR.size + len(fhdr) hdr = S_BLK_HDR.pack(0, RAR_BLOCK_FILE, flags, hlen) + fhdr - hcrc = crc32(hdr[2:]) & 0xFFFF + hcrc = rar_crc32(hdr[2:]) & 0xFFFF hdr = S_BLK_HDR.pack(hcrc, RAR_BLOCK_FILE, flags, hlen) + fhdr # archive main header - mh = S_BLK_HDR.pack(0x90CF, RAR_BLOCK_MAIN, 0, 13) + ZERO * (2+4) + mh = S_BLK_HDR.pack(0x90CF, RAR_BLOCK_MAIN, 0, 13) + ZERO * (2 + 4) # decompress via temp rar tmpfd, tmpname = mkstemp(suffix='.rar') @@ -1850,62 +2751,66 @@ def rar_decompress(vers, meth, data, declen=0, flags=0, crc=0, psw=None, salt=No os.unlink(tmpname) def to_datetime(t): - """Convert 6-part time tuple into datetime object.""" - + """Convert 6-part time tuple into datetime object. + """ if t is None: return None # extract values - year, mon, day, h, m, xs = t - s = int(xs) - us = int(1000000 * (xs - s)) + year, mon, day, h, m, s = t # assume the values are valid try: - return datetime(year, mon, day, h, m, s, us) + return datetime(year, mon, day, h, m, s) except ValueError: pass # sanitize invalid values - MDAY = (0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) - if mon < 1: mon = 1 - if mon > 12: mon = 12 - if day < 1: day = 1 - if day > MDAY[mon]: day = MDAY[mon] - if h > 23: h = 23 - if m > 59: m = 59 - if s > 59: s = 59 + mday = (0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) + if mon < 1: + mon = 1 + if mon > 12: + mon = 12 + if day < 1: + day = 1 + if day > mday[mon]: + day = mday[mon] + if h > 23: + h = 23 + if m > 59: + m = 59 + if s > 59: + s = 59 if mon == 2 and day == 29: try: - return datetime(year, mon, day, h, m, s, us) + return datetime(year, mon, day, h, m, s) except ValueError: day = 28 - return datetime(year, mon, day, h, m, s, us) + return datetime(year, mon, day, h, m, s) def parse_dos_time(stamp): - """Parse standard 32-bit DOS timestamp.""" - - sec = stamp & 0x1F; stamp = stamp >> 5 - min = stamp & 0x3F; stamp = stamp >> 6 - hr = stamp & 0x1F; stamp = stamp >> 5 - day = stamp & 0x1F; stamp = stamp >> 5 - mon = stamp & 0x0F; stamp = stamp >> 4 + """Parse standard 32-bit DOS timestamp. + """ + sec, stamp = stamp & 0x1F, stamp >> 5 + mn, stamp = stamp & 0x3F, stamp >> 6 + hr, stamp = stamp & 0x1F, stamp >> 5 + day, stamp = stamp & 0x1F, stamp >> 5 + mon, stamp = stamp & 0x0F, stamp >> 4 yr = (stamp & 0x7F) + 1980 - return (yr, mon, day, hr, min, sec * 2) + return (yr, mon, day, hr, mn, sec * 2) def custom_popen(cmd): - """Disconnect cmd from parent fds, read only from stdout.""" - + """Disconnect cmd from parent fds, read only from stdout. + """ # needed for py2exe creationflags = 0 if sys.platform == 'win32': - creationflags = 0x08000000 # CREATE_NO_WINDOW + creationflags = 0x08000000 # CREATE_NO_WINDOW # run command try: - p = Popen(cmd, bufsize = 0, - stdout = PIPE, stdin = PIPE, stderr = STDOUT, - creationflags = creationflags) + p = Popen(cmd, bufsize=0, stdout=PIPE, stdin=PIPE, stderr=STDOUT, + creationflags=creationflags) except OSError as ex: if ex.errno == errno.ENOENT: raise RarCannotExec("Unrar not installed? (rarfile.UNRAR_TOOL=%r)" % UNRAR_TOOL) @@ -1913,15 +2818,17 @@ def custom_popen(cmd): return p def custom_check(cmd, ignore_retcode=False): - """Run command, collect output, raise error if needed.""" + """Run command, collect output, raise error if needed. + """ p = custom_popen(cmd) - out, err = p.communicate() + out, _ = p.communicate() if p.returncode and not ignore_retcode: raise RarExecError("Check-run failed") return out -def add_password_arg(cmd, psw, required=False): - """Append password switch to commandline.""" +def add_password_arg(cmd, psw, ___required=False): + """Append password switch to commandline. + """ if UNRAR_TOOL == ALT_TOOL: return if psw is not None: @@ -1930,17 +2837,17 @@ def add_password_arg(cmd, psw, required=False): cmd.append('-p-') def check_returncode(p, out): - """Raise exception according to unrar exit code""" - + """Raise exception according to unrar exit code. + """ code = p.returncode if code == 0: return - # map return code to exception class + # map return code to exception class, codes from rar.txt errmap = [None, - RarWarning, RarFatalError, RarCRCError, RarLockedArchiveError, - RarWriteError, RarOpenError, RarUserError, RarMemoryError, - RarCreateError, RarNoFilesError] # codes from rar.txt + RarWarning, RarFatalError, RarCRCError, RarLockedArchiveError, # 1..4 + RarWriteError, RarOpenError, RarUserError, RarMemoryError, # 5..8 + RarCreateError, RarNoFilesError, RarWrongPassword] # 9..11 if UNRAR_TOOL == ALT_TOOL: errmap = [None] if code > 0 and code < len(errmap): @@ -1960,43 +2867,85 @@ def check_returncode(p, out): raise exc(msg) +def hmac_sha256(key, data): + """HMAC-SHA256""" + return HMAC(key, data, sha256).digest() + def membuf_tempfile(memfile): + """Write in-memory file object to real file.""" memfile.seek(0, 0) tmpfd, tmpname = mkstemp(suffix='.rar') tmpf = os.fdopen(tmpfd, "wb") try: - BSIZE = 32*1024 while True: buf = memfile.read(BSIZE) if not buf: break tmpf.write(buf) tmpf.close() - return tmpname except: tmpf.close() os.unlink(tmpname) raise + return tmpname + +class XTempFile(object): + """Real file for archive. + """ + __slots__ = ('_tmpfile', '_filename') + + def __init__(self, rarfile): + if is_filelike(rarfile): + self._tmpfile = membuf_tempfile(rarfile) + self._filename = self._tmpfile + else: + self._tmpfile = None + self._filename = rarfile + + def __enter__(self): + return self._filename + + def __exit__(self, exc_type, exc_value, tb): + if self._tmpfile: + try: + os.unlink(self._tmpfile) + except OSError: + pass + self._tmpfile = None # # Check if unrar works # -try: - # does UNRAR_TOOL work? - custom_check([UNRAR_TOOL], True) -except RarCannotExec: - try: - # does ALT_TOOL work? - custom_check([ALT_TOOL] + list(ALT_CHECK_ARGS), True) - # replace config - UNRAR_TOOL = ALT_TOOL - OPEN_ARGS = ALT_OPEN_ARGS - EXTRACT_ARGS = ALT_EXTRACT_ARGS - TEST_ARGS = ALT_TEST_ARGS - except RarCannotExec: - # no usable tool, only uncompressed archives work - pass +ORIG_UNRAR_TOOL = UNRAR_TOOL +ORIG_OPEN_ARGS = OPEN_ARGS +ORIG_EXTRACT_ARGS = EXTRACT_ARGS +ORIG_TEST_ARGS = TEST_ARGS + +def _check_unrar_tool(): + global UNRAR_TOOL, OPEN_ARGS, EXTRACT_ARGS, TEST_ARGS + try: + # does UNRAR_TOOL work? + custom_check([ORIG_UNRAR_TOOL], True) + + UNRAR_TOOL = ORIG_UNRAR_TOOL + OPEN_ARGS = ORIG_OPEN_ARGS + EXTRACT_ARGS = ORIG_EXTRACT_ARGS + TEST_ARGS = ORIG_TEST_ARGS + except RarCannotExec: + try: + # does ALT_TOOL work? + custom_check([ALT_TOOL] + list(ALT_CHECK_ARGS), True) + # replace config + UNRAR_TOOL = ALT_TOOL + OPEN_ARGS = ALT_OPEN_ARGS + EXTRACT_ARGS = ALT_EXTRACT_ARGS + TEST_ARGS = ALT_TEST_ARGS + except RarCannotExec: + # no usable tool, only uncompressed archives work + pass + +_check_unrar_tool() diff --git a/libs/stevedore/__init__.py b/libs/stevedore/__init__.py index 93a56b2e..a471f31d 100755 --- a/libs/stevedore/__init__.py +++ b/libs/stevedore/__init__.py @@ -20,17 +20,5 @@ import logging # the app we're used from does not set up logging. LOG = logging.getLogger('stevedore') -if hasattr(logging, 'NullHandler'): - LOG.addHandler(logging.NullHandler()) -else: - class NullHandler(logging.Handler): - def handle(self, record): - pass +LOG.addHandler(logging.NullHandler()) - def emit(self, record): - pass - - def createLock(self): - self.lock = None - - LOG.addHandler(NullHandler()) diff --git a/libs/stevedore/dispatch.py b/libs/stevedore/dispatch.py index 226d3ae2..a1589673 100755 --- a/libs/stevedore/dispatch.py +++ b/libs/stevedore/dispatch.py @@ -1,6 +1,19 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + import logging from .enabled import EnabledExtensionManager +from .exception import NoMatches LOG = logging.getLogger(__name__) @@ -66,7 +79,7 @@ class DispatchExtensionManager(EnabledExtensionManager): """ if not self.extensions: # FIXME: Use a more specific exception class here. - raise RuntimeError('No %s extensions found' % self.namespace) + raise NoMatches('No %s extensions found' % self.namespace) response = [] for e in self.extensions: if filter_func(e, *args, **kwds): diff --git a/libs/stevedore/driver.py b/libs/stevedore/driver.py index 47273d06..167dc671 100755 --- a/libs/stevedore/driver.py +++ b/libs/stevedore/driver.py @@ -1,3 +1,16 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from .exception import NoMatches, MultipleMatches from .named import NamedExtensionManager @@ -27,12 +40,16 @@ class DriverManager(NamedExtensionManager): :param verify_requirements: Use setuptools to enforce the dependencies of the plugin(s) being loaded. Defaults to False. :type verify_requirements: bool + :type warn_on_missing_entrypoint: bool """ def __init__(self, namespace, name, invoke_on_load=False, invoke_args=(), invoke_kwds={}, on_load_failure_callback=None, - verify_requirements=False): + verify_requirements=False, + warn_on_missing_entrypoint=True): + on_load_failure_callback = on_load_failure_callback \ + or self._default_on_load_failure super(DriverManager, self).__init__( namespace=namespace, names=[name], @@ -41,8 +58,13 @@ class DriverManager(NamedExtensionManager): invoke_kwds=invoke_kwds, on_load_failure_callback=on_load_failure_callback, verify_requirements=verify_requirements, + warn_on_missing_entrypoint=warn_on_missing_entrypoint ) + @staticmethod + def _default_on_load_failure(drivermanager, ep, err): + raise + @classmethod def make_test_instance(cls, extension, namespace='TESTING', propagate_map_exceptions=False, @@ -87,14 +109,14 @@ class DriverManager(NamedExtensionManager): if not self.extensions: name = self._names[0] - raise RuntimeError('No %r driver found, looking for %r' % - (self.namespace, name)) + raise NoMatches('No %r driver found, looking for %r' % + (self.namespace, name)) if len(self.extensions) > 1: discovered_drivers = ','.join(e.entry_point_target for e in self.extensions) - raise RuntimeError('Multiple %r drivers found: %s' % - (self.namespace, discovered_drivers)) + raise MultipleMatches('Multiple %r drivers found: %s' % + (self.namespace, discovered_drivers)) def __call__(self, func, *args, **kwds): """Invokes func() for the single loaded extension. diff --git a/libs/stevedore/enabled.py b/libs/stevedore/enabled.py index 0d228db4..c2e0c03d 100755 --- a/libs/stevedore/enabled.py +++ b/libs/stevedore/enabled.py @@ -1,3 +1,15 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + import logging from .extension import ExtensionManager @@ -16,7 +28,8 @@ class EnabledExtensionManager(ExtensionManager): :param namespace: The namespace for the entry points. :type namespace: str :param check_func: Function to determine which extensions to load. - :type check_func: callable + :type check_func: callable, taking an :class:`Extension` + instance as argument :param invoke_on_load: Boolean controlling whether to invoke the object returned by the entry point after the driver is loaded. :type invoke_on_load: bool diff --git a/libs/stevedore/example/__init__.py b/libs/stevedore/example/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/libs/stevedore/example/base.py b/libs/stevedore/example/base.py new file mode 100644 index 00000000..ec95424e --- /dev/null +++ b/libs/stevedore/example/base.py @@ -0,0 +1,22 @@ +import abc + +import six + + +@six.add_metaclass(abc.ABCMeta) +class FormatterBase(object): + """Base class for example plugin used in the tutorial. + """ + + def __init__(self, max_width=60): + self.max_width = max_width + + @abc.abstractmethod + def format(self, data): + """Format the data and return unicode text. + + :param data: A dictionary with string keys and simple types as + values. + :type data: dict(str:?) + :returns: Iterable producing the formatted text. + """ diff --git a/libs/stevedore/example/load_as_driver.py b/libs/stevedore/example/load_as_driver.py new file mode 100644 index 00000000..d8c47f5f --- /dev/null +++ b/libs/stevedore/example/load_as_driver.py @@ -0,0 +1,37 @@ +from __future__ import print_function + +import argparse + +from stevedore import driver + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument( + 'format', + nargs='?', + default='simple', + help='the output format', + ) + parser.add_argument( + '--width', + default=60, + type=int, + help='maximum output width for text', + ) + parsed_args = parser.parse_args() + + data = { + 'a': 'A', + 'b': 'B', + 'long': 'word ' * 80, + } + + mgr = driver.DriverManager( + namespace='stevedore.example.formatter', + name=parsed_args.format, + invoke_on_load=True, + invoke_args=(parsed_args.width,), + ) + for chunk in mgr.driver.format(data): + print(chunk, end='') diff --git a/libs/stevedore/example/load_as_extension.py b/libs/stevedore/example/load_as_extension.py new file mode 100644 index 00000000..436206a3 --- /dev/null +++ b/libs/stevedore/example/load_as_extension.py @@ -0,0 +1,39 @@ +from __future__ import print_function + +import argparse + +from stevedore import extension + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument( + '--width', + default=60, + type=int, + help='maximum output width for text', + ) + parsed_args = parser.parse_args() + + data = { + 'a': 'A', + 'b': 'B', + 'long': 'word ' * 80, + } + + mgr = extension.ExtensionManager( + namespace='stevedore.example.formatter', + invoke_on_load=True, + invoke_args=(parsed_args.width,), + ) + + def format_data(ext, data): + return (ext.name, ext.obj.format(data)) + + results = mgr.map(format_data, data) + + for name, result in results: + print('Formatter: {0}'.format(name)) + for chunk in result: + print(chunk, end='') + print('') diff --git a/libs/stevedore/example/setup.py b/libs/stevedore/example/setup.py new file mode 100644 index 00000000..702e6d4d --- /dev/null +++ b/libs/stevedore/example/setup.py @@ -0,0 +1,43 @@ +from setuptools import setup, find_packages + +setup( + name='stevedore-examples', + version='1.0', + + description='Demonstration package for stevedore', + + author='Doug Hellmann', + author_email='doug@doughellmann.com', + + url='http://git.openstack.org/cgit/openstack/stevedore', + + classifiers=['Development Status :: 3 - Alpha', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.5', + 'Intended Audience :: Developers', + 'Environment :: Console', + ], + + platforms=['Any'], + + scripts=[], + + provides=['stevedore.examples', + ], + + packages=find_packages(), + include_package_data=True, + + entry_points={ + 'stevedore.example.formatter': [ + 'simple = stevedore.example.simple:Simple', + 'plain = stevedore.example.simple:Simple', + ], + }, + + zip_safe=False, +) diff --git a/libs/stevedore/example/simple.py b/libs/stevedore/example/simple.py new file mode 100644 index 00000000..1cad96af --- /dev/null +++ b/libs/stevedore/example/simple.py @@ -0,0 +1,20 @@ +from stevedore.example import base + + +class Simple(base.FormatterBase): + """A very basic formatter. + """ + + def format(self, data): + """Format the data and return unicode text. + + :param data: A dictionary with string keys and simple types as + values. + :type data: dict(str:?) + """ + for name, value in sorted(data.items()): + line = '{name} = {value}\n'.format( + name=name, + value=value, + ) + yield line diff --git a/libs/stevedore/example2/__init__.py b/libs/stevedore/example2/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/libs/stevedore/example2/fields.py b/libs/stevedore/example2/fields.py new file mode 100644 index 00000000..f5c8e194 --- /dev/null +++ b/libs/stevedore/example2/fields.py @@ -0,0 +1,36 @@ +import textwrap + +from stevedore.example import base + + +class FieldList(base.FormatterBase): + """Format values as a reStructuredText field list. + + For example:: + + : name1 : value + : name2 : value + : name3 : a long value + will be wrapped with + a hanging indent + """ + + def format(self, data): + """Format the data and return unicode text. + + :param data: A dictionary with string keys and simple types as + values. + :type data: dict(str:?) + """ + for name, value in sorted(data.items()): + full_text = ': {name} : {value}'.format( + name=name, + value=value, + ) + wrapped_text = textwrap.fill( + full_text, + initial_indent='', + subsequent_indent=' ', + width=self.max_width, + ) + yield wrapped_text + '\n' diff --git a/libs/stevedore/example2/setup.py b/libs/stevedore/example2/setup.py new file mode 100644 index 00000000..bd23838a --- /dev/null +++ b/libs/stevedore/example2/setup.py @@ -0,0 +1,42 @@ +from setuptools import setup, find_packages + +setup( + name='stevedore-examples2', + version='1.0', + + description='Demonstration package for stevedore', + + author='Doug Hellmann', + author_email='doug@doughellmann.com', + + url='http://git.openstack.org/cgit/openstack/stevedore', + + classifiers=['Development Status :: 3 - Alpha', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.5', + 'Intended Audience :: Developers', + 'Environment :: Console', + ], + + platforms=['Any'], + + scripts=[], + + provides=['stevedore.examples2', + ], + + packages=find_packages(), + include_package_data=True, + + entry_points={ + 'stevedore.example.formatter': [ + 'field = stevedore.example2.fields:FieldList', + ], + }, + + zip_safe=False, +) diff --git a/libs/stevedore/exception.py b/libs/stevedore/exception.py new file mode 100644 index 00000000..aa7f1451 --- /dev/null +++ b/libs/stevedore/exception.py @@ -0,0 +1,23 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +class NoUniqueMatch(RuntimeError): + """There was more than one extension, or none, that matched the query.""" + + +class NoMatches(NoUniqueMatch): + """There were no extensions with the driver name found.""" + + +class MultipleMatches(NoUniqueMatch): + """There were multiple matches for the given name.""" diff --git a/libs/stevedore/extension.py b/libs/stevedore/extension.py index 2dd22c74..f5c22928 100755 --- a/libs/stevedore/extension.py +++ b/libs/stevedore/extension.py @@ -1,10 +1,24 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + """ExtensionManager """ +import operator import pkg_resources import logging +from .exception import NoMatches LOG = logging.getLogger(__name__) @@ -139,20 +153,39 @@ class ExtensionManager(object): def _init_plugins(self, extensions): self.extensions = extensions - self._extensions_by_name = None + self._extensions_by_name_cache = None + + @property + def _extensions_by_name(self): + if self._extensions_by_name_cache is None: + d = {} + for e in self.extensions: + d[e.name] = e + self._extensions_by_name_cache = d + return self._extensions_by_name_cache ENTRY_POINT_CACHE = {} - def _find_entry_points(self, namespace): - if namespace not in self.ENTRY_POINT_CACHE: - eps = list(pkg_resources.iter_entry_points(namespace)) - self.ENTRY_POINT_CACHE[namespace] = eps - return self.ENTRY_POINT_CACHE[namespace] + def list_entry_points(self): + """Return the list of entry points for this namespace. + + The entry points are not actually loaded, their list is just read and + returned. + + """ + if self.namespace not in self.ENTRY_POINT_CACHE: + eps = list(pkg_resources.iter_entry_points(self.namespace)) + self.ENTRY_POINT_CACHE[self.namespace] = eps + return self.ENTRY_POINT_CACHE[self.namespace] + + def entry_points_names(self): + """Return the list of entry points names for this namespace.""" + return list(map(operator.attrgetter("name"), self.list_entry_points())) def _load_plugins(self, invoke_on_load, invoke_args, invoke_kwds, verify_requirements): extensions = [] - for ep in self._find_entry_points(self.namespace): + for ep in self.list_entry_points(): LOG.debug('found extension %r', ep) try: ext = self._load_one_plugin(ep, @@ -166,15 +199,30 @@ class ExtensionManager(object): except (KeyboardInterrupt, AssertionError): raise except Exception as err: - LOG.error('Could not load %r: %s', ep.name, err) - LOG.exception(err) if self._on_load_failure_callback is not None: self._on_load_failure_callback(self, ep, err) + else: + # Log the reason we couldn't import the module, + # usually without a traceback. The most common + # reason is an ImportError due to a missing + # dependency, and the error message should be + # enough to debug that. If debug logging is + # enabled for our logger, provide the full + # traceback. + LOG.error('Could not load %r: %s', ep.name, err, + exc_info=LOG.isEnabledFor(logging.DEBUG)) return extensions def _load_one_plugin(self, ep, invoke_on_load, invoke_args, invoke_kwds, verify_requirements): - plugin = ep.load(require=verify_requirements) + # NOTE(dhellmann): Using require=False is deprecated in + # setuptools 11.3. + if hasattr(ep, 'resolve') and hasattr(ep, 'require'): + if verify_requirements: + ep.require() + plugin = ep.resolve() + else: + plugin = ep.load(require=verify_requirements) if invoke_on_load: obj = plugin(*invoke_args, **invoke_kwds) else: @@ -210,7 +258,7 @@ class ExtensionManager(object): """ if not self.extensions: # FIXME: Use a more specific exception class here. - raise RuntimeError('No %s extensions found' % self.namespace) + raise NoMatches('No %s extensions found' % self.namespace) response = [] for e in self.extensions: self._invoke_one_plugin(response.append, func, e, args, kwds) @@ -252,6 +300,14 @@ class ExtensionManager(object): LOG.error('error calling %r: %s', e.name, err) LOG.exception(err) + def items(self): + """ + Return an iterator of tuples of the form (name, extension). + + This is analogous to the Mapping.items() method. + """ + return self._extensions_by_name.items() + def __iter__(self): """Produce iterator for the manager. @@ -267,9 +323,9 @@ class ExtensionManager(object): produces the :class:`Extension` instance with the specified name. """ - if self._extensions_by_name is None: - d = {} - for e in self.extensions: - d[e.name] = e - self._extensions_by_name = d return self._extensions_by_name[name] + + def __contains__(self, name): + """Return true if name is in list of enabled extensions. + """ + return any(extension.name == name for extension in self.extensions) diff --git a/libs/stevedore/hook.py b/libs/stevedore/hook.py index d2570db0..014b7c3c 100755 --- a/libs/stevedore/hook.py +++ b/libs/stevedore/hook.py @@ -1,3 +1,15 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + from .named import NamedExtensionManager @@ -27,12 +39,25 @@ class HookManager(NamedExtensionManager): :param verify_requirements: Use setuptools to enforce the dependencies of the plugin(s) being loaded. Defaults to False. :type verify_requirements: bool + :type on_missing_entrypoints_callback: function + :param verify_requirements: Use setuptools to enforce the + dependencies of the plugin(s) being loaded. Defaults to False. + :param warn_on_missing_entrypoint: Flag to control whether failing + to load a plugin is reported via a log mess. Only applies if + on_missing_entrypoints_callback is None. + :type warn_on_missing_entrypoint: bool + """ def __init__(self, namespace, name, invoke_on_load=False, invoke_args=(), invoke_kwds={}, on_load_failure_callback=None, - verify_requirements=False): + verify_requirements=False, + on_missing_entrypoints_callback=None, + # NOTE(dhellmann): This default is different from the + # base class because for hooks it is less likely to + # be an error to have no entry points present. + warn_on_missing_entrypoint=False): super(HookManager, self).__init__( namespace, [name], @@ -40,7 +65,9 @@ class HookManager(NamedExtensionManager): invoke_args=invoke_args, invoke_kwds=invoke_kwds, on_load_failure_callback=on_load_failure_callback, + on_missing_entrypoints_callback=on_missing_entrypoints_callback, verify_requirements=verify_requirements, + warn_on_missing_entrypoint=warn_on_missing_entrypoint, ) def _init_attributes(self, namespace, names, name_order=False, diff --git a/libs/stevedore/named.py b/libs/stevedore/named.py index 18fe235b..3b47dfd3 100755 --- a/libs/stevedore/named.py +++ b/libs/stevedore/named.py @@ -1,5 +1,21 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging + from .extension import ExtensionManager +LOG = logging.getLogger(__name__) + class NamedExtensionManager(ExtensionManager): """Loads only the named extensions. @@ -34,9 +50,17 @@ class NamedExtensionManager(ExtensionManager): when this is called (when an entrypoint fails to load) are (manager, entrypoint, exception) :type on_load_failure_callback: function + :param on_missing_entrypoints_callback: Callback function that will be + called when one or more names cannot be found. The provided argument + will be a subset of the 'names' parameter. + :type on_missing_entrypoints_callback: function :param verify_requirements: Use setuptools to enforce the dependencies of the plugin(s) being loaded. Defaults to False. :type verify_requirements: bool + :param warn_on_missing_entrypoint: Flag to control whether failing + to load a plugin is reported via a log mess. Only applies if + on_missing_entrypoints_callback is None. + :type warn_on_missing_entrypoint: bool """ @@ -44,7 +68,9 @@ class NamedExtensionManager(ExtensionManager): invoke_on_load=False, invoke_args=(), invoke_kwds={}, name_order=False, propagate_map_exceptions=False, on_load_failure_callback=None, - verify_requirements=False): + on_missing_entrypoints_callback=None, + verify_requirements=False, + warn_on_missing_entrypoint=True): self._init_attributes( namespace, names, name_order=name_order, propagate_map_exceptions=propagate_map_exceptions, @@ -53,6 +79,13 @@ class NamedExtensionManager(ExtensionManager): invoke_args, invoke_kwds, verify_requirements) + self._missing_names = set(names) - set([e.name for e in extensions]) + if self._missing_names: + if on_missing_entrypoints_callback: + on_missing_entrypoints_callback(self._missing_names) + elif warn_on_missing_entrypoint: + LOG.warning('Could not load %s' % + ', '.join(self._missing_names)) self._init_plugins(extensions) @classmethod @@ -103,13 +136,15 @@ class NamedExtensionManager(ExtensionManager): on_load_failure_callback=on_load_failure_callback) self._names = names + self._missing_names = set() self._name_order = name_order def _init_plugins(self, extensions): super(NamedExtensionManager, self)._init_plugins(extensions) if self._name_order: - self.extensions = [self[n] for n in self._names] + self.extensions = [self[n] for n in self._names + if n not in self._missing_names] def _load_one_plugin(self, ep, invoke_on_load, invoke_args, invoke_kwds, verify_requirements): diff --git a/libs/stevedore/sphinxext.py b/libs/stevedore/sphinxext.py new file mode 100644 index 00000000..8ca88bbb --- /dev/null +++ b/libs/stevedore/sphinxext.py @@ -0,0 +1,115 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from __future__ import unicode_literals + +import inspect + +from docutils import nodes +from docutils.parsers import rst +from docutils.parsers.rst import directives +from docutils.statemachine import ViewList +from sphinx.util import logging +from sphinx.util.nodes import nested_parse_with_titles + +from stevedore import extension + +LOG = logging.getLogger(__name__) + + +def _get_docstring(plugin): + return inspect.getdoc(plugin) or '' + + +def _simple_list(mgr): + for name in sorted(mgr.names()): + ext = mgr[name] + doc = _get_docstring(ext.plugin) or '\n' + summary = doc.splitlines()[0].strip() + yield('* %s -- %s' % (ext.name, summary), + ext.entry_point.module_name) + + +def _detailed_list(mgr, over='', under='-', titlecase=False): + for name in sorted(mgr.names()): + ext = mgr[name] + if over: + yield (over * len(ext.name), ext.entry_point.module_name) + if titlecase: + yield (ext.name.title(), ext.entry_point.module_name) + else: + yield (ext.name, ext.entry_point.module_name) + if under: + yield (under * len(ext.name), ext.entry_point.module_name) + yield ('\n', ext.entry_point.module_name) + doc = _get_docstring(ext.plugin) + if doc: + yield (doc, ext.entry_point.module_name) + else: + yield ('.. warning:: No documentation found in %s' + % ext.entry_point, + ext.entry_point.module_name) + yield ('\n', ext.entry_point.module_name) + + +class ListPluginsDirective(rst.Directive): + """Present a simple list of the plugins in a namespace.""" + + option_spec = { + 'class': directives.class_option, + 'detailed': directives.flag, + 'titlecase': directives.flag, + 'overline-style': directives.single_char_or_unicode, + 'underline-style': directives.single_char_or_unicode, + } + + has_content = True + + def run(self): + namespace = ' '.join(self.content).strip() + LOG.info('documenting plugins from %r' % namespace) + overline_style = self.options.get('overline-style', '') + underline_style = self.options.get('underline-style', '=') + + def report_load_failure(mgr, ep, err): + LOG.warning(u'Failed to load %s: %s' % (ep.module_name, err)) + + mgr = extension.ExtensionManager( + namespace, + on_load_failure_callback=report_load_failure, + ) + + result = ViewList() + + titlecase = 'titlecase' in self.options + + if 'detailed' in self.options: + data = _detailed_list( + mgr, over=overline_style, under=underline_style, + titlecase=titlecase) + else: + data = _simple_list(mgr) + for text, source in data: + for line in text.splitlines(): + result.append(line, source) + + # Parse what we have into a new section. + node = nodes.section() + node.document = self.state.document + nested_parse_with_titles(self.state, result, node) + + return node.children + + +def setup(app): + LOG.info('loading stevedore.sphinxext') + app.add_directive('list-plugins', ListPluginsDirective) diff --git a/libs/stevedore/tests/__init__.py b/libs/stevedore/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/libs/stevedore/tests/extension_unimportable.py b/libs/stevedore/tests/extension_unimportable.py new file mode 100644 index 00000000..e69de29b diff --git a/libs/stevedore/tests/manager.py b/libs/stevedore/tests/manager.py new file mode 100644 index 00000000..8c97a680 --- /dev/null +++ b/libs/stevedore/tests/manager.py @@ -0,0 +1,67 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""TestExtensionManager + +Extension manager used only for testing. +""" + +import warnings + +from stevedore import extension + + +class TestExtensionManager(extension.ExtensionManager): + """ExtensionManager that is explicitly initialized for tests. + + .. deprecated:: 0.13 + + Use the :func:`make_test_instance` class method of the class + being replaced by the test instance instead of using this class + directly. + + :param extensions: Pre-configured Extension instances to use + instead of loading them from entry points. + :type extensions: list of :class:`~stevedore.extension.Extension` + :param namespace: The namespace for the entry points. + :type namespace: str + :param invoke_on_load: Boolean controlling whether to invoke the + object returned by the entry point after the driver is loaded. + :type invoke_on_load: bool + :param invoke_args: Positional arguments to pass when invoking + the object returned by the entry point. Only used if invoke_on_load + is True. + :type invoke_args: tuple + :param invoke_kwds: Named arguments to pass when invoking + the object returned by the entry point. Only used if invoke_on_load + is True. + :type invoke_kwds: dict + + """ + + def __init__(self, extensions, + namespace='test', + invoke_on_load=False, + invoke_args=(), + invoke_kwds={}): + super(TestExtensionManager, self).__init__(namespace, + invoke_on_load, + invoke_args, + invoke_kwds, + ) + self.extensions = extensions + warnings.warn( + 'TestExtesionManager has been replaced by make_test_instance()', + DeprecationWarning) + + def _load_plugins(self, *args, **kwds): + return [] diff --git a/libs/stevedore/tests/test_callback.py b/libs/stevedore/tests/test_callback.py new file mode 100644 index 00000000..c513aa22 --- /dev/null +++ b/libs/stevedore/tests/test_callback.py @@ -0,0 +1,55 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Tests for failure loading callback +""" +from testtools.matchers import GreaterThan +import mock + +from stevedore import extension +from stevedore import named +from stevedore.tests import utils + + +class TestCallback(utils.TestCase): + def test_extension_failure_custom_callback(self): + errors = [] + + def failure_callback(manager, entrypoint, error): + errors.append((manager, entrypoint, error)) + + em = extension.ExtensionManager('stevedore.test.extension', + invoke_on_load=True, + on_load_failure_callback= + failure_callback) + extensions = list(em.extensions) + self.assertTrue(len(extensions), GreaterThan(0)) + self.assertEqual(len(errors), 2) + for manager, entrypoint, error in errors: + self.assertIs(manager, em) + self.assertIsInstance(error, (IOError, ImportError)) + + @mock.patch('stevedore.named.NamedExtensionManager._load_plugins') + def test_missing_entrypoints_callback(self, load_fn): + errors = set() + + def callback(names): + errors.update(names) + + load_fn.return_value = [ + extension.Extension('foo', None, None, None) + ] + named.NamedExtensionManager('stevedore.test.extension', + names=['foo', 'bar'], + invoke_on_load=True, + on_missing_entrypoints_callback=callback) + self.assertEqual(errors, {'bar'}) diff --git a/libs/stevedore/tests/test_dispatch.py b/libs/stevedore/tests/test_dispatch.py new file mode 100644 index 00000000..f1c305ab --- /dev/null +++ b/libs/stevedore/tests/test_dispatch.py @@ -0,0 +1,103 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from stevedore.tests import utils +from stevedore import dispatch + + +def check_dispatch(ep, *args, **kwds): + return ep.name == 't2' + + +class TestDispatch(utils.TestCase): + def check_dispatch(ep, *args, **kwds): + return ep.name == 't2' + + def test_dispatch(self): + + def invoke(ep, *args, **kwds): + return (ep.name, args, kwds) + + em = dispatch.DispatchExtensionManager('stevedore.test.extension', + lambda *args, **kwds: True, + invoke_on_load=True, + invoke_args=('a',), + invoke_kwds={'b': 'B'}, + ) + self.assertEqual(len(em.extensions), 2) + self.assertEqual(set(em.names()), set(['t1', 't2'])) + + results = em.map(check_dispatch, + invoke, + 'first', + named='named value', + ) + expected = [('t2', ('first',), {'named': 'named value'})] + self.assertEqual(results, expected) + + def test_dispatch_map_method(self): + em = dispatch.DispatchExtensionManager('stevedore.test.extension', + lambda *args, **kwds: True, + invoke_on_load=True, + invoke_args=('a',), + invoke_kwds={'b': 'B'}, + ) + + results = em.map_method(check_dispatch, 'get_args_and_data', 'first') + self.assertEqual(results, [(('a',), {'b': 'B'}, 'first')]) + + def test_name_dispatch(self): + + def invoke(ep, *args, **kwds): + return (ep.name, args, kwds) + + em = dispatch.NameDispatchExtensionManager('stevedore.test.extension', + lambda *args, **kwds: True, + invoke_on_load=True, + invoke_args=('a',), + invoke_kwds={'b': 'B'}, + ) + self.assertEqual(len(em.extensions), 2) + self.assertEqual(set(em.names()), set(['t1', 't2'])) + + results = em.map(['t2'], invoke, 'first', named='named value',) + expected = [('t2', ('first',), {'named': 'named value'})] + self.assertEqual(results, expected) + + def test_name_dispatch_ignore_missing(self): + + def invoke(ep, *args, **kwds): + return (ep.name, args, kwds) + + em = dispatch.NameDispatchExtensionManager( + 'stevedore.test.extension', + lambda *args, **kwds: True, + invoke_on_load=True, + invoke_args=('a',), + invoke_kwds={'b': 'B'}, + ) + + results = em.map(['t3', 't1'], invoke, 'first', named='named value',) + expected = [('t1', ('first',), {'named': 'named value'})] + self.assertEqual(results, expected) + + def test_name_dispatch_map_method(self): + em = dispatch.NameDispatchExtensionManager( + 'stevedore.test.extension', + lambda *args, **kwds: True, + invoke_on_load=True, + invoke_args=('a',), + invoke_kwds={'b': 'B'}, + ) + + results = em.map_method(['t3', 't1'], 'get_args_and_data', 'first') + self.assertEqual(results, [(('a',), {'b': 'B'}, 'first')]) diff --git a/libs/stevedore/tests/test_driver.py b/libs/stevedore/tests/test_driver.py new file mode 100644 index 00000000..c568a3a8 --- /dev/null +++ b/libs/stevedore/tests/test_driver.py @@ -0,0 +1,89 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Tests for stevedore.extension +""" + +import pkg_resources + +from stevedore import driver +from stevedore import exception +from stevedore import extension +from stevedore.tests import test_extension +from stevedore.tests import utils + + +class TestCallback(utils.TestCase): + def test_detect_plugins(self): + em = driver.DriverManager('stevedore.test.extension', 't1') + names = sorted(em.names()) + self.assertEqual(names, ['t1']) + + def test_call(self): + def invoke(ext, *args, **kwds): + return (ext.name, args, kwds) + em = driver.DriverManager('stevedore.test.extension', 't1') + result = em(invoke, 'a', b='C') + self.assertEqual(result, ('t1', ('a',), {'b': 'C'})) + + def test_driver_property_not_invoked_on_load(self): + em = driver.DriverManager('stevedore.test.extension', 't1', + invoke_on_load=False) + d = em.driver + self.assertIs(d, test_extension.FauxExtension) + + def test_driver_property_invoked_on_load(self): + em = driver.DriverManager('stevedore.test.extension', 't1', + invoke_on_load=True) + d = em.driver + self.assertIsInstance(d, test_extension.FauxExtension) + + def test_no_drivers(self): + try: + driver.DriverManager('stevedore.test.extension.none', 't1') + except exception.NoMatches as err: + self.assertIn("No 'stevedore.test.extension.none' driver found", + str(err)) + + def test_bad_driver(self): + try: + driver.DriverManager('stevedore.test.extension', 'e2') + except ImportError: + pass + else: + self.assertEqual(False, "No error raised") + + def test_multiple_drivers(self): + # The idea for this test was contributed by clayg: + # https://gist.github.com/clayg/6311348 + extensions = [ + extension.Extension( + 'backend', + pkg_resources.EntryPoint.parse('backend = pkg1:driver'), + 'pkg backend', + None, + ), + extension.Extension( + 'backend', + pkg_resources.EntryPoint.parse('backend = pkg2:driver'), + 'pkg backend', + None, + ), + ] + try: + dm = driver.DriverManager.make_test_instance(extensions[0]) + # Call the initialization code that verifies the extension + dm._init_plugins(extensions) + except exception.MultipleMatches as err: + self.assertIn("Multiple", str(err)) + else: + self.fail('Should have had an error') diff --git a/libs/stevedore/tests/test_enabled.py b/libs/stevedore/tests/test_enabled.py new file mode 100644 index 00000000..32cd1992 --- /dev/null +++ b/libs/stevedore/tests/test_enabled.py @@ -0,0 +1,42 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from stevedore import enabled +from stevedore.tests import utils + + +class TestEnabled(utils.TestCase): + def test_enabled(self): + def check_enabled(ep): + return ep.name == 't2' + em = enabled.EnabledExtensionManager( + 'stevedore.test.extension', + check_enabled, + invoke_on_load=True, + invoke_args=('a',), + invoke_kwds={'b': 'B'}, + ) + self.assertEqual(len(em.extensions), 1) + self.assertEqual(em.names(), ['t2']) + + def test_enabled_after_load(self): + def check_enabled(ext): + return ext.obj and ext.name == 't2' + em = enabled.EnabledExtensionManager( + 'stevedore.test.extension', + check_enabled, + invoke_on_load=True, + invoke_args=('a',), + invoke_kwds={'b': 'B'}, + ) + self.assertEqual(len(em.extensions), 1) + self.assertEqual(em.names(), ['t2']) diff --git a/libs/stevedore/tests/test_example_fields.py b/libs/stevedore/tests/test_example_fields.py new file mode 100644 index 00000000..757917c9 --- /dev/null +++ b/libs/stevedore/tests/test_example_fields.py @@ -0,0 +1,41 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Tests for stevedore.example2.fields +""" + +from stevedore.example2 import fields +from stevedore.tests import utils + + +class TestExampleFields(utils.TestCase): + def test_simple_items(self): + f = fields.FieldList(100) + text = ''.join(f.format({'a': 'A', 'b': 'B'})) + expected = '\n'.join([ + ': a : A', + ': b : B', + '', + ]) + self.assertEqual(text, expected) + + def test_long_item(self): + f = fields.FieldList(25) + text = ''.join(f.format({'name': + 'a value longer than the allowed width'})) + expected = '\n'.join([ + ': name : a value longer', + ' than the allowed', + ' width', + '', + ]) + self.assertEqual(text, expected) diff --git a/libs/stevedore/tests/test_example_simple.py b/libs/stevedore/tests/test_example_simple.py new file mode 100644 index 00000000..382ed899 --- /dev/null +++ b/libs/stevedore/tests/test_example_simple.py @@ -0,0 +1,29 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Tests for stevedore.example.simple +""" + +from stevedore.example import simple +from stevedore.tests import utils + + +class TestExampleSimple(utils.TestCase): + def test_simple_items(self): + f = simple.Simple(100) + text = ''.join(f.format({'a': 'A', 'b': 'B'})) + expected = '\n'.join([ + 'a = A', + 'b = B', + '', + ]) + self.assertEqual(text, expected) diff --git a/libs/stevedore/tests/test_extension.py b/libs/stevedore/tests/test_extension.py new file mode 100644 index 00000000..17f270fc --- /dev/null +++ b/libs/stevedore/tests/test_extension.py @@ -0,0 +1,244 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Tests for stevedore.extension +""" + +import operator + +import mock + +from stevedore import exception +from stevedore import extension +from stevedore.tests import utils + + +ALL_NAMES = ['e1', 't1', 't2'] +WORKING_NAMES = ['t1', 't2'] + + +class FauxExtension(object): + def __init__(self, *args, **kwds): + self.args = args + self.kwds = kwds + + def get_args_and_data(self, data): + return self.args, self.kwds, data + + +class BrokenExtension(object): + def __init__(self, *args, **kwds): + raise IOError("Did not create") + + +class TestCallback(utils.TestCase): + def test_detect_plugins(self): + em = extension.ExtensionManager('stevedore.test.extension') + names = sorted(em.names()) + self.assertEqual(names, ALL_NAMES) + + def test_get_by_name(self): + em = extension.ExtensionManager('stevedore.test.extension') + e = em['t1'] + self.assertEqual(e.name, 't1') + + def test_list_entry_points(self): + em = extension.ExtensionManager('stevedore.test.extension') + n = em.list_entry_points() + self.assertEqual(set(['e1', 'e2', 't1', 't2']), + set(map(operator.attrgetter("name"), n))) + self.assertEqual(4, len(n)) + + def test_list_entry_points_names(self): + em = extension.ExtensionManager('stevedore.test.extension') + names = em.entry_points_names() + self.assertEqual(set(['e1', 'e2', 't1', 't2']), set(names)) + self.assertEqual(4, len(names)) + + def test_contains_by_name(self): + em = extension.ExtensionManager('stevedore.test.extension') + self.assertEqual('t1' in em, True) + + def test_get_by_name_missing(self): + em = extension.ExtensionManager('stevedore.test.extension') + try: + em['t3'] + except KeyError: + pass + else: + assert False, 'Failed to raise KeyError' + + def test_load_multiple_times_entry_points(self): + # We expect to get the same EntryPoint object because we save them + # in the cache. + em1 = extension.ExtensionManager('stevedore.test.extension') + eps1 = [ext.entry_point for ext in em1] + em2 = extension.ExtensionManager('stevedore.test.extension') + eps2 = [ext.entry_point for ext in em2] + self.assertIs(eps1[0], eps2[0]) + + def test_load_multiple_times_plugins(self): + # We expect to get the same plugin object (module or class) + # because the underlying import machinery will cache the values. + em1 = extension.ExtensionManager('stevedore.test.extension') + plugins1 = [ext.plugin for ext in em1] + em2 = extension.ExtensionManager('stevedore.test.extension') + plugins2 = [ext.plugin for ext in em2] + self.assertIs(plugins1[0], plugins2[0]) + + def test_use_cache(self): + # If we insert something into the cache of entry points, + # the manager should not have to call into pkg_resources + # to find the plugins. + cache = extension.ExtensionManager.ENTRY_POINT_CACHE + cache['stevedore.test.faux'] = [] + with mock.patch('pkg_resources.iter_entry_points', + side_effect= + AssertionError('called iter_entry_points')): + em = extension.ExtensionManager('stevedore.test.faux') + names = em.names() + self.assertEqual(names, []) + + def test_iterable(self): + em = extension.ExtensionManager('stevedore.test.extension') + names = sorted(e.name for e in em) + self.assertEqual(names, ALL_NAMES) + + def test_invoke_on_load(self): + em = extension.ExtensionManager('stevedore.test.extension', + invoke_on_load=True, + invoke_args=('a',), + invoke_kwds={'b': 'B'}, + ) + self.assertEqual(len(em.extensions), 2) + for e in em.extensions: + self.assertEqual(e.obj.args, ('a',)) + self.assertEqual(e.obj.kwds, {'b': 'B'}) + + def test_map_return_values(self): + def mapped(ext, *args, **kwds): + return ext.name + + em = extension.ExtensionManager('stevedore.test.extension', + invoke_on_load=True, + ) + results = em.map(mapped) + self.assertEqual(sorted(results), WORKING_NAMES) + + def test_map_arguments(self): + objs = [] + + def mapped(ext, *args, **kwds): + objs.append((ext, args, kwds)) + + em = extension.ExtensionManager('stevedore.test.extension', + invoke_on_load=True, + ) + em.map(mapped, 1, 2, a='A', b='B') + self.assertEqual(len(objs), 2) + names = sorted([o[0].name for o in objs]) + self.assertEqual(names, WORKING_NAMES) + for o in objs: + self.assertEqual(o[1], (1, 2)) + self.assertEqual(o[2], {'a': 'A', 'b': 'B'}) + + def test_map_eats_errors(self): + def mapped(ext, *args, **kwds): + raise RuntimeError('hard coded error') + + em = extension.ExtensionManager('stevedore.test.extension', + invoke_on_load=True, + ) + results = em.map(mapped, 1, 2, a='A', b='B') + self.assertEqual(results, []) + + def test_map_propagate_exceptions(self): + def mapped(ext, *args, **kwds): + raise RuntimeError('hard coded error') + + em = extension.ExtensionManager('stevedore.test.extension', + invoke_on_load=True, + propagate_map_exceptions=True + ) + + try: + em.map(mapped, 1, 2, a='A', b='B') + assert False + except RuntimeError: + pass + + def test_map_errors_when_no_plugins(self): + expected_str = 'No stevedore.test.extension.none extensions found' + + def mapped(ext, *args, **kwds): + pass + + em = extension.ExtensionManager('stevedore.test.extension.none', + invoke_on_load=True, + ) + try: + em.map(mapped, 1, 2, a='A', b='B') + except exception.NoMatches as err: + self.assertEqual(expected_str, str(err)) + + def test_map_method(self): + em = extension.ExtensionManager('stevedore.test.extension', + invoke_on_load=True, + ) + + result = em.map_method('get_args_and_data', 42) + self.assertEqual(set(r[2] for r in result), set([42])) + + def test_items(self): + em = extension.ExtensionManager('stevedore.test.extension') + expected_output = set([(name, em[name]) for name in ALL_NAMES]) + self.assertEqual(expected_output, set(em.items())) + + +class TestLoadRequirementsNewSetuptools(utils.TestCase): + # setuptools 11.3 and later + + def setUp(self): + super(TestLoadRequirementsNewSetuptools, self).setUp() + self.mock_ep = mock.Mock(spec=['require', 'resolve', 'load', 'name']) + self.em = extension.ExtensionManager.make_test_instance([]) + + def test_verify_requirements(self): + self.em._load_one_plugin(self.mock_ep, False, (), {}, + verify_requirements=True) + self.mock_ep.require.assert_called_once_with() + self.mock_ep.resolve.assert_called_once_with() + + def test_no_verify_requirements(self): + self.em._load_one_plugin(self.mock_ep, False, (), {}, + verify_requirements=False) + self.assertEqual(0, self.mock_ep.require.call_count) + self.mock_ep.resolve.assert_called_once_with() + + +class TestLoadRequirementsOldSetuptools(utils.TestCase): + # Before setuptools 11.3 + + def setUp(self): + super(TestLoadRequirementsOldSetuptools, self).setUp() + self.mock_ep = mock.Mock(spec=['load', 'name']) + self.em = extension.ExtensionManager.make_test_instance([]) + + def test_verify_requirements(self): + self.em._load_one_plugin(self.mock_ep, False, (), {}, + verify_requirements=True) + self.mock_ep.load.assert_called_once_with(require=True) + + def test_no_verify_requirements(self): + self.em._load_one_plugin(self.mock_ep, False, (), {}, + verify_requirements=False) + self.mock_ep.load.assert_called_once_with(require=False) diff --git a/libs/stevedore/tests/test_hook.py b/libs/stevedore/tests/test_hook.py new file mode 100644 index 00000000..5741bb9f --- /dev/null +++ b/libs/stevedore/tests/test_hook.py @@ -0,0 +1,55 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from stevedore import hook +from stevedore.tests import utils + + +class TestHook(utils.TestCase): + def test_hook(self): + em = hook.HookManager( + 'stevedore.test.extension', + 't1', + invoke_on_load=True, + invoke_args=('a',), + invoke_kwds={'b': 'B'}, + ) + self.assertEqual(len(em.extensions), 1) + self.assertEqual(em.names(), ['t1']) + + def test_get_by_name(self): + em = hook.HookManager( + 'stevedore.test.extension', + 't1', + invoke_on_load=True, + invoke_args=('a',), + invoke_kwds={'b': 'B'}, + ) + e_list = em['t1'] + self.assertEqual(len(e_list), 1) + e = e_list[0] + self.assertEqual(e.name, 't1') + + def test_get_by_name_missing(self): + em = hook.HookManager( + 'stevedore.test.extension', + 't1', + invoke_on_load=True, + invoke_args=('a',), + invoke_kwds={'b': 'B'}, + ) + try: + em['t2'] + except KeyError: + pass + else: + assert False, 'Failed to raise KeyError' diff --git a/libs/stevedore/tests/test_named.py b/libs/stevedore/tests/test_named.py new file mode 100644 index 00000000..757d0aab --- /dev/null +++ b/libs/stevedore/tests/test_named.py @@ -0,0 +1,93 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from stevedore import named +from stevedore.tests import utils + +import mock + + +class TestNamed(utils.TestCase): + def test_named(self): + em = named.NamedExtensionManager( + 'stevedore.test.extension', + names=['t1'], + invoke_on_load=True, + invoke_args=('a',), + invoke_kwds={'b': 'B'}, + ) + actual = em.names() + self.assertEqual(actual, ['t1']) + + def test_enabled_before_load(self): + # Set up the constructor for the FauxExtension to cause an + # AssertionError so the test fails if the class is instantiated, + # which should only happen if it is loaded before the name of the + # extension is compared against the names that should be loaded by + # the manager. + init_name = 'stevedore.tests.test_extension.FauxExtension.__init__' + with mock.patch(init_name) as m: + m.side_effect = AssertionError + em = named.NamedExtensionManager( + 'stevedore.test.extension', + # Look for an extension that does not exist so the + # __init__ we mocked should never be invoked. + names=['no-such-extension'], + invoke_on_load=True, + invoke_args=('a',), + invoke_kwds={'b': 'B'}, + ) + actual = em.names() + self.assertEqual(actual, []) + + def test_extensions_listed_in_name_order(self): + # Since we don't know the "natural" order of the extensions, run + # the test both ways: if the sorting is broken, one of them will + # fail + em = named.NamedExtensionManager( + 'stevedore.test.extension', + names=['t1', 't2'], + name_order=True + ) + actual = em.names() + self.assertEqual(actual, ['t1', 't2']) + + em = named.NamedExtensionManager( + 'stevedore.test.extension', + names=['t2', 't1'], + name_order=True + ) + actual = em.names() + self.assertEqual(actual, ['t2', 't1']) + + def test_load_fail_ignored_when_sorted(self): + em = named.NamedExtensionManager( + 'stevedore.test.extension', + names=['e1', 't2', 'e2', 't1'], + name_order=True, + invoke_on_load=True, + invoke_args=('a',), + invoke_kwds={'b': 'B'}, + ) + actual = em.names() + self.assertEqual(['t2', 't1'], actual) + + em = named.NamedExtensionManager( + 'stevedore.test.extension', + names=['e1', 't1'], + name_order=False, + invoke_on_load=True, + invoke_args=('a',), + invoke_kwds={'b': 'B'}, + ) + actual = em.names() + self.assertEqual(['t1'], actual) diff --git a/libs/stevedore/tests/test_sphinxext.py b/libs/stevedore/tests/test_sphinxext.py new file mode 100644 index 00000000..60b47944 --- /dev/null +++ b/libs/stevedore/tests/test_sphinxext.py @@ -0,0 +1,120 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +"""Tests for the sphinx extension +""" + +from __future__ import unicode_literals + +from stevedore import extension +from stevedore import sphinxext +from stevedore.tests import utils + +import mock +import pkg_resources + + +def _make_ext(name, docstring): + def inner(): + pass + + inner.__doc__ = docstring + m1 = mock.Mock(spec=pkg_resources.EntryPoint) + m1.module_name = '%s_module' % name + s = mock.Mock(return_value='ENTRY_POINT(%s)' % name) + m1.__str__ = s + return extension.Extension(name, m1, inner, None) + + +class TestSphinxExt(utils.TestCase): + + def setUp(self): + super(TestSphinxExt, self).setUp() + self.exts = [ + _make_ext('test1', 'One-line docstring'), + _make_ext('test2', 'Multi-line docstring\n\nAnother para'), + ] + self.em = extension.ExtensionManager.make_test_instance(self.exts) + + def test_simple_list(self): + results = list(sphinxext._simple_list(self.em)) + self.assertEqual( + [ + ('* test1 -- One-line docstring', 'test1_module'), + ('* test2 -- Multi-line docstring', 'test2_module'), + ], + results, + ) + + def test_simple_list_no_docstring(self): + ext = [_make_ext('nodoc', None)] + em = extension.ExtensionManager.make_test_instance(ext) + results = list(sphinxext._simple_list(em)) + self.assertEqual( + [ + ('* nodoc -- ', 'nodoc_module'), + ], + results, + ) + + def test_detailed_list(self): + results = list(sphinxext._detailed_list(self.em)) + self.assertEqual( + [ + ('test1', 'test1_module'), + ('-----', 'test1_module'), + ('\n', 'test1_module'), + ('One-line docstring', 'test1_module'), + ('\n', 'test1_module'), + ('test2', 'test2_module'), + ('-----', 'test2_module'), + ('\n', 'test2_module'), + ('Multi-line docstring\n\nAnother para', 'test2_module'), + ('\n', 'test2_module'), + ], + results, + ) + + def test_detailed_list_format(self): + results = list(sphinxext._detailed_list(self.em, over='+', under='+')) + self.assertEqual( + [ + ('+++++', 'test1_module'), + ('test1', 'test1_module'), + ('+++++', 'test1_module'), + ('\n', 'test1_module'), + ('One-line docstring', 'test1_module'), + ('\n', 'test1_module'), + ('+++++', 'test2_module'), + ('test2', 'test2_module'), + ('+++++', 'test2_module'), + ('\n', 'test2_module'), + ('Multi-line docstring\n\nAnother para', 'test2_module'), + ('\n', 'test2_module'), + ], + results, + ) + + def test_detailed_list_no_docstring(self): + ext = [_make_ext('nodoc', None)] + em = extension.ExtensionManager.make_test_instance(ext) + results = list(sphinxext._detailed_list(em)) + self.assertEqual( + [ + ('nodoc', 'nodoc_module'), + ('-----', 'nodoc_module'), + ('\n', 'nodoc_module'), + ('.. warning:: No documentation found in ENTRY_POINT(nodoc)', + 'nodoc_module'), + ('\n', 'nodoc_module'), + ], + results, + ) diff --git a/libs/stevedore/tests/test_test_manager.py b/libs/stevedore/tests/test_test_manager.py new file mode 100644 index 00000000..df056cfe --- /dev/null +++ b/libs/stevedore/tests/test_test_manager.py @@ -0,0 +1,216 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from mock import Mock, sentinel +from stevedore import (ExtensionManager, NamedExtensionManager, HookManager, + DriverManager, EnabledExtensionManager) +from stevedore.dispatch import (DispatchExtensionManager, + NameDispatchExtensionManager) +from stevedore.extension import Extension +from stevedore.tests import utils + + +test_extension = Extension('test_extension', None, None, None) +test_extension2 = Extension('another_one', None, None, None) + +mock_entry_point = Mock(module_name='test.extension', attrs=['obj']) +a_driver = Extension('test_driver', mock_entry_point, sentinel.driver_plugin, + sentinel.driver_obj) + + +# base ExtensionManager +class TestTestManager(utils.TestCase): + def test_instance_should_use_supplied_extensions(self): + extensions = [test_extension, test_extension2] + em = ExtensionManager.make_test_instance(extensions) + self.assertEqual(extensions, em.extensions) + + def test_instance_should_have_default_namespace(self): + em = ExtensionManager.make_test_instance([]) + self.assertEqual(em.namespace, 'TESTING') + + def test_instance_should_use_supplied_namespace(self): + namespace = 'testing.1.2.3' + em = ExtensionManager.make_test_instance([], namespace=namespace) + self.assertEqual(namespace, em.namespace) + + def test_extension_name_should_be_listed(self): + em = ExtensionManager.make_test_instance([test_extension]) + self.assertIn(test_extension.name, em.names()) + + def test_iterator_should_yield_extension(self): + em = ExtensionManager.make_test_instance([test_extension]) + self.assertEqual(test_extension, next(iter(em))) + + def test_manager_should_allow_name_access(self): + em = ExtensionManager.make_test_instance([test_extension]) + self.assertEqual(test_extension, em[test_extension.name]) + + def test_manager_should_call(self): + em = ExtensionManager.make_test_instance([test_extension]) + func = Mock() + em.map(func) + func.assert_called_once_with(test_extension) + + def test_manager_should_call_all(self): + em = ExtensionManager.make_test_instance([test_extension2, + test_extension]) + func = Mock() + em.map(func) + func.assert_any_call(test_extension2) + func.assert_any_call(test_extension) + + def test_manager_return_values(self): + def mapped(ext, *args, **kwds): + return ext.name + + em = ExtensionManager.make_test_instance([test_extension2, + test_extension]) + results = em.map(mapped) + self.assertEqual(sorted(results), ['another_one', 'test_extension']) + + def test_manager_should_eat_exceptions(self): + em = ExtensionManager.make_test_instance([test_extension]) + + func = Mock(side_effect=RuntimeError('hard coded error')) + + results = em.map(func, 1, 2, a='A', b='B') + self.assertEqual(results, []) + + def test_manager_should_propagate_exceptions(self): + em = ExtensionManager.make_test_instance([test_extension], + propagate_map_exceptions=True) + self.skipTest('Skipping temporarily') + func = Mock(side_effect=RuntimeError('hard coded error')) + em.map(func, 1, 2, a='A', b='B') + + # NamedExtensionManager + def test_named_manager_should_use_supplied_extensions(self): + extensions = [test_extension, test_extension2] + em = NamedExtensionManager.make_test_instance(extensions) + self.assertEqual(extensions, em.extensions) + + def test_named_manager_should_have_default_namespace(self): + em = NamedExtensionManager.make_test_instance([]) + self.assertEqual(em.namespace, 'TESTING') + + def test_named_manager_should_use_supplied_namespace(self): + namespace = 'testing.1.2.3' + em = NamedExtensionManager.make_test_instance([], namespace=namespace) + self.assertEqual(namespace, em.namespace) + + def test_named_manager_should_populate_names(self): + extensions = [test_extension, test_extension2] + em = NamedExtensionManager.make_test_instance(extensions) + self.assertEqual(em.names(), ['test_extension', 'another_one']) + + # HookManager + def test_hook_manager_should_use_supplied_extensions(self): + extensions = [test_extension, test_extension2] + em = HookManager.make_test_instance(extensions) + self.assertEqual(extensions, em.extensions) + + def test_hook_manager_should_be_first_extension_name(self): + extensions = [test_extension, test_extension2] + em = HookManager.make_test_instance(extensions) + # This will raise KeyError if the names don't match + assert(em[test_extension.name]) + + def test_hook_manager_should_have_default_namespace(self): + em = HookManager.make_test_instance([test_extension]) + self.assertEqual(em.namespace, 'TESTING') + + def test_hook_manager_should_use_supplied_namespace(self): + namespace = 'testing.1.2.3' + em = HookManager.make_test_instance([test_extension], + namespace=namespace) + self.assertEqual(namespace, em.namespace) + + def test_hook_manager_should_return_named_extensions(self): + hook1 = Extension('captain', None, None, None) + hook2 = Extension('captain', None, None, None) + em = HookManager.make_test_instance([hook1, hook2]) + self.assertEqual([hook1, hook2], em['captain']) + + # DriverManager + def test_driver_manager_should_use_supplied_extension(self): + em = DriverManager.make_test_instance(a_driver) + self.assertEqual([a_driver], em.extensions) + + def test_driver_manager_should_have_default_namespace(self): + em = DriverManager.make_test_instance(a_driver) + self.assertEqual(em.namespace, 'TESTING') + + def test_driver_manager_should_use_supplied_namespace(self): + namespace = 'testing.1.2.3' + em = DriverManager.make_test_instance(a_driver, namespace=namespace) + self.assertEqual(namespace, em.namespace) + + def test_instance_should_use_driver_name(self): + em = DriverManager.make_test_instance(a_driver) + self.assertEqual(['test_driver'], em.names()) + + def test_instance_call(self): + def invoke(ext, *args, **kwds): + return ext.name, args, kwds + + em = DriverManager.make_test_instance(a_driver) + result = em(invoke, 'a', b='C') + self.assertEqual(result, ('test_driver', ('a',), {'b': 'C'})) + + def test_instance_driver_property(self): + em = DriverManager.make_test_instance(a_driver) + self.assertEqual(sentinel.driver_obj, em.driver) + + # EnabledExtensionManager + def test_enabled_instance_should_use_supplied_extensions(self): + extensions = [test_extension, test_extension2] + em = EnabledExtensionManager.make_test_instance(extensions) + self.assertEqual(extensions, em.extensions) + + # DispatchExtensionManager + def test_dispatch_instance_should_use_supplied_extensions(self): + extensions = [test_extension, test_extension2] + em = DispatchExtensionManager.make_test_instance(extensions) + self.assertEqual(extensions, em.extensions) + + def test_dispatch_map_should_invoke_filter_for_extensions(self): + em = DispatchExtensionManager.make_test_instance([test_extension, + test_extension2]) + filter_func = Mock(return_value=False) + args = ('A',) + kw = {'big': 'Cheese'} + em.map(filter_func, None, *args, **kw) + filter_func.assert_any_call(test_extension, *args, **kw) + filter_func.assert_any_call(test_extension2, *args, **kw) + + # NameDispatchExtensionManager + def test_name_dispatch_instance_should_use_supplied_extensions(self): + extensions = [test_extension, test_extension2] + em = NameDispatchExtensionManager.make_test_instance(extensions) + + self.assertEqual(extensions, em.extensions) + + def test_name_dispatch_instance_should_build_extension_name_map(self): + extensions = [test_extension, test_extension2] + em = NameDispatchExtensionManager.make_test_instance(extensions) + self.assertEqual(test_extension, em.by_name[test_extension.name]) + self.assertEqual(test_extension2, em.by_name[test_extension2.name]) + + def test_named_dispatch_map_should_invoke_filter_for_extensions(self): + em = NameDispatchExtensionManager.make_test_instance([test_extension, + test_extension2]) + func = Mock() + args = ('A',) + kw = {'BIGGER': 'Cheese'} + em.map(['test_extension'], func, *args, **kw) + func.assert_called_once_with(test_extension, *args, **kw) diff --git a/libs/stevedore/tests/utils.py b/libs/stevedore/tests/utils.py new file mode 100644 index 00000000..f452959c --- /dev/null +++ b/libs/stevedore/tests/utils.py @@ -0,0 +1,17 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import unittest + + +class TestCase(unittest.TestCase): + pass diff --git a/libs/subliminal/subtitles/__init__.py b/libs/subliminal/subtitles/__init__.py new file mode 100644 index 00000000..2bddfe0f --- /dev/null +++ b/libs/subliminal/subtitles/__init__.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- +from datetime import time + + +class Component(object): + """Base class for cue text. + + :param list components: sub-components of this one. + + """ + tag_name = 'Component' + + def __init__(self, components=None): + if components is None: + self.components = [] + elif isinstance(components, list): + self.components = components + else: + self.components = [components] + + def __iter__(self): + return iter(self.components) + + def __len__(self): + return len(self.components) + + def __str__(self): + return ''.join(str(c) for c in self.components) + + def __repr__(self): + return '<{name}>{components}</{name}>'.format(name=self.tag_name, + components=''.join(repr(c) for c in self.components)) + + +class Bold(Component): + """Bold :class:`Component`.""" + tag_name = 'b' + + +class Italic(Component): + """Italic :class:`Component`.""" + tag_name = 'i' + + +class Underline(Component): + """Underline :class:`Component`.""" + tag_name = 'u' + + +class Strikethrough(Component): + """Strikethrough :class:`Component`.""" + tag_name = 's' + + +class Font(Component): + """Font :class:`Component`.""" + tag_name = 'font' + + def __init__(self, color, *args, **kwargs): + super(Font, self).__init__(*args, **kwargs) + self.color = color + + def __repr__(self): + return '<{name} "{color}">{components}</{name}>'.format(name=self.tag_name, color=self.color, + components=''.join(repr(c) for c in self.components)) + + +class Cue(object): + """A single subtitle cue with timings and components. + + :param datetime.time start_time: start time. + :param datetime.time end_time: end time. + :param list components: cue components. + + """ + def __init__(self, start_time, end_time, components): + self.start_time = start_time + self.end_time = end_time + self.components = components + + def __repr__(self): + return '<Cue [{start_time}->{end_time}] "{text}">'.format(start_time=self.start_time, end_time=self.end_time, + text=''.join(repr(c) for c in self.components)) + + +if __name__ == '__main__': + cue = Cue(time(), time(1), [Bold('Hello')]) + print repr(cue) diff --git a/libs/subliminal/subtitles/subrip.py b/libs/subliminal/subtitles/subrip.py new file mode 100644 index 00000000..47e5b477 --- /dev/null +++ b/libs/subliminal/subtitles/subrip.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +import re +from datetime import time + +from subliminal.subtitles import Cue + +index_re = re.compile(r'(?P<index>\d+)') +timing_re = re.compile(r'(?P<hour>\d{2}):(?P<minute>\d{2}):(?P<second>\d{2}),(?P<milliseconds>\d{3})') + + +class SubripReadError(Exception): + pass + + +class SubripReadIndexError(SubripReadError): + pass + + +class SubripReader(object): + INDEX = 1 + TIMINGS = 2 + TEXT = 3 + + def __init__(self): + self.state = self.INDEX + + def read(self, content): + pass + + def read_line(self, line): + if self.state == self.INDEX: + if index_re.match(line): + raise SubripReadIndexError + + +def read_cue(stream): + """Attempt to parse a complete Cue from the stream""" + # skip blank lines + line = '' + while not line: + line = stream.readline() + + # parse index + if not index_re.match(line): + raise SubripReadIndexError + + # parse timings + line = stream.readline() + if '-->' not in line: + raise SubripReadError + timings = line.split('-->') + if not len(timings): + raise SubripReadError + + # parse start time + match = timing_re.match(timings[0].strip()) + if not match: + raise SubripReadError + start_time = time(**match.groupdict()) + + # parse end time + match = timing_re.match(timings[0].strip()) + if not match: + raise SubripReadError + end_time = time(**match.groupdict()) + + + + +class SubripSubtitle(object): + def __init__(self): + self.cues = [] + + +if __name__ == '__main__': + print read_cue('toto') + i = 0 + for x in read_cue('toto'): + print x + if i > 10: + break + i += 1