diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index f0947104..1185e5eb 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -47,7 +47,7 @@ jobs: version: latest - name: Cache Docker Layers - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: /tmp/.buildx-cache key: ${{ runner.os }}-buildx-${{ github.sha }} diff --git a/.github/workflows/publish-installers.yml b/.github/workflows/publish-installers.yml index 45610edf..7b79eea6 100644 --- a/.github/workflows/publish-installers.yml +++ b/.github/workflows/publish-installers.yml @@ -129,7 +129,7 @@ jobs: echo "$EOF" >> $GITHUB_OUTPUT - name: Create Release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 id: create_release env: GITHUB_TOKEN: ${{ secrets.GHACTIONS_TOKEN }} diff --git a/lib/arrow/_version.py b/lib/arrow/_version.py index 10aa336c..67bc602a 100644 --- a/lib/arrow/_version.py +++ b/lib/arrow/_version.py @@ -1 +1 @@ -__version__ = "1.2.3" +__version__ = "1.3.0" diff --git a/lib/arrow/arrow.py b/lib/arrow/arrow.py index 1ede107f..8d329efd 100644 --- a/lib/arrow/arrow.py +++ b/lib/arrow/arrow.py @@ -168,9 +168,9 @@ class Arrow: isinstance(tzinfo, dt_tzinfo) and hasattr(tzinfo, "localize") and hasattr(tzinfo, "zone") - and tzinfo.zone # type: ignore[attr-defined] + and tzinfo.zone ): - tzinfo = parser.TzinfoParser.parse(tzinfo.zone) # type: ignore[attr-defined] + tzinfo = parser.TzinfoParser.parse(tzinfo.zone) elif isinstance(tzinfo, str): tzinfo = parser.TzinfoParser.parse(tzinfo) @@ -495,7 +495,7 @@ class Arrow: yield current values = [getattr(current, f) for f in cls._ATTRS] - current = cls(*values, tzinfo=tzinfo).shift( # type: ignore + current = cls(*values, tzinfo=tzinfo).shift( # type: ignore[misc] **{frame_relative: relative_steps} ) @@ -578,7 +578,7 @@ class Arrow: for _ in range(3 - len(values)): values.append(1) - floor = self.__class__(*values, tzinfo=self.tzinfo) # type: ignore + floor = self.__class__(*values, tzinfo=self.tzinfo) # type: ignore[misc] if frame_absolute == "week": # if week_start is greater than self.isoweekday() go back one week by setting delta = 7 @@ -792,7 +792,6 @@ class Arrow: return self._datetime.isoformat() def __format__(self, formatstr: str) -> str: - if len(formatstr) > 0: return self.format(formatstr) @@ -804,7 +803,6 @@ class Arrow: # attributes and properties def __getattr__(self, name: str) -> int: - if name == "week": return self.isocalendar()[1] @@ -965,7 +963,6 @@ class Arrow: absolute_kwargs = {} for key, value in kwargs.items(): - if key in self._ATTRS: absolute_kwargs[key] = value elif key in ["week", "quarter"]: @@ -1022,7 +1019,6 @@ class Arrow: additional_attrs = ["weeks", "quarters", "weekday"] for key, value in kwargs.items(): - if key in self._ATTRS_PLURAL or key in additional_attrs: relative_kwargs[key] = value else: @@ -1259,11 +1255,10 @@ class Arrow: ) if trunc(abs(delta)) != 1: - granularity += "s" # type: ignore + granularity += "s" # type: ignore[assignment] return locale.describe(granularity, delta, only_distance=only_distance) else: - if not granularity: raise ValueError( "Empty granularity list provided. " @@ -1314,7 +1309,7 @@ class Arrow: def dehumanize(self, input_string: str, locale: str = "en_us") -> "Arrow": """Returns a new :class:`Arrow ` object, that represents - the time difference relative to the attrbiutes of the + the time difference relative to the attributes of the :class:`Arrow ` object. :param timestring: a ``str`` representing a humanized relative time. @@ -1367,7 +1362,6 @@ class Arrow: # Search input string for each time unit within locale for unit, unit_object in locale_obj.timeframes.items(): - # Need to check the type of unit_object to create the correct dictionary if isinstance(unit_object, Mapping): strings_to_search = unit_object @@ -1378,7 +1372,6 @@ class Arrow: # Needs to cycle all through strings as some locales have strings that # could overlap in a regex match, since input validation isn't being performed. for time_delta, time_string in strings_to_search.items(): - # Replace {0} with regex \d representing digits search_string = str(time_string) search_string = search_string.format(r"\d+") @@ -1419,7 +1412,7 @@ class Arrow: # Assert error if string does not modify any units if not any([True for k, v in unit_visited.items() if v]): raise ValueError( - "Input string not valid. Note: Some locales do not support the week granulairty in Arrow. " + "Input string not valid. Note: Some locales do not support the week granularity in Arrow. " "If you are attempting to use the week granularity on an unsupported locale, this could be the cause of this error." ) @@ -1718,7 +1711,6 @@ class Arrow: # math def __add__(self, other: Any) -> "Arrow": - if isinstance(other, (timedelta, relativedelta)): return self.fromdatetime(self._datetime + other, self._datetime.tzinfo) @@ -1736,7 +1728,6 @@ class Arrow: pass # pragma: no cover def __sub__(self, other: Any) -> Union[timedelta, "Arrow"]: - if isinstance(other, (timedelta, relativedelta)): return self.fromdatetime(self._datetime - other, self._datetime.tzinfo) @@ -1749,7 +1740,6 @@ class Arrow: return NotImplemented def __rsub__(self, other: Any) -> timedelta: - if isinstance(other, dt_datetime): return other - self._datetime @@ -1758,42 +1748,36 @@ class Arrow: # comparisons def __eq__(self, other: Any) -> bool: - if not isinstance(other, (Arrow, dt_datetime)): return False return self._datetime == self._get_datetime(other) def __ne__(self, other: Any) -> bool: - if not isinstance(other, (Arrow, dt_datetime)): return True return not self.__eq__(other) def __gt__(self, other: Any) -> bool: - if not isinstance(other, (Arrow, dt_datetime)): return NotImplemented return self._datetime > self._get_datetime(other) def __ge__(self, other: Any) -> bool: - if not isinstance(other, (Arrow, dt_datetime)): return NotImplemented return self._datetime >= self._get_datetime(other) def __lt__(self, other: Any) -> bool: - if not isinstance(other, (Arrow, dt_datetime)): return NotImplemented return self._datetime < self._get_datetime(other) def __le__(self, other: Any) -> bool: - if not isinstance(other, (Arrow, dt_datetime)): return NotImplemented @@ -1865,7 +1849,6 @@ class Arrow: def _get_iteration_params(cls, end: Any, limit: Optional[int]) -> Tuple[Any, int]: """Sets default end and limit values for range method.""" if end is None: - if limit is None: raise ValueError("One of 'end' or 'limit' is required.") diff --git a/lib/arrow/factory.py b/lib/arrow/factory.py index aad4af8b..f35085f1 100644 --- a/lib/arrow/factory.py +++ b/lib/arrow/factory.py @@ -267,11 +267,9 @@ class ArrowFactory: raise TypeError(f"Cannot parse single argument of type {type(arg)!r}.") elif arg_count == 2: - arg_1, arg_2 = args[0], args[1] if isinstance(arg_1, datetime): - # (datetime, tzinfo/str) -> fromdatetime @ tzinfo if isinstance(arg_2, (dt_tzinfo, str)): return self.type.fromdatetime(arg_1, tzinfo=arg_2) @@ -281,7 +279,6 @@ class ArrowFactory: ) elif isinstance(arg_1, date): - # (date, tzinfo/str) -> fromdate @ tzinfo if isinstance(arg_2, (dt_tzinfo, str)): return self.type.fromdate(arg_1, tzinfo=arg_2) diff --git a/lib/arrow/formatter.py b/lib/arrow/formatter.py index 728bea1a..d45f7153 100644 --- a/lib/arrow/formatter.py +++ b/lib/arrow/formatter.py @@ -29,7 +29,6 @@ FORMAT_W3C: Final[str] = "YYYY-MM-DD HH:mm:ssZZ" class DateTimeFormatter: - # This pattern matches characters enclosed in square brackets are matched as # an atomic group. For more info on atomic groups and how to they are # emulated in Python's re library, see https://stackoverflow.com/a/13577411/2701578 @@ -41,18 +40,15 @@ class DateTimeFormatter: locale: locales.Locale def __init__(self, locale: str = DEFAULT_LOCALE) -> None: - self.locale = locales.get_locale(locale) def format(cls, dt: datetime, fmt: str) -> str: - # FIXME: _format_token() is nullable return cls._FORMAT_RE.sub( lambda m: cast(str, cls._format_token(dt, m.group(0))), fmt ) def _format_token(self, dt: datetime, token: Optional[str]) -> Optional[str]: - if token and token.startswith("[") and token.endswith("]"): return token[1:-1] diff --git a/lib/arrow/locales.py b/lib/arrow/locales.py index 3627497f..34b2a098 100644 --- a/lib/arrow/locales.py +++ b/lib/arrow/locales.py @@ -129,7 +129,6 @@ class Locale: _locale_map[locale_name.lower().replace("_", "-")] = cls def __init__(self) -> None: - self._month_name_to_ordinal = None def describe( @@ -174,7 +173,7 @@ class Locale: # Needed to determine the correct relative string to use timeframe_value = 0 - for _unit_name, unit_value in timeframes: + for _, unit_value in timeframes: if trunc(unit_value) != 0: timeframe_value = trunc(unit_value) break @@ -285,7 +284,6 @@ class Locale: timeframe: TimeFrameLiteral, delta: Union[float, int], ) -> str: - if timeframe == "now": return humanized @@ -425,7 +423,7 @@ class ItalianLocale(Locale): "hours": "{0} ore", "day": "un giorno", "days": "{0} giorni", - "week": "una settimana,", + "week": "una settimana", "weeks": "{0} settimane", "month": "un mese", "months": "{0} mesi", @@ -867,14 +865,16 @@ class FinnishLocale(Locale): timeframes: ClassVar[Mapping[TimeFrameLiteral, Union[str, Mapping[str, str]]]] = { "now": "juuri nyt", - "second": "sekunti", - "seconds": {"past": "{0} muutama sekunti", "future": "{0} muutaman sekunnin"}, + "second": {"past": "sekunti", "future": "sekunnin"}, + "seconds": {"past": "{0} sekuntia", "future": "{0} sekunnin"}, "minute": {"past": "minuutti", "future": "minuutin"}, "minutes": {"past": "{0} minuuttia", "future": "{0} minuutin"}, "hour": {"past": "tunti", "future": "tunnin"}, "hours": {"past": "{0} tuntia", "future": "{0} tunnin"}, - "day": "päivä", + "day": {"past": "päivä", "future": "päivän"}, "days": {"past": "{0} päivää", "future": "{0} päivän"}, + "week": {"past": "viikko", "future": "viikon"}, + "weeks": {"past": "{0} viikkoa", "future": "{0} viikon"}, "month": {"past": "kuukausi", "future": "kuukauden"}, "months": {"past": "{0} kuukautta", "future": "{0} kuukauden"}, "year": {"past": "vuosi", "future": "vuoden"}, @@ -1887,7 +1887,7 @@ class GermanBaseLocale(Locale): future = "in {0}" and_word = "und" - timeframes = { + timeframes: ClassVar[Dict[TimeFrameLiteral, str]] = { "now": "gerade eben", "second": "einer Sekunde", "seconds": "{0} Sekunden", @@ -1982,7 +1982,9 @@ class GermanBaseLocale(Locale): return super().describe(timeframe, delta, only_distance) # German uses a different case without 'in' or 'ago' - humanized = self.timeframes_only_distance[timeframe].format(trunc(abs(delta))) + humanized: str = self.timeframes_only_distance[timeframe].format( + trunc(abs(delta)) + ) return humanized @@ -2547,6 +2549,8 @@ class ArabicLocale(Locale): "hours": {"2": "ساعتين", "ten": "{0} ساعات", "higher": "{0} ساعة"}, "day": "يوم", "days": {"2": "يومين", "ten": "{0} أيام", "higher": "{0} يوم"}, + "week": "اسبوع", + "weeks": {"2": "اسبوعين", "ten": "{0} أسابيع", "higher": "{0} اسبوع"}, "month": "شهر", "months": {"2": "شهرين", "ten": "{0} أشهر", "higher": "{0} شهر"}, "year": "سنة", @@ -3709,6 +3713,8 @@ class HungarianLocale(Locale): "hours": {"past": "{0} órával", "future": "{0} óra"}, "day": {"past": "egy nappal", "future": "egy nap"}, "days": {"past": "{0} nappal", "future": "{0} nap"}, + "week": {"past": "egy héttel", "future": "egy hét"}, + "weeks": {"past": "{0} héttel", "future": "{0} hét"}, "month": {"past": "egy hónappal", "future": "egy hónap"}, "months": {"past": "{0} hónappal", "future": "{0} hónap"}, "year": {"past": "egy évvel", "future": "egy év"}, @@ -3934,7 +3940,6 @@ class ThaiLocale(Locale): class LaotianLocale(Locale): - names = ["lo", "lo-la"] past = "{0} ກ່ອນຫນ້ານີ້" @@ -4119,6 +4124,7 @@ class BengaliLocale(Locale): return f"{n}র্থ" if n == 6: return f"{n}ষ্ঠ" + return "" class RomanshLocale(Locale): @@ -4137,6 +4143,8 @@ class RomanshLocale(Locale): "hours": "{0} ura", "day": "in di", "days": "{0} dis", + "week": "in'emna", + "weeks": "{0} emnas", "month": "in mais", "months": "{0} mais", "year": "in onn", @@ -5399,7 +5407,7 @@ class LuxembourgishLocale(Locale): future = "an {0}" and_word = "an" - timeframes = { + timeframes: ClassVar[Dict[TimeFrameLiteral, str]] = { "now": "just elo", "second": "enger Sekonn", "seconds": "{0} Sekonnen", @@ -5487,7 +5495,9 @@ class LuxembourgishLocale(Locale): return super().describe(timeframe, delta, only_distance) # Luxembourgish uses a different case without 'in' or 'ago' - humanized = self.timeframes_only_distance[timeframe].format(trunc(abs(delta))) + humanized: str = self.timeframes_only_distance[timeframe].format( + trunc(abs(delta)) + ) return humanized diff --git a/lib/arrow/parser.py b/lib/arrow/parser.py index e95d78b0..645e3da7 100644 --- a/lib/arrow/parser.py +++ b/lib/arrow/parser.py @@ -159,7 +159,6 @@ class DateTimeParser: _input_re_map: Dict[_FORMAT_TYPE, Pattern[str]] def __init__(self, locale: str = DEFAULT_LOCALE, cache_size: int = 0) -> None: - self.locale = locales.get_locale(locale) self._input_re_map = self._BASE_INPUT_RE_MAP.copy() self._input_re_map.update( @@ -196,7 +195,6 @@ class DateTimeParser: def parse_iso( self, datetime_string: str, normalize_whitespace: bool = False ) -> datetime: - if normalize_whitespace: datetime_string = re.sub(r"\s+", " ", datetime_string.strip()) @@ -236,13 +234,14 @@ class DateTimeParser: ] if has_time: - if has_space_divider: date_string, time_string = datetime_string.split(" ", 1) else: date_string, time_string = datetime_string.split("T", 1) - time_parts = re.split(r"[\+\-Z]", time_string, 1, re.IGNORECASE) + time_parts = re.split( + r"[\+\-Z]", time_string, maxsplit=1, flags=re.IGNORECASE + ) time_components: Optional[Match[str]] = self._TIME_RE.match(time_parts[0]) @@ -303,7 +302,6 @@ class DateTimeParser: fmt: Union[List[str], str], normalize_whitespace: bool = False, ) -> datetime: - if normalize_whitespace: datetime_string = re.sub(r"\s+", " ", datetime_string) @@ -341,12 +339,11 @@ class DateTimeParser: f"Unable to find a match group for the specified token {token!r}." ) - self._parse_token(token, value, parts) # type: ignore + self._parse_token(token, value, parts) # type: ignore[arg-type] return self._build_datetime(parts) def _generate_pattern_re(self, fmt: str) -> Tuple[List[_FORMAT_TYPE], Pattern[str]]: - # fmt is a string of tokens like 'YYYY-MM-DD' # we construct a new string by replacing each # token by its pattern: @@ -498,7 +495,6 @@ class DateTimeParser: value: Any, parts: _Parts, ) -> None: - if token == "YYYY": parts["year"] = int(value) @@ -508,7 +504,7 @@ class DateTimeParser: elif token in ["MMMM", "MMM"]: # FIXME: month_number() is nullable - parts["month"] = self.locale.month_number(value.lower()) # type: ignore + parts["month"] = self.locale.month_number(value.lower()) # type: ignore[typeddict-item] elif token in ["MM", "M"]: parts["month"] = int(value) @@ -588,7 +584,6 @@ class DateTimeParser: weekdate = parts.get("weekdate") if weekdate is not None: - year, week = int(weekdate[0]), int(weekdate[1]) if weekdate[2] is not None: @@ -712,7 +707,6 @@ class DateTimeParser: ) def _parse_multiformat(self, string: str, formats: Iterable[str]) -> datetime: - _datetime: Optional[datetime] = None for fmt in formats: @@ -740,12 +734,11 @@ class DateTimeParser: class TzinfoParser: _TZINFO_RE: ClassVar[Pattern[str]] = re.compile( - r"^([\+\-])?(\d{2})(?:\:?(\d{2}))?$" + r"^(?:\(UTC)*([\+\-])?(\d{2})(?:\:?(\d{2}))?" ) @classmethod def parse(cls, tzinfo_string: str) -> dt_tzinfo: - tzinfo: Optional[dt_tzinfo] = None if tzinfo_string == "local": @@ -755,7 +748,6 @@ class TzinfoParser: tzinfo = tz.tzutc() else: - iso_match = cls._TZINFO_RE.match(tzinfo_string) if iso_match: diff --git a/lib/backports/functools_lru_cache.py b/lib/backports/functools_lru_cache.py index 1b83fe99..e372cff3 100644 --- a/lib/backports/functools_lru_cache.py +++ b/lib/backports/functools_lru_cache.py @@ -26,6 +26,12 @@ def update_wrapper( class _HashedSeq(list): + """This class guarantees that hash() will be called no more than once + per element. This is important because the lru_cache() will hash + the key multiple times on a cache miss. + + """ + __slots__ = 'hashvalue' def __init__(self, tup, hash=hash): @@ -41,45 +47,57 @@ def _make_key( kwds, typed, kwd_mark=(object(),), - fasttypes=set([int, str, frozenset, type(None)]), - sorted=sorted, + fasttypes={int, str}, tuple=tuple, type=type, len=len, ): - 'Make a cache key from optionally typed positional and keyword arguments' + """Make a cache key from optionally typed positional and keyword arguments + + The key is constructed in a way that is flat as possible rather than + as a nested structure that would take more memory. + + If there is only a single argument and its data type is known to cache + its hash value, then that argument is returned without a wrapper. This + saves space and improves lookup speed. + + """ + # All of code below relies on kwds preserving the order input by the user. + # Formerly, we sorted() the kwds before looping. The new way is *much* + # faster; however, it means that f(x=1, y=2) will now be treated as a + # distinct call from f(y=2, x=1) which will be cached separately. key = args if kwds: - sorted_items = sorted(kwds.items()) key += kwd_mark - for item in sorted_items: + for item in kwds.items(): key += item if typed: key += tuple(type(v) for v in args) if kwds: - key += tuple(type(v) for k, v in sorted_items) + key += tuple(type(v) for v in kwds.values()) elif len(key) == 1 and type(key[0]) in fasttypes: return key[0] return _HashedSeq(key) -def lru_cache(maxsize=100, typed=False): # noqa: C901 +def lru_cache(maxsize=128, typed=False): """Least-recently-used cache decorator. If *maxsize* is set to None, the LRU features are disabled and the cache can grow without bound. If *typed* is True, arguments of different types will be cached separately. - For example, f(3.0) and f(3) will be treated as distinct calls with - distinct results. + For example, f(decimal.Decimal("3.0")) and f(3.0) will be treated as + distinct calls with distinct results. Some types such as str and int may + be cached separately even when typed is false. Arguments to the cached function must be hashable. - View the cache statistics named tuple (hits, misses, maxsize, currsize) with - f.cache_info(). Clear the cache and statistics with f.cache_clear(). + View the cache statistics named tuple (hits, misses, maxsize, currsize) + with f.cache_info(). Clear the cache and statistics with f.cache_clear(). Access the underlying function with f.__wrapped__. - See: http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used + See: https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU) """ @@ -88,108 +106,138 @@ def lru_cache(maxsize=100, typed=False): # noqa: C901 # The internals of the lru_cache are encapsulated for thread safety and # to allow the implementation to change (including a possible C version). + if isinstance(maxsize, int): + # Negative maxsize is treated as 0 + if maxsize < 0: + maxsize = 0 + elif callable(maxsize) and isinstance(typed, bool): + # The user_function was passed in directly via the maxsize argument + user_function, maxsize = maxsize, 128 + wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo) + wrapper.cache_parameters = lambda: {'maxsize': maxsize, 'typed': typed} + return update_wrapper(wrapper, user_function) + elif maxsize is not None: + raise TypeError('Expected first argument to be an integer, a callable, or None') + def decorating_function(user_function): - cache = dict() - stats = [0, 0] # make statistics updateable non-locally - HITS, MISSES = 0, 1 # names for the stats fields - make_key = _make_key - cache_get = cache.get # bound method to lookup key or return None - _len = len # localize the global len() function - lock = RLock() # because linkedlist updates aren't threadsafe - root = [] # root of the circular doubly linked list - root[:] = [root, root, None, None] # initialize by pointing to self - nonlocal_root = [root] # make updateable non-locally - PREV, NEXT, KEY, RESULT = 0, 1, 2, 3 # names for the link fields - - if maxsize == 0: - - def wrapper(*args, **kwds): - # no caching, just do a statistics update after a successful call - result = user_function(*args, **kwds) - stats[MISSES] += 1 - return result - - elif maxsize is None: - - def wrapper(*args, **kwds): - # simple caching without ordering or size limit - key = make_key(args, kwds, typed) - result = cache_get( - key, root - ) # root used here as a unique not-found sentinel - if result is not root: - stats[HITS] += 1 - return result - result = user_function(*args, **kwds) - cache[key] = result - stats[MISSES] += 1 - return result - - else: - - def wrapper(*args, **kwds): - # size limited caching that tracks accesses by recency - key = make_key(args, kwds, typed) if kwds or typed else args - with lock: - link = cache_get(key) - if link is not None: - # record recent use of the key by moving it - # to the front of the list - (root,) = nonlocal_root - link_prev, link_next, key, result = link - link_prev[NEXT] = link_next - link_next[PREV] = link_prev - last = root[PREV] - last[NEXT] = root[PREV] = link - link[PREV] = last - link[NEXT] = root - stats[HITS] += 1 - return result - result = user_function(*args, **kwds) - with lock: - (root,) = nonlocal_root - if key in cache: - # getting here means that this same key was added to the - # cache while the lock was released. since the link - # update is already done, we need only return the - # computed result and update the count of misses. - pass - elif _len(cache) >= maxsize: - # use the old root to store the new key and result - oldroot = root - oldroot[KEY] = key - oldroot[RESULT] = result - # empty the oldest link and make it the new root - root = nonlocal_root[0] = oldroot[NEXT] - oldkey = root[KEY] - root[KEY] = root[RESULT] = None - # now update the cache dictionary for the new links - del cache[oldkey] - cache[key] = oldroot - else: - # put result in a new link at the front of the list - last = root[PREV] - link = [last, root, key, result] - last[NEXT] = root[PREV] = cache[key] = link - stats[MISSES] += 1 - return result - - def cache_info(): - """Report cache statistics""" - with lock: - return _CacheInfo(stats[HITS], stats[MISSES], maxsize, len(cache)) - - def cache_clear(): - """Clear the cache and cache statistics""" - with lock: - cache.clear() - root = nonlocal_root[0] - root[:] = [root, root, None, None] - stats[:] = [0, 0] - - wrapper.__wrapped__ = user_function - wrapper.cache_info = cache_info - wrapper.cache_clear = cache_clear + wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo) + wrapper.cache_parameters = lambda: {'maxsize': maxsize, 'typed': typed} return update_wrapper(wrapper, user_function) return decorating_function + + +def _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo): + # Constants shared by all lru cache instances: + sentinel = object() # unique object used to signal cache misses + make_key = _make_key # build a key from the function arguments + PREV, NEXT, KEY, RESULT = 0, 1, 2, 3 # names for the link fields + + cache = {} + hits = misses = 0 + full = False + cache_get = cache.get # bound method to lookup a key or return None + cache_len = cache.__len__ # get cache size without calling len() + lock = RLock() # because linkedlist updates aren't threadsafe + root = [] # root of the circular doubly linked list + root[:] = [root, root, None, None] # initialize by pointing to self + + if maxsize == 0: + + def wrapper(*args, **kwds): + # No caching -- just a statistics update + nonlocal misses + misses += 1 + result = user_function(*args, **kwds) + return result + + elif maxsize is None: + + def wrapper(*args, **kwds): + # Simple caching without ordering or size limit + nonlocal hits, misses + key = make_key(args, kwds, typed) + result = cache_get(key, sentinel) + if result is not sentinel: + hits += 1 + return result + misses += 1 + result = user_function(*args, **kwds) + cache[key] = result + return result + + else: + + def wrapper(*args, **kwds): + # Size limited caching that tracks accesses by recency + nonlocal root, hits, misses, full + key = make_key(args, kwds, typed) + with lock: + link = cache_get(key) + if link is not None: + # Move the link to the front of the circular queue + link_prev, link_next, _key, result = link + link_prev[NEXT] = link_next + link_next[PREV] = link_prev + last = root[PREV] + last[NEXT] = root[PREV] = link + link[PREV] = last + link[NEXT] = root + hits += 1 + return result + misses += 1 + result = user_function(*args, **kwds) + with lock: + if key in cache: + # Getting here means that this same key was added to the + # cache while the lock was released. Since the link + # update is already done, we need only return the + # computed result and update the count of misses. + pass + elif full: + # Use the old root to store the new key and result. + oldroot = root + oldroot[KEY] = key + oldroot[RESULT] = result + # Empty the oldest link and make it the new root. + # Keep a reference to the old key and old result to + # prevent their ref counts from going to zero during the + # update. That will prevent potentially arbitrary object + # clean-up code (i.e. __del__) from running while we're + # still adjusting the links. + root = oldroot[NEXT] + oldkey = root[KEY] + root[KEY] = root[RESULT] = None + # Now update the cache dictionary. + del cache[oldkey] + # Save the potentially reentrant cache[key] assignment + # for last, after the root and links have been put in + # a consistent state. + cache[key] = oldroot + else: + # Put result in a new link at the front of the queue. + last = root[PREV] + link = [last, root, key, result] + last[NEXT] = root[PREV] = cache[key] = link + # Use the cache_len bound method instead of the len() function + # which could potentially be wrapped in an lru_cache itself. + full = cache_len() >= maxsize + return result + + def cache_info(): + """Report cache statistics""" + with lock: + return _CacheInfo(hits, misses, maxsize, cache_len()) + + def cache_clear(): + """Clear the cache and cache statistics""" + nonlocal hits, misses, full + with lock: + cache.clear() + root[:] = [root, root, None, None] + hits = misses = 0 + full = False + + wrapper.cache_info = cache_info + wrapper.cache_clear = cache_clear + return wrapper diff --git a/lib/bleach/__init__.py b/lib/bleach/__init__.py index 4e87eb80..12e93b4d 100644 --- a/lib/bleach/__init__.py +++ b/lib/bleach/__init__.py @@ -11,9 +11,9 @@ from bleach.sanitizer import ( # yyyymmdd -__releasedate__ = "20230123" +__releasedate__ = "20231006" # x.y.z or x.y.z.dev0 -- semver -__version__ = "6.0.0" +__version__ = "6.1.0" __all__ = ["clean", "linkify"] diff --git a/lib/bleach/html5lib_shim.py b/lib/bleach/html5lib_shim.py index aa5189b1..ca1cc8c8 100644 --- a/lib/bleach/html5lib_shim.py +++ b/lib/bleach/html5lib_shim.py @@ -395,10 +395,17 @@ class BleachHTMLTokenizer(HTMLTokenizer): # followed by a series of characters. It's treated as a tag # name that abruptly ends, but we should treat that like # character data - yield { - "type": TAG_TOKEN_TYPE_CHARACTERS, - "data": "<" + self.currentToken["name"], - } + yield {"type": TAG_TOKEN_TYPE_CHARACTERS, "data": self.stream.get_tag()} + elif last_error_token["data"] in ( + "eof-in-attribute-name", + "eof-in-attribute-value-no-quotes", + ): + # Handle the case where the text being parsed ends with < + # followed by a series of characters and then space and then + # more characters. It's treated as a tag name followed by an + # attribute that abruptly ends, but we should treat that like + # character data. + yield {"type": TAG_TOKEN_TYPE_CHARACTERS, "data": self.stream.get_tag()} else: yield last_error_token diff --git a/lib/bleach/linkifier.py b/lib/bleach/linkifier.py index 679d7ead..8fcefb2c 100644 --- a/lib/bleach/linkifier.py +++ b/lib/bleach/linkifier.py @@ -45,8 +45,8 @@ def build_url_re(tlds=TLDS, protocols=html5lib_shim.allowed_protocols): r"""\(* # Match any opening parentheses. \b(?"]*)? - # /path/zz (excluding "unsafe" chars from RFC 1738, + (?:[/?][^\s\{{\}}\|\\\^`<>"]*)? + # /path/zz (excluding "unsafe" chars from RFC 3986, # except for # and ~, which happen in practice) """.format( "|".join(sorted(protocols)), "|".join(sorted(tlds)) @@ -591,7 +591,7 @@ class LinkifyFilter(html5lib_shim.Filter): in_a = False token_buffer = [] else: - token_buffer.append(token) + token_buffer.extend(list(self.extract_entities(token))) continue if token["type"] in ["StartTag", "EmptyTag"]: diff --git a/lib/certifi/__init__.py b/lib/certifi/__init__.py index 8ce89cef..1c91f3ec 100644 --- a/lib/certifi/__init__.py +++ b/lib/certifi/__init__.py @@ -1,4 +1,4 @@ from .core import contents, where __all__ = ["contents", "where"] -__version__ = "2023.07.22" +__version__ = "2024.02.02" diff --git a/lib/certifi/cacert.pem b/lib/certifi/cacert.pem index 02123695..fac3c319 100644 --- a/lib/certifi/cacert.pem +++ b/lib/certifi/cacert.pem @@ -245,34 +245,6 @@ mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK 4SVhM7JZG+Ju1zdXtg2pEto= -----END CERTIFICATE----- -# Issuer: O=SECOM Trust.net OU=Security Communication RootCA1 -# Subject: O=SECOM Trust.net OU=Security Communication RootCA1 -# Label: "Security Communication Root CA" -# Serial: 0 -# MD5 Fingerprint: f1:bc:63:6a:54:e0:b5:27:f5:cd:e7:1a:e3:4d:6e:4a -# SHA1 Fingerprint: 36:b1:2b:49:f9:81:9e:d7:4c:9e:bc:38:0f:c6:56:8f:5d:ac:b2:f7 -# SHA256 Fingerprint: e7:5e:72:ed:9f:56:0e:ec:6e:b4:80:00:73:a4:3f:c3:ad:19:19:5a:39:22:82:01:78:95:97:4a:99:02:6b:6c ------BEGIN CERTIFICATE----- -MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEY -MBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21t -dW5pY2F0aW9uIFJvb3RDQTEwHhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5 -WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYD -VQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEwggEiMA0GCSqGSIb3 -DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw8yl8 -9f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJ -DKaVv0uMDPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9 -Ms+k2Y7CI9eNqPPYJayX5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/N -QV3Is00qVUarH9oe4kA92819uZKAnDfdDJZkndwi92SL32HeFZRSFaB9UslLqCHJ -xrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2JChzAgMBAAGjPzA9MB0G -A1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYwDwYDVR0T -AQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vG -kl3g0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfr -Uj94nK9NrvjVT8+amCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5 -Bw+SUEmK3TGXX8npN6o7WWWXlDLJs58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJU -JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot -RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw== ------END CERTIFICATE----- - # Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com # Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com # Label: "XRamp Global CA Root" @@ -881,49 +853,6 @@ Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH WD9f -----END CERTIFICATE----- -# Issuer: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 -# Subject: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 -# Label: "Autoridad de Certificacion Firmaprofesional CIF A62634068" -# Serial: 6047274297262753887 -# MD5 Fingerprint: 73:3a:74:7a:ec:bb:a3:96:a6:c2:e4:e2:c8:9b:c0:c3 -# SHA1 Fingerprint: ae:c5:fb:3f:c8:e1:bf:c4:e5:4f:03:07:5a:9a:e8:00:b7:f7:b6:fa -# SHA256 Fingerprint: 04:04:80:28:bf:1f:28:64:d4:8f:9a:d4:d8:32:94:36:6a:82:88:56:55:3f:3b:14:30:3f:90:14:7f:5d:40:ef ------BEGIN CERTIFICATE----- -MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UE -BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h -cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEy -MzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg -Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi -MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9 -thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM -cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG -L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i -NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h -X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b -m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy -Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja -EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T -KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF -6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh -OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYD -VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNHDhpkLzCBpgYD -VR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp -cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBv -ACAAZABlACAAbABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBl -AGwAbwBuAGEAIAAwADgAMAAxADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF -661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx51tkljYyGOylMnfX40S2wBEqgLk9 -am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qkR71kMrv2JYSiJ0L1 -ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaPT481 -PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS -3a/DTg4fJl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5k -SeTy36LssUzAKh3ntLFlosS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF -3dvd6qJ2gHN99ZwExEWN57kci57q13XRcrHedUTnQn3iV2t93Jm8PYMo6oCTjcVM -ZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoRsaS8I8nkvof/uZS2+F0g -StRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTDKCOM/icz -Q0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQB -jLMi6Et8Vcad+qMUu2WFbm5PEn4KPJ2V ------END CERTIFICATE----- - # Issuer: CN=Izenpe.com O=IZENPE S.A. # Subject: CN=Izenpe.com O=IZENPE S.A. # Label: "Izenpe.com" @@ -4633,3 +4562,253 @@ o7Ey7Nmj1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5 dDTedk+SKlOxJTnbPP/lPqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcE oji2jbDwN/zIIX8/syQbPYtuzE2wFg2WHYMfRsCbvUOZ58SWLs5fyQ== -----END CERTIFICATE----- + +# Issuer: CN=TrustAsia Global Root CA G3 O=TrustAsia Technologies, Inc. +# Subject: CN=TrustAsia Global Root CA G3 O=TrustAsia Technologies, Inc. +# Label: "TrustAsia Global Root CA G3" +# Serial: 576386314500428537169965010905813481816650257167 +# MD5 Fingerprint: 30:42:1b:b7:bb:81:75:35:e4:16:4f:53:d2:94:de:04 +# SHA1 Fingerprint: 63:cf:b6:c1:27:2b:56:e4:88:8e:1c:23:9a:b6:2e:81:47:24:c3:c7 +# SHA256 Fingerprint: e0:d3:22:6a:eb:11:63:c2:e4:8f:f9:be:3b:50:b4:c6:43:1b:e7:bb:1e:ac:c5:c3:6b:5d:5e:c5:09:03:9a:08 +-----BEGIN CERTIFICATE----- +MIIFpTCCA42gAwIBAgIUZPYOZXdhaqs7tOqFhLuxibhxkw8wDQYJKoZIhvcNAQEM +BQAwWjELMAkGA1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dp +ZXMsIEluYy4xJDAiBgNVBAMMG1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHMzAe +Fw0yMTA1MjAwMjEwMTlaFw00NjA1MTkwMjEwMTlaMFoxCzAJBgNVBAYTAkNOMSUw +IwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDDBtU +cnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDAMYJhkuSUGwoqZdC+BqmHO1ES6nBBruL7dOoKjbmzTNyPtxNS +T1QY4SxzlZHFZjtqz6xjbYdT8PfxObegQ2OwxANdV6nnRM7EoYNl9lA+sX4WuDqK +AtCWHwDNBSHvBm3dIZwZQ0WhxeiAysKtQGIXBsaqvPPW5vxQfmZCHzyLpnl5hkA1 +nyDvP+uLRx+PjsXUjrYsyUQE49RDdT/VP68czH5GX6zfZBCK70bwkPAPLfSIC7Ep +qq+FqklYqL9joDiR5rPmd2jE+SoZhLsO4fWvieylL1AgdB4SQXMeJNnKziyhWTXA +yB1GJ2Faj/lN03J5Zh6fFZAhLf3ti1ZwA0pJPn9pMRJpxx5cynoTi+jm9WAPzJMs +hH/x/Gr8m0ed262IPfN2dTPXS6TIi/n1Q1hPy8gDVI+lhXgEGvNz8teHHUGf59gX +zhqcD0r83ERoVGjiQTz+LISGNzzNPy+i2+f3VANfWdP3kXjHi3dqFuVJhZBFcnAv +kV34PmVACxmZySYgWmjBNb9Pp1Hx2BErW+Canig7CjoKH8GB5S7wprlppYiU5msT +f9FkPz2ccEblooV7WIQn3MSAPmeamseaMQ4w7OYXQJXZRe0Blqq/DPNL0WP3E1jA +uPP6Z92bfW1K/zJMtSU7/xxnD4UiWQWRkUF3gdCFTIcQcf+eQxuulXUtgQIDAQAB +o2MwYTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEDk5PIj7zjKsK5Xf/Ih +MBY027ySMB0GA1UdDgQWBBRA5OTyI+84yrCuV3/yITAWNNu8kjAOBgNVHQ8BAf8E +BAMCAQYwDQYJKoZIhvcNAQEMBQADggIBACY7UeFNOPMyGLS0XuFlXsSUT9SnYaP4 +wM8zAQLpw6o1D/GUE3d3NZ4tVlFEbuHGLige/9rsR82XRBf34EzC4Xx8MnpmyFq2 +XFNFV1pF1AWZLy4jVe5jaN/TG3inEpQGAHUNcoTpLrxaatXeL1nHo+zSh2bbt1S1 +JKv0Q3jbSwTEb93mPmY+KfJLaHEih6D4sTNjduMNhXJEIlU/HHzp/LgV6FL6qj6j +ITk1dImmasI5+njPtqzn59ZW/yOSLlALqbUHM/Q4X6RJpstlcHboCoWASzY9M/eV +VHUl2qzEc4Jl6VL1XP04lQJqaTDFHApXB64ipCz5xUG3uOyfT0gA+QEEVcys+TIx +xHWVBqB/0Y0n3bOppHKH/lmLmnp0Ft0WpWIp6zqW3IunaFnT63eROfjXy9mPX1on +AX1daBli2MjN9LdyR75bl87yraKZk62Uy5P2EgmVtqvXO9A/EcswFi55gORngS1d +7XB4tmBZrOFdRWOPyN9yaFvqHbgB8X7754qz41SgOAngPN5C8sLtLpvzHzW2Ntjj +gKGLzZlkD8Kqq7HK9W+eQ42EVJmzbsASZthwEPEGNTNDqJwuuhQxzhB/HIbjj9LV ++Hfsm6vxL2PZQl/gZ4FkkfGXL/xuJvYz+NO1+MRiqzFRJQJ6+N1rZdVtTTDIZbpo +FGWsJwt0ivKH +-----END CERTIFICATE----- + +# Issuer: CN=TrustAsia Global Root CA G4 O=TrustAsia Technologies, Inc. +# Subject: CN=TrustAsia Global Root CA G4 O=TrustAsia Technologies, Inc. +# Label: "TrustAsia Global Root CA G4" +# Serial: 451799571007117016466790293371524403291602933463 +# MD5 Fingerprint: 54:dd:b2:d7:5f:d8:3e:ed:7c:e0:0b:2e:cc:ed:eb:eb +# SHA1 Fingerprint: 57:73:a5:61:5d:80:b2:e6:ac:38:82:fc:68:07:31:ac:9f:b5:92:5a +# SHA256 Fingerprint: be:4b:56:cb:50:56:c0:13:6a:52:6d:f4:44:50:8d:aa:36:a0:b5:4f:42:e4:ac:38:f7:2a:f4:70:e4:79:65:4c +-----BEGIN CERTIFICATE----- +MIICVTCCAdygAwIBAgIUTyNkuI6XY57GU4HBdk7LKnQV1tcwCgYIKoZIzj0EAwMw +WjELMAkGA1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMs +IEluYy4xJDAiBgNVBAMMG1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHNDAeFw0y +MTA1MjAwMjEwMjJaFw00NjA1MTkwMjEwMjJaMFoxCzAJBgNVBAYTAkNOMSUwIwYD +VQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDDBtUcnVz +dEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATx +s8045CVD5d4ZCbuBeaIVXxVjAd7Cq92zphtnS4CDr5nLrBfbK5bKfFJV4hrhPVbw +LxYI+hW8m7tH5j/uqOFMjPXTNvk4XatwmkcN4oFBButJ+bAp3TPsUKV/eSm4IJij +YzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUpbtKl86zK3+kMd6Xg1mD +pm9xy94wHQYDVR0OBBYEFKW7SpfOsyt/pDHel4NZg6ZvccveMA4GA1UdDwEB/wQE +AwIBBjAKBggqhkjOPQQDAwNnADBkAjBe8usGzEkxn0AAbbd+NvBNEU/zy4k6LHiR +UKNbwMp1JvK/kF0LgoxgKJ/GcJpo5PECMFxYDlZ2z1jD1xCMuo6u47xkdUfFVZDj +/bpV6wfEU6s3qe4hsiFbYI89MvHVI5TWWA== +-----END CERTIFICATE----- + +# Issuer: CN=CommScope Public Trust ECC Root-01 O=CommScope +# Subject: CN=CommScope Public Trust ECC Root-01 O=CommScope +# Label: "CommScope Public Trust ECC Root-01" +# Serial: 385011430473757362783587124273108818652468453534 +# MD5 Fingerprint: 3a:40:a7:fc:03:8c:9c:38:79:2f:3a:a2:6c:b6:0a:16 +# SHA1 Fingerprint: 07:86:c0:d8:dd:8e:c0:80:98:06:98:d0:58:7a:ef:de:a6:cc:a2:5d +# SHA256 Fingerprint: 11:43:7c:da:7b:b4:5e:41:36:5f:45:b3:9a:38:98:6b:0d:e0:0d:ef:34:8e:0c:7b:b0:87:36:33:80:0b:c3:8b +-----BEGIN CERTIFICATE----- +MIICHTCCAaOgAwIBAgIUQ3CCd89NXTTxyq4yLzf39H91oJ4wCgYIKoZIzj0EAwMw +TjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29t +bVNjb3BlIFB1YmxpYyBUcnVzdCBFQ0MgUm9vdC0wMTAeFw0yMTA0MjgxNzM1NDNa +Fw00NjA0MjgxNzM1NDJaME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21tU2Nv +cGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3QgRUNDIFJvb3QtMDEw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAARLNumuV16ocNfQj3Rid8NeeqrltqLxeP0C +flfdkXmcbLlSiFS8LwS+uM32ENEp7LXQoMPwiXAZu1FlxUOcw5tjnSCDPgYLpkJE +hRGnSjot6dZoL0hOUysHP029uax3OVejQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD +VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSOB2LAUN3GGQYARnQE9/OufXVNMDAKBggq +hkjOPQQDAwNoADBlAjEAnDPfQeMjqEI2Jpc1XHvr20v4qotzVRVcrHgpD7oh2MSg +2NED3W3ROT3Ek2DS43KyAjB8xX6I01D1HiXo+k515liWpDVfG2XqYZpwI7UNo5uS +Um9poIyNStDuiw7LR47QjRE= +-----END CERTIFICATE----- + +# Issuer: CN=CommScope Public Trust ECC Root-02 O=CommScope +# Subject: CN=CommScope Public Trust ECC Root-02 O=CommScope +# Label: "CommScope Public Trust ECC Root-02" +# Serial: 234015080301808452132356021271193974922492992893 +# MD5 Fingerprint: 59:b0:44:d5:65:4d:b8:5c:55:19:92:02:b6:d1:94:b2 +# SHA1 Fingerprint: 3c:3f:ef:57:0f:fe:65:93:86:9e:a0:fe:b0:f6:ed:8e:d1:13:c7:e5 +# SHA256 Fingerprint: 2f:fb:7f:81:3b:bb:b3:c8:9a:b4:e8:16:2d:0f:16:d7:15:09:a8:30:cc:9d:73:c2:62:e5:14:08:75:d1:ad:4a +-----BEGIN CERTIFICATE----- +MIICHDCCAaOgAwIBAgIUKP2ZYEFHpgE6yhR7H+/5aAiDXX0wCgYIKoZIzj0EAwMw +TjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29t +bVNjb3BlIFB1YmxpYyBUcnVzdCBFQ0MgUm9vdC0wMjAeFw0yMTA0MjgxNzQ0NTRa +Fw00NjA0MjgxNzQ0NTNaME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21tU2Nv +cGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3QgRUNDIFJvb3QtMDIw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAR4MIHoYx7l63FRD/cHB8o5mXxO1Q/MMDAL +j2aTPs+9xYa9+bG3tD60B8jzljHz7aRP+KNOjSkVWLjVb3/ubCK1sK9IRQq9qEmU +v4RDsNuESgMjGWdqb8FuvAY5N9GIIvejQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD +VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTmGHX/72DehKT1RsfeSlXjMjZ59TAKBggq +hkjOPQQDAwNnADBkAjAmc0l6tqvmSfR9Uj/UQQSugEODZXW5hYA4O9Zv5JOGq4/n +ich/m35rChJVYaoR4HkCMHfoMXGsPHED1oQmHhS48zs73u1Z/GtMMH9ZzkXpc2AV +mkzw5l4lIhVtwodZ0LKOag== +-----END CERTIFICATE----- + +# Issuer: CN=CommScope Public Trust RSA Root-01 O=CommScope +# Subject: CN=CommScope Public Trust RSA Root-01 O=CommScope +# Label: "CommScope Public Trust RSA Root-01" +# Serial: 354030733275608256394402989253558293562031411421 +# MD5 Fingerprint: 0e:b4:15:bc:87:63:5d:5d:02:73:d4:26:38:68:73:d8 +# SHA1 Fingerprint: 6d:0a:5f:f7:b4:23:06:b4:85:b3:b7:97:64:fc:ac:75:f5:33:f2:93 +# SHA256 Fingerprint: 02:bd:f9:6e:2a:45:dd:9b:f1:8f:c7:e1:db:df:21:a0:37:9b:a3:c9:c2:61:03:44:cf:d8:d6:06:fe:c1:ed:81 +-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIUPgNJgXUWdDGOTKvVxZAplsU5EN0wDQYJKoZIhvcNAQEL +BQAwTjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwi +Q29tbVNjb3BlIFB1YmxpYyBUcnVzdCBSU0EgUm9vdC0wMTAeFw0yMTA0MjgxNjQ1 +NTRaFw00NjA0MjgxNjQ1NTNaME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21t +U2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3QgUlNBIFJvb3Qt +MDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwSGWjDR1C45FtnYSk +YZYSwu3D2iM0GXb26v1VWvZVAVMP8syMl0+5UMuzAURWlv2bKOx7dAvnQmtVzslh +suitQDy6uUEKBU8bJoWPQ7VAtYXR1HHcg0Hz9kXHgKKEUJdGzqAMxGBWBB0HW0al +DrJLpA6lfO741GIDuZNqihS4cPgugkY4Iw50x2tBt9Apo52AsH53k2NC+zSDO3Oj +WiE260f6GBfZumbCk6SP/F2krfxQapWsvCQz0b2If4b19bJzKo98rwjyGpg/qYFl +P8GMicWWMJoKz/TUyDTtnS+8jTiGU+6Xn6myY5QXjQ/cZip8UlF1y5mO6D1cv547 +KI2DAg+pn3LiLCuz3GaXAEDQpFSOm117RTYm1nJD68/A6g3czhLmfTifBSeolz7p +UcZsBSjBAg/pGG3svZwG1KdJ9FQFa2ww8esD1eo9anbCyxooSU1/ZOD6K9pzg4H/ +kQO9lLvkuI6cMmPNn7togbGEW682v3fuHX/3SZtS7NJ3Wn2RnU3COS3kuoL4b/JO +Hg9O5j9ZpSPcPYeoKFgo0fEbNttPxP/hjFtyjMcmAyejOQoBqsCyMWCDIqFPEgkB +Ea801M/XrmLTBQe0MXXgDW1XT2mH+VepuhX2yFJtocucH+X8eKg1mp9BFM6ltM6U +CBwJrVbl2rZJmkrqYxhTnCwuwwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUN12mmnQywsL5x6YVEFm45P3luG0wDQYJ +KoZIhvcNAQELBQADggIBAK+nz97/4L1CjU3lIpbfaOp9TSp90K09FlxD533Ahuh6 +NWPxzIHIxgvoLlI1pKZJkGNRrDSsBTtXAOnTYtPZKdVUvhwQkZyybf5Z/Xn36lbQ +nmhUQo8mUuJM3y+Xpi/SB5io82BdS5pYV4jvguX6r2yBS5KPQJqTRlnLX3gWsWc+ +QgvfKNmwrZggvkN80V4aCRckjXtdlemrwWCrWxhkgPut4AZ9HcpZuPN4KWfGVh2v +trV0KnahP/t1MJ+UXjulYPPLXAziDslg+MkfFoom3ecnf+slpoq9uC02EJqxWE2a +aE9gVOX2RhOOiKy8IUISrcZKiX2bwdgt6ZYD9KJ0DLwAHb/WNyVntHKLr4W96ioD +j8z7PEQkguIBpQtZtjSNMgsSDesnwv1B10A8ckYpwIzqug/xBpMu95yo9GA+o/E4 +Xo4TwbM6l4c/ksp4qRyv0LAbJh6+cOx69TOY6lz/KwsETkPdY34Op054A5U+1C0w +lREQKC6/oAI+/15Z0wUOlV9TRe9rh9VIzRamloPh37MG88EU26fsHItdkJANclHn +YfkUyq+Dj7+vsQpZXdxc1+SWrVtgHdqul7I52Qb1dgAT+GhMIbA1xNxVssnBQVoc +icCMb3SgazNNtQEo/a2tiRc7ppqEvOuM6sRxJKi6KfkIsidWNTJf6jn7MZrVGczw +-----END CERTIFICATE----- + +# Issuer: CN=CommScope Public Trust RSA Root-02 O=CommScope +# Subject: CN=CommScope Public Trust RSA Root-02 O=CommScope +# Label: "CommScope Public Trust RSA Root-02" +# Serial: 480062499834624527752716769107743131258796508494 +# MD5 Fingerprint: e1:29:f9:62:7b:76:e2:96:6d:f3:d4:d7:0f:ae:1f:aa +# SHA1 Fingerprint: ea:b0:e2:52:1b:89:93:4c:11:68:f2:d8:9a:ac:22:4c:a3:8a:57:ae +# SHA256 Fingerprint: ff:e9:43:d7:93:42:4b:4f:7c:44:0c:1c:3d:64:8d:53:63:f3:4b:82:dc:87:aa:7a:9f:11:8f:c5:de:e1:01:f1 +-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIUVBa/O345lXGN0aoApYYNK496BU4wDQYJKoZIhvcNAQEL +BQAwTjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwi +Q29tbVNjb3BlIFB1YmxpYyBUcnVzdCBSU0EgUm9vdC0wMjAeFw0yMTA0MjgxNzE2 +NDNaFw00NjA0MjgxNzE2NDJaME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21t +U2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3QgUlNBIFJvb3Qt +MDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDh+g77aAASyE3VrCLE +NQE7xVTlWXZjpX/rwcRqmL0yjReA61260WI9JSMZNRTpf4mnG2I81lDnNJUDMrG0 +kyI9p+Kx7eZ7Ti6Hmw0zdQreqjXnfuU2mKKuJZ6VszKWpCtYHu8//mI0SFHRtI1C +rWDaSWqVcN3SAOLMV2MCe5bdSZdbkk6V0/nLKR8YSvgBKtJjCW4k6YnS5cciTNxz +hkcAqg2Ijq6FfUrpuzNPDlJwnZXjfG2WWy09X6GDRl224yW4fKcZgBzqZUPckXk2 +LHR88mcGyYnJ27/aaL8j7dxrrSiDeS/sOKUNNwFnJ5rpM9kzXzehxfCrPfp4sOcs +n/Y+n2Dg70jpkEUeBVF4GiwSLFworA2iI540jwXmojPOEXcT1A6kHkIfhs1w/tku +FT0du7jyU1fbzMZ0KZwYszZ1OC4PVKH4kh+Jlk+71O6d6Ts2QrUKOyrUZHk2EOH5 +kQMreyBUzQ0ZGshBMjTRsJnhkB4BQDa1t/qp5Xd1pCKBXbCL5CcSD1SIxtuFdOa3 +wNemKfrb3vOTlycEVS8KbzfFPROvCgCpLIscgSjX74Yxqa7ybrjKaixUR9gqiC6v +wQcQeKwRoi9C8DfF8rhW3Q5iLc4tVn5V8qdE9isy9COoR+jUKgF4z2rDN6ieZdIs +5fq6M8EGRPbmz6UNp2YINIos8wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUR9DnsSL/nSz12Vdgs7GxcJXvYXowDQYJ +KoZIhvcNAQELBQADggIBAIZpsU0v6Z9PIpNojuQhmaPORVMbc0RTAIFhzTHjCLqB +KCh6krm2qMhDnscTJk3C2OVVnJJdUNjCK9v+5qiXz1I6JMNlZFxHMaNlNRPDk7n3 ++VGXu6TwYofF1gbTl4MgqX67tiHCpQ2EAOHyJxCDut0DgdXdaMNmEMjRdrSzbyme +APnCKfWxkxlSaRosTKCL4BWaMS/TiJVZbuXEs1DIFAhKm4sTg7GkcrI7djNB3Nyq +pgdvHSQSn8h2vS/ZjvQs7rfSOBAkNlEv41xdgSGn2rtO/+YHqP65DSdsu3BaVXoT +6fEqSWnHX4dXTEN5bTpl6TBcQe7rd6VzEojov32u5cSoHw2OHG1QAk8mGEPej1WF +sQs3BWDJVTkSBKEqz3EWnzZRSb9wO55nnPt7eck5HHisd5FUmrh1CoFSl+NmYWvt +PjgelmFV4ZFUjO2MJB+ByRCac5krFk5yAD9UG/iNuovnFNa2RU9g7Jauwy8CTl2d +lklyALKrdVwPaFsdZcJfMw8eD/A7hvWwTruc9+olBdytoptLFwG+Qt81IR2tq670 +v64fG9PiO/yzcnMcmyiQiRM9HcEARwmWmjgb3bHPDcK0RPOWlc4yOo80nOAXx17O +rg3bhzjlP1v9mxnhMUF6cKojawHhRUzNlM47ni3niAIi9G7oyOzWPPO5std3eqx7 +-----END CERTIFICATE----- + +# Issuer: CN=Telekom Security TLS ECC Root 2020 O=Deutsche Telekom Security GmbH +# Subject: CN=Telekom Security TLS ECC Root 2020 O=Deutsche Telekom Security GmbH +# Label: "Telekom Security TLS ECC Root 2020" +# Serial: 72082518505882327255703894282316633856 +# MD5 Fingerprint: c1:ab:fe:6a:10:2c:03:8d:bc:1c:22:32:c0:85:a7:fd +# SHA1 Fingerprint: c0:f8:96:c5:a9:3b:01:06:21:07:da:18:42:48:bc:e9:9d:88:d5:ec +# SHA256 Fingerprint: 57:8a:f4:de:d0:85:3f:4e:59:98:db:4a:ea:f9:cb:ea:8d:94:5f:60:b6:20:a3:8d:1a:3c:13:b2:bc:7b:a8:e1 +-----BEGIN CERTIFICATE----- +MIICQjCCAcmgAwIBAgIQNjqWjMlcsljN0AFdxeVXADAKBggqhkjOPQQDAzBjMQsw +CQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBH +bWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBFQ0MgUm9vdCAyMDIw +MB4XDTIwMDgyNTA3NDgyMFoXDTQ1MDgyNTIzNTk1OVowYzELMAkGA1UEBhMCREUx +JzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkGA1UE +AwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgRUNDIFJvb3QgMjAyMDB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABM6//leov9Wq9xCazbzREaK9Z0LMkOsVGJDZos0MKiXrPk/O +tdKPD/M12kOLAoC+b1EkHQ9rK8qfwm9QMuU3ILYg/4gND21Ju9sGpIeQkpT0CdDP +f8iAC8GXs7s1J8nCG6NCMEAwHQYDVR0OBBYEFONyzG6VmUex5rNhTNHLq+O6zd6f +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2cA +MGQCMHVSi7ekEE+uShCLsoRbQuHmKjYC2qBuGT8lv9pZMo7k+5Dck2TOrbRBR2Di +z6fLHgIwN0GMZt9Ba9aDAEH9L1r3ULRn0SyocddDypwnJJGDSA3PzfdUga/sf+Rn +27iQ7t0l +-----END CERTIFICATE----- + +# Issuer: CN=Telekom Security TLS RSA Root 2023 O=Deutsche Telekom Security GmbH +# Subject: CN=Telekom Security TLS RSA Root 2023 O=Deutsche Telekom Security GmbH +# Label: "Telekom Security TLS RSA Root 2023" +# Serial: 44676229530606711399881795178081572759 +# MD5 Fingerprint: bf:5b:eb:54:40:cd:48:71:c4:20:8d:7d:de:0a:42:f2 +# SHA1 Fingerprint: 54:d3:ac:b3:bd:57:56:f6:85:9d:ce:e5:c3:21:e2:d4:ad:83:d0:93 +# SHA256 Fingerprint: ef:c6:5c:ad:bb:59:ad:b6:ef:e8:4d:a2:23:11:b3:56:24:b7:1b:3b:1e:a0:da:8b:66:55:17:4e:c8:97:86:46 +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIQIZxULej27HF3+k7ow3BXlzANBgkqhkiG9w0BAQwFADBj +MQswCQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0 +eSBHbWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBSU0EgUm9vdCAy +MDIzMB4XDTIzMDMyODEyMTY0NVoXDTQ4MDMyNzIzNTk1OVowYzELMAkGA1UEBhMC +REUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkG +A1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgUlNBIFJvb3QgMjAyMzCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAO01oYGA88tKaVvC+1GDrib94W7zgRJ9 +cUD/h3VCKSHtgVIs3xLBGYSJwb3FKNXVS2xE1kzbB5ZKVXrKNoIENqil/Cf2SfHV +cp6R+SPWcHu79ZvB7JPPGeplfohwoHP89v+1VmLhc2o0mD6CuKyVU/QBoCcHcqMA +U6DksquDOFczJZSfvkgdmOGjup5czQRxUX11eKvzWarE4GC+j4NSuHUaQTXtvPM6 +Y+mpFEXX5lLRbtLevOP1Czvm4MS9Q2QTps70mDdsipWol8hHD/BeEIvnHRz+sTug +BTNoBUGCwQMrAcjnj02r6LX2zWtEtefdi+zqJbQAIldNsLGyMcEWzv/9FIS3R/qy +8XDe24tsNlikfLMR0cN3f1+2JeANxdKz+bi4d9s3cXFH42AYTyS2dTd4uaNir73J +co4vzLuu2+QVUhkHM/tqty1LkCiCc/4YizWN26cEar7qwU02OxY2kTLvtkCJkUPg +8qKrBC7m8kwOFjQgrIfBLX7JZkcXFBGk8/ehJImr2BrIoVyxo/eMbcgByU/J7MT8 +rFEz0ciD0cmfHdRHNCk+y7AO+oMLKFjlKdw/fKifybYKu6boRhYPluV75Gp6SG12 +mAWl3G0eQh5C2hrgUve1g8Aae3g1LDj1H/1Joy7SWWO/gLCMk3PLNaaZlSJhZQNg ++y+TS/qanIA7AgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtqeX +gj10hZv3PJ+TmpV5dVKMbUcwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS2 +p5eCPXSFm/c8n5OalXl1UoxtRzANBgkqhkiG9w0BAQwFAAOCAgEAqMxhpr51nhVQ +pGv7qHBFfLp+sVr8WyP6Cnf4mHGCDG3gXkaqk/QeoMPhk9tLrbKmXauw1GLLXrtm +9S3ul0A8Yute1hTWjOKWi0FpkzXmuZlrYrShF2Y0pmtjxrlO8iLpWA1WQdH6DErw +M807u20hOq6OcrXDSvvpfeWxm4bu4uB9tPcy/SKE8YXJN3nptT+/XOR0so8RYgDd +GGah2XsjX/GO1WfoVNpbOms2b/mBsTNHM3dA+VKq3dSDz4V4mZqTuXNnQkYRIer+ +CqkbGmVps4+uFrb2S1ayLfmlyOw7YqPta9BO1UAJpB+Y1zqlklkg5LB9zVtzaL1t +xKITDmcZuI1CfmwMmm6gJC3VRRvcxAIU/oVbZZfKTpBQCHpCNfnqwmbU+AGuHrS+ +w6jv/naaoqYfRvaE7fzbzsQCzndILIyy7MMAo+wsVRjBfhnu4S/yrYObnqsZ38aK +L4x35bcF7DvB7L6Gs4a8wPfc5+pbrrLMtTWGS9DiP7bY+A4A7l3j941Y/8+LN+lj +X273CXE2whJdV/LItM3z7gLfEdxquVeEHVlNjM7IDiPCtyaaEBRx/pOyiriA8A4Q +ntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0o82bNSQ3+pCTE4FCxpgm +dTdmQRCsu/WU48IxK63nI1bMNSWSs1A= +-----END CERTIFICATE----- diff --git a/lib/certifi/core.py b/lib/certifi/core.py index de028981..91f538bb 100644 --- a/lib/certifi/core.py +++ b/lib/certifi/core.py @@ -5,6 +5,10 @@ certifi.py This module returns the installation location of cacert.pem or its contents. """ import sys +import atexit + +def exit_cacert_ctx() -> None: + _CACERT_CTX.__exit__(None, None, None) # type: ignore[union-attr] if sys.version_info >= (3, 11): @@ -35,6 +39,7 @@ if sys.version_info >= (3, 11): # we will also store that at the global level as well. _CACERT_CTX = as_file(files("certifi").joinpath("cacert.pem")) _CACERT_PATH = str(_CACERT_CTX.__enter__()) + atexit.register(exit_cacert_ctx) return _CACERT_PATH @@ -70,6 +75,7 @@ elif sys.version_info >= (3, 7): # we will also store that at the global level as well. _CACERT_CTX = get_path("certifi", "cacert.pem") _CACERT_PATH = str(_CACERT_CTX.__enter__()) + atexit.register(exit_cacert_ctx) return _CACERT_PATH diff --git a/lib/dateutil-stubs/METADATA.toml b/lib/dateutil-stubs/METADATA.toml new file mode 100644 index 00000000..5f2be1e4 --- /dev/null +++ b/lib/dateutil-stubs/METADATA.toml @@ -0,0 +1,6 @@ +version = "2.9.*" +upstream_repository = "https://github.com/dateutil/dateutil" +partial_stub = true + +[tool.stubtest] +ignore_missing_stub = true diff --git a/lib/dateutil-stubs/__init__.pyi b/lib/dateutil-stubs/__init__.pyi new file mode 100644 index 00000000..e69de29b diff --git a/lib/dateutil-stubs/_common.pyi b/lib/dateutil-stubs/_common.pyi new file mode 100644 index 00000000..75bf74c5 --- /dev/null +++ b/lib/dateutil-stubs/_common.pyi @@ -0,0 +1,9 @@ +from typing_extensions import Self + +class weekday: + def __init__(self, weekday: int, n: int | None = None) -> None: ... + def __call__(self, n: int) -> Self: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + weekday: int + n: int diff --git a/lib/dateutil-stubs/easter.pyi b/lib/dateutil-stubs/easter.pyi new file mode 100644 index 00000000..952d1191 --- /dev/null +++ b/lib/dateutil-stubs/easter.pyi @@ -0,0 +1,8 @@ +from datetime import date +from typing import Literal + +EASTER_JULIAN: Literal[1] +EASTER_ORTHODOX: Literal[2] +EASTER_WESTERN: Literal[3] + +def easter(year: int, method: Literal[1, 2, 3] = 3) -> date: ... diff --git a/lib/dateutil-stubs/parser/__init__.pyi b/lib/dateutil-stubs/parser/__init__.pyi new file mode 100644 index 00000000..c375ff15 --- /dev/null +++ b/lib/dateutil-stubs/parser/__init__.pyi @@ -0,0 +1,67 @@ +from collections.abc import Callable, Mapping +from datetime import datetime, tzinfo +from typing import IO, Any +from typing_extensions import TypeAlias + +from .isoparser import isoparse as isoparse, isoparser as isoparser + +_FileOrStr: TypeAlias = bytes | str | IO[str] | IO[Any] +_TzData: TypeAlias = tzinfo | int | str | None +_TzInfo: TypeAlias = Mapping[str, _TzData] | Callable[[str, int], _TzData] + +class parserinfo: + JUMP: list[str] + WEEKDAYS: list[tuple[str, ...]] + MONTHS: list[tuple[str, ...]] + HMS: list[tuple[str, str, str]] + AMPM: list[tuple[str, str]] + UTCZONE: list[str] + PERTAIN: list[str] + TZOFFSET: dict[str, int] + def __init__(self, dayfirst: bool = False, yearfirst: bool = False) -> None: ... + def jump(self, name: str) -> bool: ... + def weekday(self, name: str) -> int | None: ... + def month(self, name: str) -> int | None: ... + def hms(self, name: str) -> int | None: ... + def ampm(self, name: str) -> int | None: ... + def pertain(self, name: str) -> bool: ... + def utczone(self, name: str) -> bool: ... + def tzoffset(self, name: str) -> int | None: ... + def convertyear(self, year: int) -> int: ... + def validate(self, res: datetime) -> bool: ... + +class parser: + def __init__(self, info: parserinfo | None = None) -> None: ... + def parse( + self, + timestr: _FileOrStr, + default: datetime | None = None, + ignoretz: bool = False, + tzinfos: _TzInfo | None = None, + *, + dayfirst: bool | None = ..., + yearfirst: bool | None = ..., + fuzzy: bool = ..., + fuzzy_with_tokens: bool = ..., + ) -> datetime: ... + +DEFAULTPARSER: parser + +def parse( + timestr: _FileOrStr, + parserinfo: parserinfo | None = None, + *, + dayfirst: bool | None = ..., + yearfirst: bool | None = ..., + ignoretz: bool = ..., + fuzzy: bool = ..., + fuzzy_with_tokens: bool = ..., + default: datetime | None = ..., + tzinfos: _TzInfo | None = ..., +) -> datetime: ... + +class _tzparser: ... + +DEFAULTTZPARSER: _tzparser + +class ParserError(ValueError): ... diff --git a/lib/dateutil-stubs/parser/isoparser.pyi b/lib/dateutil-stubs/parser/isoparser.pyi new file mode 100644 index 00000000..fc5e46d9 --- /dev/null +++ b/lib/dateutil-stubs/parser/isoparser.pyi @@ -0,0 +1,15 @@ +from _typeshed import SupportsRead +from datetime import date, datetime, time, tzinfo +from typing_extensions import TypeAlias + +_Readable: TypeAlias = SupportsRead[str | bytes] +_TakesAscii: TypeAlias = str | bytes | _Readable + +class isoparser: + def __init__(self, sep: str | bytes | None = None): ... + def isoparse(self, dt_str: _TakesAscii) -> datetime: ... + def parse_isodate(self, datestr: _TakesAscii) -> date: ... + def parse_isotime(self, timestr: _TakesAscii) -> time: ... + def parse_tzstr(self, tzstr: _TakesAscii, zero_as_utc: bool = True) -> tzinfo: ... + +def isoparse(dt_str: _TakesAscii) -> datetime: ... diff --git a/lib/dateutil-stubs/py.typed b/lib/dateutil-stubs/py.typed new file mode 100644 index 00000000..b648ac92 --- /dev/null +++ b/lib/dateutil-stubs/py.typed @@ -0,0 +1 @@ +partial diff --git a/lib/dateutil-stubs/relativedelta.pyi b/lib/dateutil-stubs/relativedelta.pyi new file mode 100644 index 00000000..394713db --- /dev/null +++ b/lib/dateutil-stubs/relativedelta.pyi @@ -0,0 +1,97 @@ +from datetime import date, timedelta +from typing import SupportsFloat, TypeVar, overload +from typing_extensions import Self, TypeAlias + +# See #9817 for why we reexport this here +from ._common import weekday as weekday + +_DateT = TypeVar("_DateT", bound=date) +# Work around attribute and type having the same name. +_Weekday: TypeAlias = weekday + +MO: weekday +TU: weekday +WE: weekday +TH: weekday +FR: weekday +SA: weekday +SU: weekday + +class relativedelta: + years: int + months: int + days: int + leapdays: int + hours: int + minutes: int + seconds: int + microseconds: int + year: int | None + month: int | None + weekday: _Weekday | None + day: int | None + hour: int | None + minute: int | None + second: int | None + microsecond: int | None + def __init__( + self, + dt1: date | None = None, + dt2: date | None = None, + years: int | None = 0, + months: int | None = 0, + days: int | None = 0, + leapdays: int | None = 0, + weeks: int | None = 0, + hours: int | None = 0, + minutes: int | None = 0, + seconds: int | None = 0, + microseconds: int | None = 0, + year: int | None = None, + month: int | None = None, + day: int | None = None, + weekday: int | _Weekday | None = None, + yearday: int | None = None, + nlyearday: int | None = None, + hour: int | None = None, + minute: int | None = None, + second: int | None = None, + microsecond: int | None = None, + ) -> None: ... + @property + def weeks(self) -> int: ... + @weeks.setter + def weeks(self, value: int) -> None: ... + def normalized(self) -> Self: ... + # TODO: use Union when mypy will handle it properly in overloaded operator + # methods (#2129, #1442, #1264 in mypy) + @overload + def __add__(self, other: relativedelta) -> Self: ... + @overload + def __add__(self, other: timedelta) -> Self: ... + @overload + def __add__(self, other: _DateT) -> _DateT: ... + @overload + def __radd__(self, other: relativedelta) -> Self: ... + @overload + def __radd__(self, other: timedelta) -> Self: ... + @overload + def __radd__(self, other: _DateT) -> _DateT: ... + @overload + def __rsub__(self, other: relativedelta) -> Self: ... + @overload + def __rsub__(self, other: timedelta) -> Self: ... + @overload + def __rsub__(self, other: _DateT) -> _DateT: ... + def __sub__(self, other: relativedelta) -> Self: ... + def __neg__(self) -> Self: ... + def __bool__(self) -> bool: ... + def __nonzero__(self) -> bool: ... + def __mul__(self, other: SupportsFloat) -> Self: ... + def __rmul__(self, other: SupportsFloat) -> Self: ... + def __eq__(self, other: object) -> bool: ... + def __ne__(self, other: object) -> bool: ... + def __div__(self, other: SupportsFloat) -> Self: ... + def __truediv__(self, other: SupportsFloat) -> Self: ... + def __abs__(self) -> Self: ... + def __hash__(self) -> int: ... diff --git a/lib/dateutil-stubs/rrule.pyi b/lib/dateutil-stubs/rrule.pyi new file mode 100644 index 00000000..f091cfbd --- /dev/null +++ b/lib/dateutil-stubs/rrule.pyi @@ -0,0 +1,111 @@ +import datetime +from _typeshed import Incomplete +from collections.abc import Iterable, Iterator, Sequence +from typing_extensions import TypeAlias + +from ._common import weekday as weekdaybase + +YEARLY: int +MONTHLY: int +WEEKLY: int +DAILY: int +HOURLY: int +MINUTELY: int +SECONDLY: int + +class weekday(weekdaybase): ... + +weekdays: tuple[weekday, weekday, weekday, weekday, weekday, weekday, weekday] +MO: weekday +TU: weekday +WE: weekday +TH: weekday +FR: weekday +SA: weekday +SU: weekday + +class rrulebase: + def __init__(self, cache: bool = False) -> None: ... + def __iter__(self) -> Iterator[datetime.datetime]: ... + def __getitem__(self, item): ... + def __contains__(self, item): ... + def count(self): ... + def before(self, dt, inc: bool = False): ... + def after(self, dt, inc: bool = False): ... + def xafter(self, dt, count: Incomplete | None = None, inc: bool = False): ... + def between(self, after, before, inc: bool = False, count: int = 1): ... + +class rrule(rrulebase): + def __init__( + self, + freq, + dtstart: datetime.date | None = None, + interval: int = 1, + wkst: weekday | int | None = None, + count: int | None = None, + until: datetime.date | int | None = None, + bysetpos: int | Iterable[int] | None = None, + bymonth: int | Iterable[int] | None = None, + bymonthday: int | Iterable[int] | None = None, + byyearday: int | Iterable[int] | None = None, + byeaster: int | Iterable[int] | None = None, + byweekno: int | Iterable[int] | None = None, + byweekday: int | weekday | Iterable[int] | Iterable[weekday] | None = None, + byhour: int | Iterable[int] | None = None, + byminute: int | Iterable[int] | None = None, + bysecond: int | Iterable[int] | None = None, + cache: bool = False, + ) -> None: ... + def replace(self, **kwargs): ... + +_RRule: TypeAlias = rrule + +class _iterinfo: + rrule: _RRule + def __init__(self, rrule: _RRule) -> None: ... + yearlen: int | None + nextyearlen: int | None + yearordinal: int | None + yearweekday: int | None + mmask: Sequence[int] | None + mdaymask: Sequence[int] | None + nmdaymask: Sequence[int] | None + wdaymask: Sequence[int] | None + mrange: Sequence[int] | None + wnomask: Sequence[int] | None + nwdaymask: Sequence[int] | None + eastermask: Sequence[int] | None + lastyear: int | None + lastmonth: int | None + def rebuild(self, year, month): ... + def ydayset(self, year, month, day): ... + def mdayset(self, year, month, day): ... + def wdayset(self, year, month, day): ... + def ddayset(self, year, month, day): ... + def htimeset(self, hour, minute, second): ... + def mtimeset(self, hour, minute, second): ... + def stimeset(self, hour, minute, second): ... + +class rruleset(rrulebase): + class _genitem: + dt: Incomplete + genlist: list[Incomplete] + gen: Incomplete + def __init__(self, genlist, gen) -> None: ... + def __next__(self) -> None: ... + next = __next__ + def __lt__(self, other) -> bool: ... + def __gt__(self, other) -> bool: ... + def __eq__(self, other) -> bool: ... + def __ne__(self, other) -> bool: ... + + def __init__(self, cache: bool = False) -> None: ... + def rrule(self, rrule: _RRule): ... + def rdate(self, rdate): ... + def exrule(self, exrule): ... + def exdate(self, exdate): ... + +class _rrulestr: + def __call__(self, s, **kwargs) -> rrule | rruleset: ... + +rrulestr: _rrulestr diff --git a/lib/dateutil-stubs/tz/__init__.pyi b/lib/dateutil-stubs/tz/__init__.pyi new file mode 100644 index 00000000..334ca482 --- /dev/null +++ b/lib/dateutil-stubs/tz/__init__.pyi @@ -0,0 +1,15 @@ +from .tz import ( + datetime_ambiguous as datetime_ambiguous, + datetime_exists as datetime_exists, + gettz as gettz, + resolve_imaginary as resolve_imaginary, + tzfile as tzfile, + tzical as tzical, + tzlocal as tzlocal, + tzoffset as tzoffset, + tzrange as tzrange, + tzstr as tzstr, + tzutc as tzutc, +) + +UTC: tzutc diff --git a/lib/dateutil-stubs/tz/_common.pyi b/lib/dateutil-stubs/tz/_common.pyi new file mode 100644 index 00000000..4e64822f --- /dev/null +++ b/lib/dateutil-stubs/tz/_common.pyi @@ -0,0 +1,28 @@ +import abc +from datetime import datetime, timedelta, tzinfo +from typing import ClassVar + +def tzname_in_python2(namefunc): ... +def enfold(dt: datetime, fold: int = 1): ... + +class _DatetimeWithFold(datetime): + @property + def fold(self): ... + +# Doesn't actually have ABCMeta as the metaclass at runtime, +# but mypy complains if we don't have it in the stub. +# See discussion in #8908 +class _tzinfo(tzinfo, metaclass=abc.ABCMeta): + def is_ambiguous(self, dt: datetime) -> bool: ... + def fromutc(self, dt: datetime) -> datetime: ... + +class tzrangebase(_tzinfo): + def __init__(self) -> None: ... + def utcoffset(self, dt: datetime | None) -> timedelta | None: ... + def dst(self, dt: datetime | None) -> timedelta | None: ... + def tzname(self, dt: datetime | None) -> str: ... + def fromutc(self, dt: datetime) -> datetime: ... + def is_ambiguous(self, dt: datetime) -> bool: ... + __hash__: ClassVar[None] # type: ignore[assignment] + def __ne__(self, other): ... + __reduce__ = object.__reduce__ diff --git a/lib/dateutil-stubs/tz/tz.pyi b/lib/dateutil-stubs/tz/tz.pyi new file mode 100644 index 00000000..6addba65 --- /dev/null +++ b/lib/dateutil-stubs/tz/tz.pyi @@ -0,0 +1,115 @@ +import datetime +from _typeshed import Incomplete +from typing import ClassVar, Literal, Protocol, TypeVar + +from ..relativedelta import relativedelta +from ._common import _tzinfo as _tzinfo, enfold as enfold, tzname_in_python2 as tzname_in_python2, tzrangebase as tzrangebase + +_DT = TypeVar("_DT", bound=datetime.datetime) + +ZERO: datetime.timedelta +EPOCH: datetime.datetime +EPOCHORDINAL: int + +class tzutc(datetime.tzinfo): + def utcoffset(self, dt: datetime.datetime | None) -> datetime.timedelta | None: ... + def dst(self, dt: datetime.datetime | None) -> datetime.timedelta | None: ... + def tzname(self, dt: datetime.datetime | None) -> str: ... + def is_ambiguous(self, dt: datetime.datetime | None) -> bool: ... + def fromutc(self, dt: _DT) -> _DT: ... + def __eq__(self, other): ... + __hash__: ClassVar[None] # type: ignore[assignment] + def __ne__(self, other): ... + __reduce__ = object.__reduce__ + +class tzoffset(datetime.tzinfo): + def __init__(self, name, offset) -> None: ... + def utcoffset(self, dt: datetime.datetime | None) -> datetime.timedelta | None: ... + def dst(self, dt: datetime.datetime | None) -> datetime.timedelta | None: ... + def is_ambiguous(self, dt: datetime.datetime | None) -> bool: ... + def tzname(self, dt: datetime.datetime | None) -> str: ... + def fromutc(self, dt: _DT) -> _DT: ... + def __eq__(self, other): ... + __hash__: ClassVar[None] # type: ignore[assignment] + def __ne__(self, other): ... + __reduce__ = object.__reduce__ + @classmethod + def instance(cls, name, offset) -> tzoffset: ... + +class tzlocal(_tzinfo): + def __init__(self) -> None: ... + def utcoffset(self, dt: datetime.datetime | None) -> datetime.timedelta | None: ... + def dst(self, dt: datetime.datetime | None) -> datetime.timedelta | None: ... + def tzname(self, dt: datetime.datetime | None) -> str: ... + def is_ambiguous(self, dt: datetime.datetime | None) -> bool: ... + def __eq__(self, other): ... + __hash__: ClassVar[None] # type: ignore[assignment] + def __ne__(self, other): ... + __reduce__ = object.__reduce__ + +class _ttinfo: + def __init__(self) -> None: ... + def __eq__(self, other): ... + __hash__: ClassVar[None] # type: ignore[assignment] + def __ne__(self, other): ... + +class _TZFileReader(Protocol): + # optional attribute: + # name: str + def read(self, size: int, /) -> bytes: ... + def seek(self, target: int, whence: Literal[1], /) -> object: ... + +class tzfile(_tzinfo): + def __init__(self, fileobj: str | _TZFileReader, filename: str | None = None) -> None: ... + def is_ambiguous(self, dt: datetime.datetime | None, idx: int | None = None) -> bool: ... + def utcoffset(self, dt: datetime.datetime | None) -> datetime.timedelta | None: ... + def dst(self, dt: datetime.datetime | None) -> datetime.timedelta | None: ... + def tzname(self, dt: datetime.datetime | None) -> str: ... + def __eq__(self, other): ... + __hash__: ClassVar[None] # type: ignore[assignment] + def __ne__(self, other): ... + def __reduce__(self): ... + def __reduce_ex__(self, protocol): ... + +class tzrange(tzrangebase): + hasdst: bool + def __init__( + self, + stdabbr: str, + stdoffset: int | datetime.timedelta | None = None, + dstabbr: str | None = None, + dstoffset: int | datetime.timedelta | None = None, + start: relativedelta | None = None, + end: relativedelta | None = None, + ) -> None: ... + def transitions(self, year: int) -> tuple[datetime.datetime, datetime.datetime]: ... + def __eq__(self, other): ... + +class tzstr(tzrange): + hasdst: bool + def __init__(self, s: str, posix_offset: bool = False) -> None: ... + @classmethod + def instance(cls, name, offset) -> tzoffset: ... + +class _ICalReader(Protocol): + # optional attribute: + # name: str + def read(self) -> str: ... + +class tzical: + def __init__(self, fileobj: str | _ICalReader) -> None: ... + def keys(self): ... + def get(self, tzid: Incomplete | None = None): ... + +TZFILES: list[str] +TZPATHS: list[str] + +def datetime_exists(dt: datetime.datetime, tz: datetime.tzinfo | None = None) -> bool: ... +def datetime_ambiguous(dt: datetime.datetime, tz: datetime.tzinfo | None = None) -> bool: ... +def resolve_imaginary(dt: datetime.datetime) -> datetime.datetime: ... + +class _GetTZ: + def __call__(self, name: str | None = ...) -> datetime.tzinfo | None: ... + def nocache(self, name: str | None) -> datetime.tzinfo | None: ... + +gettz: _GetTZ diff --git a/lib/dateutil-stubs/utils.pyi b/lib/dateutil-stubs/utils.pyi new file mode 100644 index 00000000..8c9f5e44 --- /dev/null +++ b/lib/dateutil-stubs/utils.pyi @@ -0,0 +1,5 @@ +from datetime import datetime, timedelta, tzinfo + +def default_tzinfo(dt: datetime, tzinfo: tzinfo) -> datetime: ... +def today(tzinfo: tzinfo | None = None) -> datetime: ... +def within_delta(dt1: datetime, dt2: datetime, delta: timedelta) -> bool: ... diff --git a/lib/dateutil-stubs/zoneinfo/__init__.pyi b/lib/dateutil-stubs/zoneinfo/__init__.pyi new file mode 100644 index 00000000..7bd2845a --- /dev/null +++ b/lib/dateutil-stubs/zoneinfo/__init__.pyi @@ -0,0 +1,17 @@ +from _typeshed import Incomplete +from typing import IO +from typing_extensions import TypeAlias + +__all__ = ["get_zonefile_instance", "gettz", "gettz_db_metadata"] + +_MetadataType: TypeAlias = dict[str, Incomplete] + +class ZoneInfoFile: + zones: dict[Incomplete, Incomplete] + metadata: _MetadataType | None + def __init__(self, zonefile_stream: IO[bytes] | None = None) -> None: ... + def get(self, name, default: Incomplete | None = None): ... + +def get_zonefile_instance(new_instance: bool = False) -> ZoneInfoFile: ... +def gettz(name): ... +def gettz_db_metadata() -> _MetadataType: ... diff --git a/lib/dateutil-stubs/zoneinfo/rebuild.pyi b/lib/dateutil-stubs/zoneinfo/rebuild.pyi new file mode 100644 index 00000000..e3845926 --- /dev/null +++ b/lib/dateutil-stubs/zoneinfo/rebuild.pyi @@ -0,0 +1,11 @@ +from _typeshed import Incomplete, StrOrBytesPath +from collections.abc import Sequence +from tarfile import TarInfo + +def rebuild( + filename: StrOrBytesPath, + tag: Incomplete | None = None, + format: str = "gz", + zonegroups: Sequence[str | TarInfo] = [], + metadata: Incomplete | None = None, +) -> None: ... diff --git a/lib/dateutil/__init__.py b/lib/dateutil/__init__.py index 0defb82e..a2c19c06 100644 --- a/lib/dateutil/__init__.py +++ b/lib/dateutil/__init__.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +import sys + try: from ._version import version as __version__ except ImportError: @@ -6,3 +8,17 @@ except ImportError: __all__ = ['easter', 'parser', 'relativedelta', 'rrule', 'tz', 'utils', 'zoneinfo'] + +def __getattr__(name): + import importlib + + if name in __all__: + return importlib.import_module("." + name, __name__) + raise AttributeError( + "module {!r} has not attribute {!r}".format(__name__, name) + ) + + +def __dir__(): + # __dir__ should include all the lazy-importable modules as well. + return [x for x in globals() if x not in sys.modules] + __all__ diff --git a/lib/dateutil/_version.py b/lib/dateutil/_version.py index b723056a..ddda9809 100644 --- a/lib/dateutil/_version.py +++ b/lib/dateutil/_version.py @@ -1,5 +1,4 @@ -# coding: utf-8 # file generated by setuptools_scm # don't change, don't track in version control -version = '2.8.2' -version_tuple = (2, 8, 2) +__version__ = version = '2.9.0.post0' +__version_tuple__ = version_tuple = (2, 9, 0) diff --git a/lib/dateutil/parser/isoparser.py b/lib/dateutil/parser/isoparser.py index 5d7bee38..7060087d 100644 --- a/lib/dateutil/parser/isoparser.py +++ b/lib/dateutil/parser/isoparser.py @@ -72,7 +72,7 @@ class isoparser(object): Common: - ``YYYY`` - - ``YYYY-MM`` or ``YYYYMM`` + - ``YYYY-MM`` - ``YYYY-MM-DD`` or ``YYYYMMDD`` Uncommon: diff --git a/lib/dateutil/relativedelta.py b/lib/dateutil/relativedelta.py index a9e85f7e..cd323a54 100644 --- a/lib/dateutil/relativedelta.py +++ b/lib/dateutil/relativedelta.py @@ -48,7 +48,7 @@ class relativedelta(object): the corresponding arithmetic operation on the original datetime value with the information in the relativedelta. - weekday: + weekday: One of the weekday instances (MO, TU, etc) available in the relativedelta module. These instances may receive a parameter N, specifying the Nth weekday, which could be positive or negative diff --git a/lib/dateutil/rrule.py b/lib/dateutil/rrule.py index b3203393..571a0d2b 100644 --- a/lib/dateutil/rrule.py +++ b/lib/dateutil/rrule.py @@ -182,7 +182,7 @@ class rrulebase(object): # __len__() introduces a large performance penalty. def count(self): """ Returns the number of recurrences in this set. It will have go - trough the whole recurrence, if this hasn't been done before. """ + through the whole recurrence, if this hasn't been done before. """ if self._len is None: for x in self: pass diff --git a/lib/dateutil/tz/tz.py b/lib/dateutil/tz/tz.py index c67f56d4..61759144 100644 --- a/lib/dateutil/tz/tz.py +++ b/lib/dateutil/tz/tz.py @@ -34,7 +34,7 @@ except ImportError: from warnings import warn ZERO = datetime.timedelta(0) -EPOCH = datetime.datetime.utcfromtimestamp(0) +EPOCH = datetime.datetime(1970, 1, 1, 0, 0) EPOCHORDINAL = EPOCH.toordinal() diff --git a/lib/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz b/lib/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz index 524c48e1..1461f8c8 100644 Binary files a/lib/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz and b/lib/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz differ diff --git a/lib/distro/distro.py b/lib/distro/distro.py index 89e18680..78ccdfa4 100644 --- a/lib/distro/distro.py +++ b/lib/distro/distro.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# Copyright 2015,2016,2017 Nir Cohen +# Copyright 2015-2021 Nir Cohen # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -55,7 +55,7 @@ except ImportError: # Python 3.7 TypedDict = dict -__version__ = "1.8.0" +__version__ = "1.9.0" class VersionDict(TypedDict): @@ -125,6 +125,7 @@ _DISTRO_RELEASE_BASENAME_PATTERN = re.compile(r"(\w+)[-_](release|version)$") # Base file names to be looked up for if _UNIXCONFDIR is not readable. _DISTRO_RELEASE_BASENAMES = [ "SuSE-release", + "altlinux-release", "arch-release", "base-release", "centos-release", @@ -151,6 +152,8 @@ _DISTRO_RELEASE_IGNORE_BASENAMES = ( "system-release", "plesk-release", "iredmail-release", + "board-release", + "ec2_version", ) @@ -243,6 +246,7 @@ def id() -> str: "rocky" Rocky Linux "aix" AIX "guix" Guix System + "altlinux" ALT Linux ============== ========================================= If you have a need to get distros for reliable IDs added into this set, @@ -991,10 +995,10 @@ class LinuxDistribution: For details, see :func:`distro.info`. """ - return dict( + return InfoDict( id=self.id(), version=self.version(pretty, best), - version_parts=dict( + version_parts=VersionDict( major=self.major_version(best), minor=self.minor_version(best), build_number=self.build_number(best), diff --git a/lib/mako/__init__.py b/lib/mako/__init__.py index d7339219..d022cf82 100644 --- a/lib/mako/__init__.py +++ b/lib/mako/__init__.py @@ -1,8 +1,8 @@ # mako/__init__.py -# Copyright 2006-2022 the Mako authors and contributors +# Copyright 2006-2024 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php -__version__ = "1.2.4" +__version__ = "1.3.2" diff --git a/lib/mako/_ast_util.py b/lib/mako/_ast_util.py index 95742835..63b8f228 100644 --- a/lib/mako/_ast_util.py +++ b/lib/mako/_ast_util.py @@ -1,5 +1,5 @@ # mako/_ast_util.py -# Copyright 2006-2022 the Mako authors and contributors +# Copyright 2006-2024 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php diff --git a/lib/mako/ast.py b/lib/mako/ast.py index a3f3ee3e..e59df3eb 100644 --- a/lib/mako/ast.py +++ b/lib/mako/ast.py @@ -1,5 +1,5 @@ # mako/ast.py -# Copyright 2006-2022 the Mako authors and contributors +# Copyright 2006-2024 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php diff --git a/lib/mako/cache.py b/lib/mako/cache.py index db21bb3e..bad25257 100644 --- a/lib/mako/cache.py +++ b/lib/mako/cache.py @@ -1,5 +1,5 @@ # mako/cache.py -# Copyright 2006-2022 the Mako authors and contributors +# Copyright 2006-2024 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php diff --git a/lib/mako/cmd.py b/lib/mako/cmd.py index 93a6733f..3858a127 100755 --- a/lib/mako/cmd.py +++ b/lib/mako/cmd.py @@ -1,5 +1,5 @@ # mako/cmd.py -# Copyright 2006-2022 the Mako authors and contributors +# Copyright 2006-2024 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php @@ -25,7 +25,6 @@ def _exit(): def cmdline(argv=None): - parser = ArgumentParser() parser.add_argument( "--var", diff --git a/lib/mako/codegen.py b/lib/mako/codegen.py index d1d2c20a..ce6f83aa 100644 --- a/lib/mako/codegen.py +++ b/lib/mako/codegen.py @@ -1,5 +1,5 @@ # mako/codegen.py -# Copyright 2006-2022 the Mako authors and contributors +# Copyright 2006-2024 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php @@ -816,7 +816,6 @@ class _GenerateRenderMethod: ) or len(self.compiler.default_filters) ): - s = self.create_filter_callable( node.escapes_code.args, "%s" % node.text, True ) @@ -1181,7 +1180,6 @@ class _Identifiers: def visitBlockTag(self, node): if node is not self.node and not node.is_anonymous: - if isinstance(self.node, parsetree.DefTag): raise exceptions.CompileException( "Named block '%s' not allowed inside of def '%s'" diff --git a/lib/mako/compat.py b/lib/mako/compat.py index 48df30d6..18322159 100644 --- a/lib/mako/compat.py +++ b/lib/mako/compat.py @@ -1,17 +1,17 @@ # mako/compat.py -# Copyright 2006-2022 the Mako authors and contributors +# Copyright 2006-2024 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php import collections +from importlib import metadata as importlib_metadata from importlib import util import inspect import sys win32 = sys.platform.startswith("win") pypy = hasattr(sys, "pypy_version_info") -py38 = sys.version_info >= (3, 8) ArgSpec = collections.namedtuple( "ArgSpec", ["args", "varargs", "keywords", "defaults"] @@ -62,12 +62,6 @@ def exception_name(exc): return exc.__class__.__name__ -if py38: - from importlib import metadata as importlib_metadata -else: - import importlib_metadata # noqa - - def importlib_metadata_get(group): ep = importlib_metadata.entry_points() if hasattr(ep, "select"): diff --git a/lib/mako/exceptions.py b/lib/mako/exceptions.py index 31c695fd..ca225677 100644 --- a/lib/mako/exceptions.py +++ b/lib/mako/exceptions.py @@ -1,5 +1,5 @@ # mako/exceptions.py -# Copyright 2006-2022 the Mako authors and contributors +# Copyright 2006-2024 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php diff --git a/lib/mako/ext/autohandler.py b/lib/mako/ext/autohandler.py index 5bcfc285..dff9b6a8 100644 --- a/lib/mako/ext/autohandler.py +++ b/lib/mako/ext/autohandler.py @@ -1,5 +1,5 @@ # ext/autohandler.py -# Copyright 2006-2022 the Mako authors and contributors +# Copyright 2006-2024 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php diff --git a/lib/mako/ext/babelplugin.py b/lib/mako/ext/babelplugin.py index 907d0b80..cdb37cdf 100644 --- a/lib/mako/ext/babelplugin.py +++ b/lib/mako/ext/babelplugin.py @@ -1,5 +1,5 @@ # ext/babelplugin.py -# Copyright 2006-2022 the Mako authors and contributors +# Copyright 2006-2024 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php diff --git a/lib/mako/ext/beaker_cache.py b/lib/mako/ext/beaker_cache.py index 9aa35b06..590fbd01 100644 --- a/lib/mako/ext/beaker_cache.py +++ b/lib/mako/ext/beaker_cache.py @@ -1,5 +1,5 @@ # ext/beaker_cache.py -# Copyright 2006-2022 the Mako authors and contributors +# Copyright 2006-2024 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php diff --git a/lib/mako/ext/extract.py b/lib/mako/ext/extract.py index 9d33ee18..d03789db 100644 --- a/lib/mako/ext/extract.py +++ b/lib/mako/ext/extract.py @@ -1,5 +1,5 @@ # ext/extract.py -# Copyright 2006-2022 the Mako authors and contributors +# Copyright 2006-2024 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php diff --git a/lib/mako/ext/linguaplugin.py b/lib/mako/ext/linguaplugin.py index efb04c70..22077e66 100644 --- a/lib/mako/ext/linguaplugin.py +++ b/lib/mako/ext/linguaplugin.py @@ -1,5 +1,5 @@ # ext/linguaplugin.py -# Copyright 2006-2022 the Mako authors and contributors +# Copyright 2006-2024 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php diff --git a/lib/mako/ext/preprocessors.py b/lib/mako/ext/preprocessors.py index 80403ecd..6d1aaf5d 100644 --- a/lib/mako/ext/preprocessors.py +++ b/lib/mako/ext/preprocessors.py @@ -1,5 +1,5 @@ # ext/preprocessors.py -# Copyright 2006-2022 the Mako authors and contributors +# Copyright 2006-2024 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php diff --git a/lib/mako/ext/pygmentplugin.py b/lib/mako/ext/pygmentplugin.py index 9acbf4cd..f422623d 100644 --- a/lib/mako/ext/pygmentplugin.py +++ b/lib/mako/ext/pygmentplugin.py @@ -1,5 +1,5 @@ # ext/pygmentplugin.py -# Copyright 2006-2022 the Mako authors and contributors +# Copyright 2006-2024 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php diff --git a/lib/mako/ext/turbogears.py b/lib/mako/ext/turbogears.py index 2ebf0746..2a3ec7d7 100644 --- a/lib/mako/ext/turbogears.py +++ b/lib/mako/ext/turbogears.py @@ -1,5 +1,5 @@ # ext/turbogears.py -# Copyright 2006-2022 the Mako authors and contributors +# Copyright 2006-2024 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php diff --git a/lib/mako/filters.py b/lib/mako/filters.py index af202f3f..2a4b4386 100644 --- a/lib/mako/filters.py +++ b/lib/mako/filters.py @@ -1,5 +1,5 @@ # mako/filters.py -# Copyright 2006-2022 the Mako authors and contributors +# Copyright 2006-2024 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php diff --git a/lib/mako/lexer.py b/lib/mako/lexer.py index 75182f85..9d1f5f30 100644 --- a/lib/mako/lexer.py +++ b/lib/mako/lexer.py @@ -1,5 +1,5 @@ # mako/lexer.py -# Copyright 2006-2022 the Mako authors and contributors +# Copyright 2006-2024 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php @@ -247,6 +247,8 @@ class Lexer: continue if self.match_python_block(): continue + if self.match_percent(): + continue if self.match_text(): continue @@ -352,14 +354,24 @@ class Lexer: else: return True + def match_percent(self): + match = self.match(r"(?<=^)(\s*)%%(%*)", re.M) + if match: + self.append_node( + parsetree.Text, match.group(1) + "%" + match.group(2) + ) + return True + else: + return False + def match_text(self): match = self.match( r""" (.*?) # anything, followed by: ( - (?<=\n)(?=[ \t]*(?=%|\#\#)) # an eval or line-based - # comment preceded by a - # consumed newline and whitespace + (?<=\n)(?=[ \t]*(?=%|\#\#)) # an eval or line-based + # comment, preceded by a + # consumed newline and whitespace | (?=\${) # an expression | diff --git a/lib/mako/lookup.py b/lib/mako/lookup.py index f7410ce3..d916db77 100644 --- a/lib/mako/lookup.py +++ b/lib/mako/lookup.py @@ -1,5 +1,5 @@ # mako/lookup.py -# Copyright 2006-2022 the Mako authors and contributors +# Copyright 2006-2024 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php @@ -178,7 +178,6 @@ class TemplateLookup(TemplateCollection): lexer_cls=None, include_error_handler=None, ): - self.directories = [ posixpath.normpath(d) for d in util.to_list(directories, ()) ] diff --git a/lib/mako/parsetree.py b/lib/mako/parsetree.py index c5a12d1d..04686ece 100644 --- a/lib/mako/parsetree.py +++ b/lib/mako/parsetree.py @@ -1,5 +1,5 @@ # mako/parsetree.py -# Copyright 2006-2022 the Mako authors and contributors +# Copyright 2006-2024 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php diff --git a/lib/mako/pygen.py b/lib/mako/pygen.py index 57c69793..820e101d 100644 --- a/lib/mako/pygen.py +++ b/lib/mako/pygen.py @@ -1,5 +1,5 @@ # mako/pygen.py -# Copyright 2006-2022 the Mako authors and contributors +# Copyright 2006-2024 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php diff --git a/lib/mako/pyparser.py b/lib/mako/pyparser.py index d9b090c6..9b63dc33 100644 --- a/lib/mako/pyparser.py +++ b/lib/mako/pyparser.py @@ -1,5 +1,5 @@ # mako/pyparser.py -# Copyright 2006-2022 the Mako authors and contributors +# Copyright 2006-2024 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php @@ -64,7 +64,6 @@ class FindIdentifiers(_ast_util.NodeVisitor): self._add_declared(node.name) def visit_Assign(self, node): - # flip around the visiting of Assign so the expression gets # evaluated first, in the case of a clause like "x=x+5" (x # is undeclared) @@ -99,7 +98,6 @@ class FindIdentifiers(_ast_util.NodeVisitor): yield arg def _visit_function(self, node, islambda): - # push function state onto stack. dont log any more # identifiers as "declared" until outside of the function, # but keep logging identifiers as "undeclared". track @@ -122,7 +120,6 @@ class FindIdentifiers(_ast_util.NodeVisitor): self.local_ident_stack = local_ident_stack def visit_For(self, node): - # flip around visit self.visit(node.iter) diff --git a/lib/mako/runtime.py b/lib/mako/runtime.py index 6d7fa684..23401b70 100644 --- a/lib/mako/runtime.py +++ b/lib/mako/runtime.py @@ -530,7 +530,7 @@ class Namespace: def _populate(self, d, l): for ident in l: if ident == "*": - for (k, v) in self._get_star(): + for k, v in self._get_star(): d[k] = v else: d[ident] = getattr(self, ident) diff --git a/lib/mako/template.py b/lib/mako/template.py index 8c70731d..e2771727 100644 --- a/lib/mako/template.py +++ b/lib/mako/template.py @@ -1,5 +1,5 @@ # mako/template.py -# Copyright 2006-2022 the Mako authors and contributors +# Copyright 2006-2024 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php @@ -26,7 +26,6 @@ from mako.lexer import Lexer class Template: - r"""Represents a compiled template. :class:`.Template` includes a reference to the original diff --git a/lib/mako/testing/assertions.py b/lib/mako/testing/assertions.py index 14ea6352..22221cd2 100644 --- a/lib/mako/testing/assertions.py +++ b/lib/mako/testing/assertions.py @@ -103,7 +103,6 @@ def _assert_raises( check_context=False, cause_cls=None, ): - with _expect_raises(except_cls, msg, check_context, cause_cls) as ec: callable_(*args, **kwargs) return ec.error diff --git a/lib/mako/testing/helpers.py b/lib/mako/testing/helpers.py index 77cca367..5ae9d38d 100644 --- a/lib/mako/testing/helpers.py +++ b/lib/mako/testing/helpers.py @@ -19,6 +19,10 @@ def result_lines(result): ] +def result_raw_lines(result): + return [x for x in re.split(r"\r?\n", result) if x.strip() != ""] + + def make_path( filespec: Union[Path, str], make_absolute: bool = True, diff --git a/lib/mako/util.py b/lib/mako/util.py index 5fb683b4..91188cac 100644 --- a/lib/mako/util.py +++ b/lib/mako/util.py @@ -1,5 +1,5 @@ # mako/util.py -# Copyright 2006-2022 the Mako authors and contributors +# Copyright 2006-2024 the Mako authors and contributors # # This module is part of Mako and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php diff --git a/lib/markupsafe/__init__.py b/lib/markupsafe/__init__.py index 21d31960..b40f24c6 100644 --- a/lib/markupsafe/__init__.py +++ b/lib/markupsafe/__init__.py @@ -1,5 +1,4 @@ import functools -import re import string import sys import typing as t @@ -14,10 +13,7 @@ if t.TYPE_CHECKING: _P = te.ParamSpec("_P") -__version__ = "2.1.3" - -_strip_comments_re = re.compile(r"", re.DOTALL) -_strip_tags_re = re.compile(r"<.*?>", re.DOTALL) +__version__ = "2.1.5" def _simple_escaping_wrapper(func: "t.Callable[_P, str]") -> "t.Callable[_P, Markup]": @@ -162,9 +158,41 @@ class Markup(str): >>> Markup("Main »\tAbout").striptags() 'Main » About' """ - # Use two regexes to avoid ambiguous matches. - value = _strip_comments_re.sub("", self) - value = _strip_tags_re.sub("", value) + value = str(self) + + # Look for comments then tags separately. Otherwise, a comment that + # contains a tag would end early, leaving some of the comment behind. + + while True: + # keep finding comment start marks + start = value.find("", start) + + if end == -1: + break + + value = f"{value[:start]}{value[end + 3:]}" + + # remove tags using the same method + while True: + start = value.find("<") + + if start == -1: + break + + end = value.find(">", start) + + if end == -1: + break + + value = f"{value[:start]}{value[end + 1:]}" + + # collapse spaces value = " ".join(value.split()) return self.__class__(value).unescape() diff --git a/lib/platformdirs/__init__.py b/lib/platformdirs/__init__.py index 3d5a5bda..75c8bff9 100644 --- a/lib/platformdirs/__init__.py +++ b/lib/platformdirs/__init__.py @@ -2,6 +2,7 @@ Utilities for determining application-specific dirs. See for details and usage. """ + from __future__ import annotations import os @@ -14,11 +15,7 @@ from .version import __version_tuple__ as __version_info__ if TYPE_CHECKING: from pathlib import Path - - if sys.version_info >= (3, 8): # pragma: no cover (py38+) - from typing import Literal - else: # pragma: no cover (py38+) - from typing_extensions import Literal + from typing import Literal def _set_platform_dir_class() -> type[PlatformDirsABC]: diff --git a/lib/platformdirs/__main__.py b/lib/platformdirs/__main__.py index 3cefedbd..4bf27c9c 100644 --- a/lib/platformdirs/__main__.py +++ b/lib/platformdirs/__main__.py @@ -1,4 +1,5 @@ """Main entry point.""" + from __future__ import annotations from platformdirs import PlatformDirs, __version__ diff --git a/lib/platformdirs/android.py b/lib/platformdirs/android.py index 572559f8..4acdb638 100644 --- a/lib/platformdirs/android.py +++ b/lib/platformdirs/android.py @@ -1,4 +1,5 @@ """Android.""" + from __future__ import annotations import os diff --git a/lib/platformdirs/api.py b/lib/platformdirs/api.py index aa9ce7b9..031a38a3 100644 --- a/lib/platformdirs/api.py +++ b/lib/platformdirs/api.py @@ -1,4 +1,5 @@ """Base API.""" + from __future__ import annotations import os @@ -7,12 +8,7 @@ from pathlib import Path from typing import TYPE_CHECKING if TYPE_CHECKING: - import sys - - if sys.version_info >= (3, 8): # pragma: no cover (py38+) - from typing import Literal - else: # pragma: no cover (py38+) - from typing_extensions import Literal + from typing import Iterator, Literal class PlatformDirsABC(ABC): @@ -241,3 +237,43 @@ class PlatformDirsABC(ABC): def site_runtime_path(self) -> Path: """:return: runtime path shared by users""" return Path(self.site_runtime_dir) + + def iter_config_dirs(self) -> Iterator[str]: + """:yield: all user and site configuration directories.""" + yield self.user_config_dir + yield self.site_config_dir + + def iter_data_dirs(self) -> Iterator[str]: + """:yield: all user and site data directories.""" + yield self.user_data_dir + yield self.site_data_dir + + def iter_cache_dirs(self) -> Iterator[str]: + """:yield: all user and site cache directories.""" + yield self.user_cache_dir + yield self.site_cache_dir + + def iter_runtime_dirs(self) -> Iterator[str]: + """:yield: all user and site runtime directories.""" + yield self.user_runtime_dir + yield self.site_runtime_dir + + def iter_config_paths(self) -> Iterator[Path]: + """:yield: all user and site configuration paths.""" + for path in self.iter_config_dirs(): + yield Path(path) + + def iter_data_paths(self) -> Iterator[Path]: + """:yield: all user and site data paths.""" + for path in self.iter_data_dirs(): + yield Path(path) + + def iter_cache_paths(self) -> Iterator[Path]: + """:yield: all user and site cache paths.""" + for path in self.iter_cache_dirs(): + yield Path(path) + + def iter_runtime_paths(self) -> Iterator[Path]: + """:yield: all user and site runtime paths.""" + for path in self.iter_runtime_dirs(): + yield Path(path) diff --git a/lib/platformdirs/macos.py b/lib/platformdirs/macos.py index c01ce163..b7b48808 100644 --- a/lib/platformdirs/macos.py +++ b/lib/platformdirs/macos.py @@ -1,4 +1,5 @@ """macOS.""" + from __future__ import annotations import os.path diff --git a/lib/platformdirs/unix.py b/lib/platformdirs/unix.py index 1d0174f2..ca4728e6 100644 --- a/lib/platformdirs/unix.py +++ b/lib/platformdirs/unix.py @@ -1,10 +1,12 @@ """Unix.""" + from __future__ import annotations import os import sys from configparser import ConfigParser from pathlib import Path +from typing import Iterator from .api import PlatformDirsABC @@ -43,24 +45,24 @@ class Unix(PlatformDirsABC): return self._append_app_name_and_version(path) @property - def site_data_dir(self) -> str: - """ - :return: data directories shared by users (if `multipath ` is - enabled and ``XDG_DATA_DIR`` is set and a multi path the response is also a multi path separated by the OS - path separator), e.g. ``/usr/local/share/$appname/$version`` or ``/usr/share/$appname/$version`` - """ - # XDG default for $XDG_DATA_DIRS; only first, if multipath is False + def _site_data_dirs(self) -> list[str]: path = os.environ.get("XDG_DATA_DIRS", "") if not path.strip(): path = f"/usr/local/share{os.pathsep}/usr/share" - return self._with_multi_path(path) + return [self._append_app_name_and_version(p) for p in path.split(os.pathsep)] - def _with_multi_path(self, path: str) -> str: - path_list = path.split(os.pathsep) + @property + def site_data_dir(self) -> str: + """ + :return: data directories shared by users (if `multipath ` is + enabled and ``XDG_DATA_DIRS`` is set and a multi path the response is also a multi path separated by the + OS path separator), e.g. ``/usr/local/share/$appname/$version`` or ``/usr/share/$appname/$version`` + """ + # XDG default for $XDG_DATA_DIRS; only first, if multipath is False + dirs = self._site_data_dirs if not self.multipath: - path_list = path_list[0:1] - path_list = [self._append_app_name_and_version(os.path.expanduser(p)) for p in path_list] # noqa: PTH111 - return os.pathsep.join(path_list) + return dirs[0] + return os.pathsep.join(dirs) @property def user_config_dir(self) -> str: @@ -74,17 +76,24 @@ class Unix(PlatformDirsABC): return self._append_app_name_and_version(path) @property - def site_config_dir(self) -> str: - """ - :return: config directories shared by users (if `multipath ` - is enabled and ``XDG_DATA_DIR`` is set and a multi path the response is also a multi path separated by the OS - path separator), e.g. ``/etc/xdg/$appname/$version`` - """ - # XDG default for $XDG_CONFIG_DIRS only first, if multipath is False + def _site_config_dirs(self) -> list[str]: path = os.environ.get("XDG_CONFIG_DIRS", "") if not path.strip(): path = "/etc/xdg" - return self._with_multi_path(path) + return [self._append_app_name_and_version(p) for p in path.split(os.pathsep)] + + @property + def site_config_dir(self) -> str: + """ + :return: config directories shared by users (if `multipath ` + is enabled and ``XDG_CONFIG_DIRS`` is set and a multi path the response is also a multi path separated by + the OS path separator), e.g. ``/etc/xdg/$appname/$version`` + """ + # XDG default for $XDG_CONFIG_DIRS only first, if multipath is False + dirs = self._site_config_dirs + if not self.multipath: + return dirs[0] + return os.pathsep.join(dirs) @property def user_cache_dir(self) -> str: @@ -99,8 +108,8 @@ class Unix(PlatformDirsABC): @property def site_cache_dir(self) -> str: - """:return: cache directory shared by users, e.g. ``/var/tmp/$appname/$version``""" - return self._append_app_name_and_version("/var/tmp") # noqa: S108 + """:return: cache directory shared by users, e.g. ``/var/cache/$appname/$version``""" + return self._append_app_name_and_version("/var/cache") @property def user_state_dir(self) -> str: @@ -215,6 +224,16 @@ class Unix(PlatformDirsABC): directory = directory.split(os.pathsep)[0] return Path(directory) + def iter_config_dirs(self) -> Iterator[str]: + """:yield: all user and site configuration directories.""" + yield self.user_config_dir + yield from self._site_config_dirs + + def iter_data_dirs(self) -> Iterator[str]: + """:yield: all user and site data directories.""" + yield self.user_data_dir + yield from self._site_data_dirs + def _get_user_media_dir(env_var: str, fallback_tilde_path: str) -> str: media_dir = _get_user_dirs_folder(env_var) diff --git a/lib/platformdirs/version.py b/lib/platformdirs/version.py index c2ef2084..cc1e3456 100644 --- a/lib/platformdirs/version.py +++ b/lib/platformdirs/version.py @@ -12,5 +12,5 @@ __version__: str __version_tuple__: VERSION_TUPLE version_tuple: VERSION_TUPLE -__version__ = version = '3.11.0' -__version_tuple__ = version_tuple = (3, 11, 0) +__version__ = version = '4.2.0' +__version_tuple__ = version_tuple = (4, 2, 0) diff --git a/lib/platformdirs/windows.py b/lib/platformdirs/windows.py index 751143ad..c62d0c8d 100644 --- a/lib/platformdirs/windows.py +++ b/lib/platformdirs/windows.py @@ -1,4 +1,5 @@ """Windows.""" + from __future__ import annotations import ctypes diff --git a/lib/pytz/__init__.py b/lib/pytz/__init__.py index 98b66553..2355f8b4 100644 --- a/lib/pytz/__init__.py +++ b/lib/pytz/__init__.py @@ -22,8 +22,8 @@ from pytz.tzfile import build_tzinfo # The IANA (nee Olson) database is updated several times a year. -OLSON_VERSION = '2023c' -VERSION = '2023.3' # pip compatible version number. +OLSON_VERSION = '2024a' +VERSION = '2024.1' # pip compatible version number. __version__ = VERSION OLSEN_VERSION = OLSON_VERSION # Old releases had this misspelling diff --git a/lib/pytz/tzinfo.py b/lib/pytz/tzinfo.py index 725978d5..49b5c3fe 100644 --- a/lib/pytz/tzinfo.py +++ b/lib/pytz/tzinfo.py @@ -24,7 +24,8 @@ def memorized_timedelta(seconds): _timedelta_cache[seconds] = delta return delta -_epoch = datetime.utcfromtimestamp(0) + +_epoch = datetime(1970, 1, 1, 0, 0) # datetime.utcfromtimestamp(0) _datetime_cache = {0: _epoch} @@ -33,12 +34,13 @@ def memorized_datetime(seconds): 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) + # NB. We can't just do datetime.fromtimestamp(seconds, tz=timezone.utc).replace(tzinfo=None) + # as this fails with negative values under Windows (Bug #90096) dt = _epoch + timedelta(seconds=seconds) _datetime_cache[seconds] = dt return dt + _ttinfo_cache = {} @@ -55,6 +57,7 @@ def memorized_ttinfo(*args): _ttinfo_cache[args] = ttinfo return ttinfo + _notime = memorized_timedelta(0) @@ -355,7 +358,7 @@ class DstTzInfo(BaseTzInfo): 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. + # is an ambiguous case occurring during the end-of-DST transition. # If told to be strict, raise an exception since we have an # ambiguous case diff --git a/lib/pytz/zoneinfo/America/Godthab b/lib/pytz/zoneinfo/America/Godthab index adb7934a..29958cf1 100644 Binary files a/lib/pytz/zoneinfo/America/Godthab and b/lib/pytz/zoneinfo/America/Godthab differ diff --git a/lib/pytz/zoneinfo/America/Miquelon b/lib/pytz/zoneinfo/America/Miquelon index 5eccd861..f780ea99 100644 Binary files a/lib/pytz/zoneinfo/America/Miquelon and b/lib/pytz/zoneinfo/America/Miquelon differ diff --git a/lib/pytz/zoneinfo/America/Montreal b/lib/pytz/zoneinfo/America/Montreal index 6752c5b0..17013733 100644 Binary files a/lib/pytz/zoneinfo/America/Montreal and b/lib/pytz/zoneinfo/America/Montreal differ diff --git a/lib/pytz/zoneinfo/America/Nassau b/lib/pytz/zoneinfo/America/Nassau index 6752c5b0..17013733 100644 Binary files a/lib/pytz/zoneinfo/America/Nassau and b/lib/pytz/zoneinfo/America/Nassau differ diff --git a/lib/pytz/zoneinfo/America/Nipigon b/lib/pytz/zoneinfo/America/Nipigon index 6752c5b0..17013733 100644 Binary files a/lib/pytz/zoneinfo/America/Nipigon and b/lib/pytz/zoneinfo/America/Nipigon differ diff --git a/lib/pytz/zoneinfo/America/Nuuk b/lib/pytz/zoneinfo/America/Nuuk index adb7934a..29958cf1 100644 Binary files a/lib/pytz/zoneinfo/America/Nuuk and b/lib/pytz/zoneinfo/America/Nuuk differ diff --git a/lib/pytz/zoneinfo/America/Scoresbysund b/lib/pytz/zoneinfo/America/Scoresbysund index 286d1321..9bf411ef 100644 Binary files a/lib/pytz/zoneinfo/America/Scoresbysund and b/lib/pytz/zoneinfo/America/Scoresbysund differ diff --git a/lib/pytz/zoneinfo/America/Thunder_Bay b/lib/pytz/zoneinfo/America/Thunder_Bay index 6752c5b0..17013733 100644 Binary files a/lib/pytz/zoneinfo/America/Thunder_Bay and b/lib/pytz/zoneinfo/America/Thunder_Bay differ diff --git a/lib/pytz/zoneinfo/America/Toronto b/lib/pytz/zoneinfo/America/Toronto index 6752c5b0..17013733 100644 Binary files a/lib/pytz/zoneinfo/America/Toronto and b/lib/pytz/zoneinfo/America/Toronto differ diff --git a/lib/pytz/zoneinfo/Antarctica/Casey b/lib/pytz/zoneinfo/Antarctica/Casey index 4b98133d..586a7653 100644 Binary files a/lib/pytz/zoneinfo/Antarctica/Casey and b/lib/pytz/zoneinfo/Antarctica/Casey differ diff --git a/lib/pytz/zoneinfo/Antarctica/Vostok b/lib/pytz/zoneinfo/Antarctica/Vostok index 62bdcac1..016e06b1 100644 Binary files a/lib/pytz/zoneinfo/Antarctica/Vostok and b/lib/pytz/zoneinfo/Antarctica/Vostok differ diff --git a/lib/pytz/zoneinfo/Asia/Almaty b/lib/pytz/zoneinfo/Asia/Almaty index 91c916a3..855abbd6 100644 Binary files a/lib/pytz/zoneinfo/Asia/Almaty and b/lib/pytz/zoneinfo/Asia/Almaty differ diff --git a/lib/pytz/zoneinfo/Asia/Gaza b/lib/pytz/zoneinfo/Asia/Gaza index c9b2ff90..dd5781e8 100644 Binary files a/lib/pytz/zoneinfo/Asia/Gaza and b/lib/pytz/zoneinfo/Asia/Gaza differ diff --git a/lib/pytz/zoneinfo/Asia/Hebron b/lib/pytz/zoneinfo/Asia/Hebron index 64194fd8..a64fc9e7 100644 Binary files a/lib/pytz/zoneinfo/Asia/Hebron and b/lib/pytz/zoneinfo/Asia/Hebron differ diff --git a/lib/pytz/zoneinfo/Asia/Ho_Chi_Minh b/lib/pytz/zoneinfo/Asia/Ho_Chi_Minh index a213d290..9c45ed99 100644 Binary files a/lib/pytz/zoneinfo/Asia/Ho_Chi_Minh and b/lib/pytz/zoneinfo/Asia/Ho_Chi_Minh differ diff --git a/lib/pytz/zoneinfo/Asia/Qostanay b/lib/pytz/zoneinfo/Asia/Qostanay index f8baf676..2ee9ef7e 100644 Binary files a/lib/pytz/zoneinfo/Asia/Qostanay and b/lib/pytz/zoneinfo/Asia/Qostanay differ diff --git a/lib/pytz/zoneinfo/Asia/Saigon b/lib/pytz/zoneinfo/Asia/Saigon index a213d290..9c45ed99 100644 Binary files a/lib/pytz/zoneinfo/Asia/Saigon and b/lib/pytz/zoneinfo/Asia/Saigon differ diff --git a/lib/pytz/zoneinfo/Canada/Eastern b/lib/pytz/zoneinfo/Canada/Eastern index 6752c5b0..17013733 100644 Binary files a/lib/pytz/zoneinfo/Canada/Eastern and b/lib/pytz/zoneinfo/Canada/Eastern differ diff --git a/lib/pytz/zoneinfo/iso3166.tab b/lib/pytz/zoneinfo/iso3166.tab index be3348d1..402c015e 100644 --- a/lib/pytz/zoneinfo/iso3166.tab +++ b/lib/pytz/zoneinfo/iso3166.tab @@ -3,17 +3,22 @@ # This file is in the public domain, so clarified as of # 2009-05-17 by Arthur David Olson. # -# From Paul Eggert (2022-11-18): +# From Paul Eggert (2023-09-06): # 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 N1087 (2022-09-02). See: Updates on ISO 3166-1 -# https://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. +# ISO/TC 46 N1108 (2023-04-05). See: ISO/TC 46 Documents +# https://www.iso.org/committee/48750.html?view=documents +# 2. The usual English name for the coded region. This sometimes +# departs from ISO-listed names, sometimes so that sorted subsets +# of names are useful (e.g., "Samoa (American)" and "Samoa +# (western)" rather than "American Samoa" and "Samoa"), +# sometimes to avoid confusion among non-experts (e.g., +# "Czech Republic" and "Turkey" rather than "Czechia" and "Türkiye"), +# and sometimes to omit needless detail or churn (e.g., "Netherlands" +# rather than "Netherlands (the)" or "Netherlands (Kingdom of the)"). # # The table is sorted by country code. # diff --git a/lib/pytz/zoneinfo/leapseconds b/lib/pytz/zoneinfo/leapseconds index a6a170aa..ce150bfe 100644 --- a/lib/pytz/zoneinfo/leapseconds +++ b/lib/pytz/zoneinfo/leapseconds @@ -3,13 +3,10 @@ # This file is in the public domain. # This file is generated automatically from the data in the public-domain -# NIST format leap-seconds.list file, which can be copied from -# -# or . -# The NIST file is used instead of its IERS upstream counterpart +# NIST/IERS format leap-seconds.list file, which can be copied from # -# because under US law the NIST file is public domain -# whereas the IERS file's copyright and license status is unclear. +# or, in a variant with different comments, from +# . # For more about leap-seconds.list, please see # The NTP Timescale and Leap Seconds # . @@ -72,11 +69,11 @@ Leap 2016 Dec 31 23:59:60 + S # Any additional leap seconds will come after this. # This Expires line is commented out for now, # so that pre-2020a zic implementations do not reject this file. -#Expires 2023 Dec 28 00:00:00 +#Expires 2024 Dec 28 00:00:00 # POSIX timestamps for the data in this file: -#updated 1467936000 (2016-07-08 00:00:00 UTC) -#expires 1703721600 (2023-12-28 00:00:00 UTC) +#updated 1704708379 (2024-01-08 10:06:19 UTC) +#expires 1735344000 (2024-12-28 00:00:00 UTC) -# Updated through IERS Bulletin C65 -# File expires on: 28 December 2023 +# Updated through IERS Bulletin C (https://hpiers.obspm.fr/iers/bul/bulc/bulletinc.dat) +# File expires on 28 December 2024 diff --git a/lib/pytz/zoneinfo/tzdata.zi b/lib/pytz/zoneinfo/tzdata.zi index 23d99be4..b5a03be7 100644 --- a/lib/pytz/zoneinfo/tzdata.zi +++ b/lib/pytz/zoneinfo/tzdata.zi @@ -1,4 +1,4 @@ -# version unknown-dirty +# 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 Su>=1 23s 0 - @@ -22,27 +22,6 @@ 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 16 -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 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 @@ -77,21 +56,6 @@ R K 2014 o - Jul 31 24 1 S R K 2014 o - S lastTh 24 0 - R K 2023 ma - Ap lastF 0 1 S R K 2023 ma - O lastTh 24 0 - -Z Africa/Cairo 2:5:9 - LMT 1900 O -2 K EE%sT -Z Africa/Bissau -1:2:20 - LMT 1912 Ja 1 1u --1 - -01 1975 -0 - GMT -Z Africa/Nairobi 2:27:16 - LMT 1908 May -2:30 - +0230 1928 Jun 30 24 -3 - EAT 1930 Ja 4 24 -2:30 - +0230 1936 D 31 24 -2:45 - +0245 1942 Jul 31 24 -3 - EAT -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 @@ -109,21 +73,10 @@ 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 lastSu 2 1 - R MU 2009 o - Mar lastSu 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 - @@ -307,53 +260,15 @@ R M 2086 o - Ap 14 3 -1 - R M 2086 o - May 19 2 0 - R M 2087 o - Mar 30 3 -1 - R M 2087 o - May 11 2 0 - -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 28 3 -1 M +01/+00 -Z Africa/El_Aaiun -0:52:48 - LMT 1934 --1 - -01 1976 Ap 14 -0 M +00/+01 2018 O 28 3 -1 M +01/+00 -Z Africa/Maputo 2:10:20 - LMT 1903 Mar -2 - CAT R NA 1994 o - Mar 21 0 -1 WAT R NA 1994 2017 - S Su>=1 2 0 CAT R NA 1995 2017 - Ap Su>=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:35 - LMT 1905 Jul -0 - GMT 1908 Jul -0:13:35 - LMT 1914 -0:30 - +0030 1919 S -1 - WAT -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 2019 Ja 1 2 -0 - GMT R SA 1942 1943 - S Su>=15 2 1 - R SA 1943 1944 - Mar Su>=15 2 0 - -Z Africa/Johannesburg 1:52 - LMT 1892 F 8 -1:30 - SAST 1903 Mar -2 SA SAST 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 lastSu 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 2021 F -2 - CAT 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 @@ -379,80 +294,14 @@ R n 2005 o - May 1 0s 1 S R n 2005 o - S 30 1s 0 - R n 2006 2008 - Mar lastSu 2s 1 S R n 2006 2008 - O lastSu 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 2018 O 7 4 -11 - +11 2019 Mar 17 3 -8 - +08 2019 O 4 3 -11 - +11 2020 Mar 8 3 -8 - +08 2020 O 4 0:1 -11 - +11 -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 R Tr 2005 ma - Mar lastSu 1u 2 +02 R Tr 2004 ma - O lastSu 1u 0 +00 -Z Antarctica/Troll 0 - -00 2005 F 12 -0 Tr %s -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 lastSu 2s 1 - R AM 2011 o - O lastSu 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 lastSu 4 1 - R AZ 1997 2015 - O lastSu 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 lastSu 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/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 1919 o - Ap 12 24 1 D R Sh 1919 o - S 30 24 0 S R Sh 1940 o - Jun 1 0 1 D @@ -470,11 +319,6 @@ R Sh 1948 1949 - S 30 24 0 S R CN 1986 o - May 4 2 1 D R CN 1986 1991 - S Su>=11 2 0 S R CN 1987 1991 - Ap Su>=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 1946 o - Ap 21 0 1 S R HK 1946 o - D 1 3:30s 0 - R HK 1947 o - Ap 13 3:30s 1 S @@ -489,12 +333,6 @@ R HK 1965 1976 - O Su>=16 3:30 0 - R HK 1973 o - D 30 3:30 1 S R HK 1979 o - May 13 3:30 1 S R HK 1979 o - O 21 3:30 0 - -Z Asia/Hong_Kong 7:36:42 - LMT 1904 O 29 17u -8 - HKT 1941 Jun 15 3 -8 1 HKST 1941 O 1 4 -8 0:30 HKWT 1941 D 25 -9 - JST 1945 N 18 2 -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 @@ -510,10 +348,6 @@ 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 @@ -541,10 +375,6 @@ R _ 1973 o - D 30 3:30 1 D R _ 1975 1976 - Ap Su>=16 3:30 1 D R _ 1979 o - May 13 3:30 1 D R _ 1979 o - O Su>=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 @@ -554,65 +384,6 @@ R CY 1977 o - S 25 0 0 - R CY 1978 o - O 2 0 0 - R CY 1979 1997 - S lastSu 0 0 - R CY 1981 1998 - Mar lastSu 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 -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 lastSu -4 e +04/+05 1996 O lastSu -4 1 +05 1997 Mar lastSu -4 e +04/+05 2004 Jun 27 -3 R +03/+04 2005 Mar lastSu 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 16:40u -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 1910 o - Ja 1 0 0 - R i 1977 o - Mar 21 23 1 - R i 1977 o - O 20 24 0 - @@ -653,11 +424,6 @@ R i 2020 o - Mar 20 24 1 - R i 2020 o - S 20 24 0 - R i 2021 2022 - Mar 21 24 1 - R i 2021 2022 - S 21 24 0 - -Z Asia/Tehran 3:25:44 - LMT 1916 -3:25:44 - TMT 1935 Jun 13 -3:30 i +0330/+0430 1977 O 20 24 -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 - @@ -666,10 +432,6 @@ R IQ 1985 1990 - S lastSu 1s 0 - R IQ 1986 1990 - Mar lastSu 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 - May 31 24u 1 D R Z 1940 o - S 30 24u 0 S R Z 1940 o - N 16 24u 1 D @@ -755,15 +517,10 @@ R Z 2011 o - O 2 2 0 S R Z 2012 o - S 23 2 0 S R Z 2013 ma - Mar F>=23 2 1 D R Z 2013 ma - O lastSu 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 Sa>=1 24 1 D R JP 1948 1951 - S Sa>=8 25 0 S R JP 1949 o - Ap Sa>=1 24 1 D R JP 1950 1951 - May Sa>=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 @@ -796,83 +553,10 @@ R J 2013 o - D 20 0 0 - R J 2014 2021 - Mar lastTh 24 1 S R J 2014 2022 - O lastF 0s 0 - R J 2022 o - F lastTh 24 1 S -Z Asia/Amman 2:23:44 - LMT 1931 -2 J EE%sT 2022 O 28 0s -3 - +03 -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 2018 D 21 -5 - +05 -Z Asia/Qostanay 4:14:28 - 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 -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 Su>=7 0s 1 - R KG 1992 1996 - S lastSu 0 0 - R KG 1997 2005 - Mar lastSu 2:30 1 - R KG 1997 2004 - O lastSu 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 12 24 0 S R KR 1949 o - Ap 3 0 1 D @@ -887,18 +571,6 @@ R KR 1957 1960 - May Su>=1 0 1 D R KR 1957 1960 - S Sa>=17 24 0 S R KR 1987 1988 - May Su>=8 2 1 D R KR 1987 1988 - O Su>=8 3 0 S -Z Asia/Seoul 8:27:52 - LMT 1908 Ap -8:30 - KST 1912 -9 - JST 1945 S 8 -9 KR K%sT 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 @@ -923,18 +595,8 @@ R l 1992 o - O 4 0 0 - R l 1993 ma - Mar lastSu 0 1 S R l 1993 1998 - S lastSu 0 0 - R l 1999 ma - O lastSu 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/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 lastSu 0 1 - @@ -944,31 +606,11 @@ R X 2001 2006 - S lastSa 2 0 - R X 2002 2006 - Mar lastSa 2 1 - R X 2015 2016 - Mar lastSa 2 1 - R X 2015 2016 - S lastSa 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 Su>=2 0 1 S R PK 2002 o - O Su>=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 - @@ -1001,136 +643,90 @@ R P 2021 o - O 29 1 0 - R P 2022 o - Mar 27 0 1 S R P 2022 2035 - O Sa<=30 2 0 - R P 2023 o - Ap 29 2 1 S -R P 2024 o - Ap 13 2 1 S -R P 2025 o - Ap 5 2 1 S +R P 2024 o - Ap 20 2 1 S +R P 2025 o - Ap 12 2 1 S R P 2026 2054 - Mar Sa<=30 2 1 S R P 2036 o - O 18 2 0 - R P 2037 o - O 10 2 0 - R P 2038 o - S 25 2 0 - R P 2039 o - S 17 2 0 - -R P 2039 o - O 22 2 1 S -R P 2039 2067 - O Sa<=30 2 0 - R P 2040 o - S 1 2 0 - -R P 2040 o - O 13 2 1 S +R P 2040 o - O 20 2 1 S +R P 2040 2067 - O Sa<=30 2 0 - R P 2041 o - Au 24 2 0 - -R P 2041 o - S 28 2 1 S +R P 2041 o - O 5 2 1 S R P 2042 o - Au 16 2 0 - -R P 2042 o - S 20 2 1 S +R P 2042 o - S 27 2 1 S R P 2043 o - Au 1 2 0 - -R P 2043 o - S 12 2 1 S +R P 2043 o - S 19 2 1 S R P 2044 o - Jul 23 2 0 - -R P 2044 o - Au 27 2 1 S +R P 2044 o - S 3 2 1 S R P 2045 o - Jul 15 2 0 - -R P 2045 o - Au 19 2 1 S +R P 2045 o - Au 26 2 1 S R P 2046 o - Jun 30 2 0 - -R P 2046 o - Au 11 2 1 S +R P 2046 o - Au 18 2 1 S R P 2047 o - Jun 22 2 0 - -R P 2047 o - Jul 27 2 1 S +R P 2047 o - Au 3 2 1 S R P 2048 o - Jun 6 2 0 - -R P 2048 o - Jul 18 2 1 S +R P 2048 o - Jul 25 2 1 S R P 2049 o - May 29 2 0 - -R P 2049 o - Jul 3 2 1 S +R P 2049 o - Jul 10 2 1 S R P 2050 o - May 21 2 0 - -R P 2050 o - Jun 25 2 1 S +R P 2050 o - Jul 2 2 1 S R P 2051 o - May 6 2 0 - -R P 2051 o - Jun 17 2 1 S +R P 2051 o - Jun 24 2 1 S R P 2052 o - Ap 27 2 0 - -R P 2052 o - Jun 1 2 1 S +R P 2052 o - Jun 8 2 1 S R P 2053 o - Ap 12 2 0 - -R P 2053 o - May 24 2 1 S +R P 2053 o - May 31 2 1 S R P 2054 o - Ap 4 2 0 - -R P 2054 o - May 16 2 1 S -R P 2055 o - May 1 2 1 S -R P 2056 o - Ap 22 2 1 S -R P 2057 o - Ap 7 2 1 S -R P 2058 ma - Mar Sa<=30 2 1 S +R P 2054 o - May 23 2 1 S +R P 2055 o - May 8 2 1 S +R P 2056 o - Ap 29 2 1 S +R P 2057 o - Ap 14 2 1 S +R P 2058 o - Ap 6 2 1 S +R P 2059 ma - Mar Sa<=30 2 1 S R P 2068 o - O 20 2 0 - R P 2069 o - O 12 2 0 - R P 2070 o - O 4 2 0 - R P 2071 o - S 19 2 0 - R P 2072 o - S 10 2 0 - -R P 2072 o - O 15 2 1 S +R P 2072 o - O 22 2 1 S +R P 2072 ma - O Sa<=30 2 0 - R P 2073 o - S 2 2 0 - -R P 2073 o - O 7 2 1 S +R P 2073 o - O 14 2 1 S R P 2074 o - Au 18 2 0 - -R P 2074 o - S 29 2 1 S +R P 2074 o - O 6 2 1 S R P 2075 o - Au 10 2 0 - -R P 2075 o - S 14 2 1 S -R P 2075 ma - O Sa<=30 2 0 - +R P 2075 o - S 21 2 1 S R P 2076 o - Jul 25 2 0 - -R P 2076 o - S 5 2 1 S +R P 2076 o - S 12 2 1 S R P 2077 o - Jul 17 2 0 - -R P 2077 o - Au 28 2 1 S +R P 2077 o - S 4 2 1 S R P 2078 o - Jul 9 2 0 - -R P 2078 o - Au 13 2 1 S +R P 2078 o - Au 20 2 1 S R P 2079 o - Jun 24 2 0 - -R P 2079 o - Au 5 2 1 S +R P 2079 o - Au 12 2 1 S R P 2080 o - Jun 15 2 0 - -R P 2080 o - Jul 20 2 1 S +R P 2080 o - Jul 27 2 1 S R P 2081 o - Jun 7 2 0 - -R P 2081 o - Jul 12 2 1 S +R P 2081 o - Jul 19 2 1 S R P 2082 o - May 23 2 0 - -R P 2082 o - Jul 4 2 1 S +R P 2082 o - Jul 11 2 1 S R P 2083 o - May 15 2 0 - -R P 2083 o - Jun 19 2 1 S +R P 2083 o - Jun 26 2 1 S R P 2084 o - Ap 29 2 0 - -R P 2084 o - Jun 10 2 1 S +R P 2084 o - Jun 17 2 1 S R P 2085 o - Ap 21 2 0 - -R P 2085 o - Jun 2 2 1 S +R P 2085 o - Jun 9 2 1 S R P 2086 o - Ap 13 2 0 - -R P 2086 o - May 18 2 1 S -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 P 2086 o - May 25 2 1 S 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 -Z Asia/Riyadh 3:6:52 - LMT 1947 Mar 14 -3 - +03 -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 1981 D 31 16u -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 Su>=15 2 1 S R S 1920 1923 - O Su>=1 2 0 - R S 1962 o - Ap 29 2 1 S @@ -1172,46 +768,6 @@ R S 2009 o - Mar lastF 0 1 S R S 2010 2011 - Ap F>=1 0 1 S R S 2012 2022 - Mar lastF 0 1 S R S 2009 2022 - O lastF 0 0 - -Z Asia/Damascus 2:25:12 - LMT 1920 -2 S EE%sT 2022 O 28 -3 - +03 -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 +06 1991 S 9 2s -5 - +05 -Z Asia/Bangkok 6:42:4 - LMT 1880 -6:42:4 - BMT 1920 Ap -7 - +07 -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 -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:30 - 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 2s 1 D R AU 1917 o - Mar lastSu 2s 0 S R AU 1942 o - Ja 1 2s 1 D @@ -1219,9 +775,6 @@ R AU 1942 o - Mar lastSu 2s 0 S R AU 1942 o - S 27 2s 1 D R AU 1943 1944 - Mar lastSu 2s 0 S R AU 1943 o - O 3 2s 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 lastSu 2s 1 D R AW 1975 o - Mar Su>=1 2s 0 S R AW 1983 o - O lastSu 2s 1 D @@ -1231,25 +784,12 @@ R AW 1992 o - Mar Su>=1 2s 0 S R AW 2006 o - D 3 2s 1 D R AW 2007 2009 - Mar lastSu 2s 0 S R AW 2007 2008 - O lastSu 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 lastSu 2s 1 D R AQ 1972 o - F lastSu 2s 0 S R AQ 1989 1991 - O lastSu 2s 1 D R AQ 1990 1992 - Mar Su>=1 2s 0 S R Ho 1992 1993 - O lastSu 2s 1 D R Ho 1993 1994 - Mar Su>=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 lastSu 2s 1 D R AS 1986 o - O 19 2s 1 D R AS 1987 2007 - O lastSu 2s 1 D @@ -1265,10 +805,6 @@ R AS 2006 o - Ap 2 2s 0 S R AS 2007 o - Mar lastSu 2s 0 S R AS 2008 ma - Ap Su>=1 2s 0 S R AS 2008 ma - O Su>=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 1916 o - O Su>=1 2s 1 D R AT 1917 o - Mar lastSu 2s 0 S R AT 1917 1918 - O Su>=22 2s 1 D @@ -1292,10 +828,6 @@ R AT 2001 ma - O Su>=1 2s 1 D R AT 2006 o - Ap Su>=1 2s 0 S R AT 2007 o - Mar lastSu 2s 0 S R AT 2008 ma - Ap Su>=1 2s 0 S -Z Australia/Hobart 9:49:16 - LMT 1895 S -10 AT AE%sT 1919 O 24 -10 AU AE%sT 1967 -10 AT AE%sT R AV 1971 1985 - O lastSu 2s 1 D R AV 1972 o - F lastSu 2s 0 S R AV 1973 1985 - Mar Su>=1 2s 0 S @@ -1310,9 +842,6 @@ R AV 2006 o - Ap Su>=1 2s 0 S R AV 2007 o - Mar lastSu 2s 0 S R AV 2008 ma - Ap Su>=1 2s 0 S R AV 2008 ma - O Su>=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 lastSu 2s 1 D R AN 1972 o - F 27 2s 0 S R AN 1973 1981 - Mar Su>=1 2s 0 S @@ -1329,15 +858,6 @@ R AN 2006 o - Ap Su>=1 2s 0 S R AN 2007 o - Mar lastSu 2s 0 S R AN 2008 ma - Ap Su>=1 2s 0 S R AN 2008 ma - O Su>=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 lastSu 2 1 - R LH 1982 1985 - Mar Su>=1 2 0 - R LH 1985 o - O lastSu 2 0:30 - @@ -1352,19 +872,6 @@ R LH 2006 o - Ap Su>=1 2 0 - R LH 2007 o - Mar lastSu 2 0 - R LH 2008 ma - Ap Su>=1 2 0 - R LH 2008 ma - O Su>=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 -10 1 AEDT 2011 -10 AT AE%sT R FJ 1998 1999 - N Su>=1 2 1 - R FJ 1999 2000 - F lastSu 3 0 - R FJ 2009 o - N 29 2 1 - @@ -1377,14 +884,6 @@ R FJ 2014 2018 - N Su>=1 2 1 - R FJ 2015 2021 - Ja Su>=12 3 0 - R FJ 2019 o - N Su>=8 2 1 - R FJ 2020 o - D 20 2 1 - -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 R Gu 1959 o - Jun 27 2 1 D R Gu 1961 o - Ja 29 2 0 S R Gu 1967 o - S 1 2 1 D @@ -1399,50 +898,10 @@ R Gu 1976 o - May 26 2 1 D R Gu 1976 o - Au 22 2:1 0 S R Gu 1977 o - Ap 24 2 1 D R Gu 1977 o - Au 28 2 0 S -Z Pacific/Guam -14:21 - LMT 1844 D 31 -9:39 - LMT 1901 -10 - GST 1941 D 10 -9 - +09 1944 Jul 31 -10 Gu G%sT 2000 D 23 -10 - ChST -Z Pacific/Tarawa 11:32:4 - LMT 1901 -12 - +12 -Z Pacific/Kanton 0 - -00 1937 Au 31 --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/Kwajalein 11:9:20 - LMT 1901 -11 - +11 1937 -10 - +10 1941 Ap -9 - +09 1944 F 6 -11 - +11 1969 O --12 - -12 1993 Au 20 24 -12 - +12 -Z Pacific/Kosrae -13:8:4 - LMT 1844 D 31 -10:51:56 - LMT 1901 -11 - +11 1914 O -9 - +09 1919 F -11 - +11 1937 -10 - +10 1941 Ap -9 - +09 1945 Au -11 - +11 1969 O -12 - +12 1999 -11 - +11 -Z Pacific/Nauru 11:7:40 - LMT 1921 Ja 15 -11:30 - +1130 1942 Au 29 -9 - +09 1945 S 8 -11:30 - +1130 1979 F 10 2 -12 - +12 R NC 1977 1978 - D Su>=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 Su>=8 2 0:30 S @@ -1468,80 +927,26 @@ R NZ 2007 ma - S lastSu 2s 1 D R k 2007 ma - S lastSu 2:45s 1 - R NZ 2008 ma - Ap Su>=1 2s 0 S R k 2008 ma - Ap Su>=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 R CK 1978 o - N 12 0 0:30 - R CK 1979 1991 - Mar Su>=1 0 0 - R CK 1979 1990 - O lastSu 0 0:30 - -Z Pacific/Rarotonga 13:20:56 - LMT 1899 D 26 --10:39:4 - LMT 1952 O 16 --10:30 - -1030 1978 N 12 --10 CK -10/-0930 -Z Pacific/Niue -11:19:40 - LMT 1952 O 16 --11:20 - -1120 1964 Jul --11 - -11 -Z Pacific/Norfolk 11:11:52 - LMT 1901 -11:12 - +1112 1951 -11:30 - +1130 1974 O 27 2s -11:30 1 +1230 1975 Mar 2 2s -11:30 - +1130 2015 O 4 2s -11 - +11 2019 Jul -11 AN +11/+12 -Z Pacific/Palau -15:2:4 - LMT 1844 D 31 -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 R WS 2010 o - S lastSu 0 1 - R WS 2011 o - Ap Sa>=1 4 0 - R WS 2011 o - S lastSa 3 1 - R WS 2012 2021 - Ap Su>=1 4 0 - R WS 2012 2020 - S lastSu 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 Su>=1 2 1 - R TO 2001 2002 - Ja lastSu 2 0 - R TO 2016 o - N Su>=1 2 1 - R TO 2017 o - Ja Su>=15 3 0 - -Z Pacific/Tongatapu 12:19:12 - LMT 1945 S 10 -12:20 - +1220 1961 -13 - +13 1999 -13 TO +13/+14 R VU 1973 o - D 22 12u 1 - R VU 1974 o - Mar 30 12u 0 - R VU 1983 1991 - S Sa>=22 24 1 - R VU 1984 1991 - Mar Sa>=22 24 0 - R VU 1992 1993 - Ja Sa>=22 24 0 - R VU 1992 o - O Sa>=22 24 1 - -Z Pacific/Efate 11:13:16 - LMT 1912 Ja 13 -11 VU +11/+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 @@ -1607,11 +1012,6 @@ R G 1972 1980 - O Su>=23 2s 0 GMT R G 1981 1995 - Mar lastSu 1u 1 BST R G 1981 1989 - O Su>=23 1u 0 GMT R G 1990 1995 - O Su>=22 1u 0 GMT -Z Europe/London -0:1:15 - LMT 1847 D -0 G %s 1968 O 27 -1 - BST 1971 O 31 2u -0 G %s 1996 -0 E GMT/BST R IE 1971 o - O 31 2u -1 - R IE 1972 1980 - Mar Su>=16 2u 0 - R IE 1972 1980 - O Su>=23 2u -1 - @@ -1619,17 +1019,6 @@ R IE 1981 ma - Mar lastSu 1u 0 - R IE 1981 1989 - O Su>=23 1u -1 - R IE 1990 1995 - O Su>=22 1u -1 - R IE 1996 ma - O lastSu 1u -1 - -Z Europe/Dublin -0:25:21 - 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 Su>=1 1u 1 S R E 1977 o - S lastSu 1u 0 - R E 1978 o - O 1 1u 0 - @@ -1681,10 +1070,6 @@ R R 1981 1983 - O 1 0 0 - R R 1984 1995 - S lastSu 2s 0 - R R 1985 2010 - Mar lastSu 2s 1 S R R 1996 2010 - O lastSu 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 @@ -1710,14 +1095,6 @@ 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 @@ -1727,23 +1104,6 @@ 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 Sa>=1 23s 0 - R b 1919 o - Mar 1 23s 1 S @@ -1778,87 +1138,27 @@ 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 0:17:30 -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 Sa>=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 Su>=1 2s 0 - R CZ 1947 1948 - Ap Su>=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 -Z Atlantic/Faroe -0:27:4 - LMT 1908 Ja 11 -0 - WET 1981 -0 E WE%sT R Th 1991 1992 - Mar lastSu 2 1 D R Th 1991 1992 - S lastSu 2 0 S R Th 1993 2006 - Ap Su>=1 2 1 D R Th 1993 2006 - O lastSu 2 0 S R Th 2007 ma - Mar Su>=8 2 1 D R Th 2007 ma - N Su>=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/Nuuk -3:26:56 - LMT 1916 Jul 28 --3 - -03 1980 Ap 6 2 --3 E -03/-02 2023 O 29 1u --2 E -02/-01 -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 lastSu 2 1 S R FI 1981 1982 - S lastSu 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 R F 1916 o - Jun 14 23s 1 S R F 1916 1919 - O Su>=1 23s 0 - R F 1917 o - Mar 24 23s 1 S @@ -1901,13 +1201,6 @@ 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 16 -0:9:21 - PMT 1911 Mar 11 -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 Su>=1 2s 0 - @@ -1919,15 +1212,6 @@ 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 -Z Europe/Gibraltar -0:21:24 - LMT 1880 Au 2 -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 @@ -1947,12 +1231,6 @@ 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 1919 - Ap 15 2 1 S R h 1918 1920 - S M>=15 3 0 - R h 1920 o - Ap 5 2 1 S @@ -1972,12 +1250,6 @@ R h 1980 o - Ap 6 0 1 S R h 1980 o - S 28 1 0 - R h 1981 1983 - Mar lastSu 0 1 S R h 1981 1983 - S lastSu 1 0 - -Z Europe/Budapest 1:16:20 - LMT 1890 N -1 c CE%sT 1918 -1 h CE%sT 1941 Ap 7 23 -1 c CE%sT 1945 -1 h CE%sT 1984 -1 E CE%sT 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 @@ -2019,44 +1291,8 @@ R I 1976 o - May 30 0s 1 S R I 1977 1979 - May Su>=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 D 12 -0:49:56 - RMT 1893 O 31 23u -1 I CE%sT 1943 S 10 -1 c CE%sT 1944 Jun 4 -1 I CE%sT 1980 -1 E CE%sT R LV 1989 1996 - Mar lastSu 2s 1 S R LV 1989 1996 - S lastSu 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 lastSu 2s -2 1 EEST 1989 S lastSu 2s -2 LV EE%sT 1997 Ja 21 -2 E EE%sT 2000 F 29 -2 - EET 2001 Ja 2 -2 E EE%sT -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 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 @@ -2064,22 +1300,8 @@ R MT 1974 o - S 16 0s 0 - R MT 1975 1979 - Ap Su>=15 2 1 S R MT 1975 1980 - S Su>=15 2 0 - R MT 1980 o - Mar 31 2 1 S -Z Europe/Malta 0:58:4 - LMT 1893 N 2 -1 I CE%sT 1973 Mar 31 -1 MT CE%sT 1981 -1 E CE%sT R MD 1997 ma - Mar lastSu 2 1 S R MD 1997 ma - O lastSu 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 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 @@ -2100,15 +1322,6 @@ R O 1959 1961 - O Su>=1 1s 0 - R O 1960 o - Ap 3 1s 1 S R O 1961 1964 - May lastSu 1s 1 S R O 1962 1964 - S lastSu 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 @@ -2157,42 +1370,6 @@ R p 1979 1982 - S lastSu 1s 0 - R p 1980 o - Mar lastSu 0s 1 S R p 1981 1982 - Mar lastSu 1s 1 S R p 1983 o - Mar lastSu 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 Su>=1 0s 0 - R z 1933 1939 - Ap Su>=2 0s 1 S @@ -2202,252 +1379,6 @@ R z 1980 o - Ap 5 23 1 S R z 1980 o - S lastSu 1 0 - R z 1991 1993 - Mar lastSu 0s 1 S R z 1991 1993 - S lastSu 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 Ap 10 -2 O EE%sT 1946 Ap 7 -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 Mar 20 -2 c EE%sT 1994 May -3 c MSK/MSD 1996 Mar 31 0s -3 1 MSD 1996 O 27 3s -3 - MSK 1997 Mar lastSu 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 MSK/MSD 1991 Mar 31 2s -4 - +04 1992 Mar 29 2s -3 R MSK/MSD 2011 Mar 27 2s -4 - MSK 2014 O 26 2s -3 - MSK 2018 O 28 2s -4 - +04 2020 D 27 2s -3 - MSK -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 MSK/MSD 1991 Mar 31 2s -4 - +04 1992 Mar 29 2s -3 R MSK/MSD 2011 Mar 27 2s -4 - MSK 2014 O 26 2s -3 - MSK -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 lastSu 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 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 @@ -2487,30 +1418,8 @@ 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 1901 Ja 1 0u -0 s WE%sT 1940 Mar 16 23 -1 s CE%sT 1979 -1 E CE%sT -Z Africa/Ceuta -0:21:16 - LMT 1901 Ja 1 0u -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 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 @@ -2556,28 +1465,6 @@ R T 1986 1995 - S lastSu 1s 0 - R T 1994 o - Mar 20 1s 1 S R T 1995 2006 - Mar lastSu 1s 1 S R T 1996 2006 - O lastSu 1s 0 - -Z Europe/Istanbul 1:55:52 - LMT 1880 -1:56:56 - IMT 1910 O -2 T EE%sT 1978 Jun 29 -3 T +03/+04 1984 N 1 2 -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 -Z Europe/Kyiv 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 c EE%sT 1996 May 13 -2 E EE%sT R u 1918 1919 - Mar lastSu 2 1 D R u 1918 1919 - O lastSu 2 0 S R u 1942 o - F 9 2 1 W @@ -2591,172 +1478,34 @@ R u 1976 1986 - Ap lastSu 2 1 D R u 1987 2006 - Ap Su>=1 2 1 D R u 2007 ma - Mar Su>=8 2 1 D R u 2007 ma - N Su>=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 lastSu 2 1 D R NY 1920 o - O lastSu 2 0 S R NY 1921 1966 - Ap lastSu 2 1 D R NY 1921 1954 - S lastSu 2 0 S R NY 1955 1966 - O lastSu 2 0 S -Z America/New_York -4:56:2 - LMT 1883 N 18 17u --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 lastSu 2 0 S R Ch 1921 o - Mar lastSu 2 1 D R Ch 1922 1966 - Ap lastSu 2 1 D R Ch 1922 1954 - S lastSu 2 0 S R Ch 1955 1966 - O lastSu 2 0 S -Z America/Chicago -5:50:36 - LMT 1883 N 18 18u --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 19u --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 19u --7 u M%sT 2003 O 26 2 --6 u C%sT -Z America/North_Dakota/Beulah -6:47:7 - LMT 1883 N 18 19u --7 u M%sT 2010 N 7 2 --6 u C%sT R De 1920 1921 - Mar lastSu 2 1 D R De 1920 o - O lastSu 2 0 S R De 1921 o - May 22 2 0 S R De 1965 1966 - Ap lastSu 2 1 D R De 1965 1966 - O lastSu 2 0 S -Z America/Denver -6:59:56 - LMT 1883 N 18 19u --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 lastSu 1 1 D R CA 1950 1961 - S lastSu 2 0 S R CA 1962 1966 - O lastSu 2 0 S -Z America/Los_Angeles -7:52:58 - LMT 1883 N 18 20u --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 2018 N 4 2 --8 - PST 2019 Ja 20 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 19u --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 20u --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 lastSu 2 0 S R In 1946 1954 - Ap lastSu 2 1 D -Z America/Indiana/Indianapolis -5:44:38 - LMT 1883 N 18 18u --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 lastSu 2 1 D R Ma 1951 o - S lastSu 2 0 S R Ma 1954 1960 - Ap lastSu 2 1 D R Ma 1954 1960 - S lastSu 2 0 S -Z America/Indiana/Marengo -5:45:23 - LMT 1883 N 18 18u --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 lastSu 2 1 D R V 1946 o - S lastSu 2 0 S R V 1953 1954 - Ap lastSu 2 1 D @@ -2766,68 +1515,23 @@ R V 1956 1963 - Ap lastSu 2 1 D R V 1960 o - O lastSu 2 0 S R V 1961 o - S lastSu 2 0 S R V 1962 1963 - O lastSu 2 0 S -Z America/Indiana/Vincennes -5:50:7 - LMT 1883 N 18 18u --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 1955 o - May 1 0 1 D R Pe 1955 1960 - S lastSu 2 0 S R Pe 1956 1963 - Ap lastSu 2 1 D R Pe 1961 1963 - O lastSu 2 0 S -Z America/Indiana/Tell_City -5:47:3 - LMT 1883 N 18 18u --6 u C%sT 1946 --6 Pe C%sT 1964 Ap 26 2 --5 - EST 1967 O 29 2 --6 u C%sT 1969 Ap 27 2 --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 lastSu 2 0 S R Pi 1956 1964 - Ap lastSu 2 1 D R Pi 1961 1964 - O lastSu 2 0 S -Z America/Indiana/Petersburg -5:49:7 - LMT 1883 N 18 18u --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 lastSu 2 1 D R St 1947 1954 - S lastSu 2 0 S R St 1955 1956 - O lastSu 2 0 S R St 1957 1958 - S lastSu 2 0 S R St 1959 1961 - O lastSu 2 0 S -Z America/Indiana/Knox -5:46:30 - LMT 1883 N 18 18u --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 lastSu 2 1 D R Pu 1946 1954 - S lastSu 2 0 S R Pu 1955 1956 - O lastSu 2 0 S R Pu 1957 1960 - S lastSu 2 0 S -Z America/Indiana/Winamac -5:46:25 - LMT 1883 N 18 18u --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 18u --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 o - Ap lastSu 2 1 D @@ -2837,41 +1541,12 @@ R v 1946 o - Jun 2 2 0 S R v 1950 1961 - Ap lastSu 2 1 D R v 1950 1955 - S lastSu 2 0 S R v 1956 1961 - O lastSu 2 0 S -Z America/Kentucky/Louisville -5:43:2 - LMT 1883 N 18 18u --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 18u --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 lastSu 2 1 D R Dt 1948 o - S lastSu 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 1967 Jun 14 0:1 --5 u E%sT 1969 --5 - EST 1973 --5 u E%sT 1975 --5 - EST 1975 Ap 27 2 --5 u E%sT R Me 1946 o - Ap lastSu 2 1 D R Me 1946 o - S lastSu 2 0 S R Me 1966 o - Ap lastSu 2 1 D R Me 1966 o - O lastSu 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 @@ -2901,24 +1576,6 @@ R j 1988 o - Ap Su>=1 0:1 2 DD R j 1989 2006 - Ap Su>=1 0:1 1 D R j 2007 2011 - Mar Su>=8 0:1 1 D R j 2007 2010 - N Su>=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 @@ -2960,19 +1617,6 @@ R H 1956 1959 - Ap lastSu 2 1 D R H 1956 1959 - S lastSu 2 0 S R H 1962 1973 - Ap lastSu 2 1 D R H 1962 1973 - O lastSu 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 Su>=8 1 1 D R o 1933 1935 - S Su>=8 1 0 S R o 1936 1938 - Jun Su>=1 1 1 D @@ -2986,15 +1630,6 @@ R o 1946 1956 - S lastSu 2 0 S R o 1957 1972 - O lastSu 2 0 S R o 1993 2006 - Ap Su>=1 0:1 1 D R o 1993 2006 - O lastSu 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 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 @@ -3008,21 +1643,11 @@ R t 1927 1937 - S Su>=25 2 0 S R t 1928 1937 - Ap Su>=25 2 1 D R t 1938 1940 - Ap lastSu 2 1 D R t 1938 1939 - S lastSu 2 0 S -R t 1945 1946 - S lastSu 2 0 S -R t 1946 o - Ap lastSu 2 1 D -R t 1947 1949 - Ap lastSu 0 1 D -R t 1947 1948 - S lastSu 0 0 S -R t 1949 o - N lastSu 0 0 S -R t 1950 1973 - Ap lastSu 2 1 D -R t 1950 o - N lastSu 2 0 S +R t 1945 1948 - S lastSu 2 0 S +R t 1946 1973 - Ap lastSu 2 1 D +R t 1949 1950 - N lastSu 2 0 S R t 1951 1956 - S lastSu 2 0 S R t 1957 1973 - O lastSu 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 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 @@ -3047,9 +1672,6 @@ R W 1963 o - S 22 2 0 S R W 1966 1986 - Ap lastSu 2s 1 D R W 1966 2005 - O lastSu 2s 0 S R W 1987 2005 - Ap Su>=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 Su>=1 0 1 D @@ -3072,14 +1694,6 @@ R Sw 1957 o - O lastSu 2 0 S R Sw 1959 1961 - Ap lastSu 2 1 D R Sw 1959 o - O lastSu 2 0 S R Sw 1960 1961 - S lastSu 2 0 S -Z America/Regina -6:58:36 - LMT 1905 S --7 r M%sT 1960 Ap lastSu 2 --6 - CST -Z America/Swift_Current -7:11:20 - LMT 1905 S --7 C M%sT 1946 Ap lastSu 2 --7 r M%sT 1950 --7 Sw M%sT 1972 Ap lastSu 2 --6 - CST R Ed 1918 1919 - Ap Su>=8 2 1 D R Ed 1918 o - O 27 2 0 S R Ed 1919 o - May 27 2 0 S @@ -3093,9 +1707,6 @@ R Ed 1947 o - Ap lastSu 2 1 D R Ed 1947 o - S lastSu 2 0 S R Ed 1972 1986 - Ap lastSu 2 1 D R Ed 1972 2006 - O lastSu 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 @@ -3105,19 +1716,6 @@ R Va 1946 1986 - Ap lastSu 2 1 D R Va 1946 o - S 29 2 0 S R Va 1947 1961 - S lastSu 2 0 S R Va 1962 2006 - O lastSu 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 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 @@ -3130,42 +1728,6 @@ R Y 1972 2006 - O lastSu 2 0 S R Y 1987 2006 - Ap Su>=1 2 1 D R Yu 1965 o - Ap lastSu 0 2 DD R Yu 1965 o - O lastSu 2 0 S -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/Inuvik 0 - -00 1953 --8 Y P%sT 1979 Ap lastSu 2 --7 Y M%sT 1980 --7 C M%sT -Z America/Whitehorse -9:0:12 - LMT 1900 Au 20 --9 Y Y%sT 1965 --9 Yu Y%sT 1966 F 27 --8 - PST 1980 --8 C P%sT 2020 N --7 - MST -Z America/Dawson -9:17:40 - LMT 1900 Au 20 --9 Y Y%sT 1965 --9 Yu Y%sT 1973 O 28 --8 - PST 1980 --8 C P%sT 2020 N --7 - MST R m 1931 o - May 1 23 1 D R m 1931 o - O 1 0 0 S R m 1939 o - F 5 0 1 D @@ -3182,107 +1744,6 @@ R m 2001 o - May Su>=1 2 1 D R m 2001 o - S lastSu 2 0 S R m 2002 2022 - Ap Su>=1 2 1 D R m 2002 2022 - O lastSu 2 0 S -Z America/Cancun -5:47:4 - LMT 1922 Ja 1 6u --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 6u --6 - CST 1981 D 23 --5 - EST 1982 D 2 --6 m C%sT -Z America/Matamoros -6:30 - LMT 1922 Ja 1 6u --6 - CST 1988 --6 u C%sT 1989 --6 m C%sT 2010 --6 u C%sT -Z America/Monterrey -6:41:16 - LMT 1922 Ja 1 6u --6 - CST 1988 --6 u C%sT 1989 --6 m C%sT -Z America/Mexico_City -6:36:36 - LMT 1922 Ja 1 7u --7 - MST 1927 Jun 10 23 --6 - CST 1930 N 15 --7 m M%sT 1932 Ap --6 m C%sT 2001 S 30 2 --6 - CST 2002 F 20 --6 m C%sT -Z America/Ciudad_Juarez -7:5:56 - LMT 1922 Ja 1 7u --7 - MST 1927 Jun 10 23 --6 - CST 1930 N 15 --7 m M%sT 1932 Ap --6 - CST 1996 --6 m C%sT 1998 --6 - CST 1998 Ap Su>=1 3 --7 m M%sT 2010 --7 u M%sT 2022 O 30 2 --6 - CST 2022 N 30 --7 u M%sT -Z America/Ojinaga -6:57:40 - LMT 1922 Ja 1 7u --7 - MST 1927 Jun 10 23 --6 - CST 1930 N 15 --7 m M%sT 1932 Ap --6 - CST 1996 --6 m C%sT 1998 --6 - CST 1998 Ap Su>=1 3 --7 m M%sT 2010 --7 u M%sT 2022 O 30 2 --6 - CST 2022 N 30 --6 u C%sT -Z America/Chihuahua -7:4:20 - LMT 1922 Ja 1 7u --7 - MST 1927 Jun 10 23 --6 - CST 1930 N 15 --7 m M%sT 1932 Ap --6 - CST 1996 --6 m C%sT 1998 --6 - CST 1998 Ap Su>=1 3 --7 m M%sT 2022 O 30 2 --6 - CST -Z America/Hermosillo -7:23:52 - LMT 1922 Ja 1 7u --7 - MST 1927 Jun 10 23 --6 - CST 1930 N 15 --7 m M%sT 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 1922 Ja 1 7u --7 - MST 1927 Jun 10 23 --6 - CST 1930 N 15 --7 m M%sT 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 1922 Ja 1 7u --7 - MST 1927 Jun 10 23 --6 - CST 1930 N 15 --7 m M%sT 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 7u --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 BB 1942 o - Ap 19 5u 1 D R BB 1942 o - Au 31 6u 0 S R BB 1943 o - May 2 5u 1 D @@ -3294,10 +1755,6 @@ R BB 1977 1978 - O Su>=1 2 0 S R BB 1978 1980 - Ap Su>=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 1911 Au 28 --4 BB A%sT 1944 --4 BB AST/-0330 1945 --4 BB A%sT R BZ 1918 1941 - O Sa>=1 24 0:30 -0530 R BZ 1919 1942 - F Sa>=8 24 0 CST R BZ 1942 o - Jun 27 24 1 CWT @@ -3309,8 +1766,6 @@ 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 R Be 1917 o - Ap 5 24 1 - R Be 1917 o - S 30 24 0 - R Be 1918 o - Ap 13 24 1 - @@ -3327,19 +1782,11 @@ R Be 1948 1952 - May Su>=22 2 1 D R Be 1948 1952 - S Su>=1 2 0 S R Be 1956 o - May Su>=22 2 1 D R Be 1956 o - O lastSu 2 0 S -Z Atlantic/Bermuda -4:19:18 - LMT 1890 --4:19:18 Be BMT/BST 1930 Ja 1 2 --4 Be A%sT 1974 Ap 28 2 --4 C A%sT 1976 --4 u A%sT R CR 1979 1980 - F lastSu 0 1 D R CR 1979 1980 - Jun Su>=1 0 0 S R CR 1991 1992 - Ja Sa>=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 Su>=1 0 1 D @@ -3379,25 +1826,14 @@ R Q 2011 o - N 13 0s 0 S R Q 2012 o - Ap 1 0s 1 D R Q 2012 ma - N Su>=1 0s 0 S R Q 2013 ma - Mar Su>=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 lastSu 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 Su>=1 0 1 D R SV 1987 1988 - S lastSu 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 @@ -3406,8 +1842,6 @@ 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 lastSu 0 1 D R HT 1983 1987 - O lastSu 0 0 S @@ -3419,57 +1853,16 @@ R HT 2012 2015 - Mar Su>=8 2 1 D R HT 2012 2015 - N Su>=1 2 0 S R HT 2017 ma - Mar Su>=8 2 1 D R HT 2017 ma - N Su>=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 Su>=1 0 1 D R HN 1987 1988 - S lastSu 0 0 S R HN 2006 o - May Su>=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 Su>=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 Su>=1 0 0 S R NI 2006 o - Ap 30 2 1 D R NI 2006 o - O Su>=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 -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 Mar 8 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 - @@ -3499,150 +1892,8 @@ R A 2000 o - Mar 3 0 0 - R A 2007 o - D 30 0 1 - R A 2008 2009 - Mar Su>=15 0 0 - R A 2008 o - O Su>=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 Su>=8 0 0 - R Sa 2007 2008 - O Su>=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 -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 - @@ -3700,90 +1951,6 @@ R B 2013 2014 - F Su>=15 0 0 - R B 2015 o - F Su>=22 0 0 - R B 2016 2019 - F Su>=15 0 0 - R B 2018 o - N Su>=1 0 1 - -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 - @@ -3820,56 +1987,10 @@ R x 2019 ma - Ap Su>=2 3u 0 - R x 2019 2021 - S Su>=2 4u 1 - R x 2022 o - S Su>=9 4u 1 - R x 2023 ma - S Su>=2 4u 1 - -Z America/Santiago -4:42:45 - LMT 1890 --4:42:45 - SMT 1910 Ja 10 --5 - -05 1916 Jul --4:42:45 - SMT 1918 S 10 --4 - -04 1919 Jul --4:42:45 - SMT 1927 S --5 x -05/-04 1932 S --4 - -04 1942 Jun --5 - -05 1942 Au --4 - -04 1946 Jul 14 24 --4 1 -03 1946 Au 28 24 --5 1 -04 1947 Mar 31 24 --5 - -05 1947 May 21 23 --4 x -04/-03 -Z America/Punta_Arenas -4:43:40 - LMT 1890 --4:42:45 - SMT 1910 Ja 10 --5 - -05 1916 Jul --4:42:45 - SMT 1918 S 10 --4 - -04 1919 Jul --4:42:45 - SMT 1927 S --5 x -05/-04 1932 S --4 - -04 1942 Jun --5 - -05 1942 Au --4 - -04 1946 Au 28 24 --5 1 -04 1947 Mar 31 24 --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 - F 6 24 0 - -Z America/Bogota -4:56:16 - LMT 1884 Mar 13 --4:56:16 - BMT 1914 N 23 --5 CO -05/-04 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 lastSu 0 1 - R FK 1938 1942 - Mar Su>=19 0 0 - R FK 1939 o - O 1 0 1 - @@ -3882,20 +2003,6 @@ R FK 1985 2000 - S Su>=9 0 1 - R FK 1986 2000 - Ap Su>=16 0 0 - R FK 2001 2010 - Ap Su>=15 2 0 - R FK 2001 2010 - S Su>=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:39 - LMT 1911 Au --4 - -04 1915 Mar --3:45 - -0345 1975 Au --3 - -03 1992 Mar 29 1 --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 - @@ -3918,11 +2025,6 @@ R y 2005 2009 - Mar Su>=8 0 0 - R y 2010 ma - O Su>=1 0 1 - R y 2010 2012 - Ap Su>=8 0 0 - R y 2013 ma - Mar Su>=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 lastSu 0 1 - @@ -3933,16 +2035,6 @@ 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 R U 1923 1925 - O 1 0 0:30 - R U 1924 1926 - Ap 1 0 0 - R U 1933 1938 - O lastSu 0 0:30 - @@ -3991,6 +2083,659 @@ R U 2005 o - Mar 27 2 0 - R U 2005 o - O 9 2 1 - R U 2006 2015 - Mar Su>=8 2 0 - R U 2006 2014 - O Su>=1 2 1 - +Z Africa/Abidjan -0:16:8 - LMT 1912 +0 - GMT +Z Africa/Algiers 0:12:12 - LMT 1891 Mar 16 +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 Africa/Bissau -1:2:20 - LMT 1912 Ja 1 1u +-1 - -01 1975 +0 - GMT +Z Africa/Cairo 2:5:9 - LMT 1900 O +2 K EE%sT +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 28 3 +1 M +01/+00 +Z Africa/Ceuta -0:21:16 - LMT 1901 Ja 1 0u +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 Africa/El_Aaiun -0:52:48 - LMT 1934 +-1 - -01 1976 Ap 14 +0 M +00/+01 2018 O 28 3 +1 M +01/+00 +Z Africa/Johannesburg 1:52 - LMT 1892 F 8 +1:30 - SAST 1903 Mar +2 SA SAST +Z Africa/Juba 2:6:28 - LMT 1931 +2 SD CA%sT 2000 Ja 15 12 +3 - EAT 2021 F +2 - CAT +Z Africa/Khartoum 2:10:8 - LMT 1931 +2 SD CA%sT 2000 Ja 15 12 +3 - EAT 2017 N +2 - CAT +Z Africa/Lagos 0:13:35 - LMT 1905 Jul +0 - GMT 1908 Jul +0:13:35 - LMT 1914 +0:30 - +0030 1919 S +1 - WAT +Z Africa/Maputo 2:10:20 - LMT 1903 Mar +2 - CAT +Z Africa/Monrovia -0:43:8 - LMT 1882 +-0:43:8 - MMT 1919 Mar +-0:44:30 - MMT 1972 Ja 7 +0 - GMT +Z Africa/Nairobi 2:27:16 - LMT 1908 May +2:30 - +0230 1928 Jun 30 24 +3 - EAT 1930 Ja 4 24 +2:30 - +0230 1936 D 31 24 +2:45 - +0245 1942 Jul 31 24 +3 - EAT +Z Africa/Ndjamena 1:0:12 - LMT 1912 +1 - WAT 1979 O 14 +1 1 WAST 1980 Mar 8 +1 - WAT +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 2019 Ja 1 2 +0 - GMT +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 +Z Africa/Tunis 0:40:44 - LMT 1881 May 12 +0:9:21 - PMT 1911 Mar 11 +1 n CE%sT +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 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 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/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/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/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/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/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/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/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 +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/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/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/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/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/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 +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 +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/Bahia_Banderas -7:1 - LMT 1922 Ja 1 7u +-7 - MST 1927 Jun 10 23 +-6 - CST 1930 N 15 +-7 m M%sT 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/Barbados -3:58:29 - LMT 1911 Au 28 +-4 BB A%sT 1944 +-4 BB AST/-0330 1945 +-4 BB A%sT +Z America/Belem -3:13:56 - LMT 1914 +-3 B -03/-02 1988 S 12 +-3 - -03 +Z America/Belize -5:52:48 - LMT 1912 Ap +-6 BZ %s +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/Bogota -4:56:16 - LMT 1884 Mar 13 +-4:56:16 - BMT 1914 N 23 +-5 CO -05/-04 +Z America/Boise -7:44:49 - LMT 1883 N 18 20u +-8 u P%sT 1923 May 13 2 +-7 u M%sT 1974 +-7 - MST 1974 F 3 2 +-7 u M%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/Campo_Grande -3:38:28 - LMT 1914 +-4 B -04/-03 +Z America/Cancun -5:47:4 - LMT 1922 Ja 1 6u +-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/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 America/Cayenne -3:29:20 - LMT 1911 Jul +-4 - -04 1967 O +-3 - -03 +Z America/Chicago -5:50:36 - LMT 1883 N 18 18u +-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/Chihuahua -7:4:20 - LMT 1922 Ja 1 7u +-7 - MST 1927 Jun 10 23 +-6 - CST 1930 N 15 +-7 m M%sT 1932 Ap +-6 - CST 1996 +-6 m C%sT 1998 +-6 - CST 1998 Ap Su>=1 3 +-7 m M%sT 2022 O 30 2 +-6 - CST +Z America/Ciudad_Juarez -7:5:56 - LMT 1922 Ja 1 7u +-7 - MST 1927 Jun 10 23 +-6 - CST 1930 N 15 +-7 m M%sT 1932 Ap +-6 - CST 1996 +-6 m C%sT 1998 +-6 - CST 1998 Ap Su>=1 3 +-7 m M%sT 2010 +-7 u M%sT 2022 O 30 2 +-6 - CST 2022 N 30 +-7 u M%sT +Z America/Costa_Rica -5:36:13 - LMT 1890 +-5:36:13 - SJMT 1921 Ja 15 +-6 CR C%sT +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/Danmarkshavn -1:14:40 - LMT 1916 Jul 28 +-3 - -03 1980 Ap 6 2 +-3 E -03/-02 1996 +0 - GMT +Z America/Dawson -9:17:40 - LMT 1900 Au 20 +-9 Y Y%sT 1965 +-9 Yu Y%sT 1973 O 28 +-8 - PST 1980 +-8 C P%sT 2020 N +-7 - MST +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/Denver -6:59:56 - LMT 1883 N 18 19u +-7 u M%sT 1920 +-7 De M%sT 1942 +-7 u M%sT 1946 +-7 De M%sT 1967 +-7 u M%sT +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 1967 Jun 14 0:1 +-5 u E%sT 1969 +-5 - EST 1973 +-5 u E%sT 1975 +-5 - EST 1975 Ap 27 2 +-5 u E%sT +Z America/Edmonton -7:33:52 - LMT 1906 S +-7 Ed M%sT 1987 +-7 C M%sT +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/El_Salvador -5:56:48 - LMT 1921 +-6 SV C%sT +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/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/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 +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 +Z America/Grand_Turk -4:44:32 - LMT 1890 +-5:7:10 - KMT 1912 F +-5 - EST 1979 +-5 u E%sT 2015 Mar 8 2 +-4 - AST 2018 Mar 11 3 +-5 u E%sT +Z America/Guatemala -6:2:4 - LMT 1918 O 5 +-6 GT C%sT +Z America/Guayaquil -5:19:20 - LMT 1890 +-5:14 - QMT 1931 +-5 EC -05/-04 +Z America/Guyana -3:52:39 - LMT 1911 Au +-4 - -04 1915 Mar +-3:45 - -0345 1975 Au +-3 - -03 1992 Mar 29 1 +-4 - -04 +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/Havana -5:29:28 - LMT 1890 +-5:29:36 - HMT 1925 Jul 19 12 +-5 Q C%sT +Z America/Hermosillo -7:23:52 - LMT 1922 Ja 1 7u +-7 - MST 1927 Jun 10 23 +-6 - CST 1930 N 15 +-7 m M%sT 1932 Ap +-6 - CST 1942 Ap 24 +-7 - MST 1949 Ja 14 +-8 - PST 1970 +-7 m M%sT 1999 +-7 - MST +Z America/Indiana/Indianapolis -5:44:38 - LMT 1883 N 18 18u +-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 +Z America/Indiana/Knox -5:46:30 - LMT 1883 N 18 18u +-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 +Z America/Indiana/Marengo -5:45:23 - LMT 1883 N 18 18u +-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 +Z America/Indiana/Petersburg -5:49:7 - LMT 1883 N 18 18u +-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 +Z America/Indiana/Tell_City -5:47:3 - LMT 1883 N 18 18u +-6 u C%sT 1946 +-6 Pe C%sT 1964 Ap 26 2 +-5 - EST 1967 O 29 2 +-6 u C%sT 1969 Ap 27 2 +-5 u E%sT 1971 +-5 - EST 2006 Ap 2 2 +-6 u C%sT +Z America/Indiana/Vevay -5:40:16 - LMT 1883 N 18 18u +-6 u C%sT 1954 Ap 25 2 +-5 - EST 1969 +-5 u E%sT 1973 +-5 - EST 2006 +-5 u E%sT +Z America/Indiana/Vincennes -5:50:7 - LMT 1883 N 18 18u +-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 +Z America/Indiana/Winamac -5:46:25 - LMT 1883 N 18 18u +-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/Inuvik 0 - -00 1953 +-8 Y P%sT 1979 Ap lastSu 2 +-7 Y M%sT 1980 +-7 C M%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/Jamaica -5:7:10 - LMT 1890 +-5:7:10 - KMT 1912 F +-5 - EST 1974 +-5 u E%sT 1984 +-5 - EST +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/Kentucky/Louisville -5:43:2 - LMT 1883 N 18 18u +-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 18u +-6 u C%sT 1946 +-6 - CST 1968 +-6 u C%sT 2000 O 29 2 +-5 u E%sT +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 +Z America/Lima -5:8:12 - LMT 1890 +-5:8:36 - LMT 1908 Jul 28 +-5 PE -05/-04 +Z America/Los_Angeles -7:52:58 - LMT 1883 N 18 20u +-8 u P%sT 1946 +-8 CA P%sT 1967 +-8 u P%sT +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/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/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/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 +Z America/Matamoros -6:30 - LMT 1922 Ja 1 6u +-6 - CST 1988 +-6 u C%sT 1989 +-6 m C%sT 2010 +-6 u C%sT +Z America/Mazatlan -7:5:40 - LMT 1922 Ja 1 7u +-7 - MST 1927 Jun 10 23 +-6 - CST 1930 N 15 +-7 m M%sT 1932 Ap +-6 - CST 1942 Ap 24 +-7 - MST 1949 Ja 14 +-8 - PST 1970 +-7 m M%sT +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 +Z America/Merida -5:58:28 - LMT 1922 Ja 1 6u +-6 - CST 1981 D 23 +-5 - EST 1982 D 2 +-6 m C%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 2018 N 4 2 +-8 - PST 2019 Ja 20 2 +-9 u AK%sT +Z America/Mexico_City -6:36:36 - LMT 1922 Ja 1 7u +-7 - MST 1927 Jun 10 23 +-6 - CST 1930 N 15 +-7 m M%sT 1932 Ap +-6 m C%sT 2001 S 30 2 +-6 - CST 2002 F 20 +-6 m C%sT +Z America/Miquelon -3:44:40 - LMT 1911 Jun 15 +-4 - AST 1980 May +-3 - -03 1987 +-3 C -03/-02 +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/Monterrey -6:41:16 - LMT 1922 Ja 1 6u +-6 - CST 1988 +-6 u C%sT 1989 +-6 m C%sT Z America/Montevideo -3:44:51 - LMT 1908 Jun 10 -3:44:51 - MMT 1920 May -4 - -04 1923 O @@ -4002,30 +2747,842 @@ Z America/Montevideo -3:44:51 - LMT 1908 Jun 10 -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 +Z America/New_York -4:56:2 - LMT 1883 N 18 17u +-5 u E%sT 1920 +-5 NY E%sT 1942 +-5 u E%sT 1946 +-5 NY E%sT 1967 +-5 u E%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/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/North_Dakota/Beulah -6:47:7 - LMT 1883 N 18 19u +-7 u M%sT 2010 N 7 2 +-6 u C%sT +Z America/North_Dakota/Center -6:45:12 - LMT 1883 N 18 19u +-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 19u +-7 u M%sT 2003 O 26 2 +-6 u C%sT +Z America/Nuuk -3:26:56 - LMT 1916 Jul 28 +-3 - -03 1980 Ap 6 2 +-3 E -03/-02 2023 Mar 26 1u +-2 - -02 2023 O 29 1u +-2 E -02/-01 +Z America/Ojinaga -6:57:40 - LMT 1922 Ja 1 7u +-7 - MST 1927 Jun 10 23 +-6 - CST 1930 N 15 +-7 m M%sT 1932 Ap +-6 - CST 1996 +-6 m C%sT 1998 +-6 - CST 1998 Ap Su>=1 3 +-7 m M%sT 2010 +-7 u M%sT 2022 O 30 2 +-6 - CST 2022 N 30 +-6 u C%sT +Z America/Panama -5:18:8 - LMT 1890 +-5:19:36 - CMT 1908 Ap 22 +-5 - EST +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/Phoenix -7:28:18 - LMT 1883 N 18 19u +-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/Port-au-Prince -4:49:20 - LMT 1890 +-4:49 - PPMT 1917 Ja 24 12 +-5 HT E%sT +Z America/Porto_Velho -4:15:36 - LMT 1914 +-4 B -04/-03 1988 S 12 -4 - -04 -Z Etc/UTC 0 - UTC +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/Punta_Arenas -4:43:40 - LMT 1890 +-4:42:45 - SMT 1910 Ja 10 +-5 - -05 1916 Jul +-4:42:45 - SMT 1918 S 10 +-4 - -04 1919 Jul +-4:42:45 - SMT 1927 S +-5 x -05/-04 1932 S +-4 - -04 1942 Jun +-5 - -05 1942 Au +-4 - -04 1946 Au 28 24 +-5 1 -04 1947 Mar 31 24 +-5 - -05 1947 May 21 23 +-4 x -04/-03 2016 D 4 +-3 - -03 +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/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/Regina -6:58:36 - LMT 1905 S +-7 r M%sT 1960 Ap lastSu 2 +-6 - CST +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/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 +Z America/Santarem -3:38:48 - LMT 1914 +-4 B -04/-03 1988 S 12 +-4 - -04 2008 Jun 24 +-3 - -03 +Z America/Santiago -4:42:45 - LMT 1890 +-4:42:45 - SMT 1910 Ja 10 +-5 - -05 1916 Jul +-4:42:45 - SMT 1918 S 10 +-4 - -04 1919 Jul +-4:42:45 - SMT 1927 S +-5 x -05/-04 1932 S +-4 - -04 1942 Jun +-5 - -05 1942 Au +-4 - -04 1946 Jul 14 24 +-4 1 -03 1946 Au 28 24 +-5 1 -04 1947 Mar 31 24 +-5 - -05 1947 May 21 23 +-4 x -04/-03 +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 +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/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 2024 Mar 31 +-2 E -02/-01 +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/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/Swift_Current -7:11:20 - LMT 1905 S +-7 C M%sT 1946 Ap lastSu 2 +-7 r M%sT 1950 +-7 Sw M%sT 1972 Ap lastSu 2 +-6 - CST +Z America/Tegucigalpa -5:48:52 - LMT 1921 Ap +-6 HN C%sT +Z America/Thule -4:35:8 - LMT 1916 Jul 28 +-4 Th A%sT +Z America/Tijuana -7:48:4 - LMT 1922 Ja 1 7u +-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 +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/Vancouver -8:12:28 - LMT 1884 +-8 Va P%sT 1987 +-8 C P%sT +Z America/Whitehorse -9:0:12 - LMT 1900 Au 20 +-9 Y Y%sT 1965 +-9 Yu Y%sT 1966 F 27 +-8 - PST 1980 +-8 C P%sT 2020 N +-7 - MST +Z America/Winnipeg -6:28:36 - LMT 1887 Jul 16 +-6 W C%sT 2006 +-6 C C%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 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 2018 O 7 4 +11 - +11 2019 Mar 17 3 +8 - +08 2019 O 4 3 +11 - +11 2020 Mar 8 3 +8 - +08 2020 O 4 0:1 +11 - +11 2021 Mar 14 +8 - +08 2021 O 3 0:1 +11 - +11 2022 Mar 13 +8 - +08 2022 O 2 0:1 +11 - +11 2023 Mar 9 3 +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/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 +10 1 AEDT 2011 +10 AT AE%sT +Z Antarctica/Mawson 0 - -00 1954 F 13 +6 - +06 2009 O 18 2 +5 - +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 +Z Antarctica/Rothera 0 - -00 1976 D +-3 - -03 +Z Antarctica/Troll 0 - -00 2005 F 12 +0 Tr %s +Z Antarctica/Vostok 0 - -00 1957 D 16 +7 - +07 1994 F +0 - -00 1994 N +7 - +07 2023 D 18 2 +5 - +05 +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 2024 Mar +5 - +05 +Z Asia/Amman 2:23:44 - LMT 1931 +2 J EE%sT 2022 O 28 0s +3 - +03 +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 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/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/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/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/Baghdad 2:57:40 - LMT 1890 +2:57:36 - BMT 1918 +3 - +03 1982 May +3 IQ +03/+04 +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 lastSu 2s +4 - +04 1996 +4 E +04/+05 1997 +4 AZ +04/+05 +Z Asia/Bangkok 6:42:4 - LMT 1880 +6:42:4 - BMT 1920 Ap +7 - +07 +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/Beirut 2:22 - LMT 1880 +2 l EE%sT +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 +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/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/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 +Z Asia/Damascus 2:25:12 - LMT 1920 +2 S EE%sT 2022 O 28 +3 - +03 +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/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/Dubai 3:41:12 - LMT 1920 +4 - +04 +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 +06 1991 S 9 2s +5 - +05 +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 +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 +Z Asia/Ho_Chi_Minh 7:6:30 - 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 1 24 +7 - +07 1947 Ap +8 - +08 1955 Jul 1 1 +7 - +07 1959 D 31 23 +8 - +08 1975 Jun 13 +7 - +07 +Z Asia/Hong_Kong 7:36:42 - LMT 1904 O 29 17u +8 - HKT 1941 Jun 15 3 +8 1 HKST 1941 O 1 4 +8 0:30 HKWT 1941 D 25 +9 - JST 1945 N 18 2 +8 HK HK%sT +Z Asia/Hovd 6:6:36 - LMT 1905 Au +6 - +06 1978 +7 X +07/+08 +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/Jakarta 7:7:12 - LMT 1867 Au 10 +7:7:12 - BMT 1923 D 31 16:40u +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/Jayapura 9:22:48 - LMT 1932 N +9 - +09 1944 S +9:30 - +0930 1964 +9 - WIT +Z Asia/Jerusalem 2:20:54 - LMT 1880 +2:20:40 - JMT 1918 +2 Z I%sT +Z Asia/Kabul 4:36:48 - LMT 1890 +4 - +04 1945 +4:30 - +0430 +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/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 +Z Asia/Kathmandu 5:41:16 - LMT 1920 +5:30 - +0530 1986 +5:45 - +0545 +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/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/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/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 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 +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/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/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/Nicosia 2:13:28 - LMT 1921 N 14 +2 CY EE%sT 1998 S +2 E EE%sT +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/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/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/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 +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/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 +Z Asia/Qatar 3:26:8 - LMT 1920 +4 - +04 1972 Jun +3 - +03 +Z Asia/Qostanay 4:14:28 - 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 +6 - +06 2024 Mar +5 - +05 +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 2018 D 21 +5 - +05 +Z Asia/Riyadh 3:6:52 - LMT 1947 Mar 14 +3 - +03 +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 lastSu 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/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/Seoul 8:27:52 - LMT 1908 Ap +8:30 - KST 1912 +9 - JST 1945 S 8 +9 KR K%sT 1954 Mar 21 +8:30 KR K%sT 1961 Au 10 +9 KR K%sT +Z Asia/Shanghai 8:5:43 - LMT 1901 +8 Sh C%sT 1949 May 28 +8 CN C%sT +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 1981 D 31 16u +8 - +08 +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/Taipei 8:6 - LMT 1896 +8 - CST 1937 O +9 - JST 1945 S 21 1 +8 f C%sT +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/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 lastSu +4 e +04/+05 1996 O lastSu +4 1 +05 1997 Mar lastSu +4 e +04/+05 2004 Jun 27 +3 R +03/+04 2005 Mar lastSu 2 +4 - +04 +Z Asia/Tehran 3:25:44 - LMT 1916 +3:25:44 - TMT 1935 Jun 13 +3:30 i +0330/+0430 1977 O 20 24 +4 i +04/+05 1979 +3:30 i +0330/+0430 +Z Asia/Thimphu 5:58:36 - LMT 1947 Au 15 +5:30 - +0530 1987 O +6 - +06 +Z Asia/Tokyo 9:18:59 - LMT 1887 D 31 15u +9 JP J%sT +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/Ulaanbaatar 7:7:32 - LMT 1905 Au +7 - +07 1978 +8 X +08/+09 +Z Asia/Urumqi 5:50:20 - LMT 1928 +6 - +06 +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/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/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/Yangon 6:24:47 - LMT 1880 +6:24:47 - RMT 1920 +6:30 - +0630 1942 May +9 - +09 1945 May 3 +6:30 - +0630 +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/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 +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/Bermuda -4:19:18 - LMT 1890 +-4:19:18 Be BMT/BST 1930 Ja 1 2 +-4 Be A%sT 1974 Ap 28 2 +-4 C A%sT 1976 +-4 u A%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 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 Atlantic/Faroe -0:27:4 - LMT 1908 Ja 11 +0 - WET 1981 +0 E WE%sT +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 +Z Atlantic/South_Georgia -2:26:8 - LMT 1890 +-2 - -02 +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 Australia/Adelaide 9:14:20 - LMT 1895 F +9 - ACST 1899 May +9:30 AU AC%sT 1971 +9:30 AS AC%sT +Z Australia/Brisbane 10:12:8 - LMT 1895 +10 AU AE%sT 1971 +10 AQ 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 +Z Australia/Darwin 8:43:20 - LMT 1895 F +9 - ACST 1899 May +9:30 AU AC%sT +Z Australia/Eucla 8:35:28 - LMT 1895 D +8:45 AU +0845/+0945 1943 Jul +8:45 AW +0845/+0945 +Z Australia/Hobart 9:49:16 - LMT 1895 S +10 AT AE%sT 1919 O 24 +10 AU AE%sT 1967 +10 AT 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 +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 Australia/Melbourne 9:39:52 - LMT 1895 F +10 AU AE%sT 1971 +10 AV AE%sT +Z Australia/Perth 7:43:24 - LMT 1895 D +8 AU AW%sT 1943 Jul +8 AW AW%sT +Z Australia/Sydney 10:4:52 - LMT 1895 F +10 AU AE%sT 1971 +10 AN AE%sT +Z CET 1 c CE%sT +Z CST6CDT -6 u C%sT +Z EET 2 E EE%sT +Z EST -5 - EST +Z EST5EDT -5 u E%sT Z Etc/GMT 0 - GMT -L Etc/GMT GMT -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+10 -10 - -10 +Z Etc/GMT+11 -11 - -11 +Z Etc/GMT+12 -12 - -12 Z Etc/GMT+2 -2 - -02 Z Etc/GMT+3 -3 - -03 Z Etc/GMT+4 -4 - -04 @@ -4034,10 +3591,463 @@ 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 Etc/GMT-1 1 - +01 +Z Etc/GMT-10 10 - +10 +Z Etc/GMT-11 11 - +11 +Z Etc/GMT-12 12 - +12 +Z Etc/GMT-13 13 - +13 +Z Etc/GMT-14 14 - +14 +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/UTC 0 - UTC +Z Europe/Andorra 0:6:4 - LMT 1901 +0 - WET 1946 S 30 +1 - CET 1985 Mar 31 2 +1 E CE%sT +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/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 +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 +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 +Z Europe/Brussels 0:17:30 - LMT 1880 +0:17:30 - BMT 1892 May 1 0:17:30 +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 +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/Budapest 1:16:20 - LMT 1890 N +1 c CE%sT 1918 +1 h CE%sT 1941 Ap 7 23 +1 c CE%sT 1945 +1 h CE%sT 1984 +1 E CE%sT +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/Dublin -0:25:21 - 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 +Z Europe/Gibraltar -0:21:24 - LMT 1880 Au 2 +0 G %s 1957 Ap 14 2 +1 - CET 1982 +1 E CE%sT +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 +Z Europe/Istanbul 1:55:52 - LMT 1880 +1:56:56 - IMT 1910 O +2 T EE%sT 1978 Jun 29 +3 T +03/+04 1984 N 1 2 +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 +Z Europe/Kaliningrad 1:22 - LMT 1893 Ap +1 c CE%sT 1945 Ap 10 +2 O EE%sT 1946 Ap 7 +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/Kirov 3:18:48 - LMT 1919 Jul 1 0u +3 - +03 1930 Jun 21 +4 R +04/+05 1989 Mar 26 2s +3 R MSK/MSD 1991 Mar 31 2s +4 - +04 1992 Mar 29 2s +3 R MSK/MSD 2011 Mar 27 2s +4 - MSK 2014 O 26 2s +3 - MSK +Z Europe/Kyiv 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 c EE%sT 1996 May 13 +2 E EE%sT +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 Europe/London -0:1:15 - LMT 1847 D +0 G %s 1968 O 27 +1 - BST 1971 O 31 2u +0 G %s 1996 +0 E GMT/BST +Z Europe/Madrid -0:14:44 - LMT 1901 Ja 1 0u +0 s WE%sT 1940 Mar 16 23 +1 s CE%sT 1979 +1 E CE%sT +Z Europe/Malta 0:58:4 - LMT 1893 N 2 +1 I CE%sT 1973 Mar 31 +1 MT 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 +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/Paris 0:9:21 - LMT 1891 Mar 16 +0:9:21 - PMT 1911 Mar 11 +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 +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 +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 lastSu 2s +2 1 EEST 1989 S lastSu 2s +2 LV EE%sT 1997 Ja 21 +2 E EE%sT 2000 F 29 +2 - EET 2001 Ja 2 +2 E EE%sT +Z Europe/Rome 0:49:56 - LMT 1866 D 12 +0:49:56 - RMT 1893 O 31 23u +1 I CE%sT 1943 S 10 +1 c CE%sT 1944 Jun 4 +1 I CE%sT 1980 +1 E CE%sT +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/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/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 Mar 20 +2 c EE%sT 1994 May +3 c MSK/MSD 1996 Mar 31 0s +3 1 MSD 1996 O 27 3s +3 - MSK 1997 Mar lastSu 1u +2 E EE%sT 2014 Mar 30 2 +4 - MSK 2014 O 26 2s +3 - MSK +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 +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 +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/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 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/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 +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 MSK/MSD 1991 Mar 31 2s +4 - +04 1992 Mar 29 2s +3 R MSK/MSD 2011 Mar 27 2s +4 - MSK 2014 O 26 2s +3 - MSK 2018 O 28 2s +4 - +04 2020 D 27 2s +3 - MSK +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 +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 Z Factory 0 - -00 +Z HST -10 - HST +Z Indian/Chagos 4:49:40 - LMT 1907 +5 - +05 1996 +6 - +06 +Z Indian/Maldives 4:54 - LMT 1880 +4:54 - MMT 1960 +5 - +05 +Z Indian/Mauritius 3:50 - LMT 1907 +4 MU +04/+05 +Z MET 1 c ME%sT +Z MST -7 - MST +Z MST7MDT -7 u M%sT +Z PST8PDT -8 u P%sT +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/Auckland 11:39:4 - LMT 1868 N 2 +11:30 NZ NZ%sT 1946 +12 NZ NZ%sT +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/Chatham 12:13:48 - LMT 1868 N 2 +12:15 - +1215 1946 +12:45 k +1245/+1345 +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 Pacific/Efate 11:13:16 - LMT 1912 Ja 13 +11 VU +11/+12 +Z Pacific/Fakaofo -11:24:56 - LMT 1901 +-11 - -11 2011 D 30 +13 - +13 +Z Pacific/Fiji 11:55:44 - LMT 1915 O 26 +12 FJ +12/+13 +Z Pacific/Galapagos -5:58:24 - LMT 1931 +-5 - -05 1986 +-6 EC -06/-05 +Z Pacific/Gambier -8:59:48 - LMT 1912 O +-9 - -09 +Z Pacific/Guadalcanal 10:39:48 - LMT 1912 O +11 - +11 +Z Pacific/Guam -14:21 - LMT 1844 D 31 +9:39 - LMT 1901 +10 - GST 1941 D 10 +9 - +09 1944 Jul 31 +10 Gu G%sT 2000 D 23 +10 - ChST +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 Pacific/Kanton 0 - -00 1937 Au 31 +-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/Kosrae -13:8:4 - LMT 1844 D 31 +10:51:56 - LMT 1901 +11 - +11 1914 O +9 - +09 1919 F +11 - +11 1937 +10 - +10 1941 Ap +9 - +09 1945 Au +11 - +11 1969 O +12 - +12 1999 +11 - +11 +Z Pacific/Kwajalein 11:9:20 - LMT 1901 +11 - +11 1937 +10 - +10 1941 Ap +9 - +09 1944 F 6 +11 - +11 1969 O +-12 - -12 1993 Au 20 24 +12 - +12 +Z Pacific/Marquesas -9:18 - LMT 1912 O +-9:30 - -0930 +Z Pacific/Nauru 11:7:40 - LMT 1921 Ja 15 +11:30 - +1130 1942 Au 29 +9 - +09 1945 S 8 +11:30 - +1130 1979 F 10 2 +12 - +12 +Z Pacific/Niue -11:19:40 - LMT 1952 O 16 +-11:20 - -1120 1964 Jul +-11 - -11 +Z Pacific/Norfolk 11:11:52 - LMT 1901 +11:12 - +1112 1951 +11:30 - +1130 1974 O 27 2s +11:30 1 +1230 1975 Mar 2 2s +11:30 - +1130 2015 O 4 2s +11 - +11 2019 Jul +11 AN +11/+12 +Z Pacific/Noumea 11:5:48 - LMT 1912 Ja 13 +11 NC +11/+12 +Z Pacific/Pago_Pago 12:37:12 - LMT 1892 Jul 5 +-11:22:48 - LMT 1911 +-11 - SST +Z Pacific/Palau -15:2:4 - LMT 1844 D 31 +8:57:56 - LMT 1901 +9 - +09 +Z Pacific/Pitcairn -8:40:20 - LMT 1901 +-8:30 - -0830 1998 Ap 27 +-8 - -08 +Z Pacific/Port_Moresby 9:48:40 - LMT 1880 +9:48:32 - PMMT 1895 +10 - +10 +Z Pacific/Rarotonga 13:20:56 - LMT 1899 D 26 +-10:39:4 - LMT 1952 O 16 +-10:30 - -1030 1978 N 12 +-10 CK -10/-0930 +Z Pacific/Tahiti -9:58:16 - LMT 1912 O +-10 - -10 +Z Pacific/Tarawa 11:32:4 - LMT 1901 +12 - +12 +Z Pacific/Tongatapu 12:19:12 - LMT 1945 S 10 +12:20 - +1220 1961 +13 - +13 1999 +13 TO +13/+14 +Z WET 0 E WE%sT +L Etc/GMT GMT L Australia/Sydney Australia/ACT L Australia/Lord_Howe Australia/LHI L Australia/Sydney Australia/NSW @@ -4185,7 +4195,6 @@ L America/Puerto_Rico America/Tortola L Pacific/Port_Moresby Antarctica/DumontDUrville L Pacific/Auckland Antarctica/McMurdo L Asia/Riyadh Antarctica/Syowa -L Asia/Urumqi Antarctica/Vostok L Europe/Berlin Arctic/Longyearbyen L Asia/Riyadh Asia/Aden L Asia/Qatar Asia/Bahrain diff --git a/lib/pytz/zoneinfo/zone.tab b/lib/pytz/zoneinfo/zone.tab index dbcb6179..3fa9306a 100644 --- a/lib/pytz/zoneinfo/zone.tab +++ b/lib/pytz/zoneinfo/zone.tab @@ -48,7 +48,7 @@ AR -3124-06411 America/Argentina/Cordoba Argentina (most areas: CB, CC, CN, ER, 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 -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) @@ -87,7 +87,7 @@ 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 -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 @@ -107,21 +107,21 @@ 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 +4734-05243 America/St_Johns Newfoundland, Labrador (SE) +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 +4339-07923 America/Toronto Eastern - ON & QC (most areas) CA +6344-06828 America/Iqaluit Eastern - NU (most areas) -CA +484531-0913718 America/Atikokan EST - ON (Atikokan); NU (Coral H) -CA +4953-09709 America/Winnipeg Central - ON (west); Manitoba +CA +484531-0913718 America/Atikokan EST - ON (Atikokan), NU (Coral H) +CA +4953-09709 America/Winnipeg Central - ON (west), Manitoba 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); NT (E); SK (W) +CA +5333-11328 America/Edmonton Mountain - AB, BC(E), NT(E), SK(W) CA +690650-1050310 America/Cambridge_Bay Mountain - NU (west) CA +682059-1334300 America/Inuvik Mountain - NT (west) CA +4906-11631 America/Creston MST - BC (Creston) @@ -207,8 +207,8 @@ 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 +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 @@ -355,7 +355,7 @@ 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); N Kuril Is +RU +6728+15343 Asia/Srednekolymsk MSK+08 - Sakha (E), N Kuril Is RU +5301+15839 Asia/Kamchatka MSK+09 - Kamchatka RU +6445+17729 Asia/Anadyr MSK+09 - Bering Sea RW -0157+03004 Africa/Kigali @@ -418,7 +418,7 @@ 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 +433649-1161209 America/Boise Mountain - ID (south), OR (east) US +332654-1120424 America/Phoenix MST - AZ (except Navajo) US +340308-1181434 America/Los_Angeles Pacific US +611305-1495401 America/Anchorage Alaska (most areas) diff --git a/lib/pytz/zoneinfo/zone1970.tab b/lib/pytz/zoneinfo/zone1970.tab index 1f1cecb8..abd94897 100644 --- a/lib/pytz/zoneinfo/zone1970.tab +++ b/lib/pytz/zoneinfo/zone1970.tab @@ -37,7 +37,7 @@ #country- #codes coordinates TZ comments AD +4230+00131 Europe/Andorra -AE,OM,RE,SC,TF +2518+05518 Asia/Dubai Crozet, Scattered Is +AE,OM,RE,SC,TF +2518+05518 Asia/Dubai Crozet AF +3431+06912 Asia/Kabul AL +4120+01950 Europe/Tirane AM +4011+04430 Asia/Yerevan @@ -47,12 +47,13 @@ AQ -6736+06253 Antarctica/Mawson Mawson AQ -6448-06406 Antarctica/Palmer Palmer AQ -6734-06808 Antarctica/Rothera Rothera 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 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 -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) @@ -81,7 +82,7 @@ BG +4241+02319 Europe/Sofia BM +3217-06446 Atlantic/Bermuda BO -1630-06809 America/La_Paz BR -0351-03225 America/Noronha Atlantic islands -BR -0127-04829 America/Belem Pará (east); Amapá +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 @@ -99,19 +100,19 @@ BR -0958-06748 America/Rio_Branco Acre 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 +4734-05243 America/St_Johns Newfoundland, Labrador (SE) +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,BS +4339-07923 America/Toronto Eastern - ON, QC (most areas) +CA,BS +4339-07923 America/Toronto Eastern - ON & QC (most areas) CA +6344-06828 America/Iqaluit Eastern - NU (most areas) -CA +4953-09709 America/Winnipeg Central - ON (west); Manitoba +CA +4953-09709 America/Winnipeg Central - ON (west), Manitoba 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); NT (E); SK (W) +CA +5333-11328 America/Edmonton Mountain - AB, BC(E), NT(E), SK(W) CA +690650-1050310 America/Cambridge_Bay Mountain - NU (west) CA +682059-1334300 America/Inuvik Mountain - NT (west) CA +5546-12014 America/Dawson_Creek MST - BC (Dawson Cr, Ft St John) @@ -126,7 +127,7 @@ CL -3327-07040 America/Santiago most of Chile CL -5309-07055 America/Punta_Arenas Region of Magallanes CL -2709-10926 Pacific/Easter Easter Island CN +3114+12128 Asia/Shanghai Beijing Time -CN,AQ +4348+08735 Asia/Urumqi Xinjiang Time, Vostok +CN +4348+08735 Asia/Urumqi Xinjiang Time CO +0436-07405 America/Bogota CR +0956-08405 America/Costa_Rica CU +2308-08222 America/Havana @@ -171,8 +172,8 @@ 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 +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 @@ -251,7 +252,7 @@ PK +2452+06703 Asia/Karachi PL +5215+02100 Europe/Warsaw PM +4703-05620 America/Miquelon PN -2504-13005 Pacific/Pitcairn -PR,AG,CA,AI,AW,BL,BQ,CW,DM,GD,GP,KN,LC,MF,MS,SX,TT,VC,VG,VI +182806-0660622 America/Puerto_Rico AST +PR,AG,CA,AI,AW,BL,BQ,CW,DM,GD,GP,KN,LC,MF,MS,SX,TT,VC,VG,VI +182806-0660622 America/Puerto_Rico AST - QC (Lower North Shore) PS +3130+03428 Asia/Gaza Gaza Strip PS +313200+0350542 Asia/Hebron West Bank PT +3843-00908 Europe/Lisbon Portugal (mainland) @@ -287,7 +288,7 @@ 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); N Kuril Is +RU +6728+15343 Asia/Srednekolymsk MSK+08 - Sakha (E), N Kuril Is RU +5301+15839 Asia/Kamchatka MSK+09 - Kamchatka RU +6445+17729 Asia/Anadyr MSK+09 - Bering Sea SA,AQ,KW,YE +2438+04643 Asia/Riyadh Syowa @@ -329,7 +330,7 @@ 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 +433649-1161209 America/Boise Mountain - ID (south), OR (east) US,CA +332654-1120424 America/Phoenix MST - AZ (most areas), Creston BC US +340308-1181434 America/Los_Angeles Pacific US +611305-1495401 America/Anchorage Alaska (most areas) diff --git a/lib/pytz/zoneinfo/zonenow.tab b/lib/pytz/zoneinfo/zonenow.tab new file mode 100644 index 00000000..b6f29109 --- /dev/null +++ b/lib/pytz/zoneinfo/zonenow.tab @@ -0,0 +1,303 @@ +# tzdb timezone descriptions, for users who do not care about old timestamps +# +# This file is in the public domain. +# +# From Paul Eggert (2023-12-18): +# This file contains a table where each row stands for a timezone +# where civil timestamps are predicted to agree from now on. +# This file is like zone1970.tab (see zone1970.tab's coments), +# but with the following changes: +# +# 1. Each timezone corresponds to a set of clocks that are planned +# to agree from now on. This is a larger set of clocks than in +# zone1970.tab, where each timezone's clocks must agree from 1970 on. +# 2. The first column is irrelevant and ignored. +# 3. The table is sorted in a different way: +# first by standard time UTC offset; +# then, if DST is used, by daylight saving UTC offset; +# then by time zone abbreviation. +# 4. Every timezone has a nonempty comments column, with wording +# distinguishing the timezone only from other timezones with the +# same UTC offset at some point during the year. +# +# The format of this table is experimental, and may change in future versions. +# +# 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. +# +#XX coordinates TZ comments +# +# -11 - SST +XX -1416-17042 Pacific/Pago_Pago Midway; Samoa ("SST") +# +# -11 +XX -1901-16955 Pacific/Niue Niue +# +# -10 - HST +XX +211825-1575130 Pacific/Honolulu Hawaii ("HST") +# +# -10 +XX -1732-14934 Pacific/Tahiti Tahiti; Cook Islands +# +# -10/-09 - HST / HDT (North America DST) +XX +515248-1763929 America/Adak western Aleutians in Alaska ("HST/HDT") +# +# -09:30 +XX -0900-13930 Pacific/Marquesas Marquesas +# +# -09 +XX -2308-13457 Pacific/Gambier Gambier +# +# -09/-08 - AKST/AKDT (North America DST) +XX +611305-1495401 America/Anchorage most of Alaska ("AKST/AKDT") +# +# -08 +XX -2504-13005 Pacific/Pitcairn Pitcairn +# +# -08/-07 - PST/PDT (North America DST) +XX +340308-1181434 America/Los_Angeles Pacific ("PST/PDT") - US & Canada; Mexico near US border +# +# -07 - MST +XX +332654-1120424 America/Phoenix Mountain Standard ("MST") - Arizona; western Mexico; Yukon +# +# -07/-06 - MST/MDT (North America DST) +XX +394421-1045903 America/Denver Mountain ("MST/MDT") - US & Canada; Mexico near US border +# +# -06 +XX -0054-08936 Pacific/Galapagos Galápagos +# +# -06 - CST +XX +1924-09909 America/Mexico_City Central Standard ("CST") - Saskatchewan; central Mexico; Central America +# +# -06/-05 (Chile DST) +XX -2709-10926 Pacific/Easter Easter Island +# +# -06/-05 - CST/CDT (North America DST) +XX +415100-0873900 America/Chicago Central ("CST/CDT") - US & Canada; Mexico near US border +# +# -05 +XX -1203-07703 America/Lima eastern South America +# +# -05 - EST +XX +175805-0764736 America/Jamaica Eastern Standard ("EST") - Caymans; Jamaica; eastern Mexico; Panama +# +# -05/-04 - CST/CDT (Cuba DST) +XX +2308-08222 America/Havana Cuba +# +# -05/-04 - EST/EDT (North America DST) +XX +404251-0740023 America/New_York Eastern ("EST/EDT") - US & Canada +# +# -04 +XX +1030-06656 America/Caracas western South America +# +# -04 - AST +XX +1828-06954 America/Santo_Domingo Atlantic Standard ("AST") - eastern Caribbean +# +# -04/-03 (Chile DST) +XX -3327-07040 America/Santiago most of Chile +# +# -04/-03 (Paraguay DST) +XX -2516-05740 America/Asuncion Paraguay +# +# -04/-03 - AST/ADT (North America DST) +XX +4439-06336 America/Halifax Atlantic ("AST/ADT") - Canada; Bermuda +# +# -03:30/-02:30 - NST/NDT (North America DST) +XX +4734-05243 America/St_Johns Newfoundland ("NST/NDT") +# +# -03 +XX -2332-04637 America/Sao_Paulo eastern South America +# +# -03/-02 (North America DST) +XX +4703-05620 America/Miquelon St Pierre & Miquelon +# +# -02 +XX -0351-03225 America/Noronha Fernando de Noronha; South Georgia +# +# -02/-01 (EU DST) +XX +6411-05144 America/Nuuk most of Greenland +# +# -01 +XX +1455-02331 Atlantic/Cape_Verde Cape Verde +# +# -01/+00 (EU DST) +XX +3744-02540 Atlantic/Azores Azores +# -01/+00 (EU DST) until 2024-03-31; then -02/-01 (EU DST) +XX +7029-02158 America/Scoresbysund Ittoqqortoormiit +# +# +00 - GMT +XX +0519-00402 Africa/Abidjan far western Africa; Iceland ("GMT") +# +# +00/+01 - GMT/BST (EU DST) +XX +513030-0000731 Europe/London United Kingdom ("GMT/BST") +# +# +00/+01 - WET/WEST (EU DST) +XX +3843-00908 Europe/Lisbon western Europe ("WET/WEST") +# +# +00/+02 - Troll DST +XX -720041+0023206 Antarctica/Troll Troll Station in Antarctica +# +# +01 - CET +XX +3647+00303 Africa/Algiers Algeria, Tunisia ("CET") +# +# +01 - WAT +XX +0627+00324 Africa/Lagos western Africa ("WAT") +# +# +01/+00 - IST/GMT (EU DST in reverse) +XX +5320-00615 Europe/Dublin Ireland ("IST/GMT") +# +# +01/+00 - (Morocco DST) +XX +3339-00735 Africa/Casablanca Morocco +# +# +01/+02 - CET/CEST (EU DST) +XX +4852+00220 Europe/Paris central Europe ("CET/CEST") +# +# +02 - CAT +XX -2558+03235 Africa/Maputo central Africa ("CAT") +# +# +02 - EET +XX +3254+01311 Africa/Tripoli Libya; Kaliningrad ("EET") +# +# +02 - SAST +XX -2615+02800 Africa/Johannesburg southern Africa ("SAST") +# +# +02/+03 - EET/EEST (EU DST) +XX +3758+02343 Europe/Athens eastern Europe ("EET/EEST") +# +# +02/+03 - EET/EEST (Egypt DST) +XX +3003+03115 Africa/Cairo Egypt +# +# +02/+03 - EET/EEST (Lebanon DST) +XX +3353+03530 Asia/Beirut Lebanon +# +# +02/+03 - EET/EEST (Moldova DST) +XX +4700+02850 Europe/Chisinau Moldova +# +# +02/+03 - EET/EEST (Palestine DST) +XX +3130+03428 Asia/Gaza Palestine +# +# +02/+03 - IST/IDT (Israel DST) +XX +314650+0351326 Asia/Jerusalem Israel +# +# +03 +XX +4101+02858 Europe/Istanbul Near East; Belarus +# +# +03 - EAT +XX -0117+03649 Africa/Nairobi eastern Africa ("EAT") +# +# +03 - MSK +XX +554521+0373704 Europe/Moscow Moscow ("MSK") +# +# +03:30 +XX +3540+05126 Asia/Tehran Iran +# +# +04 +XX +2518+05518 Asia/Dubai Russia; Caucasus; Persian Gulf; Seychelles; Réunion +# +# +04:30 +XX +3431+06912 Asia/Kabul Afghanistan +# +# +05 +XX +4120+06918 Asia/Tashkent Russia; west Kazakhstan; Tajikistan; Turkmenistan; Uzbekistan; Maldives +# +# +05 - PKT +XX +2452+06703 Asia/Karachi Pakistan ("PKT") +# +# +05:30 +XX +0656+07951 Asia/Colombo Sri Lanka +# +# +05:30 - IST +XX +2232+08822 Asia/Kolkata India ("IST") +# +# +05:45 +XX +2743+08519 Asia/Kathmandu Nepal +# +# +06 +XX +2343+09025 Asia/Dhaka Russia; Kyrgyzstan; Bhutan; Bangladesh; Chagos +# +06 until 2024-03-01; then +05 +XX +4315+07657 Asia/Almaty Kazakhstan (except western areas) +# +# +06:30 +XX +1647+09610 Asia/Yangon Myanmar; Cocos +# +# +07 +XX +1345+10031 Asia/Bangkok Russia; Indochina; Christmas Island +# +# +07 - WIB +XX -0610+10648 Asia/Jakarta Indonesia ("WIB") +# +# +08 +XX +0117+10351 Asia/Singapore Russia; Brunei; Malaysia; Singapore +# +# +08 - AWST +XX -3157+11551 Australia/Perth Western Australia ("AWST") +# +# +08 - CST +XX +3114+12128 Asia/Shanghai China ("CST") +# +# +08 - HKT +XX +2217+11409 Asia/Hong_Kong Hong Kong ("HKT") +# +# +08 - PHT +XX +1435+12100 Asia/Manila Philippines ("PHT") +# +# +08 - WITA +XX -0507+11924 Asia/Makassar Indonesia ("WITA") +# +# +08:45 +XX -3143+12852 Australia/Eucla Eucla +# +# +09 +XX +5203+11328 Asia/Chita Russia; Palau; East Timor +# +# +09 - JST +XX +353916+1394441 Asia/Tokyo Japan ("JST") +# +# +09 - KST +XX +3733+12658 Asia/Seoul Korea ("KST") +# +# +09 - WIT +XX -0232+14042 Asia/Jayapura Indonesia ("WIT") +# +# +09:30 - ACST +XX -1228+13050 Australia/Darwin Northern Territory ("ACST") +# +# +09:30/+10:30 - ACST/ACDT (Australia DST) +XX -3455+13835 Australia/Adelaide South Australia ("ACST/ACDT") +# +# +10 +XX +4310+13156 Asia/Vladivostok Russia; Yap; Chuuk; Papua New Guinea; Dumont d'Urville +# +# +10 - AEST +XX -2728+15302 Australia/Brisbane Queensland ("AEST") +# +# +10 - ChST +XX +1328+14445 Pacific/Guam Mariana Islands ("ChST") +# +# +10/+11 - AEST/AEDT (Australia DST) +XX -3352+15113 Australia/Sydney southeast Australia ("AEST/AEDT") +# +# +10:30/+11 +XX -3133+15905 Australia/Lord_Howe Lord Howe Island +# +# +11 +XX -0613+15534 Pacific/Bougainville Russia; Kosrae; Bougainville; Solomons +# +# +11/+12 (Australia DST) +XX -2903+16758 Pacific/Norfolk Norfolk Island +# +# +12 +XX +5301+15839 Asia/Kamchatka Russia; Tuvalu; Fiji; etc. +# +# +12/+13 (New Zealand DST) +XX -3652+17446 Pacific/Auckland New Zealand ("NZST/NZDT") +# +# +12:45/+13:45 (Chatham DST) +XX -4357-17633 Pacific/Chatham Chatham Islands +# +# +13 +XX -210800-1751200 Pacific/Tongatapu Kanton; Tokelau; Samoa (western); Tonga +# +# +14 +XX +0152-15720 Pacific/Kiritimati Kiritimati diff --git a/lib/simplejson/__init__.py b/lib/simplejson/__init__.py index 7e533a24..2d1900d8 100644 --- a/lib/simplejson/__init__.py +++ b/lib/simplejson/__init__.py @@ -118,7 +118,7 @@ Serializing multiple objects to JSON lines (newline-delimited JSON):: """ from __future__ import absolute_import -__version__ = '3.19.1' +__version__ = '3.19.2' __all__ = [ 'dump', 'dumps', 'load', 'loads', 'JSONDecoder', 'JSONDecodeError', 'JSONEncoder', diff --git a/lib/websocket/__init__.py b/lib/websocket/__init__.py index bfa71981..2ff75879 100644 --- a/lib/websocket/__init__.py +++ b/lib/websocket/__init__.py @@ -23,4 +23,4 @@ from ._exceptions import * from ._logging import * from ._socket import * -__version__ = "1.6.2" +__version__ = "1.7.0" diff --git a/lib/websocket/_abnf.py b/lib/websocket/_abnf.py index 6c2466ad..416ebc8f 100644 --- a/lib/websocket/_abnf.py +++ b/lib/websocket/_abnf.py @@ -2,10 +2,11 @@ import array import os import struct import sys +from threading import Lock +from typing import Callable, Optional, Union from ._exceptions import * from ._utils import validate_utf8 -from threading import Lock """ _abnf.py @@ -33,8 +34,9 @@ try: # Note that wsaccel is unmaintained. from wsaccel.xormask import XorMaskerSimple - def _mask(_m, _d) -> bytes: - return XorMaskerSimple(_m).process(_d) + def _mask(mask_value: array.array, data_value: array.array) -> bytes: + mask_result: bytes = XorMaskerSimple(mask_value).process(data_value) + return mask_result except ImportError: # wsaccel is not available, use websocket-client _mask() @@ -42,26 +44,30 @@ except ImportError: def _mask(mask_value: array.array, data_value: array.array) -> bytes: datalen = len(data_value) - data_value = int.from_bytes(data_value, native_byteorder) - mask_value = int.from_bytes(mask_value * (datalen // 4) + mask_value[: datalen % 4], native_byteorder) - return (data_value ^ mask_value).to_bytes(datalen, native_byteorder) + int_data_value = int.from_bytes(data_value, native_byteorder) + int_mask_value = int.from_bytes( + mask_value * (datalen // 4) + mask_value[: datalen % 4], native_byteorder + ) + return (int_data_value ^ int_mask_value).to_bytes(datalen, native_byteorder) __all__ = [ - 'ABNF', 'continuous_frame', 'frame_buffer', - 'STATUS_NORMAL', - 'STATUS_GOING_AWAY', - 'STATUS_PROTOCOL_ERROR', - 'STATUS_UNSUPPORTED_DATA_TYPE', - 'STATUS_STATUS_NOT_AVAILABLE', - 'STATUS_ABNORMAL_CLOSED', - 'STATUS_INVALID_PAYLOAD', - 'STATUS_POLICY_VIOLATION', - 'STATUS_MESSAGE_TOO_BIG', - 'STATUS_INVALID_EXTENSION', - 'STATUS_UNEXPECTED_CONDITION', - 'STATUS_BAD_GATEWAY', - 'STATUS_TLS_HANDSHAKE_ERROR', + "ABNF", + "continuous_frame", + "frame_buffer", + "STATUS_NORMAL", + "STATUS_GOING_AWAY", + "STATUS_PROTOCOL_ERROR", + "STATUS_UNSUPPORTED_DATA_TYPE", + "STATUS_STATUS_NOT_AVAILABLE", + "STATUS_ABNORMAL_CLOSED", + "STATUS_INVALID_PAYLOAD", + "STATUS_POLICY_VIOLATION", + "STATUS_MESSAGE_TOO_BIG", + "STATUS_INVALID_EXTENSION", + "STATUS_UNEXPECTED_CONDITION", + "STATUS_BAD_GATEWAY", + "STATUS_TLS_HANDSHAKE_ERROR", ] # closing frame status codes. @@ -110,11 +116,17 @@ class ABNF: OPCODE_BINARY = 0x2 OPCODE_CLOSE = 0x8 OPCODE_PING = 0x9 - OPCODE_PONG = 0xa + OPCODE_PONG = 0xA # available operation code value tuple - OPCODES = (OPCODE_CONT, OPCODE_TEXT, OPCODE_BINARY, OPCODE_CLOSE, - OPCODE_PING, OPCODE_PONG) + OPCODES = ( + OPCODE_CONT, + OPCODE_TEXT, + OPCODE_BINARY, + OPCODE_CLOSE, + OPCODE_PING, + OPCODE_PONG, + ) # opcode human readable string OPCODE_MAP = { @@ -123,16 +135,24 @@ class ABNF: OPCODE_BINARY: "binary", OPCODE_CLOSE: "close", OPCODE_PING: "ping", - OPCODE_PONG: "pong" + OPCODE_PONG: "pong", } # data length threshold. - LENGTH_7 = 0x7e + LENGTH_7 = 0x7E LENGTH_16 = 1 << 16 LENGTH_63 = 1 << 63 - def __init__(self, fin: int = 0, rsv1: int = 0, rsv2: int = 0, rsv3: int = 0, - opcode: int = OPCODE_TEXT, mask: int = 1, data: str or bytes = "") -> None: + def __init__( + self, + fin: int = 0, + rsv1: int = 0, + rsv2: int = 0, + rsv3: int = 0, + opcode: int = OPCODE_TEXT, + mask_value: int = 1, + data: Union[str, bytes, None] = "", + ) -> None: """ Constructor for ABNF. Please check RFC for arguments. """ @@ -141,7 +161,7 @@ class ABNF: self.rsv2 = rsv2 self.rsv3 = rsv3 self.opcode = opcode - self.mask = mask + self.mask_value = mask_value if data is None: data = "" self.data = data @@ -173,7 +193,7 @@ class ABNF: if l > 2 and not skip_utf8_validation and not validate_utf8(self.data[2:]): raise WebSocketProtocolException("Invalid close frame.") - code = 256 * self.data[0] + self.data[1] + code = 256 * int(self.data[0]) + int(self.data[1]) if not self._is_valid_close_status(code): raise WebSocketProtocolException("Invalid close opcode %r", code) @@ -182,12 +202,10 @@ class ABNF: return code in VALID_CLOSE_STATUS or (3000 <= code < 5000) def __str__(self) -> str: - return "fin=" + str(self.fin) \ - + " opcode=" + str(self.opcode) \ - + " data=" + str(self.data) + return f"fin={self.fin} opcode={self.opcode} data={self.data}" @staticmethod - def create_frame(data: str, opcode: int, fin: int = 1) -> 'ABNF': + def create_frame(data: Union[bytes, str], opcode: int, fin: int = 1) -> "ABNF": """ Create frame to send text, binary and other data. @@ -219,34 +237,39 @@ class ABNF: if length >= ABNF.LENGTH_63: raise ValueError("data is too long") - frame_header = chr(self.fin << 7 | - self.rsv1 << 6 | self.rsv2 << 5 | self.rsv3 << 4 | - self.opcode).encode('latin-1') + frame_header = chr( + self.fin << 7 + | self.rsv1 << 6 + | self.rsv2 << 5 + | self.rsv3 << 4 + | self.opcode + ).encode("latin-1") if length < ABNF.LENGTH_7: - frame_header += chr(self.mask << 7 | length).encode('latin-1') + frame_header += chr(self.mask_value << 7 | length).encode("latin-1") elif length < ABNF.LENGTH_16: - frame_header += chr(self.mask << 7 | 0x7e).encode('latin-1') + frame_header += chr(self.mask_value << 7 | 0x7E).encode("latin-1") frame_header += struct.pack("!H", length) else: - frame_header += chr(self.mask << 7 | 0x7f).encode('latin-1') + frame_header += chr(self.mask_value << 7 | 0x7F).encode("latin-1") frame_header += struct.pack("!Q", length) - if not self.mask: + if not self.mask_value: + if isinstance(self.data, str): + self.data = self.data.encode("utf-8") return frame_header + self.data - else: - mask_key = self.get_mask_key(4) - return frame_header + self._get_masked(mask_key) + mask_key = self.get_mask_key(4) + return frame_header + self._get_masked(mask_key) - def _get_masked(self, mask_key: str or bytes) -> bytes: + def _get_masked(self, mask_key: Union[str, bytes]) -> bytes: s = ABNF.mask(mask_key, self.data) if isinstance(mask_key, str): - mask_key = mask_key.encode('utf-8') + mask_key = mask_key.encode("utf-8") return mask_key + s @staticmethod - def mask(mask_key: str or bytes, data: str or bytes) -> bytes: + def mask(mask_key: Union[str, bytes], data: Union[str, bytes]) -> bytes: """ Mask or unmask data. Just do xor for each byte @@ -261,10 +284,10 @@ class ABNF: data = "" if isinstance(mask_key, str): - mask_key = mask_key.encode('latin-1') + mask_key = mask_key.encode("latin-1") if isinstance(data, str): - data = data.encode('latin-1') + data = data.encode("latin-1") return _mask(array.array("B", mask_key), array.array("B", data)) @@ -273,19 +296,21 @@ class frame_buffer: _HEADER_MASK_INDEX = 5 _HEADER_LENGTH_INDEX = 6 - def __init__(self, recv_fn: int, skip_utf8_validation: bool) -> None: + def __init__( + self, recv_fn: Callable[[int], int], skip_utf8_validation: bool + ) -> None: self.recv = recv_fn self.skip_utf8_validation = skip_utf8_validation # Buffers over the packets from the layer beneath until desired amount # bytes of bytes are received. - self.recv_buffer = [] + self.recv_buffer: list = [] self.clear() self.lock = Lock() def clear(self) -> None: - self.header = None - self.length = None - self.mask = None + self.header: Optional[tuple] = None + self.length: Optional[int] = None + self.mask_value: Union[bytes, str, None] = None def has_received_header(self) -> bool: return self.header is None @@ -297,41 +322,41 @@ class frame_buffer: rsv1 = b1 >> 6 & 1 rsv2 = b1 >> 5 & 1 rsv3 = b1 >> 4 & 1 - opcode = b1 & 0xf + opcode = b1 & 0xF b2 = header[1] has_mask = b2 >> 7 & 1 - length_bits = b2 & 0x7f + length_bits = b2 & 0x7F self.header = (fin, rsv1, rsv2, rsv3, opcode, has_mask, length_bits) - def has_mask(self) -> bool or int: + def has_mask(self) -> Union[bool, int]: if not self.header: return False - return self.header[frame_buffer._HEADER_MASK_INDEX] + header_val: int = self.header[frame_buffer._HEADER_MASK_INDEX] + return header_val def has_received_length(self) -> bool: return self.length is None def recv_length(self) -> None: bits = self.header[frame_buffer._HEADER_LENGTH_INDEX] - length_bits = bits & 0x7f - if length_bits == 0x7e: + length_bits = bits & 0x7F + if length_bits == 0x7E: v = self.recv_strict(2) self.length = struct.unpack("!H", v)[0] - elif length_bits == 0x7f: + elif length_bits == 0x7F: v = self.recv_strict(8) self.length = struct.unpack("!Q", v)[0] else: self.length = length_bits def has_received_mask(self) -> bool: - return self.mask is None + return self.mask_value is None def recv_mask(self) -> None: - self.mask = self.recv_strict(4) if self.has_mask() else "" + self.mask_value = self.recv_strict(4) if self.has_mask() else "" def recv_frame(self) -> ABNF: - with self.lock: # Header if self.has_received_header(): @@ -346,12 +371,12 @@ class frame_buffer: # Mask if self.has_received_mask(): self.recv_mask() - mask = self.mask + mask_value = self.mask_value # Payload payload = self.recv_strict(length) if has_mask: - payload = ABNF.mask(mask, payload) + payload = ABNF.mask(mask_value, payload) # Reset for next frame self.clear() @@ -385,18 +410,19 @@ class frame_buffer: class continuous_frame: - def __init__(self, fire_cont_frame: bool, skip_utf8_validation: bool) -> None: self.fire_cont_frame = fire_cont_frame self.skip_utf8_validation = skip_utf8_validation - self.cont_data = None - self.recving_frames = None + self.cont_data: Optional[list] = None + self.recving_frames: Optional[int] = None def validate(self, frame: ABNF) -> None: if not self.recving_frames and frame.opcode == ABNF.OPCODE_CONT: raise WebSocketProtocolException("Illegal frame") - if self.recving_frames and \ - frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY): + if self.recving_frames and frame.opcode in ( + ABNF.OPCODE_TEXT, + ABNF.OPCODE_BINARY, + ): raise WebSocketProtocolException("Illegal frame") def add(self, frame: ABNF) -> None: @@ -410,15 +436,18 @@ class continuous_frame: if frame.fin: self.recving_frames = None - def is_fire(self, frame: ABNF) -> bool or int: + def is_fire(self, frame: ABNF) -> Union[bool, int]: return frame.fin or self.fire_cont_frame - def extract(self, frame: ABNF) -> list: + def extract(self, frame: ABNF) -> tuple: data = self.cont_data self.cont_data = None frame.data = data[1] - if not self.fire_cont_frame and data[0] == ABNF.OPCODE_TEXT and not self.skip_utf8_validation and not validate_utf8(frame.data): - raise WebSocketPayloadException( - "cannot decode: " + repr(frame.data)) - - return [data[0], frame] + if ( + not self.fire_cont_frame + and data[0] == ABNF.OPCODE_TEXT + and not self.skip_utf8_validation + and not validate_utf8(frame.data) + ): + raise WebSocketPayloadException(f"cannot decode: {repr(frame.data)}") + return data[0], frame diff --git a/lib/websocket/_app.py b/lib/websocket/_app.py index 2577f1ba..4d8af3b5 100644 --- a/lib/websocket/_app.py +++ b/lib/websocket/_app.py @@ -1,18 +1,19 @@ import inspect import selectors -import sys +import socket import threading import time -import traceback -import socket - -from typing import Callable, Any +from typing import Any, Callable, Optional, Union from . import _logging from ._abnf import ABNF -from ._url import parse_url from ._core import WebSocket, getdefaulttimeout -from ._exceptions import * +from ._exceptions import ( + WebSocketConnectionClosedException, + WebSocketException, + WebSocketTimeoutException, +) +from ._url import parse_url """ _app.py @@ -47,22 +48,24 @@ class DispatcherBase: """ DispatcherBase """ - def __init__(self, app: Any, ping_timeout: float) -> None: + + def __init__(self, app: Any, ping_timeout: Union[float, int, None]) -> None: self.app = app self.ping_timeout = ping_timeout - def timeout(self, seconds: int, callback: Callable) -> None: + def timeout(self, seconds: Union[float, int, None], callback: Callable) -> None: time.sleep(seconds) callback() def reconnect(self, seconds: int, reconnector: Callable) -> None: try: - _logging.info("reconnect() - retrying in {seconds_count} seconds [{frame_count} frames in stack]".format( - seconds_count=seconds, frame_count=len(inspect.stack()))) + _logging.info( + f"reconnect() - retrying in {seconds} seconds [{len(inspect.stack())} frames in stack]" + ) time.sleep(seconds) reconnector(reconnecting=True) except KeyboardInterrupt as e: - _logging.info("User exited {err}".format(err=e)) + _logging.info(f"User exited {e}") raise e @@ -70,13 +73,18 @@ class Dispatcher(DispatcherBase): """ Dispatcher """ - def read(self, sock: socket.socket, read_callback: Callable, check_callback: Callable) -> None: + + def read( + self, + sock: socket.socket, + read_callback: Callable, + check_callback: Callable, + ) -> None: sel = selectors.DefaultSelector() sel.register(self.app.sock.sock, selectors.EVENT_READ) try: while self.app.keep_running: - r = sel.select(self.ping_timeout) - if r: + if sel.select(self.ping_timeout): if not read_callback(): break check_callback() @@ -88,24 +96,31 @@ class SSLDispatcher(DispatcherBase): """ SSLDispatcher """ - def read(self, sock: socket.socket, read_callback: Callable, check_callback: Callable) -> None: + + def read( + self, + sock: socket.socket, + read_callback: Callable, + check_callback: Callable, + ) -> None: sock = self.app.sock.sock sel = selectors.DefaultSelector() sel.register(sock, selectors.EVENT_READ) try: while self.app.keep_running: - r = self.select(sock, sel) - if r: + if self.select(sock, sel): if not read_callback(): break check_callback() finally: sel.close() - def select(self, sock, sel:selectors.DefaultSelector): + def select(self, sock, sel: selectors.DefaultSelector): sock = self.app.sock.sock if sock.pending(): - return [sock,] + return [ + sock, + ] r = sel.select(self.ping_timeout) @@ -117,17 +132,23 @@ class WrappedDispatcher: """ WrappedDispatcher """ - def __init__(self, app, ping_timeout: float, dispatcher: Dispatcher) -> None: + + def __init__(self, app, ping_timeout: Union[float, int, None], dispatcher) -> None: self.app = app self.ping_timeout = ping_timeout self.dispatcher = dispatcher dispatcher.signal(2, dispatcher.abort) # keyboard interrupt - def read(self, sock: socket.socket, read_callback: Callable, check_callback: Callable) -> None: + def read( + self, + sock: socket.socket, + read_callback: Callable, + check_callback: Callable, + ) -> None: self.dispatcher.read(sock, read_callback) self.ping_timeout and self.timeout(self.ping_timeout, check_callback) - def timeout(self, seconds: int, callback: Callable) -> None: + def timeout(self, seconds: float, callback: Callable) -> None: self.dispatcher.timeout(seconds, callback) def reconnect(self, seconds: int, reconnector: Callable) -> None: @@ -139,14 +160,24 @@ class WebSocketApp: Higher level of APIs are provided. The interface is like JavaScript WebSocket object. """ - def __init__(self, url: str, header: list or dict or Callable = None, - on_open: Callable = None, on_message: Callable = None, on_error: Callable = None, - on_close: Callable = None, on_ping: Callable = None, on_pong: Callable = None, - on_cont_message: Callable = None, - keep_running: bool = True, get_mask_key: Callable = None, cookie: str = None, - subprotocols: list = None, - on_data: Callable = None, - socket: socket.socket = None) -> None: + def __init__( + self, + url: str, + header: Union[list, dict, Callable, None] = None, + on_open: Optional[Callable[[WebSocket], None]] = None, + on_message: Optional[Callable[[WebSocket, Any], None]] = None, + on_error: Optional[Callable[[WebSocket, Any], None]] = None, + on_close: Optional[Callable[[WebSocket, Any, Any], None]] = None, + on_ping: Optional[Callable] = None, + on_pong: Optional[Callable] = None, + on_cont_message: Optional[Callable] = None, + keep_running: bool = True, + get_mask_key: Optional[Callable] = None, + cookie: Optional[str] = None, + subprotocols: Optional[list] = None, + on_data: Optional[Callable] = None, + socket: Optional[socket.socket] = None, + ) -> None: """ WebSocketApp initialization @@ -222,13 +253,13 @@ class WebSocketApp: self.on_cont_message = on_cont_message self.keep_running = False self.get_mask_key = get_mask_key - self.sock = None - self.last_ping_tm = 0 - self.last_pong_tm = 0 - self.ping_thread = None - self.stop_ping = None - self.ping_interval = 0 - self.ping_timeout = None + self.sock: Optional[WebSocket] = None + self.last_ping_tm = float(0) + self.last_pong_tm = float(0) + self.ping_thread: Optional[threading.Thread] = None + self.stop_ping: Optional[threading.Event] = None + self.ping_interval = float(0) + self.ping_timeout: Union[float, int, None] = None self.ping_payload = "" self.subprotocols = subprotocols self.prepared_socket = socket @@ -236,7 +267,7 @@ class WebSocketApp: self.has_done_teardown = False self.has_done_teardown_lock = threading.Lock() - def send(self, data: str, opcode: int = ABNF.OPCODE_TEXT) -> None: + def send(self, data: Union[bytes, str], opcode: int = ABNF.OPCODE_TEXT) -> None: """ send message @@ -250,8 +281,21 @@ class WebSocketApp: """ if not self.sock or self.sock.send(data, opcode) == 0: - raise WebSocketConnectionClosedException( - "Connection is already closed.") + raise WebSocketConnectionClosedException("Connection is already closed.") + + def send_text(self, text_data: str) -> None: + """ + Sends UTF-8 encoded text. + """ + if not self.sock or self.sock.send(text_data, ABNF.OPCODE_TEXT) == 0: + raise WebSocketConnectionClosedException("Connection is already closed.") + + def send_bytes(self, data: Union[bytes, bytearray]) -> None: + """ + Sends a sequence of bytes. + """ + if not self.sock or self.sock.send(data, ABNF.OPCODE_BINARY) == 0: + raise WebSocketConnectionClosedException("Connection is already closed.") def close(self, **kwargs) -> None: """ @@ -263,7 +307,7 @@ class WebSocketApp: self.sock = None def _start_ping_thread(self) -> None: - self.last_ping_tm = self.last_pong_tm = 0 + self.last_ping_tm = self.last_pong_tm = float(0) self.stop_ping = threading.Event() self.ping_thread = threading.Thread(target=self._send_ping) self.ping_thread.daemon = True @@ -274,7 +318,7 @@ class WebSocketApp: self.stop_ping.set() if self.ping_thread and self.ping_thread.is_alive(): self.ping_thread.join(3) - self.last_ping_tm = self.last_pong_tm = 0 + self.last_ping_tm = self.last_pong_tm = float(0) def _send_ping(self) -> None: if self.stop_ping.wait(self.ping_interval) or self.keep_running is False: @@ -286,17 +330,28 @@ class WebSocketApp: _logging.debug("Sending ping") self.sock.ping(self.ping_payload) except Exception as e: - _logging.debug("Failed to send ping: {err}".format(err=e)) + _logging.debug(f"Failed to send ping: {e}") - def run_forever(self, sockopt: tuple = None, sslopt: dict = None, - ping_interval: float = 0, ping_timeout: float or None = None, - ping_payload: str = "", - http_proxy_host: str = None, http_proxy_port: int or str = None, - http_no_proxy: list = None, http_proxy_auth: tuple = None, - http_proxy_timeout: float = None, - skip_utf8_validation: bool = False, - host: str = None, origin: str = None, dispatcher: Dispatcher = None, - suppress_origin: bool = False, proxy_type: str = None, reconnect: int = None) -> bool: + def run_forever( + self, + sockopt: tuple = None, + sslopt: dict = None, + ping_interval: Union[float, int] = 0, + ping_timeout: Union[float, int, None] = None, + ping_payload: str = "", + http_proxy_host: str = None, + http_proxy_port: Union[int, str] = None, + http_no_proxy: list = None, + http_proxy_auth: tuple = None, + http_proxy_timeout: Optional[float] = None, + skip_utf8_validation: bool = False, + host: str = None, + origin: str = None, + dispatcher=None, + suppress_origin: bool = False, + proxy_type: str = None, + reconnect: int = None, + ) -> bool: """ Run event loop for WebSocket framework. @@ -360,7 +415,7 @@ class WebSocketApp: if ping_timeout and ping_interval and ping_interval <= ping_timeout: raise WebSocketException("Ensure ping_interval > ping_timeout") if not sockopt: - sockopt = [] + sockopt = () if not sslopt: sslopt = {} if self.sock: @@ -394,7 +449,8 @@ class WebSocketApp: if self.sock: self.sock.close() close_status_code, close_reason = self._get_close_args( - close_frame if close_frame else None) + close_frame if close_frame else None + ) self.sock = None # Finally call the callback AFTER all teardown is complete @@ -405,24 +461,34 @@ class WebSocketApp: self.sock.shutdown() self.sock = WebSocket( - self.get_mask_key, sockopt=sockopt, sslopt=sslopt, + self.get_mask_key, + sockopt=sockopt, + sslopt=sslopt, fire_cont_frame=self.on_cont_message is not None, skip_utf8_validation=skip_utf8_validation, - enable_multithread=True) + enable_multithread=True, + ) self.sock.settimeout(getdefaulttimeout()) try: - header = self.header() if callable(self.header) else self.header self.sock.connect( - self.url, header=header, cookie=self.cookie, + self.url, + header=header, + cookie=self.cookie, http_proxy_host=http_proxy_host, - http_proxy_port=http_proxy_port, http_no_proxy=http_no_proxy, - http_proxy_auth=http_proxy_auth, http_proxy_timeout=http_proxy_timeout, + http_proxy_port=http_proxy_port, + http_no_proxy=http_no_proxy, + http_proxy_auth=http_proxy_auth, + http_proxy_timeout=http_proxy_timeout, subprotocols=self.subprotocols, - host=host, origin=origin, suppress_origin=suppress_origin, - proxy_type=proxy_type, socket=self.prepared_socket) + host=host, + origin=origin, + suppress_origin=suppress_origin, + proxy_type=proxy_type, + socket=self.prepared_socket, + ) _logging.info("Websocket connected") @@ -432,7 +498,13 @@ class WebSocketApp: self._callback(self.on_open) dispatcher.read(self.sock.sock, read, check) - except (WebSocketConnectionClosedException, ConnectionRefusedError, KeyboardInterrupt, SystemExit, Exception) as e: + except ( + WebSocketConnectionClosedException, + ConnectionRefusedError, + KeyboardInterrupt, + SystemExit, + Exception, + ) as e: handleDisconnect(e, reconnecting) def read() -> bool: @@ -441,7 +513,10 @@ class WebSocketApp: try: op_code, frame = self.sock.recv_data_frame(True) - except (WebSocketConnectionClosedException, KeyboardInterrupt) as e: + except ( + WebSocketConnectionClosedException, + KeyboardInterrupt, + ) as e: if custom_dispatcher: return handleDisconnect(e) else: @@ -455,10 +530,8 @@ class WebSocketApp: self.last_pong_tm = time.time() self._callback(self.on_pong, frame.data) elif op_code == ABNF.OPCODE_CONT and self.on_cont_message: - self._callback(self.on_data, frame.data, - frame.opcode, frame.fin) - self._callback(self.on_cont_message, - frame.data, frame.fin) + self._callback(self.on_data, frame.data, frame.opcode, frame.fin) + self._callback(self.on_cont_message, frame.data, frame.fin) else: data = frame.data if op_code == ABNF.OPCODE_TEXT and not skip_utf8_validation: @@ -469,18 +542,38 @@ class WebSocketApp: return True def check() -> bool: - if (self.ping_timeout): - has_timeout_expired = time.time() - self.last_ping_tm > self.ping_timeout - has_pong_not_arrived_after_last_ping = self.last_pong_tm - self.last_ping_tm < 0 - has_pong_arrived_too_late = self.last_pong_tm - self.last_ping_tm > self.ping_timeout + if self.ping_timeout: + has_timeout_expired = ( + time.time() - self.last_ping_tm > self.ping_timeout + ) + has_pong_not_arrived_after_last_ping = ( + self.last_pong_tm - self.last_ping_tm < 0 + ) + has_pong_arrived_too_late = ( + self.last_pong_tm - self.last_ping_tm > self.ping_timeout + ) - if (self.last_ping_tm and - has_timeout_expired and - (has_pong_not_arrived_after_last_ping or has_pong_arrived_too_late)): + if ( + self.last_ping_tm + and has_timeout_expired + and ( + has_pong_not_arrived_after_last_ping + or has_pong_arrived_too_late + ) + ): raise WebSocketTimeoutException("ping/pong timed out") return True - def handleDisconnect(e: Exception, reconnecting: bool = False) -> bool: + def handleDisconnect( + e: Union[ + WebSocketConnectionClosedException, + ConnectionRefusedError, + KeyboardInterrupt, + SystemExit, + Exception, + ], + reconnecting: bool = False, + ) -> bool: self.has_errored = True self._stop_ping_thread() if not reconnecting: @@ -492,25 +585,31 @@ class WebSocketApp: raise if reconnect: - _logging.info("{err} - reconnect".format(err=e)) + _logging.info(f"{e} - reconnect") if custom_dispatcher: - _logging.debug("Calling custom dispatcher reconnect [{frame_count} frames in stack]".format(frame_count=len(inspect.stack()))) + _logging.debug( + f"Calling custom dispatcher reconnect [{len(inspect.stack())} frames in stack]" + ) dispatcher.reconnect(reconnect, setSock) else: - _logging.error("{err} - goodbye".format(err=e)) + _logging.error(f"{e} - goodbye") teardown() custom_dispatcher = bool(dispatcher) - dispatcher = self.create_dispatcher(ping_timeout, dispatcher, parse_url(self.url)[3]) + dispatcher = self.create_dispatcher( + ping_timeout, dispatcher, parse_url(self.url)[3] + ) try: setSock() if not custom_dispatcher and reconnect: while self.keep_running: - _logging.debug("Calling dispatcher reconnect [{frame_count} frames in stack]".format(frame_count=len(inspect.stack()))) + _logging.debug( + f"Calling dispatcher reconnect [{len(inspect.stack())} frames in stack]" + ) dispatcher.reconnect(reconnect, setSock) except (KeyboardInterrupt, Exception) as e: - _logging.info("tearing down on exception {err}".format(err=e)) + _logging.info(f"tearing down on exception {e}") teardown() finally: if not custom_dispatcher: @@ -519,13 +618,17 @@ class WebSocketApp: return self.has_errored - def create_dispatcher(self, ping_timeout: int, dispatcher: Dispatcher = None, is_ssl: bool = False) -> DispatcherBase: + def create_dispatcher( + self, + ping_timeout: Union[float, int, None], + dispatcher: Optional[DispatcherBase] = None, + is_ssl: bool = False, + ) -> Union[Dispatcher, SSLDispatcher, WrappedDispatcher]: if dispatcher: # If custom dispatcher is set, use WrappedDispatcher return WrappedDispatcher(self, ping_timeout, dispatcher) timeout = ping_timeout or 10 if is_ssl: return SSLDispatcher(self, timeout) - return Dispatcher(self, timeout) def _get_close_args(self, close_frame: ABNF) -> list: @@ -540,8 +643,12 @@ class WebSocketApp: # Extract close frame status code if close_frame.data and len(close_frame.data) >= 2: - close_status_code = 256 * close_frame.data[0] + close_frame.data[1] - reason = close_frame.data[2:].decode('utf-8') + close_status_code = 256 * int(close_frame.data[0]) + int( + close_frame.data[1] + ) + reason = close_frame.data[2:] + if isinstance(reason, bytes): + reason = reason.decode("utf-8") return [close_status_code, reason] else: # Most likely reached this because len(close_frame_data.data) < 2 @@ -553,6 +660,6 @@ class WebSocketApp: callback(self, *args) except Exception as e: - _logging.error("error from callback {callback}: {err}".format(callback=callback, err=e)) + _logging.error(f"error from callback {callback}: {e}") if self.on_error: self.on_error(self, e) diff --git a/lib/websocket/_cookiejar.py b/lib/websocket/_cookiejar.py index 2047eddd..61079402 100644 --- a/lib/websocket/_cookiejar.py +++ b/lib/websocket/_cookiejar.py @@ -1,4 +1,5 @@ import http.cookies +from typing import Optional """ _cookiejar.py @@ -22,18 +23,21 @@ limitations under the License. class SimpleCookieJar: def __init__(self) -> None: - self.jar = dict() + self.jar: dict = dict() - def add(self, set_cookie: str) -> None: + def add(self, set_cookie: Optional[str]) -> None: if set_cookie: simpleCookie = http.cookies.SimpleCookie(set_cookie) for k, v in simpleCookie.items(): - domain = v.get("domain") - if domain: + if domain := v.get("domain"): if not domain.startswith("."): - domain = "." + domain - cookie = self.jar.get(domain) if self.jar.get(domain) else http.cookies.SimpleCookie() + domain = f".{domain}" + cookie = ( + self.jar.get(domain) + if self.jar.get(domain) + else http.cookies.SimpleCookie() + ) cookie.update(simpleCookie) self.jar[domain.lower()] = cookie @@ -42,10 +46,9 @@ class SimpleCookieJar: simpleCookie = http.cookies.SimpleCookie(set_cookie) for k, v in simpleCookie.items(): - domain = v.get("domain") - if domain: + if domain := v.get("domain"): if not domain.startswith("."): - domain = "." + domain + domain = f".{domain}" self.jar[domain.lower()] = simpleCookie def get(self, host: str) -> str: @@ -58,7 +61,15 @@ class SimpleCookieJar: if host.endswith(domain) or host == domain[1:]: cookies.append(self.jar.get(domain)) - return "; ".join(filter( - None, sorted( - ["%s=%s" % (k, v.value) for cookie in filter(None, cookies) for k, v in cookie.items()] - ))) + return "; ".join( + filter( + None, + sorted( + [ + "%s=%s" % (k, v.value) + for cookie in filter(None, cookies) + for k, v in cookie.items() + ] + ), + ) + ) diff --git a/lib/websocket/_core.py b/lib/websocket/_core.py index e81066ad..f28ca4b9 100644 --- a/lib/websocket/_core.py +++ b/lib/websocket/_core.py @@ -2,6 +2,7 @@ import socket import struct import threading import time +from typing import Optional, Union # websocket modules from ._abnf import * @@ -32,7 +33,7 @@ See the License for the specific language governing permissions and limitations under the License. """ -__all__ = ['WebSocket', 'create_connection'] +__all__ = ["WebSocket", "create_connection"] class WebSocket: @@ -73,9 +74,16 @@ class WebSocket: Skip utf8 validation. """ - def __init__(self, get_mask_key=None, sockopt=None, sslopt=None, - fire_cont_frame: bool = False, enable_multithread: bool = True, - skip_utf8_validation: bool = False, **_): + def __init__( + self, + get_mask_key=None, + sockopt=None, + sslopt=None, + fire_cont_frame: bool = False, + enable_multithread: bool = True, + skip_utf8_validation: bool = False, + **_, + ): """ Initialize WebSocket object. @@ -86,14 +94,13 @@ class WebSocket: """ self.sock_opt = sock_opt(sockopt, sslopt) self.handshake_response = None - self.sock = None + self.sock: Optional[socket.socket] = None self.connected = False self.get_mask_key = get_mask_key # These buffer over the build-up of a single frame. self.frame_buffer = frame_buffer(self._recv, skip_utf8_validation) - self.cont_frame = continuous_frame( - fire_cont_frame, skip_utf8_validation) + self.cont_frame = continuous_frame(fire_cont_frame, skip_utf8_validation) if enable_multithread: self.lock = threading.Lock() @@ -133,7 +140,7 @@ class WebSocket: """ self.get_mask_key = func - def gettimeout(self) -> float: + def gettimeout(self) -> Union[float, int, None]: """ Get the websocket timeout (in seconds) as an int or float @@ -144,7 +151,7 @@ class WebSocket: """ return self.sock_opt.timeout - def settimeout(self, timeout: float): + def settimeout(self, timeout: Union[float, int, None]): """ Set the timeout to the websocket. @@ -245,19 +252,26 @@ class WebSocket: socket: socket Pre-initialized stream socket. """ - self.sock_opt.timeout = options.get('timeout', self.sock_opt.timeout) - self.sock, addrs = connect(url, self.sock_opt, proxy_info(**options), - options.pop('socket', None)) + self.sock_opt.timeout = options.get("timeout", self.sock_opt.timeout) + self.sock, addrs = connect( + url, self.sock_opt, proxy_info(**options), options.pop("socket", None) + ) try: self.handshake_response = handshake(self.sock, url, *addrs, **options) - for attempt in range(options.pop('redirect_limit', 3)): + for attempt in range(options.pop("redirect_limit", 3)): if self.handshake_response.status in SUPPORTED_REDIRECT_STATUSES: - url = self.handshake_response.headers['location'] + url = self.handshake_response.headers["location"] self.sock.close() - self.sock, addrs = connect(url, self.sock_opt, proxy_info(**options), - options.pop('socket', None)) - self.handshake_response = handshake(self.sock, url, *addrs, **options) + self.sock, addrs = connect( + url, + self.sock_opt, + proxy_info(**options), + options.pop("socket", None), + ) + self.handshake_response = handshake( + self.sock, url, *addrs, **options + ) self.connected = True except: if self.sock: @@ -265,7 +279,7 @@ class WebSocket: self.sock = None raise - def send(self, payload: bytes or str, opcode: int = ABNF.OPCODE_TEXT) -> int: + def send(self, payload: Union[bytes, str], opcode: int = ABNF.OPCODE_TEXT) -> int: """ Send the data as string. @@ -282,6 +296,18 @@ class WebSocket: frame = ABNF.create_frame(payload, opcode) return self.send_frame(frame) + def send_text(self, text_data: str) -> int: + """ + Sends UTF-8 encoded text. + """ + return self.send(text_data, ABNF.OPCODE_TEXT) + + def send_bytes(self, data: Union[bytes, bytearray]) -> int: + """ + Sends a sequence of bytes. + """ + return self.send(data, ABNF.OPCODE_BINARY) + def send_frame(self, frame) -> int: """ Send the data frame. @@ -303,9 +329,9 @@ class WebSocket: frame.get_mask_key = self.get_mask_key data = frame.format() length = len(data) - if (isEnabledForTrace()): - trace("++Sent raw: " + repr(data)) - trace("++Sent decoded: " + frame.__str__()) + if isEnabledForTrace(): + trace(f"++Sent raw: {repr(data)}") + trace(f"++Sent decoded: {frame.__str__()}") with self.lock: while data: l = self._send(data) @@ -324,7 +350,7 @@ class WebSocket: """ return self.send(payload, ABNF.OPCODE_BINARY) - def ping(self, payload: str or bytes = ""): + def ping(self, payload: Union[str, bytes] = ""): """ Send ping data. @@ -337,7 +363,7 @@ class WebSocket: payload = payload.encode("utf-8") self.send(payload, ABNF.OPCODE_PING) - def pong(self, payload: str or bytes = ""): + def pong(self, payload: Union[str, bytes] = ""): """ Send pong data. @@ -350,7 +376,7 @@ class WebSocket: payload = payload.encode("utf-8") self.send(payload, ABNF.OPCODE_PONG) - def recv(self) -> str or bytes: + def recv(self) -> Union[str, bytes]: """ Receive string data(byte array) from the server. @@ -361,11 +387,16 @@ class WebSocket: with self.readlock: opcode, data = self.recv_data() if opcode == ABNF.OPCODE_TEXT: - return data.decode("utf-8") - elif opcode == ABNF.OPCODE_TEXT or opcode == ABNF.OPCODE_BINARY: - return data + data_received: Union[bytes, str] = data + if isinstance(data_received, bytes): + return data_received.decode("utf-8") + elif isinstance(data_received, str): + return data_received + elif opcode == ABNF.OPCODE_BINARY: + data_binary: bytes = data + return data_binary else: - return '' + return "" def recv_data(self, control_frame: bool = False) -> tuple: """ @@ -385,7 +416,7 @@ class WebSocket: opcode, frame = self.recv_data_frame(control_frame) return opcode, frame.data - def recv_data_frame(self, control_frame: bool = False): + def recv_data_frame(self, control_frame: bool = False) -> tuple: """ Receive data with operation code. @@ -404,15 +435,18 @@ class WebSocket: """ while True: frame = self.recv_frame() - if (isEnabledForTrace()): - trace("++Rcv raw: " + repr(frame.format())) - trace("++Rcv decoded: " + frame.__str__()) + if isEnabledForTrace(): + trace(f"++Rcv raw: {repr(frame.format())}") + trace(f"++Rcv decoded: {frame.__str__()}") if not frame: # handle error: # 'NoneType' object has no attribute 'opcode' - raise WebSocketProtocolException( - "Not a valid frame {frame}".format(frame=frame)) - elif frame.opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_BINARY, ABNF.OPCODE_CONT): + raise WebSocketProtocolException(f"Not a valid frame {frame}") + elif frame.opcode in ( + ABNF.OPCODE_TEXT, + ABNF.OPCODE_BINARY, + ABNF.OPCODE_CONT, + ): self.cont_frame.validate(frame) self.cont_frame.add(frame) @@ -426,8 +460,7 @@ class WebSocket: if len(frame.data) < 126: self.pong(frame.data) else: - raise WebSocketProtocolException( - "Ping message is too long") + raise WebSocketProtocolException("Ping message is too long") if control_frame: return frame.opcode, frame elif frame.opcode == ABNF.OPCODE_PONG: @@ -458,9 +491,9 @@ class WebSocket: if status < 0 or status >= ABNF.LENGTH_16: raise ValueError("code is invalid range") self.connected = False - self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE) + self.send(struct.pack("!H", status) + reason, ABNF.OPCODE_CLOSE) - def close(self, status: int = STATUS_NORMAL, reason: bytes = b"", timeout: float = 3): + def close(self, status: int = STATUS_NORMAL, reason: bytes = b"", timeout: int = 3): """ Close Websocket object @@ -474,36 +507,37 @@ class WebSocket: Timeout until receive a close frame. If None, it will wait forever until receive a close frame. """ - if self.connected: - if status < 0 or status >= ABNF.LENGTH_16: - raise ValueError("code is invalid range") + if not self.connected: + return + if status < 0 or status >= ABNF.LENGTH_16: + raise ValueError("code is invalid range") - try: - self.connected = False - self.send(struct.pack('!H', status) + reason, ABNF.OPCODE_CLOSE) - sock_timeout = self.sock.gettimeout() - self.sock.settimeout(timeout) - start_time = time.time() - while timeout is None or time.time() - start_time < timeout: - try: - frame = self.recv_frame() - if frame.opcode != ABNF.OPCODE_CLOSE: - continue - if isEnabledForError(): - recv_status = struct.unpack("!H", frame.data[0:2])[0] - if recv_status >= 3000 and recv_status <= 4999: - debug("close status: " + repr(recv_status)) - elif recv_status != STATUS_NORMAL: - error("close status: " + repr(recv_status)) - break - except: - break - self.sock.settimeout(sock_timeout) - self.sock.shutdown(socket.SHUT_RDWR) - except: - pass + try: + self.connected = False + self.send(struct.pack("!H", status) + reason, ABNF.OPCODE_CLOSE) + sock_timeout = self.sock.gettimeout() + self.sock.settimeout(timeout) + start_time = time.time() + while timeout is None or time.time() - start_time < timeout: + try: + frame = self.recv_frame() + if frame.opcode != ABNF.OPCODE_CLOSE: + continue + if isEnabledForError(): + recv_status = struct.unpack("!H", frame.data[0:2])[0] + if recv_status >= 3000 and recv_status <= 4999: + debug(f"close status: {repr(recv_status)}") + elif recv_status != STATUS_NORMAL: + error(f"close status: {repr(recv_status)}") + break + except: + break + self.sock.settimeout(sock_timeout) + self.sock.shutdown(socket.SHUT_RDWR) + except: + pass - self.shutdown() + self.shutdown() def abort(self): """ @@ -521,7 +555,7 @@ class WebSocket: self.sock = None self.connected = False - def _send(self, data: str or bytes): + def _send(self, data: Union[str, bytes]): return send(self.sock, data) def _recv(self, bufsize): @@ -600,10 +634,14 @@ def create_connection(url: str, timeout=None, class_=WebSocket, **options): fire_cont_frame = options.pop("fire_cont_frame", False) enable_multithread = options.pop("enable_multithread", True) skip_utf8_validation = options.pop("skip_utf8_validation", False) - websock = class_(sockopt=sockopt, sslopt=sslopt, - fire_cont_frame=fire_cont_frame, - enable_multithread=enable_multithread, - skip_utf8_validation=skip_utf8_validation, **options) + websock = class_( + sockopt=sockopt, + sslopt=sslopt, + fire_cont_frame=fire_cont_frame, + enable_multithread=enable_multithread, + skip_utf8_validation=skip_utf8_validation, + **options, + ) websock.settimeout(timeout if timeout is not None else getdefaulttimeout()) websock.connect(url, **options) return websock diff --git a/lib/websocket/_exceptions.py b/lib/websocket/_exceptions.py index 48f40a07..c146fa5d 100644 --- a/lib/websocket/_exceptions.py +++ b/lib/websocket/_exceptions.py @@ -22,6 +22,7 @@ class WebSocketException(Exception): """ WebSocket exception class. """ + pass @@ -29,6 +30,7 @@ class WebSocketProtocolException(WebSocketException): """ If the WebSocket protocol is invalid, this exception will be raised. """ + pass @@ -36,6 +38,7 @@ class WebSocketPayloadException(WebSocketException): """ If the WebSocket payload is invalid, this exception will be raised. """ + pass @@ -44,6 +47,7 @@ class WebSocketConnectionClosedException(WebSocketException): If remote host closed the connection or some network error happened, this exception will be raised. """ + pass @@ -51,6 +55,7 @@ class WebSocketTimeoutException(WebSocketException): """ WebSocketTimeoutException will be raised at socket timeout during read/write data. """ + pass @@ -58,6 +63,7 @@ class WebSocketProxyException(WebSocketException): """ WebSocketProxyException will be raised when proxy error occurred. """ + pass @@ -66,7 +72,14 @@ class WebSocketBadStatusException(WebSocketException): WebSocketBadStatusException will be raised when we get bad handshake status code. """ - def __init__(self, message: str, status_code: int, status_message=None, resp_headers=None, resp_body=None): + def __init__( + self, + message: str, + status_code: int, + status_message=None, + resp_headers=None, + resp_body=None, + ): super().__init__(message) self.status_code = status_code self.resp_headers = resp_headers @@ -77,4 +90,5 @@ class WebSocketAddressException(WebSocketException): """ If the websocket address info cannot be found, this exception will be raised. """ + pass diff --git a/lib/websocket/_handshake.py b/lib/websocket/_handshake.py index d28aefd7..e63dc979 100644 --- a/lib/websocket/_handshake.py +++ b/lib/websocket/_handshake.py @@ -20,7 +20,8 @@ import hashlib import hmac import os from base64 import encodebytes as base64encode -from http import client as HTTPStatus +from http import HTTPStatus + from ._cookiejar import SimpleCookieJar from ._exceptions import * from ._http import * @@ -32,14 +33,19 @@ __all__ = ["handshake_response", "handshake", "SUPPORTED_REDIRECT_STATUSES"] # websocket supported version. VERSION = 13 -SUPPORTED_REDIRECT_STATUSES = (HTTPStatus.MOVED_PERMANENTLY, HTTPStatus.FOUND, HTTPStatus.SEE_OTHER,) +SUPPORTED_REDIRECT_STATUSES = ( + HTTPStatus.MOVED_PERMANENTLY, + HTTPStatus.FOUND, + HTTPStatus.SEE_OTHER, + HTTPStatus.TEMPORARY_REDIRECT, + HTTPStatus.PERMANENT_REDIRECT, +) SUCCESS_STATUSES = SUPPORTED_REDIRECT_STATUSES + (HTTPStatus.SWITCHING_PROTOCOLS,) CookieJar = SimpleCookieJar() class handshake_response: - def __init__(self, status: int, headers: dict, subprotocol): self.status = status self.headers = headers @@ -47,7 +53,9 @@ class handshake_response: CookieJar.add(headers.get("set-cookie")) -def handshake(sock, url: str, hostname: str, port: int, resource: str, **options): +def handshake( + sock, url: str, hostname: str, port: int, resource: str, **options +) -> handshake_response: headers, key = _get_handshake_headers(resource, url, hostname, port, options) header_str = "\r\n".join(headers) @@ -66,74 +74,64 @@ def handshake(sock, url: str, hostname: str, port: int, resource: str, **options def _pack_hostname(hostname: str) -> str: # IPv6 address - if ':' in hostname: - return '[' + hostname + ']' - + if ":" in hostname: + return f"[{hostname}]" return hostname -def _get_handshake_headers(resource: str, url: str, host: str, port: int, options: dict): - headers = [ - "GET {resource} HTTP/1.1".format(resource=resource), - "Upgrade: websocket" - ] - if port == 80 or port == 443: +def _get_handshake_headers( + resource: str, url: str, host: str, port: int, options: dict +) -> tuple: + headers = [f"GET {resource} HTTP/1.1", "Upgrade: websocket"] + if port in [80, 443]: hostport = _pack_hostname(host) else: - hostport = "{h}:{p}".format(h=_pack_hostname(host), p=port) + hostport = f"{_pack_hostname(host)}:{port}" if options.get("host"): - headers.append("Host: {h}".format(h=options["host"])) + headers.append(f'Host: {options["host"]}') else: - headers.append("Host: {hp}".format(hp=hostport)) + headers.append(f"Host: {hostport}") # scheme indicates whether http or https is used in Origin # The same approach is used in parse_url of _url.py to set default port scheme, url = url.split(":", 1) if not options.get("suppress_origin"): if "origin" in options and options["origin"] is not None: - headers.append("Origin: {origin}".format(origin=options["origin"])) + headers.append(f'Origin: {options["origin"]}') elif scheme == "wss": - headers.append("Origin: https://{hp}".format(hp=hostport)) + headers.append(f"Origin: https://{hostport}") else: - headers.append("Origin: http://{hp}".format(hp=hostport)) + headers.append(f"Origin: http://{hostport}") key = _create_sec_websocket_key() # Append Sec-WebSocket-Key & Sec-WebSocket-Version if not manually specified - if not options.get('header') or 'Sec-WebSocket-Key' not in options['header']: - headers.append("Sec-WebSocket-Key: {key}".format(key=key)) + if not options.get("header") or "Sec-WebSocket-Key" not in options["header"]: + headers.append(f"Sec-WebSocket-Key: {key}") else: - key = options['header']['Sec-WebSocket-Key'] + key = options["header"]["Sec-WebSocket-Key"] - if not options.get('header') or 'Sec-WebSocket-Version' not in options['header']: - headers.append("Sec-WebSocket-Version: {version}".format(version=VERSION)) + if not options.get("header") or "Sec-WebSocket-Version" not in options["header"]: + headers.append(f"Sec-WebSocket-Version: {VERSION}") - if not options.get('connection'): - headers.append('Connection: Upgrade') + if not options.get("connection"): + headers.append("Connection: Upgrade") else: - headers.append(options['connection']) + headers.append(options["connection"]) - subprotocols = options.get("subprotocols") - if subprotocols: - headers.append("Sec-WebSocket-Protocol: {protocols}".format(protocols=",".join(subprotocols))) + if subprotocols := options.get("subprotocols"): + headers.append(f'Sec-WebSocket-Protocol: {",".join(subprotocols)}') - header = options.get("header") - if header: + if header := options.get("header"): if isinstance(header, dict): - header = [ - ": ".join([k, v]) - for k, v in header.items() - if v is not None - ] + header = [": ".join([k, v]) for k, v in header.items() if v is not None] headers.extend(header) server_cookie = CookieJar.get(host) client_cookie = options.get("cookie", None) - cookie = "; ".join(filter(None, [server_cookie, client_cookie])) - - if cookie: - headers.append("Cookie: {cookie}".format(cookie=cookie)) + if cookie := "; ".join(filter(None, [server_cookie, client_cookie])): + headers.append(f"Cookie: {cookie}") headers.extend(("", "")) return headers, key @@ -142,12 +140,20 @@ def _get_handshake_headers(resource: str, url: str, host: str, port: int, option def _get_resp_headers(sock, success_statuses: tuple = SUCCESS_STATUSES) -> tuple: status, resp_headers, status_message = read_headers(sock) if status not in success_statuses: - content_len = resp_headers.get('content-length') + content_len = resp_headers.get("content-length") if content_len: - response_body = sock.recv(int(content_len)) # read the body of the HTTP error message response and include it in the exception + response_body = sock.recv( + int(content_len) + ) # read the body of the HTTP error message response and include it in the exception else: response_body = None - raise WebSocketBadStatusException("Handshake status {status} {message} -+-+- {headers} -+-+- {body}".format(status=status, message=status_message, headers=resp_headers, body=response_body), status, status_message, resp_headers, response_body) + raise WebSocketBadStatusException( + f"Handshake status {status} {status_message} -+-+- {resp_headers} -+-+- {response_body}", + status, + status_message, + resp_headers, + response_body, + ) return status, resp_headers @@ -157,20 +163,20 @@ _HEADERS_TO_CHECK = { } -def _validate(headers, key: str, subprotocols): +def _validate(headers, key: str, subprotocols) -> tuple: subproto = None for k, v in _HEADERS_TO_CHECK.items(): r = headers.get(k, None) if not r: return False, None - r = [x.strip().lower() for x in r.split(',')] + r = [x.strip().lower() for x in r.split(",")] if v not in r: return False, None if subprotocols: subproto = headers.get("sec-websocket-protocol", None) if not subproto or subproto.lower() not in [s.lower() for s in subprotocols]: - error("Invalid subprotocol: " + str(subprotocols)) + error(f"Invalid subprotocol: {subprotocols}") return False, None subproto = subproto.lower() @@ -180,13 +186,12 @@ def _validate(headers, key: str, subprotocols): result = result.lower() if isinstance(result, str): - result = result.encode('utf-8') + result = result.encode("utf-8") - value = (key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").encode('utf-8') + value = f"{key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11".encode("utf-8") hashed = base64encode(hashlib.sha1(value).digest()).strip().lower() - success = hmac.compare_digest(hashed, result) - if success: + if hmac.compare_digest(hashed, result): return True, subproto else: return False, None @@ -194,4 +199,4 @@ def _validate(headers, key: str, subprotocols): def _create_sec_websocket_key() -> str: randomness = os.urandom(16) - return base64encode(randomness).decode('utf-8').strip() + return base64encode(randomness).decode("utf-8").strip() diff --git a/lib/websocket/_http.py b/lib/websocket/_http.py index 13183b20..977a30f1 100644 --- a/lib/websocket/_http.py +++ b/lib/websocket/_http.py @@ -19,6 +19,7 @@ limitations under the License. import errno import os import socket +from base64 import encodebytes as base64encode from ._exceptions import * from ._logging import * @@ -26,14 +27,13 @@ from ._socket import * from ._ssl_compat import * from ._url import * -from base64 import encodebytes as base64encode - __all__ = ["proxy_info", "connect", "read_headers"] try: - from python_socks.sync import Proxy from python_socks._errors import * from python_socks._types import ProxyType + from python_socks.sync import Proxy + HAVE_PYTHON_SOCKS = True except: HAVE_PYTHON_SOCKS = False @@ -49,7 +49,6 @@ except: class proxy_info: - def __init__(self, **options): self.proxy_host = options.get("http_proxy_host", None) if self.proxy_host: @@ -59,8 +58,16 @@ class proxy_info: self.proxy_protocol = options.get("proxy_type", "http") # Note: If timeout not specified, default python-socks timeout is 60 seconds self.proxy_timeout = options.get("http_proxy_timeout", None) - if self.proxy_protocol not in ['http', 'socks4', 'socks4a', 'socks5', 'socks5h']: - raise ProxyError("Only http, socks4, socks5 proxy protocols are supported") + if self.proxy_protocol not in [ + "http", + "socks4", + "socks4a", + "socks5", + "socks5h", + ]: + raise ProxyError( + "Only http, socks4, socks5 proxy protocols are supported" + ) else: self.proxy_port = 0 self.auth = None @@ -68,25 +75,28 @@ class proxy_info: self.proxy_protocol = "http" -def _start_proxied_socket(url: str, options, proxy): +def _start_proxied_socket(url: str, options, proxy) -> tuple: if not HAVE_PYTHON_SOCKS: - raise WebSocketException("Python Socks is needed for SOCKS proxying but is not available") + raise WebSocketException( + "Python Socks is needed for SOCKS proxying but is not available" + ) hostname, port, resource, is_secure = parse_url(url) - if proxy.proxy_protocol == "socks5": - rdns = False - proxy_type = ProxyType.SOCKS5 if proxy.proxy_protocol == "socks4": rdns = False proxy_type = ProxyType.SOCKS4 - # socks5h and socks4a send DNS through proxy - if proxy.proxy_protocol == "socks5h": - rdns = True - proxy_type = ProxyType.SOCKS5 - if proxy.proxy_protocol == "socks4a": + # socks4a sends DNS through proxy + elif proxy.proxy_protocol == "socks4a": rdns = True proxy_type = ProxyType.SOCKS4 + elif proxy.proxy_protocol == "socks5": + rdns = False + proxy_type = ProxyType.SOCKS5 + # socks5h sends DNS through proxy + elif proxy.proxy_protocol == "socks5h": + rdns = True + proxy_type = ProxyType.SOCKS5 ws_proxy = Proxy.create( proxy_type=proxy_type, @@ -94,14 +104,16 @@ def _start_proxied_socket(url: str, options, proxy): port=int(proxy.proxy_port), username=proxy.auth[0] if proxy.auth else None, password=proxy.auth[1] if proxy.auth else None, - rdns=rdns) + rdns=rdns, + ) sock = ws_proxy.connect(hostname, port, timeout=proxy.proxy_timeout) - if is_secure and HAVE_SSL: - sock = _ssl_socket(sock, options.sslopt, hostname) - elif is_secure: - raise WebSocketException("SSL not available.") + if is_secure: + if HAVE_SSL: + sock = _ssl_socket(sock, options.sslopt, hostname) + else: + raise WebSocketException("SSL not available.") return sock, (hostname, port, resource) @@ -110,7 +122,7 @@ def connect(url: str, options, proxy, socket): # Use _start_proxied_socket() only for socks4 or socks5 proxy # Use _tunnel() for http proxy # TODO: Use python-socks for http protocol also, to standardize flow - if proxy.proxy_host and not socket and not (proxy.proxy_protocol == "http"): + if proxy.proxy_host and not socket and proxy.proxy_protocol != "http": return _start_proxied_socket(url, options, proxy) hostname, port_from_url, resource, is_secure = parse_url(url) @@ -119,10 +131,10 @@ def connect(url: str, options, proxy, socket): return socket, (hostname, port_from_url, resource) addrinfo_list, need_tunnel, auth = _get_addrinfo_list( - hostname, port_from_url, is_secure, proxy) + hostname, port_from_url, is_secure, proxy + ) if not addrinfo_list: - raise WebSocketException( - "Host not found.: " + hostname + ":" + str(port_from_url)) + raise WebSocketException(f"Host not found.: {hostname}:{port_from_url}") sock = None try: @@ -143,16 +155,23 @@ def connect(url: str, options, proxy, socket): raise -def _get_addrinfo_list(hostname, port, is_secure, proxy): +def _get_addrinfo_list(hostname, port: int, is_secure: bool, proxy) -> tuple: phost, pport, pauth = get_proxy_info( - hostname, is_secure, proxy.proxy_host, proxy.proxy_port, proxy.auth, proxy.no_proxy) + hostname, + is_secure, + proxy.proxy_host, + proxy.proxy_port, + proxy.auth, + proxy.no_proxy, + ) try: # when running on windows 10, getaddrinfo without socktype returns a socktype 0. # This generates an error exception: `_on_error: exception Socket type must be stream or datagram, not 0` # or `OSError: [Errno 22] Invalid argument` when creating socket. Force the socket type to SOCK_STREAM. if not phost: addrinfo_list = socket.getaddrinfo( - hostname, port, 0, socket.SOCK_STREAM, socket.SOL_TCP) + hostname, port, 0, socket.SOCK_STREAM, socket.SOL_TCP + ) return addrinfo_list, False, None else: pport = pport and pport or 80 @@ -160,7 +179,9 @@ def _get_addrinfo_list(hostname, port, is_secure, proxy): # returns a socktype 0. This generates an error exception: # _on_error: exception Socket type must be stream or datagram, not 0 # Force the socket type to SOCK_STREAM - addrinfo_list = socket.getaddrinfo(phost, pport, 0, socket.SOCK_STREAM, socket.SOL_TCP) + addrinfo_list = socket.getaddrinfo( + phost, pport, 0, socket.SOCK_STREAM, socket.SOL_TCP + ) return addrinfo_list, True, pauth except socket.gaierror as e: raise WebSocketAddressException(e) @@ -186,14 +207,17 @@ def _open_socket(addrinfo_list, sockopt, timeout): sock.close() error.remote_ip = str(address[0]) try: - eConnRefused = (errno.ECONNREFUSED, errno.WSAECONNREFUSED, errno.ENETUNREACH) + eConnRefused = ( + errno.ECONNREFUSED, + errno.WSAECONNREFUSED, + errno.ENETUNREACH, + ) except AttributeError: eConnRefused = (errno.ECONNREFUSED, errno.ENETUNREACH) - if error.errno in eConnRefused: - err = error - continue - else: + if error.errno not in eConnRefused: raise error + err = error + continue else: break else: @@ -206,89 +230,97 @@ def _open_socket(addrinfo_list, sockopt, timeout): return sock -def _wrap_sni_socket(sock, sslopt, hostname, check_hostname): - context = sslopt.get('context', None) +def _wrap_sni_socket(sock: socket.socket, sslopt: dict, hostname, check_hostname): + context = sslopt.get("context", None) if not context: - context = ssl.SSLContext(sslopt.get('ssl_version', ssl.PROTOCOL_TLS_CLIENT)) + context = ssl.SSLContext(sslopt.get("ssl_version", ssl.PROTOCOL_TLS_CLIENT)) # Non default context need to manually enable SSLKEYLOGFILE support by setting the keylog_filename attribute. # For more details see also: # * https://docs.python.org/3.8/library/ssl.html?highlight=sslkeylogfile#context-creation # * https://docs.python.org/3.8/library/ssl.html?highlight=sslkeylogfile#ssl.SSLContext.keylog_filename context.keylog_filename = os.environ.get("SSLKEYLOGFILE", None) - if sslopt.get('cert_reqs', ssl.CERT_NONE) != ssl.CERT_NONE: - cafile = sslopt.get('ca_certs', None) - capath = sslopt.get('ca_cert_path', None) + if sslopt.get("cert_reqs", ssl.CERT_NONE) != ssl.CERT_NONE: + cafile = sslopt.get("ca_certs", None) + capath = sslopt.get("ca_cert_path", None) if cafile or capath: context.load_verify_locations(cafile=cafile, capath=capath) - elif hasattr(context, 'load_default_certs'): + elif hasattr(context, "load_default_certs"): context.load_default_certs(ssl.Purpose.SERVER_AUTH) - if sslopt.get('certfile', None): + if sslopt.get("certfile", None): context.load_cert_chain( - sslopt['certfile'], - sslopt.get('keyfile', None), - sslopt.get('password', None), + sslopt["certfile"], + sslopt.get("keyfile", None), + sslopt.get("password", None), ) # Python 3.10 switch to PROTOCOL_TLS_CLIENT defaults to "cert_reqs = ssl.CERT_REQUIRED" and "check_hostname = True" # If both disabled, set check_hostname before verify_mode # see https://github.com/liris/websocket-client/commit/b96a2e8fa765753e82eea531adb19716b52ca3ca#commitcomment-10803153 - if sslopt.get('cert_reqs', ssl.CERT_NONE) == ssl.CERT_NONE and not sslopt.get('check_hostname', False): + if sslopt.get("cert_reqs", ssl.CERT_NONE) == ssl.CERT_NONE and not sslopt.get( + "check_hostname", False + ): context.check_hostname = False context.verify_mode = ssl.CERT_NONE else: - context.check_hostname = sslopt.get('check_hostname', True) - context.verify_mode = sslopt.get('cert_reqs', ssl.CERT_REQUIRED) + context.check_hostname = sslopt.get("check_hostname", True) + context.verify_mode = sslopt.get("cert_reqs", ssl.CERT_REQUIRED) - if 'ciphers' in sslopt: - context.set_ciphers(sslopt['ciphers']) - if 'cert_chain' in sslopt: - certfile, keyfile, password = sslopt['cert_chain'] + if "ciphers" in sslopt: + context.set_ciphers(sslopt["ciphers"]) + if "cert_chain" in sslopt: + certfile, keyfile, password = sslopt["cert_chain"] context.load_cert_chain(certfile, keyfile, password) - if 'ecdh_curve' in sslopt: - context.set_ecdh_curve(sslopt['ecdh_curve']) + if "ecdh_curve" in sslopt: + context.set_ecdh_curve(sslopt["ecdh_curve"]) return context.wrap_socket( sock, - do_handshake_on_connect=sslopt.get('do_handshake_on_connect', True), - suppress_ragged_eofs=sslopt.get('suppress_ragged_eofs', True), + do_handshake_on_connect=sslopt.get("do_handshake_on_connect", True), + suppress_ragged_eofs=sslopt.get("suppress_ragged_eofs", True), server_hostname=hostname, ) -def _ssl_socket(sock, user_sslopt, hostname): - sslopt = dict(cert_reqs=ssl.CERT_REQUIRED) +def _ssl_socket(sock: socket.socket, user_sslopt: dict, hostname): + sslopt: dict = dict(cert_reqs=ssl.CERT_REQUIRED) sslopt.update(user_sslopt) - certPath = os.environ.get('WEBSOCKET_CLIENT_CA_BUNDLE') - if certPath and os.path.isfile(certPath) \ - and user_sslopt.get('ca_certs', None) is None: - sslopt['ca_certs'] = certPath - elif certPath and os.path.isdir(certPath) \ - and user_sslopt.get('ca_cert_path', None) is None: - sslopt['ca_cert_path'] = certPath + certPath = os.environ.get("WEBSOCKET_CLIENT_CA_BUNDLE") + if ( + certPath + and os.path.isfile(certPath) + and user_sslopt.get("ca_certs", None) is None + ): + sslopt["ca_certs"] = certPath + elif ( + certPath + and os.path.isdir(certPath) + and user_sslopt.get("ca_cert_path", None) is None + ): + sslopt["ca_cert_path"] = certPath - if sslopt.get('server_hostname', None): - hostname = sslopt['server_hostname'] + if sslopt.get("server_hostname", None): + hostname = sslopt["server_hostname"] - check_hostname = sslopt.get('check_hostname', True) + check_hostname = sslopt.get("check_hostname", True) sock = _wrap_sni_socket(sock, sslopt, hostname, check_hostname) return sock -def _tunnel(sock, host, port, auth): +def _tunnel(sock: socket.socket, host, port: int, auth) -> socket.socket: debug("Connecting proxy...") - connect_header = "CONNECT {h}:{p} HTTP/1.1\r\n".format(h=host, p=port) - connect_header += "Host: {h}:{p}\r\n".format(h=host, p=port) + connect_header = f"CONNECT {host}:{port} HTTP/1.1\r\n" + connect_header += f"Host: {host}:{port}\r\n" # TODO: support digest auth. if auth and auth[0]: auth_str = auth[0] if auth[1]: - auth_str += ":" + auth[1] - encoded_str = base64encode(auth_str.encode()).strip().decode().replace('\n', '') - connect_header += "Proxy-Authorization: Basic {str}\r\n".format(str=encoded_str) + auth_str += f":{auth[1]}" + encoded_str = base64encode(auth_str.encode()).strip().decode().replace("\n", "") + connect_header += f"Proxy-Authorization: Basic {encoded_str}\r\n" connect_header += "\r\n" dump("request header", connect_header) @@ -300,40 +332,37 @@ def _tunnel(sock, host, port, auth): raise WebSocketProxyException(str(e)) if status != 200: - raise WebSocketProxyException( - "failed CONNECT via proxy status: {status}".format(status=status)) + raise WebSocketProxyException(f"failed CONNECT via proxy status: {status}") return sock -def read_headers(sock): +def read_headers(sock: socket.socket) -> tuple: status = None status_message = None - headers = {} + headers: dict = {} trace("--- response header ---") while True: line = recv_line(sock) - line = line.decode('utf-8').strip() + line = line.decode("utf-8").strip() if not line: break trace(line) if not status: - status_info = line.split(" ", 2) status = int(status_info[1]) if len(status_info) > 2: status_message = status_info[2] else: kv = line.split(":", 1) - if len(kv) == 2: - key, value = kv - if key.lower() == "set-cookie" and headers.get("set-cookie"): - headers["set-cookie"] = headers.get("set-cookie") + "; " + value.strip() - else: - headers[key.lower()] = value.strip() - else: + if len(kv) != 2: raise WebSocketException("Invalid header") + key, value = kv + if key.lower() == "set-cookie" and headers.get("set-cookie"): + headers["set-cookie"] = headers.get("set-cookie") + "; " + value.strip() + else: + headers[key.lower()] = value.strip() trace("-----------------------") diff --git a/lib/websocket/_logging.py b/lib/websocket/_logging.py index 806de4d4..b88cda37 100644 --- a/lib/websocket/_logging.py +++ b/lib/websocket/_logging.py @@ -19,25 +19,38 @@ See the License for the specific language governing permissions and limitations under the License. """ -_logger = logging.getLogger('websocket') +_logger = logging.getLogger("websocket") try: from logging import NullHandler except ImportError: + class NullHandler(logging.Handler): def emit(self, record) -> None: pass + _logger.addHandler(NullHandler()) _traceEnabled = False -__all__ = ["enableTrace", "dump", "error", "warning", "debug", "trace", - "isEnabledForError", "isEnabledForDebug", "isEnabledForTrace"] +__all__ = [ + "enableTrace", + "dump", + "error", + "warning", + "debug", + "trace", + "isEnabledForError", + "isEnabledForDebug", + "isEnabledForTrace", +] -def enableTrace(traceable: bool, - handler: logging.StreamHandler = logging.StreamHandler(), - level: str = "DEBUG") -> None: +def enableTrace( + traceable: bool, + handler: logging.StreamHandler = logging.StreamHandler(), + level: str = "DEBUG", +) -> None: """ Turn on/off the traceability. @@ -55,7 +68,7 @@ def enableTrace(traceable: bool, def dump(title: str, message: str) -> None: if _traceEnabled: - _logger.debug("--- " + title + " ---") + _logger.debug(f"--- {title} ---") _logger.debug(message) _logger.debug("-----------------------") diff --git a/lib/websocket/_socket.py b/lib/websocket/_socket.py index e8858fc8..f0ad27a5 100644 --- a/lib/websocket/_socket.py +++ b/lib/websocket/_socket.py @@ -1,6 +1,7 @@ import errno import selectors import socket +from typing import Union from ._exceptions import * from ._ssl_compat import * @@ -37,12 +38,18 @@ if hasattr(socket, "TCP_KEEPCNT"): _default_timeout = None -__all__ = ["DEFAULT_SOCKET_OPTION", "sock_opt", "setdefaulttimeout", "getdefaulttimeout", - "recv", "recv_line", "send"] +__all__ = [ + "DEFAULT_SOCKET_OPTION", + "sock_opt", + "setdefaulttimeout", + "getdefaulttimeout", + "recv", + "recv_line", + "send", +] class sock_opt: - def __init__(self, sockopt: list, sslopt: dict) -> None: if sockopt is None: sockopt = [] @@ -53,7 +60,7 @@ class sock_opt: self.timeout = None -def setdefaulttimeout(timeout: int or float) -> None: +def setdefaulttimeout(timeout: Union[int, float, None]) -> None: """ Set the global timeout setting to connect. @@ -66,7 +73,7 @@ def setdefaulttimeout(timeout: int or float) -> None: _default_timeout = timeout -def getdefaulttimeout() -> int or float: +def getdefaulttimeout() -> Union[int, float, None]: """ Get default timeout @@ -89,7 +96,7 @@ def recv(sock: socket.socket, bufsize: int) -> bytes: pass except socket.error as exc: error_code = extract_error_code(exc) - if error_code != errno.EAGAIN and error_code != errno.EWOULDBLOCK: + if error_code not in [errno.EAGAIN, errno.EWOULDBLOCK]: raise sel = selectors.DefaultSelector() @@ -113,14 +120,13 @@ def recv(sock: socket.socket, bufsize: int) -> bytes: raise WebSocketTimeoutException(message) except SSLError as e: message = extract_err_message(e) - if isinstance(message, str) and 'timed out' in message: + if isinstance(message, str) and "timed out" in message: raise WebSocketTimeoutException(message) else: raise if not bytes_: - raise WebSocketConnectionClosedException( - "Connection to remote host was lost.") + raise WebSocketConnectionClosedException("Connection to remote host was lost.") return bytes_ @@ -130,14 +136,14 @@ def recv_line(sock: socket.socket) -> bytes: while True: c = recv(sock, 1) line.append(c) - if c == b'\n': + if c == b"\n": break - return b''.join(line) + return b"".join(line) -def send(sock: socket.socket, data: bytes) -> int: +def send(sock: socket.socket, data: Union[bytes, str]) -> int: if isinstance(data, str): - data = data.encode('utf-8') + data = data.encode("utf-8") if not sock: raise WebSocketConnectionClosedException("socket is already closed.") @@ -151,7 +157,7 @@ def send(sock: socket.socket, data: bytes) -> int: error_code = extract_error_code(exc) if error_code is None: raise - if error_code != errno.EAGAIN and error_code != errno.EWOULDBLOCK: + if error_code not in [errno.EAGAIN, errno.EWOULDBLOCK]: raise sel = selectors.DefaultSelector() diff --git a/lib/websocket/_ssl_compat.py b/lib/websocket/_ssl_compat.py index b2eba387..029a5c6e 100644 --- a/lib/websocket/_ssl_compat.py +++ b/lib/websocket/_ssl_compat.py @@ -20,9 +20,8 @@ __all__ = ["HAVE_SSL", "ssl", "SSLError", "SSLWantReadError", "SSLWantWriteError try: import ssl - from ssl import SSLError - from ssl import SSLWantReadError - from ssl import SSLWantWriteError + from ssl import SSLError, SSLWantReadError, SSLWantWriteError + HAVE_SSL = True except ImportError: # dummy class of SSLError for environment without ssl support diff --git a/lib/websocket/_url.py b/lib/websocket/_url.py index 2141b021..7d53830e 100644 --- a/lib/websocket/_url.py +++ b/lib/websocket/_url.py @@ -1,7 +1,7 @@ import os import socket import struct - +from typing import Optional from urllib.parse import unquote, urlparse """ @@ -67,7 +67,7 @@ def parse_url(url: str) -> tuple: resource = "/" if parsed.query: - resource += "?" + parsed.query + resource += f"?{parsed.query}" return hostname, port, resource, is_secure @@ -93,37 +93,50 @@ def _is_subnet_address(hostname: str) -> bool: def _is_address_in_network(ip: str, net: str) -> bool: - ipaddr = struct.unpack('!I', socket.inet_aton(ip))[0] - netaddr, netmask = net.split('/') - netaddr = struct.unpack('!I', socket.inet_aton(netaddr))[0] + ipaddr: int = struct.unpack("!I", socket.inet_aton(ip))[0] + netaddr, netmask = net.split("/") + netaddr: int = struct.unpack("!I", socket.inet_aton(netaddr))[0] netmask = (0xFFFFFFFF << (32 - int(netmask))) & 0xFFFFFFFF return ipaddr & netmask == netaddr -def _is_no_proxy_host(hostname: str, no_proxy: list) -> bool: +def _is_no_proxy_host(hostname: str, no_proxy: Optional[list]) -> bool: if not no_proxy: - v = os.environ.get("no_proxy", os.environ.get("NO_PROXY", "")).replace(" ", "") - if v: + if v := os.environ.get("no_proxy", os.environ.get("NO_PROXY", "")).replace( + " ", "" + ): no_proxy = v.split(",") if not no_proxy: no_proxy = DEFAULT_NO_PROXY_HOST - if '*' in no_proxy: + if "*" in no_proxy: return True if hostname in no_proxy: return True if _is_ip_address(hostname): - return any([_is_address_in_network(hostname, subnet) for subnet in no_proxy if _is_subnet_address(subnet)]) - for domain in [domain for domain in no_proxy if domain.startswith('.')]: + return any( + [ + _is_address_in_network(hostname, subnet) + for subnet in no_proxy + if _is_subnet_address(subnet) + ] + ) + for domain in [domain for domain in no_proxy if domain.startswith(".")]: if hostname.endswith(domain): return True return False def get_proxy_info( - hostname: str, is_secure: bool, proxy_host: str = None, proxy_port: int = 0, proxy_auth: tuple = None, - no_proxy: list = None, proxy_type: str = 'http') -> tuple: + hostname: str, + is_secure: bool, + proxy_host: Optional[str] = None, + proxy_port: int = 0, + proxy_auth: Optional[tuple] = None, + no_proxy: Optional[list] = None, + proxy_type: str = "http", +) -> tuple: """ Try to retrieve proxy host and port from environment if not provided in options. @@ -159,10 +172,16 @@ def get_proxy_info( return proxy_host, port, auth env_key = "https_proxy" if is_secure else "http_proxy" - value = os.environ.get(env_key, os.environ.get(env_key.upper(), "")).replace(" ", "") + value = os.environ.get(env_key, os.environ.get(env_key.upper(), "")).replace( + " ", "" + ) if value: proxy = urlparse(value) - auth = (unquote(proxy.username), unquote(proxy.password)) if proxy.username else None + auth = ( + (unquote(proxy.username), unquote(proxy.password)) + if proxy.username + else None + ) return proxy.hostname, proxy.port, auth return None, 0, None diff --git a/lib/websocket/_utils.py b/lib/websocket/_utils.py index 3db2d83b..c63adfa5 100644 --- a/lib/websocket/_utils.py +++ b/lib/websocket/_utils.py @@ -1,3 +1,5 @@ +from typing import Union + """ _url.py websocket - WebSocket client library for Python @@ -20,7 +22,6 @@ __all__ = ["NoLock", "validate_utf8", "extract_err_message", "extract_error_code class NoLock: - def __enter__(self) -> None: pass @@ -33,8 +34,9 @@ try: # strings. from wsaccel.utf8validator import Utf8Validator - def _validate_utf8(utfbytes: bytes) -> bool: - return Utf8Validator().validate(utfbytes)[0] + def _validate_utf8(utfbytes: Union[str, bytes]) -> bool: + result: bool = Utf8Validator().validate(utfbytes)[0] + return result except ImportError: # UTF-8 validator @@ -46,44 +48,396 @@ except ImportError: _UTF8D = [ # The first part of the table maps bytes to character classes that # to reduce the size of the transition table and create bitmasks. - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, - 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, - 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, - + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 9, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 7, + 8, + 8, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 10, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 4, + 3, + 3, + 11, + 6, + 6, + 6, + 5, + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 8, # The second part is a transition table that maps a combination # of a state of the automaton and a character class to a state. - 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, - 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, - 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, - 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, - 12,36,12,12,12,12,12,12,12,12,12,12, ] + 0, + 12, + 24, + 36, + 60, + 96, + 84, + 12, + 12, + 12, + 48, + 72, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 0, + 12, + 12, + 12, + 12, + 12, + 0, + 12, + 0, + 12, + 12, + 12, + 24, + 12, + 12, + 12, + 12, + 12, + 24, + 12, + 24, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 24, + 12, + 12, + 12, + 12, + 12, + 24, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 24, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 36, + 12, + 36, + 12, + 12, + 12, + 36, + 12, + 12, + 12, + 12, + 12, + 36, + 12, + 36, + 12, + 12, + 12, + 36, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + 12, + ] def _decode(state: int, codep: int, ch: int) -> tuple: tp = _UTF8D[ch] - codep = (ch & 0x3f) | (codep << 6) if ( - state != _UTF8_ACCEPT) else (0xff >> tp) & ch + codep = ( + (ch & 0x3F) | (codep << 6) if (state != _UTF8_ACCEPT) else (0xFF >> tp) & ch + ) state = _UTF8D[256 + state + tp] return state, codep - def _validate_utf8(utfbytes: str or bytes) -> bool: + def _validate_utf8(utfbytes: Union[str, bytes]) -> bool: state = _UTF8_ACCEPT codep = 0 for i in utfbytes: - state, codep = _decode(state, codep, i) + state, codep = _decode(state, codep, int(i)) if state == _UTF8_REJECT: return False return True -def validate_utf8(utfbytes: str or bytes) -> bool: +def validate_utf8(utfbytes: Union[str, bytes]) -> bool: """ validate utf8 byte string. utfbytes: utf byte string to check. @@ -92,13 +446,14 @@ def validate_utf8(utfbytes: str or bytes) -> bool: return _validate_utf8(utfbytes) -def extract_err_message(exception: Exception) -> str or None: +def extract_err_message(exception: Exception) -> Union[str, None]: if exception.args: - return exception.args[0] + exception_message: str = exception.args[0] + return exception_message else: return None -def extract_error_code(exception: Exception) -> int or None: +def extract_error_code(exception: Exception) -> Union[int, None]: if exception.args and len(exception.args) > 1: return exception.args[0] if isinstance(exception.args[0], int) else None diff --git a/lib/websocket/_wsdump.py b/lib/websocket/_wsdump.py index d637ce2b..34c3d127 100644 --- a/lib/websocket/_wsdump.py +++ b/lib/websocket/_wsdump.py @@ -21,11 +21,11 @@ limitations under the License. import argparse import code +import gzip +import ssl import sys import threading import time -import ssl -import gzip import zlib from urllib.parse import urlparse @@ -50,8 +50,13 @@ ENCODING = get_encoding() class VAction(argparse.Action): - - def __call__(self, parser: argparse.Namespace, args: tuple, values: str, option_string: str = None) -> None: + def __call__( + self, + parser: argparse.Namespace, + args: tuple, + values: str, + option_string: str = None, + ) -> None: if values is None: values = "1" try: @@ -63,36 +68,42 @@ class VAction(argparse.Action): def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="WebSocket Simple Dump Tool") - parser.add_argument("url", metavar="ws_url", - help="websocket url. ex. ws://echo.websocket.events/") - parser.add_argument("-p", "--proxy", - help="proxy url. ex. http://127.0.0.1:8080") - parser.add_argument("-v", "--verbose", default=0, nargs='?', action=VAction, - dest="verbose", - help="set verbose mode. If set to 1, show opcode. " - "If set to 2, enable to trace websocket module") - parser.add_argument("-n", "--nocert", action='store_true', - help="Ignore invalid SSL cert") - parser.add_argument("-r", "--raw", action="store_true", - help="raw output") - parser.add_argument("-s", "--subprotocols", nargs='*', - help="Set subprotocols") - parser.add_argument("-o", "--origin", - help="Set origin") - parser.add_argument("--eof-wait", default=0, type=int, - help="wait time(second) after 'EOF' received.") - parser.add_argument("-t", "--text", - help="Send initial text") - parser.add_argument("--timings", action="store_true", - help="Print timings in seconds") - parser.add_argument("--headers", - help="Set custom headers. Use ',' as separator") + parser.add_argument( + "url", metavar="ws_url", help="websocket url. ex. ws://echo.websocket.events/" + ) + parser.add_argument("-p", "--proxy", help="proxy url. ex. http://127.0.0.1:8080") + parser.add_argument( + "-v", + "--verbose", + default=0, + nargs="?", + action=VAction, + dest="verbose", + help="set verbose mode. If set to 1, show opcode. " + "If set to 2, enable to trace websocket module", + ) + parser.add_argument( + "-n", "--nocert", action="store_true", help="Ignore invalid SSL cert" + ) + parser.add_argument("-r", "--raw", action="store_true", help="raw output") + parser.add_argument("-s", "--subprotocols", nargs="*", help="Set subprotocols") + parser.add_argument("-o", "--origin", help="Set origin") + parser.add_argument( + "--eof-wait", + default=0, + type=int, + help="wait time(second) after 'EOF' received.", + ) + parser.add_argument("-t", "--text", help="Send initial text") + parser.add_argument( + "--timings", action="store_true", help="Print timings in seconds" + ) + parser.add_argument("--headers", help="Set custom headers. Use ',' as separator") return parser.parse_args() class RawInput: - def raw_input(self, prompt: str = "") -> str: line = input(prompt) @@ -105,7 +116,6 @@ class RawInput: class InteractiveConsole(RawInput, code.InteractiveConsole): - def write(self, data: str) -> None: sys.stdout.write("\033[2K\033[E") # sys.stdout.write("\n") @@ -118,7 +128,6 @@ class InteractiveConsole(RawInput, code.InteractiveConsole): class NonInteractive(RawInput): - def write(self, data: str) -> None: sys.stdout.write(data) sys.stdout.write("\n") @@ -146,7 +155,7 @@ def main() -> None: if args.nocert: opts = {"cert_reqs": ssl.CERT_NONE, "check_hostname": False} if args.headers: - options['header'] = list(map(str.strip, args.headers.split(','))) + options["header"] = list(map(str.strip, args.headers.split(","))) ws = websocket.create_connection(args.url, sslopt=opts, **options) if args.raw: console = NonInteractive() @@ -160,7 +169,7 @@ def main() -> None: except websocket.WebSocketException: return websocket.ABNF.OPCODE_CLOSE, "" if not frame: - raise websocket.WebSocketException("Not a valid frame {frame}".format(frame=frame)) + raise websocket.WebSocketException(f"Not a valid frame {frame}") elif frame.opcode in OPCODE_DATA: return frame.opcode, frame.data elif frame.opcode == websocket.ABNF.OPCODE_CLOSE: @@ -178,14 +187,18 @@ def main() -> None: msg = None if opcode == websocket.ABNF.OPCODE_TEXT and isinstance(data, bytes): data = str(data, "utf-8") - if isinstance(data, bytes) and len(data) > 2 and data[:2] == b'\037\213': # gzip magick + if ( + isinstance(data, bytes) and len(data) > 2 and data[:2] == b"\037\213" + ): # gzip magick try: data = "[gzip] " + str(gzip.decompress(data), "utf-8") except: pass elif isinstance(data, bytes): try: - data = "[zlib] " + str(zlib.decompress(data, -zlib.MAX_WBITS), "utf-8") + data = "[zlib] " + str( + zlib.decompress(data, -zlib.MAX_WBITS), "utf-8" + ) except: pass @@ -193,13 +206,13 @@ def main() -> None: data = repr(data) if args.verbose: - msg = "{opcode}: {data}".format(opcode=websocket.ABNF.OPCODE_MAP.get(opcode), data=data) + msg = f"{websocket.ABNF.OPCODE_MAP.get(opcode)}: {data}" else: msg = data if msg is not None: if args.timings: - console.write(str(time.time() - start_time) + ": " + msg) + console.write(f"{time.time() - start_time}: {msg}") else: console.write(msg) diff --git a/lib/websocket/py.typed b/lib/websocket/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/lib/websocket/tests/echo-server.py b/lib/websocket/tests/echo-server.py index 42dd9e43..a32c6503 100644 --- a/lib/websocket/tests/echo-server.py +++ b/lib/websocket/tests/echo-server.py @@ -3,10 +3,11 @@ # From https://github.com/aaugustin/websockets/blob/main/example/echo.py import asyncio -import websockets import os -LOCAL_WS_SERVER_PORT = int(os.environ.get('LOCAL_WS_SERVER_PORT', '8765')) +import websockets + +LOCAL_WS_SERVER_PORT = int(os.environ.get("LOCAL_WS_SERVER_PORT", "8765")) async def echo(websocket, path): @@ -18,4 +19,5 @@ async def main(): async with websockets.serve(echo, "localhost", LOCAL_WS_SERVER_PORT): await asyncio.Future() # run forever + asyncio.run(main()) diff --git a/lib/websocket/tests/test_abnf.py b/lib/websocket/tests/test_abnf.py index dbf9b636..df5bc6bd 100644 --- a/lib/websocket/tests/test_abnf.py +++ b/lib/websocket/tests/test_abnf.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- # +import unittest + import websocket as ws from websocket._abnf import * -import unittest """ test_abnf.py @@ -25,54 +26,89 @@ limitations under the License. class ABNFTest(unittest.TestCase): - def testInit(self): - a = ABNF(0,0,0,0, opcode=ABNF.OPCODE_PING) + a = ABNF(0, 0, 0, 0, opcode=ABNF.OPCODE_PING) self.assertEqual(a.fin, 0) self.assertEqual(a.rsv1, 0) self.assertEqual(a.rsv2, 0) self.assertEqual(a.rsv3, 0) self.assertEqual(a.opcode, 9) - self.assertEqual(a.data, '') - a_bad = ABNF(0,1,0,0, opcode=77) + self.assertEqual(a.data, "") + a_bad = ABNF(0, 1, 0, 0, opcode=77) self.assertEqual(a_bad.rsv1, 1) self.assertEqual(a_bad.opcode, 77) def testValidate(self): - a_invalid_ping = ABNF(0,0,0,0, opcode=ABNF.OPCODE_PING) - self.assertRaises(ws._exceptions.WebSocketProtocolException, a_invalid_ping.validate, skip_utf8_validation=False) - a_bad_rsv_value = ABNF(0,1,0,0, opcode=ABNF.OPCODE_TEXT) - self.assertRaises(ws._exceptions.WebSocketProtocolException, a_bad_rsv_value.validate, skip_utf8_validation=False) - a_bad_opcode = ABNF(0,0,0,0, opcode=77) - self.assertRaises(ws._exceptions.WebSocketProtocolException, a_bad_opcode.validate, skip_utf8_validation=False) - a_bad_close_frame = ABNF(0,0,0,0, opcode=ABNF.OPCODE_CLOSE, data=b'\x01') - self.assertRaises(ws._exceptions.WebSocketProtocolException, a_bad_close_frame.validate, skip_utf8_validation=False) - a_bad_close_frame_2 = ABNF(0,0,0,0, opcode=ABNF.OPCODE_CLOSE, data=b'\x01\x8a\xaa\xff\xdd') - self.assertRaises(ws._exceptions.WebSocketProtocolException, a_bad_close_frame_2.validate, skip_utf8_validation=False) - a_bad_close_frame_3 = ABNF(0,0,0,0, opcode=ABNF.OPCODE_CLOSE, data=b'\x03\xe7') - self.assertRaises(ws._exceptions.WebSocketProtocolException, a_bad_close_frame_3.validate, skip_utf8_validation=True) + a_invalid_ping = ABNF(0, 0, 0, 0, opcode=ABNF.OPCODE_PING) + self.assertRaises( + ws._exceptions.WebSocketProtocolException, + a_invalid_ping.validate, + skip_utf8_validation=False, + ) + a_bad_rsv_value = ABNF(0, 1, 0, 0, opcode=ABNF.OPCODE_TEXT) + self.assertRaises( + ws._exceptions.WebSocketProtocolException, + a_bad_rsv_value.validate, + skip_utf8_validation=False, + ) + a_bad_opcode = ABNF(0, 0, 0, 0, opcode=77) + self.assertRaises( + ws._exceptions.WebSocketProtocolException, + a_bad_opcode.validate, + skip_utf8_validation=False, + ) + a_bad_close_frame = ABNF(0, 0, 0, 0, opcode=ABNF.OPCODE_CLOSE, data=b"\x01") + self.assertRaises( + ws._exceptions.WebSocketProtocolException, + a_bad_close_frame.validate, + skip_utf8_validation=False, + ) + a_bad_close_frame_2 = ABNF( + 0, 0, 0, 0, opcode=ABNF.OPCODE_CLOSE, data=b"\x01\x8a\xaa\xff\xdd" + ) + self.assertRaises( + ws._exceptions.WebSocketProtocolException, + a_bad_close_frame_2.validate, + skip_utf8_validation=False, + ) + a_bad_close_frame_3 = ABNF( + 0, 0, 0, 0, opcode=ABNF.OPCODE_CLOSE, data=b"\x03\xe7" + ) + self.assertRaises( + ws._exceptions.WebSocketProtocolException, + a_bad_close_frame_3.validate, + skip_utf8_validation=True, + ) def testMask(self): - abnf_none_data = ABNF(0,0,0,0, opcode=ABNF.OPCODE_PING, mask=1, data=None) + abnf_none_data = ABNF( + 0, 0, 0, 0, opcode=ABNF.OPCODE_PING, mask_value=1, data=None + ) bytes_val = b"aaaa" self.assertEqual(abnf_none_data._get_masked(bytes_val), bytes_val) - abnf_str_data = ABNF(0,0,0,0, opcode=ABNF.OPCODE_PING, mask=1, data="a") - self.assertEqual(abnf_str_data._get_masked(bytes_val), b'aaaa\x00') + abnf_str_data = ABNF( + 0, 0, 0, 0, opcode=ABNF.OPCODE_PING, mask_value=1, data="a" + ) + self.assertEqual(abnf_str_data._get_masked(bytes_val), b"aaaa\x00") def testFormat(self): - abnf_bad_rsv_bits = ABNF(2,0,0,0, opcode=ABNF.OPCODE_TEXT) + abnf_bad_rsv_bits = ABNF(2, 0, 0, 0, opcode=ABNF.OPCODE_TEXT) self.assertRaises(ValueError, abnf_bad_rsv_bits.format) - abnf_bad_opcode = ABNF(0,0,0,0, opcode=5) + abnf_bad_opcode = ABNF(0, 0, 0, 0, opcode=5) self.assertRaises(ValueError, abnf_bad_opcode.format) - abnf_length_10 = ABNF(0,0,0,0, opcode=ABNF.OPCODE_TEXT, data="abcdefghij") - self.assertEqual(b'\x01', abnf_length_10.format()[0].to_bytes(1, 'big')) - self.assertEqual(b'\x8a', abnf_length_10.format()[1].to_bytes(1, 'big')) + abnf_length_10 = ABNF(0, 0, 0, 0, opcode=ABNF.OPCODE_TEXT, data="abcdefghij") + self.assertEqual(b"\x01", abnf_length_10.format()[0].to_bytes(1, "big")) + self.assertEqual(b"\x8a", abnf_length_10.format()[1].to_bytes(1, "big")) self.assertEqual("fin=0 opcode=1 data=abcdefghij", abnf_length_10.__str__()) - abnf_length_20 = ABNF(0,0,0,0, opcode=ABNF.OPCODE_BINARY, data="abcdefghijabcdefghij") - self.assertEqual(b'\x02', abnf_length_20.format()[0].to_bytes(1, 'big')) - self.assertEqual(b'\x94', abnf_length_20.format()[1].to_bytes(1, 'big')) - abnf_no_mask = ABNF(0,0,0,0, opcode=ABNF.OPCODE_TEXT, mask=0, data=b'\x01\x8a\xcc') - self.assertEqual(b'\x01\x03\x01\x8a\xcc', abnf_no_mask.format()) + abnf_length_20 = ABNF( + 0, 0, 0, 0, opcode=ABNF.OPCODE_BINARY, data="abcdefghijabcdefghij" + ) + self.assertEqual(b"\x02", abnf_length_20.format()[0].to_bytes(1, "big")) + self.assertEqual(b"\x94", abnf_length_20.format()[1].to_bytes(1, "big")) + abnf_no_mask = ABNF( + 0, 0, 0, 0, opcode=ABNF.OPCODE_TEXT, mask_value=0, data=b"\x01\x8a\xcc" + ) + self.assertEqual(b"\x01\x03\x01\x8a\xcc", abnf_no_mask.format()) def testFrameBuffer(self): fb = frame_buffer(0, True) @@ -81,7 +117,7 @@ class ABNFTest(unittest.TestCase): fb.clear self.assertEqual(fb.header, None) self.assertEqual(fb.length, None) - self.assertEqual(fb.mask, None) + self.assertEqual(fb.mask_value, None) self.assertEqual(fb.has_mask(), False) diff --git a/lib/websocket/tests/test_app.py b/lib/websocket/tests/test_app.py index ff90a0aa..5ed9a22e 100644 --- a/lib/websocket/tests/test_app.py +++ b/lib/websocket/tests/test_app.py @@ -2,11 +2,12 @@ # import os import os.path -import threading -import websocket as ws import ssl +import threading import unittest +import websocket as ws + """ test_app.py websocket - WebSocket client library for Python @@ -27,18 +28,16 @@ limitations under the License. """ # Skip test to access the internet unless TEST_WITH_INTERNET == 1 -TEST_WITH_INTERNET = os.environ.get('TEST_WITH_INTERNET', '0') == '1' +TEST_WITH_INTERNET = os.environ.get("TEST_WITH_INTERNET", "0") == "1" # Skip tests relying on local websockets server unless LOCAL_WS_SERVER_PORT != -1 -LOCAL_WS_SERVER_PORT = os.environ.get('LOCAL_WS_SERVER_PORT', '-1') -TEST_WITH_LOCAL_SERVER = LOCAL_WS_SERVER_PORT != '-1' +LOCAL_WS_SERVER_PORT = os.environ.get("LOCAL_WS_SERVER_PORT", "-1") +TEST_WITH_LOCAL_SERVER = LOCAL_WS_SERVER_PORT != "-1" TRACEABLE = True class WebSocketAppTest(unittest.TestCase): - class NotSetYet: - """ A marker class for signalling that a value hasn't been set yet. - """ + """A marker class for signalling that a value hasn't been set yet.""" def setUp(self): ws.enableTrace(TRACEABLE) @@ -54,14 +53,16 @@ class WebSocketAppTest(unittest.TestCase): WebSocketAppTest.get_mask_key_id = WebSocketAppTest.NotSetYet() WebSocketAppTest.on_error_data = WebSocketAppTest.NotSetYet() - @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled") + @unittest.skipUnless( + TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled" + ) def testKeepRunning(self): - """ A WebSocketApp should keep running as long as its self.keep_running + """A WebSocketApp should keep running as long as its self.keep_running is not False (in the boolean context). """ def on_open(self, *args, **kwargs): - """ Set the keep_running flag for later inspection and immediately + """Set the keep_running flag for later inspection and immediately close the connection. """ self.send("hello!") @@ -73,23 +74,26 @@ class WebSocketAppTest(unittest.TestCase): self.close() def on_close(self, *args, **kwargs): - """ Set the keep_running flag for the test to use. - """ + """Set the keep_running flag for the test to use.""" WebSocketAppTest.keep_running_close = self.keep_running - app = ws.WebSocketApp('ws://127.0.0.1:' + LOCAL_WS_SERVER_PORT, on_open=on_open, on_close=on_close, on_message=on_message) + app = ws.WebSocketApp( + f"ws://127.0.0.1:{LOCAL_WS_SERVER_PORT}", + on_open=on_open, + on_close=on_close, + on_message=on_message, + ) app.run_forever() -# @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled") + # @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled") @unittest.skipUnless(False, "Test disabled for now (requires rel)") def testRunForeverDispatcher(self): - """ A WebSocketApp should keep running as long as its self.keep_running + """A WebSocketApp should keep running as long as its self.keep_running is not False (in the boolean context). """ def on_open(self, *args, **kwargs): - """ Send a message, receive, and send one more - """ + """Send a message, receive, and send one more""" self.send("hello!") self.recv() self.send("goodbye!") @@ -98,30 +102,38 @@ class WebSocketAppTest(unittest.TestCase): print(message) self.close() - app = ws.WebSocketApp('ws://127.0.0.1:' + LOCAL_WS_SERVER_PORT, on_open=on_open, on_message=on_message) + app = ws.WebSocketApp( + f"ws://127.0.0.1:{LOCAL_WS_SERVER_PORT}", + on_open=on_open, + on_message=on_message, + ) app.run_forever(dispatcher="Dispatcher") # doesn't work -# app.run_forever(dispatcher=rel) # would work -# rel.dispatch() - @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled") + # app.run_forever(dispatcher=rel) # would work + # rel.dispatch() + + @unittest.skipUnless( + TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled" + ) def testRunForeverTeardownCleanExit(self): - """ The WebSocketApp.run_forever() method should return `False` when the application ends gracefully. - """ - app = ws.WebSocketApp('ws://127.0.0.1:' + LOCAL_WS_SERVER_PORT) + """The WebSocketApp.run_forever() method should return `False` when the application ends gracefully.""" + app = ws.WebSocketApp(f"ws://127.0.0.1:{LOCAL_WS_SERVER_PORT}") threading.Timer(interval=0.2, function=app.close).start() teardown = app.run_forever() self.assertEqual(teardown, False) @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") def testSockMaskKey(self): - """ A WebSocketApp should forward the received mask_key function down + """A WebSocketApp should forward the received mask_key function down to the actual socket. """ def my_mask_key_func(): return "\x00\x00\x00\x00" - app = ws.WebSocketApp('wss://api-pub.bitfinex.com/ws/1', get_mask_key=my_mask_key_func) + app = ws.WebSocketApp( + "wss://api-pub.bitfinex.com/ws/1", get_mask_key=my_mask_key_func + ) # if numpy is installed, this assertion fail # Note: We can't use 'is' for comparing the functions directly, need to use 'id'. @@ -129,8 +141,7 @@ class WebSocketAppTest(unittest.TestCase): @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") def testInvalidPingIntervalPingTimeout(self): - """ Test exception handling if ping_interval < ping_timeout - """ + """Test exception handling if ping_interval < ping_timeout""" def on_ping(app, msg): print("Got a ping!") @@ -140,13 +151,20 @@ class WebSocketAppTest(unittest.TestCase): print("Got a pong! No need to respond") app.close() - app = ws.WebSocketApp('wss://api-pub.bitfinex.com/ws/1', on_ping=on_ping, on_pong=on_pong) - self.assertRaises(ws.WebSocketException, app.run_forever, ping_interval=1, ping_timeout=2, sslopt={"cert_reqs": ssl.CERT_NONE}) + app = ws.WebSocketApp( + "wss://api-pub.bitfinex.com/ws/1", on_ping=on_ping, on_pong=on_pong + ) + self.assertRaises( + ws.WebSocketException, + app.run_forever, + ping_interval=1, + ping_timeout=2, + sslopt={"cert_reqs": ssl.CERT_NONE}, + ) @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") def testPingInterval(self): - """ Test WebSocketApp proper ping functionality - """ + """Test WebSocketApp proper ping functionality""" def on_ping(app, msg): print("Got a ping!") @@ -156,15 +174,18 @@ class WebSocketAppTest(unittest.TestCase): print("Got a pong! No need to respond") app.close() - app = ws.WebSocketApp('wss://api-pub.bitfinex.com/ws/1', on_ping=on_ping, on_pong=on_pong) - app.run_forever(ping_interval=2, ping_timeout=1, sslopt={"cert_reqs": ssl.CERT_NONE}) + app = ws.WebSocketApp( + "wss://api-pub.bitfinex.com/ws/1", on_ping=on_ping, on_pong=on_pong + ) + app.run_forever( + ping_interval=2, ping_timeout=1, sslopt={"cert_reqs": ssl.CERT_NONE} + ) @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") def testOpcodeClose(self): - """ Test WebSocketApp close opcode - """ + """Test WebSocketApp close opcode""" - app = ws.WebSocketApp('wss://tsock.us1.twilio.com/v3/wsconnect') + app = ws.WebSocketApp("wss://tsock.us1.twilio.com/v3/wsconnect") app.run_forever(ping_interval=2, ping_timeout=1, ping_payload="Ping payload") # This is commented out because the URL no longer responds in the expected way @@ -177,41 +198,59 @@ class WebSocketAppTest(unittest.TestCase): @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") def testBadPingInterval(self): - """ A WebSocketApp handling of negative ping_interval - """ - app = ws.WebSocketApp('wss://api-pub.bitfinex.com/ws/1') - self.assertRaises(ws.WebSocketException, app.run_forever, ping_interval=-5, sslopt={"cert_reqs": ssl.CERT_NONE}) + """A WebSocketApp handling of negative ping_interval""" + app = ws.WebSocketApp("wss://api-pub.bitfinex.com/ws/1") + self.assertRaises( + ws.WebSocketException, + app.run_forever, + ping_interval=-5, + sslopt={"cert_reqs": ssl.CERT_NONE}, + ) @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") def testBadPingTimeout(self): - """ A WebSocketApp handling of negative ping_timeout - """ - app = ws.WebSocketApp('wss://api-pub.bitfinex.com/ws/1') - self.assertRaises(ws.WebSocketException, app.run_forever, ping_timeout=-3, sslopt={"cert_reqs": ssl.CERT_NONE}) + """A WebSocketApp handling of negative ping_timeout""" + app = ws.WebSocketApp("wss://api-pub.bitfinex.com/ws/1") + self.assertRaises( + ws.WebSocketException, + app.run_forever, + ping_timeout=-3, + sslopt={"cert_reqs": ssl.CERT_NONE}, + ) @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") def testCloseStatusCode(self): - """ Test extraction of close frame status code and close reason in WebSocketApp - """ + """Test extraction of close frame status code and close reason in WebSocketApp""" + def on_close(wsapp, close_status_code, close_msg): print("on_close reached") - app = ws.WebSocketApp('wss://tsock.us1.twilio.com/v3/wsconnect', on_close=on_close) - closeframe = ws.ABNF(opcode=ws.ABNF.OPCODE_CLOSE, data=b'\x03\xe8no-init-from-client') - self.assertEqual([1000, 'no-init-from-client'], app._get_close_args(closeframe)) + app = ws.WebSocketApp( + "wss://tsock.us1.twilio.com/v3/wsconnect", on_close=on_close + ) + closeframe = ws.ABNF( + opcode=ws.ABNF.OPCODE_CLOSE, data=b"\x03\xe8no-init-from-client" + ) + self.assertEqual([1000, "no-init-from-client"], app._get_close_args(closeframe)) - closeframe = ws.ABNF(opcode=ws.ABNF.OPCODE_CLOSE, data=b'') + closeframe = ws.ABNF(opcode=ws.ABNF.OPCODE_CLOSE, data=b"") self.assertEqual([None, None], app._get_close_args(closeframe)) - app2 = ws.WebSocketApp('wss://tsock.us1.twilio.com/v3/wsconnect') - closeframe = ws.ABNF(opcode=ws.ABNF.OPCODE_CLOSE, data=b'') + app2 = ws.WebSocketApp("wss://tsock.us1.twilio.com/v3/wsconnect") + closeframe = ws.ABNF(opcode=ws.ABNF.OPCODE_CLOSE, data=b"") self.assertEqual([None, None], app2._get_close_args(closeframe)) - self.assertRaises(ws.WebSocketConnectionClosedException, app.send, data="test if connection is closed") + self.assertRaises( + ws.WebSocketConnectionClosedException, + app.send, + data="test if connection is closed", + ) - @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled") + @unittest.skipUnless( + TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled" + ) def testCallbackFunctionException(self): - """ Test callback function exception handling """ + """Test callback function exception handling""" exc = None passed_app = None @@ -228,26 +267,33 @@ class WebSocketAppTest(unittest.TestCase): def on_pong(app, msg): app.close() - app = ws.WebSocketApp('ws://127.0.0.1:' + LOCAL_WS_SERVER_PORT, on_open=on_open, on_error=on_error, on_pong=on_pong) + app = ws.WebSocketApp( + f"ws://127.0.0.1:{LOCAL_WS_SERVER_PORT}", + on_open=on_open, + on_error=on_error, + on_pong=on_pong, + ) app.run_forever(ping_interval=2, ping_timeout=1) self.assertEqual(passed_app, app) self.assertIsInstance(exc, RuntimeError) self.assertEqual(str(exc), "Callback failed") - @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled") + @unittest.skipUnless( + TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled" + ) def testCallbackMethodException(self): - """ Test callback method exception handling """ + """Test callback method exception handling""" class Callbacks: def __init__(self): self.exc = None self.passed_app = None self.app = ws.WebSocketApp( - 'ws://127.0.0.1:' + LOCAL_WS_SERVER_PORT, + f"ws://127.0.0.1:{LOCAL_WS_SERVER_PORT}", on_open=self.on_open, on_error=self.on_error, - on_pong=self.on_pong + on_pong=self.on_pong, ) self.app.run_forever(ping_interval=2, ping_timeout=1) @@ -267,9 +313,11 @@ class WebSocketAppTest(unittest.TestCase): self.assertIsInstance(callbacks.exc, RuntimeError) self.assertEqual(str(callbacks.exc), "Callback failed") - @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled") + @unittest.skipUnless( + TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled" + ) def testReconnect(self): - """ Test reconnect """ + """Test reconnect""" pong_count = 0 exc = None @@ -287,7 +335,9 @@ class WebSocketAppTest(unittest.TestCase): # Got second pong after reconnect app.close() - app = ws.WebSocketApp('ws://127.0.0.1:' + LOCAL_WS_SERVER_PORT, on_pong=on_pong, on_error=on_error) + app = ws.WebSocketApp( + f"ws://127.0.0.1:{LOCAL_WS_SERVER_PORT}", on_pong=on_pong, on_error=on_error + ) app.run_forever(ping_interval=2, ping_timeout=1, reconnect=3) self.assertEqual(pong_count, 2) diff --git a/lib/websocket/tests/test_cookiejar.py b/lib/websocket/tests/test_cookiejar.py index 8f835e9e..0de87517 100644 --- a/lib/websocket/tests/test_cookiejar.py +++ b/lib/websocket/tests/test_cookiejar.py @@ -1,4 +1,5 @@ import unittest + from websocket._cookiejar import SimpleCookieJar """ @@ -25,11 +26,15 @@ class CookieJarTest(unittest.TestCase): def testAdd(self): cookie_jar = SimpleCookieJar() cookie_jar.add("") - self.assertFalse(cookie_jar.jar, "Cookie with no domain should not be added to the jar") + self.assertFalse( + cookie_jar.jar, "Cookie with no domain should not be added to the jar" + ) cookie_jar = SimpleCookieJar() cookie_jar.add("a=b") - self.assertFalse(cookie_jar.jar, "Cookie with no domain should not be added to the jar") + self.assertFalse( + cookie_jar.jar, "Cookie with no domain should not be added to the jar" + ) cookie_jar = SimpleCookieJar() cookie_jar.add("a=b; domain=.abc") @@ -65,7 +70,9 @@ class CookieJarTest(unittest.TestCase): def testSet(self): cookie_jar = SimpleCookieJar() cookie_jar.set("a=b") - self.assertFalse(cookie_jar.jar, "Cookie with no domain should not be added to the jar") + self.assertFalse( + cookie_jar.jar, "Cookie with no domain should not be added to the jar" + ) cookie_jar = SimpleCookieJar() cookie_jar.set("a=b; domain=.abc") diff --git a/lib/websocket/tests/test_http.py b/lib/websocket/tests/test_http.py index d7de29d3..0d6031a3 100644 --- a/lib/websocket/tests/test_http.py +++ b/lib/websocket/tests/test_http.py @@ -2,12 +2,20 @@ # import os import os.path -import websocket as ws -from websocket._http import proxy_info, read_headers, _start_proxied_socket, _tunnel, _get_addrinfo_list, connect -import unittest -import ssl -import websocket import socket +import ssl +import unittest + +import websocket +import websocket as ws +from websocket._http import ( + _get_addrinfo_list, + _start_proxied_socket, + _tunnel, + connect, + proxy_info, + read_headers, +) """ test_http.py @@ -29,16 +37,16 @@ limitations under the License. """ try: - from python_socks._errors import ProxyError, ProxyTimeoutError, ProxyConnectionError + from python_socks._errors import ProxyConnectionError, ProxyError, ProxyTimeoutError except: - from websocket._http import ProxyError, ProxyTimeoutError, ProxyConnectionError + from websocket._http import ProxyConnectionError, ProxyError, ProxyTimeoutError # Skip test to access the internet unless TEST_WITH_INTERNET == 1 -TEST_WITH_INTERNET = os.environ.get('TEST_WITH_INTERNET', '0') == '1' -TEST_WITH_PROXY = os.environ.get('TEST_WITH_PROXY', '0') == '1' +TEST_WITH_INTERNET = os.environ.get("TEST_WITH_INTERNET", "0") == "1" +TEST_WITH_PROXY = os.environ.get("TEST_WITH_PROXY", "0") == "1" # Skip tests relying on local websockets server unless LOCAL_WS_SERVER_PORT != -1 -LOCAL_WS_SERVER_PORT = os.environ.get('LOCAL_WS_SERVER_PORT', '-1') -TEST_WITH_LOCAL_SERVER = LOCAL_WS_SERVER_PORT != '-1' +LOCAL_WS_SERVER_PORT = os.environ.get("LOCAL_WS_SERVER_PORT", "-1") +TEST_WITH_LOCAL_SERVER = LOCAL_WS_SERVER_PORT != "-1" class SockMock: @@ -70,7 +78,6 @@ class SockMock: class HeaderSockMock(SockMock): - def __init__(self, fname): SockMock.__init__(self) path = os.path.join(os.path.dirname(__file__), fname) @@ -78,8 +85,7 @@ class HeaderSockMock(SockMock): self.add_packet(f.read()) -class OptsList(): - +class OptsList: def __init__(self): self.timeout = 1 self.sockopt = [] @@ -87,17 +93,34 @@ class OptsList(): class HttpTest(unittest.TestCase): - def testReadHeader(self): - status, header, status_message = read_headers(HeaderSockMock("data/header01.txt")) + status, header, status_message = read_headers( + HeaderSockMock("data/header01.txt") + ) self.assertEqual(status, 101) self.assertEqual(header["connection"], "Upgrade") # header02.txt is intentionally malformed - self.assertRaises(ws.WebSocketException, read_headers, HeaderSockMock("data/header02.txt")) + self.assertRaises( + ws.WebSocketException, read_headers, HeaderSockMock("data/header02.txt") + ) def testTunnel(self): - self.assertRaises(ws.WebSocketProxyException, _tunnel, HeaderSockMock("data/header01.txt"), "example.com", 80, ("username", "password")) - self.assertRaises(ws.WebSocketProxyException, _tunnel, HeaderSockMock("data/header02.txt"), "example.com", 80, ("username", "password")) + self.assertRaises( + ws.WebSocketProxyException, + _tunnel, + HeaderSockMock("data/header01.txt"), + "example.com", + 80, + ("username", "password"), + ) + self.assertRaises( + ws.WebSocketProxyException, + _tunnel, + HeaderSockMock("data/header02.txt"), + "example.com", + 80, + ("username", "password"), + ) @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") def testConnect(self): @@ -105,34 +128,164 @@ class HttpTest(unittest.TestCase): if ws._http.HAVE_PYTHON_SOCKS: # Need this check, otherwise case where python_socks is not installed triggers # websocket._exceptions.WebSocketException: Python Socks is needed for SOCKS proxying but is not available - self.assertRaises((ProxyTimeoutError, OSError), _start_proxied_socket, "wss://example.com", OptsList(), proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="socks4", http_proxy_timeout=1)) - self.assertRaises((ProxyTimeoutError, OSError), _start_proxied_socket, "wss://example.com", OptsList(), proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="socks4a", http_proxy_timeout=1)) - self.assertRaises((ProxyTimeoutError, OSError), _start_proxied_socket, "wss://example.com", OptsList(), proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="socks5", http_proxy_timeout=1)) - self.assertRaises((ProxyTimeoutError, OSError), _start_proxied_socket, "wss://example.com", OptsList(), proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="socks5h", http_proxy_timeout=1)) - self.assertRaises(ProxyConnectionError, connect, "wss://example.com", OptsList(), proxy_info(http_proxy_host="127.0.0.1", http_proxy_port=9999, proxy_type="socks4", http_proxy_timeout=1), None) + self.assertRaises( + (ProxyTimeoutError, OSError), + _start_proxied_socket, + "wss://example.com", + OptsList(), + proxy_info( + http_proxy_host="example.com", + http_proxy_port="8080", + proxy_type="socks4", + http_proxy_timeout=1, + ), + ) + self.assertRaises( + (ProxyTimeoutError, OSError), + _start_proxied_socket, + "wss://example.com", + OptsList(), + proxy_info( + http_proxy_host="example.com", + http_proxy_port="8080", + proxy_type="socks4a", + http_proxy_timeout=1, + ), + ) + self.assertRaises( + (ProxyTimeoutError, OSError), + _start_proxied_socket, + "wss://example.com", + OptsList(), + proxy_info( + http_proxy_host="example.com", + http_proxy_port="8080", + proxy_type="socks5", + http_proxy_timeout=1, + ), + ) + self.assertRaises( + (ProxyTimeoutError, OSError), + _start_proxied_socket, + "wss://example.com", + OptsList(), + proxy_info( + http_proxy_host="example.com", + http_proxy_port="8080", + proxy_type="socks5h", + http_proxy_timeout=1, + ), + ) + self.assertRaises( + ProxyConnectionError, + connect, + "wss://example.com", + OptsList(), + proxy_info( + http_proxy_host="127.0.0.1", + http_proxy_port=9999, + proxy_type="socks4", + http_proxy_timeout=1, + ), + None, + ) - self.assertRaises(TypeError, _get_addrinfo_list, None, 80, True, proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="9999", proxy_type="http")) - self.assertRaises(TypeError, _get_addrinfo_list, None, 80, True, proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="9999", proxy_type="http")) - self.assertRaises(socket.timeout, connect, "wss://google.com", OptsList(), proxy_info(http_proxy_host="8.8.8.8", http_proxy_port=9999, proxy_type="http", http_proxy_timeout=1), None) + self.assertRaises( + TypeError, + _get_addrinfo_list, + None, + 80, + True, + proxy_info( + http_proxy_host="127.0.0.1", http_proxy_port="9999", proxy_type="http" + ), + ) + self.assertRaises( + TypeError, + _get_addrinfo_list, + None, + 80, + True, + proxy_info( + http_proxy_host="127.0.0.1", http_proxy_port="9999", proxy_type="http" + ), + ) + self.assertRaises( + socket.timeout, + connect, + "wss://google.com", + OptsList(), + proxy_info( + http_proxy_host="8.8.8.8", + http_proxy_port=9999, + proxy_type="http", + http_proxy_timeout=1, + ), + None, + ) self.assertEqual( - connect("wss://google.com", OptsList(), proxy_info(http_proxy_host="8.8.8.8", http_proxy_port=8080, proxy_type="http"), True), - (True, ("google.com", 443, "/"))) + connect( + "wss://google.com", + OptsList(), + proxy_info( + http_proxy_host="8.8.8.8", http_proxy_port=8080, proxy_type="http" + ), + True, + ), + (True, ("google.com", 443, "/")), + ) # The following test fails on Mac OS with a gaierror, not an OverflowError # self.assertRaises(OverflowError, connect, "wss://example.com", OptsList(), proxy_info(http_proxy_host="127.0.0.1", http_proxy_port=99999, proxy_type="socks4", timeout=2), False) @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") - @unittest.skipUnless(TEST_WITH_PROXY, "This test requires a HTTP proxy to be running on port 8899") - @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled") + @unittest.skipUnless( + TEST_WITH_PROXY, "This test requires a HTTP proxy to be running on port 8899" + ) + @unittest.skipUnless( + TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled" + ) def testProxyConnect(self): ws = websocket.WebSocket() - ws.connect("ws://127.0.0.1:" + LOCAL_WS_SERVER_PORT, http_proxy_host="127.0.0.1", http_proxy_port="8899", proxy_type="http") + ws.connect( + f"ws://127.0.0.1:{LOCAL_WS_SERVER_PORT}", + http_proxy_host="127.0.0.1", + http_proxy_port="8899", + proxy_type="http", + ) ws.send("Hello, Server") server_response = ws.recv() self.assertEqual(server_response, "Hello, Server") # self.assertEqual(_start_proxied_socket("wss://api.bitfinex.com/ws/2", OptsList(), proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8899", proxy_type="http"))[1], ("api.bitfinex.com", 443, '/ws/2')) - self.assertEqual(_get_addrinfo_list("api.bitfinex.com", 443, True, proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8899", proxy_type="http")), - (socket.getaddrinfo("127.0.0.1", 8899, 0, socket.SOCK_STREAM, socket.SOL_TCP), True, None)) - self.assertEqual(connect("wss://api.bitfinex.com/ws/2", OptsList(), proxy_info(http_proxy_host="127.0.0.1", http_proxy_port=8899, proxy_type="http"), None)[1], ("api.bitfinex.com", 443, '/ws/2')) + self.assertEqual( + _get_addrinfo_list( + "api.bitfinex.com", + 443, + True, + proxy_info( + http_proxy_host="127.0.0.1", + http_proxy_port="8899", + proxy_type="http", + ), + ), + ( + socket.getaddrinfo( + "127.0.0.1", 8899, 0, socket.SOCK_STREAM, socket.SOL_TCP + ), + True, + None, + ), + ) + self.assertEqual( + connect( + "wss://api.bitfinex.com/ws/2", + OptsList(), + proxy_info( + http_proxy_host="127.0.0.1", http_proxy_port=8899, proxy_type="http" + ), + None, + )[1], + ("api.bitfinex.com", 443, "/ws/2"), + ) # TODO: Test SOCKS4 and SOCK5 proxies with unit tests @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") @@ -151,7 +304,7 @@ class HttpTest(unittest.TestCase): DHE-RSA-AES256-SHA256:ECDHE-ECDSA-AES128-SHA256:\ ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:\ ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA", - "ecdh_curve": "prime256v1" + "ecdh_curve": "prime256v1", } ws_ssl1 = websocket.WebSocket(sslopt=ssloptions) ws_ssl1.connect("wss://api.bitfinex.com/ws/2") @@ -163,13 +316,55 @@ class HttpTest(unittest.TestCase): ws_ssl2.close def testProxyInfo(self): - self.assertEqual(proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http").proxy_protocol, "http") - self.assertRaises(ProxyError, proxy_info, http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="badval") - self.assertEqual(proxy_info(http_proxy_host="example.com", http_proxy_port="8080", proxy_type="http").proxy_host, "example.com") - self.assertEqual(proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http").proxy_port, "8080") - self.assertEqual(proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http").auth, None) - self.assertEqual(proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http", http_proxy_auth=("my_username123", "my_pass321")).auth[0], "my_username123") - self.assertEqual(proxy_info(http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http", http_proxy_auth=("my_username123", "my_pass321")).auth[1], "my_pass321") + self.assertEqual( + proxy_info( + http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http" + ).proxy_protocol, + "http", + ) + self.assertRaises( + ProxyError, + proxy_info, + http_proxy_host="127.0.0.1", + http_proxy_port="8080", + proxy_type="badval", + ) + self.assertEqual( + proxy_info( + http_proxy_host="example.com", http_proxy_port="8080", proxy_type="http" + ).proxy_host, + "example.com", + ) + self.assertEqual( + proxy_info( + http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http" + ).proxy_port, + "8080", + ) + self.assertEqual( + proxy_info( + http_proxy_host="127.0.0.1", http_proxy_port="8080", proxy_type="http" + ).auth, + None, + ) + self.assertEqual( + proxy_info( + http_proxy_host="127.0.0.1", + http_proxy_port="8080", + proxy_type="http", + http_proxy_auth=("my_username123", "my_pass321"), + ).auth[0], + "my_username123", + ) + self.assertEqual( + proxy_info( + http_proxy_host="127.0.0.1", + http_proxy_port="8080", + proxy_type="http", + http_proxy_auth=("my_username123", "my_pass321"), + ).auth[1], + "my_pass321", + ) if __name__ == "__main__": diff --git a/lib/websocket/tests/test_url.py b/lib/websocket/tests/test_url.py index a74dd766..792399eb 100644 --- a/lib/websocket/tests/test_url.py +++ b/lib/websocket/tests/test_url.py @@ -2,7 +2,13 @@ # import os import unittest -from websocket._url import get_proxy_info, parse_url, _is_address_in_network, _is_no_proxy_host + +from websocket._url import ( + _is_address_in_network, + _is_no_proxy_host, + get_proxy_info, + parse_url, +) """ test_url.py @@ -25,11 +31,10 @@ limitations under the License. class UrlTest(unittest.TestCase): - def test_address_in_network(self): - self.assertTrue(_is_address_in_network('127.0.0.1', '127.0.0.0/8')) - self.assertTrue(_is_address_in_network('127.1.0.1', '127.0.0.0/8')) - self.assertFalse(_is_address_in_network('127.1.0.1', '127.0.0.0/24')) + self.assertTrue(_is_address_in_network("127.0.0.1", "127.0.0.0/8")) + self.assertTrue(_is_address_in_network("127.1.0.1", "127.0.0.0/8")) + self.assertFalse(_is_address_in_network("127.1.0.1", "127.0.0.0/24")) def testParseUrl(self): p = parse_url("ws://www.example.com/r") @@ -126,57 +131,71 @@ class IsNoProxyHostTest(unittest.TestCase): del os.environ["no_proxy"] def testMatchAll(self): - self.assertTrue(_is_no_proxy_host("any.websocket.org", ['*'])) - self.assertTrue(_is_no_proxy_host("192.168.0.1", ['*'])) - self.assertTrue(_is_no_proxy_host("any.websocket.org", ['other.websocket.org', '*'])) - os.environ['no_proxy'] = '*' + self.assertTrue(_is_no_proxy_host("any.websocket.org", ["*"])) + self.assertTrue(_is_no_proxy_host("192.168.0.1", ["*"])) + self.assertTrue( + _is_no_proxy_host("any.websocket.org", ["other.websocket.org", "*"]) + ) + os.environ["no_proxy"] = "*" self.assertTrue(_is_no_proxy_host("any.websocket.org", None)) self.assertTrue(_is_no_proxy_host("192.168.0.1", None)) - os.environ['no_proxy'] = 'other.websocket.org, *' + os.environ["no_proxy"] = "other.websocket.org, *" self.assertTrue(_is_no_proxy_host("any.websocket.org", None)) def testIpAddress(self): - self.assertTrue(_is_no_proxy_host("127.0.0.1", ['127.0.0.1'])) - self.assertFalse(_is_no_proxy_host("127.0.0.2", ['127.0.0.1'])) - self.assertTrue(_is_no_proxy_host("127.0.0.1", ['other.websocket.org', '127.0.0.1'])) - self.assertFalse(_is_no_proxy_host("127.0.0.2", ['other.websocket.org', '127.0.0.1'])) - os.environ['no_proxy'] = '127.0.0.1' + self.assertTrue(_is_no_proxy_host("127.0.0.1", ["127.0.0.1"])) + self.assertFalse(_is_no_proxy_host("127.0.0.2", ["127.0.0.1"])) + self.assertTrue( + _is_no_proxy_host("127.0.0.1", ["other.websocket.org", "127.0.0.1"]) + ) + self.assertFalse( + _is_no_proxy_host("127.0.0.2", ["other.websocket.org", "127.0.0.1"]) + ) + os.environ["no_proxy"] = "127.0.0.1" self.assertTrue(_is_no_proxy_host("127.0.0.1", None)) self.assertFalse(_is_no_proxy_host("127.0.0.2", None)) - os.environ['no_proxy'] = 'other.websocket.org, 127.0.0.1' + os.environ["no_proxy"] = "other.websocket.org, 127.0.0.1" self.assertTrue(_is_no_proxy_host("127.0.0.1", None)) self.assertFalse(_is_no_proxy_host("127.0.0.2", None)) def testIpAddressInRange(self): - self.assertTrue(_is_no_proxy_host("127.0.0.1", ['127.0.0.0/8'])) - self.assertTrue(_is_no_proxy_host("127.0.0.2", ['127.0.0.0/8'])) - self.assertFalse(_is_no_proxy_host("127.1.0.1", ['127.0.0.0/24'])) - os.environ['no_proxy'] = '127.0.0.0/8' + self.assertTrue(_is_no_proxy_host("127.0.0.1", ["127.0.0.0/8"])) + self.assertTrue(_is_no_proxy_host("127.0.0.2", ["127.0.0.0/8"])) + self.assertFalse(_is_no_proxy_host("127.1.0.1", ["127.0.0.0/24"])) + os.environ["no_proxy"] = "127.0.0.0/8" self.assertTrue(_is_no_proxy_host("127.0.0.1", None)) self.assertTrue(_is_no_proxy_host("127.0.0.2", None)) - os.environ['no_proxy'] = '127.0.0.0/24' + os.environ["no_proxy"] = "127.0.0.0/24" self.assertFalse(_is_no_proxy_host("127.1.0.1", None)) def testHostnameMatch(self): - self.assertTrue(_is_no_proxy_host("my.websocket.org", ['my.websocket.org'])) - self.assertTrue(_is_no_proxy_host("my.websocket.org", ['other.websocket.org', 'my.websocket.org'])) - self.assertFalse(_is_no_proxy_host("my.websocket.org", ['other.websocket.org'])) - os.environ['no_proxy'] = 'my.websocket.org' + self.assertTrue(_is_no_proxy_host("my.websocket.org", ["my.websocket.org"])) + self.assertTrue( + _is_no_proxy_host( + "my.websocket.org", ["other.websocket.org", "my.websocket.org"] + ) + ) + self.assertFalse(_is_no_proxy_host("my.websocket.org", ["other.websocket.org"])) + os.environ["no_proxy"] = "my.websocket.org" self.assertTrue(_is_no_proxy_host("my.websocket.org", None)) self.assertFalse(_is_no_proxy_host("other.websocket.org", None)) - os.environ['no_proxy'] = 'other.websocket.org, my.websocket.org' + os.environ["no_proxy"] = "other.websocket.org, my.websocket.org" self.assertTrue(_is_no_proxy_host("my.websocket.org", None)) def testHostnameMatchDomain(self): - self.assertTrue(_is_no_proxy_host("any.websocket.org", ['.websocket.org'])) - self.assertTrue(_is_no_proxy_host("my.other.websocket.org", ['.websocket.org'])) - self.assertTrue(_is_no_proxy_host("any.websocket.org", ['my.websocket.org', '.websocket.org'])) - self.assertFalse(_is_no_proxy_host("any.websocket.com", ['.websocket.org'])) - os.environ['no_proxy'] = '.websocket.org' + self.assertTrue(_is_no_proxy_host("any.websocket.org", [".websocket.org"])) + self.assertTrue(_is_no_proxy_host("my.other.websocket.org", [".websocket.org"])) + self.assertTrue( + _is_no_proxy_host( + "any.websocket.org", ["my.websocket.org", ".websocket.org"] + ) + ) + self.assertFalse(_is_no_proxy_host("any.websocket.com", [".websocket.org"])) + os.environ["no_proxy"] = ".websocket.org" self.assertTrue(_is_no_proxy_host("any.websocket.org", None)) self.assertTrue(_is_no_proxy_host("my.other.websocket.org", None)) self.assertFalse(_is_no_proxy_host("any.websocket.com", None)) - os.environ['no_proxy'] = 'my.websocket.org, .websocket.org' + os.environ["no_proxy"] = "my.websocket.org, .websocket.org" self.assertTrue(_is_no_proxy_host("any.websocket.org", None)) @@ -209,96 +228,205 @@ class ProxyInfoTest(unittest.TestCase): del os.environ["no_proxy"] def testProxyFromArgs(self): - self.assertEqual(get_proxy_info("echo.websocket.events", False, proxy_host="localhost"), ("localhost", 0, None)) - self.assertEqual(get_proxy_info("echo.websocket.events", False, proxy_host="localhost", proxy_port=3128), - ("localhost", 3128, None)) - self.assertEqual(get_proxy_info("echo.websocket.events", True, proxy_host="localhost"), ("localhost", 0, None)) - self.assertEqual(get_proxy_info("echo.websocket.events", True, proxy_host="localhost", proxy_port=3128), - ("localhost", 3128, None)) - - self.assertEqual(get_proxy_info("echo.websocket.events", False, proxy_host="localhost", proxy_auth=("a", "b")), - ("localhost", 0, ("a", "b"))) self.assertEqual( - get_proxy_info("echo.websocket.events", False, proxy_host="localhost", proxy_port=3128, proxy_auth=("a", "b")), - ("localhost", 3128, ("a", "b"))) - self.assertEqual(get_proxy_info("echo.websocket.events", True, proxy_host="localhost", proxy_auth=("a", "b")), - ("localhost", 0, ("a", "b"))) + get_proxy_info("echo.websocket.events", False, proxy_host="localhost"), + ("localhost", 0, None), + ) self.assertEqual( - get_proxy_info("echo.websocket.events", True, proxy_host="localhost", proxy_port=3128, proxy_auth=("a", "b")), - ("localhost", 3128, ("a", "b"))) + get_proxy_info( + "echo.websocket.events", False, proxy_host="localhost", proxy_port=3128 + ), + ("localhost", 3128, None), + ) + self.assertEqual( + get_proxy_info("echo.websocket.events", True, proxy_host="localhost"), + ("localhost", 0, None), + ) + self.assertEqual( + get_proxy_info( + "echo.websocket.events", True, proxy_host="localhost", proxy_port=3128 + ), + ("localhost", 3128, None), + ) - self.assertEqual(get_proxy_info("echo.websocket.events", True, proxy_host="localhost", proxy_port=3128, - no_proxy=["example.com"], proxy_auth=("a", "b")), - ("localhost", 3128, ("a", "b"))) - self.assertEqual(get_proxy_info("echo.websocket.events", True, proxy_host="localhost", proxy_port=3128, - no_proxy=["echo.websocket.events"], proxy_auth=("a", "b")), - (None, 0, None)) + self.assertEqual( + get_proxy_info( + "echo.websocket.events", + False, + proxy_host="localhost", + proxy_auth=("a", "b"), + ), + ("localhost", 0, ("a", "b")), + ) + self.assertEqual( + get_proxy_info( + "echo.websocket.events", + False, + proxy_host="localhost", + proxy_port=3128, + proxy_auth=("a", "b"), + ), + ("localhost", 3128, ("a", "b")), + ) + self.assertEqual( + get_proxy_info( + "echo.websocket.events", + True, + proxy_host="localhost", + proxy_auth=("a", "b"), + ), + ("localhost", 0, ("a", "b")), + ) + self.assertEqual( + get_proxy_info( + "echo.websocket.events", + True, + proxy_host="localhost", + proxy_port=3128, + proxy_auth=("a", "b"), + ), + ("localhost", 3128, ("a", "b")), + ) + + self.assertEqual( + get_proxy_info( + "echo.websocket.events", + True, + proxy_host="localhost", + proxy_port=3128, + no_proxy=["example.com"], + proxy_auth=("a", "b"), + ), + ("localhost", 3128, ("a", "b")), + ) + self.assertEqual( + get_proxy_info( + "echo.websocket.events", + True, + proxy_host="localhost", + proxy_port=3128, + no_proxy=["echo.websocket.events"], + proxy_auth=("a", "b"), + ), + (None, 0, None), + ) def testProxyFromEnv(self): os.environ["http_proxy"] = "http://localhost/" - self.assertEqual(get_proxy_info("echo.websocket.events", False), ("localhost", None, None)) + self.assertEqual( + get_proxy_info("echo.websocket.events", False), ("localhost", None, None) + ) os.environ["http_proxy"] = "http://localhost:3128/" - self.assertEqual(get_proxy_info("echo.websocket.events", False), ("localhost", 3128, None)) + self.assertEqual( + get_proxy_info("echo.websocket.events", False), ("localhost", 3128, None) + ) os.environ["http_proxy"] = "http://localhost/" os.environ["https_proxy"] = "http://localhost2/" - self.assertEqual(get_proxy_info("echo.websocket.events", False), ("localhost", None, None)) + self.assertEqual( + get_proxy_info("echo.websocket.events", False), ("localhost", None, None) + ) os.environ["http_proxy"] = "http://localhost:3128/" os.environ["https_proxy"] = "http://localhost2:3128/" - self.assertEqual(get_proxy_info("echo.websocket.events", False), ("localhost", 3128, None)) + self.assertEqual( + get_proxy_info("echo.websocket.events", False), ("localhost", 3128, None) + ) os.environ["http_proxy"] = "http://localhost/" os.environ["https_proxy"] = "http://localhost2/" - self.assertEqual(get_proxy_info("echo.websocket.events", True), ("localhost2", None, None)) + self.assertEqual( + get_proxy_info("echo.websocket.events", True), ("localhost2", None, None) + ) os.environ["http_proxy"] = "http://localhost:3128/" os.environ["https_proxy"] = "http://localhost2:3128/" - self.assertEqual(get_proxy_info("echo.websocket.events", True), ("localhost2", 3128, None)) + self.assertEqual( + get_proxy_info("echo.websocket.events", True), ("localhost2", 3128, None) + ) os.environ["http_proxy"] = "" os.environ["https_proxy"] = "http://localhost2/" - self.assertEqual(get_proxy_info("echo.websocket.events", True), ("localhost2", None, None)) - self.assertEqual(get_proxy_info("echo.websocket.events", False), (None, 0, None)) + self.assertEqual( + get_proxy_info("echo.websocket.events", True), ("localhost2", None, None) + ) + self.assertEqual( + get_proxy_info("echo.websocket.events", False), (None, 0, None) + ) os.environ["http_proxy"] = "" os.environ["https_proxy"] = "http://localhost2:3128/" - self.assertEqual(get_proxy_info("echo.websocket.events", True), ("localhost2", 3128, None)) - self.assertEqual(get_proxy_info("echo.websocket.events", False), (None, 0, None)) + self.assertEqual( + get_proxy_info("echo.websocket.events", True), ("localhost2", 3128, None) + ) + self.assertEqual( + get_proxy_info("echo.websocket.events", False), (None, 0, None) + ) os.environ["http_proxy"] = "http://localhost/" os.environ["https_proxy"] = "" self.assertEqual(get_proxy_info("echo.websocket.events", True), (None, 0, None)) - self.assertEqual(get_proxy_info("echo.websocket.events", False), ("localhost", None, None)) + self.assertEqual( + get_proxy_info("echo.websocket.events", False), ("localhost", None, None) + ) os.environ["http_proxy"] = "http://localhost:3128/" os.environ["https_proxy"] = "" self.assertEqual(get_proxy_info("echo.websocket.events", True), (None, 0, None)) - self.assertEqual(get_proxy_info("echo.websocket.events", False), ("localhost", 3128, None)) + self.assertEqual( + get_proxy_info("echo.websocket.events", False), ("localhost", 3128, None) + ) os.environ["http_proxy"] = "http://a:b@localhost/" - self.assertEqual(get_proxy_info("echo.websocket.events", False), ("localhost", None, ("a", "b"))) + self.assertEqual( + get_proxy_info("echo.websocket.events", False), + ("localhost", None, ("a", "b")), + ) os.environ["http_proxy"] = "http://a:b@localhost:3128/" - self.assertEqual(get_proxy_info("echo.websocket.events", False), ("localhost", 3128, ("a", "b"))) + self.assertEqual( + get_proxy_info("echo.websocket.events", False), + ("localhost", 3128, ("a", "b")), + ) os.environ["http_proxy"] = "http://a:b@localhost/" os.environ["https_proxy"] = "http://a:b@localhost2/" - self.assertEqual(get_proxy_info("echo.websocket.events", False), ("localhost", None, ("a", "b"))) + self.assertEqual( + get_proxy_info("echo.websocket.events", False), + ("localhost", None, ("a", "b")), + ) os.environ["http_proxy"] = "http://a:b@localhost:3128/" os.environ["https_proxy"] = "http://a:b@localhost2:3128/" - self.assertEqual(get_proxy_info("echo.websocket.events", False), ("localhost", 3128, ("a", "b"))) + self.assertEqual( + get_proxy_info("echo.websocket.events", False), + ("localhost", 3128, ("a", "b")), + ) os.environ["http_proxy"] = "http://a:b@localhost/" os.environ["https_proxy"] = "http://a:b@localhost2/" - self.assertEqual(get_proxy_info("echo.websocket.events", True), ("localhost2", None, ("a", "b"))) + self.assertEqual( + get_proxy_info("echo.websocket.events", True), + ("localhost2", None, ("a", "b")), + ) os.environ["http_proxy"] = "http://a:b@localhost:3128/" os.environ["https_proxy"] = "http://a:b@localhost2:3128/" - self.assertEqual(get_proxy_info("echo.websocket.events", True), ("localhost2", 3128, ("a", "b"))) + self.assertEqual( + get_proxy_info("echo.websocket.events", True), + ("localhost2", 3128, ("a", "b")), + ) - os.environ["http_proxy"] = "http://john%40example.com:P%40SSWORD@localhost:3128/" - os.environ["https_proxy"] = "http://john%40example.com:P%40SSWORD@localhost2:3128/" - self.assertEqual(get_proxy_info("echo.websocket.events", True), ("localhost2", 3128, ("john@example.com", "P@SSWORD"))) + os.environ[ + "http_proxy" + ] = "http://john%40example.com:P%40SSWORD@localhost:3128/" + os.environ[ + "https_proxy" + ] = "http://john%40example.com:P%40SSWORD@localhost2:3128/" + self.assertEqual( + get_proxy_info("echo.websocket.events", True), + ("localhost2", 3128, ("john@example.com", "P@SSWORD")), + ) os.environ["http_proxy"] = "http://a:b@localhost/" os.environ["https_proxy"] = "http://a:b@localhost2/" os.environ["no_proxy"] = "example1.com,example2.com" - self.assertEqual(get_proxy_info("example.1.com", True), ("localhost2", None, ("a", "b"))) + self.assertEqual( + get_proxy_info("example.1.com", True), ("localhost2", None, ("a", "b")) + ) os.environ["http_proxy"] = "http://a:b@localhost:3128/" os.environ["https_proxy"] = "http://a:b@localhost2:3128/" os.environ["no_proxy"] = "example1.com,example2.com, echo.websocket.events" diff --git a/lib/websocket/tests/test_websocket.py b/lib/websocket/tests/test_websocket.py index a140066e..498def57 100644 --- a/lib/websocket/tests/test_websocket.py +++ b/lib/websocket/tests/test_websocket.py @@ -3,13 +3,14 @@ import os import os.path import socket -import websocket as ws import unittest -from websocket._handshake import _create_sec_websocket_key, \ - _validate as _validate_header +from base64 import decodebytes as base64decode + +import websocket as ws +from websocket._handshake import _create_sec_websocket_key +from websocket._handshake import _validate as _validate_header from websocket._http import read_headers from websocket._utils import validate_utf8 -from base64 import decodebytes as base64decode """ test_websocket.py @@ -38,11 +39,12 @@ except ImportError: class SSLError(Exception): pass + # Skip test to access the internet unless TEST_WITH_INTERNET == 1 -TEST_WITH_INTERNET = os.environ.get('TEST_WITH_INTERNET', '0') == '1' +TEST_WITH_INTERNET = os.environ.get("TEST_WITH_INTERNET", "0") == "1" # Skip tests relying on local websockets server unless LOCAL_WS_SERVER_PORT != -1 -LOCAL_WS_SERVER_PORT = os.environ.get('LOCAL_WS_SERVER_PORT', '-1') -TEST_WITH_LOCAL_SERVER = LOCAL_WS_SERVER_PORT != '-1' +LOCAL_WS_SERVER_PORT = os.environ.get("LOCAL_WS_SERVER_PORT", "-1") +TEST_WITH_LOCAL_SERVER = LOCAL_WS_SERVER_PORT != "-1" TRACEABLE = True @@ -79,7 +81,6 @@ class SockMock: class HeaderSockMock(SockMock): - def __init__(self, fname): SockMock.__init__(self) path = os.path.join(os.path.dirname(__file__), fname) @@ -103,11 +104,10 @@ class WebSocketTest(unittest.TestCase): def testWSKey(self): key = _create_sec_websocket_key() self.assertTrue(key != 24) - self.assertTrue(str("¥n") not in key) + self.assertTrue("¥n" not in key) def testNonce(self): - """ WebSocket key should be a random 16-byte nonce. - """ + """WebSocket key should be a random 16-byte nonce.""" key = _create_sec_websocket_key() nonce = base64decode(key.encode("utf-8")) self.assertEqual(16, len(nonce)) @@ -117,7 +117,8 @@ class WebSocketTest(unittest.TestCase): required_header = { "upgrade": "websocket", "connection": "upgrade", - "sec-websocket-accept": "Kxep+hNu9n51529fGidYu7a3wO0="} + "sec-websocket-accept": "Kxep+hNu9n51529fGidYu7a3wO0=", + } self.assertEqual(_validate_header(required_header, key, None), (True, None)) header = required_header.copy() @@ -140,29 +141,39 @@ class WebSocketTest(unittest.TestCase): header = required_header.copy() header["sec-websocket-protocol"] = "sub1" - self.assertEqual(_validate_header(header, key, ["sub1", "sub2"]), (True, "sub1")) + self.assertEqual( + _validate_header(header, key, ["sub1", "sub2"]), (True, "sub1") + ) # This case will print out a logging error using the error() function, but that is expected self.assertEqual(_validate_header(header, key, ["sub2", "sub3"]), (False, None)) header = required_header.copy() header["sec-websocket-protocol"] = "sUb1" - self.assertEqual(_validate_header(header, key, ["Sub1", "suB2"]), (True, "sub1")) + self.assertEqual( + _validate_header(header, key, ["Sub1", "suB2"]), (True, "sub1") + ) header = required_header.copy() # This case will print out a logging error using the error() function, but that is expected self.assertEqual(_validate_header(header, key, ["Sub1", "suB2"]), (False, None)) def testReadHeader(self): - status, header, status_message = read_headers(HeaderSockMock("data/header01.txt")) + status, header, status_message = read_headers( + HeaderSockMock("data/header01.txt") + ) self.assertEqual(status, 101) self.assertEqual(header["connection"], "Upgrade") - status, header, status_message = read_headers(HeaderSockMock("data/header03.txt")) + status, header, status_message = read_headers( + HeaderSockMock("data/header03.txt") + ) self.assertEqual(status, 101) self.assertEqual(header["connection"], "Upgrade, Keep-Alive") HeaderSockMock("data/header02.txt") - self.assertRaises(ws.WebSocketException, read_headers, HeaderSockMock("data/header02.txt")) + self.assertRaises( + ws.WebSocketException, read_headers, HeaderSockMock("data/header02.txt") + ) def testSend(self): # TODO: add longer frame data @@ -170,33 +181,38 @@ class WebSocketTest(unittest.TestCase): sock.set_mask_key(create_mask_key) s = sock.sock = HeaderSockMock("data/header01.txt") sock.send("Hello") - self.assertEqual(s.sent[0], b'\x81\x85abcd)\x07\x0f\x08\x0e') + self.assertEqual(s.sent[0], b"\x81\x85abcd)\x07\x0f\x08\x0e") sock.send("こんにちは") - self.assertEqual(s.sent[1], b'\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc') + self.assertEqual( + s.sent[1], + b"\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc", + ) -# sock.send("x" * 5000) -# self.assertEqual(s.sent[1], b'\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc") + # sock.send("x" * 5000) + # self.assertEqual(s.sent[1], b'\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc") - self.assertEqual(sock.send_binary(b'1111111111101'), 19) + self.assertEqual(sock.send_binary(b"1111111111101"), 19) def testRecv(self): # TODO: add longer frame data sock = ws.WebSocket() s = sock.sock = SockMock() - something = b'\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc' + something = ( + b"\x81\x8fabcd\x82\xe3\xf0\x87\xe3\xf1\x80\xe5\xca\x81\xe2\xc5\x82\xe3\xcc" + ) s.add_packet(something) data = sock.recv() self.assertEqual(data, "こんにちは") - s.add_packet(b'\x81\x85abcd)\x07\x0f\x08\x0e') + s.add_packet(b"\x81\x85abcd)\x07\x0f\x08\x0e") data = sock.recv() self.assertEqual(data, "Hello") @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") def testIter(self): count = 2 - s = ws.create_connection('wss://api.bitfinex.com/ws/2') + s = ws.create_connection("wss://api.bitfinex.com/ws/2") s.send('{"event": "subscribe", "channel": "ticker"}') for _ in s: count -= 1 @@ -205,34 +221,34 @@ class WebSocketTest(unittest.TestCase): @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") def testNext(self): - sock = ws.create_connection('wss://api.bitfinex.com/ws/2') + sock = ws.create_connection("wss://api.bitfinex.com/ws/2") self.assertEqual(str, type(next(sock))) def testInternalRecvStrict(self): sock = ws.WebSocket() s = sock.sock = SockMock() - s.add_packet(b'foo') + s.add_packet(b"foo") s.add_packet(socket.timeout()) - s.add_packet(b'bar') + s.add_packet(b"bar") # s.add_packet(SSLError("The read operation timed out")) - s.add_packet(b'baz') + s.add_packet(b"baz") with self.assertRaises(ws.WebSocketTimeoutException): sock.frame_buffer.recv_strict(9) # with self.assertRaises(SSLError): # data = sock._recv_strict(9) data = sock.frame_buffer.recv_strict(9) - self.assertEqual(data, b'foobarbaz') + self.assertEqual(data, b"foobarbaz") with self.assertRaises(ws.WebSocketConnectionClosedException): sock.frame_buffer.recv_strict(1) def testRecvTimeout(self): sock = ws.WebSocket() s = sock.sock = SockMock() - s.add_packet(b'\x81') + s.add_packet(b"\x81") s.add_packet(socket.timeout()) - s.add_packet(b'\x8dabcd\x29\x07\x0f\x08\x0e') + s.add_packet(b"\x8dabcd\x29\x07\x0f\x08\x0e") s.add_packet(socket.timeout()) - s.add_packet(b'\x4e\x43\x33\x0e\x10\x0f\x00\x40') + s.add_packet(b"\x4e\x43\x33\x0e\x10\x0f\x00\x40") with self.assertRaises(ws.WebSocketTimeoutException): sock.recv() with self.assertRaises(ws.WebSocketTimeoutException): @@ -246,9 +262,9 @@ class WebSocketTest(unittest.TestCase): sock = ws.WebSocket() s = sock.sock = SockMock() # OPCODE=TEXT, FIN=0, MSG="Brevity is " - s.add_packet(b'\x01\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C') + s.add_packet(b"\x01\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C") # OPCODE=CONT, FIN=1, MSG="the soul of wit" - s.add_packet(b'\x80\x8fabcd\x15\n\x06D\x12\r\x16\x08A\r\x05D\x16\x0b\x17') + s.add_packet(b"\x80\x8fabcd\x15\n\x06D\x12\r\x16\x08A\r\x05D\x16\x0b\x17") data = sock.recv() self.assertEqual(data, "Brevity is the soul of wit") with self.assertRaises(ws.WebSocketConnectionClosedException): @@ -258,21 +274,21 @@ class WebSocketTest(unittest.TestCase): sock = ws.WebSocket(fire_cont_frame=True) s = sock.sock = SockMock() # OPCODE=TEXT, FIN=0, MSG="Brevity is " - s.add_packet(b'\x01\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C') + s.add_packet(b"\x01\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C") # OPCODE=CONT, FIN=0, MSG="Brevity is " - s.add_packet(b'\x00\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C') + s.add_packet(b"\x00\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C") # OPCODE=CONT, FIN=1, MSG="the soul of wit" - s.add_packet(b'\x80\x8fabcd\x15\n\x06D\x12\r\x16\x08A\r\x05D\x16\x0b\x17') + s.add_packet(b"\x80\x8fabcd\x15\n\x06D\x12\r\x16\x08A\r\x05D\x16\x0b\x17") _, data = sock.recv_data() - self.assertEqual(data, b'Brevity is ') + self.assertEqual(data, b"Brevity is ") _, data = sock.recv_data() - self.assertEqual(data, b'Brevity is ') + self.assertEqual(data, b"Brevity is ") _, data = sock.recv_data() - self.assertEqual(data, b'the soul of wit') + self.assertEqual(data, b"the soul of wit") # OPCODE=CONT, FIN=0, MSG="Brevity is " - s.add_packet(b'\x80\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C') + s.add_packet(b"\x80\x8babcd#\x10\x06\x12\x08\x16\x1aD\x08\x11C") with self.assertRaises(ws.WebSocketException): sock.recv_data() @@ -288,7 +304,7 @@ class WebSocketTest(unittest.TestCase): sock = ws.WebSocket() s = sock.sock = SockMock() sock.connected = True - s.add_packet(b'\x88\x80\x17\x98p\x84') + s.add_packet(b"\x88\x80\x17\x98p\x84") sock.recv() self.assertEqual(sock.connected, False) @@ -296,22 +312,22 @@ class WebSocketTest(unittest.TestCase): sock = ws.WebSocket() s = sock.sock = SockMock() # OPCODE=CONT, FIN=1, MSG="the soul of wit" - s.add_packet(b'\x80\x8fabcd\x15\n\x06D\x12\r\x16\x08A\r\x05D\x16\x0b\x17') + s.add_packet(b"\x80\x8fabcd\x15\n\x06D\x12\r\x16\x08A\r\x05D\x16\x0b\x17") self.assertRaises(ws.WebSocketException, sock.recv) def testRecvWithProlongedFragmentation(self): sock = ws.WebSocket() s = sock.sock = SockMock() # OPCODE=TEXT, FIN=0, MSG="Once more unto the breach, " - s.add_packet(b'\x01\x9babcd.\x0c\x00\x01A\x0f\x0c\x16\x04B\x16\n\x15\rC\x10\t\x07C\x06\x13\x07\x02\x07\tNC') + s.add_packet( + b"\x01\x9babcd.\x0c\x00\x01A\x0f\x0c\x16\x04B\x16\n\x15\rC\x10\t\x07C\x06\x13\x07\x02\x07\tNC" + ) # OPCODE=CONT, FIN=0, MSG="dear friends, " - s.add_packet(b'\x00\x8eabcd\x05\x07\x02\x16A\x04\x11\r\x04\x0c\x07\x17MB') + s.add_packet(b"\x00\x8eabcd\x05\x07\x02\x16A\x04\x11\r\x04\x0c\x07\x17MB") # OPCODE=CONT, FIN=1, MSG="once more" - s.add_packet(b'\x80\x89abcd\x0e\x0c\x00\x01A\x0f\x0c\x16\x04') + s.add_packet(b"\x80\x89abcd\x0e\x0c\x00\x01A\x0f\x0c\x16\x04") data = sock.recv() - self.assertEqual( - data, - "Once more unto the breach, dear friends, once more") + self.assertEqual(data, "Once more unto the breach, dear friends, once more") with self.assertRaises(ws.WebSocketConnectionClosedException): sock.recv() @@ -320,22 +336,24 @@ class WebSocketTest(unittest.TestCase): sock.set_mask_key(create_mask_key) s = sock.sock = SockMock() # OPCODE=TEXT, FIN=0, MSG="Too much " - s.add_packet(b'\x01\x89abcd5\r\x0cD\x0c\x17\x00\x0cA') + s.add_packet(b"\x01\x89abcd5\r\x0cD\x0c\x17\x00\x0cA") # OPCODE=PING, FIN=1, MSG="Please PONG this" - s.add_packet(b'\x89\x90abcd1\x0e\x06\x05\x12\x07C4.,$D\x15\n\n\x17') + s.add_packet(b"\x89\x90abcd1\x0e\x06\x05\x12\x07C4.,$D\x15\n\n\x17") # OPCODE=CONT, FIN=1, MSG="of a good thing" - s.add_packet(b'\x80\x8fabcd\x0e\x04C\x05A\x05\x0c\x0b\x05B\x17\x0c\x08\x0c\x04') + s.add_packet(b"\x80\x8fabcd\x0e\x04C\x05A\x05\x0c\x0b\x05B\x17\x0c\x08\x0c\x04") data = sock.recv() self.assertEqual(data, "Too much of a good thing") with self.assertRaises(ws.WebSocketConnectionClosedException): sock.recv() self.assertEqual( - s.sent[0], - b'\x8a\x90abcd1\x0e\x06\x05\x12\x07C4.,$D\x15\n\n\x17') + s.sent[0], b"\x8a\x90abcd1\x0e\x06\x05\x12\x07C4.,$D\x15\n\n\x17" + ) - @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled") + @unittest.skipUnless( + TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled" + ) def testWebSocket(self): - s = ws.create_connection("ws://127.0.0.1:" + LOCAL_WS_SERVER_PORT) + s = ws.create_connection(f"ws://127.0.0.1:{LOCAL_WS_SERVER_PORT}") self.assertNotEqual(s, None) s.send("Hello, World") result = s.next() @@ -348,9 +366,11 @@ class WebSocketTest(unittest.TestCase): self.assertRaises(ValueError, s.send_close, -1, "") s.close() - @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled") + @unittest.skipUnless( + TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled" + ) def testPingPong(self): - s = ws.create_connection("ws://127.0.0.1:" + LOCAL_WS_SERVER_PORT) + s = ws.create_connection(f"ws://127.0.0.1:{LOCAL_WS_SERVER_PORT}") self.assertNotEqual(s, None) s.ping("Hello") s.pong("Hi") @@ -359,12 +379,15 @@ class WebSocketTest(unittest.TestCase): @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") def testSupportRedirect(self): s = ws.WebSocket() - self.assertRaises(ws._exceptions.WebSocketBadStatusException, s.connect, "ws://google.com/") + self.assertRaises( + ws._exceptions.WebSocketBadStatusException, s.connect, "ws://google.com/" + ) # Need to find a URL that has a redirect code leading to a websocket @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") def testSecureWebSocket(self): import ssl + s = ws.create_connection("wss://api.bitfinex.com/ws/2") self.assertNotEqual(s, None) self.assertTrue(isinstance(s.sock, ssl.SSLSocket)) @@ -375,10 +398,14 @@ class WebSocketTest(unittest.TestCase): self.assertEqual(s.getsubprotocol(), None) s.abort() - @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled") + @unittest.skipUnless( + TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled" + ) def testWebSocketWithCustomHeader(self): - s = ws.create_connection("ws://127.0.0.1:" + LOCAL_WS_SERVER_PORT, - headers={"User-Agent": "PythonWebsocketClient"}) + s = ws.create_connection( + f"ws://127.0.0.1:{LOCAL_WS_SERVER_PORT}", + headers={"User-Agent": "PythonWebsocketClient"}, + ) self.assertNotEqual(s, None) self.assertEqual(s.getsubprotocol(), None) s.send("Hello, World") @@ -387,9 +414,11 @@ class WebSocketTest(unittest.TestCase): self.assertRaises(ValueError, s.close, -1, "") s.close() - @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled") + @unittest.skipUnless( + TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled" + ) def testAfterClose(self): - s = ws.create_connection("ws://127.0.0.1:" + LOCAL_WS_SERVER_PORT) + s = ws.create_connection(f"ws://127.0.0.1:{LOCAL_WS_SERVER_PORT}") self.assertNotEqual(s, None) s.close() self.assertRaises(ws.WebSocketConnectionClosedException, s.send, "Hello") @@ -397,48 +426,69 @@ class WebSocketTest(unittest.TestCase): class SockOptTest(unittest.TestCase): - @unittest.skipUnless(TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled") + @unittest.skipUnless( + TEST_WITH_LOCAL_SERVER, "Tests using local websocket server are disabled" + ) def testSockOpt(self): sockopt = ((socket.IPPROTO_TCP, socket.TCP_NODELAY, 1),) - s = ws.create_connection("ws://127.0.0.1:" + LOCAL_WS_SERVER_PORT, sockopt=sockopt) - self.assertNotEqual(s.sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY), 0) + s = ws.create_connection( + f"ws://127.0.0.1:{LOCAL_WS_SERVER_PORT}", sockopt=sockopt + ) + self.assertNotEqual( + s.sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY), 0 + ) s.close() class UtilsTest(unittest.TestCase): def testUtf8Validator(self): - state = validate_utf8(b'\xf0\x90\x80\x80') + state = validate_utf8(b"\xf0\x90\x80\x80") self.assertEqual(state, True) - state = validate_utf8(b'\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5\xed\xa0\x80edited') + state = validate_utf8( + b"\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5\xed\xa0\x80edited" + ) self.assertEqual(state, False) - state = validate_utf8(b'') + state = validate_utf8(b"") self.assertEqual(state, True) class HandshakeTest(unittest.TestCase): @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") def test_http_SSL(self): - websock1 = ws.WebSocket(sslopt={"cert_chain": ssl.get_default_verify_paths().capath}, enable_multithread=False) - self.assertRaises(ValueError, - websock1.connect, "wss://api.bitfinex.com/ws/2") + websock1 = ws.WebSocket( + sslopt={"cert_chain": ssl.get_default_verify_paths().capath}, + enable_multithread=False, + ) + self.assertRaises(ValueError, websock1.connect, "wss://api.bitfinex.com/ws/2") websock2 = ws.WebSocket(sslopt={"certfile": "myNonexistentCertFile"}) - self.assertRaises(FileNotFoundError, - websock2.connect, "wss://api.bitfinex.com/ws/2") + self.assertRaises( + FileNotFoundError, websock2.connect, "wss://api.bitfinex.com/ws/2" + ) @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") def testManualHeaders(self): - websock3 = ws.WebSocket(sslopt={"ca_certs": ssl.get_default_verify_paths().cafile, - "ca_cert_path": ssl.get_default_verify_paths().capath}) - self.assertRaises(ws._exceptions.WebSocketBadStatusException, - websock3.connect, "wss://api.bitfinex.com/ws/2", cookie="chocolate", - origin="testing_websockets.com", - host="echo.websocket.events/websocket-client-test", - subprotocols=["testproto"], - connection="Upgrade", - header={"CustomHeader1":"123", - "Cookie":"TestValue", - "Sec-WebSocket-Key":"k9kFAUWNAMmf5OEMfTlOEA==", - "Sec-WebSocket-Protocol":"newprotocol"}) + websock3 = ws.WebSocket( + sslopt={ + "ca_certs": ssl.get_default_verify_paths().cafile, + "ca_cert_path": ssl.get_default_verify_paths().capath, + } + ) + self.assertRaises( + ws._exceptions.WebSocketBadStatusException, + websock3.connect, + "wss://api.bitfinex.com/ws/2", + cookie="chocolate", + origin="testing_websockets.com", + host="echo.websocket.events/websocket-client-test", + subprotocols=["testproto"], + connection="Upgrade", + header={ + "CustomHeader1": "123", + "Cookie": "TestValue", + "Sec-WebSocket-Key": "k9kFAUWNAMmf5OEMfTlOEA==", + "Sec-WebSocket-Protocol": "newprotocol", + }, + ) def testIPv6(self): websock2 = ws.WebSocket() @@ -447,7 +497,9 @@ class HandshakeTest(unittest.TestCase): def testBadURLs(self): websock3 = ws.WebSocket() self.assertRaises(ValueError, websock3.connect, "ws//example.com") - self.assertRaises(ws.WebSocketAddressException, websock3.connect, "ws://example") + self.assertRaises( + ws.WebSocketAddressException, websock3.connect, "ws://example" + ) self.assertRaises(ValueError, websock3.connect, "example.com") diff --git a/plexpy/logger.py b/plexpy/logger.py index 9f44df95..7227ab10 100644 --- a/plexpy/logger.py +++ b/plexpy/logger.py @@ -76,7 +76,7 @@ def filter_usernames(new_users=None): global _FILTER_USERNAMES if new_users is None: - new_users = [user['username'] for user in users.Users().get_users()] + new_users = [user['username'] for user in users.Users().get_users(include_deleted=True)] for username in new_users: if username.lower() not in ('local', 'guest') and len(username) > 3 and username not in _FILTER_USERNAMES: diff --git a/plexpy/users.py b/plexpy/users.py index 0e201791..974d30cd 100644 --- a/plexpy/users.py +++ b/plexpy/users.py @@ -679,15 +679,17 @@ class Users(object): return recently_watched - def get_users(self): + def get_users(self, include_deleted=False): monitor_db = database.MonitorDatabase() + where = '' if include_deleted else 'WHERE deleted_user = 0' + try: query = "SELECT id AS row_id, user_id, username, friendly_name, thumb, custom_avatar_url, email, " \ "is_active, is_admin, is_home_user, is_allow_sync, is_restricted, " \ "do_notify, keep_history, allow_guest, shared_libraries, " \ "filter_all, filter_movies, filter_tv, filter_music, filter_photos " \ - "FROM users WHERE deleted_user = 0" + "FROM users %s" % where result = monitor_db.select(query=query) except Exception as e: logger.warn("Tautulli Users :: Unable to execute database query for get_users: %s." % e) diff --git a/requirements.txt b/requirements.txt index 3696ca5f..64458e26 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,15 +1,15 @@ apscheduler==3.10.1 -arrow==1.2.3 +arrow==1.3.0 backports.csv==1.0.7 -backports.functools-lru-cache==1.6.6 +backports.functools-lru-cache==2.0.0 backports.zoneinfo==0.2.1;python_version<"3.9" beautifulsoup4==4.12.2 -bleach==6.0.0 -certifi==2023.7.22 +bleach==6.1.0 +certifi==2024.2.2 cheroot==10.0.0 cherrypy==18.8.0 cloudinary==1.34.0 -distro==1.8.0 +distro==1.9.0 dnspython==2.6.1 facebook-sdk==3.1.0 future==0.18.3 @@ -22,12 +22,12 @@ importlib-metadata==6.8.0 importlib-resources==6.0.1 git+https://github.com/Tautulli/ipwhois.git@master#egg=ipwhois IPy==1.01 -Mako==1.2.4 +Mako==1.3.2 MarkupSafe==2.1.3 musicbrainzngs==0.7.1 packaging==23.1 paho-mqtt==1.6.1 -platformdirs==3.11.0 +platformdirs==4.2.0 plexapi==4.15.10 portend==3.2.0 profilehooks==1.12.0 @@ -35,11 +35,11 @@ PyJWT==2.8.0 pyparsing==3.1.1 python-dateutil==2.8.2 python-twitter==3.5 -pytz==2023.3 +pytz==2024.1 requests==2.31.0 requests-oauthlib==1.3.1 rumps==0.4.0; platform_system == "Darwin" -simplejson==3.19.1 +simplejson==3.19.2 six==1.16.0 tempora==5.5.0 tokenize-rt==5.2.0 @@ -47,7 +47,7 @@ tzdata==2023.3 tzlocal==5.0.1 urllib3<2 webencodings==0.5.1 -websocket-client==1.6.2 +websocket-client==1.7.0 xmltodict==0.13.0 zipp==3.16.2