From 75a1750a4e1882efc2f0a2fcc3843006e20c3e3b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Mar 2024 15:10:51 -0700 Subject: [PATCH 1/6] Bump cherrypy from 18.8.0 to 18.9.0 (#2289) Bumps [cherrypy](https://github.com/cherrypy/cherrypy) from 18.8.0 to 18.9.0. - [Changelog](https://github.com/cherrypy/cherrypy/blob/main/CHANGES.rst) - [Commits](https://github.com/cherrypy/cherrypy/compare/v18.8.0...v18.9.0) --- updated-dependencies: - dependency-name: cherrypy dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> [skip ci] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index dea2b2a3..baf358c5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ bleach==6.1.0 certifi==2024.2.2 cheroot==10.0.0 cloudinary==1.39.1 -cherrypy==18.8.0 +cherrypy==18.9.0 distro==1.9.0 dnspython==2.6.1 facebook-sdk==3.1.0 From f82aecb88ce3f3df4dba6b3e5173cb8b269a858d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 30 Mar 2024 15:27:16 -0700 Subject: [PATCH 2/6] Bump paho-mqtt from 1.6.1 to 2.0.0 (#2288) * Bump paho-mqtt from 1.6.1 to 2.0.0 Bumps [paho-mqtt](https://github.com/eclipse/paho.mqtt.python) from 1.6.1 to 2.0.0. - [Release notes](https://github.com/eclipse/paho.mqtt.python/releases) - [Changelog](https://github.com/eclipse/paho.mqtt.python/blob/master/ChangeLog.txt) - [Commits](https://github.com/eclipse/paho.mqtt.python/compare/v1.6.1...v2.0.0) --- updated-dependencies: - dependency-name: paho-mqtt dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Update paho-mqtt==2.0.0 --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> [skip ci] --- lib/paho/mqtt/__init__.py | 2 +- lib/paho/mqtt/client.py | 3454 +++++++++++++++++++---------- lib/paho/mqtt/enums.py | 113 + lib/paho/mqtt/matcher.py | 12 +- lib/paho/mqtt/packettypes.py | 6 +- lib/paho/mqtt/properties.py | 87 +- lib/paho/mqtt/publish.py | 177 +- lib/paho/mqtt/py.typed | 0 lib/paho/mqtt/reasoncodes.py | 93 +- lib/paho/mqtt/subscribe.py | 97 +- lib/paho/mqtt/subscribeoptions.py | 43 +- requirements.txt | 2 +- 12 files changed, 2643 insertions(+), 1443 deletions(-) create mode 100644 lib/paho/mqtt/enums.py create mode 100644 lib/paho/mqtt/py.typed diff --git a/lib/paho/mqtt/__init__.py b/lib/paho/mqtt/__init__.py index 0d349fc3..19b6a4ab 100644 --- a/lib/paho/mqtt/__init__.py +++ b/lib/paho/mqtt/__init__.py @@ -1,4 +1,4 @@ -__version__ = "1.6.1" +__version__ = "2.0.0" class MQTTException(Exception): diff --git a/lib/paho/mqtt/client.py b/lib/paho/mqtt/client.py index 1c0236e4..f5897328 100644 --- a/lib/paho/mqtt/client.py +++ b/lib/paho/mqtt/client.py @@ -5,61 +5,105 @@ # and Eclipse Distribution License v1.0 which accompany this distribution. # # The Eclipse Public License is available at -# http://www.eclipse.org/legal/epl-v10.html +# http://www.eclipse.org/legal/epl-v20.html # and the Eclipse Distribution License is available at # http://www.eclipse.org/org/documents/edl-v10.php. # # Contributors: # Roger Light - initial API and implementation # Ian Craggs - MQTT V5 support - -import base64 -import hashlib -import logging -import string -import struct -import sys -import threading -import time -import uuid - -from .matcher import MQTTMatcher -from .properties import Properties -from .reasoncodes import ReasonCodes -from .subscribeoptions import SubscribeOptions - """ This is an MQTT client module. MQTT is a lightweight pub/sub messaging protocol that is easy to implement and suitable for low powered devices. """ +from __future__ import annotations + +import base64 import collections import errno +import hashlib +import logging import os import platform import select import socket +import string +import struct +import threading +import time +import urllib.parse +import urllib.request +import uuid +import warnings +from typing import TYPE_CHECKING, Any, Callable, Dict, Iterator, List, NamedTuple, Sequence, Tuple, Union, cast + +from paho.mqtt.packettypes import PacketTypes + +from .enums import CallbackAPIVersion, ConnackCode, LogLevel, MessageState, MessageType, MQTTErrorCode, MQTTProtocolVersion, PahoClientMode, _ConnectionState +from .matcher import MQTTMatcher +from .properties import Properties +from .reasoncodes import ReasonCode, ReasonCodes +from .subscribeoptions import SubscribeOptions + +try: + from typing import Literal +except ImportError: + from typing_extensions import Literal # type: ignore + +if TYPE_CHECKING: + try: + from typing import TypedDict # type: ignore + except ImportError: + from typing_extensions import TypedDict + + try: + from typing import Protocol # type: ignore + except ImportError: + from typing_extensions import Protocol # type: ignore + + class _InPacket(TypedDict): + command: int + have_remaining: int + remaining_count: list[int] + remaining_mult: int + remaining_length: int + packet: bytearray + to_process: int + pos: int + + + class _OutPacket(TypedDict): + command: int + mid: int + qos: int + pos: int + to_process: int + packet: bytes + info: MQTTMessageInfo | None + + class SocketLike(Protocol): + def recv(self, buffer_size: int) -> bytes: + ... + def send(self, buffer: bytes) -> int: + ... + def close(self) -> None: + ... + def fileno(self) -> int: + ... + def setblocking(self, flag: bool) -> None: + ... + -ssl = None try: import ssl except ImportError: - pass + ssl = None # type: ignore[assignment] -socks = None -try: - import socks -except ImportError: - pass try: - # Python 3 - from urllib import parse as urllib_dot_parse - from urllib import request as urllib_dot_request + import socks # type: ignore[import-untyped] except ImportError: - # Python 2 - import urllib as urllib_dot_request - - import urlparse as urllib_dot_parse + socks = None # type: ignore[assignment] try: @@ -70,123 +114,181 @@ except AttributeError: try: import dns.resolver + + HAVE_DNS = True except ImportError: HAVE_DNS = False -else: - HAVE_DNS = True if platform.system() == 'Windows': - EAGAIN = errno.WSAEWOULDBLOCK + EAGAIN = errno.WSAEWOULDBLOCK # type: ignore[attr-defined] else: EAGAIN = errno.EAGAIN -# Python 2.7 does not have BlockingIOError. Fall back to IOError -try: - BlockingIOError -except NameError: - BlockingIOError = IOError +# Avoid linter complain. We kept importing it as ReasonCodes (plural) for compatibility +_ = ReasonCodes -MQTTv31 = 3 -MQTTv311 = 4 -MQTTv5 = 5 - -if sys.version_info[0] >= 3: - # define some alias for python2 compatibility - unicode = str - basestring = str - -# Message types -CONNECT = 0x10 -CONNACK = 0x20 -PUBLISH = 0x30 -PUBACK = 0x40 -PUBREC = 0x50 -PUBREL = 0x60 -PUBCOMP = 0x70 -SUBSCRIBE = 0x80 -SUBACK = 0x90 -UNSUBSCRIBE = 0xA0 -UNSUBACK = 0xB0 -PINGREQ = 0xC0 -PINGRESP = 0xD0 -DISCONNECT = 0xE0 -AUTH = 0xF0 +# Keep copy of enums values for compatibility. +CONNECT = MessageType.CONNECT +CONNACK = MessageType.CONNACK +PUBLISH = MessageType.PUBLISH +PUBACK = MessageType.PUBACK +PUBREC = MessageType.PUBREC +PUBREL = MessageType.PUBREL +PUBCOMP = MessageType.PUBCOMP +SUBSCRIBE = MessageType.SUBSCRIBE +SUBACK = MessageType.SUBACK +UNSUBSCRIBE = MessageType.UNSUBSCRIBE +UNSUBACK = MessageType.UNSUBACK +PINGREQ = MessageType.PINGREQ +PINGRESP = MessageType.PINGRESP +DISCONNECT = MessageType.DISCONNECT +AUTH = MessageType.AUTH # Log levels -MQTT_LOG_INFO = 0x01 -MQTT_LOG_NOTICE = 0x02 -MQTT_LOG_WARNING = 0x04 -MQTT_LOG_ERR = 0x08 -MQTT_LOG_DEBUG = 0x10 +MQTT_LOG_INFO = LogLevel.MQTT_LOG_INFO +MQTT_LOG_NOTICE = LogLevel.MQTT_LOG_NOTICE +MQTT_LOG_WARNING = LogLevel.MQTT_LOG_WARNING +MQTT_LOG_ERR = LogLevel.MQTT_LOG_ERR +MQTT_LOG_DEBUG = LogLevel.MQTT_LOG_DEBUG LOGGING_LEVEL = { - MQTT_LOG_DEBUG: logging.DEBUG, - MQTT_LOG_INFO: logging.INFO, - MQTT_LOG_NOTICE: logging.INFO, # This has no direct equivalent level - MQTT_LOG_WARNING: logging.WARNING, - MQTT_LOG_ERR: logging.ERROR, + LogLevel.MQTT_LOG_DEBUG: logging.DEBUG, + LogLevel.MQTT_LOG_INFO: logging.INFO, + LogLevel.MQTT_LOG_NOTICE: logging.INFO, # This has no direct equivalent level + LogLevel.MQTT_LOG_WARNING: logging.WARNING, + LogLevel.MQTT_LOG_ERR: logging.ERROR, } # CONNACK codes -CONNACK_ACCEPTED = 0 -CONNACK_REFUSED_PROTOCOL_VERSION = 1 -CONNACK_REFUSED_IDENTIFIER_REJECTED = 2 -CONNACK_REFUSED_SERVER_UNAVAILABLE = 3 -CONNACK_REFUSED_BAD_USERNAME_PASSWORD = 4 -CONNACK_REFUSED_NOT_AUTHORIZED = 5 - -# Connection state -mqtt_cs_new = 0 -mqtt_cs_connected = 1 -mqtt_cs_disconnecting = 2 -mqtt_cs_connect_async = 3 +CONNACK_ACCEPTED = ConnackCode.CONNACK_ACCEPTED +CONNACK_REFUSED_PROTOCOL_VERSION = ConnackCode.CONNACK_REFUSED_PROTOCOL_VERSION +CONNACK_REFUSED_IDENTIFIER_REJECTED = ConnackCode.CONNACK_REFUSED_IDENTIFIER_REJECTED +CONNACK_REFUSED_SERVER_UNAVAILABLE = ConnackCode.CONNACK_REFUSED_SERVER_UNAVAILABLE +CONNACK_REFUSED_BAD_USERNAME_PASSWORD = ConnackCode.CONNACK_REFUSED_BAD_USERNAME_PASSWORD +CONNACK_REFUSED_NOT_AUTHORIZED = ConnackCode.CONNACK_REFUSED_NOT_AUTHORIZED # Message state -mqtt_ms_invalid = 0 -mqtt_ms_publish = 1 -mqtt_ms_wait_for_puback = 2 -mqtt_ms_wait_for_pubrec = 3 -mqtt_ms_resend_pubrel = 4 -mqtt_ms_wait_for_pubrel = 5 -mqtt_ms_resend_pubcomp = 6 -mqtt_ms_wait_for_pubcomp = 7 -mqtt_ms_send_pubrec = 8 -mqtt_ms_queued = 9 +mqtt_ms_invalid = MessageState.MQTT_MS_INVALID +mqtt_ms_publish = MessageState.MQTT_MS_PUBLISH +mqtt_ms_wait_for_puback = MessageState.MQTT_MS_WAIT_FOR_PUBACK +mqtt_ms_wait_for_pubrec = MessageState.MQTT_MS_WAIT_FOR_PUBREC +mqtt_ms_resend_pubrel = MessageState.MQTT_MS_RESEND_PUBREL +mqtt_ms_wait_for_pubrel = MessageState.MQTT_MS_WAIT_FOR_PUBREL +mqtt_ms_resend_pubcomp = MessageState.MQTT_MS_RESEND_PUBCOMP +mqtt_ms_wait_for_pubcomp = MessageState.MQTT_MS_WAIT_FOR_PUBCOMP +mqtt_ms_send_pubrec = MessageState.MQTT_MS_SEND_PUBREC +mqtt_ms_queued = MessageState.MQTT_MS_QUEUED -# Error values -MQTT_ERR_AGAIN = -1 -MQTT_ERR_SUCCESS = 0 -MQTT_ERR_NOMEM = 1 -MQTT_ERR_PROTOCOL = 2 -MQTT_ERR_INVAL = 3 -MQTT_ERR_NO_CONN = 4 -MQTT_ERR_CONN_REFUSED = 5 -MQTT_ERR_NOT_FOUND = 6 -MQTT_ERR_CONN_LOST = 7 -MQTT_ERR_TLS = 8 -MQTT_ERR_PAYLOAD_SIZE = 9 -MQTT_ERR_NOT_SUPPORTED = 10 -MQTT_ERR_AUTH = 11 -MQTT_ERR_ACL_DENIED = 12 -MQTT_ERR_UNKNOWN = 13 -MQTT_ERR_ERRNO = 14 -MQTT_ERR_QUEUE_SIZE = 15 -MQTT_ERR_KEEPALIVE = 16 +MQTT_ERR_AGAIN = MQTTErrorCode.MQTT_ERR_AGAIN +MQTT_ERR_SUCCESS = MQTTErrorCode.MQTT_ERR_SUCCESS +MQTT_ERR_NOMEM = MQTTErrorCode.MQTT_ERR_NOMEM +MQTT_ERR_PROTOCOL = MQTTErrorCode.MQTT_ERR_PROTOCOL +MQTT_ERR_INVAL = MQTTErrorCode.MQTT_ERR_INVAL +MQTT_ERR_NO_CONN = MQTTErrorCode.MQTT_ERR_NO_CONN +MQTT_ERR_CONN_REFUSED = MQTTErrorCode.MQTT_ERR_CONN_REFUSED +MQTT_ERR_NOT_FOUND = MQTTErrorCode.MQTT_ERR_NOT_FOUND +MQTT_ERR_CONN_LOST = MQTTErrorCode.MQTT_ERR_CONN_LOST +MQTT_ERR_TLS = MQTTErrorCode.MQTT_ERR_TLS +MQTT_ERR_PAYLOAD_SIZE = MQTTErrorCode.MQTT_ERR_PAYLOAD_SIZE +MQTT_ERR_NOT_SUPPORTED = MQTTErrorCode.MQTT_ERR_NOT_SUPPORTED +MQTT_ERR_AUTH = MQTTErrorCode.MQTT_ERR_AUTH +MQTT_ERR_ACL_DENIED = MQTTErrorCode.MQTT_ERR_ACL_DENIED +MQTT_ERR_UNKNOWN = MQTTErrorCode.MQTT_ERR_UNKNOWN +MQTT_ERR_ERRNO = MQTTErrorCode.MQTT_ERR_ERRNO +MQTT_ERR_QUEUE_SIZE = MQTTErrorCode.MQTT_ERR_QUEUE_SIZE +MQTT_ERR_KEEPALIVE = MQTTErrorCode.MQTT_ERR_KEEPALIVE -MQTT_CLIENT = 0 -MQTT_BRIDGE = 1 +MQTTv31 = MQTTProtocolVersion.MQTTv31 +MQTTv311 = MQTTProtocolVersion.MQTTv311 +MQTTv5 = MQTTProtocolVersion.MQTTv5 + +MQTT_CLIENT = PahoClientMode.MQTT_CLIENT +MQTT_BRIDGE = PahoClientMode.MQTT_BRIDGE # For MQTT V5, use the clean start flag only on the first successful connect -MQTT_CLEAN_START_FIRST_ONLY = 3 +MQTT_CLEAN_START_FIRST_ONLY: CleanStartOption = 3 sockpair_data = b"0" +# Payload support all those type and will be converted to bytes: +# * str are utf8 encoded +# * int/float are converted to string and utf8 encoded (e.g. 1 is converted to b"1") +# * None is converted to a zero-length payload (i.e. b"") +PayloadType = Union[str, bytes, bytearray, int, float, None] -class WebsocketConnectionError(ValueError): +HTTPHeader = Dict[str, str] +WebSocketHeaders = Union[Callable[[HTTPHeader], HTTPHeader], HTTPHeader] + +CleanStartOption = Union[bool, Literal[3]] + + +class ConnectFlags(NamedTuple): + """Contains additional information passed to `on_connect` callback""" + + session_present: bool + """ + this flag is useful for clients that are + using clean session set to False only (MQTTv3) or clean_start = False (MQTTv5). + In that case, if client that reconnects to a broker that it has previously + connected to, this flag indicates whether the broker still has the + session information for the client. If true, the session still exists. + """ + + +class DisconnectFlags(NamedTuple): + """Contains additional information passed to `on_disconnect` callback""" + + is_disconnect_packet_from_server: bool + """ + tells whether this on_disconnect call is the result + of receiving an DISCONNECT packet from the broker or if the on_disconnect is only + generated by the client library. + When true, the reason code is generated by the broker. + """ + + +CallbackOnConnect_v1_mqtt3 = Callable[["Client", Any, Dict[str, Any], MQTTErrorCode], None] +CallbackOnConnect_v1_mqtt5 = Callable[["Client", Any, Dict[str, Any], ReasonCode, Union[Properties, None]], None] +CallbackOnConnect_v1 = Union[CallbackOnConnect_v1_mqtt5, CallbackOnConnect_v1_mqtt3] +CallbackOnConnect_v2 = Callable[["Client", Any, ConnectFlags, ReasonCode, Union[Properties, None]], None] +CallbackOnConnect = Union[CallbackOnConnect_v1, CallbackOnConnect_v2] +CallbackOnConnectFail = Callable[["Client", Any], None] +CallbackOnDisconnect_v1_mqtt3 = Callable[["Client", Any, MQTTErrorCode], None] +CallbackOnDisconnect_v1_mqtt5 = Callable[["Client", Any, Union[ReasonCode, int, None], Union[Properties, None]], None] +CallbackOnDisconnect_v1 = Union[CallbackOnDisconnect_v1_mqtt3, CallbackOnDisconnect_v1_mqtt5] +CallbackOnDisconnect_v2 = Callable[["Client", Any, DisconnectFlags, ReasonCode, Union[Properties, None]], None] +CallbackOnDisconnect = Union[CallbackOnDisconnect_v1, CallbackOnDisconnect_v2] +CallbackOnLog = Callable[["Client", Any, int, str], None] +CallbackOnMessage = Callable[["Client", Any, "MQTTMessage"], None] +CallbackOnPreConnect = Callable[["Client", Any], None] +CallbackOnPublish_v1 = Callable[["Client", Any, int], None] +CallbackOnPublish_v2 = Callable[["Client", Any, int, ReasonCode, Properties], None] +CallbackOnPublish = Union[CallbackOnPublish_v1, CallbackOnPublish_v2] +CallbackOnSocket = Callable[["Client", Any, "SocketLike"], None] +CallbackOnSubscribe_v1_mqtt3 = Callable[["Client", Any, int, Tuple[int, ...]], None] +CallbackOnSubscribe_v1_mqtt5 = Callable[["Client", Any, int, List[ReasonCode], Properties], None] +CallbackOnSubscribe_v1 = Union[CallbackOnSubscribe_v1_mqtt3, CallbackOnSubscribe_v1_mqtt5] +CallbackOnSubscribe_v2 = Callable[["Client", Any, int, List[ReasonCode], Union[Properties, None]], None] +CallbackOnSubscribe = Union[CallbackOnSubscribe_v1, CallbackOnSubscribe_v2] +CallbackOnUnsubscribe_v1_mqtt3 = Callable[["Client", Any, int], None] +CallbackOnUnsubscribe_v1_mqtt5 = Callable[["Client", Any, int, Properties, Union[ReasonCode, List[ReasonCode]]], None] +CallbackOnUnsubscribe_v1 = Union[CallbackOnUnsubscribe_v1_mqtt3, CallbackOnUnsubscribe_v1_mqtt5] +CallbackOnUnsubscribe_v2 = Callable[["Client", Any, int, List[ReasonCode], Union[Properties, None]], None] +CallbackOnUnsubscribe = Union[CallbackOnUnsubscribe_v1, CallbackOnUnsubscribe_v2] + +# This is needed for typing because class Client redefined the name "socket" +_socket = socket + + +class WebsocketConnectionError(ConnectionError): + """ WebsocketConnectionError is a subclass of ConnectionError. + + It's raised when unable to perform the Websocket handshake. + """ pass -def error_string(mqtt_errno): +def error_string(mqtt_errno: MQTTErrorCode) -> str: """Return the error string associated with an mqtt error number.""" if mqtt_errno == MQTT_ERR_SUCCESS: return "No error." @@ -226,8 +328,11 @@ def error_string(mqtt_errno): return "Unknown error." -def connack_string(connack_code): - """Return the string associated with a CONNACK result.""" +def connack_string(connack_code: int|ReasonCode) -> str: + """Return the string associated with a CONNACK result or CONNACK reason code.""" + if isinstance(connack_code, ReasonCode): + return str(connack_code) + if connack_code == CONNACK_ACCEPTED: return "Connection Accepted." elif connack_code == CONNACK_REFUSED_PROTOCOL_VERSION: @@ -244,9 +349,69 @@ def connack_string(connack_code): return "Connection Refused: unknown reason." -def base62(num, base=string.digits + string.ascii_letters, padding=1): +def convert_connack_rc_to_reason_code(connack_code: ConnackCode) -> ReasonCode: + """Convert a MQTTv3 / MQTTv3.1.1 connack result to `ReasonCode`. + + This is used in `on_connect` callback to have a consistent API. + + Be careful that the numeric value isn't the same, for example: + + >>> ConnackCode.CONNACK_REFUSED_SERVER_UNAVAILABLE == 3 + >>> convert_connack_rc_to_reason_code(ConnackCode.CONNACK_REFUSED_SERVER_UNAVAILABLE) == 136 + + It's recommended to compare by names + + >>> code_to_test = ReasonCode(PacketTypes.CONNACK, "Server unavailable") + >>> convert_connack_rc_to_reason_code(ConnackCode.CONNACK_REFUSED_SERVER_UNAVAILABLE) == code_to_test + """ + if connack_code == ConnackCode.CONNACK_ACCEPTED: + return ReasonCode(PacketTypes.CONNACK, "Success") + if connack_code == ConnackCode.CONNACK_REFUSED_PROTOCOL_VERSION: + return ReasonCode(PacketTypes.CONNACK, "Unsupported protocol version") + if connack_code == ConnackCode.CONNACK_REFUSED_IDENTIFIER_REJECTED: + return ReasonCode(PacketTypes.CONNACK, "Client identifier not valid") + if connack_code == ConnackCode.CONNACK_REFUSED_SERVER_UNAVAILABLE: + return ReasonCode(PacketTypes.CONNACK, "Server unavailable") + if connack_code == ConnackCode.CONNACK_REFUSED_BAD_USERNAME_PASSWORD: + return ReasonCode(PacketTypes.CONNACK, "Bad user name or password") + if connack_code == ConnackCode.CONNACK_REFUSED_NOT_AUTHORIZED: + return ReasonCode(PacketTypes.CONNACK, "Not authorized") + + return ReasonCode(PacketTypes.CONNACK, "Unspecified error") + + +def convert_disconnect_error_code_to_reason_code(rc: MQTTErrorCode) -> ReasonCode: + """Convert an MQTTErrorCode to Reason code. + + This is used in `on_disconnect` callback to have a consistent API. + + Be careful that the numeric value isn't the same, for example: + + >>> MQTTErrorCode.MQTT_ERR_PROTOCOL == 2 + >>> convert_disconnect_error_code_to_reason_code(MQTTErrorCode.MQTT_ERR_PROTOCOL) == 130 + + It's recommended to compare by names + + >>> code_to_test = ReasonCode(PacketTypes.DISCONNECT, "Protocol error") + >>> convert_disconnect_error_code_to_reason_code(MQTTErrorCode.MQTT_ERR_PROTOCOL) == code_to_test + """ + if rc == MQTTErrorCode.MQTT_ERR_SUCCESS: + return ReasonCode(PacketTypes.DISCONNECT, "Success") + if rc == MQTTErrorCode.MQTT_ERR_KEEPALIVE: + return ReasonCode(PacketTypes.DISCONNECT, "Keep alive timeout") + if rc == MQTTErrorCode.MQTT_ERR_CONN_LOST: + return ReasonCode(PacketTypes.DISCONNECT, "Unspecified error") + return ReasonCode(PacketTypes.DISCONNECT, "Unspecified error") + + +def _base62( + num: int, + base: str = string.digits + string.ascii_letters, + padding: int = 1, +) -> str: """Convert a number to base-62 representation.""" - assert num >= 0 + if num < 0: + raise ValueError("Number must be positive or zero") digits = [] while num: num, rest = divmod(num, 62) @@ -255,13 +420,13 @@ def base62(num, base=string.digits + string.ascii_letters, padding=1): return ''.join(reversed(digits)) -def topic_matches_sub(sub, topic): +def topic_matches_sub(sub: str, topic: str) -> bool: """Check whether a topic matches a subscription. For example: - foo/bar would match the subscription foo/# or +/bar - non/matching would not match the subscription non/+/+ + * Topic "foo/bar" would match the subscription "foo/#" or "+/bar" + * Topic "non/matching" would not match the subscription "non/+/+" """ matcher = MQTTMatcher() matcher[sub] = True @@ -272,7 +437,7 @@ def topic_matches_sub(sub, topic): return False -def _socketpair_compat(): +def _socketpair_compat() -> tuple[socket.socket, socket.socket]: """TCP/IP socketpair including Windows support""" listensock = socket.socket( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_IP) @@ -283,43 +448,70 @@ def _socketpair_compat(): iface, port = listensock.getsockname() sock1 = socket.socket( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_IP) - sock1.setblocking(0) + sock1.setblocking(False) try: sock1.connect(("127.0.0.1", port)) except BlockingIOError: pass sock2, address = listensock.accept() - sock2.setblocking(0) + sock2.setblocking(False) listensock.close() return (sock1, sock2) -class MQTTMessageInfo(object): - """This is a class returned from Client.publish() and can be used to find +def _force_bytes(s: str | bytes) -> bytes: + if isinstance(s, str): + return s.encode("utf-8") + return s + + +def _encode_payload(payload: str | bytes | bytearray | int | float | None) -> bytes: + if isinstance(payload, str): + return payload.encode("utf-8") + + if isinstance(payload, (int, float)): + return str(payload).encode("ascii") + + if payload is None: + return b"" + + if not isinstance(payload, (bytes, bytearray)): + raise TypeError( + "payload must be a string, bytearray, int, float or None." + ) + + return payload + + +class MQTTMessageInfo: + """This is a class returned from `Client.publish()` and can be used to find out the mid of the message that was published, and to determine whether the message has been published, and/or wait until it is published. """ __slots__ = 'mid', '_published', '_condition', 'rc', '_iterpos' - def __init__(self, mid): + def __init__(self, mid: int): self.mid = mid + """ The message Id (int)""" self._published = False self._condition = threading.Condition() - self.rc = 0 + self.rc: MQTTErrorCode = MQTTErrorCode.MQTT_ERR_SUCCESS + """ The `MQTTErrorCode` that give status for this message. + This value could change until the message `is_published`""" self._iterpos = 0 - def __str__(self): + def __str__(self) -> str: return str((self.rc, self.mid)) - def __iter__(self): + def __iter__(self) -> Iterator[MQTTErrorCode | int]: self._iterpos = 0 return self - def __next__(self): + def __next__(self) -> MQTTErrorCode | int: return self.next() - def next(self): + def next(self) -> MQTTErrorCode | int: if self._iterpos == 0: self._iterpos = 1 return self.rc @@ -329,7 +521,7 @@ class MQTTMessageInfo(object): else: raise StopIteration - def __getitem__(self, index): + def __getitem__(self, index: int) -> MQTTErrorCode | int: if index == 0: return self.rc elif index == 1: @@ -337,162 +529,129 @@ class MQTTMessageInfo(object): else: raise IndexError("index out of range") - def _set_as_published(self): + def _set_as_published(self) -> None: with self._condition: self._published = True self._condition.notify() - def wait_for_publish(self, timeout=None): + def wait_for_publish(self, timeout: float | None = None) -> None: """Block until the message associated with this object is published, or until the timeout occurs. If timeout is None, this will never time out. Set timeout to a positive number of seconds, e.g. 1.2, to enable the timeout. - Raises ValueError if the message was not queued due to the outgoing - queue being full. + :raises ValueError: if the message was not queued due to the outgoing + queue being full. - Raises RuntimeError if the message was not published for another - reason. + :raises RuntimeError: if the message was not published for another + reason. """ if self.rc == MQTT_ERR_QUEUE_SIZE: raise ValueError('Message is not queued due to ERR_QUEUE_SIZE') elif self.rc == MQTT_ERR_AGAIN: pass elif self.rc > 0: - raise RuntimeError('Message publish failed: %s' % (error_string(self.rc))) + raise RuntimeError(f'Message publish failed: {error_string(self.rc)}') - timeout_time = None if timeout is None else time.time() + timeout + timeout_time = None if timeout is None else time_func() + timeout timeout_tenth = None if timeout is None else timeout / 10. - def timed_out(): - return False if timeout is None else time.time() > timeout_time + def timed_out() -> bool: + return False if timeout_time is None else time_func() > timeout_time with self._condition: while not self._published and not timed_out(): self._condition.wait(timeout_tenth) - def is_published(self): + if self.rc > 0: + raise RuntimeError(f'Message publish failed: {error_string(self.rc)}') + + def is_published(self) -> bool: """Returns True if the message associated with this object has been - published, else returns False.""" - if self.rc == MQTT_ERR_QUEUE_SIZE: + published, else returns False. + + To wait for this to become true, look at `wait_for_publish`. + """ + if self.rc == MQTTErrorCode.MQTT_ERR_QUEUE_SIZE: raise ValueError('Message is not queued due to ERR_QUEUE_SIZE') - elif self.rc == MQTT_ERR_AGAIN: + elif self.rc == MQTTErrorCode.MQTT_ERR_AGAIN: pass elif self.rc > 0: - raise RuntimeError('Message publish failed: %s' % (error_string(self.rc))) + raise RuntimeError(f'Message publish failed: {error_string(self.rc)}') with self._condition: return self._published -class MQTTMessage(object): - """ This is a class that describes an incoming or outgoing message. It is - passed to the on_message callback as the message parameter. - - Members: - - topic : String. topic that the message was published on. - payload : Bytes/Byte array. the message payload. - qos : Integer. The message Quality of Service 0, 1 or 2. - retain : Boolean. If true, the message is a retained message and not fresh. - mid : Integer. The message id. - properties: Properties class. In MQTT v5.0, the properties associated with the message. +class MQTTMessage: + """ This is a class that describes an incoming message. It is + passed to the `on_message` callback as the message parameter. """ - __slots__ = 'timestamp', 'state', 'dup', 'mid', '_topic', 'payload', 'qos', 'retain', 'info', 'properties' - def __init__(self, mid=0, topic=b""): - self.timestamp = 0 + def __init__(self, mid: int = 0, topic: bytes = b""): + self.timestamp = 0.0 self.state = mqtt_ms_invalid self.dup = False self.mid = mid + """ The message id (int).""" self._topic = topic self.payload = b"" + """the message payload (bytes)""" self.qos = 0 + """ The message Quality of Service (0, 1 or 2).""" self.retain = False + """ If true, the message is a retained message and not fresh.""" self.info = MQTTMessageInfo(mid) + self.properties: Properties | None = None + """ In MQTT v5.0, the properties associated with the message. (`Properties`)""" - def __eq__(self, other): + def __eq__(self, other: object) -> bool: """Override the default Equals behavior""" if isinstance(other, self.__class__): return self.mid == other.mid return False - def __ne__(self, other): + def __ne__(self, other: object) -> bool: """Define a non-equality test""" return not self.__eq__(other) @property - def topic(self): + def topic(self) -> str: + """topic that the message was published on. + + This property is read-only. + """ return self._topic.decode('utf-8') @topic.setter - def topic(self, value): + def topic(self, value: bytes) -> None: self._topic = value -class Client(object): +class Client: """MQTT version 3.1/3.1.1/5.0 client class. This is the main class for use communicating with an MQTT broker. General usage flow: - * Use connect()/connect_async() to connect to a broker - * Call loop() frequently to maintain network traffic flow with the broker - * Or use loop_start() to set a thread running to call loop() for you. - * Or use loop_forever() to handle calling loop() for you in a blocking - * function. - * Use subscribe() to subscribe to a topic and receive messages - * Use publish() to send messages - * Use disconnect() to disconnect from the broker + * Use `connect()`, `connect_async()` or `connect_srv()` to connect to a broker + * Use `loop_start()` to set a thread running to call `loop()` for you. + * Or use `loop_forever()` to handle calling `loop()` for you in a blocking function. + * Or call `loop()` frequently to maintain network traffic flow with the broker + * Use `subscribe()` to subscribe to a topic and receive messages + * Use `publish()` to send messages + * Use `disconnect()` to disconnect from the broker Data returned from the broker is made available with the use of callback functions as described below. - Callbacks - ========= + :param CallbackAPIVersion callback_api_version: define the API version for user-callback (on_connect, on_publish,...). + This field is required and it's recommended to use the latest version (CallbackAPIVersion.API_VERSION2). + See each callback for description of API for each version. The file migrations.md contains details on + how to migrate between version. - A number of callback functions are available to receive data back from the - broker. To use a callback, define a function and then assign it to the - client: - - def on_connect(client, userdata, flags, rc): - print("Connection returned " + str(rc)) - - client.on_connect = on_connect - - Callbacks can also be attached using decorators: - - client = paho.mqtt.Client() - - @client.connect_callback() - def on_connect(client, userdata, flags, rc): - print("Connection returned " + str(rc)) - - - **IMPORTANT** the required function signature for a callback can differ - depending on whether you are using MQTT v5 or MQTT v3.1.1/v3.1. See the - documentation for each callback. - - All of the callbacks as described below have a "client" and an "userdata" - argument. "client" is the Client instance that is calling the callback. - "userdata" is user data of any type and can be set when creating a new client - instance or with user_data_set(userdata). - - If you wish to suppress exceptions within a callback, you should set - `client.suppress_exceptions = True` - - The callbacks are listed below, documentation for each of them can be found - at the same function name: - - on_connect, on_connect_fail, on_disconnect, on_message, on_publish, - on_subscribe, on_unsubscribe, on_log, on_socket_open, on_socket_close, - on_socket_register_write, on_socket_unregister_write - """ - - def __init__(self, client_id="", clean_session=None, userdata=None, - protocol=MQTTv311, transport="tcp", reconnect_on_failure=True): - """client_id is the unique client id string used when connecting to the + :param str client_id: the unique client id string used when connecting to the broker. If client_id is zero length or None, then the behaviour is defined by which protocol version is in use. If using MQTT v3.1.1, then a zero length client id will be sent to the broker and the broker will @@ -500,7 +659,7 @@ class Client(object): randomly generated. In both cases, clean_session must be True. If this is not the case a ValueError will be raised. - clean_session is a boolean that determines the client type. If True, + :param bool clean_session: a boolean that determines the client type. If True, the broker will remove all information about this client when it disconnects. If False, the client is a persistent client and subscription information and queued messages will be retained when the @@ -512,30 +671,105 @@ class Client(object): It is not accepted if the MQTT version is v5.0 - use the clean_start argument on connect() instead. - userdata is user defined data of any type that is passed as the "userdata" + :param userdata: user defined data of any type that is passed as the "userdata" parameter to callbacks. It may be updated at a later point with the user_data_set() function. - The protocol argument allows explicit setting of the MQTT version to + :param int protocol: allows explicit setting of the MQTT version to use for this client. Can be paho.mqtt.client.MQTTv311 (v3.1.1), paho.mqtt.client.MQTTv31 (v3.1) or paho.mqtt.client.MQTTv5 (v5.0), with the default being v3.1.1. - Set transport to "websockets" to use WebSockets as the transport + :param transport: use "websockets" to use WebSockets as the transport mechanism. Set to "tcp" to use raw TCP, which is the default. - """ - if transport.lower() not in ('websockets', 'tcp'): + :param bool manual_ack: normally, when a message is received, the library automatically + acknowledges after on_message callback returns. manual_ack=True allows the application to + acknowledge receipt after it has completed processing of a message + using a the ack() method. This addresses vulnerability to message loss + if applications fails while processing a message, or while it pending + locally. + + Callbacks + ========= + + A number of callback functions are available to receive data back from the + broker. To use a callback, define a function and then assign it to the + client:: + + def on_connect(client, userdata, flags, reason_code, properties): + print(f"Connected with result code {reason_code}") + + client.on_connect = on_connect + + Callbacks can also be attached using decorators:: + + mqttc = paho.mqtt.Client() + + @mqttc.connect_callback() + def on_connect(client, userdata, flags, reason_code, properties): + print(f"Connected with result code {reason_code}") + + All of the callbacks as described below have a "client" and an "userdata" + argument. "client" is the `Client` instance that is calling the callback. + userdata" is user data of any type and can be set when creating a new client + instance or with `user_data_set()`. + + If you wish to suppress exceptions within a callback, you should set + ``mqttc.suppress_exceptions = True`` + + The callbacks are listed below, documentation for each of them can be found + at the same function name: + + `on_connect`, `on_connect_fail`, `on_disconnect`, `on_message`, `on_publish`, + `on_subscribe`, `on_unsubscribe`, `on_log`, `on_socket_open`, `on_socket_close`, + `on_socket_register_write`, `on_socket_unregister_write` + """ + + def __init__( + self, + callback_api_version: CallbackAPIVersion, + client_id: str = "", + clean_session: bool | None = None, + userdata: Any = None, + protocol: int = MQTTv311, + transport: Literal["tcp", "websockets"] = "tcp", + reconnect_on_failure: bool = True, + manual_ack: bool = False, + ) -> None: + transport = transport.lower() # type: ignore + if transport not in ("websockets", "tcp"): raise ValueError( - 'transport must be "websockets" or "tcp", not %s' % transport) - self._transport = transport.lower() + f'transport must be "websockets" or "tcp", not {transport}') + + self._manual_ack = manual_ack + self._transport = transport self._protocol = protocol self._userdata = userdata - self._sock = None - self._sockpairR, self._sockpairW = (None, None,) + self._sock: SocketLike | None = None + self._sockpairR: socket.socket | None = None + self._sockpairW: socket.socket | None = None self._keepalive = 60 self._connect_timeout = 5.0 self._client_mode = MQTT_CLIENT + self._callback_api_version = callback_api_version + + if self._callback_api_version == CallbackAPIVersion.VERSION1: + warnings.warn( + "Callback API version 1 is deprecated, update to latest version", + category=DeprecationWarning, + stacklevel=2, + ) + if isinstance(self._callback_api_version, str): + # Help user to migrate, it probably provided a client id + # as first arguments + raise ValueError( + "Unsupported callback API version: version 2.0 added a callback_api_version, see migrations.md for details" + ) + if self._callback_api_version not in CallbackAPIVersion: + raise ValueError("Unsupported callback API version") + + self._clean_start: int = MQTT_CLEAN_START_FIRST_ONLY if protocol == MQTTv5: if clean_session is not None: @@ -551,17 +785,15 @@ class Client(object): # [MQTT-3.1.3-4] Client Id must be UTF-8 encoded string. if client_id == "" or client_id is None: if protocol == MQTTv31: - self._client_id = base62(uuid.uuid4().int, padding=22) + self._client_id = _base62(uuid.uuid4().int, padding=22).encode("utf8") else: self._client_id = b"" else: - self._client_id = client_id - if isinstance(self._client_id, unicode): - self._client_id = self._client_id.encode('utf-8') + self._client_id = _force_bytes(client_id) - self._username = None - self._password = None - self._in_packet = { + self._username: bytes | None = None + self._password: bytes | None = None + self._in_packet: _InPacket = { "command": 0, "have_remaining": 0, "remaining_count": [], @@ -569,24 +801,29 @@ class Client(object): "remaining_length": 0, "packet": bytearray(b""), "to_process": 0, - "pos": 0} - self._out_packet = collections.deque() + "pos": 0, + } + self._out_packet: collections.deque[_OutPacket] = collections.deque() self._last_msg_in = time_func() self._last_msg_out = time_func() self._reconnect_min_delay = 1 self._reconnect_max_delay = 120 - self._reconnect_delay = None + self._reconnect_delay: int | None = None self._reconnect_on_failure = reconnect_on_failure - self._ping_t = 0 + self._ping_t = 0.0 self._last_mid = 0 - self._state = mqtt_cs_new - self._out_messages = collections.OrderedDict() - self._in_messages = collections.OrderedDict() + self._state = _ConnectionState.MQTT_CS_NEW + self._out_messages: collections.OrderedDict[ + int, MQTTMessage + ] = collections.OrderedDict() + self._in_messages: collections.OrderedDict[ + int, MQTTMessage + ] = collections.OrderedDict() self._max_inflight_messages = 20 self._inflight_messages = 0 self._max_queued_messages = 0 - self._connect_properties = None - self._will_properties = None + self._connect_properties: Properties | None = None + self._will_properties: Properties | None = None self._will = False self._will_topic = b"" self._will_payload = b"" @@ -597,7 +834,7 @@ class Client(object): self._port = 1883 self._bind_address = "" self._bind_port = 0 - self._proxy = {} + self._proxy: Any = {} self._in_callback_mutex = threading.Lock() self._callback_mutex = threading.RLock() self._msgtime_mutex = threading.Lock() @@ -605,58 +842,279 @@ class Client(object): self._in_message_mutex = threading.Lock() self._reconnect_delay_mutex = threading.Lock() self._mid_generate_mutex = threading.Lock() - self._thread = None + self._thread: threading.Thread | None = None self._thread_terminate = False self._ssl = False - self._ssl_context = None + self._ssl_context: ssl.SSLContext | None = None # Only used when SSL context does not have check_hostname attribute self._tls_insecure = False - self._logger = None + self._logger: logging.Logger | None = None self._registered_write = False # No default callbacks - self._on_log = None - self._on_connect = None - self._on_connect_fail = None - self._on_subscribe = None - self._on_message = None - self._on_publish = None - self._on_unsubscribe = None - self._on_disconnect = None - self._on_socket_open = None - self._on_socket_close = None - self._on_socket_register_write = None - self._on_socket_unregister_write = None + self._on_log: CallbackOnLog | None = None + self._on_pre_connect: CallbackOnPreConnect | None = None + self._on_connect: CallbackOnConnect | None = None + self._on_connect_fail: CallbackOnConnectFail | None = None + self._on_subscribe: CallbackOnSubscribe | None = None + self._on_message: CallbackOnMessage | None = None + self._on_publish: CallbackOnPublish | None = None + self._on_unsubscribe: CallbackOnUnsubscribe | None = None + self._on_disconnect: CallbackOnDisconnect | None = None + self._on_socket_open: CallbackOnSocket | None = None + self._on_socket_close: CallbackOnSocket | None = None + self._on_socket_register_write: CallbackOnSocket | None = None + self._on_socket_unregister_write: CallbackOnSocket | None = None self._websocket_path = "/mqtt" - self._websocket_extra_headers = None + self._websocket_extra_headers: WebSocketHeaders | None = None # for clean_start == MQTT_CLEAN_START_FIRST_ONLY self._mqttv5_first_connect = True self.suppress_exceptions = False # For callbacks - def __del__(self): + def __del__(self) -> None: self._reset_sockets() - def _sock_recv(self, bufsize): + @property + def host(self) -> str: + """ + Host to connect to. If `connect()` hasn't been called yet, returns an empty string. + + This property may not be changed if the connection is already open. + """ + return self._host + + @host.setter + def host(self, value: str) -> None: + if not self._connection_closed(): + raise RuntimeError("updating host on established connection is not supported") + + if not value: + raise ValueError("Invalid host.") + self._host = value + + @property + def port(self) -> int: + """ + Broker TCP port to connect to. + + This property may not be changed if the connection is already open. + """ + return self._port + + @port.setter + def port(self, value: int) -> None: + if not self._connection_closed(): + raise RuntimeError("updating port on established connection is not supported") + + if value <= 0: + raise ValueError("Invalid port number.") + self._port = value + + @property + def keepalive(self) -> int: + """ + Client keepalive interval (in seconds). + + This property may not be changed if the connection is already open. + """ + return self._keepalive + + @keepalive.setter + def keepalive(self, value: int) -> None: + if not self._connection_closed(): + # The issue here is that the previous value of keepalive matter to possibly + # sent ping packet. + raise RuntimeError("updating keepalive on established connection is not supported") + + if value < 0: + raise ValueError("Keepalive must be >=0.") + + self._keepalive = value + + @property + def transport(self) -> Literal["tcp", "websockets"]: + """ + Transport method used for the connection ("tcp" or "websockets"). + + This property may not be changed if the connection is already open. + """ + return self._transport + + @transport.setter + def transport(self, value: Literal["tcp", "websockets"]) -> None: + if not self._connection_closed(): + raise RuntimeError("updating transport on established connection is not supported") + + self._transport = value + + @property + def protocol(self) -> MQTTProtocolVersion: + """ + Protocol version used (MQTT v3, MQTT v3.11, MQTTv5) + + This property is read-only. + """ + return self.protocol + + @property + def connect_timeout(self) -> float: + """ + Connection establishment timeout in seconds. + + This property may not be changed if the connection is already open. + """ + return self._connect_timeout + + @connect_timeout.setter + def connect_timeout(self, value: float) -> None: + if not self._connection_closed(): + raise RuntimeError("updating connect_timeout on established connection is not supported") + + if value <= 0.0: + raise ValueError("timeout must be a positive number") + + self._connect_timeout = value + + @property + def username(self) -> str | None: + """The username used to connect to the MQTT broker, or None if no username is used. + + This property may not be changed if the connection is already open. + """ + if self._username is None: + return None + return self._username.decode("utf-8") + + @username.setter + def username(self, value: str | None) -> None: + if not self._connection_closed(): + raise RuntimeError("updating username on established connection is not supported") + + if value is None: + self._username = None + else: + self._username = value.encode("utf-8") + + @property + def password(self) -> str | None: + """The password used to connect to the MQTT broker, or None if no password is used. + + This property may not be changed if the connection is already open. + """ + if self._password is None: + return None + return self._password.decode("utf-8") + + @password.setter + def password(self, value: str | None) -> None: + if not self._connection_closed(): + raise RuntimeError("updating password on established connection is not supported") + + if value is None: + self._password = None + else: + self._password = value.encode("utf-8") + + @property + def max_inflight_messages(self) -> int: + """ + Maximum number of messages with QoS > 0 that can be partway through the network flow at once + + This property may not be changed if the connection is already open. + """ + return self._max_inflight_messages + + @max_inflight_messages.setter + def max_inflight_messages(self, value: int) -> None: + if not self._connection_closed(): + # Not tested. Some doubt that everything is okay when max_inflight change between 0 + # and > 0 value because _update_inflight is skipped when _max_inflight_messages == 0 + raise RuntimeError("updating max_inflight_messages on established connection is not supported") + + if value < 0: + raise ValueError("Invalid inflight.") + + self._max_inflight_messages = value + + @property + def max_queued_messages(self) -> int: + """ + Maximum number of message in the outgoing message queue, 0 means unlimited + + This property may not be changed if the connection is already open. + """ + return self._max_queued_messages + + @max_queued_messages.setter + def max_queued_messages(self, value: int) -> None: + if not self._connection_closed(): + # Not tested. + raise RuntimeError("updating max_queued_messages on established connection is not supported") + + if value < 0: + raise ValueError("Invalid queue size.") + + self._max_queued_messages = value + + @property + def will_topic(self) -> str | None: + """ + The topic name a will message is sent to when disconnecting unexpectedly. None if a will shall not be sent. + + This property is read-only. Use `will_set()` to change its value. + """ + if self._will_topic is None: + return None + + return self._will_topic.decode("utf-8") + + @property + def will_payload(self) -> bytes | None: + """ + The payload for the will message that is sent when disconnecting unexpectedly. None if a will shall not be sent. + + This property is read-only. Use `will_set()` to change its value. + """ + return self._will_payload + + @property + def logger(self) -> logging.Logger | None: + return self._logger + + @logger.setter + def logger(self, value: logging.Logger | None) -> None: + self._logger = value + + def _sock_recv(self, bufsize: int) -> bytes: + if self._sock is None: + raise ConnectionError("self._sock is None") try: return self._sock.recv(bufsize) - except ssl.SSLWantReadError: - raise BlockingIOError - except ssl.SSLWantWriteError: + except ssl.SSLWantReadError as err: + raise BlockingIOError() from err + except ssl.SSLWantWriteError as err: self._call_socket_register_write() - raise BlockingIOError + raise BlockingIOError() from err + except AttributeError as err: + self._easy_log( + MQTT_LOG_DEBUG, "socket was None: %s", err) + raise ConnectionError() from err + + def _sock_send(self, buf: bytes) -> int: + if self._sock is None: + raise ConnectionError("self._sock is None") - def _sock_send(self, buf): try: return self._sock.send(buf) - except ssl.SSLWantReadError: - raise BlockingIOError - except ssl.SSLWantWriteError: + except ssl.SSLWantReadError as err: + raise BlockingIOError() from err + except ssl.SSLWantWriteError as err: self._call_socket_register_write() - raise BlockingIOError - except BlockingIOError: + raise BlockingIOError() from err + except BlockingIOError as err: self._call_socket_register_write() - raise BlockingIOError + raise BlockingIOError() from err - def _sock_close(self): + def _sock_close(self) -> None: """Close the connection to the server.""" if not self._sock: return @@ -670,8 +1128,8 @@ class Client(object): # In case a callback fails, still close the socket to avoid leaking the file descriptor. sock.close() - def _reset_sockets(self, sockpair_only=False): - if sockpair_only == False: + def _reset_sockets(self, sockpair_only: bool = False) -> None: + if not sockpair_only: self._sock_close() if self._sockpairR: @@ -681,21 +1139,30 @@ class Client(object): self._sockpairW.close() self._sockpairW = None - def reinitialise(self, client_id="", clean_session=True, userdata=None): + def reinitialise( + self, + client_id: str = "", + clean_session: bool = True, + userdata: Any = None, + ) -> None: self._reset_sockets() - self.__init__(client_id, clean_session, userdata) + self.__init__(client_id, clean_session, userdata) # type: ignore[misc] - def ws_set_options(self, path="/mqtt", headers=None): + def ws_set_options( + self, + path: str = "/mqtt", + headers: WebSocketHeaders | None = None, + ) -> None: """ Set the path and headers for a websocket connection - path is a string starting with / which should be the endpoint of the - mqtt connection on the remote server + :param str path: a string starting with / which should be the endpoint of the + mqtt connection on the remote server - headers can be either a dict or a callable object. If it is a dict then - the extra items in the dict are added to the websocket headers. If it is - a callable, then the default websocket headers are passed into this - function and the result is used as the new headers. + :param headers: can be either a dict or a callable object. If it is a dict then + the extra items in the dict are added to the websocket headers. If it is + a callable, then the default websocket headers are passed into this + function and the result is used as the new headers. """ self._websocket_path = path @@ -706,24 +1173,21 @@ class Client(object): raise ValueError( "'headers' option to ws_set_options has to be either a dictionary or callable") - def tls_set_context(self, context=None): + def tls_set_context( + self, + context: ssl.SSLContext | None = None, + ) -> None: """Configure network encryption and authentication context. Enables SSL/TLS support. - context : an ssl.SSLContext object. By default this is given by - `ssl.create_default_context()`, if available. + :param context: an ssl.SSLContext object. By default this is given by + ``ssl.create_default_context()``, if available. - Must be called before connect() or connect_async().""" + Must be called before `connect()`, `connect_async()` or `connect_srv()`.""" if self._ssl_context is not None: raise ValueError('SSL/TLS has already been configured.') - # Assume that have SSL support, or at least that context input behaves like ssl.SSLContext - # in current versions of Python - if context is None: - if hasattr(ssl, 'create_default_context'): - context = ssl.create_default_context() - else: - raise ValueError('SSL/TLS context must be specified') + context = ssl.create_default_context() self._ssl = True self._ssl_context = context @@ -732,46 +1196,59 @@ class Client(object): if hasattr(context, 'check_hostname'): self._tls_insecure = not context.check_hostname - def tls_set(self, ca_certs=None, certfile=None, keyfile=None, cert_reqs=None, tls_version=None, ciphers=None, keyfile_password=None): + def tls_set( + self, + ca_certs: str | None = None, + certfile: str | None = None, + keyfile: str | None = None, + cert_reqs: ssl.VerifyMode | None = None, + tls_version: int | None = None, + ciphers: str | None = None, + keyfile_password: str | None = None, + alpn_protocols: list[str] | None = None, + ) -> None: """Configure network encryption and authentication options. Enables SSL/TLS support. - ca_certs : a string path to the Certificate Authority certificate files - that are to be treated as trusted by this client. If this is the only - option given then the client will operate in a similar manner to a web - browser. That is to say it will require the broker to have a - certificate signed by the Certificate Authorities in ca_certs and will - communicate using TLS v1,2, but will not attempt any form of - authentication. This provides basic network encryption but may not be - sufficient depending on how the broker is configured. - By default, on Python 2.7.9+ or 3.4+, the default certification - authority of the system is used. On older Python version this parameter - is mandatory. + :param str ca_certs: a string path to the Certificate Authority certificate files + that are to be treated as trusted by this client. If this is the only + option given then the client will operate in a similar manner to a web + browser. That is to say it will require the broker to have a + certificate signed by the Certificate Authorities in ca_certs and will + communicate using TLS v1,2, but will not attempt any form of + authentication. This provides basic network encryption but may not be + sufficient depending on how the broker is configured. - certfile and keyfile are strings pointing to the PEM encoded client - certificate and private keys respectively. If these arguments are not - None then they will be used as client information for TLS based - authentication. Support for this feature is broker dependent. Note - that if either of these files in encrypted and needs a password to - decrypt it, then this can be passed using the keyfile_password - argument - you should take precautions to ensure that your password is - not hard coded into your program by loading the password from a file - for example. If you do not provide keyfile_password, the password will - be requested to be typed in at a terminal window. + By default, on Python 2.7.9+ or 3.4+, the default certification + authority of the system is used. On older Python version this parameter + is mandatory. + :param str certfile: PEM encoded client certificate filename. Used with + keyfile for client TLS based authentication. Support for this feature is + broker dependent. Note that if the files in encrypted and needs a password to + decrypt it, then this can be passed using the keyfile_password argument - you + should take precautions to ensure that your password is + not hard coded into your program by loading the password from a file + for example. If you do not provide keyfile_password, the password will + be requested to be typed in at a terminal window. + :param str keyfile: PEM encoded client private keys filename. Used with + certfile for client TLS based authentication. Support for this feature is + broker dependent. Note that if the files in encrypted and needs a password to + decrypt it, then this can be passed using the keyfile_password argument - you + should take precautions to ensure that your password is + not hard coded into your program by loading the password from a file + for example. If you do not provide keyfile_password, the password will + be requested to be typed in at a terminal window. + :param cert_reqs: the certificate requirements that the client imposes + on the broker to be changed. By default this is ssl.CERT_REQUIRED, + which means that the broker must provide a certificate. See the ssl + pydoc for more information on this parameter. + :param tls_version: the version of the SSL/TLS protocol used to be + specified. By default TLS v1.2 is used. Previous versions are allowed + but not recommended due to possible security problems. + :param str ciphers: encryption ciphers that are allowed + for this connection, or None to use the defaults. See the ssl pydoc for + more information. - cert_reqs allows the certificate requirements that the client imposes - on the broker to be changed. By default this is ssl.CERT_REQUIRED, - which means that the broker must provide a certificate. See the ssl - pydoc for more information on this parameter. - - tls_version allows the version of the SSL/TLS protocol used to be - specified. By default TLS v1.2 is used. Previous versions are allowed - but not recommended due to possible security problems. - - ciphers is a string specifying which encryption ciphers are allowable - for this connection, or None to use the defaults. See the ssl pydoc for - more information. - - Must be called before connect() or connect_async().""" + Must be called before `connect()`, `connect_async()` or `connect_srv()`.""" if ssl is None: raise ValueError('This platform has no SSL/TLS.') @@ -787,11 +1264,17 @@ class Client(object): if tls_version is None: tls_version = ssl.PROTOCOL_TLSv1_2 # If the python version supports it, use highest TLS version automatically - if hasattr(ssl, "PROTOCOL_TLS"): + if hasattr(ssl, "PROTOCOL_TLS_CLIENT"): + # This also enables CERT_REQUIRED and check_hostname by default. + tls_version = ssl.PROTOCOL_TLS_CLIENT + elif hasattr(ssl, "PROTOCOL_TLS"): tls_version = ssl.PROTOCOL_TLS context = ssl.SSLContext(tls_version) # Configure context + if ciphers is not None: + context.set_ciphers(ciphers) + if certfile is not None: context.load_cert_chain(certfile, keyfile, keyfile_password) @@ -805,8 +1288,10 @@ class Client(object): else: context.load_default_certs() - if ciphers is not None: - context.set_ciphers(ciphers) + if alpn_protocols is not None: + if not getattr(ssl, "HAS_ALPN", None): + raise ValueError("SSL library has no support for ALPN") + context.set_alpn_protocols(alpn_protocols) self.tls_set_context(context) @@ -818,7 +1303,7 @@ class Client(object): # But with ssl.CERT_NONE, we can not check_hostname self.tls_insecure_set(True) - def tls_insecure_set(self, value): + def tls_insecure_set(self, value: bool) -> None: """Configure verification of the server hostname in the server certificate. If value is set to true, it is impossible to guarantee that the host @@ -830,8 +1315,8 @@ class Client(object): Do not use this function in a real system. Setting value to true means there is no point using encryption. - Must be called before connect() and after either tls_set() or - tls_set_context().""" + Must be called before `connect()` and after either `tls_set()` or + `tls_set_context()`.""" if self._ssl_context is None: raise ValueError( @@ -845,7 +1330,7 @@ class Client(object): # If verify_mode is CERT_NONE then the host name will never be checked self._ssl_context.check_hostname = not value - def proxy_set(self, **proxy_args): + def proxy_set(self, **proxy_args: Any) -> None: """Configure proxying of MQTT connection. Enables support for SOCKS or HTTP proxies. @@ -853,16 +1338,23 @@ class Client(object): proxy_args parameters are below; see the PySocks docs for more info. (Required) - proxy_type: One of {socks.HTTP, socks.SOCKS4, or socks.SOCKS5} - proxy_addr: IP address or DNS name of proxy server + + :param proxy_type: One of {socks.HTTP, socks.SOCKS4, or socks.SOCKS5} + :param proxy_addr: IP address or DNS name of proxy server (Optional) - proxy_rdns: boolean indicating whether proxy lookup should be performed - remotely (True, default) or locally (False) - proxy_username: username for SOCKS5 proxy, or userid for SOCKS4 proxy - proxy_password: password for SOCKS5 proxy - Must be called before connect() or connect_async().""" + :param proxy_port: (int) port number of the proxy server. If not provided, + the PySocks package default value will be utilized, which differs by proxy_type. + :param proxy_rdns: boolean indicating whether proxy lookup should be performed + remotely (True, default) or locally (False) + :param proxy_username: username for SOCKS5 proxy, or userid for SOCKS4 proxy + :param proxy_password: password for SOCKS5 proxy + + Example:: + + mqttc.proxy_set(proxy_type=socks.HTTP, proxy_addr='1.2.3.4', proxy_port=4231) + """ if socks is None: raise ValueError("PySocks must be installed for proxy support.") elif not self._proxy_is_valid(proxy_args): @@ -870,35 +1362,58 @@ class Client(object): else: self._proxy = proxy_args - def enable_logger(self, logger=None): - """ Enables a logger to send log messages to """ + def enable_logger(self, logger: logging.Logger | None = None) -> None: + """ + Enables a logger to send log messages to + + :param logging.Logger logger: if specified, that ``logging.Logger`` object will be used, otherwise + one will be created automatically. + + See `disable_logger` to undo this action. + """ if logger is None: if self._logger is not None: # Do not replace existing logger return logger = logging.getLogger(__name__) - self._logger = logger + self.logger = logger - def disable_logger(self): + def disable_logger(self) -> None: + """ + Disable logging using standard python logging package. This has no effect on the `on_log` callback. + """ self._logger = None - def connect(self, host, port=1883, keepalive=60, bind_address="", bind_port=0, - clean_start=MQTT_CLEAN_START_FIRST_ONLY, properties=None): - """Connect to a remote broker. + def connect( + self, + host: str, + port: int = 1883, + keepalive: int = 60, + bind_address: str = "", + bind_port: int = 0, + clean_start: CleanStartOption = MQTT_CLEAN_START_FIRST_ONLY, + properties: Properties | None = None, + ) -> MQTTErrorCode: + """Connect to a remote broker. This is a blocking call that establishes + the underlying connection and transmits a CONNECT packet. + Note that the connection status will not be updated until a CONNACK is received and + processed (this requires a running network loop, see `loop_start`, `loop_forever`, `loop`...). - host is the hostname or IP address of the remote broker. - port is the network port of the server host to connect to. Defaults to - 1883. Note that the default port for MQTT over SSL/TLS is 8883 so if you - are using tls_set() the port may need providing. - keepalive: Maximum period in seconds between communications with the - broker. If no other messages are being exchanged, this controls the - rate at which the client will send ping messages to the broker. - clean_start: (MQTT v5.0 only) True, False or MQTT_CLEAN_START_FIRST_ONLY. - Sets the MQTT v5.0 clean_start flag always, never or on the first successful connect only, - respectively. MQTT session data (such as outstanding messages and subscriptions) - is cleared on successful connect when the clean_start flag is set. - properties: (MQTT v5.0 only) the MQTT v5.0 properties to be sent in the - MQTT connect packet. + :param str host: the hostname or IP address of the remote broker. + :param int port: the network port of the server host to connect to. Defaults to + 1883. Note that the default port for MQTT over SSL/TLS is 8883 so if you + are using `tls_set()` the port may need providing. + :param int keepalive: Maximum period in seconds between communications with the + broker. If no other messages are being exchanged, this controls the + rate at which the client will send ping messages to the broker. + :param bool clean_start: (MQTT v5.0 only) True, False or MQTT_CLEAN_START_FIRST_ONLY. + Sets the MQTT v5.0 clean_start flag always, never or on the first successful connect only, + respectively. MQTT session data (such as outstanding messages and subscriptions) + is cleared on successful connect when the clean_start flag is set. + For MQTT v3.1.1, the ``clean_session`` argument of `Client` should be used for similar + result. + :param Properties properties: (MQTT v5.0 only) the MQTT v5.0 properties to be sent in the + MQTT connect packet. """ if self._protocol == MQTTv5: @@ -906,97 +1421,110 @@ class Client(object): else: if clean_start != MQTT_CLEAN_START_FIRST_ONLY: raise ValueError("Clean start only applies to MQTT V5") - if properties != None: + if properties: raise ValueError("Properties only apply to MQTT V5") self.connect_async(host, port, keepalive, bind_address, bind_port, clean_start, properties) return self.reconnect() - def connect_srv(self, domain=None, keepalive=60, bind_address="", - clean_start=MQTT_CLEAN_START_FIRST_ONLY, properties=None): + def connect_srv( + self, + domain: str | None = None, + keepalive: int = 60, + bind_address: str = "", + bind_port: int = 0, + clean_start: CleanStartOption = MQTT_CLEAN_START_FIRST_ONLY, + properties: Properties | None = None, + ) -> MQTTErrorCode: """Connect to a remote broker. - domain is the DNS domain to search for SRV records; if None, - try to determine local domain name. - keepalive, bind_address, clean_start and properties are as for connect() + :param str domain: the DNS domain to search for SRV records; if None, + try to determine local domain name. + :param keepalive, bind_address, clean_start and properties: see `connect()` """ if HAVE_DNS is False: raise ValueError( - 'No DNS resolver library found, try "pip install dnspython" or "pip3 install dnspython3".') + 'No DNS resolver library found, try "pip install dnspython".') if domain is None: domain = socket.getfqdn() domain = domain[domain.find('.') + 1:] try: - rr = '_mqtt._tcp.%s' % domain + rr = f'_mqtt._tcp.{domain}' if self._ssl: # IANA specifies secure-mqtt (not mqtts) for port 8883 - rr = '_secure-mqtt._tcp.%s' % domain + rr = f'_secure-mqtt._tcp.{domain}' answers = [] for answer in dns.resolver.query(rr, dns.rdatatype.SRV): addr = answer.target.to_text()[:-1] answers.append( (addr, answer.port, answer.priority, answer.weight)) - except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.NoNameservers): - raise ValueError("No answer/NXDOMAIN for SRV in %s" % (domain)) + except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.NoNameservers) as err: + raise ValueError(f"No answer/NXDOMAIN for SRV in {domain}") from err # FIXME: doesn't account for weight for answer in answers: host, port, prio, weight = answer try: - return self.connect(host, port, keepalive, bind_address, clean_start, properties) - except Exception: + return self.connect(host, port, keepalive, bind_address, bind_port, clean_start, properties) + except Exception: # noqa: S110 pass raise ValueError("No SRV hosts responded") - def connect_async(self, host, port=1883, keepalive=60, bind_address="", bind_port=0, - clean_start=MQTT_CLEAN_START_FIRST_ONLY, properties=None): + def connect_async( + self, + host: str, + port: int = 1883, + keepalive: int = 60, + bind_address: str = "", + bind_port: int = 0, + clean_start: CleanStartOption = MQTT_CLEAN_START_FIRST_ONLY, + properties: Properties | None = None, + ) -> None: """Connect to a remote broker asynchronously. This is a non-blocking - connect call that can be used with loop_start() to provide very quick + connect call that can be used with `loop_start()` to provide very quick start. - host is the hostname or IP address of the remote broker. - port is the network port of the server host to connect to. Defaults to - 1883. Note that the default port for MQTT over SSL/TLS is 8883 so if you - are using tls_set() the port may need providing. - keepalive: Maximum period in seconds between communications with the - broker. If no other messages are being exchanged, this controls the - rate at which the client will send ping messages to the broker. - clean_start: (MQTT v5.0 only) True, False or MQTT_CLEAN_START_FIRST_ONLY. - Sets the MQTT v5.0 clean_start flag always, never or on the first successful connect only, - respectively. MQTT session data (such as outstanding messages and subscriptions) - is cleared on successful connect when the clean_start flag is set. - properties: (MQTT v5.0 only) the MQTT v5.0 properties to be sent in the - MQTT connect packet. Use the Properties class. + Any already established connection will be terminated immediately. + + :param str host: the hostname or IP address of the remote broker. + :param int port: the network port of the server host to connect to. Defaults to + 1883. Note that the default port for MQTT over SSL/TLS is 8883 so if you + are using `tls_set()` the port may need providing. + :param int keepalive: Maximum period in seconds between communications with the + broker. If no other messages are being exchanged, this controls the + rate at which the client will send ping messages to the broker. + :param bool clean_start: (MQTT v5.0 only) True, False or MQTT_CLEAN_START_FIRST_ONLY. + Sets the MQTT v5.0 clean_start flag always, never or on the first successful connect only, + respectively. MQTT session data (such as outstanding messages and subscriptions) + is cleared on successful connect when the clean_start flag is set. + For MQTT v3.1.1, the ``clean_session`` argument of `Client` should be used for similar + result. + :param Properties properties: (MQTT v5.0 only) the MQTT v5.0 properties to be sent in the + MQTT connect packet. """ - if host is None or len(host) == 0: - raise ValueError('Invalid host.') - if port <= 0: - raise ValueError('Invalid port number.') - if keepalive < 0: - raise ValueError('Keepalive must be >=0.') - if bind_address != "" and bind_address is not None: - if sys.version_info < (2, 7) or (3, 0) < sys.version_info < (3, 2): - raise ValueError('bind_address requires Python 2.7 or 3.2.') if bind_port < 0: raise ValueError('Invalid bind port number.') - self._host = host - self._port = port - self._keepalive = keepalive + # Switch to state NEW to allow update of host, port & co. + self._sock_close() + self._state = _ConnectionState.MQTT_CS_NEW + + self.host = host + self.port = port + self.keepalive = keepalive self._bind_address = bind_address self._bind_port = bind_port self._clean_start = clean_start self._connect_properties = properties - self._state = mqtt_cs_connect_async + self._state = _ConnectionState.MQTT_CS_CONNECT_ASYNC - - def reconnect_delay_set(self, min_delay=1, max_delay=120): + def reconnect_delay_set(self, min_delay: int = 1, max_delay: int = 120) -> None: """ Configure the exponential reconnect delay When connection is lost, wait initially min_delay seconds and @@ -1009,7 +1537,7 @@ class Client(object): self._reconnect_max_delay = max_delay self._reconnect_delay = None - def reconnect(self): + def reconnect(self) -> MQTTErrorCode: """Reconnect the client after a disconnect. Can only be called after connect()/connect_async().""" if len(self._host) == 0: @@ -1025,88 +1553,69 @@ class Client(object): "remaining_length": 0, "packet": bytearray(b""), "to_process": 0, - "pos": 0} + "pos": 0, + } - self._out_packet = collections.deque() + self._ping_t = 0.0 + self._state = _ConnectionState.MQTT_CS_CONNECTING + + self._sock_close() + + # Mark all currently outgoing QoS = 0 packets as lost, + # or `wait_for_publish()` could hang forever + for pkt in self._out_packet: + if pkt["command"] & 0xF0 == PUBLISH and pkt["qos"] == 0 and pkt["info"] is not None: + pkt["info"].rc = MQTT_ERR_CONN_LOST + pkt["info"]._set_as_published() + + self._out_packet.clear() with self._msgtime_mutex: self._last_msg_in = time_func() self._last_msg_out = time_func() - self._ping_t = 0 - self._state = mqtt_cs_new - - self._sock_close() - # Put messages in progress in a valid state. self._messages_reconnect_reset() - sock = self._create_socket_connection() + with self._callback_mutex: + on_pre_connect = self.on_pre_connect - if self._ssl: - # SSL is only supported when SSLContext is available (implies Python >= 2.7.9 or >= 3.2) - - verify_host = not self._tls_insecure + if on_pre_connect: try: - # Try with server_hostname, even it's not supported in certain scenarios - sock = self._ssl_context.wrap_socket( - sock, - server_hostname=self._host, - do_handshake_on_connect=False, - ) - except ssl.CertificateError: - # CertificateError is derived from ValueError - raise - except ValueError: - # Python version requires SNI in order to handle server_hostname, but SNI is not available - sock = self._ssl_context.wrap_socket( - sock, - do_handshake_on_connect=False, - ) - else: - # If SSL context has already checked hostname, then don't need to do it again - if (hasattr(self._ssl_context, 'check_hostname') and - self._ssl_context.check_hostname): - verify_host = False + on_pre_connect(self, self._userdata) + except Exception as err: + self._easy_log( + MQTT_LOG_ERR, 'Caught exception in on_pre_connect: %s', err) + if not self.suppress_exceptions: + raise - sock.settimeout(self._keepalive) - sock.do_handshake() + self._sock = self._create_socket() - if verify_host: - ssl.match_hostname(sock.getpeercert(), self._host) - - if self._transport == "websockets": - sock.settimeout(self._keepalive) - sock = WebsocketWrapper(sock, self._host, self._port, self._ssl, - self._websocket_path, self._websocket_extra_headers) - - self._sock = sock - self._sock.setblocking(0) + self._sock.setblocking(False) # type: ignore[attr-defined] self._registered_write = False - self._call_socket_open() + self._call_socket_open(self._sock) return self._send_connect(self._keepalive) - def loop(self, timeout=1.0, max_packets=1): + def loop(self, timeout: float = 1.0) -> MQTTErrorCode: """Process network events. - It is strongly recommended that you use loop_start(), or - loop_forever(), or if you are using an external event loop using - loop_read(), loop_write(), and loop_misc(). Using loop() on it's own is + It is strongly recommended that you use `loop_start()`, or + `loop_forever()`, or if you are using an external event loop using + `loop_read()`, `loop_write()`, and `loop_misc()`. Using loop() on it's own is no longer recommended. This function must be called regularly to ensure communication with the broker is carried out. It calls select() on the network socket to wait for network events. If incoming data is present it will then be - processed. Outgoing commands, from e.g. publish(), are normally sent + processed. Outgoing commands, from e.g. `publish()`, are normally sent immediately that their function is called, but this is not always possible. loop() will also attempt to send any remaining outgoing messages, which also includes commands that are part of the flow for messages with QoS>0. - timeout: The time in seconds to wait for incoming/outgoing network + :param int timeout: The time in seconds to wait for incoming/outgoing network traffic before timing out and returning. - max_packets: Not currently used. Returns MQTT_ERR_SUCCESS on success. Returns >0 on error. @@ -1119,21 +1628,19 @@ class Client(object): return self._loop(timeout) - def _loop(self, timeout=1.0): + def _loop(self, timeout: float = 1.0) -> MQTTErrorCode: if timeout < 0.0: raise ValueError('Invalid timeout.') - try: - packet = self._out_packet.popleft() - self._out_packet.appendleft(packet) + if self.want_write(): wlist = [self._sock] - except IndexError: + else: wlist = [] # used to check if there are any bytes left in the (SSL) socket pending_bytes = 0 if hasattr(self._sock, 'pending'): - pending_bytes = self._sock.pending() + pending_bytes = self._sock.pending() # type: ignore[union-attr] # if bytes are pending do not wait in select if pending_bytes > 0: @@ -1150,15 +1657,24 @@ class Client(object): socklist = select.select(rlist, wlist, [], timeout) except TypeError: # Socket isn't correct type, in likelihood connection is lost - return MQTT_ERR_CONN_LOST + # ... or we called disconnect(). In that case the socket will + # be closed but some loop (like loop_forever) will continue to + # call _loop(). We still want to break that loop by returning an + # rc != MQTT_ERR_SUCCESS and we don't want state to change from + # mqtt_cs_disconnecting. + if self._state not in (_ConnectionState.MQTT_CS_DISCONNECTING, _ConnectionState.MQTT_CS_DISCONNECTED): + self._state = _ConnectionState.MQTT_CS_CONNECTION_LOST + return MQTTErrorCode.MQTT_ERR_CONN_LOST except ValueError: # Can occur if we just reconnected but rlist/wlist contain a -1 for # some reason. - return MQTT_ERR_CONN_LOST + if self._state not in (_ConnectionState.MQTT_CS_DISCONNECTING, _ConnectionState.MQTT_CS_DISCONNECTED): + self._state = _ConnectionState.MQTT_CS_CONNECTION_LOST + return MQTTErrorCode.MQTT_ERR_CONN_LOST except Exception: # Note that KeyboardInterrupt, etc. can still terminate since they # are not derived from Exception - return MQTT_ERR_UNKNOWN + return MQTTErrorCode.MQTT_ERR_UNKNOWN if self._sock in socklist[0] or pending_bytes > 0: rc = self.loop_read() @@ -1184,32 +1700,38 @@ class Client(object): return self.loop_misc() - def publish(self, topic, payload=None, qos=0, retain=False, properties=None): + def publish( + self, + topic: str, + payload: PayloadType = None, + qos: int = 0, + retain: bool = False, + properties: Properties | None = None, + ) -> MQTTMessageInfo: """Publish a message on a topic. This causes a message to be sent to the broker and subsequently from the broker to any clients subscribing to matching topics. - topic: The topic that the message should be published on. - payload: The actual message to send. If not given, or set to None a - zero length message will be used. Passing an int or float will result - in the payload being converted to a string representing that number. If - you wish to send a true int/float, use struct.pack() to create the - payload you require. - qos: The quality of service level to use. - retain: If set to true, the message will be set as the "last known - good"/retained message for the topic. - properties: (MQTT v5.0 only) the MQTT v5.0 properties to be included. - Use the Properties class. + :param str topic: The topic that the message should be published on. + :param payload: The actual message to send. If not given, or set to None a + zero length message will be used. Passing an int or float will result + in the payload being converted to a string representing that number. If + you wish to send a true int/float, use struct.pack() to create the + payload you require. + :param int qos: The quality of service level to use. + :param bool retain: If set to true, the message will be set as the "last known + good"/retained message for the topic. + :param Properties properties: (MQTT v5.0 only) the MQTT v5.0 properties to be included. - Returns a MQTTMessageInfo class, which can be used to determine whether - the message has been delivered (using info.is_published()) or to block - waiting for the message to be delivered (info.wait_for_publish()). The + Returns a `MQTTMessageInfo` class, which can be used to determine whether + the message has been delivered (using `is_published()`) or to block + waiting for the message to be delivered (`wait_for_publish()`). The message ID and return code of the publish() call can be found at - info.mid and info.rc. + :py:attr:`info.mid ` and :py:attr:`info.rc `. - For backwards compatibility, the MQTTMessageInfo class is iterable so - the old construct of (rc, mid) = client.publish(...) is still valid. + For backwards compatibility, the `MQTTMessageInfo` class is iterable so + the old construct of ``(rc, mid) = client.publish(...)`` is still valid. rc is MQTT_ERR_SUCCESS to indicate success or MQTT_ERR_NO_CONN if the client is not currently connected. mid is the message ID for the @@ -1217,35 +1739,24 @@ class Client(object): by checking against the mid argument in the on_publish() callback if it is defined. - A ValueError will be raised if topic is None, has zero length or is - invalid (contains a wildcard), except if the MQTT version used is v5.0. - For v5.0, a zero length topic can be used when a Topic Alias has been set. - - A ValueError will be raised if qos is not one of 0, 1 or 2, or if - the length of the payload is greater than 268435455 bytes.""" + :raises ValueError: if topic is None, has zero length or is + invalid (contains a wildcard), except if the MQTT version used is v5.0. + For v5.0, a zero length topic can be used when a Topic Alias has been set. + :raises ValueError: if qos is not one of 0, 1 or 2 + :raises ValueError: if the length of the payload is greater than 268435455 bytes. + """ if self._protocol != MQTTv5: if topic is None or len(topic) == 0: raise ValueError('Invalid topic.') - topic = topic.encode('utf-8') + topic_bytes = topic.encode('utf-8') - if self._topic_wildcard_len_check(topic) != MQTT_ERR_SUCCESS: - raise ValueError('Publish topic cannot contain wildcards.') + self._raise_for_invalid_topic(topic_bytes) if qos < 0 or qos > 2: raise ValueError('Invalid QoS level.') - if isinstance(payload, unicode): - local_payload = payload.encode('utf-8') - elif isinstance(payload, (bytes, bytearray)): - local_payload = payload - elif isinstance(payload, (int, float)): - local_payload = str(payload).encode('ascii') - elif payload is None: - local_payload = b'' - else: - raise TypeError( - 'payload must be a string, bytearray, int, float or None.') + local_payload = _encode_payload(payload) if len(local_payload) > 268435455: raise ValueError('Payload too large.') @@ -1255,11 +1766,11 @@ class Client(object): if qos == 0: info = MQTTMessageInfo(local_mid) rc = self._send_publish( - local_mid, topic, local_payload, qos, retain, False, info, properties) + local_mid, topic_bytes, local_payload, qos, retain, False, info, properties) info.rc = rc return info else: - message = MQTTMessage(local_mid, topic) + message = MQTTMessage(local_mid, topic_bytes) message.timestamp = time_func() message.payload = local_payload message.qos = qos @@ -1269,11 +1780,11 @@ class Client(object): with self._out_message_mutex: if self._max_queued_messages > 0 and len(self._out_messages) >= self._max_queued_messages: - message.info.rc = MQTT_ERR_QUEUE_SIZE + message.info.rc = MQTTErrorCode.MQTT_ERR_QUEUE_SIZE return message.info if local_mid in self._out_messages: - message.info.rc = MQTT_ERR_QUEUE_SIZE + message.info.rc = MQTTErrorCode.MQTT_ERR_QUEUE_SIZE return message.info self._out_messages[message.mid] = message @@ -1284,11 +1795,11 @@ class Client(object): elif qos == 2: message.state = mqtt_ms_wait_for_pubrec - rc = self._send_publish(message.mid, topic, message.payload, message.qos, message.retain, + rc = self._send_publish(message.mid, topic_bytes, message.payload, message.qos, message.retain, message.dup, message.info, message.properties) # remove from inflight messages so it will be send after a connection is made - if rc is MQTT_ERR_NO_CONN: + if rc == MQTTErrorCode.MQTT_ERR_NO_CONN: self._inflight_messages -= 1 message.state = mqtt_ms_publish @@ -1296,32 +1807,35 @@ class Client(object): return message.info else: message.state = mqtt_ms_queued - message.info.rc = MQTT_ERR_SUCCESS + message.info.rc = MQTTErrorCode.MQTT_ERR_SUCCESS return message.info - def username_pw_set(self, username, password=None): + def username_pw_set( + self, username: str | None, password: str | None = None + ) -> None: """Set a username and optionally a password for broker authentication. Must be called before connect() to have any effect. - Requires a broker that supports MQTT v3.1. + Requires a broker that supports MQTT v3.1 or more. - username: The username to authenticate with. Need have no relationship to the client id. Must be unicode + :param str username: The username to authenticate with. Need have no relationship to the client id. Must be str [MQTT-3.1.3-11]. Set to None to reset client back to not using username/password for broker authentication. - password: The password to authenticate with. Optional, set to None if not required. If it is unicode, then it + :param str password: The password to authenticate with. Optional, set to None if not required. If it is str, then it will be encoded as UTF-8. """ # [MQTT-3.1.3-11] User name must be UTF-8 encoded string self._username = None if username is None else username.encode('utf-8') - self._password = password - if isinstance(self._password, unicode): - self._password = self._password.encode('utf-8') + if isinstance(password, str): + self._password = password.encode('utf-8') + else: + self._password = password - def enable_bridge_mode(self): + def enable_bridge_mode(self) -> None: """Sets the client in a bridge mode instead of client mode. - Must be called before connect() to have any effect. + Must be called before `connect()` to have any effect. Requires brokers that support bridge mode. Under bridge mode, the broker will identify the client as a bridge and @@ -1334,30 +1848,50 @@ class Client(object): """ self._client_mode = MQTT_BRIDGE - def is_connected(self): + def _connection_closed(self) -> bool: + """ + Return true if the connection is closed (and not trying to be opened). + """ + return ( + self._state == _ConnectionState.MQTT_CS_NEW + or (self._state in (_ConnectionState.MQTT_CS_DISCONNECTING, _ConnectionState.MQTT_CS_DISCONNECTED) and self._sock is None)) + + def is_connected(self) -> bool: """Returns the current status of the connection True if connection exists False if connection is closed """ - return self._state == mqtt_cs_connected + return self._state == _ConnectionState.MQTT_CS_CONNECTED - def disconnect(self, reasoncode=None, properties=None): + def disconnect( + self, + reasoncode: ReasonCode | None = None, + properties: Properties | None = None, + ) -> MQTTErrorCode: """Disconnect a connected client from the broker. - reasoncode: (MQTT v5.0 only) a ReasonCodes instance setting the MQTT v5.0 - reasoncode to be sent with the disconnect. It is optional, the receiver - then assuming that 0 (success) is the value. - properties: (MQTT v5.0 only) a Properties instance setting the MQTT v5.0 properties - to be included. Optional - if not set, no properties are sent. - """ - self._state = mqtt_cs_disconnecting + :param ReasonCode reasoncode: (MQTT v5.0 only) a ReasonCode instance setting the MQTT v5.0 + reasoncode to be sent with the disconnect packet. It is optional, the receiver + then assuming that 0 (success) is the value. + :param Properties properties: (MQTT v5.0 only) a Properties instance setting the MQTT v5.0 properties + to be included. Optional - if not set, no properties are sent. + """ if self._sock is None: + self._state = _ConnectionState.MQTT_CS_DISCONNECTED return MQTT_ERR_NO_CONN + else: + self._state = _ConnectionState.MQTT_CS_DISCONNECTING return self._send_disconnect(reasoncode, properties) - def subscribe(self, topic, qos=0, options=None, properties=None): + def subscribe( + self, + topic: str | tuple[str, int] | tuple[str, SubscribeOptions] | list[tuple[str, int]] | list[tuple[str, SubscribeOptions]], + qos: int = 0, + options: SubscribeOptions | None = None, + properties: Properties | None = None, + ) -> tuple[MQTTErrorCode, int | None]: """Subscribe the client to one or more topics. This function may be called in three different ways (and a further three for MQTT v5.0): @@ -1366,40 +1900,40 @@ class Client(object): ------------------------- e.g. subscribe("my/topic", 2) - topic: A string specifying the subscription topic to subscribe to. - qos: The desired quality of service level for the subscription. - Defaults to 0. - options and properties: Not used. + :topic: A string specifying the subscription topic to subscribe to. + :qos: The desired quality of service level for the subscription. + Defaults to 0. + :options and properties: Not used. Simple string and subscribe options (MQTT v5.0 only) ---------------------------------------------------- e.g. subscribe("my/topic", options=SubscribeOptions(qos=2)) - topic: A string specifying the subscription topic to subscribe to. - qos: Not used. - options: The MQTT v5.0 subscribe options. - properties: a Properties instance setting the MQTT v5.0 properties - to be included. Optional - if not set, no properties are sent. + :topic: A string specifying the subscription topic to subscribe to. + :qos: Not used. + :options: The MQTT v5.0 subscribe options. + :properties: a Properties instance setting the MQTT v5.0 properties + to be included. Optional - if not set, no properties are sent. String and integer tuple ------------------------ e.g. subscribe(("my/topic", 1)) - topic: A tuple of (topic, qos). Both topic and qos must be present in + :topic: A tuple of (topic, qos). Both topic and qos must be present in the tuple. - qos and options: Not used. - properties: Only used for MQTT v5.0. A Properties instance setting the - MQTT v5.0 properties. Optional - if not set, no properties are sent. + :qos and options: Not used. + :properties: Only used for MQTT v5.0. A Properties instance setting the + MQTT v5.0 properties. Optional - if not set, no properties are sent. String and subscribe options tuple (MQTT v5.0 only) --------------------------------------------------- e.g. subscribe(("my/topic", SubscribeOptions(qos=1))) - topic: A tuple of (topic, SubscribeOptions). Both topic and subscribe + :topic: A tuple of (topic, SubscribeOptions). Both topic and subscribe options must be present in the tuple. - qos and options: Not used. - properties: a Properties instance setting the MQTT v5.0 properties - to be included. Optional - if not set, no properties are sent. + :qos and options: Not used. + :properties: a Properties instance setting the MQTT v5.0 properties + to be included. Optional - if not set, no properties are sent. List of string and integer tuples --------------------------------- @@ -1409,9 +1943,9 @@ class Client(object): command, which is more efficient than using multiple calls to subscribe(). - topic: A list of tuple of format (topic, qos). Both topic and qos must + :topic: A list of tuple of format (topic, qos). Both topic and qos must be present in all of the tuples. - qos, options and properties: Not used. + :qos, options and properties: Not used. List of string and subscribe option tuples (MQTT v5.0 only) ----------------------------------------------------------- @@ -1421,11 +1955,11 @@ class Client(object): command, which is more efficient than using multiple calls to subscribe(). - topic: A list of tuple of format (topic, SubscribeOptions). Both topic and subscribe + :topic: A list of tuple of format (topic, SubscribeOptions). Both topic and subscribe options must be present in all of the tuples. - qos and options: Not used. - properties: a Properties instance setting the MQTT v5.0 properties - to be included. Optional - if not set, no properties are sent. + :qos and options: Not used. + :properties: a Properties instance setting the MQTT v5.0 properties + to be included. Optional - if not set, no properties are sent. The function returns a tuple (result, mid), where result is MQTT_ERR_SUCCESS to indicate success or (MQTT_ERR_NO_CONN, None) if the @@ -1441,14 +1975,14 @@ class Client(object): if isinstance(topic, tuple): if self._protocol == MQTTv5: - topic, options = topic + topic, options = topic # type: ignore if not isinstance(options, SubscribeOptions): raise ValueError( 'Subscribe options must be instance of SubscribeOptions class.') else: - topic, qos = topic + topic, qos = topic # type: ignore - if isinstance(topic, basestring): + if isinstance(topic, (bytes, str)): if qos < 0 or qos > 2: raise ValueError('Invalid QoS level.') if self._protocol == MQTTv5: @@ -1465,8 +1999,10 @@ class Client(object): else: if topic is None or len(topic) == 0: raise ValueError('Invalid topic.') - topic_qos_list = [(topic.encode('utf-8'), qos)] + topic_qos_list = [(topic.encode('utf-8'), qos)] # type: ignore elif isinstance(topic, list): + if len(topic) == 0: + raise ValueError('Empty topic list') topic_qos_list = [] if self._protocol == MQTTv5: for t, o in topic: @@ -1478,11 +2014,11 @@ class Client(object): topic_qos_list.append((t.encode('utf-8'), o)) else: for t, q in topic: - if q < 0 or q > 2: + if isinstance(q, SubscribeOptions) or q < 0 or q > 2: raise ValueError('Invalid QoS level.') - if t is None or len(t) == 0 or not isinstance(t, basestring): + if t is None or len(t) == 0 or not isinstance(t, (bytes, str)): raise ValueError('Invalid topic.') - topic_qos_list.append((t.encode('utf-8'), q)) + topic_qos_list.append((t.encode('utf-8'), q)) # type: ignore if topic_qos_list is None: raise ValueError("No topic specified, or incorrect topic type.") @@ -1495,13 +2031,15 @@ class Client(object): return self._send_subscribe(False, topic_qos_list, properties) - def unsubscribe(self, topic, properties=None): + def unsubscribe( + self, topic: str, properties: Properties | None = None + ) -> tuple[MQTTErrorCode, int | None]: """Unsubscribe the client from one or more topics. - topic: A single string, or list of strings that are the subscription - topics to unsubscribe from. - properties: (MQTT v5.0 only) a Properties instance setting the MQTT v5.0 properties - to be included. Optional - if not set, no properties are sent. + :param topic: A single string, or list of strings that are the subscription + topics to unsubscribe from. + :param properties: (MQTT v5.0 only) a Properties instance setting the MQTT v5.0 properties + to be included. Optional - if not set, no properties are sent. Returns a tuple (result, mid), where result is MQTT_ERR_SUCCESS to indicate success or (MQTT_ERR_NO_CONN, None) if the client is not @@ -1510,20 +2048,20 @@ class Client(object): used to track the unsubscribe request by checking against the mid argument in the on_unsubscribe() callback if it is defined. - Raises a ValueError if topic is None or has zero string length, or is - not a string or list. + :raises ValueError: if topic is None or has zero string length, or is + not a string or list. """ topic_list = None if topic is None: raise ValueError('Invalid topic.') - if isinstance(topic, basestring): + if isinstance(topic, (bytes, str)): if len(topic) == 0: raise ValueError('Invalid topic.') topic_list = [topic.encode('utf-8')] elif isinstance(topic, list): topic_list = [] for t in topic: - if len(t) == 0 or not isinstance(t, basestring): + if len(t) == 0 or not isinstance(t, (bytes, str)): raise ValueError('Invalid topic.') topic_list.append(t.encode('utf-8')) @@ -1531,20 +2069,20 @@ class Client(object): raise ValueError("No topic specified, or incorrect topic type.") if self._sock is None: - return (MQTT_ERR_NO_CONN, None) + return (MQTTErrorCode.MQTT_ERR_NO_CONN, None) return self._send_unsubscribe(False, topic_list, properties) - def loop_read(self, max_packets=1): - """Process read network events. Use in place of calling loop() if you + def loop_read(self, max_packets: int = 1) -> MQTTErrorCode: + """Process read network events. Use in place of calling `loop()` if you wish to handle your client reads as part of your own application. - Use socket() to obtain the client socket to call select() or equivalent + Use `socket()` to obtain the client socket to call select() or equivalent on. - Do not use if you are using the threaded interface loop_start().""" + Do not use if you are using `loop_start()` or `loop_forever()`.""" if self._sock is None: - return MQTT_ERR_NO_CONN + return MQTTErrorCode.MQTT_ERR_NO_CONN max_packets = len(self._out_messages) + len(self._in_messages) if max_packets < 1: @@ -1552,59 +2090,54 @@ class Client(object): for _ in range(0, max_packets): if self._sock is None: - return MQTT_ERR_NO_CONN + return MQTTErrorCode.MQTT_ERR_NO_CONN rc = self._packet_read() if rc > 0: return self._loop_rc_handle(rc) - elif rc == MQTT_ERR_AGAIN: - return MQTT_ERR_SUCCESS - return MQTT_ERR_SUCCESS + elif rc == MQTTErrorCode.MQTT_ERR_AGAIN: + return MQTTErrorCode.MQTT_ERR_SUCCESS + return MQTTErrorCode.MQTT_ERR_SUCCESS - def loop_write(self, max_packets=1): - """Process write network events. Use in place of calling loop() if you + def loop_write(self) -> MQTTErrorCode: + """Process write network events. Use in place of calling `loop()` if you wish to handle your client writes as part of your own application. - Use socket() to obtain the client socket to call select() or equivalent + Use `socket()` to obtain the client socket to call select() or equivalent on. - Use want_write() to determine if there is data waiting to be written. + Use `want_write()` to determine if there is data waiting to be written. - Do not use if you are using the threaded interface loop_start().""" + Do not use if you are using `loop_start()` or `loop_forever()`.""" if self._sock is None: - return MQTT_ERR_NO_CONN + return MQTTErrorCode.MQTT_ERR_NO_CONN try: rc = self._packet_write() - if rc == MQTT_ERR_AGAIN: - return MQTT_ERR_SUCCESS + if rc == MQTTErrorCode.MQTT_ERR_AGAIN: + return MQTTErrorCode.MQTT_ERR_SUCCESS elif rc > 0: return self._loop_rc_handle(rc) else: - return MQTT_ERR_SUCCESS + return MQTTErrorCode.MQTT_ERR_SUCCESS finally: if self.want_write(): self._call_socket_register_write() else: self._call_socket_unregister_write() - def want_write(self): + def want_write(self) -> bool: """Call to determine if there is network data waiting to be written. - Useful if you are calling select() yourself rather than using loop(). + Useful if you are calling select() yourself rather than using `loop()`, `loop_start()` or `loop_forever()`. """ - try: - packet = self._out_packet.popleft() - self._out_packet.appendleft(packet) - return True - except IndexError: - return False + return len(self._out_packet) > 0 - def loop_misc(self): - """Process miscellaneous network events. Use in place of calling loop() if you + def loop_misc(self) -> MQTTErrorCode: + """Process miscellaneous network events. Use in place of calling `loop()` if you wish to call select() or equivalent on. - Do not use if you are using the threaded interface loop_start().""" + Do not use if you are using `loop_start()` or `loop_forever()`.""" if self._sock is None: - return MQTT_ERR_NO_CONN + return MQTTErrorCode.MQTT_ERR_NO_CONN now = time_func() self._check_keepalive() @@ -1614,61 +2147,72 @@ class Client(object): # This hasn't happened in the keepalive time so we should disconnect. self._sock_close() - if self._state == mqtt_cs_disconnecting: - rc = MQTT_ERR_SUCCESS + if self._state in (_ConnectionState.MQTT_CS_DISCONNECTING, _ConnectionState.MQTT_CS_DISCONNECTED): + self._state = _ConnectionState.MQTT_CS_DISCONNECTED + rc = MQTTErrorCode.MQTT_ERR_SUCCESS else: - rc = MQTT_ERR_KEEPALIVE + self._state = _ConnectionState.MQTT_CS_CONNECTION_LOST + rc = MQTTErrorCode.MQTT_ERR_KEEPALIVE - self._do_on_disconnect(rc) + self._do_on_disconnect( + packet_from_broker=False, + v1_rc=rc, + ) - return MQTT_ERR_CONN_LOST + return MQTTErrorCode.MQTT_ERR_CONN_LOST - return MQTT_ERR_SUCCESS + return MQTTErrorCode.MQTT_ERR_SUCCESS - def max_inflight_messages_set(self, inflight): + def max_inflight_messages_set(self, inflight: int) -> None: """Set the maximum number of messages with QoS>0 that can be part way through their network flow at once. Defaults to 20.""" - if inflight < 0: - raise ValueError('Invalid inflight.') - self._max_inflight_messages = inflight + self.max_inflight_messages = inflight - def max_queued_messages_set(self, queue_size): + def max_queued_messages_set(self, queue_size: int) -> Client: """Set the maximum number of messages in the outgoing message queue. 0 means unlimited.""" - if queue_size < 0: - raise ValueError('Invalid queue size.') if not isinstance(queue_size, int): raise ValueError('Invalid type of queue size.') - self._max_queued_messages = queue_size + self.max_queued_messages = queue_size return self - def message_retry_set(self, retry): - """No longer used, remove in version 2.0""" - pass - - def user_data_set(self, userdata): + def user_data_set(self, userdata: Any) -> None: """Set the user data variable passed to callbacks. May be any data type.""" self._userdata = userdata - def will_set(self, topic, payload=None, qos=0, retain=False, properties=None): + def user_data_get(self) -> Any: + """Get the user data variable passed to callbacks. May be any data type.""" + return self._userdata + + def will_set( + self, + topic: str, + payload: PayloadType = None, + qos: int = 0, + retain: bool = False, + properties: Properties | None = None, + ) -> None: """Set a Will to be sent by the broker in case the client disconnects unexpectedly. This must be called before connect() to have any effect. - topic: The topic that the will message should be published on. - payload: The message to send as a will. If not given, or set to None a - zero length message will be used as the will. Passing an int or float - will result in the payload being converted to a string representing - that number. If you wish to send a true int/float, use struct.pack() to - create the payload you require. - qos: The quality of service level to use for the will. - retain: If set to true, the will message will be set as the "last known - good"/retained message for the topic. - properties: (MQTT v5.0 only) a Properties instance setting the MQTT v5.0 properties - to be included with the will message. Optional - if not set, no properties are sent. + :param str topic: The topic that the will message should be published on. + :param payload: The message to send as a will. If not given, or set to None a + zero length message will be used as the will. Passing an int or float + will result in the payload being converted to a string representing + that number. If you wish to send a true int/float, use struct.pack() to + create the payload you require. + :param int qos: The quality of service level to use for the will. + :param bool retain: If set to true, the will message will be set as the "last known + good"/retained message for the topic. + :param Properties properties: (MQTT v5.0 only) the MQTT v5.0 properties + to be included with the will message. Optional - if not set, no properties are sent. - Raises a ValueError if qos is not 0, 1 or 2, or if topic is None or has - zero string length. + :raises ValueError: if qos is not 0, 1 or 2, or if topic is None or has + zero string length. + + See `will_clear` to clear will. Note that will are NOT send if the client disconnect cleanly + for example by calling `disconnect()`. """ if topic is None or len(topic) == 0: raise ValueError('Invalid topic.') @@ -1676,30 +2220,19 @@ class Client(object): if qos < 0 or qos > 2: raise ValueError('Invalid QoS level.') - if properties != None and not isinstance(properties, Properties): + if properties and not isinstance(properties, Properties): raise ValueError( "The properties argument must be an instance of the Properties class.") - if isinstance(payload, unicode): - self._will_payload = payload.encode('utf-8') - elif isinstance(payload, (bytes, bytearray)): - self._will_payload = payload - elif isinstance(payload, (int, float)): - self._will_payload = str(payload).encode('ascii') - elif payload is None: - self._will_payload = b"" - else: - raise TypeError( - 'payload must be a string, bytearray, int, float or None.') - + self._will_payload = _encode_payload(payload) self._will = True self._will_topic = topic.encode('utf-8') self._will_qos = qos self._will_retain = retain self._will_properties = properties - def will_clear(self): - """ Removes a will that was previously configured with will_set(). + def will_clear(self) -> None: + """ Removes a will that was previously configured with `will_set()`. Must be called before connect() to have any effect.""" self._will = False @@ -1708,27 +2241,29 @@ class Client(object): self._will_qos = 0 self._will_retain = False - def socket(self): + def socket(self) -> SocketLike | None: """Return the socket or ssl object for this client.""" return self._sock - def loop_forever(self, timeout=1.0, max_packets=1, retry_first_connection=False): + def loop_forever( + self, + timeout: float = 1.0, + retry_first_connection: bool = False, + ) -> MQTTErrorCode: """This function calls the network loop functions for you in an infinite blocking loop. It is useful for the case where you only want to run the MQTT client loop in your program. loop_forever() will handle reconnecting for you if reconnect_on_failure is - true (this is the default behavior). If you call disconnect() in a callback + true (this is the default behavior). If you call `disconnect()` in a callback it will return. - - timeout: The time in seconds to wait for incoming/outgoing network + :param int timeout: The time in seconds to wait for incoming/outgoing network traffic before timing out and returning. - max_packets: Not currently used. - retry_first_connection: Should the first connection attempt be retried on failure. + :param bool retry_first_connection: Should the first connection attempt be retried on failure. This is independent of the reconnect_on_failure setting. - Raises OSError/WebsocketConnectionError on first connection failures unless retry_first_connection=True + :raises OSError: if the first connection fail unless retry_first_connection=True """ run = True @@ -1737,10 +2272,10 @@ class Client(object): if self._thread_terminate is True: break - if self._state == mqtt_cs_connect_async: + if self._state == _ConnectionState.MQTT_CS_CONNECT_ASYNC: try: self.reconnect() - except (OSError, WebsocketConnectionError): + except OSError: self._handle_on_connect_fail() if not retry_first_connection: raise @@ -1751,8 +2286,8 @@ class Client(object): break while run: - rc = MQTT_ERR_SUCCESS - while rc == MQTT_ERR_SUCCESS: + rc = MQTTErrorCode.MQTT_ERR_SUCCESS + while rc == MQTTErrorCode.MQTT_ERR_SUCCESS: rc = self._loop(timeout) # We don't need to worry about locking here, because we've # either called loop_forever() when in single threaded mode, or @@ -1761,11 +2296,15 @@ class Client(object): if (self._thread_terminate is True and len(self._out_packet) == 0 and len(self._out_messages) == 0): - rc = 1 + rc = MQTTErrorCode.MQTT_ERR_NOMEM run = False - def should_exit(): - return self._state == mqtt_cs_disconnecting or run is False or self._thread_terminate is True + def should_exit() -> bool: + return ( + self._state in (_ConnectionState.MQTT_CS_DISCONNECTING, _ConnectionState.MQTT_CS_DISCONNECTED) or + run is False or # noqa: B023 (uses the run variable from the outer scope on purpose) + self._thread_terminate is True + ) if should_exit() or not self._reconnect_on_failure: run = False @@ -1777,99 +2316,153 @@ class Client(object): else: try: self.reconnect() - except (OSError, WebsocketConnectionError): + except OSError: self._handle_on_connect_fail() self._easy_log( MQTT_LOG_DEBUG, "Connection failed, retrying") return rc - def loop_start(self): + def loop_start(self) -> MQTTErrorCode: """This is part of the threaded client interface. Call this once to start a new thread to process network traffic. This provides an - alternative to repeatedly calling loop() yourself. + alternative to repeatedly calling `loop()` yourself. + + Under the hood, this will call `loop_forever` in a thread, which means that + the thread will terminate if you call `disconnect()` """ if self._thread is not None: - return MQTT_ERR_INVAL + return MQTTErrorCode.MQTT_ERR_INVAL self._sockpairR, self._sockpairW = _socketpair_compat() self._thread_terminate = False - self._thread = threading.Thread(target=self._thread_main) + self._thread = threading.Thread(target=self._thread_main, name=f"paho-mqtt-client-{self._client_id.decode()}") self._thread.daemon = True self._thread.start() - def loop_stop(self, force=False): + return MQTTErrorCode.MQTT_ERR_SUCCESS + + def loop_stop(self) -> MQTTErrorCode: """This is part of the threaded client interface. Call this once to - stop the network thread previously created with loop_start(). This call + stop the network thread previously created with `loop_start()`. This call will block until the network thread finishes. - The force parameter is currently ignored. + This don't guarantee that publish packet are sent, use `wait_for_publish` or + `on_publish` to ensure `publish` are sent. """ if self._thread is None: - return MQTT_ERR_INVAL + return MQTTErrorCode.MQTT_ERR_INVAL self._thread_terminate = True if threading.current_thread() != self._thread: self._thread.join() self._thread = None + return MQTTErrorCode.MQTT_ERR_SUCCESS + @property - def on_log(self): - """If implemented, called when the client has log information. - Defined to allow debugging.""" + def callback_api_version(self) -> CallbackAPIVersion: + """ + Return the callback API version used for user-callback. See docstring for + each user-callback (`on_connect`, `on_publish`, ...) for details. + + This property is read-only. + """ + return self._callback_api_version + + @property + def on_log(self) -> CallbackOnLog | None: + """The callback called when the client has log information. + Defined to allow debugging. + + Expected signature is:: + + log_callback(client, userdata, level, buf) + + :param Client client: the client instance for this callback + :param userdata: the private user data as set in Client() or user_data_set() + :param int level: gives the severity of the message and will be one of + MQTT_LOG_INFO, MQTT_LOG_NOTICE, MQTT_LOG_WARNING, + MQTT_LOG_ERR, and MQTT_LOG_DEBUG. + :param str buf: the message itself + + Decorator: @client.log_callback() (``client`` is the name of the + instance which this callback is being attached to) + """ return self._on_log @on_log.setter - def on_log(self, func): - """ Define the logging callback implementation. - - Expected signature is: - log_callback(client, userdata, level, buf) - - client: the client instance for this callback - userdata: the private user data as set in Client() or userdata_set() - level: gives the severity of the message and will be one of - MQTT_LOG_INFO, MQTT_LOG_NOTICE, MQTT_LOG_WARNING, - MQTT_LOG_ERR, and MQTT_LOG_DEBUG. - buf: the message itself - - Decorator: @client.log_callback() (```client``` is the name of the - instance which this callback is being attached to) - """ + def on_log(self, func: CallbackOnLog | None) -> None: self._on_log = func - def log_callback(self): - def decorator(func): + def log_callback(self) -> Callable[[CallbackOnLog], CallbackOnLog]: + def decorator(func: CallbackOnLog) -> CallbackOnLog: self.on_log = func return func return decorator @property - def on_connect(self): - """If implemented, called when the broker responds to our connection - request.""" - return self._on_connect + def on_pre_connect(self) -> CallbackOnPreConnect | None: + """The callback called immediately prior to the connection is made + request. - @on_connect.setter - def on_connect(self, func): - """ Define the connect callback implementation. + Expected signature (for all callback API version):: - Expected signature for MQTT v3.1 and v3.1.1 is: - connect_callback(client, userdata, flags, rc) + connect_callback(client, userdata) - and for MQTT v5.0: - connect_callback(client, userdata, flags, reasonCode, properties) + :parama Client client: the client instance for this callback + :parama userdata: the private user data as set in Client() or user_data_set() - client: the client instance for this callback - userdata: the private user data as set in Client() or userdata_set() - flags: response flags sent by the broker - rc: the connection result - reasonCode: the MQTT v5.0 reason code: an instance of the ReasonCode class. - ReasonCode may be compared to integer. - properties: the MQTT v5.0 properties returned from the broker. An instance - of the Properties class. - For MQTT v3.1 and v3.1.1 properties is not provided but for compatibility - with MQTT v5.0, we recommend adding properties=None. + Decorator: @client.pre_connect_callback() (``client`` is the name of the + instance which this callback is being attached to) + + """ + return self._on_pre_connect + + @on_pre_connect.setter + def on_pre_connect(self, func: CallbackOnPreConnect | None) -> None: + with self._callback_mutex: + self._on_pre_connect = func + + def pre_connect_callback( + self, + ) -> Callable[[CallbackOnPreConnect], CallbackOnPreConnect]: + def decorator(func: CallbackOnPreConnect) -> CallbackOnPreConnect: + self.on_pre_connect = func + return func + return decorator + + @property + def on_connect(self) -> CallbackOnConnect | None: + """The callback called when the broker reponds to our connection request. + + Expected signature for callback API version 2:: + + connect_callback(client, userdata, connect_flags, reason_code, properties) + + Expected signature for callback API version 1 change with MQTT protocol version: + * For MQTT v3.1 and v3.1.1 it's:: + + connect_callback(client, userdata, flags, rc) + + * For MQTT it's v5.0:: + + connect_callback(client, userdata, flags, reason_code, properties) + + + :param Client client: the client instance for this callback + :param userdata: the private user data as set in Client() or user_data_set() + :param ConnectFlags connect_flags: the flags for this connection + :param ReasonCode reason_code: the connection reason code received from the broken. + In MQTT v5.0 it's the reason code defined by the standard. + In MQTT v3, we convert return code to a reason code, see + `convert_connack_rc_to_reason_code()`. + `ReasonCode` may be compared to integer. + :param Properties properties: the MQTT v5.0 properties received from the broker. + For MQTT v3.1 and v3.1.1 properties is not provided and an empty Properties + object is always used. + :param dict flags: response flags sent by the broker + :param int rc: the connection result, should have a value of `ConnackCode` flags is a dict that contains response flags from the broker: flags['session present'] - this flag is useful for clients that are @@ -1879,272 +2472,339 @@ class Client(object): session information for the client. If 1, the session still exists. The value of rc indicates success or not: - 0: Connection successful - 1: Connection refused - incorrect protocol version - 2: Connection refused - invalid client identifier - 3: Connection refused - server unavailable - 4: Connection refused - bad username or password - 5: Connection refused - not authorised - 6-255: Currently unused. + - 0: Connection successful + - 1: Connection refused - incorrect protocol version + - 2: Connection refused - invalid client identifier + - 3: Connection refused - server unavailable + - 4: Connection refused - bad username or password + - 5: Connection refused - not authorised + - 6-255: Currently unused. - Decorator: @client.connect_callback() (```client``` is the name of the + Decorator: @client.connect_callback() (``client`` is the name of the instance which this callback is being attached to) - """ + return self._on_connect + + @on_connect.setter + def on_connect(self, func: CallbackOnConnect | None) -> None: with self._callback_mutex: self._on_connect = func - def connect_callback(self): - def decorator(func): + def connect_callback( + self, + ) -> Callable[[CallbackOnConnect], CallbackOnConnect]: + def decorator(func: CallbackOnConnect) -> CallbackOnConnect: self.on_connect = func return func return decorator @property - def on_connect_fail(self): - """If implemented, called when the client failed to connect - to the broker.""" + def on_connect_fail(self) -> CallbackOnConnectFail | None: + """The callback called when the client failed to connect + to the broker. + + Expected signature is (for all callback_api_version):: + + connect_fail_callback(client, userdata) + + :param Client client: the client instance for this callback + :parama userdata: the private user data as set in Client() or user_data_set() + + Decorator: @client.connect_fail_callback() (``client`` is the name of the + instance which this callback is being attached to) + """ return self._on_connect_fail @on_connect_fail.setter - def on_connect_fail(self, func): - """ Define the connection failure callback implementation - - Expected signature is: - on_connect_fail(client, userdata) - - client: the client instance for this callback - userdata: the private user data as set in Client() or userdata_set() - - Decorator: @client.connect_fail_callback() (```client``` is the name of the - instance which this callback is being attached to) - - """ + def on_connect_fail(self, func: CallbackOnConnectFail | None) -> None: with self._callback_mutex: self._on_connect_fail = func - def connect_fail_callback(self): - def decorator(func): + def connect_fail_callback( + self, + ) -> Callable[[CallbackOnConnectFail], CallbackOnConnectFail]: + def decorator(func: CallbackOnConnectFail) -> CallbackOnConnectFail: self.on_connect_fail = func return func return decorator @property - def on_subscribe(self): - """If implemented, called when the broker responds to a subscribe - request.""" + def on_subscribe(self) -> CallbackOnSubscribe | None: + """The callback called when the broker responds to a subscribe + request. + + Expected signature for callback API version 2:: + + subscribe_callback(client, userdata, mid, reason_code_list, properties) + + Expected signature for callback API version 1 change with MQTT protocol version: + * For MQTT v3.1 and v3.1.1 it's:: + + subscribe_callback(client, userdata, mid, granted_qos) + + * For MQTT v5.0 it's:: + + subscribe_callback(client, userdata, mid, reason_code_list, properties) + + :param Client client: the client instance for this callback + :param userdata: the private user data as set in Client() or user_data_set() + :param int mid: matches the mid variable returned from the corresponding + subscribe() call. + :param list[ReasonCode] reason_code_list: reason codes received from the broker for each subscription. + In MQTT v5.0 it's the reason code defined by the standard. + In MQTT v3, we convert granted QoS to a reason code. + It's a list of ReasonCode instances. + :param Properties properties: the MQTT v5.0 properties received from the broker. + For MQTT v3.1 and v3.1.1 properties is not provided and an empty Properties + object is always used. + :param list[int] granted_qos: list of integers that give the QoS level the broker has + granted for each of the different subscription requests. + + Decorator: @client.subscribe_callback() (``client`` is the name of the + instance which this callback is being attached to) + """ return self._on_subscribe @on_subscribe.setter - def on_subscribe(self, func): - """ Define the subscribe callback implementation. - - Expected signature for MQTT v3.1.1 and v3.1 is: - subscribe_callback(client, userdata, mid, granted_qos) - - and for MQTT v5.0: - subscribe_callback(client, userdata, mid, reasonCodes, properties) - - client: the client instance for this callback - userdata: the private user data as set in Client() or userdata_set() - mid: matches the mid variable returned from the corresponding - subscribe() call. - granted_qos: list of integers that give the QoS level the broker has - granted for each of the different subscription requests. - reasonCodes: the MQTT v5.0 reason codes received from the broker for each - subscription. A list of ReasonCodes instances. - properties: the MQTT v5.0 properties received from the broker. A - list of Properties class instances. - - Decorator: @client.subscribe_callback() (```client``` is the name of the - instance which this callback is being attached to) - """ + def on_subscribe(self, func: CallbackOnSubscribe | None) -> None: with self._callback_mutex: self._on_subscribe = func - def subscribe_callback(self): - def decorator(func): + def subscribe_callback( + self, + ) -> Callable[[CallbackOnSubscribe], CallbackOnSubscribe]: + def decorator(func: CallbackOnSubscribe) -> CallbackOnSubscribe: self.on_subscribe = func return func return decorator @property - def on_message(self): - """If implemented, called when a message has been received on a topic + def on_message(self) -> CallbackOnMessage | None: + """The callback called when a message has been received on a topic that the client subscribes to. - This callback will be called for every message received. Use - message_callback_add() to define multiple callbacks that will be called - for specific topic filters.""" + This callback will be called for every message received unless a + `message_callback_add()` matched the message. + + Expected signature is (for all callback API version): + message_callback(client, userdata, message) + + :param Client client: the client instance for this callback + :param userdata: the private user data as set in Client() or user_data_set() + :param MQTTMessage message: the received message. + This is a class with members topic, payload, qos, retain. + + Decorator: @client.message_callback() (``client`` is the name of the + instance which this callback is being attached to) + """ return self._on_message @on_message.setter - def on_message(self, func): - """ Define the message received callback implementation. - - Expected signature is: - on_message_callback(client, userdata, message) - - client: the client instance for this callback - userdata: the private user data as set in Client() or userdata_set() - message: an instance of MQTTMessage. - This is a class with members topic, payload, qos, retain. - - Decorator: @client.message_callback() (```client``` is the name of the - instance which this callback is being attached to) - - """ + def on_message(self, func: CallbackOnMessage | None) -> None: with self._callback_mutex: self._on_message = func - def message_callback(self): - def decorator(func): + def message_callback( + self, + ) -> Callable[[CallbackOnMessage], CallbackOnMessage]: + def decorator(func: CallbackOnMessage) -> CallbackOnMessage: self.on_message = func return func return decorator @property - def on_publish(self): - """If implemented, called when a message that was to be sent using the - publish() call has completed transmission to the broker. + def on_publish(self) -> CallbackOnPublish | None: + """The callback called when a message that was to be sent using the + `publish()` call has completed transmission to the broker. For messages with QoS levels 1 and 2, this means that the appropriate handshakes have completed. For QoS 0, this simply means that the message has left the client. - This callback is important because even if the publish() call returns - success, it does not always mean that the message has been sent.""" - return self._on_publish + This callback is important because even if the `publish()` call returns + success, it does not always mean that the message has been sent. - @on_publish.setter - def on_publish(self, func): - """ Define the published message callback implementation. + See also `wait_for_publish` which could be simpler to use. - Expected signature is: - on_publish_callback(client, userdata, mid) + Expected signature for callback API version 2:: - client: the client instance for this callback - userdata: the private user data as set in Client() or userdata_set() - mid: matches the mid variable returned from the corresponding - publish() call, to allow outgoing messages to be tracked. + publish_callback(client, userdata, mid, reason_code, properties) - Decorator: @client.publish_callback() (```client``` is the name of the + Expected signature for callback API version 1:: + + publish_callback(client, userdata, mid) + + :param Client client: the client instance for this callback + :param userdata: the private user data as set in Client() or user_data_set() + :param int mid: matches the mid variable returned from the corresponding + `publish()` call, to allow outgoing messages to be tracked. + :param ReasonCode reason_code: the connection reason code received from the broken. + In MQTT v5.0 it's the reason code defined by the standard. + In MQTT v3 it's always the reason code Success + :parama Properties properties: the MQTT v5.0 properties received from the broker. + For MQTT v3.1 and v3.1.1 properties is not provided and an empty Properties + object is always used. + + Note: for QoS = 0, the reason_code and the properties don't really exist, it's the client + library that generate them. It's always an empty properties and a success reason code. + Because the (MQTTv5) standard don't have reason code for PUBLISH packet, the library create them + at PUBACK packet, as if the message was sent with QoS = 1. + + Decorator: @client.publish_callback() (``client`` is the name of the instance which this callback is being attached to) """ + return self._on_publish + + @on_publish.setter + def on_publish(self, func: CallbackOnPublish | None) -> None: with self._callback_mutex: self._on_publish = func - def publish_callback(self): - def decorator(func): + def publish_callback( + self, + ) -> Callable[[CallbackOnPublish], CallbackOnPublish]: + def decorator(func: CallbackOnPublish) -> CallbackOnPublish: self.on_publish = func return func return decorator @property - def on_unsubscribe(self): - """If implemented, called when the broker responds to an unsubscribe - request.""" + def on_unsubscribe(self) -> CallbackOnUnsubscribe | None: + """The callback called when the broker responds to an unsubscribe + request. + + Expected signature for callback API version 2:: + + unsubscribe_callback(client, userdata, mid, reason_code_list, properties) + + Expected signature for callback API version 1 change with MQTT protocol version: + * For MQTT v3.1 and v3.1.1 it's:: + + unsubscribe_callback(client, userdata, mid) + + * For MQTT v5.0 it's:: + + unsubscribe_callback(client, userdata, mid, properties, v1_reason_codes) + + :param Client client: the client instance for this callback + :param userdata: the private user data as set in Client() or user_data_set() + :param mid: matches the mid variable returned from the corresponding + unsubscribe() call. + :param list[ReasonCode] reason_code_list: reason codes received from the broker for each unsubscription. + In MQTT v5.0 it's the reason code defined by the standard. + In MQTT v3, there is not equivalent from broken and empty list + is always used. + :param Properties properties: the MQTT v5.0 properties received from the broker. + For MQTT v3.1 and v3.1.1 properties is not provided and an empty Properties + object is always used. + :param v1_reason_codes: the MQTT v5.0 reason codes received from the broker for each + unsubscribe topic. A list of ReasonCode instances OR a single + ReasonCode when we unsubscribe from a single topic. + + Decorator: @client.unsubscribe_callback() (``client`` is the name of the + instance which this callback is being attached to) + """ return self._on_unsubscribe @on_unsubscribe.setter - def on_unsubscribe(self, func): - """ Define the unsubscribe callback implementation. - - Expected signature for MQTT v3.1.1 and v3.1 is: - unsubscribe_callback(client, userdata, mid) - - and for MQTT v5.0: - unsubscribe_callback(client, userdata, mid, properties, reasonCodes) - - client: the client instance for this callback - userdata: the private user data as set in Client() or userdata_set() - mid: matches the mid variable returned from the corresponding - unsubscribe() call. - properties: the MQTT v5.0 properties received from the broker. A - list of Properties class instances. - reasonCodes: the MQTT v5.0 reason codes received from the broker for each - unsubscribe topic. A list of ReasonCodes instances - - Decorator: @client.unsubscribe_callback() (```client``` is the name of the - instance which this callback is being attached to) - """ + def on_unsubscribe(self, func: CallbackOnUnsubscribe | None) -> None: with self._callback_mutex: self._on_unsubscribe = func - def unsubscribe_callback(self): - def decorator(func): + def unsubscribe_callback( + self, + ) -> Callable[[CallbackOnUnsubscribe], CallbackOnUnsubscribe]: + def decorator(func: CallbackOnUnsubscribe) -> CallbackOnUnsubscribe: self.on_unsubscribe = func return func return decorator @property - def on_disconnect(self): - """If implemented, called when the client disconnects from the broker. + def on_disconnect(self) -> CallbackOnDisconnect | None: + """The callback called when the client disconnects from the broker. + + Expected signature for callback API version 2:: + + disconnect_callback(client, userdata, disconnect_flags, reason_code, properties) + + Expected signature for callback API version 1 change with MQTT protocol version: + * For MQTT v3.1 and v3.1.1 it's:: + + disconnect_callback(client, userdata, rc) + + * For MQTT it's v5.0:: + + disconnect_callback(client, userdata, reason_code, properties) + + :param Client client: the client instance for this callback + :param userdata: the private user data as set in Client() or user_data_set() + :param DisconnectFlag disconnect_flags: the flags for this disconnection. + :param ReasonCode reason_code: the disconnection reason code possibly received from the broker (see disconnect_flags). + In MQTT v5.0 it's the reason code defined by the standard. + In MQTT v3 it's never received from the broker, we convert an MQTTErrorCode, + see `convert_disconnect_error_code_to_reason_code()`. + `ReasonCode` may be compared to integer. + :param Properties properties: the MQTT v5.0 properties received from the broker. + For MQTT v3.1 and v3.1.1 properties is not provided and an empty Properties + object is always used. + :param int rc: the disconnection result + The rc parameter indicates the disconnection state. If + MQTT_ERR_SUCCESS (0), the callback was called in response to + a disconnect() call. If any other value the disconnection + was unexpected, such as might be caused by a network error. + + Decorator: @client.disconnect_callback() (``client`` is the name of the + instance which this callback is being attached to) + """ return self._on_disconnect @on_disconnect.setter - def on_disconnect(self, func): - """ Define the disconnect callback implementation. - - Expected signature for MQTT v3.1.1 and v3.1 is: - disconnect_callback(client, userdata, rc) - - and for MQTT v5.0: - disconnect_callback(client, userdata, reasonCode, properties) - - client: the client instance for this callback - userdata: the private user data as set in Client() or userdata_set() - rc: the disconnection result - The rc parameter indicates the disconnection state. If - MQTT_ERR_SUCCESS (0), the callback was called in response to - a disconnect() call. If any other value the disconnection - was unexpected, such as might be caused by a network error. - - Decorator: @client.disconnect_callback() (```client``` is the name of the - instance which this callback is being attached to) - - """ + def on_disconnect(self, func: CallbackOnDisconnect | None) -> None: with self._callback_mutex: self._on_disconnect = func - def disconnect_callback(self): - def decorator(func): + def disconnect_callback( + self, + ) -> Callable[[CallbackOnDisconnect], CallbackOnDisconnect]: + def decorator(func: CallbackOnDisconnect) -> CallbackOnDisconnect: self.on_disconnect = func return func return decorator @property - def on_socket_open(self): - """If implemented, called just after the socket was opend.""" - return self._on_socket_open - - @on_socket_open.setter - def on_socket_open(self, func): - """Define the socket_open callback implementation. + def on_socket_open(self) -> CallbackOnSocket | None: + """The callback called just after the socket was opend. This should be used to register the socket to an external event loop for reading. - Expected signature is: + Expected signature is (for all callback API version):: + socket_open_callback(client, userdata, socket) - client: the client instance for this callback - userdata: the private user data as set in Client() or userdata_set() - sock: the socket which was just opened. + :param Client client: the client instance for this callback + :param userdata: the private user data as set in Client() or user_data_set() + :param SocketLike sock: the socket which was just opened. - Decorator: @client.socket_open_callback() (```client``` is the name of the + Decorator: @client.socket_open_callback() (``client`` is the name of the instance which this callback is being attached to) """ + return self._on_socket_open + + @on_socket_open.setter + def on_socket_open(self, func: CallbackOnSocket | None) -> None: with self._callback_mutex: self._on_socket_open = func - def socket_open_callback(self): - def decorator(func): + def socket_open_callback( + self, + ) -> Callable[[CallbackOnSocket], CallbackOnSocket]: + def decorator(func: CallbackOnSocket) -> CallbackOnSocket: self.on_socket_open = func return func return decorator - def _call_socket_open(self): + def _call_socket_open(self, sock: SocketLike) -> None: """Call the socket_open callback with the just-opened socket""" with self._callback_mutex: on_socket_open = self.on_socket_open @@ -2152,7 +2812,7 @@ class Client(object): if on_socket_open: with self._in_callback_mutex: try: - on_socket_open(self, self._userdata, self._sock) + on_socket_open(self, self._userdata, sock) except Exception as err: self._easy_log( MQTT_LOG_ERR, 'Caught exception in on_socket_open: %s', err) @@ -2160,36 +2820,38 @@ class Client(object): raise @property - def on_socket_close(self): - """If implemented, called just before the socket is closed.""" - return self._on_socket_close - - @on_socket_close.setter - def on_socket_close(self, func): - """Define the socket_close callback implementation. + def on_socket_close(self) -> CallbackOnSocket | None: + """The callback called just before the socket is closed. This should be used to unregister the socket from an external event loop for reading. - Expected signature is: + Expected signature is (for all callback API version):: + socket_close_callback(client, userdata, socket) - client: the client instance for this callback - userdata: the private user data as set in Client() or userdata_set() - sock: the socket which is about to be closed. + :param Client client: the client instance for this callback + :param userdata: the private user data as set in Client() or user_data_set() + :param SocketLike sock: the socket which is about to be closed. - Decorator: @client.socket_close_callback() (```client``` is the name of the + Decorator: @client.socket_close_callback() (``client`` is the name of the instance which this callback is being attached to) """ + return self._on_socket_close + + @on_socket_close.setter + def on_socket_close(self, func: CallbackOnSocket | None) -> None: with self._callback_mutex: self._on_socket_close = func - def socket_close_callback(self): - def decorator(func): + def socket_close_callback( + self, + ) -> Callable[[CallbackOnSocket], CallbackOnSocket]: + def decorator(func: CallbackOnSocket) -> CallbackOnSocket: self.on_socket_close = func return func return decorator - def _call_socket_close(self, sock): + def _call_socket_close(self, sock: SocketLike) -> None: """Call the socket_close callback with the about-to-be-closed socket""" with self._callback_mutex: on_socket_close = self.on_socket_close @@ -2205,36 +2867,38 @@ class Client(object): raise @property - def on_socket_register_write(self): - """If implemented, called when the socket needs writing but can't.""" - return self._on_socket_register_write - - @on_socket_register_write.setter - def on_socket_register_write(self, func): - """Define the socket_register_write callback implementation. + def on_socket_register_write(self) -> CallbackOnSocket | None: + """The callback called when the socket needs writing but can't. This should be used to register the socket with an external event loop for writing. - Expected signature is: + Expected signature is (for all callback API version):: + socket_register_write_callback(client, userdata, socket) - client: the client instance for this callback - userdata: the private user data as set in Client() or userdata_set() - sock: the socket which should be registered for writing + :param Client client: the client instance for this callback + :param userdata: the private user data as set in Client() or user_data_set() + :param SocketLike sock: the socket which should be registered for writing - Decorator: @client.socket_register_write_callback() (```client``` is the name of the + Decorator: @client.socket_register_write_callback() (``client`` is the name of the instance which this callback is being attached to) """ + return self._on_socket_register_write + + @on_socket_register_write.setter + def on_socket_register_write(self, func: CallbackOnSocket | None) -> None: with self._callback_mutex: self._on_socket_register_write = func - def socket_register_write_callback(self): - def decorator(func): + def socket_register_write_callback( + self, + ) -> Callable[[CallbackOnSocket], CallbackOnSocket]: + def decorator(func: CallbackOnSocket) -> CallbackOnSocket: self._on_socket_register_write = func return func return decorator - def _call_socket_register_write(self): + def _call_socket_register_write(self) -> None: """Call the socket_register_write callback with the unwritable socket""" if not self._sock or self._registered_write: return @@ -2253,36 +2917,46 @@ class Client(object): raise @property - def on_socket_unregister_write(self): - """If implemented, called when the socket doesn't need writing anymore.""" - return self._on_socket_unregister_write - - @on_socket_unregister_write.setter - def on_socket_unregister_write(self, func): - """Define the socket_unregister_write callback implementation. + def on_socket_unregister_write( + self, + ) -> CallbackOnSocket | None: + """The callback called when the socket doesn't need writing anymore. This should be used to unregister the socket from an external event loop for writing. - Expected signature is: + Expected signature is (for all callback API version):: + socket_unregister_write_callback(client, userdata, socket) - client: the client instance for this callback - userdata: the private user data as set in Client() or userdata_set() - sock: the socket which should be unregistered for writing + :param Client client: the client instance for this callback + :param userdata: the private user data as set in Client() or user_data_set() + :param SocketLike sock: the socket which should be unregistered for writing - Decorator: @client.socket_unregister_write_callback() (```client``` is the name of the + Decorator: @client.socket_unregister_write_callback() (``client`` is the name of the instance which this callback is being attached to) """ + return self._on_socket_unregister_write + + @on_socket_unregister_write.setter + def on_socket_unregister_write( + self, func: CallbackOnSocket | None + ) -> None: with self._callback_mutex: self._on_socket_unregister_write = func - def socket_unregister_write_callback(self): - def decorator(func): + def socket_unregister_write_callback( + self, + ) -> Callable[[CallbackOnSocket], CallbackOnSocket]: + def decorator( + func: CallbackOnSocket, + ) -> CallbackOnSocket: self._on_socket_unregister_write = func return func return decorator - def _call_socket_unregister_write(self, sock=None): + def _call_socket_unregister_write( + self, sock: SocketLike | None = None + ) -> None: """Call the socket_unregister_write callback with the writable socket""" sock = sock or self._sock if not sock or not self._registered_write: @@ -2301,32 +2975,46 @@ class Client(object): if not self.suppress_exceptions: raise - def message_callback_add(self, sub, callback): + def message_callback_add(self, sub: str, callback: CallbackOnMessage) -> None: """Register a message callback for a specific topic. Messages that match 'sub' will be passed to 'callback'. Any - non-matching messages will be passed to the default on_message + non-matching messages will be passed to the default `on_message` callback. Call multiple times with different 'sub' to define multiple topic specific callbacks. Topic specific callbacks may be removed with - message_callback_remove().""" + `message_callback_remove()`. + + See `on_message` for the expected signature of the callback. + + Decorator: @client.topic_callback(sub) (``client`` is the name of the + instance which this callback is being attached to) + + Example:: + + @client.topic_callback("mytopic/#") + def handle_mytopic(client, userdata, message): + ... + """ if callback is None or sub is None: raise ValueError("sub and callback must both be defined.") with self._callback_mutex: self._on_message_filtered[sub] = callback - def topic_callback(self, sub): - def decorator(func): + def topic_callback( + self, sub: str + ) -> Callable[[CallbackOnMessage], CallbackOnMessage]: + def decorator(func: CallbackOnMessage) -> CallbackOnMessage: self.message_callback_add(sub, func) return func return decorator - def message_callback_remove(self, sub): + def message_callback_remove(self, sub: str) -> None: """Remove a message callback previously registered with - message_callback_add().""" + `message_callback_add()`.""" if sub is None: raise ValueError("sub must defined.") @@ -2340,18 +3028,25 @@ class Client(object): # Private functions # ============================================================ - def _loop_rc_handle(self, rc, properties=None): + def _loop_rc_handle( + self, + rc: MQTTErrorCode, + ) -> MQTTErrorCode: if rc: self._sock_close() - if self._state == mqtt_cs_disconnecting: - rc = MQTT_ERR_SUCCESS + if self._state in (_ConnectionState.MQTT_CS_DISCONNECTING, _ConnectionState.MQTT_CS_DISCONNECTED): + self._state = _ConnectionState.MQTT_CS_DISCONNECTED + rc = MQTTErrorCode.MQTT_ERR_SUCCESS - self._do_on_disconnect(rc, properties) + self._do_on_disconnect(packet_from_broker=False, v1_rc=rc) + + if rc == MQTT_ERR_CONN_LOST: + self._state = _ConnectionState.MQTT_CS_CONNECTION_LOST return rc - def _packet_read(self): + def _packet_read(self) -> MQTTErrorCode: # This gets called if pselect() indicates that there is network data # available - ie. at least one byte. What we do depends on what data we # already have. @@ -2369,16 +3064,19 @@ class Client(object): try: command = self._sock_recv(1) except BlockingIOError: - return MQTT_ERR_AGAIN - except ConnectionError as err: + return MQTTErrorCode.MQTT_ERR_AGAIN + except TimeoutError as err: + self._easy_log( + MQTT_LOG_ERR, 'timeout on socket: %s', err) + return MQTTErrorCode.MQTT_ERR_CONN_LOST + except OSError as err: self._easy_log( MQTT_LOG_ERR, 'failed to receive on socket: %s', err) - return MQTT_ERR_CONN_LOST + return MQTTErrorCode.MQTT_ERR_CONN_LOST else: if len(command) == 0: - return MQTT_ERR_CONN_LOST - command, = struct.unpack("!B", command) - self._in_packet['command'] = command + return MQTTErrorCode.MQTT_ERR_CONN_LOST + self._in_packet['command'] = command[0] if self._in_packet['have_remaining'] == 0: # Read remaining @@ -2388,26 +3086,26 @@ class Client(object): try: byte = self._sock_recv(1) except BlockingIOError: - return MQTT_ERR_AGAIN - except ConnectionError as err: + return MQTTErrorCode.MQTT_ERR_AGAIN + except OSError as err: self._easy_log( MQTT_LOG_ERR, 'failed to receive on socket: %s', err) - return MQTT_ERR_CONN_LOST + return MQTTErrorCode.MQTT_ERR_CONN_LOST else: if len(byte) == 0: - return MQTT_ERR_CONN_LOST - byte, = struct.unpack("!B", byte) - self._in_packet['remaining_count'].append(byte) + return MQTTErrorCode.MQTT_ERR_CONN_LOST + byte_value = byte[0] + self._in_packet['remaining_count'].append(byte_value) # Max 4 bytes length for remaining length as defined by protocol. # Anything more likely means a broken/malicious client. if len(self._in_packet['remaining_count']) > 4: - return MQTT_ERR_PROTOCOL + return MQTTErrorCode.MQTT_ERR_PROTOCOL self._in_packet['remaining_length'] += ( - byte & 127) * self._in_packet['remaining_mult'] + byte_value & 127) * self._in_packet['remaining_mult'] self._in_packet['remaining_mult'] = self._in_packet['remaining_mult'] * 128 - if (byte & 128) == 0: + if (byte_value & 128) == 0: break self._in_packet['have_remaining'] = 1 @@ -2418,21 +3116,21 @@ class Client(object): try: data = self._sock_recv(self._in_packet['to_process']) except BlockingIOError: - return MQTT_ERR_AGAIN - except ConnectionError as err: + return MQTTErrorCode.MQTT_ERR_AGAIN + except OSError as err: self._easy_log( MQTT_LOG_ERR, 'failed to receive on socket: %s', err) - return MQTT_ERR_CONN_LOST + return MQTTErrorCode.MQTT_ERR_CONN_LOST else: if len(data) == 0: - return MQTT_ERR_CONN_LOST + return MQTTErrorCode.MQTT_ERR_CONN_LOST self._in_packet['to_process'] -= len(data) self._in_packet['packet'] += data count -= 1 if count == 0: with self._msgtime_mutex: self._last_msg_in = time_func() - return MQTT_ERR_AGAIN + return MQTTErrorCode.MQTT_ERR_AGAIN # All data for this packet is read. self._in_packet['pos'] = 0 @@ -2440,40 +3138,41 @@ class Client(object): # Free data and reset values self._in_packet = { - 'command': 0, - 'have_remaining': 0, - 'remaining_count': [], - 'remaining_mult': 1, - 'remaining_length': 0, - 'packet': bytearray(b""), - 'to_process': 0, - 'pos': 0} + "command": 0, + "have_remaining": 0, + "remaining_count": [], + "remaining_mult": 1, + "remaining_length": 0, + "packet": bytearray(b""), + "to_process": 0, + "pos": 0, + } with self._msgtime_mutex: self._last_msg_in = time_func() return rc - def _packet_write(self): + def _packet_write(self) -> MQTTErrorCode: while True: try: packet = self._out_packet.popleft() except IndexError: - return MQTT_ERR_SUCCESS + return MQTTErrorCode.MQTT_ERR_SUCCESS try: write_length = self._sock_send( packet['packet'][packet['pos']:]) except (AttributeError, ValueError): self._out_packet.appendleft(packet) - return MQTT_ERR_SUCCESS + return MQTTErrorCode.MQTT_ERR_SUCCESS except BlockingIOError: self._out_packet.appendleft(packet) - return MQTT_ERR_AGAIN - except ConnectionError as err: + return MQTTErrorCode.MQTT_ERR_AGAIN + except OSError as err: self._out_packet.appendleft(packet) self._easy_log( MQTT_LOG_ERR, 'failed to receive on socket: %s', err) - return MQTT_ERR_CONN_LOST + return MQTTErrorCode.MQTT_ERR_CONN_LOST if write_length > 0: packet['to_process'] -= write_length @@ -2487,23 +3186,49 @@ class Client(object): if on_publish: with self._in_callback_mutex: try: - on_publish( - self, self._userdata, packet['mid']) + if self._callback_api_version == CallbackAPIVersion.VERSION1: + on_publish = cast(CallbackOnPublish_v1, on_publish) + + on_publish(self, self._userdata, packet["mid"]) + elif self._callback_api_version == CallbackAPIVersion.VERSION2: + on_publish = cast(CallbackOnPublish_v2, on_publish) + + on_publish( + self, + self._userdata, + packet["mid"], + ReasonCode(PacketTypes.PUBACK), + Properties(PacketTypes.PUBACK), + ) + else: + raise RuntimeError("Unsupported callback API version") except Exception as err: self._easy_log( MQTT_LOG_ERR, 'Caught exception in on_publish: %s', err) if not self.suppress_exceptions: raise - packet['info']._set_as_published() + # TODO: Something is odd here. I don't see why packet["info"] can't be None. + # A packet could be produced by _handle_connack with qos=0 and no info + # (around line 3645). Ignore the mypy check for now but I feel there is a bug + # somewhere. + packet['info']._set_as_published() # type: ignore if (packet['command'] & 0xF0) == DISCONNECT: with self._msgtime_mutex: self._last_msg_out = time_func() - self._do_on_disconnect(MQTT_ERR_SUCCESS) + self._do_on_disconnect( + packet_from_broker=False, + v1_rc=MQTTErrorCode.MQTT_ERR_SUCCESS, + ) self._sock_close() - return MQTT_ERR_SUCCESS + # Only change to disconnected if the disconnection was wanted + # by the client (== state was disconnecting). If the broker disconnected + # use unilaterally don't change the state and client may reconnect. + if self._state == _ConnectionState.MQTT_CS_DISCONNECTING: + self._state = _ConnectionState.MQTT_CS_DISCONNECTED + return MQTTErrorCode.MQTT_ERR_SUCCESS else: # We haven't finished with this packet @@ -2514,23 +3239,23 @@ class Client(object): with self._msgtime_mutex: self._last_msg_out = time_func() - return MQTT_ERR_SUCCESS + return MQTTErrorCode.MQTT_ERR_SUCCESS - def _easy_log(self, level, fmt, *args): + def _easy_log(self, level: LogLevel, fmt: str, *args: Any) -> None: if self.on_log is not None: buf = fmt % args try: self.on_log(self, self._userdata, level, buf) - except Exception: + except Exception: # noqa: S110 # Can't _easy_log this, as we'll recurse until we break pass # self._logger will pick this up, so we're fine if self._logger is not None: level_std = LOGGING_LEVEL[level] self._logger.log(level_std, fmt, *args) - def _check_keepalive(self): + def _check_keepalive(self) -> None: if self._keepalive == 0: - return MQTT_ERR_SUCCESS + return now = time_func() @@ -2539,12 +3264,15 @@ class Client(object): last_msg_in = self._last_msg_in if self._sock is not None and (now - last_msg_out >= self._keepalive or now - last_msg_in >= self._keepalive): - if self._state == mqtt_cs_connected and self._ping_t == 0: + if self._state == _ConnectionState.MQTT_CS_CONNECTED and self._ping_t == 0: try: self._send_pingreq() except Exception: self._sock_close() - self._do_on_disconnect(MQTT_ERR_CONN_LOST) + self._do_on_disconnect( + packet_from_broker=False, + v1_rc=MQTTErrorCode.MQTT_ERR_CONN_LOST, + ) else: with self._msgtime_mutex: self._last_msg_out = now @@ -2552,14 +3280,18 @@ class Client(object): else: self._sock_close() - if self._state == mqtt_cs_disconnecting: - rc = MQTT_ERR_SUCCESS + if self._state in (_ConnectionState.MQTT_CS_DISCONNECTING, _ConnectionState.MQTT_CS_DISCONNECTED): + self._state = _ConnectionState.MQTT_CS_DISCONNECTED + rc = MQTTErrorCode.MQTT_ERR_SUCCESS else: - rc = MQTT_ERR_KEEPALIVE + rc = MQTTErrorCode.MQTT_ERR_KEEPALIVE - self._do_on_disconnect(rc) + self._do_on_disconnect( + packet_from_broker=False, + v1_rc=rc, + ) - def _mid_generate(self): + def _mid_generate(self) -> int: with self._mid_generate_mutex: self._last_mid += 1 if self._last_mid == 65536: @@ -2567,44 +3299,47 @@ class Client(object): return self._last_mid @staticmethod - def _topic_wildcard_len_check(topic): - # Search for + or # in a topic. Return MQTT_ERR_INVAL if found. - # Also returns MQTT_ERR_INVAL if the topic string is too long. - # Returns MQTT_ERR_SUCCESS if everything is fine. - if b'+' in topic or b'#' in topic or len(topic) > 65535: - return MQTT_ERR_INVAL - else: - return MQTT_ERR_SUCCESS + def _raise_for_invalid_topic(topic: bytes) -> None: + """ Check if the topic is a topic without wildcard and valid length. + + Raise ValueError if the topic isn't valid. + """ + if b'+' in topic or b'#' in topic: + raise ValueError('Publish topic cannot contain wildcards.') + if len(topic) > 65535: + raise ValueError('Publish topic is too long.') @staticmethod - def _filter_wildcard_len_check(sub): + def _filter_wildcard_len_check(sub: bytes) -> MQTTErrorCode: if (len(sub) == 0 or len(sub) > 65535 or any(b'+' in p or b'#' in p for p in sub.split(b'/') if len(p) > 1) or b'#/' in sub): - return MQTT_ERR_INVAL + return MQTTErrorCode.MQTT_ERR_INVAL else: - return MQTT_ERR_SUCCESS + return MQTTErrorCode.MQTT_ERR_SUCCESS - def _send_pingreq(self): + def _send_pingreq(self) -> MQTTErrorCode: self._easy_log(MQTT_LOG_DEBUG, "Sending PINGREQ") rc = self._send_simple_command(PINGREQ) - if rc == MQTT_ERR_SUCCESS: + if rc == MQTTErrorCode.MQTT_ERR_SUCCESS: self._ping_t = time_func() return rc - def _send_pingresp(self): + def _send_pingresp(self) -> MQTTErrorCode: self._easy_log(MQTT_LOG_DEBUG, "Sending PINGRESP") return self._send_simple_command(PINGRESP) - def _send_puback(self, mid): + def _send_puback(self, mid: int) -> MQTTErrorCode: self._easy_log(MQTT_LOG_DEBUG, "Sending PUBACK (Mid: %d)", mid) return self._send_command_with_mid(PUBACK, mid, False) - def _send_pubcomp(self, mid): + def _send_pubcomp(self, mid: int) -> MQTTErrorCode: self._easy_log(MQTT_LOG_DEBUG, "Sending PUBCOMP (Mid: %d)", mid) return self._send_command_with_mid(PUBCOMP, mid, False) - def _pack_remaining_length(self, packet, remaining_length): + def _pack_remaining_length( + self, packet: bytearray, remaining_length: int + ) -> bytearray: remaining_bytes = [] while True: byte = remaining_length % 128 @@ -2619,19 +3354,30 @@ class Client(object): # FIXME - this doesn't deal with incorrectly large payloads return packet - def _pack_str16(self, packet, data): - if isinstance(data, unicode): - data = data.encode('utf-8') + def _pack_str16(self, packet: bytearray, data: bytes | str) -> None: + data = _force_bytes(data) packet.extend(struct.pack("!H", len(data))) packet.extend(data) - def _send_publish(self, mid, topic, payload=b'', qos=0, retain=False, dup=False, info=None, properties=None): + def _send_publish( + self, + mid: int, + topic: bytes, + payload: bytes = b"", + qos: int = 0, + retain: bool = False, + dup: bool = False, + info: MQTTMessageInfo | None = None, + properties: Properties | None = None, + ) -> MQTTErrorCode: # we assume that topic and payload are already properly encoded - assert not isinstance(topic, unicode) and not isinstance( - payload, unicode) and payload is not None + if not isinstance(topic, bytes): + raise TypeError('topic must be bytes, not str') + if payload and not isinstance(payload, bytes): + raise TypeError('payload must be bytes if set') if self._sock is None: - return MQTT_ERR_NO_CONN + return MQTTErrorCode.MQTT_ERR_NO_CONN command = PUBLISH | ((dup & 0x1) << 3) | (qos << 1) | retain packet = bytearray() @@ -2692,15 +3438,15 @@ class Client(object): return self._packet_queue(PUBLISH, packet, mid, qos, info) - def _send_pubrec(self, mid): + def _send_pubrec(self, mid: int) -> MQTTErrorCode: self._easy_log(MQTT_LOG_DEBUG, "Sending PUBREC (Mid: %d)", mid) return self._send_command_with_mid(PUBREC, mid, False) - def _send_pubrel(self, mid): + def _send_pubrel(self, mid: int) -> MQTTErrorCode: self._easy_log(MQTT_LOG_DEBUG, "Sending PUBREL (Mid: %d)", mid) return self._send_command_with_mid(PUBREL | 2, mid, False) - def _send_command_with_mid(self, command, mid, dup): + def _send_command_with_mid(self, command: int, mid: int, dup: int) -> MQTTErrorCode: # For PUBACK, PUBCOMP, PUBREC, and PUBREL if dup: command |= 0x8 @@ -2709,13 +3455,13 @@ class Client(object): packet = struct.pack('!BBH', command, remaining_length, mid) return self._packet_queue(command, packet, mid, 1) - def _send_simple_command(self, command): + def _send_simple_command(self, command: int) -> MQTTErrorCode: # For DISCONNECT, PINGREQ and PINGRESP remaining_length = 0 packet = struct.pack('!BB', command, remaining_length) return self._packet_queue(command, packet, 0, 0) - def _send_connect(self, keepalive): + def _send_connect(self, keepalive: int) -> MQTTErrorCode: proto_ver = self._protocol # hard-coded UTF-8 encoded string protocol = b"MQTT" if proto_ver >= MQTTv311 else b"MQIsdp" @@ -2725,7 +3471,7 @@ class Client(object): connect_flags = 0 if self._protocol == MQTTv5: - if self._clean_start == True: + if self._clean_start is True: connect_flags |= 0x02 elif self._clean_start == MQTT_CLEAN_START_FIRST_ONLY and self._mqttv5_first_connect: connect_flags |= 0x02 @@ -2768,8 +3514,10 @@ class Client(object): proto_ver |= 0x80 self._pack_remaining_length(packet, remaining_length) - packet.extend(struct.pack("!H" + str(len(protocol)) + "sBBH", len(protocol), protocol, proto_ver, connect_flags, - keepalive)) + packet.extend(struct.pack( + f"!H{len(protocol)}sBBH", + len(protocol), protocol, proto_ver, connect_flags, keepalive, + )) if self._protocol == MQTTv5: packet += packed_connect_properties @@ -2818,7 +3566,11 @@ class Client(object): ) return self._packet_queue(command, packet, 0, 0) - def _send_disconnect(self, reasoncode=None, properties=None): + def _send_disconnect( + self, + reasoncode: ReasonCode | None = None, + properties: Properties | None = None, + ) -> MQTTErrorCode: if self._protocol == MQTTv5: self._easy_log(MQTT_LOG_DEBUG, "Sending DISCONNECT reasonCode=%s properties=%s", reasoncode, @@ -2836,7 +3588,7 @@ class Client(object): if self._protocol == MQTTv5: if properties is not None or reasoncode is not None: if reasoncode is None: - reasoncode = ReasonCodes(DISCONNECT >> 4, identifier=0) + reasoncode = ReasonCode(DISCONNECT >> 4, identifier=0) remaining_length += 1 if properties is not None: packed_props = properties.pack() @@ -2845,14 +3597,19 @@ class Client(object): self._pack_remaining_length(packet, remaining_length) if self._protocol == MQTTv5: - if reasoncode != None: + if reasoncode is not None: packet += reasoncode.pack() - if properties != None: + if properties is not None: packet += packed_props return self._packet_queue(command, packet, 0, 0) - def _send_subscribe(self, dup, topics, properties=None): + def _send_subscribe( + self, + dup: int, + topics: Sequence[tuple[bytes, SubscribeOptions | int]], + properties: Properties | None = None, + ) -> tuple[MQTTErrorCode, int]: remaining_length = 2 if self._protocol == MQTTv5: if properties is None: @@ -2876,9 +3633,9 @@ class Client(object): for t, q in topics: self._pack_str16(packet, t) if self._protocol == MQTTv5: - packet += q.pack() + packet += q.pack() # type: ignore else: - packet.append(q) + packet.append(q) # type: ignore self._easy_log( MQTT_LOG_DEBUG, @@ -2889,7 +3646,12 @@ class Client(object): ) return (self._packet_queue(command, packet, local_mid, 1), local_mid) - def _send_unsubscribe(self, dup, topics, properties=None): + def _send_unsubscribe( + self, + dup: int, + topics: list[bytes], + properties: Properties | None = None, + ) -> tuple[MQTTErrorCode, int]: remaining_length = 2 if self._protocol == MQTTv5: if properties is None: @@ -2933,16 +3695,16 @@ class Client(object): ) return (self._packet_queue(command, packet, local_mid, 1), local_mid) - def _check_clean_session(self): + def _check_clean_session(self) -> bool: if self._protocol == MQTTv5: if self._clean_start == MQTT_CLEAN_START_FIRST_ONLY: return self._mqttv5_first_connect else: - return self._clean_start + return self._clean_start # type: ignore else: return self._clean_session - def _messages_reconnect_reset_out(self): + def _messages_reconnect_reset_out(self) -> None: with self._out_message_mutex: self._inflight_messages = 0 for m in self._out_messages.values(): @@ -2971,7 +3733,7 @@ class Client(object): else: m.state = mqtt_ms_queued - def _messages_reconnect_reset_in(self): + def _messages_reconnect_reset_in(self) -> None: with self._in_message_mutex: if self._check_clean_session(): self._in_messages = collections.OrderedDict() @@ -2984,19 +3746,27 @@ class Client(object): # Preserve current state pass - def _messages_reconnect_reset(self): + def _messages_reconnect_reset(self) -> None: self._messages_reconnect_reset_out() self._messages_reconnect_reset_in() - def _packet_queue(self, command, packet, mid, qos, info=None): - mpkt = { - 'command': command, - 'mid': mid, - 'qos': qos, - 'pos': 0, - 'to_process': len(packet), - 'packet': packet, - 'info': info} + def _packet_queue( + self, + command: int, + packet: bytes, + mid: int, + qos: int, + info: MQTTMessageInfo | None = None, + ) -> MQTTErrorCode: + mpkt: _OutPacket = { + "command": command, + "mid": mid, + "qos": qos, + "pos": 0, + "to_process": len(packet), + "packet": packet, + "info": info, + } self._out_packet.append(mpkt) @@ -3017,9 +3787,9 @@ class Client(object): self._call_socket_register_write() - return MQTT_ERR_SUCCESS + return MQTTErrorCode.MQTT_ERR_SUCCESS - def _packet_handle(self): + def _packet_handle(self) -> MQTTErrorCode: cmd = self._in_packet['command'] & 0xF0 if cmd == PINGREQ: return self._handle_pingreq() @@ -3038,38 +3808,40 @@ class Client(object): elif cmd == CONNACK: return self._handle_connack() elif cmd == SUBACK: - return self._handle_suback() + self._handle_suback() + return MQTTErrorCode.MQTT_ERR_SUCCESS elif cmd == UNSUBACK: return self._handle_unsuback() elif cmd == DISCONNECT and self._protocol == MQTTv5: # only allowed in MQTT 5.0 - return self._handle_disconnect() + self._handle_disconnect() + return MQTTErrorCode.MQTT_ERR_SUCCESS else: # If we don't recognise the command, return an error straight away. self._easy_log(MQTT_LOG_ERR, "Error: Unrecognised command %s", cmd) - return MQTT_ERR_PROTOCOL + return MQTTErrorCode.MQTT_ERR_PROTOCOL - def _handle_pingreq(self): + def _handle_pingreq(self) -> MQTTErrorCode: if self._in_packet['remaining_length'] != 0: - return MQTT_ERR_PROTOCOL + return MQTTErrorCode.MQTT_ERR_PROTOCOL self._easy_log(MQTT_LOG_DEBUG, "Received PINGREQ") return self._send_pingresp() - def _handle_pingresp(self): + def _handle_pingresp(self) -> MQTTErrorCode: if self._in_packet['remaining_length'] != 0: - return MQTT_ERR_PROTOCOL + return MQTTErrorCode.MQTT_ERR_PROTOCOL # No longer waiting for a PINGRESP. self._ping_t = 0 self._easy_log(MQTT_LOG_DEBUG, "Received PINGRESP") - return MQTT_ERR_SUCCESS + return MQTTErrorCode.MQTT_ERR_SUCCESS - def _handle_connack(self): + def _handle_connack(self) -> MQTTErrorCode: if self._protocol == MQTTv5: if self._in_packet['remaining_length'] < 2: - return MQTT_ERR_PROTOCOL + return MQTTErrorCode.MQTT_ERR_PROTOCOL elif self._in_packet['remaining_length'] != 2: - return MQTT_ERR_PROTOCOL + return MQTTErrorCode.MQTT_ERR_PROTOCOL if self._protocol == MQTTv5: (flags, result) = struct.unpack( @@ -3077,14 +3849,16 @@ class Client(object): if result == 1: # This is probably a failure from a broker that doesn't support # MQTT v5. - reason = 132 # Unsupported protocol version + reason = ReasonCode(CONNACK >> 4, aName="Unsupported protocol version") properties = None else: - reason = ReasonCodes(CONNACK >> 4, identifier=result) + reason = ReasonCode(CONNACK >> 4, identifier=result) properties = Properties(CONNACK >> 4) properties.unpack(self._in_packet['packet'][2:]) else: (flags, result) = struct.unpack("!BB", self._in_packet['packet']) + reason = convert_connack_rc_to_reason_code(result) + properties = None if self._protocol == MQTTv311: if result == CONNACK_REFUSED_PROTOCOL_VERSION: if not self._reconnect_on_failure: @@ -3106,11 +3880,11 @@ class Client(object): "Received CONNACK (%s, %s), attempting to use non-empty CID", flags, result, ) - self._client_id = base62(uuid.uuid4().int, padding=22) + self._client_id = _base62(uuid.uuid4().int, padding=22).encode("utf8") return self.reconnect() if result == 0: - self._state = mqtt_cs_connected + self._state = _ConnectionState.MQTT_CS_CONNECTED self._reconnect_delay = None if self._protocol == MQTTv5: @@ -3131,12 +3905,36 @@ class Client(object): flags_dict['session present'] = flags & 0x01 with self._in_callback_mutex: try: - if self._protocol == MQTTv5: - on_connect(self, self._userdata, - flags_dict, reason, properties) - else: + if self._callback_api_version == CallbackAPIVersion.VERSION1: + if self._protocol == MQTTv5: + on_connect = cast(CallbackOnConnect_v1_mqtt5, on_connect) + + on_connect(self, self._userdata, + flags_dict, reason, properties) + else: + on_connect = cast(CallbackOnConnect_v1_mqtt3, on_connect) + + on_connect( + self, self._userdata, flags_dict, result) + elif self._callback_api_version == CallbackAPIVersion.VERSION2: + on_connect = cast(CallbackOnConnect_v2, on_connect) + + connect_flags = ConnectFlags( + session_present=flags_dict['session present'] > 0 + ) + + if properties is None: + properties = Properties(PacketTypes.CONNACK) + on_connect( - self, self._userdata, flags_dict, result) + self, + self._userdata, + connect_flags, + reason, + properties, + ) + else: + raise RuntimeError("Unsupported callback API version") except Exception as err: self._easy_log( MQTT_LOG_ERR, 'Caught exception in on_connect: %s', err) @@ -3144,7 +3942,7 @@ class Client(object): raise if result == 0: - rc = 0 + rc = MQTTErrorCode.MQTT_ERR_SUCCESS with self._out_message_mutex: for m in self._out_messages.values(): m.timestamp = time_func() @@ -3163,7 +3961,7 @@ class Client(object): m.dup, properties=m.properties ) - if rc != 0: + if rc != MQTTErrorCode.MQTT_ERR_SUCCESS: return rc elif m.qos == 1: if m.state == mqtt_ms_publish: @@ -3179,7 +3977,7 @@ class Client(object): m.dup, properties=m.properties ) - if rc != 0: + if rc != MQTTErrorCode.MQTT_ERR_SUCCESS: return rc elif m.qos == 2: if m.state == mqtt_ms_publish: @@ -3195,28 +3993,28 @@ class Client(object): m.dup, properties=m.properties ) - if rc != 0: + if rc != MQTTErrorCode.MQTT_ERR_SUCCESS: return rc elif m.state == mqtt_ms_resend_pubrel: self._inflight_messages += 1 m.state = mqtt_ms_wait_for_pubcomp with self._in_callback_mutex: # Don't call loop_write after _send_publish() rc = self._send_pubrel(m.mid) - if rc != 0: + if rc != MQTTErrorCode.MQTT_ERR_SUCCESS: return rc self.loop_write() # Process outgoing messages that have just been queued up return rc elif result > 0 and result < 6: - return MQTT_ERR_CONN_REFUSED + return MQTTErrorCode.MQTT_ERR_CONN_REFUSED else: - return MQTT_ERR_PROTOCOL + return MQTTErrorCode.MQTT_ERR_PROTOCOL - def _handle_disconnect(self): + def _handle_disconnect(self) -> None: packet_type = DISCONNECT >> 4 reasonCode = properties = None if self._in_packet['remaining_length'] > 2: - reasonCode = ReasonCodes(packet_type) + reasonCode = ReasonCode(packet_type) reasonCode.unpack(self._in_packet['packet']) if self._in_packet['remaining_length'] > 3: properties = Properties(packet_type) @@ -3227,26 +4025,28 @@ class Client(object): properties ) - self._loop_rc_handle(reasonCode, properties) + self._sock_close() + self._do_on_disconnect( + packet_from_broker=True, + v1_rc=MQTTErrorCode.MQTT_ERR_SUCCESS, # If reason is absent (remaining length < 1), it means normal disconnection + reason=reasonCode, + properties=properties, + ) - return MQTT_ERR_SUCCESS - - def _handle_suback(self): + def _handle_suback(self) -> None: self._easy_log(MQTT_LOG_DEBUG, "Received SUBACK") - pack_format = "!H" + str(len(self._in_packet['packet']) - 2) + 's' + pack_format = f"!H{len(self._in_packet['packet']) - 2}s" (mid, packet) = struct.unpack(pack_format, self._in_packet['packet']) if self._protocol == MQTTv5: properties = Properties(SUBACK >> 4) props, props_len = properties.unpack(packet) - reasoncodes = [] - for c in packet[props_len:]: - if sys.version_info[0] < 3: - c = ord(c) - reasoncodes.append(ReasonCodes(SUBACK >> 4, identifier=c)) + reasoncodes = [ReasonCode(SUBACK >> 4, identifier=c) for c in packet[props_len:]] else: - pack_format = "!" + "B" * len(packet) + pack_format = f"!{'B' * len(packet)}" granted_qos = struct.unpack(pack_format, packet) + reasoncodes = [ReasonCode(SUBACK >> 4, identifier=c) for c in granted_qos] + properties = Properties(SUBACK >> 4) with self._callback_mutex: on_subscribe = self.on_subscribe @@ -3254,36 +4054,49 @@ class Client(object): if on_subscribe: with self._in_callback_mutex: # Don't call loop_write after _send_publish() try: - if self._protocol == MQTTv5: + if self._callback_api_version == CallbackAPIVersion.VERSION1: + if self._protocol == MQTTv5: + on_subscribe = cast(CallbackOnSubscribe_v1_mqtt5, on_subscribe) + + on_subscribe( + self, self._userdata, mid, reasoncodes, properties) + else: + on_subscribe = cast(CallbackOnSubscribe_v1_mqtt3, on_subscribe) + + on_subscribe( + self, self._userdata, mid, granted_qos) + elif self._callback_api_version == CallbackAPIVersion.VERSION2: + on_subscribe = cast(CallbackOnSubscribe_v2, on_subscribe) + on_subscribe( - self, self._userdata, mid, reasoncodes, properties) + self, + self._userdata, + mid, + reasoncodes, + properties, + ) else: - on_subscribe( - self, self._userdata, mid, granted_qos) + raise RuntimeError("Unsupported callback API version") except Exception as err: self._easy_log( MQTT_LOG_ERR, 'Caught exception in on_subscribe: %s', err) if not self.suppress_exceptions: raise - return MQTT_ERR_SUCCESS - - def _handle_publish(self): - rc = 0 - + def _handle_publish(self) -> MQTTErrorCode: header = self._in_packet['command'] message = MQTTMessage() - message.dup = (header & 0x08) >> 3 + message.dup = ((header & 0x08) >> 3) != 0 message.qos = (header & 0x06) >> 1 - message.retain = (header & 0x01) + message.retain = (header & 0x01) != 0 - pack_format = "!H" + str(len(self._in_packet['packet']) - 2) + 's' + pack_format = f"!H{len(self._in_packet['packet']) - 2}s" (slen, packet) = struct.unpack(pack_format, self._in_packet['packet']) - pack_format = '!' + str(slen) + 's' + str(len(packet) - slen) + 's' + pack_format = f"!{slen}s{len(packet) - slen}s" (topic, packet) = struct.unpack(pack_format, packet) if self._protocol != MQTTv5 and len(topic) == 0: - return MQTT_ERR_PROTOCOL + return MQTTErrorCode.MQTT_ERR_PROTOCOL # Handle topics with invalid UTF-8 # This replaces an invalid topic with a message and the hex @@ -3292,12 +4105,12 @@ class Client(object): try: print_topic = topic.decode('utf-8') except UnicodeDecodeError: - print_topic = "TOPIC WITH INVALID UTF-8: " + str(topic) + print_topic = f"TOPIC WITH INVALID UTF-8: {topic!r}" message.topic = topic if message.qos > 0: - pack_format = "!H" + str(len(packet) - 2) + 's' + pack_format = f"!H{len(packet) - 2}s" (message.mid, packet) = struct.unpack(pack_format, packet) if self._protocol == MQTTv5: @@ -3325,27 +4138,63 @@ class Client(object): message.timestamp = time_func() if message.qos == 0: self._handle_on_message(message) - return MQTT_ERR_SUCCESS + return MQTTErrorCode.MQTT_ERR_SUCCESS elif message.qos == 1: self._handle_on_message(message) - return self._send_puback(message.mid) + if self._manual_ack: + return MQTTErrorCode.MQTT_ERR_SUCCESS + else: + return self._send_puback(message.mid) elif message.qos == 2: + rc = self._send_pubrec(message.mid) + message.state = mqtt_ms_wait_for_pubrel with self._in_message_mutex: self._in_messages[message.mid] = message + return rc else: - return MQTT_ERR_PROTOCOL + return MQTTErrorCode.MQTT_ERR_PROTOCOL - def _handle_pubrel(self): + def ack(self, mid: int, qos: int) -> MQTTErrorCode: + """ + send an acknowledgement for a given message id (stored in :py:attr:`message.mid `). + only useful in QoS>=1 and ``manual_ack=True`` (option of `Client`) + """ + if self._manual_ack : + if qos == 1: + return self._send_puback(mid) + elif qos == 2: + return self._send_pubcomp(mid) + + return MQTTErrorCode.MQTT_ERR_SUCCESS + + def manual_ack_set(self, on: bool) -> None: + """ + The paho library normally acknowledges messages as soon as they are delivered to the caller. + If manual_ack is turned on, then the caller MUST manually acknowledge every message once + application processing is complete using `ack()` + """ + self._manual_ack = on + + + def _handle_pubrel(self) -> MQTTErrorCode: if self._protocol == MQTTv5: if self._in_packet['remaining_length'] < 2: - return MQTT_ERR_PROTOCOL + return MQTTErrorCode.MQTT_ERR_PROTOCOL elif self._in_packet['remaining_length'] != 2: - return MQTT_ERR_PROTOCOL + return MQTTErrorCode.MQTT_ERR_PROTOCOL - mid, = struct.unpack("!H", self._in_packet['packet']) + mid, = struct.unpack("!H", self._in_packet['packet'][:2]) + if self._protocol == MQTTv5: + if self._in_packet['remaining_length'] > 2: + reasonCode = ReasonCode(PUBREL >> 4) + reasonCode.unpack(self._in_packet['packet'][2:]) + if self._in_packet['remaining_length'] > 3: + properties = Properties(PUBREL >> 4) + props, props_len = properties.unpack( + self._in_packet['packet'][3:]) self._easy_log(MQTT_LOG_DEBUG, "Received PUBREL (Mid: %d)", mid) with self._in_message_mutex: @@ -3358,18 +4207,21 @@ class Client(object): if self._max_inflight_messages > 0: with self._out_message_mutex: rc = self._update_inflight() - if rc != MQTT_ERR_SUCCESS: + if rc != MQTTErrorCode.MQTT_ERR_SUCCESS: return rc # FIXME: this should only be done if the message is known # If unknown it's a protocol error and we should close the connection. # But since we don't have (on disk) persistence for the session, it # is possible that we must known about this message. - # Choose to acknwoledge this messsage (and thus losing a message) but + # Choose to acknowledge this message (thus losing a message) but # avoid hanging. See #284. - return self._send_pubcomp(mid) + if self._manual_ack: + return MQTTErrorCode.MQTT_ERR_SUCCESS + else: + return self._send_pubcomp(mid) - def _update_inflight(self): + def _update_inflight(self) -> MQTTErrorCode: # Dont lock message_mutex here for m in self._out_messages.values(): if self._inflight_messages < self._max_inflight_messages: @@ -3388,23 +4240,23 @@ class Client(object): m.dup, properties=m.properties, ) - if rc != 0: + if rc != MQTTErrorCode.MQTT_ERR_SUCCESS: return rc else: - return MQTT_ERR_SUCCESS - return MQTT_ERR_SUCCESS + return MQTTErrorCode.MQTT_ERR_SUCCESS + return MQTTErrorCode.MQTT_ERR_SUCCESS - def _handle_pubrec(self): + def _handle_pubrec(self) -> MQTTErrorCode: if self._protocol == MQTTv5: if self._in_packet['remaining_length'] < 2: - return MQTT_ERR_PROTOCOL + return MQTTErrorCode.MQTT_ERR_PROTOCOL elif self._in_packet['remaining_length'] != 2: - return MQTT_ERR_PROTOCOL + return MQTTErrorCode.MQTT_ERR_PROTOCOL mid, = struct.unpack("!H", self._in_packet['packet'][:2]) if self._protocol == MQTTv5: if self._in_packet['remaining_length'] > 2: - reasonCode = ReasonCodes(PUBREC >> 4) + reasonCode = ReasonCode(PUBREC >> 4) reasonCode.unpack(self._in_packet['packet'][2:]) if self._in_packet['remaining_length'] > 3: properties = Properties(PUBREC >> 4) @@ -3419,27 +4271,27 @@ class Client(object): msg.timestamp = time_func() return self._send_pubrel(mid) - return MQTT_ERR_SUCCESS + return MQTTErrorCode.MQTT_ERR_SUCCESS - def _handle_unsuback(self): + def _handle_unsuback(self) -> MQTTErrorCode: if self._protocol == MQTTv5: if self._in_packet['remaining_length'] < 4: - return MQTT_ERR_PROTOCOL + return MQTTErrorCode.MQTT_ERR_PROTOCOL elif self._in_packet['remaining_length'] != 2: - return MQTT_ERR_PROTOCOL + return MQTTErrorCode.MQTT_ERR_PROTOCOL mid, = struct.unpack("!H", self._in_packet['packet'][:2]) if self._protocol == MQTTv5: packet = self._in_packet['packet'][2:] properties = Properties(UNSUBACK >> 4) props, props_len = properties.unpack(packet) - reasoncodes = [] - for c in packet[props_len:]: - if sys.version_info[0] < 3: - c = ord(c) - reasoncodes.append(ReasonCodes(UNSUBACK >> 4, identifier=c)) - if len(reasoncodes) == 1: - reasoncodes = reasoncodes[0] + reasoncodes_list = [ + ReasonCode(UNSUBACK >> 4, identifier=c) + for c in packet[props_len:] + ] + else: + reasoncodes_list = [] + properties = Properties(UNSUBACK >> 4) self._easy_log(MQTT_LOG_DEBUG, "Received UNSUBACK (Mid: %d)", mid) with self._callback_mutex: @@ -3448,45 +4300,119 @@ class Client(object): if on_unsubscribe: with self._in_callback_mutex: try: - if self._protocol == MQTTv5: + if self._callback_api_version == CallbackAPIVersion.VERSION1: + if self._protocol == MQTTv5: + on_unsubscribe = cast(CallbackOnUnsubscribe_v1_mqtt5, on_unsubscribe) + + reasoncodes: ReasonCode | list[ReasonCode] = reasoncodes_list + if len(reasoncodes_list) == 1: + reasoncodes = reasoncodes_list[0] + + on_unsubscribe( + self, self._userdata, mid, properties, reasoncodes) + else: + on_unsubscribe = cast(CallbackOnUnsubscribe_v1_mqtt3, on_unsubscribe) + + on_unsubscribe(self, self._userdata, mid) + elif self._callback_api_version == CallbackAPIVersion.VERSION2: + on_unsubscribe = cast(CallbackOnUnsubscribe_v2, on_unsubscribe) + + if properties is None: + properties = Properties(PacketTypes.CONNACK) + on_unsubscribe( - self, self._userdata, mid, properties, reasoncodes) + self, + self._userdata, + mid, + reasoncodes_list, + properties, + ) else: - on_unsubscribe(self, self._userdata, mid) + raise RuntimeError("Unsupported callback API version") except Exception as err: self._easy_log( MQTT_LOG_ERR, 'Caught exception in on_unsubscribe: %s', err) if not self.suppress_exceptions: raise - return MQTT_ERR_SUCCESS + return MQTTErrorCode.MQTT_ERR_SUCCESS - def _do_on_disconnect(self, rc, properties=None): + def _do_on_disconnect( + self, + packet_from_broker: bool, + v1_rc: MQTTErrorCode, + reason: ReasonCode | None = None, + properties: Properties | None = None, + ) -> None: with self._callback_mutex: on_disconnect = self.on_disconnect if on_disconnect: with self._in_callback_mutex: try: - if self._protocol == MQTTv5: + if self._callback_api_version == CallbackAPIVersion.VERSION1: + if self._protocol == MQTTv5: + on_disconnect = cast(CallbackOnDisconnect_v1_mqtt5, on_disconnect) + + if packet_from_broker: + on_disconnect(self, self._userdata, reason, properties) + else: + on_disconnect(self, self._userdata, v1_rc, None) + else: + on_disconnect = cast(CallbackOnDisconnect_v1_mqtt3, on_disconnect) + + on_disconnect(self, self._userdata, v1_rc) + elif self._callback_api_version == CallbackAPIVersion.VERSION2: + on_disconnect = cast(CallbackOnDisconnect_v2, on_disconnect) + + disconnect_flags = DisconnectFlags( + is_disconnect_packet_from_server=packet_from_broker + ) + + if reason is None: + reason = convert_disconnect_error_code_to_reason_code(v1_rc) + + if properties is None: + properties = Properties(PacketTypes.DISCONNECT) + on_disconnect( - self, self._userdata, rc, properties) + self, + self._userdata, + disconnect_flags, + reason, + properties, + ) else: - on_disconnect(self, self._userdata, rc) + raise RuntimeError("Unsupported callback API version") except Exception as err: self._easy_log( MQTT_LOG_ERR, 'Caught exception in on_disconnect: %s', err) if not self.suppress_exceptions: raise - def _do_on_publish(self, mid): + def _do_on_publish(self, mid: int, reason_code: ReasonCode, properties: Properties) -> MQTTErrorCode: with self._callback_mutex: on_publish = self.on_publish if on_publish: with self._in_callback_mutex: try: - on_publish(self, self._userdata, mid) + if self._callback_api_version == CallbackAPIVersion.VERSION1: + on_publish = cast(CallbackOnPublish_v1, on_publish) + + on_publish(self, self._userdata, mid) + elif self._callback_api_version == CallbackAPIVersion.VERSION2: + on_publish = cast(CallbackOnPublish_v2, on_publish) + + on_publish( + self, + self._userdata, + mid, + reason_code, + properties, + ) + else: + raise RuntimeError("Unsupported callback API version") except Exception as err: self._easy_log( MQTT_LOG_ERR, 'Caught exception in on_publish: %s', err) @@ -3499,26 +4425,28 @@ class Client(object): self._inflight_messages -= 1 if self._max_inflight_messages > 0: rc = self._update_inflight() - if rc != MQTT_ERR_SUCCESS: + if rc != MQTTErrorCode.MQTT_ERR_SUCCESS: return rc - return MQTT_ERR_SUCCESS + return MQTTErrorCode.MQTT_ERR_SUCCESS - def _handle_pubackcomp(self, cmd): + def _handle_pubackcomp( + self, cmd: Literal['PUBACK'] | Literal['PUBCOMP'] + ) -> MQTTErrorCode: if self._protocol == MQTTv5: if self._in_packet['remaining_length'] < 2: - return MQTT_ERR_PROTOCOL + return MQTTErrorCode.MQTT_ERR_PROTOCOL elif self._in_packet['remaining_length'] != 2: - return MQTT_ERR_PROTOCOL + return MQTTErrorCode.MQTT_ERR_PROTOCOL - packet_type = PUBACK if cmd == "PUBACK" else PUBCOMP - packet_type = packet_type >> 4 + packet_type_enum = PUBACK if cmd == "PUBACK" else PUBCOMP + packet_type = packet_type_enum.value >> 4 mid, = struct.unpack("!H", self._in_packet['packet'][:2]) + reasonCode = ReasonCode(packet_type) + properties = Properties(packet_type) if self._protocol == MQTTv5: if self._in_packet['remaining_length'] > 2: - reasonCode = ReasonCodes(packet_type) reasonCode.unpack(self._in_packet['packet'][2:]) if self._in_packet['remaining_length'] > 3: - properties = Properties(packet_type) props, props_len = properties.unpack( self._in_packet['packet'][3:]) self._easy_log(MQTT_LOG_DEBUG, "Received %s (Mid: %d)", cmd, mid) @@ -3526,13 +4454,12 @@ class Client(object): with self._out_message_mutex: if mid in self._out_messages: # Only inform the client the message has been sent once. - rc = self._do_on_publish(mid) + rc = self._do_on_publish(mid, reasonCode, properties) return rc - return MQTT_ERR_SUCCESS + return MQTTErrorCode.MQTT_ERR_SUCCESS - def _handle_on_message(self, message): - matched = False + def _handle_on_message(self, message: MQTTMessage) -> None: try: topic = message.topic @@ -3542,8 +4469,7 @@ class Client(object): on_message_callbacks = [] with self._callback_mutex: if topic is not None: - for callback in self._on_message_filtered.iter_match(message.topic): - on_message_callbacks.append(callback) + on_message_callbacks = list(self._on_message_filtered.iter_match(message.topic)) if len(on_message_callbacks) == 0: on_message = self.on_message @@ -3575,7 +4501,7 @@ class Client(object): raise - def _handle_on_connect_fail(self): + def _handle_on_connect_fail(self) -> None: with self._callback_mutex: on_connect_fail = self.on_connect_fail @@ -3587,10 +4513,10 @@ class Client(object): self._easy_log( MQTT_LOG_ERR, 'Caught exception in on_connect_fail: %s', err) - def _thread_main(self): + def _thread_main(self) -> None: self.loop_forever(retry_first_connection=True) - def _reconnect_wait(self): + def _reconnect_wait(self) -> None: # See reconnect_delay_set for details now = time_func() with self._reconnect_delay_mutex: @@ -3605,7 +4531,7 @@ class Client(object): target_time = now + self._reconnect_delay remaining = target_time - now - while (self._state != mqtt_cs_disconnecting + while (self._state not in (_ConnectionState.MQTT_CS_DISCONNECTING, _ConnectionState.MQTT_CS_DISCONNECTED) and not self._thread_terminate and remaining > 0): @@ -3613,10 +4539,10 @@ class Client(object): remaining = target_time - time_func() @staticmethod - def _proxy_is_valid(p): - def check(t, a): + def _proxy_is_valid(p) -> bool: # type: ignore[no-untyped-def] + def check(t, a) -> bool: # type: ignore[no-untyped-def] return (socks is not None and - t in set([socks.HTTP, socks.SOCKS4, socks.SOCKS5]) and a) + t in {socks.HTTP, socks.SOCKS4, socks.SOCKS5} and a) if isinstance(p, dict): return check(p.get("proxy_type"), p.get("proxy_addr")) @@ -3625,7 +4551,7 @@ class Client(object): else: return False - def _get_proxy(self): + def _get_proxy(self) -> dict[str, Any] | None: if socks is None: return None @@ -3636,11 +4562,11 @@ class Client(object): # Next, check for an mqtt_proxy environment variable as long as the host # we're trying to connect to isn't listed under the no_proxy environment # variable (matches built-in module urllib's behavior) - if not (hasattr(urllib_dot_request, "proxy_bypass") and - urllib_dot_request.proxy_bypass(self._host)): - env_proxies = urllib_dot_request.getproxies() + if not (hasattr(urllib.request, "proxy_bypass") and + urllib.request.proxy_bypass(self._host)): + env_proxies = urllib.request.getproxies() if "mqtt" in env_proxies: - parts = urllib_dot_parse.urlparse(env_proxies["mqtt"]) + parts = urllib.parse.urlparse(env_proxies["mqtt"]) if parts.scheme == "http": proxy = { "proxy_type": socks.HTTP, @@ -3668,24 +4594,74 @@ class Client(object): # None to indicate that the connection should be handled normally return None - def _create_socket_connection(self): + def _create_socket(self) -> SocketLike: + sock = self._create_socket_connection() + if self._ssl: + sock = self._ssl_wrap_socket(sock) + + if self._transport == "websockets": + sock.settimeout(self._keepalive) + return _WebsocketWrapper( + socket=sock, + host=self._host, + port=self._port, + is_ssl=self._ssl, + path=self._websocket_path, + extra_headers=self._websocket_extra_headers, + ) + + return sock + + def _create_socket_connection(self) -> _socket.socket: proxy = self._get_proxy() addr = (self._host, self._port) source = (self._bind_address, self._bind_port) - - if sys.version_info < (2, 7) or (3, 0) < sys.version_info < (3, 2): - # Have to short-circuit here because of unsupported source_address - # param in earlier Python versions. - return socket.create_connection(addr, timeout=self._connect_timeout) - if proxy: return socks.create_connection(addr, timeout=self._connect_timeout, source_address=source, **proxy) else: return socket.create_connection(addr, timeout=self._connect_timeout, source_address=source) + def _ssl_wrap_socket(self, tcp_sock: _socket.socket) -> ssl.SSLSocket: + if self._ssl_context is None: + raise ValueError( + "Impossible condition. _ssl_context should never be None if _ssl is True" + ) -class WebsocketWrapper(object): + verify_host = not self._tls_insecure + try: + # Try with server_hostname, even it's not supported in certain scenarios + ssl_sock = self._ssl_context.wrap_socket( + tcp_sock, + server_hostname=self._host, + do_handshake_on_connect=False, + ) + except ssl.CertificateError: + # CertificateError is derived from ValueError + raise + except ValueError: + # Python version requires SNI in order to handle server_hostname, but SNI is not available + ssl_sock = self._ssl_context.wrap_socket( + tcp_sock, + do_handshake_on_connect=False, + ) + else: + # If SSL context has already checked hostname, then don't need to do it again + if getattr(self._ssl_context, 'check_hostname', False): # type: ignore + verify_host = False + + ssl_sock.settimeout(self._keepalive) + ssl_sock.do_handshake() + + if verify_host: + # TODO: this type error is a true error: + # error: Module has no attribute "match_hostname" [attr-defined] + # Python 3.12 no longer have this method. + ssl.match_hostname(ssl_sock.getpeercert(), self._host) # type: ignore + + return ssl_sock + +class _WebsocketWrapper: OPCODE_CONTINUATION = 0x0 OPCODE_TEXT = 0x1 OPCODE_BINARY = 0x2 @@ -3693,8 +4669,15 @@ class WebsocketWrapper(object): OPCODE_PING = 0x9 OPCODE_PONG = 0xa - def __init__(self, socket, host, port, is_ssl, path, extra_headers): - + def __init__( + self, + socket: socket.socket | ssl.SSLSocket, + host: str, + port: int, + is_ssl: bool, + path: str, + extra_headers: WebSocketHeaders | None, + ): self.connected = False self._ssl = is_ssl @@ -3712,21 +4695,32 @@ class WebsocketWrapper(object): self._do_handshake(extra_headers) - def __del__(self): + def __del__(self) -> None: + self._sendbuffer = bytearray() + self._readbuffer = bytearray() - self._sendbuffer = None - self._readbuffer = None - - def _do_handshake(self, extra_headers): + def _do_handshake(self, extra_headers: WebSocketHeaders | None) -> None: sec_websocket_key = uuid.uuid4().bytes sec_websocket_key = base64.b64encode(sec_websocket_key) + if self._ssl: + default_port = 443 + http_schema = "https" + else: + default_port = 80 + http_schema = "http" + + if default_port == self._port: + host_port = f"{self._host}" + else: + host_port = f"{self._host}:{self._port}" + websocket_headers = { - "Host": "{self._host:s}:{self._port:d}".format(self=self), + "Host": host_port, "Upgrade": "websocket", "Connection": "Upgrade", - "Origin": "https://{self._host:s}:{self._port:d}".format(self=self), + "Origin": f"{http_schema}://{host_port}", "Sec-WebSocket-Key": sec_websocket_key.decode("utf8"), "Sec-Websocket-Version": "13", "Sec-Websocket-Protocol": "mqtt", @@ -3740,9 +4734,8 @@ class WebsocketWrapper(object): websocket_headers = extra_headers(websocket_headers) header = "\r\n".join([ - "GET {self._path} HTTP/1.1".format(self=self), - "\r\n".join("{}: {}".format(i, j) - for i, j in websocket_headers.items()), + f"GET {self._path} HTTP/1.1", + "\r\n".join(f"{i}: {j}" for i, j in websocket_headers.items()), "\r\n", ]).encode("utf8") @@ -3753,7 +4746,10 @@ class WebsocketWrapper(object): while True: # read HTTP response header as lines - byte = self._socket.recv(1) + try: + byte = self._socket.recv(1) + except ConnectionResetError: + byte = b"" self._readbuffer.extend(byte) @@ -3772,13 +4768,14 @@ class WebsocketWrapper(object): if b"sec-websocket-accept" in str(self._readbuffer).lower().encode('utf-8'): GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" - server_hash = self._readbuffer.decode( + server_hash_str = self._readbuffer.decode( 'utf-8').split(": ", 1)[1] - server_hash = server_hash.strip().encode('utf-8') + server_hash = server_hash_str.strip().encode('utf-8') - client_hash = sec_websocket_key.decode('utf-8') + GUID - client_hash = hashlib.sha1(client_hash.encode('utf-8')) - client_hash = base64.b64encode(client_hash.digest()) + client_hash_key = sec_websocket_key.decode('utf-8') + GUID + # Use of SHA-1 is OK here; it's according to the Websocket spec. + client_hash_digest = hashlib.sha1(client_hash_key.encode('utf-8')) # noqa: S324 + client_hash = base64.b64encode(client_hash_digest.digest()) if server_hash != client_hash: raise WebsocketConnectionError( @@ -3802,8 +4799,9 @@ class WebsocketWrapper(object): self._readbuffer = bytearray() self.connected = True - def _create_frame(self, opcode, data, do_masking=1): - + def _create_frame( + self, opcode: int, data: bytearray, do_masking: int = 1 + ) -> bytearray: header = bytearray() length = len(data) @@ -3834,7 +4832,7 @@ class WebsocketWrapper(object): return header + data - def _buffered_read(self, length): + def _buffered_read(self, length: int) -> bytearray: # try to recv and store needed bytes wanted_bytes = length - (len(self._readbuffer) - self._readbuffer_head) @@ -3853,14 +4851,14 @@ class WebsocketWrapper(object): self._readbuffer_head += length return self._readbuffer[self._readbuffer_head - length:self._readbuffer_head] - def _recv_impl(self, length): + def _recv_impl(self, length: int) -> bytes: # try to decode websocket payload part from data try: self._readbuffer_head = 0 - result = None + result = b"" chunk_startindex = self._payload_head chunk_endindex = self._payload_head + length @@ -3899,7 +4897,7 @@ class WebsocketWrapper(object): payload = self._buffered_read(readindex) # unmask only the needed part - if maskbit: + if mask_key is not None: for index in range(chunk_startindex, readindex): payload[index] ^= mask_key[index % 4] @@ -3913,20 +4911,20 @@ class WebsocketWrapper(object): self._readbuffer = bytearray() self._payload_head = 0 - # respond to non-binary opcodes, their arrival is not guaranteed beacause of non-blocking sockets - if opcode == WebsocketWrapper.OPCODE_CONNCLOSE: + # respond to non-binary opcodes, their arrival is not guaranteed because of non-blocking sockets + if opcode == _WebsocketWrapper.OPCODE_CONNCLOSE: frame = self._create_frame( - WebsocketWrapper.OPCODE_CONNCLOSE, payload, 0) + _WebsocketWrapper.OPCODE_CONNCLOSE, payload, 0) self._socket.send(frame) - if opcode == WebsocketWrapper.OPCODE_PING: + if opcode == _WebsocketWrapper.OPCODE_PING: frame = self._create_frame( - WebsocketWrapper.OPCODE_PONG, payload, 0) + _WebsocketWrapper.OPCODE_PONG, payload, 0) self._socket.send(frame) # This isn't *proper* handling of continuation frames, but given # that we only support binary frames, it is *probably* good enough. - if (opcode == WebsocketWrapper.OPCODE_BINARY or opcode == WebsocketWrapper.OPCODE_CONTINUATION) \ + if (opcode == _WebsocketWrapper.OPCODE_BINARY or opcode == _WebsocketWrapper.OPCODE_CONTINUATION) \ and payload_length > 0: return result else: @@ -3936,13 +4934,13 @@ class WebsocketWrapper(object): self.connected = False return b'' - def _send_impl(self, data): + def _send_impl(self, data: bytes) -> int: # if previous frame was sent successfully if len(self._sendbuffer) == 0: # create websocket frame frame = self._create_frame( - WebsocketWrapper.OPCODE_BINARY, bytearray(data)) + _WebsocketWrapper.OPCODE_BINARY, bytearray(data)) self._sendbuffer.extend(frame) self._requested_size = len(data) @@ -3958,32 +4956,32 @@ class WebsocketWrapper(object): # couldn't send whole data, request the same data again with 0 as sent length return 0 - def recv(self, length): + def recv(self, length: int) -> bytes: return self._recv_impl(length) - def read(self, length): + def read(self, length: int) -> bytes: return self._recv_impl(length) - def send(self, data): + def send(self, data: bytes) -> int: return self._send_impl(data) - def write(self, data): + def write(self, data: bytes) -> int: return self._send_impl(data) - def close(self): + def close(self) -> None: self._socket.close() - def fileno(self): + def fileno(self) -> int: return self._socket.fileno() - def pending(self): + def pending(self) -> int: # Fix for bug #131: a SSL socket may still have data available # for reading without select() being aware of it. if self._ssl: - return self._socket.pending() + return self._socket.pending() # type: ignore[union-attr] else: # normal socket rely only on select() return 0 - def setblocking(self, flag): + def setblocking(self, flag: bool) -> None: self._socket.setblocking(flag) diff --git a/lib/paho/mqtt/enums.py b/lib/paho/mqtt/enums.py new file mode 100644 index 00000000..5428769f --- /dev/null +++ b/lib/paho/mqtt/enums.py @@ -0,0 +1,113 @@ +import enum + + +class MQTTErrorCode(enum.IntEnum): + MQTT_ERR_AGAIN = -1 + MQTT_ERR_SUCCESS = 0 + MQTT_ERR_NOMEM = 1 + MQTT_ERR_PROTOCOL = 2 + MQTT_ERR_INVAL = 3 + MQTT_ERR_NO_CONN = 4 + MQTT_ERR_CONN_REFUSED = 5 + MQTT_ERR_NOT_FOUND = 6 + MQTT_ERR_CONN_LOST = 7 + MQTT_ERR_TLS = 8 + MQTT_ERR_PAYLOAD_SIZE = 9 + MQTT_ERR_NOT_SUPPORTED = 10 + MQTT_ERR_AUTH = 11 + MQTT_ERR_ACL_DENIED = 12 + MQTT_ERR_UNKNOWN = 13 + MQTT_ERR_ERRNO = 14 + MQTT_ERR_QUEUE_SIZE = 15 + MQTT_ERR_KEEPALIVE = 16 + + +class MQTTProtocolVersion(enum.IntEnum): + MQTTv31 = 3 + MQTTv311 = 4 + MQTTv5 = 5 + + +class CallbackAPIVersion(enum.Enum): + """Defined the arguments passed to all user-callback. + + See each callbacks for details: `on_connect`, `on_connect_fail`, `on_disconnect`, `on_message`, `on_publish`, + `on_subscribe`, `on_unsubscribe`, `on_log`, `on_socket_open`, `on_socket_close`, + `on_socket_register_write`, `on_socket_unregister_write` + """ + VERSION1 = 1 + """The version used with paho-mqtt 1.x before introducing CallbackAPIVersion. + + This version had different arguments depending if MQTTv5 or MQTTv3 was used. `Properties` & `ReasonCode` were missing + on some callback (apply only to MQTTv5). + + This version is deprecated and will be removed in version 3.0. + """ + VERSION2 = 2 + """ This version fix some of the shortcoming of previous version. + + Callback have the same signature if using MQTTv5 or MQTTv3. `ReasonCode` are used in MQTTv3. + """ + + +class MessageType(enum.IntEnum): + CONNECT = 0x10 + CONNACK = 0x20 + PUBLISH = 0x30 + PUBACK = 0x40 + PUBREC = 0x50 + PUBREL = 0x60 + PUBCOMP = 0x70 + SUBSCRIBE = 0x80 + SUBACK = 0x90 + UNSUBSCRIBE = 0xA0 + UNSUBACK = 0xB0 + PINGREQ = 0xC0 + PINGRESP = 0xD0 + DISCONNECT = 0xE0 + AUTH = 0xF0 + + +class LogLevel(enum.IntEnum): + MQTT_LOG_INFO = 0x01 + MQTT_LOG_NOTICE = 0x02 + MQTT_LOG_WARNING = 0x04 + MQTT_LOG_ERR = 0x08 + MQTT_LOG_DEBUG = 0x10 + + +class ConnackCode(enum.IntEnum): + CONNACK_ACCEPTED = 0 + CONNACK_REFUSED_PROTOCOL_VERSION = 1 + CONNACK_REFUSED_IDENTIFIER_REJECTED = 2 + CONNACK_REFUSED_SERVER_UNAVAILABLE = 3 + CONNACK_REFUSED_BAD_USERNAME_PASSWORD = 4 + CONNACK_REFUSED_NOT_AUTHORIZED = 5 + + +class _ConnectionState(enum.Enum): + MQTT_CS_NEW = enum.auto() + MQTT_CS_CONNECT_ASYNC = enum.auto() + MQTT_CS_CONNECTING = enum.auto() + MQTT_CS_CONNECTED = enum.auto() + MQTT_CS_CONNECTION_LOST = enum.auto() + MQTT_CS_DISCONNECTING = enum.auto() + MQTT_CS_DISCONNECTED = enum.auto() + + +class MessageState(enum.IntEnum): + MQTT_MS_INVALID = 0 + MQTT_MS_PUBLISH = 1 + MQTT_MS_WAIT_FOR_PUBACK = 2 + MQTT_MS_WAIT_FOR_PUBREC = 3 + MQTT_MS_RESEND_PUBREL = 4 + MQTT_MS_WAIT_FOR_PUBREL = 5 + MQTT_MS_RESEND_PUBCOMP = 6 + MQTT_MS_WAIT_FOR_PUBCOMP = 7 + MQTT_MS_SEND_PUBREC = 8 + MQTT_MS_QUEUED = 9 + + +class PahoClientMode(enum.IntEnum): + MQTT_CLIENT = 0 + MQTT_BRIDGE = 1 diff --git a/lib/paho/mqtt/matcher.py b/lib/paho/mqtt/matcher.py index 01ce295c..b73c13ac 100644 --- a/lib/paho/mqtt/matcher.py +++ b/lib/paho/mqtt/matcher.py @@ -1,4 +1,4 @@ -class MQTTMatcher(object): +class MQTTMatcher: """Intended to manage topic filters including wildcards. Internally, MQTTMatcher use a prefix tree (trie) to store @@ -6,7 +6,7 @@ class MQTTMatcher(object): method to iterate efficiently over all filters that match some topic name.""" - class Node(object): + class Node: __slots__ = '_children', '_content' def __init__(self): @@ -33,8 +33,8 @@ class MQTTMatcher(object): if node._content is None: raise KeyError(key) return node._content - except KeyError: - raise KeyError(key) + except KeyError as ke: + raise KeyError(key) from ke def __delitem__(self, key): """Delete the value associated with some topic filter :key""" @@ -46,8 +46,8 @@ class MQTTMatcher(object): lst.append((parent, k, node)) # TODO node._content = None - except KeyError: - raise KeyError(key) + except KeyError as ke: + raise KeyError(key) from ke else: # cleanup for parent, k, node in reversed(lst): if node._children or node._content is not None: diff --git a/lib/paho/mqtt/packettypes.py b/lib/paho/mqtt/packettypes.py index 2fd6a1b5..d2051490 100644 --- a/lib/paho/mqtt/packettypes.py +++ b/lib/paho/mqtt/packettypes.py @@ -7,7 +7,7 @@ and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at - http://www.eclipse.org/legal/epl-v10.html + http://www.eclipse.org/legal/epl-v20.html and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. @@ -37,7 +37,7 @@ class PacketTypes: # Dummy packet type for properties use - will delay only applies to will WILLMESSAGE = 99 - Names = [ "reserved", \ + Names = ( "reserved", \ "Connect", "Connack", "Publish", "Puback", "Pubrec", "Pubrel", \ "Pubcomp", "Subscribe", "Suback", "Unsubscribe", "Unsuback", \ - "Pingreq", "Pingresp", "Disconnect", "Auth"] + "Pingreq", "Pingresp", "Disconnect", "Auth") diff --git a/lib/paho/mqtt/properties.py b/lib/paho/mqtt/properties.py index dbcf543e..f307b865 100644 --- a/lib/paho/mqtt/properties.py +++ b/lib/paho/mqtt/properties.py @@ -1,23 +1,20 @@ -""" -******************************************************************* - Copyright (c) 2017, 2019 IBM Corp. - - All rights reserved. This program and the accompanying materials - are made available under the terms of the Eclipse Public License v2.0 - and Eclipse Distribution License v1.0 which accompany this distribution. - - The Eclipse Public License is available at - http://www.eclipse.org/legal/epl-v10.html - and the Eclipse Distribution License is available at - http://www.eclipse.org/org/documents/edl-v10.php. - - Contributors: - Ian Craggs - initial implementation and/or documentation -******************************************************************* -""" +# ******************************************************************* +# Copyright (c) 2017, 2019 IBM Corp. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v2.0 +# and Eclipse Distribution License v1.0 which accompany this distribution. +# +# The Eclipse Public License is available at +# http://www.eclipse.org/legal/epl-v20.html +# and the Eclipse Distribution License is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# Contributors: +# Ian Craggs - initial implementation and/or documentation +# ******************************************************************* import struct -import sys from .packettypes import PacketTypes @@ -52,10 +49,8 @@ def readInt32(buf): def writeUTF(data): # data could be a string, or bytes. If string, encode into bytes with utf-8 - if sys.version_info[0] < 3: - data = bytearray(data, 'utf-8') - else: - data = data if type(data) == type(b"") else bytes(data, "utf-8") + if not isinstance(data, bytes): + data = bytes(data, "utf-8") return writeInt16(len(data)) + data @@ -100,19 +95,17 @@ class VariableByteIntegers: # Variable Byte Integer def encode(x): """ Convert an integer 0 <= x <= 268435455 into multi-byte format. - Returns the buffer convered from the integer. + Returns the buffer converted from the integer. """ - assert 0 <= x <= 268435455 + if not 0 <= x <= 268435455: + raise ValueError(f"Value {x!r} must be in range 0-268435455") buffer = b'' while 1: digit = x % 128 x //= 128 if x > 0: digit |= 0x80 - if sys.version_info[0] >= 3: - buffer += bytes([digit]) - else: - buffer += bytes(chr(digit)) + buffer += bytes([digit]) if x == 0: break return buffer @@ -139,21 +132,21 @@ class VariableByteIntegers: # Variable Byte Integer return (value, bytes) -class Properties(object): +class Properties: """MQTT v5.0 properties class. See Properties.names for a list of accepted property names along with their numeric values. See Properties.properties for the data type of each property. - Example of use: + Example of use:: publish_properties = Properties(PacketTypes.PUBLISH) publish_properties.UserProperty = ("a", "2") publish_properties.UserProperty = ("c", "3") First the object is created with packet type as argument, no properties will be present at - this point. Then properties are added as attributes, the name of which is the string property + this point. Then properties are added as attributes, the name of which is the string property name without the spaces. """ @@ -264,37 +257,33 @@ class Properties(object): # the name could have spaces in, or not. Remove spaces before assignment if name not in [aname.replace(' ', '') for aname in self.names.keys()]: raise MQTTException( - "Property name must be one of "+str(self.names.keys())) + f"Property name must be one of {self.names.keys()}") # check that this attribute applies to the packet type if self.packetType not in self.properties[self.getIdentFromName(name)][1]: - raise MQTTException("Property %s does not apply to packet type %s" - % (name, PacketTypes.Names[self.packetType])) + raise MQTTException(f"Property {name} does not apply to packet type {PacketTypes.Names[self.packetType]}") # Check for forbidden values - if type(value) != type([]): + if not isinstance(value, list): if name in ["ReceiveMaximum", "TopicAlias"] \ and (value < 1 or value > 65535): - raise MQTTException( - "%s property value must be in the range 1-65535" % (name)) + raise MQTTException(f"{name} property value must be in the range 1-65535") elif name in ["TopicAliasMaximum"] \ and (value < 0 or value > 65535): - raise MQTTException( - "%s property value must be in the range 0-65535" % (name)) + raise MQTTException(f"{name} property value must be in the range 0-65535") elif name in ["MaximumPacketSize", "SubscriptionIdentifier"] \ and (value < 1 or value > 268435455): - raise MQTTException( - "%s property value must be in the range 1-268435455" % (name)) + raise MQTTException(f"{name} property value must be in the range 1-268435455") elif name in ["RequestResponseInformation", "RequestProblemInformation", "PayloadFormatIndicator"] \ and (value != 0 and value != 1): raise MQTTException( - "%s property value must be 0 or 1" % (name)) + f"{name} property value must be 0 or 1") if self.allowsMultiple(name): - if type(value) != type([]): + if not isinstance(value, list): value = [value] if hasattr(self, name): value = object.__getattribute__(self, name) + value @@ -308,8 +297,7 @@ class Properties(object): if hasattr(self, compressedName): if not first: buffer += ", " - buffer += compressedName + " : " + \ - str(getattr(self, compressedName)) + buffer += f"{compressedName} : {getattr(self, compressedName)}" first = False buffer += "]" return buffer @@ -345,10 +333,7 @@ class Properties(object): buffer = b"" buffer += VariableByteIntegers.encode(identifier) # identifier if type == self.types.index("Byte"): # value - if sys.version_info[0] < 3: - buffer += chr(value) - else: - buffer += bytes([value]) + buffer += bytes([value]) elif type == self.types.index("Two Byte Integer"): buffer += writeInt16(value) elif type == self.types.index("Four Byte Integer"): @@ -412,8 +397,6 @@ class Properties(object): return rc def unpack(self, buffer): - if sys.version_info[0] < 3: - buffer = bytearray(buffer) self.clear() # deserialize properties into attributes from buffer received from network propslen, VBIlen = VariableByteIntegers.decode(buffer) @@ -433,6 +416,6 @@ class Properties(object): compressedName = propname.replace(' ', '') if not self.allowsMultiple(compressedName) and hasattr(self, compressedName): raise MQTTException( - "Property '%s' must not exist more than once" % property) + f"Property '{property}' must not exist more than once") setattr(self, propname, value) return self, propslen + VBIlen diff --git a/lib/paho/mqtt/publish.py b/lib/paho/mqtt/publish.py index 6d1589a7..42435156 100644 --- a/lib/paho/mqtt/publish.py +++ b/lib/paho/mqtt/publish.py @@ -5,7 +5,7 @@ # and Eclipse Distribution License v1.0 which accompany this distribution. # # The Eclipse Public License is available at -# http://www.eclipse.org/legal/epl-v10.html +# http://www.eclipse.org/legal/epl-v20.html # and the Eclipse Distribution License is available at # http://www.eclipse.org/org/documents/edl-v10.php. # @@ -18,20 +18,58 @@ of messages in a one-shot manner. In other words, they are useful for the situation where you have a single/multiple messages you want to publish to a broker, then disconnect and nothing else is required. """ -from __future__ import absolute_import +from __future__ import annotations import collections +from collections.abc import Iterable +from typing import TYPE_CHECKING, Any, List, Tuple, Union -try: - from collections.abc import Iterable -except ImportError: - from collections import Iterable +from paho.mqtt.enums import CallbackAPIVersion +from paho.mqtt.properties import Properties +from paho.mqtt.reasoncodes import ReasonCode from .. import mqtt from . import client as paho +if TYPE_CHECKING: + try: + from typing import NotRequired, Required, TypedDict # type: ignore + except ImportError: + from typing_extensions import NotRequired, Required, TypedDict -def _do_publish(client): + try: + from typing import Literal + except ImportError: + from typing_extensions import Literal # type: ignore + + + + class AuthParameter(TypedDict, total=False): + username: Required[str] + password: NotRequired[str] + + + class TLSParameter(TypedDict, total=False): + ca_certs: Required[str] + certfile: NotRequired[str] + keyfile: NotRequired[str] + tls_version: NotRequired[int] + ciphers: NotRequired[str] + insecure: NotRequired[bool] + + + class MessageDict(TypedDict, total=False): + topic: Required[str] + payload: NotRequired[paho.PayloadType] + qos: NotRequired[int] + retain: NotRequired[bool] + + MessageTuple = Tuple[str, paho.PayloadType, int, bool] + + MessagesList = List[Union[MessageDict, MessageTuple]] + + +def _do_publish(client: paho.Client): """Internal function""" message = client._userdata.popleft() @@ -44,21 +82,18 @@ def _do_publish(client): raise TypeError('message must be a dict, tuple, or list') -def _on_connect(client, userdata, flags, rc): - """Internal callback""" - #pylint: disable=invalid-name, unused-argument - - if rc == 0: +def _on_connect(client: paho.Client, userdata: MessagesList, flags, reason_code, properties): + """Internal v5 callback""" + if reason_code == 0: if len(userdata) > 0: _do_publish(client) else: - raise mqtt.MQTTException(paho.connack_string(rc)) + raise mqtt.MQTTException(paho.connack_string(reason_code)) -def _on_connect_v5(client, userdata, flags, rc, properties): - """Internal v5 callback""" - _on_connect(client, userdata, flags, rc) -def _on_publish(client, userdata, mid): +def _on_publish( + client: paho.Client, userdata: collections.deque[MessagesList], mid: int, reason_codes: ReasonCode, properties: Properties, +) -> None: """Internal callback""" #pylint: disable=unused-argument @@ -68,16 +103,26 @@ def _on_publish(client, userdata, mid): _do_publish(client) -def multiple(msgs, hostname="localhost", port=1883, client_id="", keepalive=60, - will=None, auth=None, tls=None, protocol=paho.MQTTv311, - transport="tcp", proxy_args=None): +def multiple( + msgs: MessagesList, + hostname: str = "localhost", + port: int = 1883, + client_id: str = "", + keepalive: int = 60, + will: MessageDict | None = None, + auth: AuthParameter | None = None, + tls: TLSParameter | None = None, + protocol: int = paho.MQTTv311, + transport: Literal["tcp", "websockets"] = "tcp", + proxy_args: Any | None = None, +) -> None: """Publish multiple messages to a broker, then disconnect cleanly. This function creates an MQTT client, connects to a broker and publishes a list of messages. Once the messages have been delivered, it disconnects cleanly from the broker. - msgs : a list of messages to publish. Each message is either a dict or a + :param msgs: a list of messages to publish. Each message is either a dict or a tuple. If a dict, only the topic must be present. Default values will be @@ -94,30 +139,30 @@ def multiple(msgs, hostname="localhost", port=1883, client_id="", keepalive=60, If a tuple, then it must be of the form: ("", "", qos, retain) - hostname : a string containing the address of the broker to connect to. + :param str hostname: the address of the broker to connect to. Defaults to localhost. - port : the port to connect to the broker on. Defaults to 1883. + :param int port: the port to connect to the broker on. Defaults to 1883. - client_id : the MQTT client id to use. If "" or None, the Paho library will + :param str client_id: the MQTT client id to use. If "" or None, the Paho library will generate a client id automatically. - keepalive : the keepalive timeout value for the client. Defaults to 60 + :param int keepalive: the keepalive timeout value for the client. Defaults to 60 seconds. - will : a dict containing will parameters for the client: will = {'topic': + :param will: a dict containing will parameters for the client: will = {'topic': "", 'payload':", 'qos':, 'retain':}. Topic is required, all other parameters are optional and will default to None, 0 and False respectively. Defaults to None, which indicates no will should be used. - auth : a dict containing authentication parameters for the client: + :param auth: a dict containing authentication parameters for the client: auth = {'username':"", 'password':""} Username is required, password is optional and will default to None if not provided. Defaults to None, which indicates no authentication is to be used. - tls : a dict containing TLS configuration parameters for the client: + :param tls: a dict containing TLS configuration parameters for the client: dict = {'ca_certs':"", 'certfile':"", 'keyfile':"", 'tls_version':"", 'ciphers':", 'insecure':""} @@ -128,23 +173,28 @@ def multiple(msgs, hostname="localhost", port=1883, client_id="", keepalive=60, processed using the tls_set_context method. Defaults to None, which indicates that TLS should not be used. - transport : set to "tcp" to use the default setting of transport which is + :param str transport: set to "tcp" to use the default setting of transport which is raw TCP. Set to "websockets" to use WebSockets as the transport. - proxy_args: a dictionary that will be given to the client. + + :param proxy_args: a dictionary that will be given to the client. """ if not isinstance(msgs, Iterable): raise TypeError('msgs must be an iterable') + if len(msgs) == 0: + raise ValueError('msgs is empty') + client = paho.Client( + CallbackAPIVersion.VERSION2, + client_id=client_id, + userdata=collections.deque(msgs), + protocol=protocol, + transport=transport, + ) - client = paho.Client(client_id=client_id, userdata=collections.deque(msgs), - protocol=protocol, transport=transport) - + client.enable_logger() client.on_publish = _on_publish - if protocol == mqtt.client.MQTTv5: - client.on_connect = _on_connect_v5 - else: - client.on_connect = _on_connect + client.on_connect = _on_connect # type: ignore if proxy_args is not None: client.proxy_set(**proxy_args) @@ -164,7 +214,8 @@ def multiple(msgs, hostname="localhost", port=1883, client_id="", keepalive=60, if tls is not None: if isinstance(tls, dict): insecure = tls.pop('insecure', False) - client.tls_set(**tls) + # mypy don't get that tls no longer contains the key insecure + client.tls_set(**tls) # type: ignore[misc] if insecure: # Must be set *after* the `client.tls_set()` call since it sets # up the SSL context that `client.tls_insecure_set` alters. @@ -177,49 +228,62 @@ def multiple(msgs, hostname="localhost", port=1883, client_id="", keepalive=60, client.loop_forever() -def single(topic, payload=None, qos=0, retain=False, hostname="localhost", - port=1883, client_id="", keepalive=60, will=None, auth=None, - tls=None, protocol=paho.MQTTv311, transport="tcp", proxy_args=None): +def single( + topic: str, + payload: paho.PayloadType = None, + qos: int = 0, + retain: bool = False, + hostname: str = "localhost", + port: int = 1883, + client_id: str = "", + keepalive: int = 60, + will: MessageDict | None = None, + auth: AuthParameter | None = None, + tls: TLSParameter | None = None, + protocol: int = paho.MQTTv311, + transport: Literal["tcp", "websockets"] = "tcp", + proxy_args: Any | None = None, +) -> None: """Publish a single message to a broker, then disconnect cleanly. This function creates an MQTT client, connects to a broker and publishes a single message. Once the message has been delivered, it disconnects cleanly from the broker. - topic : the only required argument must be the topic string to which the + :param str topic: the only required argument must be the topic string to which the payload will be published. - payload : the payload to be published. If "" or None, a zero length payload + :param payload: the payload to be published. If "" or None, a zero length payload will be published. - qos : the qos to use when publishing, default to 0. + :param int qos: the qos to use when publishing, default to 0. - retain : set the message to be retained (True) or not (False). + :param bool retain: set the message to be retained (True) or not (False). - hostname : a string containing the address of the broker to connect to. + :param str hostname: the address of the broker to connect to. Defaults to localhost. - port : the port to connect to the broker on. Defaults to 1883. + :param int port: the port to connect to the broker on. Defaults to 1883. - client_id : the MQTT client id to use. If "" or None, the Paho library will + :param str client_id: the MQTT client id to use. If "" or None, the Paho library will generate a client id automatically. - keepalive : the keepalive timeout value for the client. Defaults to 60 + :param int keepalive: the keepalive timeout value for the client. Defaults to 60 seconds. - will : a dict containing will parameters for the client: will = {'topic': + :param will: a dict containing will parameters for the client: will = {'topic': "", 'payload':", 'qos':, 'retain':}. Topic is required, all other parameters are optional and will default to None, 0 and False respectively. Defaults to None, which indicates no will should be used. - auth : a dict containing authentication parameters for the client: - auth = {'username':"", 'password':""} + :param auth: a dict containing authentication parameters for the client: Username is required, password is optional and will default to None + auth = {'username':"", 'password':""} if not provided. Defaults to None, which indicates no authentication is to be used. - tls : a dict containing TLS configuration parameters for the client: + :param tls: a dict containing TLS configuration parameters for the client: dict = {'ca_certs':"", 'certfile':"", 'keyfile':"", 'tls_version':"", 'ciphers':", 'insecure':""} @@ -230,12 +294,13 @@ def single(topic, payload=None, qos=0, retain=False, hostname="localhost", Alternatively, tls input can be an SSLContext object, which will be processed using the tls_set_context method. - transport : set to "tcp" to use the default setting of transport which is + :param transport: set to "tcp" to use the default setting of transport which is raw TCP. Set to "websockets" to use WebSockets as the transport. - proxy_args: a dictionary that will be given to the client. + + :param proxy_args: a dictionary that will be given to the client. """ - msg = {'topic':topic, 'payload':payload, 'qos':qos, 'retain':retain} + msg: MessageDict = {'topic':topic, 'payload':payload, 'qos':qos, 'retain':retain} multiple([msg], hostname, port, client_id, keepalive, will, auth, tls, protocol, transport, proxy_args) diff --git a/lib/paho/mqtt/py.typed b/lib/paho/mqtt/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/lib/paho/mqtt/reasoncodes.py b/lib/paho/mqtt/reasoncodes.py index c42e5ba9..6b30cb83 100644 --- a/lib/paho/mqtt/reasoncodes.py +++ b/lib/paho/mqtt/reasoncodes.py @@ -1,30 +1,31 @@ -""" -******************************************************************* - Copyright (c) 2017, 2019 IBM Corp. +# ******************************************************************* +# Copyright (c) 2017, 2019 IBM Corp. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v2.0 +# and Eclipse Distribution License v1.0 which accompany this distribution. +# +# The Eclipse Public License is available at +# http://www.eclipse.org/legal/epl-v20.html +# and the Eclipse Distribution License is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# Contributors: +# Ian Craggs - initial implementation and/or documentation +# ******************************************************************* - All rights reserved. This program and the accompanying materials - are made available under the terms of the Eclipse Public License v2.0 - and Eclipse Distribution License v1.0 which accompany this distribution. - - The Eclipse Public License is available at - http://www.eclipse.org/legal/epl-v10.html - and the Eclipse Distribution License is available at - http://www.eclipse.org/org/documents/edl-v10.php. - - Contributors: - Ian Craggs - initial implementation and/or documentation -******************************************************************* -""" - -import sys +import functools +import warnings +from typing import Any from .packettypes import PacketTypes -class ReasonCodes: +@functools.total_ordering +class ReasonCode: """MQTT version 5.0 reason codes class. - See ReasonCodes.names for a list of possible numeric values along with their + See ReasonCode.names for a list of possible numeric values along with their names and the packets to which they apply. """ @@ -135,10 +136,12 @@ class ReasonCodes: Used when displaying the reason code. """ - assert identifier in self.names.keys(), identifier + if identifier not in self.names: + raise KeyError(identifier) names = self.names[identifier] namelist = [name for name in names.keys() if packetType in names[name]] - assert len(namelist) == 1 + if len(namelist) != 1: + raise ValueError(f"Expected exactly one name, found {namelist!r}") return namelist[0] def getId(self, name): @@ -148,22 +151,17 @@ class ReasonCodes: Used when setting the reason code for a packetType check that only valid codes for the packet are set. """ - identifier = None for code in self.names.keys(): if name in self.names[code].keys(): if self.packetType in self.names[code][name]: - identifier = code - break - assert identifier is not None, name - return identifier + return code + raise KeyError(f"Reason code name not found: {name}") def set(self, name): self.value = self.getId(name) def unpack(self, buffer): c = buffer[0] - if sys.version_info[0] < 3: - c = ord(c) name = self.__getName__(self.packetType, c) self.value = self.getId(name) return 1 @@ -177,11 +175,26 @@ class ReasonCodes: if isinstance(other, int): return self.value == other if isinstance(other, str): - return self.value == str(self) - if isinstance(other, ReasonCodes): + return other == str(self) + if isinstance(other, ReasonCode): return self.value == other.value return False + def __lt__(self, other): + if isinstance(other, int): + return self.value < other + if isinstance(other, ReasonCode): + return self.value < other.value + return NotImplemented + + def __repr__(self): + try: + packet_name = PacketTypes.Names[self.packetType] + except IndexError: + packet_name = "Unknown" + + return f"ReasonCode({packet_name}, {self.getName()!r})" + def __str__(self): return self.getName() @@ -190,3 +203,21 @@ class ReasonCodes: def pack(self): return bytearray([self.value]) + + @property + def is_failure(self) -> bool: + return self.value >= 0x80 + + +class _CompatibilityIsInstance(type): + def __instancecheck__(self, other: Any) -> bool: + return isinstance(other, ReasonCode) + + +class ReasonCodes(ReasonCode, metaclass=_CompatibilityIsInstance): + def __init__(self, *args, **kwargs): + warnings.warn("ReasonCodes is deprecated, use ReasonCode (singular) instead", + category=DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) diff --git a/lib/paho/mqtt/subscribe.py b/lib/paho/mqtt/subscribe.py index 643df9c1..b6c80f44 100644 --- a/lib/paho/mqtt/subscribe.py +++ b/lib/paho/mqtt/subscribe.py @@ -5,7 +5,7 @@ # and Eclipse Distribution License v1.0 which accompany this distribution. # # The Eclipse Public License is available at -# http://www.eclipse.org/legal/epl-v10.html +# http://www.eclipse.org/legal/epl-v20.html # and the Eclipse Distribution License is available at # http://www.eclipse.org/org/documents/edl-v10.php. # @@ -18,16 +18,15 @@ to topics and retrieving messages. The two functions are simple(), which returns one or messages matching a set of topics, and callback() which allows you to pass a callback for processing of messages. """ -from __future__ import absolute_import from .. import mqtt from . import client as paho -def _on_connect_v5(client, userdata, flags, rc, properties): +def _on_connect(client, userdata, flags, reason_code, properties): """Internal callback""" - if rc != 0: - raise mqtt.MQTTException(paho.connack_string(rc)) + if reason_code != 0: + raise mqtt.MQTTException(paho.connack_string(reason_code)) if isinstance(userdata['topics'], list): for topic in userdata['topics']: @@ -35,10 +34,6 @@ def _on_connect_v5(client, userdata, flags, rc, properties): else: client.subscribe(userdata['topics'], userdata['qos']) -def _on_connect(client, userdata, flags, rc): - """Internal v5 callback""" - _on_connect_v5(client, userdata, flags, rc, None) - def _on_message_callback(client, userdata, message): """Internal callback""" @@ -77,40 +72,41 @@ def callback(callback, topics, qos=0, userdata=None, hostname="localhost", to a list of topics. Incoming messages are processed by the user provided callback. This is a blocking function and will never return. - callback : function of the form "on_message(client, userdata, message)" for + :param callback: function with the same signature as `on_message` for processing the messages received. - topics : either a string containing a single topic to subscribe to, or a + :param topics: either a string containing a single topic to subscribe to, or a list of topics to subscribe to. - qos : the qos to use when subscribing. This is applied to all topics. + :param int qos: the qos to use when subscribing. This is applied to all topics. - userdata : passed to the callback + :param userdata: passed to the callback - hostname : a string containing the address of the broker to connect to. + :param str hostname: the address of the broker to connect to. Defaults to localhost. - port : the port to connect to the broker on. Defaults to 1883. + :param int port: the port to connect to the broker on. Defaults to 1883. - client_id : the MQTT client id to use. If "" or None, the Paho library will + :param str client_id: the MQTT client id to use. If "" or None, the Paho library will generate a client id automatically. - keepalive : the keepalive timeout value for the client. Defaults to 60 + :param int keepalive: the keepalive timeout value for the client. Defaults to 60 seconds. - will : a dict containing will parameters for the client: will = {'topic': + :param will: a dict containing will parameters for the client: will = {'topic': "", 'payload':", 'qos':, 'retain':}. Topic is required, all other parameters are optional and will default to None, 0 and False respectively. + Defaults to None, which indicates no will should be used. - auth : a dict containing authentication parameters for the client: + :param auth: a dict containing authentication parameters for the client: auth = {'username':"", 'password':""} Username is required, password is optional and will default to None if not provided. Defaults to None, which indicates no authentication is to be used. - tls : a dict containing TLS configuration parameters for the client: + :param tls: a dict containing TLS configuration parameters for the client: dict = {'ca_certs':"", 'certfile':"", 'keyfile':"", 'tls_version':"", 'ciphers':", 'insecure':""} @@ -121,17 +117,17 @@ def callback(callback, topics, qos=0, userdata=None, hostname="localhost", processed using the tls_set_context method. Defaults to None, which indicates that TLS should not be used. - transport : set to "tcp" to use the default setting of transport which is + :param str transport: set to "tcp" to use the default setting of transport which is raw TCP. Set to "websockets" to use WebSockets as the transport. - clean_session : a boolean that determines the client type. If True, + :param clean_session: a boolean that determines the client type. If True, the broker will remove all information about this client when it disconnects. If False, the client is a persistent client and subscription information and queued messages will be retained when the client disconnects. Defaults to True. - proxy_args: a dictionary that will be given to the client. + :param proxy_args: a dictionary that will be given to the client. """ if qos < 0 or qos > 2: @@ -143,14 +139,18 @@ def callback(callback, topics, qos=0, userdata=None, hostname="localhost", 'qos':qos, 'userdata':userdata} - client = paho.Client(client_id=client_id, userdata=callback_userdata, - protocol=protocol, transport=transport, - clean_session=clean_session) + client = paho.Client( + paho.CallbackAPIVersion.VERSION2, + client_id=client_id, + userdata=callback_userdata, + protocol=protocol, + transport=transport, + clean_session=clean_session, + ) + client.enable_logger() + client.on_message = _on_message_callback - if protocol == mqtt.client.MQTTv5: - client.on_connect = _on_connect_v5 - else: - client.on_connect = _on_connect + client.on_connect = _on_connect if proxy_args is not None: client.proxy_set(**proxy_args) @@ -193,45 +193,45 @@ def simple(topics, qos=0, msg_count=1, retained=True, hostname="localhost", to a list of topics. Once "msg_count" messages have been received, it disconnects cleanly from the broker and returns the messages. - topics : either a string containing a single topic to subscribe to, or a + :param topics: either a string containing a single topic to subscribe to, or a list of topics to subscribe to. - qos : the qos to use when subscribing. This is applied to all topics. + :param int qos: the qos to use when subscribing. This is applied to all topics. - msg_count : the number of messages to retrieve from the broker. + :param int msg_count: the number of messages to retrieve from the broker. if msg_count == 1 then a single MQTTMessage will be returned. if msg_count > 1 then a list of MQTTMessages will be returned. - retained : If set to True, retained messages will be processed the same as + :param bool retained: If set to True, retained messages will be processed the same as non-retained messages. If set to False, retained messages will be ignored. This means that with retained=False and msg_count=1, the function will return the first message received that does not have the retained flag set. - hostname : a string containing the address of the broker to connect to. + :param str hostname: the address of the broker to connect to. Defaults to localhost. - port : the port to connect to the broker on. Defaults to 1883. + :param int port: the port to connect to the broker on. Defaults to 1883. - client_id : the MQTT client id to use. If "" or None, the Paho library will + :param str client_id: the MQTT client id to use. If "" or None, the Paho library will generate a client id automatically. - keepalive : the keepalive timeout value for the client. Defaults to 60 + :param int keepalive: the keepalive timeout value for the client. Defaults to 60 seconds. - will : a dict containing will parameters for the client: will = {'topic': + :param will: a dict containing will parameters for the client: will = {'topic': "", 'payload':", 'qos':, 'retain':}. Topic is required, all other parameters are optional and will default to None, 0 and False respectively. Defaults to None, which indicates no will should be used. - auth : a dict containing authentication parameters for the client: + :param auth: a dict containing authentication parameters for the client: auth = {'username':"", 'password':""} Username is required, password is optional and will default to None if not provided. Defaults to None, which indicates no authentication is to be used. - tls : a dict containing TLS configuration parameters for the client: + :param tls: a dict containing TLS configuration parameters for the client: dict = {'ca_certs':"", 'certfile':"", 'keyfile':"", 'tls_version':"", 'ciphers':", 'insecure':""} @@ -242,17 +242,20 @@ def simple(topics, qos=0, msg_count=1, retained=True, hostname="localhost", processed using the tls_set_context method. Defaults to None, which indicates that TLS should not be used. - transport : set to "tcp" to use the default setting of transport which is + :param protocol: the MQTT protocol version to use. Defaults to MQTTv311. + + :param transport: set to "tcp" to use the default setting of transport which is raw TCP. Set to "websockets" to use WebSockets as the transport. - clean_session : a boolean that determines the client type. If True, + :param clean_session: a boolean that determines the client type. If True, the broker will remove all information about this client when it disconnects. If False, the client is a persistent client and subscription information and queued messages will be retained when the client disconnects. - Defaults to True. + Defaults to True. If protocol is MQTTv50, clean_session + is ignored. - proxy_args: a dictionary that will be given to the client. + :param proxy_args: a dictionary that will be given to the client. """ if msg_count < 1: @@ -265,6 +268,10 @@ def simple(topics, qos=0, msg_count=1, retained=True, hostname="localhost", else: messages = [] + # Ignore clean_session if protocol is MQTTv50, otherwise Client will raise + if protocol == paho.MQTTv5: + clean_session = None + userdata = {'retained':retained, 'msg_count':msg_count, 'messages':messages} callback(_on_message_simple, topics, qos, userdata, hostname, port, diff --git a/lib/paho/mqtt/subscribeoptions.py b/lib/paho/mqtt/subscribeoptions.py index 5b4f0733..7e0605de 100644 --- a/lib/paho/mqtt/subscribeoptions.py +++ b/lib/paho/mqtt/subscribeoptions.py @@ -7,7 +7,7 @@ and Eclipse Distribution License v1.0 which accompany this distribution. The Eclipse Public License is available at - http://www.eclipse.org/legal/epl-v10.html + http://www.eclipse.org/legal/epl-v20.html and the Eclipse Distribution License is available at http://www.eclipse.org/org/documents/edl-v10.php. @@ -16,14 +16,13 @@ ******************************************************************* """ -import sys class MQTTException(Exception): pass -class SubscribeOptions(object): +class SubscribeOptions: """The MQTT v5.0 subscribe options class. The options are: @@ -42,7 +41,13 @@ class SubscribeOptions(object): RETAIN_SEND_ON_SUBSCRIBE, RETAIN_SEND_IF_NEW_SUB, RETAIN_DO_NOT_SEND = range( 0, 3) - def __init__(self, qos=0, noLocal=False, retainAsPublished=False, retainHandling=RETAIN_SEND_ON_SUBSCRIBE): + def __init__( + self, + qos: int = 0, + noLocal: bool = False, + retainAsPublished: bool = False, + retainHandling: int = RETAIN_SEND_ON_SUBSCRIBE, + ): """ qos: 0, 1 or 2. 0 is the default. noLocal: True or False. False is the default and corresponds to MQTT v3.1.1 behavior. @@ -56,29 +61,27 @@ class SubscribeOptions(object): self.noLocal = noLocal # bit 2 self.retainAsPublished = retainAsPublished # bit 3 self.retainHandling = retainHandling # bits 4 and 5: 0, 1 or 2 - assert self.QoS in [0, 1, 2] - assert self.retainHandling in [ - 0, 1, 2], "Retain handling should be 0, 1 or 2" + if self.retainHandling not in (0, 1, 2): + raise AssertionError(f"Retain handling should be 0, 1 or 2, not {self.retainHandling}") + if self.QoS not in (0, 1, 2): + raise AssertionError(f"QoS should be 0, 1 or 2, not {self.QoS}") def __setattr__(self, name, value): if name not in self.names: raise MQTTException( - name + " Attribute name must be one of "+str(self.names)) + f"{name} Attribute name must be one of {self.names}") object.__setattr__(self, name, value) def pack(self): - assert self.QoS in [0, 1, 2] - assert self.retainHandling in [ - 0, 1, 2], "Retain handling should be 0, 1 or 2" + if self.retainHandling not in (0, 1, 2): + raise AssertionError(f"Retain handling should be 0, 1 or 2, not {self.retainHandling}") + if self.QoS not in (0, 1, 2): + raise AssertionError(f"QoS should be 0, 1 or 2, not {self.QoS}") noLocal = 1 if self.noLocal else 0 retainAsPublished = 1 if self.retainAsPublished else 0 data = [(self.retainHandling << 4) | (retainAsPublished << 3) | (noLocal << 2) | self.QoS] - if sys.version_info[0] >= 3: - buffer = bytes(data) - else: - buffer = bytearray(data) - return buffer + return bytes(data) def unpack(self, buffer): b0 = buffer[0] @@ -86,10 +89,10 @@ class SubscribeOptions(object): self.retainAsPublished = True if ((b0 >> 3) & 0x01) == 1 else False self.noLocal = True if ((b0 >> 2) & 0x01) == 1 else False self.QoS = (b0 & 0x03) - assert self.retainHandling in [ - 0, 1, 2], "Retain handling should be 0, 1 or 2, not %d" % self.retainHandling - assert self.QoS in [ - 0, 1, 2], "QoS should be 0, 1 or 2, not %d" % self.QoS + if self.retainHandling not in (0, 1, 2): + raise AssertionError(f"Retain handling should be 0, 1 or 2, not {self.retainHandling}") + if self.QoS not in (0, 1, 2): + raise AssertionError(f"QoS should be 0, 1 or 2, not {self.QoS}") return 1 def __repr__(self): diff --git a/requirements.txt b/requirements.txt index baf358c5..76948175 100644 --- a/requirements.txt +++ b/requirements.txt @@ -26,7 +26,7 @@ Mako==1.3.2 MarkupSafe==2.1.3 musicbrainzngs==0.7.1 packaging==24.0 -paho-mqtt==1.6.1 +paho-mqtt==2.0.0 platformdirs==4.2.0 plexapi==4.15.10 portend==3.2.0 From bfc4f667396df18e7a4353a848700afd8e320482 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 30 Mar 2024 15:27:31 -0700 Subject: [PATCH 3/6] Bump python-dateutil from 2.8.2 to 2.9.0.post0 (#2291) Bumps [python-dateutil](https://github.com/dateutil/dateutil) from 2.8.2 to 2.9.0.post0. - [Release notes](https://github.com/dateutil/dateutil/releases) - [Changelog](https://github.com/dateutil/dateutil/blob/master/NEWS) - [Commits](https://github.com/dateutil/dateutil/compare/2.8.2...2.9.0.post0) --- updated-dependencies: - dependency-name: python-dateutil dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> [skip ci] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 76948175..85ea9fbe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -33,7 +33,7 @@ portend==3.2.0 profilehooks==1.12.0 PyJWT==2.8.0 pyparsing==3.1.1 -python-dateutil==2.8.2 +python-dateutil==2.9.0.post0 python-twitter==3.5 pytz==2024.1 requests==2.31.0 From 452a4afdcf9bb2cfdd72cd40b5c164e2927454fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 30 Mar 2024 15:27:42 -0700 Subject: [PATCH 4/6] Bump tempora from 5.5.0 to 5.5.1 (#2292) Bumps [tempora](https://github.com/jaraco/tempora) from 5.5.0 to 5.5.1. - [Release notes](https://github.com/jaraco/tempora/releases) - [Changelog](https://github.com/jaraco/tempora/blob/main/NEWS.rst) - [Commits](https://github.com/jaraco/tempora/compare/v5.5.0...v5.5.1) --- updated-dependencies: - dependency-name: tempora dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> [skip ci] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 85ea9fbe..fcea2c1c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -41,7 +41,7 @@ requests-oauthlib==1.3.1 rumps==0.4.0; platform_system == "Darwin" simplejson==3.19.2 six==1.16.0 -tempora==5.5.0 +tempora==5.5.1 tokenize-rt==5.2.0 tzdata==2023.3 tzlocal==5.0.1 From 0d1d2a3e6b169b8c268c2f5a832617484699c9aa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 30 Mar 2024 15:28:02 -0700 Subject: [PATCH 5/6] Bump requests-oauthlib from 1.3.1 to 2.0.0 (#2293) * Bump requests-oauthlib from 1.3.1 to 2.0.0 Bumps [requests-oauthlib](https://github.com/requests/requests-oauthlib) from 1.3.1 to 2.0.0. - [Release notes](https://github.com/requests/requests-oauthlib/releases) - [Changelog](https://github.com/requests/requests-oauthlib/blob/master/HISTORY.rst) - [Commits](https://github.com/requests/requests-oauthlib/compare/v1.3.1...v2.0.0) --- updated-dependencies: - dependency-name: requests-oauthlib dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Update requests-oauthlib==2.0.0 --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> [skip ci] --- lib/charset_normalizer/__main__.py | 4 + lib/charset_normalizer/assets/__init__.py | 1440 ------------ lib/charset_normalizer/cd.py | 9 +- lib/charset_normalizer/cli/__init__.py | 6 + .../cli/{normalizer.py => __main__.py} | 0 lib/charset_normalizer/constant.py | 2070 ++++++++++++++--- lib/charset_normalizer/md.py | 49 +- lib/charset_normalizer/models.py | 11 +- lib/charset_normalizer/utils.py | 43 +- lib/charset_normalizer/version.py | 2 +- lib/idna/codec.py | 34 +- lib/idna/core.py | 10 +- lib/idna/idnadata.py | 9 +- lib/idna/package_data.py | 2 +- lib/idna/uts46data.py | 454 ++-- lib/oauthlib/__init__.py | 2 +- lib/oauthlib/common.py | 6 +- lib/oauthlib/oauth1/__init__.py | 35 +- lib/oauthlib/oauth1/rfc5849/endpoints/base.py | 11 +- .../oauth1/rfc5849/endpoints/request_token.py | 4 +- .../oauth1/rfc5849/endpoints/resource.py | 4 +- .../oauth1/rfc5849/request_validator.py | 6 +- lib/oauthlib/oauth1/rfc5849/signature.py | 65 +- .../rfc6749/clients/backend_application.py | 2 +- lib/oauthlib/oauth2/rfc6749/clients/base.py | 165 +- .../rfc6749/clients/legacy_application.py | 2 +- .../rfc6749/clients/mobile_application.py | 2 +- .../rfc6749/clients/service_application.py | 4 +- .../oauth2/rfc6749/clients/web_application.py | 4 +- .../oauth2/rfc6749/endpoints/introspect.py | 10 +- .../oauth2/rfc6749/endpoints/metadata.py | 4 +- .../oauth2/rfc6749/endpoints/revocation.py | 4 +- .../rfc6749/grant_types/authorization_code.py | 18 - .../oauth2/rfc6749/grant_types/base.py | 18 + .../rfc6749/grant_types/refresh_token.py | 1 + lib/oauthlib/oauth2/rfc6749/parameters.py | 2 +- .../oauth2/rfc6749/request_validator.py | 10 +- lib/oauthlib/oauth2/rfc6749/tokens.py | 1 + lib/oauthlib/oauth2/rfc8628/clients/device.py | 7 +- .../openid/connect/core/endpoints/userinfo.py | 21 +- .../openid/connect/core/grant_types/base.py | 1 - .../connect/core/grant_types/dispatchers.py | 2 +- lib/oauthlib/openid/connect/core/tokens.py | 4 +- lib/oauthlib/uri_validate.py | 2 +- lib/requests_oauthlib/__init__.py | 3 +- .../compliance_fixes/__init__.py | 3 +- .../compliance_fixes/douban.py | 4 +- .../compliance_fixes/ebay.py | 3 +- .../compliance_fixes/facebook.py | 10 +- .../compliance_fixes/fitbit.py | 4 +- .../compliance_fixes/instagram.py | 5 +- .../compliance_fixes/mailchimp.py | 6 +- .../compliance_fixes/plentymarkets.py | 4 +- .../compliance_fixes/slack.py | 5 +- .../compliance_fixes/weibo.py | 4 +- lib/requests_oauthlib/oauth1_auth.py | 13 +- lib/requests_oauthlib/oauth1_session.py | 13 +- lib/requests_oauthlib/oauth2_auth.py | 1 - lib/requests_oauthlib/oauth2_session.py | 65 +- requirements.txt | 2 +- 60 files changed, 2414 insertions(+), 2291 deletions(-) create mode 100644 lib/charset_normalizer/__main__.py delete mode 100644 lib/charset_normalizer/assets/__init__.py rename lib/charset_normalizer/cli/{normalizer.py => __main__.py} (100%) diff --git a/lib/charset_normalizer/__main__.py b/lib/charset_normalizer/__main__.py new file mode 100644 index 00000000..beae2ef7 --- /dev/null +++ b/lib/charset_normalizer/__main__.py @@ -0,0 +1,4 @@ +from .cli import cli_detect + +if __name__ == "__main__": + cli_detect() diff --git a/lib/charset_normalizer/assets/__init__.py b/lib/charset_normalizer/assets/__init__.py deleted file mode 100644 index 9075930d..00000000 --- a/lib/charset_normalizer/assets/__init__.py +++ /dev/null @@ -1,1440 +0,0 @@ -# -*- coding: utf-8 -*- -from typing import Dict, List - -# Language label that contain the em dash "—" -# character are to be considered alternative seq to origin -FREQUENCIES: Dict[str, List[str]] = { - "English": [ - "e", - "a", - "t", - "i", - "o", - "n", - "s", - "r", - "h", - "l", - "d", - "c", - "u", - "m", - "f", - "p", - "g", - "w", - "y", - "b", - "v", - "k", - "x", - "j", - "z", - "q", - ], - "English—": [ - "e", - "a", - "t", - "i", - "o", - "n", - "s", - "r", - "h", - "l", - "d", - "c", - "m", - "u", - "f", - "p", - "g", - "w", - "b", - "y", - "v", - "k", - "j", - "x", - "z", - "q", - ], - "German": [ - "e", - "n", - "i", - "r", - "s", - "t", - "a", - "d", - "h", - "u", - "l", - "g", - "o", - "c", - "m", - "b", - "f", - "k", - "w", - "z", - "p", - "v", - "ü", - "ä", - "ö", - "j", - ], - "French": [ - "e", - "a", - "s", - "n", - "i", - "t", - "r", - "l", - "u", - "o", - "d", - "c", - "p", - "m", - "é", - "v", - "g", - "f", - "b", - "h", - "q", - "à", - "x", - "è", - "y", - "j", - ], - "Dutch": [ - "e", - "n", - "a", - "i", - "r", - "t", - "o", - "d", - "s", - "l", - "g", - "h", - "v", - "m", - "u", - "k", - "c", - "p", - "b", - "w", - "j", - "z", - "f", - "y", - "x", - "ë", - ], - "Italian": [ - "e", - "i", - "a", - "o", - "n", - "l", - "t", - "r", - "s", - "c", - "d", - "u", - "p", - "m", - "g", - "v", - "f", - "b", - "z", - "h", - "q", - "è", - "à", - "k", - "y", - "ò", - ], - "Polish": [ - "a", - "i", - "o", - "e", - "n", - "r", - "z", - "w", - "s", - "c", - "t", - "k", - "y", - "d", - "p", - "m", - "u", - "l", - "j", - "ł", - "g", - "b", - "h", - "ą", - "ę", - "ó", - ], - "Spanish": [ - "e", - "a", - "o", - "n", - "s", - "r", - "i", - "l", - "d", - "t", - "c", - "u", - "m", - "p", - "b", - "g", - "v", - "f", - "y", - "ó", - "h", - "q", - "í", - "j", - "z", - "á", - ], - "Russian": [ - "о", - "а", - "е", - "и", - "н", - "с", - "т", - "р", - "в", - "л", - "к", - "м", - "д", - "п", - "у", - "г", - "я", - "ы", - "з", - "б", - "й", - "ь", - "ч", - "х", - "ж", - "ц", - ], - # Jap-Kanji - "Japanese": [ - "人", - "一", - "大", - "亅", - "丁", - "丨", - "竹", - "笑", - "口", - "日", - "今", - "二", - "彳", - "行", - "十", - "土", - "丶", - "寸", - "寺", - "時", - "乙", - "丿", - "乂", - "气", - "気", - "冂", - "巾", - "亠", - "市", - "目", - "儿", - "見", - "八", - "小", - "凵", - "県", - "月", - "彐", - "門", - "間", - "木", - "東", - "山", - "出", - "本", - "中", - "刀", - "分", - "耳", - "又", - "取", - "最", - "言", - "田", - "心", - "思", - "刂", - "前", - "京", - "尹", - "事", - "生", - "厶", - "云", - "会", - "未", - "来", - "白", - "冫", - "楽", - "灬", - "馬", - "尸", - "尺", - "駅", - "明", - "耂", - "者", - "了", - "阝", - "都", - "高", - "卜", - "占", - "厂", - "广", - "店", - "子", - "申", - "奄", - "亻", - "俺", - "上", - "方", - "冖", - "学", - "衣", - "艮", - "食", - "自", - ], - # Jap-Katakana - "Japanese—": [ - "ー", - "ン", - "ス", - "・", - "ル", - "ト", - "リ", - "イ", - "ア", - "ラ", - "ッ", - "ク", - "ド", - "シ", - "レ", - "ジ", - "タ", - "フ", - "ロ", - "カ", - "テ", - "マ", - "ィ", - "グ", - "バ", - "ム", - "プ", - "オ", - "コ", - "デ", - "ニ", - "ウ", - "メ", - "サ", - "ビ", - "ナ", - "ブ", - "ャ", - "エ", - "ュ", - "チ", - "キ", - "ズ", - "ダ", - "パ", - "ミ", - "ェ", - "ョ", - "ハ", - "セ", - "ベ", - "ガ", - "モ", - "ツ", - "ネ", - "ボ", - "ソ", - "ノ", - "ァ", - "ヴ", - "ワ", - "ポ", - "ペ", - "ピ", - "ケ", - "ゴ", - "ギ", - "ザ", - "ホ", - "ゲ", - "ォ", - "ヤ", - "ヒ", - "ユ", - "ヨ", - "ヘ", - "ゼ", - "ヌ", - "ゥ", - "ゾ", - "ヶ", - "ヂ", - "ヲ", - "ヅ", - "ヵ", - "ヱ", - "ヰ", - "ヮ", - "ヽ", - "゠", - "ヾ", - "ヷ", - "ヿ", - "ヸ", - "ヹ", - "ヺ", - ], - # Jap-Hiragana - "Japanese——": [ - "の", - "に", - "る", - "た", - "と", - "は", - "し", - "い", - "を", - "で", - "て", - "が", - "な", - "れ", - "か", - "ら", - "さ", - "っ", - "り", - "す", - "あ", - "も", - "こ", - "ま", - "う", - "く", - "よ", - "き", - "ん", - "め", - "お", - "け", - "そ", - "つ", - "だ", - "や", - "え", - "ど", - "わ", - "ち", - "み", - "せ", - "じ", - "ば", - "へ", - "び", - "ず", - "ろ", - "ほ", - "げ", - "む", - "べ", - "ひ", - "ょ", - "ゆ", - "ぶ", - "ご", - "ゃ", - "ね", - "ふ", - "ぐ", - "ぎ", - "ぼ", - "ゅ", - "づ", - "ざ", - "ぞ", - "ぬ", - "ぜ", - "ぱ", - "ぽ", - "ぷ", - "ぴ", - "ぃ", - "ぁ", - "ぇ", - "ぺ", - "ゞ", - "ぢ", - "ぉ", - "ぅ", - "ゐ", - "ゝ", - "ゑ", - "゛", - "゜", - "ゎ", - "ゔ", - "゚", - "ゟ", - "゙", - "ゕ", - "ゖ", - ], - "Portuguese": [ - "a", - "e", - "o", - "s", - "i", - "r", - "d", - "n", - "t", - "m", - "u", - "c", - "l", - "p", - "g", - "v", - "b", - "f", - "h", - "ã", - "q", - "é", - "ç", - "á", - "z", - "í", - ], - "Swedish": [ - "e", - "a", - "n", - "r", - "t", - "s", - "i", - "l", - "d", - "o", - "m", - "k", - "g", - "v", - "h", - "f", - "u", - "p", - "ä", - "c", - "b", - "ö", - "å", - "y", - "j", - "x", - ], - "Chinese": [ - "的", - "一", - "是", - "不", - "了", - "在", - "人", - "有", - "我", - "他", - "这", - "个", - "们", - "中", - "来", - "上", - "大", - "为", - "和", - "国", - "地", - "到", - "以", - "说", - "时", - "要", - "就", - "出", - "会", - "可", - "也", - "你", - "对", - "生", - "能", - "而", - "子", - "那", - "得", - "于", - "着", - "下", - "自", - "之", - "年", - "过", - "发", - "后", - "作", - "里", - "用", - "道", - "行", - "所", - "然", - "家", - "种", - "事", - "成", - "方", - "多", - "经", - "么", - "去", - "法", - "学", - "如", - "都", - "同", - "现", - "当", - "没", - "动", - "面", - "起", - "看", - "定", - "天", - "分", - "还", - "进", - "好", - "小", - "部", - "其", - "些", - "主", - "样", - "理", - "心", - "她", - "本", - "前", - "开", - "但", - "因", - "只", - "从", - "想", - "实", - ], - "Ukrainian": [ - "о", - "а", - "н", - "і", - "и", - "р", - "в", - "т", - "е", - "с", - "к", - "л", - "у", - "д", - "м", - "п", - "з", - "я", - "ь", - "б", - "г", - "й", - "ч", - "х", - "ц", - "ї", - ], - "Norwegian": [ - "e", - "r", - "n", - "t", - "a", - "s", - "i", - "o", - "l", - "d", - "g", - "k", - "m", - "v", - "f", - "p", - "u", - "b", - "h", - "å", - "y", - "j", - "ø", - "c", - "æ", - "w", - ], - "Finnish": [ - "a", - "i", - "n", - "t", - "e", - "s", - "l", - "o", - "u", - "k", - "ä", - "m", - "r", - "v", - "j", - "h", - "p", - "y", - "d", - "ö", - "g", - "c", - "b", - "f", - "w", - "z", - ], - "Vietnamese": [ - "n", - "h", - "t", - "i", - "c", - "g", - "a", - "o", - "u", - "m", - "l", - "r", - "à", - "đ", - "s", - "e", - "v", - "p", - "b", - "y", - "ư", - "d", - "á", - "k", - "ộ", - "ế", - ], - "Czech": [ - "o", - "e", - "a", - "n", - "t", - "s", - "i", - "l", - "v", - "r", - "k", - "d", - "u", - "m", - "p", - "í", - "c", - "h", - "z", - "á", - "y", - "j", - "b", - "ě", - "é", - "ř", - ], - "Hungarian": [ - "e", - "a", - "t", - "l", - "s", - "n", - "k", - "r", - "i", - "o", - "z", - "á", - "é", - "g", - "m", - "b", - "y", - "v", - "d", - "h", - "u", - "p", - "j", - "ö", - "f", - "c", - ], - "Korean": [ - "이", - "다", - "에", - "의", - "는", - "로", - "하", - "을", - "가", - "고", - "지", - "서", - "한", - "은", - "기", - "으", - "년", - "대", - "사", - "시", - "를", - "리", - "도", - "인", - "스", - "일", - ], - "Indonesian": [ - "a", - "n", - "e", - "i", - "r", - "t", - "u", - "s", - "d", - "k", - "m", - "l", - "g", - "p", - "b", - "o", - "h", - "y", - "j", - "c", - "w", - "f", - "v", - "z", - "x", - "q", - ], - "Turkish": [ - "a", - "e", - "i", - "n", - "r", - "l", - "ı", - "k", - "d", - "t", - "s", - "m", - "y", - "u", - "o", - "b", - "ü", - "ş", - "v", - "g", - "z", - "h", - "c", - "p", - "ç", - "ğ", - ], - "Romanian": [ - "e", - "i", - "a", - "r", - "n", - "t", - "u", - "l", - "o", - "c", - "s", - "d", - "p", - "m", - "ă", - "f", - "v", - "î", - "g", - "b", - "ș", - "ț", - "z", - "h", - "â", - "j", - ], - "Farsi": [ - "ا", - "ی", - "ر", - "د", - "ن", - "ه", - "و", - "م", - "ت", - "ب", - "س", - "ل", - "ک", - "ش", - "ز", - "ف", - "گ", - "ع", - "خ", - "ق", - "ج", - "آ", - "پ", - "ح", - "ط", - "ص", - ], - "Arabic": [ - "ا", - "ل", - "ي", - "م", - "و", - "ن", - "ر", - "ت", - "ب", - "ة", - "ع", - "د", - "س", - "ف", - "ه", - "ك", - "ق", - "أ", - "ح", - "ج", - "ش", - "ط", - "ص", - "ى", - "خ", - "إ", - ], - "Danish": [ - "e", - "r", - "n", - "t", - "a", - "i", - "s", - "d", - "l", - "o", - "g", - "m", - "k", - "f", - "v", - "u", - "b", - "h", - "p", - "å", - "y", - "ø", - "æ", - "c", - "j", - "w", - ], - "Serbian": [ - "а", - "и", - "о", - "е", - "н", - "р", - "с", - "у", - "т", - "к", - "ј", - "в", - "д", - "м", - "п", - "л", - "г", - "з", - "б", - "a", - "i", - "e", - "o", - "n", - "ц", - "ш", - ], - "Lithuanian": [ - "i", - "a", - "s", - "o", - "r", - "e", - "t", - "n", - "u", - "k", - "m", - "l", - "p", - "v", - "d", - "j", - "g", - "ė", - "b", - "y", - "ų", - "š", - "ž", - "c", - "ą", - "į", - ], - "Slovene": [ - "e", - "a", - "i", - "o", - "n", - "r", - "s", - "l", - "t", - "j", - "v", - "k", - "d", - "p", - "m", - "u", - "z", - "b", - "g", - "h", - "č", - "c", - "š", - "ž", - "f", - "y", - ], - "Slovak": [ - "o", - "a", - "e", - "n", - "i", - "r", - "v", - "t", - "s", - "l", - "k", - "d", - "m", - "p", - "u", - "c", - "h", - "j", - "b", - "z", - "á", - "y", - "ý", - "í", - "č", - "é", - ], - "Hebrew": [ - "י", - "ו", - "ה", - "ל", - "ר", - "ב", - "ת", - "מ", - "א", - "ש", - "נ", - "ע", - "ם", - "ד", - "ק", - "ח", - "פ", - "ס", - "כ", - "ג", - "ט", - "צ", - "ן", - "ז", - "ך", - ], - "Bulgarian": [ - "а", - "и", - "о", - "е", - "н", - "т", - "р", - "с", - "в", - "л", - "к", - "д", - "п", - "м", - "з", - "г", - "я", - "ъ", - "у", - "б", - "ч", - "ц", - "й", - "ж", - "щ", - "х", - ], - "Croatian": [ - "a", - "i", - "o", - "e", - "n", - "r", - "j", - "s", - "t", - "u", - "k", - "l", - "v", - "d", - "m", - "p", - "g", - "z", - "b", - "c", - "č", - "h", - "š", - "ž", - "ć", - "f", - ], - "Hindi": [ - "क", - "र", - "स", - "न", - "त", - "म", - "ह", - "प", - "य", - "ल", - "व", - "ज", - "द", - "ग", - "ब", - "श", - "ट", - "अ", - "ए", - "थ", - "भ", - "ड", - "च", - "ध", - "ष", - "इ", - ], - "Estonian": [ - "a", - "i", - "e", - "s", - "t", - "l", - "u", - "n", - "o", - "k", - "r", - "d", - "m", - "v", - "g", - "p", - "j", - "h", - "ä", - "b", - "õ", - "ü", - "f", - "c", - "ö", - "y", - ], - "Thai": [ - "า", - "น", - "ร", - "อ", - "ก", - "เ", - "ง", - "ม", - "ย", - "ล", - "ว", - "ด", - "ท", - "ส", - "ต", - "ะ", - "ป", - "บ", - "ค", - "ห", - "แ", - "จ", - "พ", - "ช", - "ข", - "ใ", - ], - "Greek": [ - "α", - "τ", - "ο", - "ι", - "ε", - "ν", - "ρ", - "σ", - "κ", - "η", - "π", - "ς", - "υ", - "μ", - "λ", - "ί", - "ό", - "ά", - "γ", - "έ", - "δ", - "ή", - "ω", - "χ", - "θ", - "ύ", - ], - "Tamil": [ - "க", - "த", - "ப", - "ட", - "ர", - "ம", - "ல", - "ன", - "வ", - "ற", - "ய", - "ள", - "ச", - "ந", - "இ", - "ண", - "அ", - "ஆ", - "ழ", - "ங", - "எ", - "உ", - "ஒ", - "ஸ", - ], - "Kazakh": [ - "а", - "ы", - "е", - "н", - "т", - "р", - "л", - "і", - "д", - "с", - "м", - "қ", - "к", - "о", - "б", - "и", - "у", - "ғ", - "ж", - "ң", - "з", - "ш", - "й", - "п", - "г", - "ө", - ], -} diff --git a/lib/charset_normalizer/cd.py b/lib/charset_normalizer/cd.py index 6e56fe84..4ea6760c 100644 --- a/lib/charset_normalizer/cd.py +++ b/lib/charset_normalizer/cd.py @@ -4,8 +4,13 @@ from collections import Counter from functools import lru_cache from typing import Counter as TypeCounter, Dict, List, Optional, Tuple -from .assets import FREQUENCIES -from .constant import KO_NAMES, LANGUAGE_SUPPORTED_COUNT, TOO_SMALL_SEQUENCE, ZH_NAMES +from .constant import ( + FREQUENCIES, + KO_NAMES, + LANGUAGE_SUPPORTED_COUNT, + TOO_SMALL_SEQUENCE, + ZH_NAMES, +) from .md import is_suspiciously_successive_range from .models import CoherenceMatches from .utils import ( diff --git a/lib/charset_normalizer/cli/__init__.py b/lib/charset_normalizer/cli/__init__.py index e69de29b..d95fedfe 100644 --- a/lib/charset_normalizer/cli/__init__.py +++ b/lib/charset_normalizer/cli/__init__.py @@ -0,0 +1,6 @@ +from .__main__ import cli_detect, query_yes_no + +__all__ = ( + "cli_detect", + "query_yes_no", +) diff --git a/lib/charset_normalizer/cli/normalizer.py b/lib/charset_normalizer/cli/__main__.py similarity index 100% rename from lib/charset_normalizer/cli/normalizer.py rename to lib/charset_normalizer/cli/__main__.py diff --git a/lib/charset_normalizer/constant.py b/lib/charset_normalizer/constant.py index 3188108d..86349046 100644 --- a/lib/charset_normalizer/constant.py +++ b/lib/charset_normalizer/constant.py @@ -1,10 +1,9 @@ +# -*- coding: utf-8 -*- from codecs import BOM_UTF8, BOM_UTF16_BE, BOM_UTF16_LE, BOM_UTF32_BE, BOM_UTF32_LE from encodings.aliases import aliases from re import IGNORECASE, compile as re_compile from typing import Dict, List, Set, Union -from .assets import FREQUENCIES - # Contain for each eligible encoding a list of/item bytes SIG/BOM ENCODING_MARKS: Dict[str, Union[bytes, List[bytes]]] = { "utf_8": BOM_UTF8, @@ -23,288 +22,338 @@ ENCODING_MARKS: Dict[str, Union[bytes, List[bytes]]] = { TOO_SMALL_SEQUENCE: int = 32 TOO_BIG_SEQUENCE: int = int(10e6) -UTF8_MAXIMAL_ALLOCATION: int = 1112064 +UTF8_MAXIMAL_ALLOCATION: int = 1_112_064 +# Up-to-date Unicode ucd/15.0.0 UNICODE_RANGES_COMBINED: Dict[str, range] = { - "Control character": range(31 + 1), - "Basic Latin": range(32, 127 + 1), - "Latin-1 Supplement": range(128, 255 + 1), - "Latin Extended-A": range(256, 383 + 1), - "Latin Extended-B": range(384, 591 + 1), - "IPA Extensions": range(592, 687 + 1), - "Spacing Modifier Letters": range(688, 767 + 1), - "Combining Diacritical Marks": range(768, 879 + 1), - "Greek and Coptic": range(880, 1023 + 1), - "Cyrillic": range(1024, 1279 + 1), - "Cyrillic Supplement": range(1280, 1327 + 1), - "Armenian": range(1328, 1423 + 1), - "Hebrew": range(1424, 1535 + 1), - "Arabic": range(1536, 1791 + 1), - "Syriac": range(1792, 1871 + 1), - "Arabic Supplement": range(1872, 1919 + 1), - "Thaana": range(1920, 1983 + 1), - "NKo": range(1984, 2047 + 1), - "Samaritan": range(2048, 2111 + 1), - "Mandaic": range(2112, 2143 + 1), - "Syriac Supplement": range(2144, 2159 + 1), - "Arabic Extended-A": range(2208, 2303 + 1), - "Devanagari": range(2304, 2431 + 1), - "Bengali": range(2432, 2559 + 1), - "Gurmukhi": range(2560, 2687 + 1), - "Gujarati": range(2688, 2815 + 1), - "Oriya": range(2816, 2943 + 1), - "Tamil": range(2944, 3071 + 1), - "Telugu": range(3072, 3199 + 1), - "Kannada": range(3200, 3327 + 1), - "Malayalam": range(3328, 3455 + 1), - "Sinhala": range(3456, 3583 + 1), - "Thai": range(3584, 3711 + 1), - "Lao": range(3712, 3839 + 1), - "Tibetan": range(3840, 4095 + 1), - "Myanmar": range(4096, 4255 + 1), - "Georgian": range(4256, 4351 + 1), - "Hangul Jamo": range(4352, 4607 + 1), - "Ethiopic": range(4608, 4991 + 1), - "Ethiopic Supplement": range(4992, 5023 + 1), - "Cherokee": range(5024, 5119 + 1), - "Unified Canadian Aboriginal Syllabics": range(5120, 5759 + 1), - "Ogham": range(5760, 5791 + 1), - "Runic": range(5792, 5887 + 1), - "Tagalog": range(5888, 5919 + 1), - "Hanunoo": range(5920, 5951 + 1), - "Buhid": range(5952, 5983 + 1), - "Tagbanwa": range(5984, 6015 + 1), - "Khmer": range(6016, 6143 + 1), - "Mongolian": range(6144, 6319 + 1), - "Unified Canadian Aboriginal Syllabics Extended": range(6320, 6399 + 1), - "Limbu": range(6400, 6479 + 1), - "Tai Le": range(6480, 6527 + 1), - "New Tai Lue": range(6528, 6623 + 1), - "Khmer Symbols": range(6624, 6655 + 1), - "Buginese": range(6656, 6687 + 1), - "Tai Tham": range(6688, 6831 + 1), - "Combining Diacritical Marks Extended": range(6832, 6911 + 1), - "Balinese": range(6912, 7039 + 1), - "Sundanese": range(7040, 7103 + 1), - "Batak": range(7104, 7167 + 1), - "Lepcha": range(7168, 7247 + 1), - "Ol Chiki": range(7248, 7295 + 1), - "Cyrillic Extended C": range(7296, 7311 + 1), - "Sundanese Supplement": range(7360, 7375 + 1), - "Vedic Extensions": range(7376, 7423 + 1), - "Phonetic Extensions": range(7424, 7551 + 1), - "Phonetic Extensions Supplement": range(7552, 7615 + 1), - "Combining Diacritical Marks Supplement": range(7616, 7679 + 1), - "Latin Extended Additional": range(7680, 7935 + 1), - "Greek Extended": range(7936, 8191 + 1), - "General Punctuation": range(8192, 8303 + 1), - "Superscripts and Subscripts": range(8304, 8351 + 1), - "Currency Symbols": range(8352, 8399 + 1), - "Combining Diacritical Marks for Symbols": range(8400, 8447 + 1), - "Letterlike Symbols": range(8448, 8527 + 1), - "Number Forms": range(8528, 8591 + 1), - "Arrows": range(8592, 8703 + 1), - "Mathematical Operators": range(8704, 8959 + 1), - "Miscellaneous Technical": range(8960, 9215 + 1), - "Control Pictures": range(9216, 9279 + 1), - "Optical Character Recognition": range(9280, 9311 + 1), - "Enclosed Alphanumerics": range(9312, 9471 + 1), - "Box Drawing": range(9472, 9599 + 1), - "Block Elements": range(9600, 9631 + 1), - "Geometric Shapes": range(9632, 9727 + 1), - "Miscellaneous Symbols": range(9728, 9983 + 1), - "Dingbats": range(9984, 10175 + 1), - "Miscellaneous Mathematical Symbols-A": range(10176, 10223 + 1), - "Supplemental Arrows-A": range(10224, 10239 + 1), - "Braille Patterns": range(10240, 10495 + 1), - "Supplemental Arrows-B": range(10496, 10623 + 1), - "Miscellaneous Mathematical Symbols-B": range(10624, 10751 + 1), - "Supplemental Mathematical Operators": range(10752, 11007 + 1), - "Miscellaneous Symbols and Arrows": range(11008, 11263 + 1), - "Glagolitic": range(11264, 11359 + 1), - "Latin Extended-C": range(11360, 11391 + 1), - "Coptic": range(11392, 11519 + 1), - "Georgian Supplement": range(11520, 11567 + 1), - "Tifinagh": range(11568, 11647 + 1), - "Ethiopic Extended": range(11648, 11743 + 1), - "Cyrillic Extended-A": range(11744, 11775 + 1), - "Supplemental Punctuation": range(11776, 11903 + 1), - "CJK Radicals Supplement": range(11904, 12031 + 1), - "Kangxi Radicals": range(12032, 12255 + 1), - "Ideographic Description Characters": range(12272, 12287 + 1), - "CJK Symbols and Punctuation": range(12288, 12351 + 1), - "Hiragana": range(12352, 12447 + 1), - "Katakana": range(12448, 12543 + 1), - "Bopomofo": range(12544, 12591 + 1), - "Hangul Compatibility Jamo": range(12592, 12687 + 1), - "Kanbun": range(12688, 12703 + 1), - "Bopomofo Extended": range(12704, 12735 + 1), - "CJK Strokes": range(12736, 12783 + 1), - "Katakana Phonetic Extensions": range(12784, 12799 + 1), - "Enclosed CJK Letters and Months": range(12800, 13055 + 1), - "CJK Compatibility": range(13056, 13311 + 1), - "CJK Unified Ideographs Extension A": range(13312, 19903 + 1), - "Yijing Hexagram Symbols": range(19904, 19967 + 1), - "CJK Unified Ideographs": range(19968, 40959 + 1), - "Yi Syllables": range(40960, 42127 + 1), - "Yi Radicals": range(42128, 42191 + 1), - "Lisu": range(42192, 42239 + 1), - "Vai": range(42240, 42559 + 1), - "Cyrillic Extended-B": range(42560, 42655 + 1), - "Bamum": range(42656, 42751 + 1), - "Modifier Tone Letters": range(42752, 42783 + 1), - "Latin Extended-D": range(42784, 43007 + 1), - "Syloti Nagri": range(43008, 43055 + 1), - "Common Indic Number Forms": range(43056, 43071 + 1), - "Phags-pa": range(43072, 43135 + 1), - "Saurashtra": range(43136, 43231 + 1), - "Devanagari Extended": range(43232, 43263 + 1), - "Kayah Li": range(43264, 43311 + 1), - "Rejang": range(43312, 43359 + 1), - "Hangul Jamo Extended-A": range(43360, 43391 + 1), - "Javanese": range(43392, 43487 + 1), - "Myanmar Extended-B": range(43488, 43519 + 1), - "Cham": range(43520, 43615 + 1), - "Myanmar Extended-A": range(43616, 43647 + 1), - "Tai Viet": range(43648, 43743 + 1), - "Meetei Mayek Extensions": range(43744, 43775 + 1), - "Ethiopic Extended-A": range(43776, 43823 + 1), - "Latin Extended-E": range(43824, 43887 + 1), - "Cherokee Supplement": range(43888, 43967 + 1), - "Meetei Mayek": range(43968, 44031 + 1), - "Hangul Syllables": range(44032, 55215 + 1), - "Hangul Jamo Extended-B": range(55216, 55295 + 1), - "High Surrogates": range(55296, 56191 + 1), - "High Private Use Surrogates": range(56192, 56319 + 1), - "Low Surrogates": range(56320, 57343 + 1), - "Private Use Area": range(57344, 63743 + 1), - "CJK Compatibility Ideographs": range(63744, 64255 + 1), - "Alphabetic Presentation Forms": range(64256, 64335 + 1), - "Arabic Presentation Forms-A": range(64336, 65023 + 1), - "Variation Selectors": range(65024, 65039 + 1), - "Vertical Forms": range(65040, 65055 + 1), - "Combining Half Marks": range(65056, 65071 + 1), - "CJK Compatibility Forms": range(65072, 65103 + 1), - "Small Form Variants": range(65104, 65135 + 1), - "Arabic Presentation Forms-B": range(65136, 65279 + 1), - "Halfwidth and Fullwidth Forms": range(65280, 65519 + 1), - "Specials": range(65520, 65535 + 1), - "Linear B Syllabary": range(65536, 65663 + 1), - "Linear B Ideograms": range(65664, 65791 + 1), - "Aegean Numbers": range(65792, 65855 + 1), - "Ancient Greek Numbers": range(65856, 65935 + 1), - "Ancient Symbols": range(65936, 65999 + 1), - "Phaistos Disc": range(66000, 66047 + 1), - "Lycian": range(66176, 66207 + 1), - "Carian": range(66208, 66271 + 1), - "Coptic Epact Numbers": range(66272, 66303 + 1), - "Old Italic": range(66304, 66351 + 1), - "Gothic": range(66352, 66383 + 1), - "Old Permic": range(66384, 66431 + 1), - "Ugaritic": range(66432, 66463 + 1), - "Old Persian": range(66464, 66527 + 1), - "Deseret": range(66560, 66639 + 1), - "Shavian": range(66640, 66687 + 1), - "Osmanya": range(66688, 66735 + 1), - "Osage": range(66736, 66815 + 1), - "Elbasan": range(66816, 66863 + 1), - "Caucasian Albanian": range(66864, 66927 + 1), - "Linear A": range(67072, 67455 + 1), - "Cypriot Syllabary": range(67584, 67647 + 1), - "Imperial Aramaic": range(67648, 67679 + 1), - "Palmyrene": range(67680, 67711 + 1), - "Nabataean": range(67712, 67759 + 1), - "Hatran": range(67808, 67839 + 1), - "Phoenician": range(67840, 67871 + 1), - "Lydian": range(67872, 67903 + 1), - "Meroitic Hieroglyphs": range(67968, 67999 + 1), - "Meroitic Cursive": range(68000, 68095 + 1), - "Kharoshthi": range(68096, 68191 + 1), - "Old South Arabian": range(68192, 68223 + 1), - "Old North Arabian": range(68224, 68255 + 1), - "Manichaean": range(68288, 68351 + 1), - "Avestan": range(68352, 68415 + 1), - "Inscriptional Parthian": range(68416, 68447 + 1), - "Inscriptional Pahlavi": range(68448, 68479 + 1), - "Psalter Pahlavi": range(68480, 68527 + 1), - "Old Turkic": range(68608, 68687 + 1), - "Old Hungarian": range(68736, 68863 + 1), - "Rumi Numeral Symbols": range(69216, 69247 + 1), - "Brahmi": range(69632, 69759 + 1), - "Kaithi": range(69760, 69839 + 1), - "Sora Sompeng": range(69840, 69887 + 1), - "Chakma": range(69888, 69967 + 1), - "Mahajani": range(69968, 70015 + 1), - "Sharada": range(70016, 70111 + 1), - "Sinhala Archaic Numbers": range(70112, 70143 + 1), - "Khojki": range(70144, 70223 + 1), - "Multani": range(70272, 70319 + 1), - "Khudawadi": range(70320, 70399 + 1), - "Grantha": range(70400, 70527 + 1), - "Newa": range(70656, 70783 + 1), - "Tirhuta": range(70784, 70879 + 1), - "Siddham": range(71040, 71167 + 1), - "Modi": range(71168, 71263 + 1), - "Mongolian Supplement": range(71264, 71295 + 1), - "Takri": range(71296, 71375 + 1), - "Ahom": range(71424, 71487 + 1), - "Warang Citi": range(71840, 71935 + 1), - "Zanabazar Square": range(72192, 72271 + 1), - "Soyombo": range(72272, 72367 + 1), - "Pau Cin Hau": range(72384, 72447 + 1), - "Bhaiksuki": range(72704, 72815 + 1), - "Marchen": range(72816, 72895 + 1), - "Masaram Gondi": range(72960, 73055 + 1), - "Cuneiform": range(73728, 74751 + 1), - "Cuneiform Numbers and Punctuation": range(74752, 74879 + 1), - "Early Dynastic Cuneiform": range(74880, 75087 + 1), - "Egyptian Hieroglyphs": range(77824, 78895 + 1), - "Anatolian Hieroglyphs": range(82944, 83583 + 1), - "Bamum Supplement": range(92160, 92735 + 1), - "Mro": range(92736, 92783 + 1), - "Bassa Vah": range(92880, 92927 + 1), - "Pahawh Hmong": range(92928, 93071 + 1), - "Miao": range(93952, 94111 + 1), - "Ideographic Symbols and Punctuation": range(94176, 94207 + 1), - "Tangut": range(94208, 100351 + 1), - "Tangut Components": range(100352, 101119 + 1), - "Kana Supplement": range(110592, 110847 + 1), - "Kana Extended-A": range(110848, 110895 + 1), - "Nushu": range(110960, 111359 + 1), - "Duployan": range(113664, 113823 + 1), - "Shorthand Format Controls": range(113824, 113839 + 1), - "Byzantine Musical Symbols": range(118784, 119039 + 1), - "Musical Symbols": range(119040, 119295 + 1), - "Ancient Greek Musical Notation": range(119296, 119375 + 1), - "Tai Xuan Jing Symbols": range(119552, 119647 + 1), - "Counting Rod Numerals": range(119648, 119679 + 1), - "Mathematical Alphanumeric Symbols": range(119808, 120831 + 1), - "Sutton SignWriting": range(120832, 121519 + 1), - "Glagolitic Supplement": range(122880, 122927 + 1), - "Mende Kikakui": range(124928, 125151 + 1), - "Adlam": range(125184, 125279 + 1), - "Arabic Mathematical Alphabetic Symbols": range(126464, 126719 + 1), - "Mahjong Tiles": range(126976, 127023 + 1), - "Domino Tiles": range(127024, 127135 + 1), - "Playing Cards": range(127136, 127231 + 1), - "Enclosed Alphanumeric Supplement": range(127232, 127487 + 1), - "Enclosed Ideographic Supplement": range(127488, 127743 + 1), - "Miscellaneous Symbols and Pictographs": range(127744, 128511 + 1), - "Emoticons range(Emoji)": range(128512, 128591 + 1), - "Ornamental Dingbats": range(128592, 128639 + 1), - "Transport and Map Symbols": range(128640, 128767 + 1), - "Alchemical Symbols": range(128768, 128895 + 1), - "Geometric Shapes Extended": range(128896, 129023 + 1), - "Supplemental Arrows-C": range(129024, 129279 + 1), - "Supplemental Symbols and Pictographs": range(129280, 129535 + 1), - "CJK Unified Ideographs Extension B": range(131072, 173791 + 1), - "CJK Unified Ideographs Extension C": range(173824, 177983 + 1), - "CJK Unified Ideographs Extension D": range(177984, 178207 + 1), - "CJK Unified Ideographs Extension E": range(178208, 183983 + 1), - "CJK Unified Ideographs Extension F": range(183984, 191471 + 1), - "CJK Compatibility Ideographs Supplement": range(194560, 195103 + 1), - "Tags": range(917504, 917631 + 1), - "Variation Selectors Supplement": range(917760, 917999 + 1), + "Control character": range(32), + "Basic Latin": range(32, 128), + "Latin-1 Supplement": range(128, 256), + "Latin Extended-A": range(256, 384), + "Latin Extended-B": range(384, 592), + "IPA Extensions": range(592, 688), + "Spacing Modifier Letters": range(688, 768), + "Combining Diacritical Marks": range(768, 880), + "Greek and Coptic": range(880, 1024), + "Cyrillic": range(1024, 1280), + "Cyrillic Supplement": range(1280, 1328), + "Armenian": range(1328, 1424), + "Hebrew": range(1424, 1536), + "Arabic": range(1536, 1792), + "Syriac": range(1792, 1872), + "Arabic Supplement": range(1872, 1920), + "Thaana": range(1920, 1984), + "NKo": range(1984, 2048), + "Samaritan": range(2048, 2112), + "Mandaic": range(2112, 2144), + "Syriac Supplement": range(2144, 2160), + "Arabic Extended-B": range(2160, 2208), + "Arabic Extended-A": range(2208, 2304), + "Devanagari": range(2304, 2432), + "Bengali": range(2432, 2560), + "Gurmukhi": range(2560, 2688), + "Gujarati": range(2688, 2816), + "Oriya": range(2816, 2944), + "Tamil": range(2944, 3072), + "Telugu": range(3072, 3200), + "Kannada": range(3200, 3328), + "Malayalam": range(3328, 3456), + "Sinhala": range(3456, 3584), + "Thai": range(3584, 3712), + "Lao": range(3712, 3840), + "Tibetan": range(3840, 4096), + "Myanmar": range(4096, 4256), + "Georgian": range(4256, 4352), + "Hangul Jamo": range(4352, 4608), + "Ethiopic": range(4608, 4992), + "Ethiopic Supplement": range(4992, 5024), + "Cherokee": range(5024, 5120), + "Unified Canadian Aboriginal Syllabics": range(5120, 5760), + "Ogham": range(5760, 5792), + "Runic": range(5792, 5888), + "Tagalog": range(5888, 5920), + "Hanunoo": range(5920, 5952), + "Buhid": range(5952, 5984), + "Tagbanwa": range(5984, 6016), + "Khmer": range(6016, 6144), + "Mongolian": range(6144, 6320), + "Unified Canadian Aboriginal Syllabics Extended": range(6320, 6400), + "Limbu": range(6400, 6480), + "Tai Le": range(6480, 6528), + "New Tai Lue": range(6528, 6624), + "Khmer Symbols": range(6624, 6656), + "Buginese": range(6656, 6688), + "Tai Tham": range(6688, 6832), + "Combining Diacritical Marks Extended": range(6832, 6912), + "Balinese": range(6912, 7040), + "Sundanese": range(7040, 7104), + "Batak": range(7104, 7168), + "Lepcha": range(7168, 7248), + "Ol Chiki": range(7248, 7296), + "Cyrillic Extended-C": range(7296, 7312), + "Georgian Extended": range(7312, 7360), + "Sundanese Supplement": range(7360, 7376), + "Vedic Extensions": range(7376, 7424), + "Phonetic Extensions": range(7424, 7552), + "Phonetic Extensions Supplement": range(7552, 7616), + "Combining Diacritical Marks Supplement": range(7616, 7680), + "Latin Extended Additional": range(7680, 7936), + "Greek Extended": range(7936, 8192), + "General Punctuation": range(8192, 8304), + "Superscripts and Subscripts": range(8304, 8352), + "Currency Symbols": range(8352, 8400), + "Combining Diacritical Marks for Symbols": range(8400, 8448), + "Letterlike Symbols": range(8448, 8528), + "Number Forms": range(8528, 8592), + "Arrows": range(8592, 8704), + "Mathematical Operators": range(8704, 8960), + "Miscellaneous Technical": range(8960, 9216), + "Control Pictures": range(9216, 9280), + "Optical Character Recognition": range(9280, 9312), + "Enclosed Alphanumerics": range(9312, 9472), + "Box Drawing": range(9472, 9600), + "Block Elements": range(9600, 9632), + "Geometric Shapes": range(9632, 9728), + "Miscellaneous Symbols": range(9728, 9984), + "Dingbats": range(9984, 10176), + "Miscellaneous Mathematical Symbols-A": range(10176, 10224), + "Supplemental Arrows-A": range(10224, 10240), + "Braille Patterns": range(10240, 10496), + "Supplemental Arrows-B": range(10496, 10624), + "Miscellaneous Mathematical Symbols-B": range(10624, 10752), + "Supplemental Mathematical Operators": range(10752, 11008), + "Miscellaneous Symbols and Arrows": range(11008, 11264), + "Glagolitic": range(11264, 11360), + "Latin Extended-C": range(11360, 11392), + "Coptic": range(11392, 11520), + "Georgian Supplement": range(11520, 11568), + "Tifinagh": range(11568, 11648), + "Ethiopic Extended": range(11648, 11744), + "Cyrillic Extended-A": range(11744, 11776), + "Supplemental Punctuation": range(11776, 11904), + "CJK Radicals Supplement": range(11904, 12032), + "Kangxi Radicals": range(12032, 12256), + "Ideographic Description Characters": range(12272, 12288), + "CJK Symbols and Punctuation": range(12288, 12352), + "Hiragana": range(12352, 12448), + "Katakana": range(12448, 12544), + "Bopomofo": range(12544, 12592), + "Hangul Compatibility Jamo": range(12592, 12688), + "Kanbun": range(12688, 12704), + "Bopomofo Extended": range(12704, 12736), + "CJK Strokes": range(12736, 12784), + "Katakana Phonetic Extensions": range(12784, 12800), + "Enclosed CJK Letters and Months": range(12800, 13056), + "CJK Compatibility": range(13056, 13312), + "CJK Unified Ideographs Extension A": range(13312, 19904), + "Yijing Hexagram Symbols": range(19904, 19968), + "CJK Unified Ideographs": range(19968, 40960), + "Yi Syllables": range(40960, 42128), + "Yi Radicals": range(42128, 42192), + "Lisu": range(42192, 42240), + "Vai": range(42240, 42560), + "Cyrillic Extended-B": range(42560, 42656), + "Bamum": range(42656, 42752), + "Modifier Tone Letters": range(42752, 42784), + "Latin Extended-D": range(42784, 43008), + "Syloti Nagri": range(43008, 43056), + "Common Indic Number Forms": range(43056, 43072), + "Phags-pa": range(43072, 43136), + "Saurashtra": range(43136, 43232), + "Devanagari Extended": range(43232, 43264), + "Kayah Li": range(43264, 43312), + "Rejang": range(43312, 43360), + "Hangul Jamo Extended-A": range(43360, 43392), + "Javanese": range(43392, 43488), + "Myanmar Extended-B": range(43488, 43520), + "Cham": range(43520, 43616), + "Myanmar Extended-A": range(43616, 43648), + "Tai Viet": range(43648, 43744), + "Meetei Mayek Extensions": range(43744, 43776), + "Ethiopic Extended-A": range(43776, 43824), + "Latin Extended-E": range(43824, 43888), + "Cherokee Supplement": range(43888, 43968), + "Meetei Mayek": range(43968, 44032), + "Hangul Syllables": range(44032, 55216), + "Hangul Jamo Extended-B": range(55216, 55296), + "High Surrogates": range(55296, 56192), + "High Private Use Surrogates": range(56192, 56320), + "Low Surrogates": range(56320, 57344), + "Private Use Area": range(57344, 63744), + "CJK Compatibility Ideographs": range(63744, 64256), + "Alphabetic Presentation Forms": range(64256, 64336), + "Arabic Presentation Forms-A": range(64336, 65024), + "Variation Selectors": range(65024, 65040), + "Vertical Forms": range(65040, 65056), + "Combining Half Marks": range(65056, 65072), + "CJK Compatibility Forms": range(65072, 65104), + "Small Form Variants": range(65104, 65136), + "Arabic Presentation Forms-B": range(65136, 65280), + "Halfwidth and Fullwidth Forms": range(65280, 65520), + "Specials": range(65520, 65536), + "Linear B Syllabary": range(65536, 65664), + "Linear B Ideograms": range(65664, 65792), + "Aegean Numbers": range(65792, 65856), + "Ancient Greek Numbers": range(65856, 65936), + "Ancient Symbols": range(65936, 66000), + "Phaistos Disc": range(66000, 66048), + "Lycian": range(66176, 66208), + "Carian": range(66208, 66272), + "Coptic Epact Numbers": range(66272, 66304), + "Old Italic": range(66304, 66352), + "Gothic": range(66352, 66384), + "Old Permic": range(66384, 66432), + "Ugaritic": range(66432, 66464), + "Old Persian": range(66464, 66528), + "Deseret": range(66560, 66640), + "Shavian": range(66640, 66688), + "Osmanya": range(66688, 66736), + "Osage": range(66736, 66816), + "Elbasan": range(66816, 66864), + "Caucasian Albanian": range(66864, 66928), + "Vithkuqi": range(66928, 67008), + "Linear A": range(67072, 67456), + "Latin Extended-F": range(67456, 67520), + "Cypriot Syllabary": range(67584, 67648), + "Imperial Aramaic": range(67648, 67680), + "Palmyrene": range(67680, 67712), + "Nabataean": range(67712, 67760), + "Hatran": range(67808, 67840), + "Phoenician": range(67840, 67872), + "Lydian": range(67872, 67904), + "Meroitic Hieroglyphs": range(67968, 68000), + "Meroitic Cursive": range(68000, 68096), + "Kharoshthi": range(68096, 68192), + "Old South Arabian": range(68192, 68224), + "Old North Arabian": range(68224, 68256), + "Manichaean": range(68288, 68352), + "Avestan": range(68352, 68416), + "Inscriptional Parthian": range(68416, 68448), + "Inscriptional Pahlavi": range(68448, 68480), + "Psalter Pahlavi": range(68480, 68528), + "Old Turkic": range(68608, 68688), + "Old Hungarian": range(68736, 68864), + "Hanifi Rohingya": range(68864, 68928), + "Rumi Numeral Symbols": range(69216, 69248), + "Yezidi": range(69248, 69312), + "Arabic Extended-C": range(69312, 69376), + "Old Sogdian": range(69376, 69424), + "Sogdian": range(69424, 69488), + "Old Uyghur": range(69488, 69552), + "Chorasmian": range(69552, 69600), + "Elymaic": range(69600, 69632), + "Brahmi": range(69632, 69760), + "Kaithi": range(69760, 69840), + "Sora Sompeng": range(69840, 69888), + "Chakma": range(69888, 69968), + "Mahajani": range(69968, 70016), + "Sharada": range(70016, 70112), + "Sinhala Archaic Numbers": range(70112, 70144), + "Khojki": range(70144, 70224), + "Multani": range(70272, 70320), + "Khudawadi": range(70320, 70400), + "Grantha": range(70400, 70528), + "Newa": range(70656, 70784), + "Tirhuta": range(70784, 70880), + "Siddham": range(71040, 71168), + "Modi": range(71168, 71264), + "Mongolian Supplement": range(71264, 71296), + "Takri": range(71296, 71376), + "Ahom": range(71424, 71504), + "Dogra": range(71680, 71760), + "Warang Citi": range(71840, 71936), + "Dives Akuru": range(71936, 72032), + "Nandinagari": range(72096, 72192), + "Zanabazar Square": range(72192, 72272), + "Soyombo": range(72272, 72368), + "Unified Canadian Aboriginal Syllabics Extended-A": range(72368, 72384), + "Pau Cin Hau": range(72384, 72448), + "Devanagari Extended-A": range(72448, 72544), + "Bhaiksuki": range(72704, 72816), + "Marchen": range(72816, 72896), + "Masaram Gondi": range(72960, 73056), + "Gunjala Gondi": range(73056, 73136), + "Makasar": range(73440, 73472), + "Kawi": range(73472, 73568), + "Lisu Supplement": range(73648, 73664), + "Tamil Supplement": range(73664, 73728), + "Cuneiform": range(73728, 74752), + "Cuneiform Numbers and Punctuation": range(74752, 74880), + "Early Dynastic Cuneiform": range(74880, 75088), + "Cypro-Minoan": range(77712, 77824), + "Egyptian Hieroglyphs": range(77824, 78896), + "Egyptian Hieroglyph Format Controls": range(78896, 78944), + "Anatolian Hieroglyphs": range(82944, 83584), + "Bamum Supplement": range(92160, 92736), + "Mro": range(92736, 92784), + "Tangsa": range(92784, 92880), + "Bassa Vah": range(92880, 92928), + "Pahawh Hmong": range(92928, 93072), + "Medefaidrin": range(93760, 93856), + "Miao": range(93952, 94112), + "Ideographic Symbols and Punctuation": range(94176, 94208), + "Tangut": range(94208, 100352), + "Tangut Components": range(100352, 101120), + "Khitan Small Script": range(101120, 101632), + "Tangut Supplement": range(101632, 101760), + "Kana Extended-B": range(110576, 110592), + "Kana Supplement": range(110592, 110848), + "Kana Extended-A": range(110848, 110896), + "Small Kana Extension": range(110896, 110960), + "Nushu": range(110960, 111360), + "Duployan": range(113664, 113824), + "Shorthand Format Controls": range(113824, 113840), + "Znamenny Musical Notation": range(118528, 118736), + "Byzantine Musical Symbols": range(118784, 119040), + "Musical Symbols": range(119040, 119296), + "Ancient Greek Musical Notation": range(119296, 119376), + "Kaktovik Numerals": range(119488, 119520), + "Mayan Numerals": range(119520, 119552), + "Tai Xuan Jing Symbols": range(119552, 119648), + "Counting Rod Numerals": range(119648, 119680), + "Mathematical Alphanumeric Symbols": range(119808, 120832), + "Sutton SignWriting": range(120832, 121520), + "Latin Extended-G": range(122624, 122880), + "Glagolitic Supplement": range(122880, 122928), + "Cyrillic Extended-D": range(122928, 123024), + "Nyiakeng Puachue Hmong": range(123136, 123216), + "Toto": range(123536, 123584), + "Wancho": range(123584, 123648), + "Nag Mundari": range(124112, 124160), + "Ethiopic Extended-B": range(124896, 124928), + "Mende Kikakui": range(124928, 125152), + "Adlam": range(125184, 125280), + "Indic Siyaq Numbers": range(126064, 126144), + "Ottoman Siyaq Numbers": range(126208, 126288), + "Arabic Mathematical Alphabetic Symbols": range(126464, 126720), + "Mahjong Tiles": range(126976, 127024), + "Domino Tiles": range(127024, 127136), + "Playing Cards": range(127136, 127232), + "Enclosed Alphanumeric Supplement": range(127232, 127488), + "Enclosed Ideographic Supplement": range(127488, 127744), + "Miscellaneous Symbols and Pictographs": range(127744, 128512), + "Emoticons range(Emoji)": range(128512, 128592), + "Ornamental Dingbats": range(128592, 128640), + "Transport and Map Symbols": range(128640, 128768), + "Alchemical Symbols": range(128768, 128896), + "Geometric Shapes Extended": range(128896, 129024), + "Supplemental Arrows-C": range(129024, 129280), + "Supplemental Symbols and Pictographs": range(129280, 129536), + "Chess Symbols": range(129536, 129648), + "Symbols and Pictographs Extended-A": range(129648, 129792), + "Symbols for Legacy Computing": range(129792, 130048), + "CJK Unified Ideographs Extension B": range(131072, 173792), + "CJK Unified Ideographs Extension C": range(173824, 177984), + "CJK Unified Ideographs Extension D": range(177984, 178208), + "CJK Unified Ideographs Extension E": range(178208, 183984), + "CJK Unified Ideographs Extension F": range(183984, 191472), + "CJK Compatibility Ideographs Supplement": range(194560, 195104), + "CJK Unified Ideographs Extension G": range(196608, 201552), + "CJK Unified Ideographs Extension H": range(201552, 205744), + "Tags": range(917504, 917632), + "Variation Selectors Supplement": range(917760, 918000), + "Supplementary Private Use Area-A": range(983040, 1048576), + "Supplementary Private Use Area-B": range(1048576, 1114112), } @@ -331,11 +380,23 @@ RE_POSSIBLE_ENCODING_INDICATION = re_compile( IGNORECASE, ) +IANA_NO_ALIASES = [ + "cp720", + "cp737", + "cp856", + "cp874", + "cp875", + "cp1006", + "koi8_r", + "koi8_t", + "koi8_u", +] + IANA_SUPPORTED: List[str] = sorted( filter( lambda x: x.endswith("_codec") is False and x not in {"rot_13", "tactis", "mbcs"}, - list(set(aliases.values())), + list(set(aliases.values())) + IANA_NO_ALIASES, ) ) @@ -489,7 +550,1446 @@ COMMON_SAFE_ASCII_CHARACTERS: Set[str] = { KO_NAMES: Set[str] = {"johab", "cp949", "euc_kr"} ZH_NAMES: Set[str] = {"big5", "cp950", "big5hkscs", "hz"} -LANGUAGE_SUPPORTED_COUNT: int = len(FREQUENCIES) - # Logging LEVEL below DEBUG TRACE: int = 5 + + +# Language label that contain the em dash "—" +# character are to be considered alternative seq to origin +FREQUENCIES: Dict[str, List[str]] = { + "English": [ + "e", + "a", + "t", + "i", + "o", + "n", + "s", + "r", + "h", + "l", + "d", + "c", + "u", + "m", + "f", + "p", + "g", + "w", + "y", + "b", + "v", + "k", + "x", + "j", + "z", + "q", + ], + "English—": [ + "e", + "a", + "t", + "i", + "o", + "n", + "s", + "r", + "h", + "l", + "d", + "c", + "m", + "u", + "f", + "p", + "g", + "w", + "b", + "y", + "v", + "k", + "j", + "x", + "z", + "q", + ], + "German": [ + "e", + "n", + "i", + "r", + "s", + "t", + "a", + "d", + "h", + "u", + "l", + "g", + "o", + "c", + "m", + "b", + "f", + "k", + "w", + "z", + "p", + "v", + "ü", + "ä", + "ö", + "j", + ], + "French": [ + "e", + "a", + "s", + "n", + "i", + "t", + "r", + "l", + "u", + "o", + "d", + "c", + "p", + "m", + "é", + "v", + "g", + "f", + "b", + "h", + "q", + "à", + "x", + "è", + "y", + "j", + ], + "Dutch": [ + "e", + "n", + "a", + "i", + "r", + "t", + "o", + "d", + "s", + "l", + "g", + "h", + "v", + "m", + "u", + "k", + "c", + "p", + "b", + "w", + "j", + "z", + "f", + "y", + "x", + "ë", + ], + "Italian": [ + "e", + "i", + "a", + "o", + "n", + "l", + "t", + "r", + "s", + "c", + "d", + "u", + "p", + "m", + "g", + "v", + "f", + "b", + "z", + "h", + "q", + "è", + "à", + "k", + "y", + "ò", + ], + "Polish": [ + "a", + "i", + "o", + "e", + "n", + "r", + "z", + "w", + "s", + "c", + "t", + "k", + "y", + "d", + "p", + "m", + "u", + "l", + "j", + "ł", + "g", + "b", + "h", + "ą", + "ę", + "ó", + ], + "Spanish": [ + "e", + "a", + "o", + "n", + "s", + "r", + "i", + "l", + "d", + "t", + "c", + "u", + "m", + "p", + "b", + "g", + "v", + "f", + "y", + "ó", + "h", + "q", + "í", + "j", + "z", + "á", + ], + "Russian": [ + "о", + "а", + "е", + "и", + "н", + "с", + "т", + "р", + "в", + "л", + "к", + "м", + "д", + "п", + "у", + "г", + "я", + "ы", + "з", + "б", + "й", + "ь", + "ч", + "х", + "ж", + "ц", + ], + # Jap-Kanji + "Japanese": [ + "人", + "一", + "大", + "亅", + "丁", + "丨", + "竹", + "笑", + "口", + "日", + "今", + "二", + "彳", + "行", + "十", + "土", + "丶", + "寸", + "寺", + "時", + "乙", + "丿", + "乂", + "气", + "気", + "冂", + "巾", + "亠", + "市", + "目", + "儿", + "見", + "八", + "小", + "凵", + "県", + "月", + "彐", + "門", + "間", + "木", + "東", + "山", + "出", + "本", + "中", + "刀", + "分", + "耳", + "又", + "取", + "最", + "言", + "田", + "心", + "思", + "刂", + "前", + "京", + "尹", + "事", + "生", + "厶", + "云", + "会", + "未", + "来", + "白", + "冫", + "楽", + "灬", + "馬", + "尸", + "尺", + "駅", + "明", + "耂", + "者", + "了", + "阝", + "都", + "高", + "卜", + "占", + "厂", + "广", + "店", + "子", + "申", + "奄", + "亻", + "俺", + "上", + "方", + "冖", + "学", + "衣", + "艮", + "食", + "自", + ], + # Jap-Katakana + "Japanese—": [ + "ー", + "ン", + "ス", + "・", + "ル", + "ト", + "リ", + "イ", + "ア", + "ラ", + "ッ", + "ク", + "ド", + "シ", + "レ", + "ジ", + "タ", + "フ", + "ロ", + "カ", + "テ", + "マ", + "ィ", + "グ", + "バ", + "ム", + "プ", + "オ", + "コ", + "デ", + "ニ", + "ウ", + "メ", + "サ", + "ビ", + "ナ", + "ブ", + "ャ", + "エ", + "ュ", + "チ", + "キ", + "ズ", + "ダ", + "パ", + "ミ", + "ェ", + "ョ", + "ハ", + "セ", + "ベ", + "ガ", + "モ", + "ツ", + "ネ", + "ボ", + "ソ", + "ノ", + "ァ", + "ヴ", + "ワ", + "ポ", + "ペ", + "ピ", + "ケ", + "ゴ", + "ギ", + "ザ", + "ホ", + "ゲ", + "ォ", + "ヤ", + "ヒ", + "ユ", + "ヨ", + "ヘ", + "ゼ", + "ヌ", + "ゥ", + "ゾ", + "ヶ", + "ヂ", + "ヲ", + "ヅ", + "ヵ", + "ヱ", + "ヰ", + "ヮ", + "ヽ", + "゠", + "ヾ", + "ヷ", + "ヿ", + "ヸ", + "ヹ", + "ヺ", + ], + # Jap-Hiragana + "Japanese——": [ + "の", + "に", + "る", + "た", + "と", + "は", + "し", + "い", + "を", + "で", + "て", + "が", + "な", + "れ", + "か", + "ら", + "さ", + "っ", + "り", + "す", + "あ", + "も", + "こ", + "ま", + "う", + "く", + "よ", + "き", + "ん", + "め", + "お", + "け", + "そ", + "つ", + "だ", + "や", + "え", + "ど", + "わ", + "ち", + "み", + "せ", + "じ", + "ば", + "へ", + "び", + "ず", + "ろ", + "ほ", + "げ", + "む", + "べ", + "ひ", + "ょ", + "ゆ", + "ぶ", + "ご", + "ゃ", + "ね", + "ふ", + "ぐ", + "ぎ", + "ぼ", + "ゅ", + "づ", + "ざ", + "ぞ", + "ぬ", + "ぜ", + "ぱ", + "ぽ", + "ぷ", + "ぴ", + "ぃ", + "ぁ", + "ぇ", + "ぺ", + "ゞ", + "ぢ", + "ぉ", + "ぅ", + "ゐ", + "ゝ", + "ゑ", + "゛", + "゜", + "ゎ", + "ゔ", + "゚", + "ゟ", + "゙", + "ゕ", + "ゖ", + ], + "Portuguese": [ + "a", + "e", + "o", + "s", + "i", + "r", + "d", + "n", + "t", + "m", + "u", + "c", + "l", + "p", + "g", + "v", + "b", + "f", + "h", + "ã", + "q", + "é", + "ç", + "á", + "z", + "í", + ], + "Swedish": [ + "e", + "a", + "n", + "r", + "t", + "s", + "i", + "l", + "d", + "o", + "m", + "k", + "g", + "v", + "h", + "f", + "u", + "p", + "ä", + "c", + "b", + "ö", + "å", + "y", + "j", + "x", + ], + "Chinese": [ + "的", + "一", + "是", + "不", + "了", + "在", + "人", + "有", + "我", + "他", + "这", + "个", + "们", + "中", + "来", + "上", + "大", + "为", + "和", + "国", + "地", + "到", + "以", + "说", + "时", + "要", + "就", + "出", + "会", + "可", + "也", + "你", + "对", + "生", + "能", + "而", + "子", + "那", + "得", + "于", + "着", + "下", + "自", + "之", + "年", + "过", + "发", + "后", + "作", + "里", + "用", + "道", + "行", + "所", + "然", + "家", + "种", + "事", + "成", + "方", + "多", + "经", + "么", + "去", + "法", + "学", + "如", + "都", + "同", + "现", + "当", + "没", + "动", + "面", + "起", + "看", + "定", + "天", + "分", + "还", + "进", + "好", + "小", + "部", + "其", + "些", + "主", + "样", + "理", + "心", + "她", + "本", + "前", + "开", + "但", + "因", + "只", + "从", + "想", + "实", + ], + "Ukrainian": [ + "о", + "а", + "н", + "і", + "и", + "р", + "в", + "т", + "е", + "с", + "к", + "л", + "у", + "д", + "м", + "п", + "з", + "я", + "ь", + "б", + "г", + "й", + "ч", + "х", + "ц", + "ї", + ], + "Norwegian": [ + "e", + "r", + "n", + "t", + "a", + "s", + "i", + "o", + "l", + "d", + "g", + "k", + "m", + "v", + "f", + "p", + "u", + "b", + "h", + "å", + "y", + "j", + "ø", + "c", + "æ", + "w", + ], + "Finnish": [ + "a", + "i", + "n", + "t", + "e", + "s", + "l", + "o", + "u", + "k", + "ä", + "m", + "r", + "v", + "j", + "h", + "p", + "y", + "d", + "ö", + "g", + "c", + "b", + "f", + "w", + "z", + ], + "Vietnamese": [ + "n", + "h", + "t", + "i", + "c", + "g", + "a", + "o", + "u", + "m", + "l", + "r", + "à", + "đ", + "s", + "e", + "v", + "p", + "b", + "y", + "ư", + "d", + "á", + "k", + "ộ", + "ế", + ], + "Czech": [ + "o", + "e", + "a", + "n", + "t", + "s", + "i", + "l", + "v", + "r", + "k", + "d", + "u", + "m", + "p", + "í", + "c", + "h", + "z", + "á", + "y", + "j", + "b", + "ě", + "é", + "ř", + ], + "Hungarian": [ + "e", + "a", + "t", + "l", + "s", + "n", + "k", + "r", + "i", + "o", + "z", + "á", + "é", + "g", + "m", + "b", + "y", + "v", + "d", + "h", + "u", + "p", + "j", + "ö", + "f", + "c", + ], + "Korean": [ + "이", + "다", + "에", + "의", + "는", + "로", + "하", + "을", + "가", + "고", + "지", + "서", + "한", + "은", + "기", + "으", + "년", + "대", + "사", + "시", + "를", + "리", + "도", + "인", + "스", + "일", + ], + "Indonesian": [ + "a", + "n", + "e", + "i", + "r", + "t", + "u", + "s", + "d", + "k", + "m", + "l", + "g", + "p", + "b", + "o", + "h", + "y", + "j", + "c", + "w", + "f", + "v", + "z", + "x", + "q", + ], + "Turkish": [ + "a", + "e", + "i", + "n", + "r", + "l", + "ı", + "k", + "d", + "t", + "s", + "m", + "y", + "u", + "o", + "b", + "ü", + "ş", + "v", + "g", + "z", + "h", + "c", + "p", + "ç", + "ğ", + ], + "Romanian": [ + "e", + "i", + "a", + "r", + "n", + "t", + "u", + "l", + "o", + "c", + "s", + "d", + "p", + "m", + "ă", + "f", + "v", + "î", + "g", + "b", + "ș", + "ț", + "z", + "h", + "â", + "j", + ], + "Farsi": [ + "ا", + "ی", + "ر", + "د", + "ن", + "ه", + "و", + "م", + "ت", + "ب", + "س", + "ل", + "ک", + "ش", + "ز", + "ف", + "گ", + "ع", + "خ", + "ق", + "ج", + "آ", + "پ", + "ح", + "ط", + "ص", + ], + "Arabic": [ + "ا", + "ل", + "ي", + "م", + "و", + "ن", + "ر", + "ت", + "ب", + "ة", + "ع", + "د", + "س", + "ف", + "ه", + "ك", + "ق", + "أ", + "ح", + "ج", + "ش", + "ط", + "ص", + "ى", + "خ", + "إ", + ], + "Danish": [ + "e", + "r", + "n", + "t", + "a", + "i", + "s", + "d", + "l", + "o", + "g", + "m", + "k", + "f", + "v", + "u", + "b", + "h", + "p", + "å", + "y", + "ø", + "æ", + "c", + "j", + "w", + ], + "Serbian": [ + "а", + "и", + "о", + "е", + "н", + "р", + "с", + "у", + "т", + "к", + "ј", + "в", + "д", + "м", + "п", + "л", + "г", + "з", + "б", + "a", + "i", + "e", + "o", + "n", + "ц", + "ш", + ], + "Lithuanian": [ + "i", + "a", + "s", + "o", + "r", + "e", + "t", + "n", + "u", + "k", + "m", + "l", + "p", + "v", + "d", + "j", + "g", + "ė", + "b", + "y", + "ų", + "š", + "ž", + "c", + "ą", + "į", + ], + "Slovene": [ + "e", + "a", + "i", + "o", + "n", + "r", + "s", + "l", + "t", + "j", + "v", + "k", + "d", + "p", + "m", + "u", + "z", + "b", + "g", + "h", + "č", + "c", + "š", + "ž", + "f", + "y", + ], + "Slovak": [ + "o", + "a", + "e", + "n", + "i", + "r", + "v", + "t", + "s", + "l", + "k", + "d", + "m", + "p", + "u", + "c", + "h", + "j", + "b", + "z", + "á", + "y", + "ý", + "í", + "č", + "é", + ], + "Hebrew": [ + "י", + "ו", + "ה", + "ל", + "ר", + "ב", + "ת", + "מ", + "א", + "ש", + "נ", + "ע", + "ם", + "ד", + "ק", + "ח", + "פ", + "ס", + "כ", + "ג", + "ט", + "צ", + "ן", + "ז", + "ך", + ], + "Bulgarian": [ + "а", + "и", + "о", + "е", + "н", + "т", + "р", + "с", + "в", + "л", + "к", + "д", + "п", + "м", + "з", + "г", + "я", + "ъ", + "у", + "б", + "ч", + "ц", + "й", + "ж", + "щ", + "х", + ], + "Croatian": [ + "a", + "i", + "o", + "e", + "n", + "r", + "j", + "s", + "t", + "u", + "k", + "l", + "v", + "d", + "m", + "p", + "g", + "z", + "b", + "c", + "č", + "h", + "š", + "ž", + "ć", + "f", + ], + "Hindi": [ + "क", + "र", + "स", + "न", + "त", + "म", + "ह", + "प", + "य", + "ल", + "व", + "ज", + "द", + "ग", + "ब", + "श", + "ट", + "अ", + "ए", + "थ", + "भ", + "ड", + "च", + "ध", + "ष", + "इ", + ], + "Estonian": [ + "a", + "i", + "e", + "s", + "t", + "l", + "u", + "n", + "o", + "k", + "r", + "d", + "m", + "v", + "g", + "p", + "j", + "h", + "ä", + "b", + "õ", + "ü", + "f", + "c", + "ö", + "y", + ], + "Thai": [ + "า", + "น", + "ร", + "อ", + "ก", + "เ", + "ง", + "ม", + "ย", + "ล", + "ว", + "ด", + "ท", + "ส", + "ต", + "ะ", + "ป", + "บ", + "ค", + "ห", + "แ", + "จ", + "พ", + "ช", + "ข", + "ใ", + ], + "Greek": [ + "α", + "τ", + "ο", + "ι", + "ε", + "ν", + "ρ", + "σ", + "κ", + "η", + "π", + "ς", + "υ", + "μ", + "λ", + "ί", + "ό", + "ά", + "γ", + "έ", + "δ", + "ή", + "ω", + "χ", + "θ", + "ύ", + ], + "Tamil": [ + "க", + "த", + "ப", + "ட", + "ர", + "ம", + "ல", + "ன", + "வ", + "ற", + "ய", + "ள", + "ச", + "ந", + "இ", + "ண", + "அ", + "ஆ", + "ழ", + "ங", + "எ", + "உ", + "ஒ", + "ஸ", + ], + "Kazakh": [ + "а", + "ы", + "е", + "н", + "т", + "р", + "л", + "і", + "д", + "с", + "м", + "қ", + "к", + "о", + "б", + "и", + "у", + "ғ", + "ж", + "ң", + "з", + "ш", + "й", + "п", + "г", + "ө", + ], +} + +LANGUAGE_SUPPORTED_COUNT: int = len(FREQUENCIES) diff --git a/lib/charset_normalizer/md.py b/lib/charset_normalizer/md.py index 13aa062e..77897aae 100644 --- a/lib/charset_normalizer/md.py +++ b/lib/charset_normalizer/md.py @@ -9,7 +9,8 @@ from .constant import ( ) from .utils import ( is_accentuated, - is_ascii, + is_arabic, + is_arabic_isolated_form, is_case_variable, is_cjk, is_emoticon, @@ -128,8 +129,9 @@ class TooManyAccentuatedPlugin(MessDetectorPlugin): @property def ratio(self) -> float: - if self._character_count == 0 or self._character_count < 8: + if self._character_count < 8: return 0.0 + ratio_of_accentuation: float = self._accentuated_count / self._character_count return ratio_of_accentuation if ratio_of_accentuation >= 0.35 else 0.0 @@ -234,16 +236,13 @@ class SuspiciousRange(MessDetectorPlugin): @property def ratio(self) -> float: - if self._character_count == 0: + if self._character_count <= 24: return 0.0 ratio_of_suspicious_range_usage: float = ( self._suspicious_successive_range_count * 2 ) / self._character_count - if ratio_of_suspicious_range_usage < 0.1: - return 0.0 - return ratio_of_suspicious_range_usage @@ -296,7 +295,11 @@ class SuperWeirdWordPlugin(MessDetectorPlugin): self._is_current_word_bad = True # Word/Buffer ending with an upper case accentuated letter are so rare, # that we will consider them all as suspicious. Same weight as foreign_long suspicious. - if is_accentuated(self._buffer[-1]) and self._buffer[-1].isupper(): + if ( + is_accentuated(self._buffer[-1]) + and self._buffer[-1].isupper() + and all(_.isupper() for _ in self._buffer) is False + ): self._foreign_long_count += 1 self._is_current_word_bad = True if buffer_length >= 24 and self._foreign_long_watch: @@ -419,7 +422,7 @@ class ArchaicUpperLowerPlugin(MessDetectorPlugin): return - if self._current_ascii_only is True and is_ascii(character) is False: + if self._current_ascii_only is True and character.isascii() is False: self._current_ascii_only = False if self._last_alpha_seen is not None: @@ -455,6 +458,34 @@ class ArchaicUpperLowerPlugin(MessDetectorPlugin): return self._successive_upper_lower_count_final / self._character_count +class ArabicIsolatedFormPlugin(MessDetectorPlugin): + def __init__(self) -> None: + self._character_count: int = 0 + self._isolated_form_count: int = 0 + + def reset(self) -> None: # pragma: no cover + self._character_count = 0 + self._isolated_form_count = 0 + + def eligible(self, character: str) -> bool: + return is_arabic(character) + + def feed(self, character: str) -> None: + self._character_count += 1 + + if is_arabic_isolated_form(character): + self._isolated_form_count += 1 + + @property + def ratio(self) -> float: + if self._character_count < 8: + return 0.0 + + isolated_form_usage: float = self._isolated_form_count / self._character_count + + return isolated_form_usage + + @lru_cache(maxsize=1024) def is_suspiciously_successive_range( unicode_range_a: Optional[str], unicode_range_b: Optional[str] @@ -522,6 +553,8 @@ def is_suspiciously_successive_range( return False if "Forms" in unicode_range_a or "Forms" in unicode_range_b: return False + if unicode_range_a == "Basic Latin" or unicode_range_b == "Basic Latin": + return False return True diff --git a/lib/charset_normalizer/models.py b/lib/charset_normalizer/models.py index 7f8ca389..a760b9c5 100644 --- a/lib/charset_normalizer/models.py +++ b/lib/charset_normalizer/models.py @@ -54,16 +54,19 @@ class CharsetMatch: # Below 1% difference --> Use Coherence if chaos_difference < 0.01 and coherence_difference > 0.02: - # When having a tough decision, use the result that decoded as many multi-byte as possible. - if chaos_difference == 0.0 and self.coherence == other.coherence: - return self.multi_byte_usage > other.multi_byte_usage return self.coherence > other.coherence + elif chaos_difference < 0.01 and coherence_difference <= 0.02: + # When having a difficult decision, use the result that decoded as many multi-byte as possible. + # preserve RAM usage! + if len(self._payload) >= TOO_BIG_SEQUENCE: + return self.chaos < other.chaos + return self.multi_byte_usage > other.multi_byte_usage return self.chaos < other.chaos @property def multi_byte_usage(self) -> float: - return 1.0 - len(str(self)) / len(self.raw) + return 1.0 - (len(str(self)) / len(self.raw)) def __str__(self) -> str: # Lazy Str Loading diff --git a/lib/charset_normalizer/utils.py b/lib/charset_normalizer/utils.py index bf2767a0..e5cbbf4c 100644 --- a/lib/charset_normalizer/utils.py +++ b/lib/charset_normalizer/utils.py @@ -32,6 +32,8 @@ def is_accentuated(character: str) -> bool: or "WITH DIAERESIS" in description or "WITH CIRCUMFLEX" in description or "WITH TILDE" in description + or "WITH MACRON" in description + or "WITH RING ABOVE" in description ) @@ -69,15 +71,6 @@ def is_latin(character: str) -> bool: return "LATIN" in description -@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) -def is_ascii(character: str) -> bool: - try: - character.encode("ascii") - except UnicodeEncodeError: - return False - return True - - @lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) def is_punctuation(character: str) -> bool: character_category: str = unicodedata.category(character) @@ -105,7 +98,7 @@ def is_symbol(character: str) -> bool: if character_range is None: return False - return "Forms" in character_range + return "Forms" in character_range and character_category != "Lo" @lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) @@ -115,7 +108,7 @@ def is_emoticon(character: str) -> bool: if character_range is None: return False - return "Emoticons" in character_range + return "Emoticons" in character_range or "Pictographs" in character_range @lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) @@ -133,12 +126,6 @@ def is_case_variable(character: str) -> bool: return character.islower() != character.isupper() -def is_private_use_only(character: str) -> bool: - character_category: str = unicodedata.category(character) - - return character_category == "Co" - - @lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) def is_cjk(character: str) -> bool: try: @@ -189,6 +176,26 @@ def is_thai(character: str) -> bool: return "THAI" in character_name +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_arabic(character: str) -> bool: + try: + character_name = unicodedata.name(character) + except ValueError: + return False + + return "ARABIC" in character_name + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_arabic_isolated_form(character: str) -> bool: + try: + character_name = unicodedata.name(character) + except ValueError: + return False + + return "ARABIC" in character_name and "ISOLATED FORM" in character_name + + @lru_cache(maxsize=len(UNICODE_RANGES_COMBINED)) def is_unicode_range_secondary(range_name: str) -> bool: return any(keyword in range_name for keyword in UNICODE_SECONDARY_RANGE_KEYWORD) @@ -205,7 +212,7 @@ def is_unprintable(character: str) -> bool: ) -def any_specified_encoding(sequence: bytes, search_zone: int = 4096) -> Optional[str]: +def any_specified_encoding(sequence: bytes, search_zone: int = 8192) -> Optional[str]: """ Extract using ASCII-only decoder any specified encoding in the first n-bytes. """ diff --git a/lib/charset_normalizer/version.py b/lib/charset_normalizer/version.py index 5eed49a4..5a4da4ff 100644 --- a/lib/charset_normalizer/version.py +++ b/lib/charset_normalizer/version.py @@ -2,5 +2,5 @@ Expose version """ -__version__ = "3.2.0" +__version__ = "3.3.2" VERSION = __version__.split(".") diff --git a/lib/idna/codec.py b/lib/idna/codec.py index 1ca9ba62..c855a4de 100644 --- a/lib/idna/codec.py +++ b/lib/idna/codec.py @@ -1,7 +1,7 @@ from .core import encode, decode, alabel, ulabel, IDNAError import codecs import re -from typing import Tuple, Optional +from typing import Any, Tuple, Optional _unicode_dots_re = re.compile('[\u002e\u3002\uff0e\uff61]') @@ -26,24 +26,24 @@ class Codec(codecs.Codec): return decode(data), len(data) class IncrementalEncoder(codecs.BufferedIncrementalEncoder): - def _buffer_encode(self, data: str, errors: str, final: bool) -> Tuple[str, int]: # type: ignore + def _buffer_encode(self, data: str, errors: str, final: bool) -> Tuple[bytes, int]: if errors != 'strict': raise IDNAError('Unsupported error handling \"{}\"'.format(errors)) if not data: - return "", 0 + return b'', 0 labels = _unicode_dots_re.split(data) - trailing_dot = '' + trailing_dot = b'' if labels: if not labels[-1]: - trailing_dot = '.' + trailing_dot = b'.' del labels[-1] elif not final: # Keep potentially unfinished label until the next call del labels[-1] if labels: - trailing_dot = '.' + trailing_dot = b'.' result = [] size = 0 @@ -54,18 +54,21 @@ class IncrementalEncoder(codecs.BufferedIncrementalEncoder): size += len(label) # Join with U+002E - result_str = '.'.join(result) + trailing_dot # type: ignore + result_bytes = b'.'.join(result) + trailing_dot size += len(trailing_dot) - return result_str, size + return result_bytes, size class IncrementalDecoder(codecs.BufferedIncrementalDecoder): - def _buffer_decode(self, data: str, errors: str, final: bool) -> Tuple[str, int]: # type: ignore + def _buffer_decode(self, data: Any, errors: str, final: bool) -> Tuple[str, int]: if errors != 'strict': raise IDNAError('Unsupported error handling \"{}\"'.format(errors)) if not data: return ('', 0) + if not isinstance(data, str): + data = str(data, 'ascii') + labels = _unicode_dots_re.split(data) trailing_dot = '' if labels: @@ -99,14 +102,17 @@ class StreamReader(Codec, codecs.StreamReader): pass -def getregentry() -> codecs.CodecInfo: - # Compatibility as a search_function for codecs.register() +def search_function(name: str) -> Optional[codecs.CodecInfo]: + if name != 'idna2008': + return None return codecs.CodecInfo( - name='idna', - encode=Codec().encode, # type: ignore - decode=Codec().decode, # type: ignore + name=name, + encode=Codec().encode, + decode=Codec().decode, incrementalencoder=IncrementalEncoder, incrementaldecoder=IncrementalDecoder, streamwriter=StreamWriter, streamreader=StreamReader, ) + +codecs.register(search_function) diff --git a/lib/idna/core.py b/lib/idna/core.py index 4f300371..aaf7d658 100644 --- a/lib/idna/core.py +++ b/lib/idna/core.py @@ -318,7 +318,7 @@ def uts46_remap(domain: str, std3_rules: bool = True, transitional: bool = False status = uts46row[1] replacement = None # type: Optional[str] if len(uts46row) == 3: - replacement = uts46row[2] # type: ignore + replacement = uts46row[2] if (status == 'V' or (status == 'D' and not transitional) or (status == '3' and not std3_rules and replacement is None)): @@ -338,9 +338,9 @@ def uts46_remap(domain: str, std3_rules: bool = True, transitional: bool = False def encode(s: Union[str, bytes, bytearray], strict: bool = False, uts46: bool = False, std3_rules: bool = False, transitional: bool = False) -> bytes: - if isinstance(s, (bytes, bytearray)): + if not isinstance(s, str): try: - s = s.decode('ascii') + s = str(s, 'ascii') except UnicodeDecodeError: raise IDNAError('should pass a unicode string to the function rather than a byte string.') if uts46: @@ -372,8 +372,8 @@ def encode(s: Union[str, bytes, bytearray], strict: bool = False, uts46: bool = def decode(s: Union[str, bytes, bytearray], strict: bool = False, uts46: bool = False, std3_rules: bool = False) -> str: try: - if isinstance(s, (bytes, bytearray)): - s = s.decode('ascii') + if not isinstance(s, str): + s = str(s, 'ascii') except UnicodeDecodeError: raise IDNAError('Invalid ASCII in A-label') if uts46: diff --git a/lib/idna/idnadata.py b/lib/idna/idnadata.py index 67db4625..5cd05d90 100644 --- a/lib/idna/idnadata.py +++ b/lib/idna/idnadata.py @@ -1,6 +1,6 @@ # This file is automatically generated by tools/idna-data -__version__ = '15.0.0' +__version__ = '15.1.0' scripts = { 'Greek': ( 0x37000000374, @@ -59,6 +59,7 @@ scripts = { 0x2b7400002b81e, 0x2b8200002cea2, 0x2ceb00002ebe1, + 0x2ebf00002ee5e, 0x2f8000002fa1e, 0x300000003134b, 0x31350000323b0, @@ -1834,7 +1835,6 @@ codepoint_classes = { 0xa7d50000a7d6, 0xa7d70000a7d8, 0xa7d90000a7da, - 0xa7f20000a7f5, 0xa7f60000a7f8, 0xa7fa0000a828, 0xa82c0000a82d, @@ -1907,9 +1907,7 @@ codepoint_classes = { 0x1060000010737, 0x1074000010756, 0x1076000010768, - 0x1078000010786, - 0x10787000107b1, - 0x107b2000107bb, + 0x1078000010781, 0x1080000010806, 0x1080800010809, 0x1080a00010836, @@ -2134,6 +2132,7 @@ codepoint_classes = { 0x2b7400002b81e, 0x2b8200002cea2, 0x2ceb00002ebe1, + 0x2ebf00002ee5e, 0x300000003134b, 0x31350000323b0, ), diff --git a/lib/idna/package_data.py b/lib/idna/package_data.py index 8501893b..c5b7220c 100644 --- a/lib/idna/package_data.py +++ b/lib/idna/package_data.py @@ -1,2 +1,2 @@ -__version__ = '3.4' +__version__ = '3.6' diff --git a/lib/idna/uts46data.py b/lib/idna/uts46data.py index 186796c1..6a1eddbf 100644 --- a/lib/idna/uts46data.py +++ b/lib/idna/uts46data.py @@ -7,7 +7,7 @@ from typing import List, Tuple, Union """IDNA Mapping Table from UTS46.""" -__version__ = '15.0.0' +__version__ = '15.1.0' def _seg_0() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: return [ (0x0, '3'), @@ -1899,7 +1899,7 @@ def _seg_18() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x1E9A, 'M', 'aʾ'), (0x1E9B, 'M', 'ṡ'), (0x1E9C, 'V'), - (0x1E9E, 'M', 'ss'), + (0x1E9E, 'M', 'ß'), (0x1E9F, 'V'), (0x1EA0, 'M', 'ạ'), (0x1EA1, 'V'), @@ -2418,10 +2418,6 @@ def _seg_23() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x222F, 'M', '∮∮'), (0x2230, 'M', '∮∮∮'), (0x2231, 'V'), - (0x2260, '3'), - (0x2261, 'V'), - (0x226E, '3'), - (0x2270, 'V'), (0x2329, 'M', '〈'), (0x232A, 'M', '〉'), (0x232B, 'V'), @@ -2502,14 +2498,14 @@ def _seg_23() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x24BA, 'M', 'e'), (0x24BB, 'M', 'f'), (0x24BC, 'M', 'g'), - ] - -def _seg_24() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x24BD, 'M', 'h'), (0x24BE, 'M', 'i'), (0x24BF, 'M', 'j'), (0x24C0, 'M', 'k'), + ] + +def _seg_24() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x24C1, 'M', 'l'), (0x24C2, 'M', 'm'), (0x24C3, 'M', 'n'), @@ -2606,14 +2602,14 @@ def _seg_24() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x2C26, 'M', 'ⱖ'), (0x2C27, 'M', 'ⱗ'), (0x2C28, 'M', 'ⱘ'), - ] - -def _seg_25() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x2C29, 'M', 'ⱙ'), (0x2C2A, 'M', 'ⱚ'), (0x2C2B, 'M', 'ⱛ'), (0x2C2C, 'M', 'ⱜ'), + ] + +def _seg_25() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x2C2D, 'M', 'ⱝ'), (0x2C2E, 'M', 'ⱞ'), (0x2C2F, 'M', 'ⱟ'), @@ -2710,14 +2706,14 @@ def _seg_25() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x2CC0, 'M', 'ⳁ'), (0x2CC1, 'V'), (0x2CC2, 'M', 'ⳃ'), - ] - -def _seg_26() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x2CC3, 'V'), (0x2CC4, 'M', 'ⳅ'), (0x2CC5, 'V'), (0x2CC6, 'M', 'ⳇ'), + ] + +def _seg_26() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x2CC7, 'V'), (0x2CC8, 'M', 'ⳉ'), (0x2CC9, 'V'), @@ -2814,14 +2810,14 @@ def _seg_26() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x2F13, 'M', '勹'), (0x2F14, 'M', '匕'), (0x2F15, 'M', '匚'), - ] - -def _seg_27() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x2F16, 'M', '匸'), (0x2F17, 'M', '十'), (0x2F18, 'M', '卜'), (0x2F19, 'M', '卩'), + ] + +def _seg_27() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x2F1A, 'M', '厂'), (0x2F1B, 'M', '厶'), (0x2F1C, 'M', '又'), @@ -2918,14 +2914,14 @@ def _seg_27() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x2F77, 'M', '糸'), (0x2F78, 'M', '缶'), (0x2F79, 'M', '网'), - ] - -def _seg_28() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x2F7A, 'M', '羊'), (0x2F7B, 'M', '羽'), (0x2F7C, 'M', '老'), (0x2F7D, 'M', '而'), + ] + +def _seg_28() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x2F7E, 'M', '耒'), (0x2F7F, 'M', '耳'), (0x2F80, 'M', '聿'), @@ -3022,14 +3018,14 @@ def _seg_28() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x3036, 'M', '〒'), (0x3037, 'V'), (0x3038, 'M', '十'), - ] - -def _seg_29() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x3039, 'M', '卄'), (0x303A, 'M', '卅'), (0x303B, 'V'), (0x3040, 'X'), + ] + +def _seg_29() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x3041, 'V'), (0x3097, 'X'), (0x3099, 'V'), @@ -3126,14 +3122,14 @@ def _seg_29() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x3182, 'M', 'ᇱ'), (0x3183, 'M', 'ᇲ'), (0x3184, 'M', 'ᅗ'), - ] - -def _seg_30() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x3185, 'M', 'ᅘ'), (0x3186, 'M', 'ᅙ'), (0x3187, 'M', 'ᆄ'), (0x3188, 'M', 'ᆅ'), + ] + +def _seg_30() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x3189, 'M', 'ᆈ'), (0x318A, 'M', 'ᆑ'), (0x318B, 'M', 'ᆒ'), @@ -3230,14 +3226,14 @@ def _seg_30() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x3244, 'M', '問'), (0x3245, 'M', '幼'), (0x3246, 'M', '文'), - ] - -def _seg_31() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x3247, 'M', '箏'), (0x3248, 'V'), (0x3250, 'M', 'pte'), (0x3251, 'M', '21'), + ] + +def _seg_31() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x3252, 'M', '22'), (0x3253, 'M', '23'), (0x3254, 'M', '24'), @@ -3334,14 +3330,14 @@ def _seg_31() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x32AF, 'M', '協'), (0x32B0, 'M', '夜'), (0x32B1, 'M', '36'), - ] - -def _seg_32() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x32B2, 'M', '37'), (0x32B3, 'M', '38'), (0x32B4, 'M', '39'), (0x32B5, 'M', '40'), + ] + +def _seg_32() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x32B6, 'M', '41'), (0x32B7, 'M', '42'), (0x32B8, 'M', '43'), @@ -3438,14 +3434,14 @@ def _seg_32() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x3313, 'M', 'ギルダー'), (0x3314, 'M', 'キロ'), (0x3315, 'M', 'キログラム'), - ] - -def _seg_33() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x3316, 'M', 'キロメートル'), (0x3317, 'M', 'キロワット'), (0x3318, 'M', 'グラム'), (0x3319, 'M', 'グラムトン'), + ] + +def _seg_33() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x331A, 'M', 'クルゼイロ'), (0x331B, 'M', 'クローネ'), (0x331C, 'M', 'ケース'), @@ -3542,14 +3538,14 @@ def _seg_33() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x3377, 'M', 'dm'), (0x3378, 'M', 'dm2'), (0x3379, 'M', 'dm3'), - ] - -def _seg_34() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x337A, 'M', 'iu'), (0x337B, 'M', '平成'), (0x337C, 'M', '昭和'), (0x337D, 'M', '大正'), + ] + +def _seg_34() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x337E, 'M', '明治'), (0x337F, 'M', '株式会社'), (0x3380, 'M', 'pa'), @@ -3646,14 +3642,14 @@ def _seg_34() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x33DB, 'M', 'sr'), (0x33DC, 'M', 'sv'), (0x33DD, 'M', 'wb'), - ] - -def _seg_35() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x33DE, 'M', 'v∕m'), (0x33DF, 'M', 'a∕m'), (0x33E0, 'M', '1日'), (0x33E1, 'M', '2日'), + ] + +def _seg_35() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x33E2, 'M', '3日'), (0x33E3, 'M', '4日'), (0x33E4, 'M', '5日'), @@ -3750,14 +3746,14 @@ def _seg_35() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0xA68B, 'V'), (0xA68C, 'M', 'ꚍ'), (0xA68D, 'V'), - ] - -def _seg_36() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0xA68E, 'M', 'ꚏ'), (0xA68F, 'V'), (0xA690, 'M', 'ꚑ'), (0xA691, 'V'), + ] + +def _seg_36() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0xA692, 'M', 'ꚓ'), (0xA693, 'V'), (0xA694, 'M', 'ꚕ'), @@ -3854,14 +3850,14 @@ def _seg_36() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0xA779, 'M', 'ꝺ'), (0xA77A, 'V'), (0xA77B, 'M', 'ꝼ'), - ] - -def _seg_37() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0xA77C, 'V'), (0xA77D, 'M', 'ᵹ'), (0xA77E, 'M', 'ꝿ'), (0xA77F, 'V'), + ] + +def _seg_37() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0xA780, 'M', 'ꞁ'), (0xA781, 'V'), (0xA782, 'M', 'ꞃ'), @@ -3958,14 +3954,14 @@ def _seg_37() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0xA878, 'X'), (0xA880, 'V'), (0xA8C6, 'X'), - ] - -def _seg_38() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0xA8CE, 'V'), (0xA8DA, 'X'), (0xA8E0, 'V'), (0xA954, 'X'), + ] + +def _seg_38() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0xA95F, 'V'), (0xA97D, 'X'), (0xA980, 'V'), @@ -4062,14 +4058,14 @@ def _seg_38() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0xABA8, 'M', 'Ꮨ'), (0xABA9, 'M', 'Ꮩ'), (0xABAA, 'M', 'Ꮪ'), - ] - -def _seg_39() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0xABAB, 'M', 'Ꮫ'), (0xABAC, 'M', 'Ꮬ'), (0xABAD, 'M', 'Ꮭ'), (0xABAE, 'M', 'Ꮮ'), + ] + +def _seg_39() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0xABAF, 'M', 'Ꮯ'), (0xABB0, 'M', 'Ꮰ'), (0xABB1, 'M', 'Ꮱ'), @@ -4166,14 +4162,14 @@ def _seg_39() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0xF943, 'M', '弄'), (0xF944, 'M', '籠'), (0xF945, 'M', '聾'), - ] - -def _seg_40() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0xF946, 'M', '牢'), (0xF947, 'M', '磊'), (0xF948, 'M', '賂'), (0xF949, 'M', '雷'), + ] + +def _seg_40() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0xF94A, 'M', '壘'), (0xF94B, 'M', '屢'), (0xF94C, 'M', '樓'), @@ -4270,14 +4266,14 @@ def _seg_40() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0xF9A7, 'M', '獵'), (0xF9A8, 'M', '令'), (0xF9A9, 'M', '囹'), - ] - -def _seg_41() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0xF9AA, 'M', '寧'), (0xF9AB, 'M', '嶺'), (0xF9AC, 'M', '怜'), (0xF9AD, 'M', '玲'), + ] + +def _seg_41() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0xF9AE, 'M', '瑩'), (0xF9AF, 'M', '羚'), (0xF9B0, 'M', '聆'), @@ -4374,14 +4370,14 @@ def _seg_41() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0xFA0B, 'M', '廓'), (0xFA0C, 'M', '兀'), (0xFA0D, 'M', '嗀'), - ] - -def _seg_42() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0xFA0E, 'V'), (0xFA10, 'M', '塚'), (0xFA11, 'V'), (0xFA12, 'M', '晴'), + ] + +def _seg_42() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0xFA13, 'V'), (0xFA15, 'M', '凞'), (0xFA16, 'M', '猪'), @@ -4478,14 +4474,14 @@ def _seg_42() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0xFA76, 'M', '勇'), (0xFA77, 'M', '勺'), (0xFA78, 'M', '喝'), - ] - -def _seg_43() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0xFA79, 'M', '啕'), (0xFA7A, 'M', '喙'), (0xFA7B, 'M', '嗢'), (0xFA7C, 'M', '塚'), + ] + +def _seg_43() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0xFA7D, 'M', '墳'), (0xFA7E, 'M', '奄'), (0xFA7F, 'M', '奔'), @@ -4582,14 +4578,14 @@ def _seg_43() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0xFADA, 'X'), (0xFB00, 'M', 'ff'), (0xFB01, 'M', 'fi'), - ] - -def _seg_44() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0xFB02, 'M', 'fl'), (0xFB03, 'M', 'ffi'), (0xFB04, 'M', 'ffl'), (0xFB05, 'M', 'st'), + ] + +def _seg_44() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0xFB07, 'X'), (0xFB13, 'M', 'մն'), (0xFB14, 'M', 'մե'), @@ -4686,14 +4682,14 @@ def _seg_44() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0xFBDB, 'M', 'ۈ'), (0xFBDD, 'M', 'ۇٴ'), (0xFBDE, 'M', 'ۋ'), - ] - -def _seg_45() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0xFBE0, 'M', 'ۅ'), (0xFBE2, 'M', 'ۉ'), (0xFBE4, 'M', 'ې'), (0xFBE8, 'M', 'ى'), + ] + +def _seg_45() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0xFBEA, 'M', 'ئا'), (0xFBEC, 'M', 'ئە'), (0xFBEE, 'M', 'ئو'), @@ -4790,14 +4786,14 @@ def _seg_45() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0xFC54, 'M', 'هي'), (0xFC55, 'M', 'يج'), (0xFC56, 'M', 'يح'), - ] - -def _seg_46() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0xFC57, 'M', 'يخ'), (0xFC58, 'M', 'يم'), (0xFC59, 'M', 'يى'), (0xFC5A, 'M', 'يي'), + ] + +def _seg_46() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0xFC5B, 'M', 'ذٰ'), (0xFC5C, 'M', 'رٰ'), (0xFC5D, 'M', 'ىٰ'), @@ -4894,14 +4890,14 @@ def _seg_46() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0xFCB8, 'M', 'طح'), (0xFCB9, 'M', 'ظم'), (0xFCBA, 'M', 'عج'), - ] - -def _seg_47() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0xFCBB, 'M', 'عم'), (0xFCBC, 'M', 'غج'), (0xFCBD, 'M', 'غم'), (0xFCBE, 'M', 'فج'), + ] + +def _seg_47() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0xFCBF, 'M', 'فح'), (0xFCC0, 'M', 'فخ'), (0xFCC1, 'M', 'فم'), @@ -4998,14 +4994,14 @@ def _seg_47() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0xFD1C, 'M', 'حي'), (0xFD1D, 'M', 'جى'), (0xFD1E, 'M', 'جي'), - ] - -def _seg_48() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0xFD1F, 'M', 'خى'), (0xFD20, 'M', 'خي'), (0xFD21, 'M', 'صى'), (0xFD22, 'M', 'صي'), + ] + +def _seg_48() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0xFD23, 'M', 'ضى'), (0xFD24, 'M', 'ضي'), (0xFD25, 'M', 'شج'), @@ -5102,14 +5098,14 @@ def _seg_48() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0xFDA4, 'M', 'تمى'), (0xFDA5, 'M', 'جمي'), (0xFDA6, 'M', 'جحى'), - ] - -def _seg_49() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0xFDA7, 'M', 'جمى'), (0xFDA8, 'M', 'سخى'), (0xFDA9, 'M', 'صحي'), (0xFDAA, 'M', 'شحي'), + ] + +def _seg_49() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0xFDAB, 'M', 'ضحي'), (0xFDAC, 'M', 'لجي'), (0xFDAD, 'M', 'لمي'), @@ -5206,14 +5202,14 @@ def _seg_49() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0xFE5B, '3', '{'), (0xFE5C, '3', '}'), (0xFE5D, 'M', '〔'), - ] - -def _seg_50() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0xFE5E, 'M', '〕'), (0xFE5F, '3', '#'), (0xFE60, '3', '&'), (0xFE61, '3', '*'), + ] + +def _seg_50() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0xFE62, '3', '+'), (0xFE63, 'M', '-'), (0xFE64, '3', '<'), @@ -5310,14 +5306,14 @@ def _seg_50() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0xFF18, 'M', '8'), (0xFF19, 'M', '9'), (0xFF1A, '3', ':'), - ] - -def _seg_51() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0xFF1B, '3', ';'), (0xFF1C, '3', '<'), (0xFF1D, '3', '='), (0xFF1E, '3', '>'), + ] + +def _seg_51() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0xFF1F, '3', '?'), (0xFF20, '3', '@'), (0xFF21, 'M', 'a'), @@ -5414,14 +5410,14 @@ def _seg_51() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0xFF7C, 'M', 'シ'), (0xFF7D, 'M', 'ス'), (0xFF7E, 'M', 'セ'), - ] - -def _seg_52() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0xFF7F, 'M', 'ソ'), (0xFF80, 'M', 'タ'), (0xFF81, 'M', 'チ'), (0xFF82, 'M', 'ツ'), + ] + +def _seg_52() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0xFF83, 'M', 'テ'), (0xFF84, 'M', 'ト'), (0xFF85, 'M', 'ナ'), @@ -5518,14 +5514,14 @@ def _seg_52() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0xFFE7, 'X'), (0xFFE8, 'M', '│'), (0xFFE9, 'M', '←'), - ] - -def _seg_53() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0xFFEA, 'M', '↑'), (0xFFEB, 'M', '→'), (0xFFEC, 'M', '↓'), (0xFFED, 'M', '■'), + ] + +def _seg_53() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0xFFEE, 'M', '○'), (0xFFEF, 'X'), (0x10000, 'V'), @@ -5622,14 +5618,14 @@ def _seg_53() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x104B3, 'M', '𐓛'), (0x104B4, 'M', '𐓜'), (0x104B5, 'M', '𐓝'), - ] - -def _seg_54() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x104B6, 'M', '𐓞'), (0x104B7, 'M', '𐓟'), (0x104B8, 'M', '𐓠'), (0x104B9, 'M', '𐓡'), + ] + +def _seg_54() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x104BA, 'M', '𐓢'), (0x104BB, 'M', '𐓣'), (0x104BC, 'M', '𐓤'), @@ -5726,14 +5722,14 @@ def _seg_54() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x10786, 'X'), (0x10787, 'M', 'ʣ'), (0x10788, 'M', 'ꭦ'), - ] - -def _seg_55() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x10789, 'M', 'ʥ'), (0x1078A, 'M', 'ʤ'), (0x1078B, 'M', 'ɖ'), (0x1078C, 'M', 'ɗ'), + ] + +def _seg_55() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x1078D, 'M', 'ᶑ'), (0x1078E, 'M', 'ɘ'), (0x1078F, 'M', 'ɞ'), @@ -5830,14 +5826,14 @@ def _seg_55() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x10A60, 'V'), (0x10AA0, 'X'), (0x10AC0, 'V'), - ] - -def _seg_56() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x10AE7, 'X'), (0x10AEB, 'V'), (0x10AF7, 'X'), (0x10B00, 'V'), + ] + +def _seg_56() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x10B36, 'X'), (0x10B39, 'V'), (0x10B56, 'X'), @@ -5934,14 +5930,14 @@ def _seg_56() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x1107F, 'V'), (0x110BD, 'X'), (0x110BE, 'V'), - ] - -def _seg_57() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x110C3, 'X'), (0x110D0, 'V'), (0x110E9, 'X'), (0x110F0, 'V'), + ] + +def _seg_57() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x110FA, 'X'), (0x11100, 'V'), (0x11135, 'X'), @@ -6038,14 +6034,14 @@ def _seg_57() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x118A4, 'M', '𑣄'), (0x118A5, 'M', '𑣅'), (0x118A6, 'M', '𑣆'), - ] - -def _seg_58() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x118A7, 'M', '𑣇'), (0x118A8, 'M', '𑣈'), (0x118A9, 'M', '𑣉'), (0x118AA, 'M', '𑣊'), + ] + +def _seg_58() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x118AB, 'M', '𑣋'), (0x118AC, 'M', '𑣌'), (0x118AD, 'M', '𑣍'), @@ -6142,14 +6138,14 @@ def _seg_58() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x11EE0, 'V'), (0x11EF9, 'X'), (0x11F00, 'V'), - ] - -def _seg_59() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x11F11, 'X'), (0x11F12, 'V'), (0x11F3B, 'X'), (0x11F3E, 'V'), + ] + +def _seg_59() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x11F5A, 'X'), (0x11FB0, 'V'), (0x11FB1, 'X'), @@ -6246,14 +6242,14 @@ def _seg_59() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x18D00, 'V'), (0x18D09, 'X'), (0x1AFF0, 'V'), - ] - -def _seg_60() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x1AFF4, 'X'), (0x1AFF5, 'V'), (0x1AFFC, 'X'), (0x1AFFD, 'V'), + ] + +def _seg_60() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x1AFFF, 'X'), (0x1B000, 'V'), (0x1B123, 'X'), @@ -6350,14 +6346,14 @@ def _seg_60() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x1D41E, 'M', 'e'), (0x1D41F, 'M', 'f'), (0x1D420, 'M', 'g'), - ] - -def _seg_61() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x1D421, 'M', 'h'), (0x1D422, 'M', 'i'), (0x1D423, 'M', 'j'), (0x1D424, 'M', 'k'), + ] + +def _seg_61() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x1D425, 'M', 'l'), (0x1D426, 'M', 'm'), (0x1D427, 'M', 'n'), @@ -6454,14 +6450,14 @@ def _seg_61() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x1D482, 'M', 'a'), (0x1D483, 'M', 'b'), (0x1D484, 'M', 'c'), - ] - -def _seg_62() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x1D485, 'M', 'd'), (0x1D486, 'M', 'e'), (0x1D487, 'M', 'f'), (0x1D488, 'M', 'g'), + ] + +def _seg_62() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x1D489, 'M', 'h'), (0x1D48A, 'M', 'i'), (0x1D48B, 'M', 'j'), @@ -6558,14 +6554,14 @@ def _seg_62() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x1D4E9, 'M', 'z'), (0x1D4EA, 'M', 'a'), (0x1D4EB, 'M', 'b'), - ] - -def _seg_63() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x1D4EC, 'M', 'c'), (0x1D4ED, 'M', 'd'), (0x1D4EE, 'M', 'e'), (0x1D4EF, 'M', 'f'), + ] + +def _seg_63() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x1D4F0, 'M', 'g'), (0x1D4F1, 'M', 'h'), (0x1D4F2, 'M', 'i'), @@ -6662,14 +6658,14 @@ def _seg_63() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x1D550, 'M', 'y'), (0x1D551, 'X'), (0x1D552, 'M', 'a'), - ] - -def _seg_64() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x1D553, 'M', 'b'), (0x1D554, 'M', 'c'), (0x1D555, 'M', 'd'), (0x1D556, 'M', 'e'), + ] + +def _seg_64() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x1D557, 'M', 'f'), (0x1D558, 'M', 'g'), (0x1D559, 'M', 'h'), @@ -6766,14 +6762,14 @@ def _seg_64() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x1D5B4, 'M', 'u'), (0x1D5B5, 'M', 'v'), (0x1D5B6, 'M', 'w'), - ] - -def _seg_65() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x1D5B7, 'M', 'x'), (0x1D5B8, 'M', 'y'), (0x1D5B9, 'M', 'z'), (0x1D5BA, 'M', 'a'), + ] + +def _seg_65() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x1D5BB, 'M', 'b'), (0x1D5BC, 'M', 'c'), (0x1D5BD, 'M', 'd'), @@ -6870,14 +6866,14 @@ def _seg_65() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x1D618, 'M', 'q'), (0x1D619, 'M', 'r'), (0x1D61A, 'M', 's'), - ] - -def _seg_66() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x1D61B, 'M', 't'), (0x1D61C, 'M', 'u'), (0x1D61D, 'M', 'v'), (0x1D61E, 'M', 'w'), + ] + +def _seg_66() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x1D61F, 'M', 'x'), (0x1D620, 'M', 'y'), (0x1D621, 'M', 'z'), @@ -6974,14 +6970,14 @@ def _seg_66() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x1D67C, 'M', 'm'), (0x1D67D, 'M', 'n'), (0x1D67E, 'M', 'o'), - ] - -def _seg_67() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x1D67F, 'M', 'p'), (0x1D680, 'M', 'q'), (0x1D681, 'M', 'r'), (0x1D682, 'M', 's'), + ] + +def _seg_67() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x1D683, 'M', 't'), (0x1D684, 'M', 'u'), (0x1D685, 'M', 'v'), @@ -7078,14 +7074,14 @@ def _seg_67() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x1D6E2, 'M', 'α'), (0x1D6E3, 'M', 'β'), (0x1D6E4, 'M', 'γ'), - ] - -def _seg_68() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x1D6E5, 'M', 'δ'), (0x1D6E6, 'M', 'ε'), (0x1D6E7, 'M', 'ζ'), (0x1D6E8, 'M', 'η'), + ] + +def _seg_68() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x1D6E9, 'M', 'θ'), (0x1D6EA, 'M', 'ι'), (0x1D6EB, 'M', 'κ'), @@ -7182,14 +7178,14 @@ def _seg_68() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x1D747, 'M', 'σ'), (0x1D749, 'M', 'τ'), (0x1D74A, 'M', 'υ'), - ] - -def _seg_69() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x1D74B, 'M', 'φ'), (0x1D74C, 'M', 'χ'), (0x1D74D, 'M', 'ψ'), (0x1D74E, 'M', 'ω'), + ] + +def _seg_69() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x1D74F, 'M', '∂'), (0x1D750, 'M', 'ε'), (0x1D751, 'M', 'θ'), @@ -7286,14 +7282,14 @@ def _seg_69() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x1D7AD, 'M', 'δ'), (0x1D7AE, 'M', 'ε'), (0x1D7AF, 'M', 'ζ'), - ] - -def _seg_70() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x1D7B0, 'M', 'η'), (0x1D7B1, 'M', 'θ'), (0x1D7B2, 'M', 'ι'), (0x1D7B3, 'M', 'κ'), + ] + +def _seg_70() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x1D7B4, 'M', 'λ'), (0x1D7B5, 'M', 'μ'), (0x1D7B6, 'M', 'ν'), @@ -7390,14 +7386,14 @@ def _seg_70() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x1E030, 'M', 'а'), (0x1E031, 'M', 'б'), (0x1E032, 'M', 'в'), - ] - -def _seg_71() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x1E033, 'M', 'г'), (0x1E034, 'M', 'д'), (0x1E035, 'M', 'е'), (0x1E036, 'M', 'ж'), + ] + +def _seg_71() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x1E037, 'M', 'з'), (0x1E038, 'M', 'и'), (0x1E039, 'M', 'к'), @@ -7494,14 +7490,14 @@ def _seg_71() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x1E907, 'M', '𞤩'), (0x1E908, 'M', '𞤪'), (0x1E909, 'M', '𞤫'), - ] - -def _seg_72() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x1E90A, 'M', '𞤬'), (0x1E90B, 'M', '𞤭'), (0x1E90C, 'M', '𞤮'), (0x1E90D, 'M', '𞤯'), + ] + +def _seg_72() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x1E90E, 'M', '𞤰'), (0x1E90F, 'M', '𞤱'), (0x1E910, 'M', '𞤲'), @@ -7598,14 +7594,14 @@ def _seg_72() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x1EE48, 'X'), (0x1EE49, 'M', 'ي'), (0x1EE4A, 'X'), - ] - -def _seg_73() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x1EE4B, 'M', 'ل'), (0x1EE4C, 'X'), (0x1EE4D, 'M', 'ن'), (0x1EE4E, 'M', 'س'), + ] + +def _seg_73() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x1EE4F, 'M', 'ع'), (0x1EE50, 'X'), (0x1EE51, 'M', 'ص'), @@ -7702,14 +7698,14 @@ def _seg_73() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x1EEB2, 'M', 'ق'), (0x1EEB3, 'M', 'ر'), (0x1EEB4, 'M', 'ش'), - ] - -def _seg_74() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x1EEB5, 'M', 'ت'), (0x1EEB6, 'M', 'ث'), (0x1EEB7, 'M', 'خ'), (0x1EEB8, 'M', 'ذ'), + ] + +def _seg_74() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x1EEB9, 'M', 'ض'), (0x1EEBA, 'M', 'ظ'), (0x1EEBB, 'M', 'غ'), @@ -7806,14 +7802,14 @@ def _seg_74() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x1F150, 'V'), (0x1F16A, 'M', 'mc'), (0x1F16B, 'M', 'md'), - ] - -def _seg_75() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x1F16C, 'M', 'mr'), (0x1F16D, 'V'), (0x1F190, 'M', 'dj'), (0x1F191, 'V'), + ] + +def _seg_75() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x1F1AE, 'X'), (0x1F1E6, 'V'), (0x1F200, 'M', 'ほか'), @@ -7910,14 +7906,14 @@ def _seg_75() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x1FA54, 'X'), (0x1FA60, 'V'), (0x1FA6E, 'X'), - ] - -def _seg_76() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: - return [ (0x1FA70, 'V'), (0x1FA7D, 'X'), (0x1FA80, 'V'), (0x1FA89, 'X'), + ] + +def _seg_76() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: + return [ (0x1FA90, 'V'), (0x1FABE, 'X'), (0x1FABF, 'V'), @@ -7953,6 +7949,8 @@ def _seg_76() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x2CEA2, 'X'), (0x2CEB0, 'V'), (0x2EBE1, 'X'), + (0x2EBF0, 'V'), + (0x2EE5E, 'X'), (0x2F800, 'M', '丽'), (0x2F801, 'M', '丸'), (0x2F802, 'M', '乁'), @@ -8014,12 +8012,12 @@ def _seg_76() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x2F83C, 'M', '咞'), (0x2F83D, 'M', '吸'), (0x2F83E, 'M', '呈'), + (0x2F83F, 'M', '周'), + (0x2F840, 'M', '咢'), ] def _seg_77() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: return [ - (0x2F83F, 'M', '周'), - (0x2F840, 'M', '咢'), (0x2F841, 'M', '哶'), (0x2F842, 'M', '唐'), (0x2F843, 'M', '啓'), @@ -8118,12 +8116,12 @@ def _seg_77() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x2F8A4, 'M', '𢛔'), (0x2F8A5, 'M', '惇'), (0x2F8A6, 'M', '慈'), + (0x2F8A7, 'M', '慌'), + (0x2F8A8, 'M', '慎'), ] def _seg_78() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: return [ - (0x2F8A7, 'M', '慌'), - (0x2F8A8, 'M', '慎'), (0x2F8A9, 'M', '慌'), (0x2F8AA, 'M', '慺'), (0x2F8AB, 'M', '憎'), @@ -8222,12 +8220,12 @@ def _seg_78() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x2F908, 'M', '港'), (0x2F909, 'M', '湮'), (0x2F90A, 'M', '㴳'), + (0x2F90B, 'M', '滋'), + (0x2F90C, 'M', '滇'), ] def _seg_79() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: return [ - (0x2F90B, 'M', '滋'), - (0x2F90C, 'M', '滇'), (0x2F90D, 'M', '𣻑'), (0x2F90E, 'M', '淹'), (0x2F90F, 'M', '潮'), @@ -8326,12 +8324,12 @@ def _seg_79() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x2F96F, 'M', '縂'), (0x2F970, 'M', '繅'), (0x2F971, 'M', '䌴'), + (0x2F972, 'M', '𦈨'), + (0x2F973, 'M', '𦉇'), ] def _seg_80() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: return [ - (0x2F972, 'M', '𦈨'), - (0x2F973, 'M', '𦉇'), (0x2F974, 'M', '䍙'), (0x2F975, 'M', '𦋙'), (0x2F976, 'M', '罺'), @@ -8430,12 +8428,12 @@ def _seg_80() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: (0x2F9D3, 'M', '𧲨'), (0x2F9D4, 'M', '貫'), (0x2F9D5, 'M', '賁'), + (0x2F9D6, 'M', '贛'), + (0x2F9D7, 'M', '起'), ] def _seg_81() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]: return [ - (0x2F9D6, 'M', '贛'), - (0x2F9D7, 'M', '起'), (0x2F9D8, 'M', '𧼯'), (0x2F9D9, 'M', '𠠄'), (0x2F9DA, 'M', '跋'), diff --git a/lib/oauthlib/__init__.py b/lib/oauthlib/__init__.py index 5dbffc96..d9a5e38e 100644 --- a/lib/oauthlib/__init__.py +++ b/lib/oauthlib/__init__.py @@ -12,7 +12,7 @@ import logging from logging import NullHandler __author__ = 'The OAuthlib Community' -__version__ = '3.2.0' +__version__ = '3.2.2' logging.getLogger('oauthlib').addHandler(NullHandler()) diff --git a/lib/oauthlib/common.py b/lib/oauthlib/common.py index b5fbf52e..395e75ef 100644 --- a/lib/oauthlib/common.py +++ b/lib/oauthlib/common.py @@ -18,11 +18,9 @@ from urllib.parse import ( from . import get_debug try: - from secrets import randbits - from secrets import SystemRandom + from secrets import SystemRandom, randbits except ImportError: - from random import getrandbits as randbits - from random import SystemRandom + from random import SystemRandom, getrandbits as randbits UNICODE_ASCII_CHARACTER_SET = ('abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' diff --git a/lib/oauthlib/oauth1/__init__.py b/lib/oauthlib/oauth1/__init__.py index 07ef4226..9caf12a9 100644 --- a/lib/oauthlib/oauth1/__init__.py +++ b/lib/oauthlib/oauth1/__init__.py @@ -5,24 +5,19 @@ oauthlib.oauth1 This module is a wrapper for the most recent implementation of OAuth 1.0 Client and Server classes. """ -from .rfc5849 import Client -from .rfc5849 import (SIGNATURE_HMAC, - SIGNATURE_HMAC_SHA1, - SIGNATURE_HMAC_SHA256, - SIGNATURE_HMAC_SHA512, - SIGNATURE_RSA, - SIGNATURE_RSA_SHA1, - SIGNATURE_RSA_SHA256, - SIGNATURE_RSA_SHA512, - SIGNATURE_PLAINTEXT) -from .rfc5849 import SIGNATURE_TYPE_AUTH_HEADER, SIGNATURE_TYPE_QUERY -from .rfc5849 import SIGNATURE_TYPE_BODY +from .rfc5849 import ( + SIGNATURE_HMAC, SIGNATURE_HMAC_SHA1, SIGNATURE_HMAC_SHA256, + SIGNATURE_HMAC_SHA512, SIGNATURE_PLAINTEXT, SIGNATURE_RSA, + SIGNATURE_RSA_SHA1, SIGNATURE_RSA_SHA256, SIGNATURE_RSA_SHA512, + SIGNATURE_TYPE_AUTH_HEADER, SIGNATURE_TYPE_BODY, SIGNATURE_TYPE_QUERY, + Client, +) +from .rfc5849.endpoints import ( + AccessTokenEndpoint, AuthorizationEndpoint, RequestTokenEndpoint, + ResourceEndpoint, SignatureOnlyEndpoint, WebApplicationServer, +) +from .rfc5849.errors import ( + InsecureTransportError, InvalidClientError, InvalidRequestError, + InvalidSignatureMethodError, OAuth1Error, +) from .rfc5849.request_validator import RequestValidator -from .rfc5849.endpoints import RequestTokenEndpoint, AuthorizationEndpoint -from .rfc5849.endpoints import AccessTokenEndpoint, ResourceEndpoint -from .rfc5849.endpoints import SignatureOnlyEndpoint, WebApplicationServer -from .rfc5849.errors import (InsecureTransportError, - InvalidClientError, - InvalidRequestError, - InvalidSignatureMethodError, - OAuth1Error) diff --git a/lib/oauthlib/oauth1/rfc5849/endpoints/base.py b/lib/oauthlib/oauth1/rfc5849/endpoints/base.py index 3a8c2677..7831be7c 100644 --- a/lib/oauthlib/oauth1/rfc5849/endpoints/base.py +++ b/lib/oauthlib/oauth1/rfc5849/endpoints/base.py @@ -11,12 +11,11 @@ import time from oauthlib.common import CaseInsensitiveDict, Request, generate_token from .. import ( - CONTENT_TYPE_FORM_URLENCODED, - SIGNATURE_HMAC_SHA1, SIGNATURE_HMAC_SHA256, SIGNATURE_HMAC_SHA512, - SIGNATURE_RSA_SHA1, SIGNATURE_RSA_SHA256, SIGNATURE_RSA_SHA512, - SIGNATURE_PLAINTEXT, - SIGNATURE_TYPE_AUTH_HEADER, SIGNATURE_TYPE_BODY, - SIGNATURE_TYPE_QUERY, errors, signature, utils) + CONTENT_TYPE_FORM_URLENCODED, SIGNATURE_HMAC_SHA1, SIGNATURE_HMAC_SHA256, + SIGNATURE_HMAC_SHA512, SIGNATURE_PLAINTEXT, SIGNATURE_RSA_SHA1, + SIGNATURE_RSA_SHA256, SIGNATURE_RSA_SHA512, SIGNATURE_TYPE_AUTH_HEADER, + SIGNATURE_TYPE_BODY, SIGNATURE_TYPE_QUERY, errors, signature, utils, +) class BaseEndpoint: diff --git a/lib/oauthlib/oauth1/rfc5849/endpoints/request_token.py b/lib/oauthlib/oauth1/rfc5849/endpoints/request_token.py index bb67e71e..0323cfb8 100644 --- a/lib/oauthlib/oauth1/rfc5849/endpoints/request_token.py +++ b/lib/oauthlib/oauth1/rfc5849/endpoints/request_token.py @@ -152,7 +152,7 @@ class RequestTokenEndpoint(BaseEndpoint): request.client_key = self.request_validator.dummy_client # Note that `realm`_ is only used in authorization headers and how - # it should be interepreted is not included in the OAuth spec. + # it should be interpreted is not included in the OAuth spec. # However they could be seen as a scope or realm to which the # client has access and as such every client should be checked # to ensure it is authorized access to that scope or realm. @@ -164,7 +164,7 @@ class RequestTokenEndpoint(BaseEndpoint): # workflow where a client requests access to a specific realm. # This first step (obtaining request token) need not require a realm # and can then be identified by checking the require_resource_owner - # flag and abscence of realm. + # flag and absence of realm. # # Clients obtaining an access token will not supply a realm and it will # not be checked. Instead the previously requested realm should be diff --git a/lib/oauthlib/oauth1/rfc5849/endpoints/resource.py b/lib/oauthlib/oauth1/rfc5849/endpoints/resource.py index 45bdaaac..8641152e 100644 --- a/lib/oauthlib/oauth1/rfc5849/endpoints/resource.py +++ b/lib/oauthlib/oauth1/rfc5849/endpoints/resource.py @@ -113,7 +113,7 @@ class ResourceEndpoint(BaseEndpoint): request.resource_owner_key = self.request_validator.dummy_access_token # Note that `realm`_ is only used in authorization headers and how - # it should be interepreted is not included in the OAuth spec. + # it should be interpreted is not included in the OAuth spec. # However they could be seen as a scope or realm to which the # client has access and as such every client should be checked # to ensure it is authorized access to that scope or realm. @@ -125,7 +125,7 @@ class ResourceEndpoint(BaseEndpoint): # workflow where a client requests access to a specific realm. # This first step (obtaining request token) need not require a realm # and can then be identified by checking the require_resource_owner - # flag and abscence of realm. + # flag and absence of realm. # # Clients obtaining an access token will not supply a realm and it will # not be checked. Instead the previously requested realm should be diff --git a/lib/oauthlib/oauth1/rfc5849/request_validator.py b/lib/oauthlib/oauth1/rfc5849/request_validator.py index dc5bf0eb..e937aabf 100644 --- a/lib/oauthlib/oauth1/rfc5849/request_validator.py +++ b/lib/oauthlib/oauth1/rfc5849/request_validator.py @@ -19,7 +19,7 @@ class RequestValidator: Methods used to check the format of input parameters. Common tests include length, character set, membership, range or pattern. These tests are referred to as `whitelisting or blacklisting`_. Whitelisting is better - but blacklisting can be usefull to spot malicious activity. + but blacklisting can be useful to spot malicious activity. The following have methods a default implementation: - check_client_key @@ -443,7 +443,7 @@ class RequestValidator: :type request: oauthlib.common.Request :returns: None - Per `Section 2.3`__ of the spec: + Per `Section 2.3`_ of the spec: "The server MUST (...) ensure that the temporary credentials have not expired or been used before." @@ -831,7 +831,7 @@ class RequestValidator: """Associate an authorization verifier with a request token. :param token: A request token string. - :param verifier A dictionary containing the oauth_verifier and + :param verifier: A dictionary containing the oauth_verifier and oauth_token :param request: OAuthlib request. :type request: oauthlib.common.Request diff --git a/lib/oauthlib/oauth1/rfc5849/signature.py b/lib/oauthlib/oauth1/rfc5849/signature.py index a370ccd6..9cb1a517 100644 --- a/lib/oauthlib/oauth1/rfc5849/signature.py +++ b/lib/oauthlib/oauth1/rfc5849/signature.py @@ -37,15 +37,15 @@ should have no impact on properly behaving programs. import binascii import hashlib import hmac +import ipaddress import logging +import urllib.parse as urlparse import warnings from oauthlib.common import extract_params, safe_string_equals, urldecode -import urllib.parse as urlparse from . import utils - log = logging.getLogger(__name__) @@ -131,7 +131,12 @@ def base_string_uri(uri: str, host: str = None) -> str: raise ValueError('uri must be a string.') # FIXME: urlparse does not support unicode - scheme, netloc, path, params, query, fragment = urlparse.urlparse(uri) + output = urlparse.urlparse(uri) + scheme = output.scheme + hostname = output.hostname + port = output.port + path = output.path + params = output.params # The scheme, authority, and path of the request resource URI `RFC3986` # are included by constructing an "http" or "https" URI representing @@ -153,13 +158,22 @@ def base_string_uri(uri: str, host: str = None) -> str: # 1. The scheme and host MUST be in lowercase. scheme = scheme.lower() - netloc = netloc.lower() # Note: if ``host`` is used, it will be converted to lowercase below + if hostname is not None: + hostname = hostname.lower() # 2. The host and port values MUST match the content of the HTTP # request "Host" header field. if host is not None: - netloc = host.lower() # override value in uri with provided host + # NOTE: override value in uri with provided host + # Host argument is equal to netloc. It means it's missing scheme. + # Add it back, before parsing. + + host = host.lower() + host = f"{scheme}://{host}" + output = urlparse.urlparse(host) + hostname = output.hostname + port = output.port # 3. The port MUST be included if it is not the default port for the # scheme, and MUST be excluded if it is the default. Specifically, @@ -170,33 +184,28 @@ def base_string_uri(uri: str, host: str = None) -> str: # .. _`RFC2616`: https://tools.ietf.org/html/rfc2616 # .. _`RFC2818`: https://tools.ietf.org/html/rfc2818 - if ':' in netloc: - # Contains a colon ":", so try to parse as "host:port" + if hostname is None: + raise ValueError('missing host') - hostname, port_str = netloc.split(':', 1) + # NOTE: Try guessing if we're dealing with IP or hostname + try: + hostname = ipaddress.ip_address(hostname) + except ValueError: + pass - if len(hostname) == 0: - raise ValueError('missing host') # error: netloc was ":port" or ":" + if isinstance(hostname, ipaddress.IPv6Address): + hostname = f"[{hostname}]" + elif isinstance(hostname, ipaddress.IPv4Address): + hostname = f"{hostname}" - if len(port_str) == 0: - netloc = hostname # was "host:", so just use the host part - else: - try: - port_num = int(port_str) # try to parse into an integer number - except ValueError: - raise ValueError('port is not an integer') - - if port_num <= 0 or 65535 < port_num: - raise ValueError('port out of range') # 16-bit unsigned ints - if (scheme, port_num) in (('http', 80), ('https', 443)): - netloc = hostname # default port for scheme: exclude port num - else: - netloc = hostname + ':' + str(port_num) # use hostname:port + if port is not None and not (0 < port <= 65535): + raise ValueError('port out of range') # 16-bit unsigned ints + if (scheme, port) in (('http', 80), ('https', 443)): + netloc = hostname # default port for scheme: exclude port num + elif port: + netloc = f"{hostname}:{port}" # use hostname:port else: - # Does not contain a colon, so entire value must be the hostname - - if len(netloc) == 0: - raise ValueError('missing host') # error: netloc was empty string + netloc = hostname v = urlparse.urlunparse((scheme, netloc, path, params, '', '')) diff --git a/lib/oauthlib/oauth2/rfc6749/clients/backend_application.py b/lib/oauthlib/oauth2/rfc6749/clients/backend_application.py index 0e2a8299..e11e8fae 100644 --- a/lib/oauthlib/oauth2/rfc6749/clients/backend_application.py +++ b/lib/oauthlib/oauth2/rfc6749/clients/backend_application.py @@ -39,7 +39,7 @@ class BackendApplicationClient(Client): format per `Appendix B`_ in the HTTP request entity-body: :param body: Existing request body (URL encoded string) to embed parameters - into. This may contain extra paramters. Default ''. + into. This may contain extra parameters. Default ''. :param scope: The scope of the access request as described by `Section 3.3`_. diff --git a/lib/oauthlib/oauth2/rfc6749/clients/base.py b/lib/oauthlib/oauth2/rfc6749/clients/base.py index bb4c1338..d5eb0cc1 100644 --- a/lib/oauthlib/oauth2/rfc6749/clients/base.py +++ b/lib/oauthlib/oauth2/rfc6749/clients/base.py @@ -6,12 +6,12 @@ oauthlib.oauth2.rfc6749 This module is an implementation of various logic needed for consuming OAuth 2.0 RFC6749. """ +import base64 +import hashlib +import re +import secrets import time import warnings -import secrets -import re -import hashlib -import base64 from oauthlib.common import generate_token from oauthlib.oauth2.rfc6749 import tokens @@ -228,26 +228,21 @@ class Client: required parameters to the authorization URL. :param authorization_url: Provider authorization endpoint URL. - :param state: CSRF protection string. Will be automatically created if - not provided. The generated state is available via the ``state`` - attribute. Clients should verify that the state is unchanged and - present in the authorization response. This verification is done - automatically if using the ``authorization_response`` parameter - with ``prepare_token_request``. - + not provided. The generated state is available via the ``state`` + attribute. Clients should verify that the state is unchanged and + present in the authorization response. This verification is done + automatically if using the ``authorization_response`` parameter + with ``prepare_token_request``. :param redirect_url: Redirect URL to which the user will be returned - after authorization. Must be provided unless previously setup with - the provider. If provided then it must also be provided in the - token request. - + after authorization. Must be provided unless previously setup with + the provider. If provided then it must also be provided in the + token request. :param scope: List of scopes to request. Must be equal to - or a subset of the scopes granted when obtaining the refresh - token. If none is provided, the ones provided in the constructor are - used. - + or a subset of the scopes granted when obtaining the refresh + token. If none is provided, the ones provided in the constructor are + used. :param kwargs: Additional parameters to included in the request. - :returns: The prepared request tuple with (url, headers, body). """ if not is_secure_transport(authorization_url): @@ -271,22 +266,16 @@ class Client: credentials. :param token_url: Provider token creation endpoint URL. - :param authorization_response: The full redirection URL string, i.e. - the location to which the user was redirected after successfull - authorization. Used to mine credentials needed to obtain a token - in this step, such as authorization code. - + the location to which the user was redirected after successful + authorization. Used to mine credentials needed to obtain a token + in this step, such as authorization code. :param redirect_url: The redirect_url supplied with the authorization - request (if there was one). - + request (if there was one). :param state: - :param body: Existing request body (URL encoded string) to embed parameters - into. This may contain extra paramters. Default ''. - + into. This may contain extra parameters. Default ''. :param kwargs: Additional parameters to included in the request. - :returns: The prepared request tuple with (url, headers, body). """ if not is_secure_transport(token_url): @@ -312,19 +301,14 @@ class Client: obtain a new access token, and possibly a new refresh token. :param token_url: Provider token refresh endpoint URL. - :param refresh_token: Refresh token string. - :param body: Existing request body (URL encoded string) to embed parameters - into. This may contain extra paramters. Default ''. - + into. This may contain extra parameters. Default ''. :param scope: List of scopes to request. Must be equal to - or a subset of the scopes granted when obtaining the refresh - token. If none is provided, the ones provided in the constructor are - used. - + or a subset of the scopes granted when obtaining the refresh + token. If none is provided, the ones provided in the constructor are + used. :param kwargs: Additional parameters to included in the request. - :returns: The prepared request tuple with (url, headers, body). """ if not is_secure_transport(token_url): @@ -341,20 +325,14 @@ class Client: """Prepare a token revocation request. :param revocation_url: Provider token revocation endpoint URL. - :param token: The access or refresh token to be revoked (string). - :param token_type_hint: ``"access_token"`` (default) or - ``"refresh_token"``. This is optional and if you wish to not pass it you - must provide ``token_type_hint=None``. - + ``"refresh_token"``. This is optional and if you wish to not pass it you + must provide ``token_type_hint=None``. :param body: - :param callback: A jsonp callback such as ``package.callback`` to be invoked - upon receiving the response. Not that it should not include a () suffix. - + upon receiving the response. Not that it should not include a () suffix. :param kwargs: Additional parameters to included in the request. - :returns: The prepared request tuple with (url, headers, body). Note that JSONP request may use GET requests as the parameters will @@ -362,7 +340,7 @@ class Client: An example of a revocation request - .. code-block: http + .. code-block:: http POST /revoke HTTP/1.1 Host: server.example.com @@ -373,7 +351,7 @@ class Client: An example of a jsonp revocation request - .. code-block: http + .. code-block:: http GET /revoke?token=agabcdefddddafdd&callback=package.myCallback HTTP/1.1 Host: server.example.com @@ -382,9 +360,9 @@ class Client: and an error response - .. code-block: http + .. code-block:: javascript - package.myCallback({"error":"unsupported_token_type"}); + package.myCallback({"error":"unsupported_token_type"}); Note that these requests usually require client credentials, client_id in the case for public clients and provider specific authentication @@ -408,9 +386,10 @@ class Client: :param body: The response body from the token request. :param scope: Scopes originally requested. If none is provided, the ones - provided in the constructor are used. + provided in the constructor are used. :return: Dictionary of token parameters. - :raises: Warning if scope has changed. OAuth2Error if response is invalid. + :raises: Warning if scope has changed. :py:class:`oauthlib.oauth2.errors.OAuth2Error` + if response is invalid. These response are json encoded and could easily be parsed without the assistance of OAuthLib. However, there are a few subtle issues @@ -436,7 +415,7 @@ class Client: If omitted, the authorization server SHOULD provide the expiration time via other means or document the default value. - **scope** + **scope** Providers may supply this in all responses but are required to only if it has changed since the authorization request. @@ -454,20 +433,16 @@ class Client: If the authorization server issued a refresh token to the client, the client makes a refresh request to the token endpoint by adding the - following parameters using the "application/x-www-form-urlencoded" + following parameters using the `application/x-www-form-urlencoded` format in the HTTP request entity-body: - grant_type - REQUIRED. Value MUST be set to "refresh_token". - refresh_token - REQUIRED. The refresh token issued to the client. - scope - OPTIONAL. The scope of the access request as described by - Section 3.3. The requested scope MUST NOT include any scope - not originally granted by the resource owner, and if omitted is - treated as equal to the scope originally granted by the - resource owner. Note that if none is provided, the ones provided - in the constructor are used if any. + :param refresh_token: REQUIRED. The refresh token issued to the client. + :param scope: OPTIONAL. The scope of the access request as described by + Section 3.3. The requested scope MUST NOT include any scope + not originally granted by the resource owner, and if omitted is + treated as equal to the scope originally granted by the + resource owner. Note that if none is provided, the ones provided + in the constructor are used if any. """ refresh_token = refresh_token or self.refresh_token scope = self.scope if scope is None else scope @@ -492,18 +467,21 @@ class Client: def create_code_verifier(self, length): """Create PKCE **code_verifier** used in computing **code_challenge**. + See `RFC7636 Section 4.1`_ - :param length: REQUIRED. The length of the code_verifier. + :param length: REQUIRED. The length of the code_verifier. - The client first creates a code verifier, "code_verifier", for each - OAuth 2.0 [RFC6749] Authorization Request, in the following manner: + The client first creates a code verifier, "code_verifier", for each + OAuth 2.0 [RFC6749] Authorization Request, in the following manner: - code_verifier = high-entropy cryptographic random STRING using the - unreserved characters [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~" - from Section 2.3 of [RFC3986], with a minimum length of 43 characters - and a maximum length of 128 characters. - - .. _`Section 4.1`: https://tools.ietf.org/html/rfc7636#section-4.1 + .. code-block:: text + + code_verifier = high-entropy cryptographic random STRING using the + unreserved characters [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~" + from Section 2.3 of [RFC3986], with a minimum length of 43 characters + and a maximum length of 128 characters. + + .. _`RFC7636 Section 4.1`: https://tools.ietf.org/html/rfc7636#section-4.1 """ code_verifier = None @@ -525,33 +503,30 @@ class Client: def create_code_challenge(self, code_verifier, code_challenge_method=None): """Create PKCE **code_challenge** derived from the **code_verifier**. + See `RFC7636 Section 4.2`_ - :param code_verifier: REQUIRED. The **code_verifier** generated from create_code_verifier(). - :param code_challenge_method: OPTIONAL. The method used to derive the **code_challenge**. Acceptable - values include "S256". DEFAULT is "plain". + :param code_verifier: REQUIRED. The **code_verifier** generated from `create_code_verifier()`. + :param code_challenge_method: OPTIONAL. The method used to derive the **code_challenge**. Acceptable values include `S256`. DEFAULT is `plain`. - - The client then creates a code challenge derived from the code + The client then creates a code challenge derived from the code verifier by using one of the following transformations on the code - verifier: + verifier:: - plain - code_challenge = code_verifier + plain + code_challenge = code_verifier + S256 + code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) - S256 - code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) - - If the client is capable of using "S256", it MUST use "S256", as - "S256" is Mandatory To Implement (MTI) on the server. Clients are - permitted to use "plain" only if they cannot support "S256" for some + If the client is capable of using `S256`, it MUST use `S256`, as + `S256` is Mandatory To Implement (MTI) on the server. Clients are + permitted to use `plain` only if they cannot support `S256` for some technical reason and know via out-of-band configuration that the - server supports "plain". + server supports `plain`. The plain transformation is for compatibility with existing - deployments and for constrained environments that can't use the S256 - transformation. + deployments and for constrained environments that can't use the S256 transformation. - .. _`Section 4.2`: https://tools.ietf.org/html/rfc7636#section-4.2 + .. _`RFC7636 Section 4.2`: https://tools.ietf.org/html/rfc7636#section-4.2 """ code_challenge = None diff --git a/lib/oauthlib/oauth2/rfc6749/clients/legacy_application.py b/lib/oauthlib/oauth2/rfc6749/clients/legacy_application.py index 7af68f34..9920981d 100644 --- a/lib/oauthlib/oauth2/rfc6749/clients/legacy_application.py +++ b/lib/oauthlib/oauth2/rfc6749/clients/legacy_application.py @@ -49,7 +49,7 @@ class LegacyApplicationClient(Client): :param username: The resource owner username. :param password: The resource owner password. :param body: Existing request body (URL encoded string) to embed parameters - into. This may contain extra paramters. Default ''. + into. This may contain extra parameters. Default ''. :param scope: The scope of the access request as described by `Section 3.3`_. :param include_client_id: `True` to send the `client_id` in the diff --git a/lib/oauthlib/oauth2/rfc6749/clients/mobile_application.py b/lib/oauthlib/oauth2/rfc6749/clients/mobile_application.py index cd325f4e..b10b41ce 100644 --- a/lib/oauthlib/oauth2/rfc6749/clients/mobile_application.py +++ b/lib/oauthlib/oauth2/rfc6749/clients/mobile_application.py @@ -55,7 +55,7 @@ class MobileApplicationClient(Client): using the "application/x-www-form-urlencoded" format, per `Appendix B`_: :param redirect_uri: OPTIONAL. The redirect URI must be an absolute URI - and it should have been registerd with the OAuth + and it should have been registered with the OAuth provider prior to use. As described in `Section 3.1.2`_. :param scope: OPTIONAL. The scope of the access request as described by diff --git a/lib/oauthlib/oauth2/rfc6749/clients/service_application.py b/lib/oauthlib/oauth2/rfc6749/clients/service_application.py index c751c8b0..8fb17377 100644 --- a/lib/oauthlib/oauth2/rfc6749/clients/service_application.py +++ b/lib/oauthlib/oauth2/rfc6749/clients/service_application.py @@ -31,7 +31,7 @@ class ServiceApplicationClient(Client): def __init__(self, client_id, private_key=None, subject=None, issuer=None, audience=None, **kwargs): - """Initalize a JWT client with defaults for implicit use later. + """Initialize a JWT client with defaults for implicit use later. :param client_id: Client identifier given by the OAuth provider upon registration. @@ -99,7 +99,7 @@ class ServiceApplicationClient(Client): :param extra_claims: A dict of additional claims to include in the JWT. :param body: Existing request body (URL encoded string) to embed parameters - into. This may contain extra paramters. Default ''. + into. This may contain extra parameters. Default ''. :param scope: The scope of the access request. diff --git a/lib/oauthlib/oauth2/rfc6749/clients/web_application.py b/lib/oauthlib/oauth2/rfc6749/clients/web_application.py index 1d3b2b5b..50890fbf 100644 --- a/lib/oauthlib/oauth2/rfc6749/clients/web_application.py +++ b/lib/oauthlib/oauth2/rfc6749/clients/web_application.py @@ -49,7 +49,7 @@ class WebApplicationClient(Client): using the "application/x-www-form-urlencoded" format, per `Appendix B`_: :param redirect_uri: OPTIONAL. The redirect URI must be an absolute URI - and it should have been registerd with the OAuth + and it should have been registered with the OAuth provider prior to use. As described in `Section 3.1.2`_. :param scope: OPTIONAL. The scope of the access request as described by @@ -117,7 +117,7 @@ class WebApplicationClient(Client): values MUST be identical. :param body: Existing request body (URL encoded string) to embed parameters - into. This may contain extra paramters. Default ''. + into. This may contain extra parameters. Default ''. :param include_client_id: `True` (default) to send the `client_id` in the body of the upstream request. This is required diff --git a/lib/oauthlib/oauth2/rfc6749/endpoints/introspect.py b/lib/oauthlib/oauth2/rfc6749/endpoints/introspect.py index 63570d9c..3cc61e66 100644 --- a/lib/oauthlib/oauth2/rfc6749/endpoints/introspect.py +++ b/lib/oauthlib/oauth2/rfc6749/endpoints/introspect.py @@ -86,9 +86,9 @@ class IntrospectEndpoint(BaseEndpoint): an HTTP POST request with parameters sent as "application/x-www-form-urlencoded". - token REQUIRED. The string value of the token. + * token REQUIRED. The string value of the token. + * token_type_hint OPTIONAL. - token_type_hint OPTIONAL. A hint about the type of the token submitted for introspection. The protected resource MAY pass this parameter to help the authorization server optimize the token lookup. If the @@ -96,11 +96,9 @@ class IntrospectEndpoint(BaseEndpoint): extend its search across all of its supported token types. An authorization server MAY ignore this parameter, particularly if it is able to detect the token type automatically. - * access_token: An Access Token as defined in [`RFC6749`], - `section 1.4`_ - * refresh_token: A Refresh Token as defined in [`RFC6749`], - `section 1.5`_ + * access_token: An Access Token as defined in [`RFC6749`], `section 1.4`_ + * refresh_token: A Refresh Token as defined in [`RFC6749`], `section 1.5`_ The introspection endpoint MAY accept other OPTIONAL parameters to provide further context to the query. For diff --git a/lib/oauthlib/oauth2/rfc6749/endpoints/metadata.py b/lib/oauthlib/oauth2/rfc6749/endpoints/metadata.py index d43a8247..a2820f28 100644 --- a/lib/oauthlib/oauth2/rfc6749/endpoints/metadata.py +++ b/lib/oauthlib/oauth2/rfc6749/endpoints/metadata.py @@ -10,7 +10,7 @@ import copy import json import logging -from .. import grant_types +from .. import grant_types, utils from .authorization import AuthorizationEndpoint from .base import BaseEndpoint, catch_errors_and_unavailability from .introspect import IntrospectEndpoint @@ -68,7 +68,7 @@ class MetadataEndpoint(BaseEndpoint): raise ValueError("key {} is a mandatory metadata.".format(key)) elif is_issuer: - if not array[key].startswith("https"): + if not utils.is_secure_transport(array[key]): raise ValueError("key {}: {} must be an HTTPS URL".format(key, array[key])) if "?" in array[key] or "&" in array[key] or "#" in array[key]: raise ValueError("key {}: {} must not contain query or fragment components".format(key, array[key])) diff --git a/lib/oauthlib/oauth2/rfc6749/endpoints/revocation.py b/lib/oauthlib/oauth2/rfc6749/endpoints/revocation.py index 4aa5ec6e..596d0860 100644 --- a/lib/oauthlib/oauth2/rfc6749/endpoints/revocation.py +++ b/lib/oauthlib/oauth2/rfc6749/endpoints/revocation.py @@ -42,7 +42,7 @@ class RevocationEndpoint(BaseEndpoint): The authorization server responds with HTTP status code 200 if the - token has been revoked sucessfully or if the client submitted an + token has been revoked successfully or if the client submitted an invalid token. Note: invalid tokens do not cause an error response since the client @@ -95,7 +95,7 @@ class RevocationEndpoint(BaseEndpoint): submitted for revocation. Clients MAY pass this parameter in order to help the authorization server to optimize the token lookup. If the server is unable to locate the token using the given hint, it MUST - extend its search accross all of its supported token types. An + extend its search across all of its supported token types. An authorization server MAY ignore this parameter, particularly if it is able to detect the token type automatically. This specification defines two such values: diff --git a/lib/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py b/lib/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py index b799823e..858855a1 100644 --- a/lib/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py +++ b/lib/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py @@ -10,7 +10,6 @@ import logging from oauthlib import common from .. import errors -from ..utils import is_secure_transport from .base import GrantTypeBase log = logging.getLogger(__name__) @@ -547,20 +546,3 @@ class AuthorizationCodeGrant(GrantTypeBase): if challenge_method in self._code_challenge_methods: return self._code_challenge_methods[challenge_method](verifier, challenge) raise NotImplementedError('Unknown challenge_method %s' % challenge_method) - - def _create_cors_headers(self, request): - """If CORS is allowed, create the appropriate headers.""" - if 'origin' not in request.headers: - return {} - - origin = request.headers['origin'] - if not is_secure_transport(origin): - log.debug('Origin "%s" is not HTTPS, CORS not allowed.', origin) - return {} - elif not self.request_validator.is_origin_allowed( - request.client_id, origin, request): - log.debug('Invalid origin "%s", CORS not allowed.', origin) - return {} - else: - log.debug('Valid origin "%s", injecting CORS headers.', origin) - return {'Access-Control-Allow-Origin': origin} diff --git a/lib/oauthlib/oauth2/rfc6749/grant_types/base.py b/lib/oauthlib/oauth2/rfc6749/grant_types/base.py index a64f168c..ca343a11 100644 --- a/lib/oauthlib/oauth2/rfc6749/grant_types/base.py +++ b/lib/oauthlib/oauth2/rfc6749/grant_types/base.py @@ -10,6 +10,7 @@ from oauthlib.oauth2.rfc6749 import errors, utils from oauthlib.uri_validate import is_absolute_uri from ..request_validator import RequestValidator +from ..utils import is_secure_transport log = logging.getLogger(__name__) @@ -248,3 +249,20 @@ class GrantTypeBase: raise errors.MissingRedirectURIError(request=request) if not is_absolute_uri(request.redirect_uri): raise errors.InvalidRedirectURIError(request=request) + + def _create_cors_headers(self, request): + """If CORS is allowed, create the appropriate headers.""" + if 'origin' not in request.headers: + return {} + + origin = request.headers['origin'] + if not is_secure_transport(origin): + log.debug('Origin "%s" is not HTTPS, CORS not allowed.', origin) + return {} + elif not self.request_validator.is_origin_allowed( + request.client_id, origin, request): + log.debug('Invalid origin "%s", CORS not allowed.', origin) + return {} + else: + log.debug('Valid origin "%s", injecting CORS headers.', origin) + return {'Access-Control-Allow-Origin': origin} diff --git a/lib/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py b/lib/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py index f801de4a..ce33df0e 100644 --- a/lib/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py +++ b/lib/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py @@ -69,6 +69,7 @@ class RefreshTokenGrant(GrantTypeBase): log.debug('Issuing new token to client id %r (%r), %r.', request.client_id, request.client, token) + headers.update(self._create_cors_headers(request)) return headers, json.dumps(token), 200 def validate_token_request(self, request): diff --git a/lib/oauthlib/oauth2/rfc6749/parameters.py b/lib/oauthlib/oauth2/rfc6749/parameters.py index 44738bb4..8f6ce2c7 100644 --- a/lib/oauthlib/oauth2/rfc6749/parameters.py +++ b/lib/oauthlib/oauth2/rfc6749/parameters.py @@ -45,7 +45,7 @@ def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None, back to the client. The parameter SHOULD be used for preventing cross-site request forgery as described in `Section 10.12`_. - :param code_challenge: PKCE paramater. A challenge derived from the + :param code_challenge: PKCE parameter. A challenge derived from the code_verifier that is sent in the authorization request, to be verified against later. :param code_challenge_method: PKCE parameter. A method that was used to derive the diff --git a/lib/oauthlib/oauth2/rfc6749/request_validator.py b/lib/oauthlib/oauth2/rfc6749/request_validator.py index 610a708d..3910c0b9 100644 --- a/lib/oauthlib/oauth2/rfc6749/request_validator.py +++ b/lib/oauthlib/oauth2/rfc6749/request_validator.py @@ -191,6 +191,7 @@ class RequestValidator: claims associated, or `None` in case the token is unknown. Below the list of registered claims you should be interested in: + - scope : space-separated list of scopes - client_id : client identifier - username : human-readable identifier for the resource owner @@ -204,10 +205,10 @@ class RequestValidator: - jti : string identifier for the token Note that most of them are coming directly from JWT RFC. More details - can be found in `Introspect Claims`_ or `_JWT Claims`_. + can be found in `Introspect Claims`_ or `JWT Claims`_. The implementation can use *token_type_hint* to improve lookup - efficency, but must fallback to other types to be compliant with RFC. + efficiency, but must fallback to other types to be compliant with RFC. The dict of claims is added to request.token after this method. @@ -443,6 +444,7 @@ class RequestValidator: - request.user - request.scopes - request.claims (if given) + OBS! The request.user attribute should be set to the resource owner associated with this authorization code. Similarly request.scopes must also be set. @@ -451,6 +453,7 @@ class RequestValidator: If PKCE is enabled (see 'is_pkce_required' and 'save_authorization_code') you MUST set the following based on the information stored: + - request.code_challenge - request.code_challenge_method @@ -561,7 +564,7 @@ class RequestValidator: OBS! The validation should also set the user attribute of the request to a valid resource owner, i.e. request.user = username or similar. If not set you will be unable to associate a token with a user in the - persistance method used (commonly, save_bearer_token). + persistence method used (commonly, save_bearer_token). :param username: Unicode username. :param password: Unicode password. @@ -671,6 +674,7 @@ class RequestValidator: Method is used by: - Authorization Code Grant + - Refresh Token Grant """ return False diff --git a/lib/oauthlib/oauth2/rfc6749/tokens.py b/lib/oauthlib/oauth2/rfc6749/tokens.py index 6284248d..0757d07e 100644 --- a/lib/oauthlib/oauth2/rfc6749/tokens.py +++ b/lib/oauthlib/oauth2/rfc6749/tokens.py @@ -257,6 +257,7 @@ def get_token_from_header(request): class TokenBase: + __slots__ = () def __call__(self, request, refresh_token=False): raise NotImplementedError('Subclasses must implement this method.') diff --git a/lib/oauthlib/oauth2/rfc8628/clients/device.py b/lib/oauthlib/oauth2/rfc8628/clients/device.py index 95c4f5a2..b9ba2150 100644 --- a/lib/oauthlib/oauth2/rfc8628/clients/device.py +++ b/lib/oauthlib/oauth2/rfc8628/clients/device.py @@ -5,12 +5,11 @@ oauthlib.oauth2.rfc8628 This module is an implementation of various logic needed for consuming and providing OAuth 2.0 Device Authorization RFC8628. """ - +from oauthlib.common import add_params_to_uri from oauthlib.oauth2 import BackendApplicationClient, Client from oauthlib.oauth2.rfc6749.errors import InsecureTransportError from oauthlib.oauth2.rfc6749.parameters import prepare_token_request from oauthlib.oauth2.rfc6749.utils import is_secure_transport, list_to_scope -from oauthlib.common import add_params_to_uri class DeviceClient(Client): @@ -62,7 +61,7 @@ class DeviceClient(Client): body. :param body: Existing request body (URL encoded string) to embed parameters - into. This may contain extra paramters. Default ''. + into. This may contain extra parameters. Default ''. :param scope: The scope of the access request as described by `Section 3.3`_. @@ -84,6 +83,8 @@ class DeviceClient(Client): >>> client.prepare_request_body(scope=['hello', 'world']) 'grant_type=urn:ietf:params:oauth:grant-type:device_code&scope=hello+world' + .. _`Section 3.2.1`: https://datatracker.ietf.org/doc/html/rfc6749#section-3.2.1 + .. _`Section 3.3`: https://datatracker.ietf.org/doc/html/rfc6749#section-3.3 .. _`Section 3.4`: https://datatracker.ietf.org/doc/html/rfc8628#section-3.4 """ diff --git a/lib/oauthlib/openid/connect/core/endpoints/userinfo.py b/lib/oauthlib/openid/connect/core/endpoints/userinfo.py index 1c29cc55..7aa2bbe9 100644 --- a/lib/oauthlib/openid/connect/core/endpoints/userinfo.py +++ b/lib/oauthlib/openid/connect/core/endpoints/userinfo.py @@ -69,7 +69,7 @@ class UserInfoEndpoint(BaseEndpoint): 5.3.1. UserInfo Request The Client sends the UserInfo Request using either HTTP GET or HTTP POST. The Access Token obtained from an OpenID Connect Authentication - Request MUST be sent as a Bearer Token, per Section 2 of OAuth 2.0 + Request MUST be sent as a Bearer Token, per `Section 2`_ of OAuth 2.0 Bearer Token Usage [RFC6750]. It is RECOMMENDED that the request use the HTTP GET method and the @@ -77,21 +77,28 @@ class UserInfoEndpoint(BaseEndpoint): The following is a non-normative example of a UserInfo Request: - GET /userinfo HTTP/1.1 - Host: server.example.com - Authorization: Bearer SlAV32hkKG + .. code-block:: http + + GET /userinfo HTTP/1.1 + Host: server.example.com + Authorization: Bearer SlAV32hkKG 5.3.3. UserInfo Error Response When an error condition occurs, the UserInfo Endpoint returns an Error - Response as defined in Section 3 of OAuth 2.0 Bearer Token Usage + Response as defined in `Section 3`_ of OAuth 2.0 Bearer Token Usage [RFC6750]. (HTTP errors unrelated to RFC 6750 are returned to the User Agent using the appropriate HTTP status code.) The following is a non-normative example of a UserInfo Error Response: - HTTP/1.1 401 Unauthorized - WWW-Authenticate: Bearer error="invalid_token", + .. code-block:: http + + HTTP/1.1 401 Unauthorized + WWW-Authenticate: Bearer error="invalid_token", error_description="The Access Token expired" + + .. _`Section 2`: https://datatracker.ietf.org/doc/html/rfc6750#section-2 + .. _`Section 3`: https://datatracker.ietf.org/doc/html/rfc6750#section-3 """ if not self.bearer.validate_request(request): raise errors.InvalidTokenError() diff --git a/lib/oauthlib/openid/connect/core/grant_types/base.py b/lib/oauthlib/openid/connect/core/grant_types/base.py index 76173e6c..33411dad 100644 --- a/lib/oauthlib/openid/connect/core/grant_types/base.py +++ b/lib/oauthlib/openid/connect/core/grant_types/base.py @@ -8,7 +8,6 @@ from oauthlib.oauth2.rfc6749.errors import ( ConsentRequired, InvalidRequestError, LoginRequired, ) - log = logging.getLogger(__name__) diff --git a/lib/oauthlib/openid/connect/core/grant_types/dispatchers.py b/lib/oauthlib/openid/connect/core/grant_types/dispatchers.py index 2734c387..5aa7d469 100644 --- a/lib/oauthlib/openid/connect/core/grant_types/dispatchers.py +++ b/lib/oauthlib/openid/connect/core/grant_types/dispatchers.py @@ -84,7 +84,7 @@ class AuthorizationTokenGrantDispatcher(Dispatcher): code = parameters.get('code', None) redirect_uri = parameters.get('redirect_uri', None) - # If code is not pressent fallback to `default_grant` which will + # If code is not present fallback to `default_grant` which will # raise an error for the missing `code` in `create_token_response` step. if code: scopes = self.request_validator.get_authorization_code_scopes(client_id, code, redirect_uri, request) diff --git a/lib/oauthlib/openid/connect/core/tokens.py b/lib/oauthlib/openid/connect/core/tokens.py index a312e2d2..936ab52e 100644 --- a/lib/oauthlib/openid/connect/core/tokens.py +++ b/lib/oauthlib/openid/connect/core/tokens.py @@ -4,7 +4,9 @@ authlib.openid.connect.core.tokens This module contains methods for adding JWT tokens to requests. """ -from oauthlib.oauth2.rfc6749.tokens import TokenBase, random_token_generator, get_token_from_header +from oauthlib.oauth2.rfc6749.tokens import ( + TokenBase, get_token_from_header, random_token_generator, +) class JWTToken(TokenBase): diff --git a/lib/oauthlib/uri_validate.py b/lib/oauthlib/uri_validate.py index 8a6d9c27..a6fe0fb2 100644 --- a/lib/oauthlib/uri_validate.py +++ b/lib/oauthlib/uri_validate.py @@ -66,7 +66,7 @@ IPv4address = r"%(dec_octet)s \. %(dec_octet)s \. %(dec_octet)s \. %(dec_octet)s ) # IPv6address -IPv6address = r"([A-Fa-f0-9:]+:+)+[A-Fa-f0-9]+" +IPv6address = r"([A-Fa-f0-9:]+[:$])[A-Fa-f0-9]{1,4}" # IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) IPvFuture = r"v %(HEXDIG)s+ \. (?: %(unreserved)s | %(sub_delims)s | : )+" % locals() diff --git a/lib/requests_oauthlib/__init__.py b/lib/requests_oauthlib/__init__.py index 0d3e49f9..865d72fb 100644 --- a/lib/requests_oauthlib/__init__.py +++ b/lib/requests_oauthlib/__init__.py @@ -1,3 +1,4 @@ +# ruff: noqa: F401 import logging from .oauth1_auth import OAuth1 @@ -5,7 +6,7 @@ from .oauth1_session import OAuth1Session from .oauth2_auth import OAuth2 from .oauth2_session import OAuth2Session, TokenUpdated -__version__ = "1.3.1" +__version__ = "2.0.0" import requests diff --git a/lib/requests_oauthlib/compliance_fixes/__init__.py b/lib/requests_oauthlib/compliance_fixes/__init__.py index 0e8e3ac8..8815ea0b 100644 --- a/lib/requests_oauthlib/compliance_fixes/__init__.py +++ b/lib/requests_oauthlib/compliance_fixes/__init__.py @@ -1,5 +1,4 @@ -from __future__ import absolute_import - +# ruff: noqa: F401 from .facebook import facebook_compliance_fix from .fitbit import fitbit_compliance_fix from .slack import slack_compliance_fix diff --git a/lib/requests_oauthlib/compliance_fixes/douban.py b/lib/requests_oauthlib/compliance_fixes/douban.py index ecc57b08..c8b99c72 100644 --- a/lib/requests_oauthlib/compliance_fixes/douban.py +++ b/lib/requests_oauthlib/compliance_fixes/douban.py @@ -1,14 +1,12 @@ import json -from oauthlib.common import to_unicode - def douban_compliance_fix(session): def fix_token_type(r): token = json.loads(r.text) token.setdefault("token_type", "Bearer") fixed_token = json.dumps(token) - r._content = to_unicode(fixed_token).encode("utf-8") + r._content = fixed_token.encode() return r session._client_default_token_placement = "query" diff --git a/lib/requests_oauthlib/compliance_fixes/ebay.py b/lib/requests_oauthlib/compliance_fixes/ebay.py index 4aa423b3..ef33f391 100644 --- a/lib/requests_oauthlib/compliance_fixes/ebay.py +++ b/lib/requests_oauthlib/compliance_fixes/ebay.py @@ -1,5 +1,4 @@ import json -from oauthlib.common import to_unicode def ebay_compliance_fix(session): @@ -13,7 +12,7 @@ def ebay_compliance_fix(session): if token.get("token_type") in ["Application Access Token", "User Access Token"]: token["token_type"] = "Bearer" fixed_token = json.dumps(token) - response._content = to_unicode(fixed_token).encode("utf-8") + response._content = fixed_token.encode() return response diff --git a/lib/requests_oauthlib/compliance_fixes/facebook.py b/lib/requests_oauthlib/compliance_fixes/facebook.py index 90e79212..f44558a8 100644 --- a/lib/requests_oauthlib/compliance_fixes/facebook.py +++ b/lib/requests_oauthlib/compliance_fixes/facebook.py @@ -1,11 +1,5 @@ from json import dumps - -try: - from urlparse import parse_qsl -except ImportError: - from urllib.parse import parse_qsl - -from oauthlib.common import to_unicode +from urllib.parse import parse_qsl def facebook_compliance_fix(session): @@ -26,7 +20,7 @@ def facebook_compliance_fix(session): if expires is not None: token["expires_in"] = expires token["token_type"] = "Bearer" - r._content = to_unicode(dumps(token)).encode("UTF-8") + r._content = dumps(token).encode() return r session.register_compliance_hook("access_token_response", _compliance_fix) diff --git a/lib/requests_oauthlib/compliance_fixes/fitbit.py b/lib/requests_oauthlib/compliance_fixes/fitbit.py index 7e627024..aacc68bf 100644 --- a/lib/requests_oauthlib/compliance_fixes/fitbit.py +++ b/lib/requests_oauthlib/compliance_fixes/fitbit.py @@ -8,8 +8,6 @@ MissingTokenError. from json import loads, dumps -from oauthlib.common import to_unicode - def fitbit_compliance_fix(session): def _missing_error(r): @@ -17,7 +15,7 @@ def fitbit_compliance_fix(session): if "errors" in token: # Set the error to the first one we have token["error"] = token["errors"][0]["errorType"] - r._content = to_unicode(dumps(token)).encode("UTF-8") + r._content = dumps(token).encode() return r session.register_compliance_hook("access_token_response", _missing_error) diff --git a/lib/requests_oauthlib/compliance_fixes/instagram.py b/lib/requests_oauthlib/compliance_fixes/instagram.py index 4e07fe08..7d5a2ad4 100644 --- a/lib/requests_oauthlib/compliance_fixes/instagram.py +++ b/lib/requests_oauthlib/compliance_fixes/instagram.py @@ -1,7 +1,4 @@ -try: - from urlparse import urlparse, parse_qs -except ImportError: - from urllib.parse import urlparse, parse_qs +from urllib.parse import urlparse, parse_qs from oauthlib.common import add_params_to_uri diff --git a/lib/requests_oauthlib/compliance_fixes/mailchimp.py b/lib/requests_oauthlib/compliance_fixes/mailchimp.py index c69ce9fd..0d602659 100644 --- a/lib/requests_oauthlib/compliance_fixes/mailchimp.py +++ b/lib/requests_oauthlib/compliance_fixes/mailchimp.py @@ -1,21 +1,19 @@ import json -from oauthlib.common import to_unicode - def mailchimp_compliance_fix(session): def _null_scope(r): token = json.loads(r.text) if "scope" in token and token["scope"] is None: token.pop("scope") - r._content = to_unicode(json.dumps(token)).encode("utf-8") + r._content = json.dumps(token).encode() return r def _non_zero_expiration(r): token = json.loads(r.text) if "expires_in" in token and token["expires_in"] == 0: token["expires_in"] = 3600 - r._content = to_unicode(json.dumps(token)).encode("utf-8") + r._content = json.dumps(token).encode() return r session.register_compliance_hook("access_token_response", _null_scope) diff --git a/lib/requests_oauthlib/compliance_fixes/plentymarkets.py b/lib/requests_oauthlib/compliance_fixes/plentymarkets.py index 9f605f05..859f0566 100644 --- a/lib/requests_oauthlib/compliance_fixes/plentymarkets.py +++ b/lib/requests_oauthlib/compliance_fixes/plentymarkets.py @@ -1,8 +1,6 @@ from json import dumps, loads import re -from oauthlib.common import to_unicode - def plentymarkets_compliance_fix(session): def _to_snake_case(n): @@ -22,7 +20,7 @@ def plentymarkets_compliance_fix(session): for k, v in token.items(): fixed_token[_to_snake_case(k)] = v - r._content = to_unicode(dumps(fixed_token)).encode("UTF-8") + r._content = dumps(fixed_token).encode() return r session.register_compliance_hook("access_token_response", _compliance_fix) diff --git a/lib/requests_oauthlib/compliance_fixes/slack.py b/lib/requests_oauthlib/compliance_fixes/slack.py index 3f574b03..9095a470 100644 --- a/lib/requests_oauthlib/compliance_fixes/slack.py +++ b/lib/requests_oauthlib/compliance_fixes/slack.py @@ -1,7 +1,4 @@ -try: - from urlparse import urlparse, parse_qs -except ImportError: - from urllib.parse import urlparse, parse_qs +from urllib.parse import urlparse, parse_qs from oauthlib.common import add_params_to_uri diff --git a/lib/requests_oauthlib/compliance_fixes/weibo.py b/lib/requests_oauthlib/compliance_fixes/weibo.py index 6733abeb..f1623fd6 100644 --- a/lib/requests_oauthlib/compliance_fixes/weibo.py +++ b/lib/requests_oauthlib/compliance_fixes/weibo.py @@ -1,13 +1,11 @@ from json import loads, dumps -from oauthlib.common import to_unicode - def weibo_compliance_fix(session): def _missing_token_type(r): token = loads(r.text) token["token_type"] = "Bearer" - r._content = to_unicode(dumps(token)).encode("UTF-8") + r._content = dumps(token).encode() return r session._client.default_token_placement = "query" diff --git a/lib/requests_oauthlib/oauth1_auth.py b/lib/requests_oauthlib/oauth1_auth.py index cfbbd590..f8c0bd6e 100644 --- a/lib/requests_oauthlib/oauth1_auth.py +++ b/lib/requests_oauthlib/oauth1_auth.py @@ -1,20 +1,15 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - import logging from oauthlib.common import extract_params from oauthlib.oauth1 import Client, SIGNATURE_HMAC, SIGNATURE_TYPE_AUTH_HEADER from oauthlib.oauth1 import SIGNATURE_TYPE_BODY -from requests.compat import is_py3 from requests.utils import to_native_string from requests.auth import AuthBase CONTENT_TYPE_FORM_URLENCODED = "application/x-www-form-urlencoded" CONTENT_TYPE_MULTI_PART = "multipart/form-data" -if is_py3: - unicode = str log = logging.getLogger(__name__) @@ -83,7 +78,7 @@ class OAuth1(AuthBase): or self.client.signature_type == SIGNATURE_TYPE_BODY ): content_type = CONTENT_TYPE_FORM_URLENCODED - if not isinstance(content_type, unicode): + if not isinstance(content_type, str): content_type = content_type.decode("utf-8") is_form_encoded = CONTENT_TYPE_FORM_URLENCODED in content_type @@ -96,17 +91,17 @@ class OAuth1(AuthBase): if is_form_encoded: r.headers["Content-Type"] = CONTENT_TYPE_FORM_URLENCODED r.url, headers, r.body = self.client.sign( - unicode(r.url), unicode(r.method), r.body or "", r.headers + str(r.url), str(r.method), r.body or "", r.headers ) elif self.force_include_body: # To allow custom clients to work on non form encoded bodies. r.url, headers, r.body = self.client.sign( - unicode(r.url), unicode(r.method), r.body or "", r.headers + str(r.url), str(r.method), r.body or "", r.headers ) else: # Omit body data in the signing of non form-encoded requests r.url, headers, _ = self.client.sign( - unicode(r.url), unicode(r.method), None, r.headers + str(r.url), str(r.method), None, r.headers ) r.prepare_headers(headers) diff --git a/lib/requests_oauthlib/oauth1_session.py b/lib/requests_oauthlib/oauth1_session.py index 88f2853c..7625c808 100644 --- a/lib/requests_oauthlib/oauth1_session.py +++ b/lib/requests_oauthlib/oauth1_session.py @@ -1,9 +1,4 @@ -from __future__ import unicode_literals - -try: - from urlparse import urlparse -except ImportError: - from urllib.parse import urlparse +from urllib.parse import urlparse import logging @@ -85,7 +80,7 @@ class OAuth1Session(requests.Session): 'https://api.twitter.com/oauth/authorize?oauth_token=sdf0o9823sjdfsdf&oauth_callback=https%3A%2F%2F127.0.0.1%2Fcallback' >>> >>> # Third step. Fetch the access token - >>> redirect_response = raw_input('Paste the full redirect URL here.') + >>> redirect_response = input('Paste the full redirect URL here.') >>> oauth_session.parse_authorization_response(redirect_response) { 'oauth_token: 'kjerht2309u', @@ -258,7 +253,7 @@ class OAuth1Session(requests.Session): return add_params_to_uri(url, kwargs.items()) def fetch_request_token(self, url, realm=None, **request_kwargs): - r"""Fetch a request token. + """Fetch a request token. This is the first step in the OAuth 1 workflow. A request token is obtained by making a signed post request to url. The token is then @@ -267,7 +262,7 @@ class OAuth1Session(requests.Session): :param url: The request token endpoint URL. :param realm: A list of realms to request access to. - :param \*\*request_kwargs: Optional arguments passed to ''post'' + :param request_kwargs: Optional arguments passed to ''post'' function in ''requests.Session'' :returns: The response in dict format. diff --git a/lib/requests_oauthlib/oauth2_auth.py b/lib/requests_oauthlib/oauth2_auth.py index b880f72f..f19f52ac 100644 --- a/lib/requests_oauthlib/oauth2_auth.py +++ b/lib/requests_oauthlib/oauth2_auth.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals from oauthlib.oauth2 import WebApplicationClient, InsecureTransportError from oauthlib.oauth2 import is_secure_transport from requests.auth import AuthBase diff --git a/lib/requests_oauthlib/oauth2_session.py b/lib/requests_oauthlib/oauth2_session.py index db446808..93cc4d7b 100644 --- a/lib/requests_oauthlib/oauth2_session.py +++ b/lib/requests_oauthlib/oauth2_session.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import logging from oauthlib.common import generate_token, urldecode @@ -46,6 +44,7 @@ class OAuth2Session(requests.Session): token=None, state=None, token_updater=None, + pkce=None, **kwargs ): """Construct a new OAuth 2 client session. @@ -72,18 +71,23 @@ class OAuth2Session(requests.Session): set a TokenUpdated warning will be raised when a token has been refreshed. This warning will carry the token in its token argument. + :param pkce: Set "S256" or "plain" to enable PKCE. Default is disabled. :param kwargs: Arguments to pass to the Session constructor. """ super(OAuth2Session, self).__init__(**kwargs) self._client = client or WebApplicationClient(client_id, token=token) self.token = token or {} - self.scope = scope + self._scope = scope self.redirect_uri = redirect_uri self.state = state or generate_token self._state = state self.auto_refresh_url = auto_refresh_url self.auto_refresh_kwargs = auto_refresh_kwargs or {} self.token_updater = token_updater + self._pkce = pkce + + if self._pkce not in ["S256", "plain", None]: + raise AttributeError("Wrong value for {}(.., pkce={})".format(self.__class__, self._pkce)) # Ensure that requests doesn't do any automatic auth. See #278. # The default behavior can be re-enabled by setting auth to None. @@ -95,8 +99,24 @@ class OAuth2Session(requests.Session): "access_token_response": set(), "refresh_token_response": set(), "protected_request": set(), + "refresh_token_request": set(), + "access_token_request": set(), } + @property + def scope(self): + """By default the scope from the client is used, except if overridden""" + if self._scope is not None: + return self._scope + elif self._client is not None: + return self._client.scope + else: + return None + + @scope.setter + def scope(self, scope): + self._scope = scope + def new_state(self): """Generates a state string to be used in authorizations.""" try: @@ -161,6 +181,13 @@ class OAuth2Session(requests.Session): :return: authorization_url, state """ state = state or self.new_state() + if self._pkce: + self._code_verifier = self._client.create_code_verifier(43) + kwargs["code_challenge_method"] = self._pkce + kwargs["code_challenge"] = self._client.create_code_challenge( + code_verifier=self._code_verifier, + code_challenge_method=self._pkce + ) return ( self._client.prepare_request_uri( url, @@ -185,7 +212,7 @@ class OAuth2Session(requests.Session): force_querystring=False, timeout=None, headers=None, - verify=True, + verify=None, proxies=None, include_client_id=None, client_secret=None, @@ -252,6 +279,13 @@ class OAuth2Session(requests.Session): "Please supply either code or " "authorization_response parameters." ) + if self._pkce: + if self._code_verifier is None: + raise ValueError( + "Code verifier is not found, authorization URL must be generated before" + ) + kwargs["code_verifier"] = self._code_verifier + # Earlier versions of this library build an HTTPBasicAuth header out of # `username` and `password`. The RFC states, however these attributes # must be in the request body and not the header. @@ -325,7 +359,7 @@ class OAuth2Session(requests.Session): headers = headers or { "Accept": "application/json", - "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8", + "Content-Type": "application/x-www-form-urlencoded", } self.token = {} request_kwargs = {} @@ -338,6 +372,12 @@ class OAuth2Session(requests.Session): else: raise ValueError("The method kwarg must be POST or GET.") + for hook in self.compliance_hook["access_token_request"]: + log.debug("Invoking access_token_request hook %s.", hook) + token_url, headers, request_kwargs = hook( + token_url, headers, request_kwargs + ) + r = self.request( method=method, url=token_url, @@ -388,7 +428,7 @@ class OAuth2Session(requests.Session): auth=None, timeout=None, headers=None, - verify=True, + verify=None, proxies=None, **kwargs ): @@ -426,9 +466,13 @@ class OAuth2Session(requests.Session): if headers is None: headers = { "Accept": "application/json", - "Content-Type": ("application/x-www-form-urlencoded;charset=UTF-8"), + "Content-Type": ("application/x-www-form-urlencoded"), } + for hook in self.compliance_hook["refresh_token_request"]: + log.debug("Invoking refresh_token_request hook %s.", hook) + token_url, headers, body = hook(token_url, headers, body) + r = self.post( token_url, data=dict(urldecode(body)), @@ -450,7 +494,7 @@ class OAuth2Session(requests.Session): r = hook(r) self.token = self._client.parse_request_body_response(r.text, scope=self.scope) - if not "refresh_token" in self.token: + if "refresh_token" not in self.token: log.debug("No new refresh token given. Re-using old.") self.token["refresh_token"] = refresh_token return self.token @@ -464,6 +508,7 @@ class OAuth2Session(requests.Session): withhold_token=False, client_id=None, client_secret=None, + files=None, **kwargs ): """Intercept all requests and add the OAuth 2 token if present.""" @@ -519,7 +564,7 @@ class OAuth2Session(requests.Session): log.debug("Supplying headers %s and data %s", headers, data) log.debug("Passing through key word arguments %s.", kwargs) return super(OAuth2Session, self).request( - method, url, headers=headers, data=data, **kwargs + method, url, headers=headers, data=data, files=files, **kwargs ) def register_compliance_hook(self, hook_type, hook): @@ -529,6 +574,8 @@ class OAuth2Session(requests.Session): access_token_response invoked before token parsing. refresh_token_response invoked before refresh token parsing. protected_request invoked before making a request. + access_token_request invoked before making a token fetch request. + refresh_token_request invoked before making a refresh request. If you find a new hook is needed please send a GitHub PR request or open an issue. diff --git a/requirements.txt b/requirements.txt index fcea2c1c..f697ecc8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,7 +37,7 @@ python-dateutil==2.9.0.post0 python-twitter==3.5 pytz==2024.1 requests==2.31.0 -requests-oauthlib==1.3.1 +requests-oauthlib==2.0.0 rumps==0.4.0; platform_system == "Darwin" simplejson==3.19.2 six==1.16.0 From 1d96e0f8599609f134d6f4e64d358fa2e0fe0a10 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 30 Mar 2024 15:28:14 -0700 Subject: [PATCH 6/6] Bump tzdata from 2023.3 to 2024.1 (#2295) * Bump tzdata from 2023.3 to 2024.1 Bumps [tzdata](https://github.com/python/tzdata) from 2023.3 to 2024.1. - [Release notes](https://github.com/python/tzdata/releases) - [Changelog](https://github.com/python/tzdata/blob/master/NEWS.md) - [Commits](https://github.com/python/tzdata/compare/2023.3...2024.1) --- updated-dependencies: - dependency-name: tzdata dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Update tzdata==2024.1 --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> [skip ci] --- lib/tzdata/__init__.py | 4 +- lib/tzdata/zoneinfo/America/Ensenada | Bin 1025 -> 1025 bytes lib/tzdata/zoneinfo/America/Godthab | Bin 965 -> 965 bytes lib/tzdata/zoneinfo/America/Goose_Bay | Bin 1580 -> 1580 bytes lib/tzdata/zoneinfo/America/Indiana/Winamac | Bin 612 -> 603 bytes lib/tzdata/zoneinfo/America/Matamoros | Bin 437 -> 437 bytes lib/tzdata/zoneinfo/America/Metlakatla | Bin 595 -> 586 bytes lib/tzdata/zoneinfo/America/Miquelon | Bin 550 -> 550 bytes lib/tzdata/zoneinfo/America/Moncton | Bin 1493 -> 1493 bytes lib/tzdata/zoneinfo/America/Montreal | Bin 1717 -> 1717 bytes lib/tzdata/zoneinfo/America/Nassau | Bin 1717 -> 1717 bytes lib/tzdata/zoneinfo/America/Nipigon | Bin 1717 -> 1717 bytes lib/tzdata/zoneinfo/America/Nuuk | Bin 965 -> 965 bytes lib/tzdata/zoneinfo/America/Ojinaga | Bin 709 -> 718 bytes lib/tzdata/zoneinfo/America/Santa_Isabel | Bin 1025 -> 1025 bytes lib/tzdata/zoneinfo/America/Scoresbysund | Bin 479 -> 984 bytes lib/tzdata/zoneinfo/America/St_Johns | Bin 1878 -> 1878 bytes lib/tzdata/zoneinfo/America/Thunder_Bay | Bin 1717 -> 1717 bytes lib/tzdata/zoneinfo/America/Tijuana | Bin 1025 -> 1025 bytes lib/tzdata/zoneinfo/America/Toronto | Bin 1717 -> 1717 bytes lib/tzdata/zoneinfo/Antarctica/Casey | Bin 243 -> 287 bytes lib/tzdata/zoneinfo/Antarctica/Macquarie | Bin 976 -> 976 bytes lib/tzdata/zoneinfo/Antarctica/Troll | Bin 177 -> 158 bytes lib/tzdata/zoneinfo/Antarctica/Vostok | Bin 133 -> 170 bytes lib/tzdata/zoneinfo/Asia/Almaty | Bin 609 -> 618 bytes lib/tzdata/zoneinfo/Asia/Gaza | Bin 2518 -> 2950 bytes lib/tzdata/zoneinfo/Asia/Hebron | Bin 2536 -> 2968 bytes lib/tzdata/zoneinfo/Asia/Ho_Chi_Minh | Bin 236 -> 236 bytes lib/tzdata/zoneinfo/Asia/Nicosia | Bin 597 -> 597 bytes lib/tzdata/zoneinfo/Asia/Qostanay | Bin 615 -> 624 bytes lib/tzdata/zoneinfo/Asia/Saigon | Bin 236 -> 236 bytes lib/tzdata/zoneinfo/Canada/Eastern | Bin 1717 -> 1717 bytes lib/tzdata/zoneinfo/Canada/Newfoundland | Bin 1878 -> 1878 bytes lib/tzdata/zoneinfo/Europe/Belfast | Bin 1599 -> 1599 bytes lib/tzdata/zoneinfo/Europe/Bucharest | Bin 661 -> 661 bytes lib/tzdata/zoneinfo/Europe/Chisinau | Bin 755 -> 755 bytes lib/tzdata/zoneinfo/Europe/Guernsey | Bin 1599 -> 1599 bytes lib/tzdata/zoneinfo/Europe/Isle_of_Man | Bin 1599 -> 1599 bytes lib/tzdata/zoneinfo/Europe/Jersey | Bin 1599 -> 1599 bytes lib/tzdata/zoneinfo/Europe/Kiev | Bin 558 -> 558 bytes lib/tzdata/zoneinfo/Europe/Kyiv | Bin 558 -> 558 bytes lib/tzdata/zoneinfo/Europe/London | Bin 1599 -> 1599 bytes lib/tzdata/zoneinfo/Europe/Nicosia | Bin 597 -> 597 bytes lib/tzdata/zoneinfo/Europe/Riga | Bin 694 -> 694 bytes lib/tzdata/zoneinfo/Europe/Sofia | Bin 592 -> 592 bytes lib/tzdata/zoneinfo/Europe/Tallinn | Bin 675 -> 675 bytes lib/tzdata/zoneinfo/Europe/Tiraspol | Bin 755 -> 755 bytes lib/tzdata/zoneinfo/Europe/Uzhgorod | Bin 558 -> 558 bytes lib/tzdata/zoneinfo/Europe/Vilnius | Bin 676 -> 676 bytes lib/tzdata/zoneinfo/Europe/Zaporozhye | Bin 558 -> 558 bytes lib/tzdata/zoneinfo/GB | Bin 1599 -> 1599 bytes lib/tzdata/zoneinfo/GB-Eire | Bin 1599 -> 1599 bytes lib/tzdata/zoneinfo/Mexico/BajaNorte | Bin 1025 -> 1025 bytes lib/tzdata/zoneinfo/Pacific/Norfolk | Bin 247 -> 237 bytes lib/tzdata/zoneinfo/iso3166.tab | 17 +- lib/tzdata/zoneinfo/leapseconds | 19 +- lib/tzdata/zoneinfo/tzdata.zi | 3961 ++++++++++--------- lib/tzdata/zoneinfo/zone.tab | 24 +- lib/tzdata/zoneinfo/zone1970.tab | 29 +- lib/tzdata/zoneinfo/zonenow.tab | 303 ++ lib/tzdata/zones | 656 +-- requirements.txt | 2 +- 62 files changed, 2665 insertions(+), 2350 deletions(-) create mode 100644 lib/tzdata/zoneinfo/zonenow.tab diff --git a/lib/tzdata/__init__.py b/lib/tzdata/__init__.py index a7d6b0b5..b319ed55 100644 --- a/lib/tzdata/__init__.py +++ b/lib/tzdata/__init__.py @@ -1,6 +1,6 @@ # IANA versions like 2020a are not valid PEP 440 identifiers; the recommended # way to translate the version is to use YYYY.n where `n` is a 0-based index. -__version__ = "2023.3" +__version__ = "2024.1" # This exposes the original IANA version number. -IANA_VERSION = "2023c" +IANA_VERSION = "2024a" diff --git a/lib/tzdata/zoneinfo/America/Ensenada b/lib/tzdata/zoneinfo/America/Ensenada index e8be26b139e653e1b07a3db14ea34bd290441947..42087af4cceb049f1395cabaaa85b7c39253ed97 100644 GIT binary patch delta 21 ccmZqVXyn-7!^~{Ex?!?Eb1Wm%c2Oa*T+TaR8hG37`M~ diff --git a/lib/tzdata/zoneinfo/America/Matamoros b/lib/tzdata/zoneinfo/America/Matamoros index 88cabcd1528c678db4e1ab1218e9dd6c032ed901..993ac47559c154e52af8fba1bcd7c9d0e3bea880 100644 GIT binary patch delta 22 dcmdnWyp?%F3?s8`f5PNMMmZ)%#>w@JW&lq>1`PlJ delta 23 fcmdnWyp?%F3?uWL=?#+;8D*K77?~#5GnxSaTeSw4 diff --git a/lib/tzdata/zoneinfo/America/Metlakatla b/lib/tzdata/zoneinfo/America/Metlakatla index 9fefee388e6fa613f9a273f62ffcc020d44b72ab..71b0eab085dbbb48050d7b6a271ebb1a29fb1926 100644 GIT binary patch delta 20 ccmcc2a*Aa_7^C*a@C3%m`x&(-zh=w;08f$!h5!Hn delta 47 zcmX@ba+zg97^BX{@B~I41|W!Od1t`L#LP1JAfx2u3yf@>Z0sP&#Ku1P5n~1bDKZNy diff --git a/lib/tzdata/zoneinfo/America/Miquelon b/lib/tzdata/zoneinfo/America/Miquelon index 3b62585d0a13c9b35824ff7ecc91e29428c360e1..ba95cb0b3f755ab94154a54fd8cf6e6b9cdb0556 100644 GIT binary patch delta 14 VcmZ3+vW#Uy3iEw&jg9I1838A01wsG- delta 14 WcmZ3+vW#Uy3iCFL6&us{GXel9kOjH` diff --git a/lib/tzdata/zoneinfo/America/Moncton b/lib/tzdata/zoneinfo/America/Moncton index ecb69ef2c96a2a7f6fbf185882f060068979c642..020e33d976179e8f61a6040caaef3c8eb7f348bc 100644 GIT binary patch delta 26 icmcc0eU*E|8y1!s?GB8S1zANWv$9q*GEZ(~4FLdtP6!MD delta 26 icmcc0eU*E|8y1$&vmP)`7GxEj%*tBL$TYc;H3R^U@(CFL diff --git a/lib/tzdata/zoneinfo/America/Montreal b/lib/tzdata/zoneinfo/America/Montreal index fe6be8ea8c97db1c1d99c4198f4b9b024e3aba4c..668e70d765dc3fb0eda16fb0f1932af607b53412 100644 GIT binary patch delta 55 ycmdnWyOnptUq-ECAN~WuHHG~TK=gIf6=41ihJ|4MP0=ne|CaHjgw5(qeJlV<{U&Ar delta 55 ycmdnWyOnptUq-D}7ybjmHHF0oK=gIf9x(p~Lo1knQ#23EzhzwJuvwj{j|BiF#U%a! diff --git a/lib/tzdata/zoneinfo/America/Nassau b/lib/tzdata/zoneinfo/America/Nassau index fe6be8ea8c97db1c1d99c4198f4b9b024e3aba4c..668e70d765dc3fb0eda16fb0f1932af607b53412 100644 GIT binary patch delta 55 ycmdnWyOnptUq-ECAN~WuHHG~TK=gIf6=41ihJ|4MP0=ne|CaHjgw5(qeJlV<{U&Ar delta 55 ycmdnWyOnptUq-D}7ybjmHHF0oK=gIf9x(p~Lo1knQ#23EzhzwJuvwj{j|BiF#U%a! diff --git a/lib/tzdata/zoneinfo/America/Nipigon b/lib/tzdata/zoneinfo/America/Nipigon index fe6be8ea8c97db1c1d99c4198f4b9b024e3aba4c..668e70d765dc3fb0eda16fb0f1932af607b53412 100644 GIT binary patch delta 55 ycmdnWyOnptUq-ECAN~WuHHG~TK=gIf6=41ihJ|4MP0=ne|CaHjgw5(qeJlV<{U&Ar delta 55 ycmdnWyOnptUq-D}7ybjmHHF0oK=gIf9x(p~Lo1knQ#23EzhzwJuvwj{j|BiF#U%a! diff --git a/lib/tzdata/zoneinfo/America/Nuuk b/lib/tzdata/zoneinfo/America/Nuuk index 00b57bb13fbfa802e61f8b93d08622163a35a9a4..310774ea4fdd1798782a41f905d16e3548cd191e 100644 GIT binary patch delta 13 UcmX@gew2Mf9Wx{I8 zKg5U&Mi3S)ihc-NzpVN}?F$hkn!m(|$|x8WeIW{yijeC*ul)i3)aUrzi8+RHa-N`^$o!Ff&|U(4p_}LLW{`? zH*1WgC+FUu@_+%NZ%l?M!^-s{2dIjwx)3E-?1mvQ(ARm6okhzYb zEOdItp{wTtY^b~p8y&|AE)K&cV@f9Xn%?xIoBtj~w@mubn_maf?i&&48E=8!GwrZ- z)CAjNX4pQ`%#e9o{93dxYD90fd_;Hn7SVp~6gpsjiw=DI0Jmu#6`Xwvx96|H9hsX@ zn0X+R`Qo`Gx^wI*dS@bm?n<6Uckek1d*Xv|*QPj>gaGWV=!Co3+Je707%cNMWPb3) z3iRHE8g%IHPxQW=9^IE-4#U~6aR2Z;jHG7afk+PapZF}3c?VnXp$Eb<=xEhN^q?n$ zj_K~mbP-*w`5#Y_qI@|-QHqj(M-?l|kMFdOyeS>|GrB|&CCbJ*)Xh_M97pNhYIz;% VoATNtL@*F-n{XgVtt$P3f delta 74 zcmcb?exI2&Bq}q_XriFlLcQPBv7}^=yXd4*V V>G&G!nd%wn8|e5N8h}}bTmZxI5f%Ud diff --git a/lib/tzdata/zoneinfo/America/St_Johns b/lib/tzdata/zoneinfo/America/St_Johns index e5f2aec2bb2b56dbdb66f0c0a00ba243ed788d58..94d790baaccb72298bb577041cf3c8400339a7da 100644 GIT binary patch delta 29 lcmcb{ca3j@2^;hJ$Q6?v*`y~2uyIX}VVlaxJoyt_3ILka3CjQg delta 29 lcmcb{ca3j@2^;e^UXRI+Y|@hh*tjOguuWxTnf!?@1ptm*2`2ym diff --git a/lib/tzdata/zoneinfo/America/Thunder_Bay b/lib/tzdata/zoneinfo/America/Thunder_Bay index fe6be8ea8c97db1c1d99c4198f4b9b024e3aba4c..668e70d765dc3fb0eda16fb0f1932af607b53412 100644 GIT binary patch delta 55 ycmdnWyOnptUq-ECAN~WuHHG~TK=gIf6=41ihJ|4MP0=ne|CaHjgw5(qeJlV<{U&Ar delta 55 ycmdnWyOnptUq-D}7ybjmHHF0oK=gIf9x(p~Lo1knQ#23EzhzwJuvwj{j|BiF#U%a! diff --git a/lib/tzdata/zoneinfo/America/Tijuana b/lib/tzdata/zoneinfo/America/Tijuana index e8be26b139e653e1b07a3db14ea34bd290441947..42087af4cceb049f1395cabaaa85b7c39253ed97 100644 GIT binary patch delta 21 ccmZqVXyn-7!^~{Ex?!?Eb1Wm%et~_12{XIwet~_12{Ze24+cgiMuy3b%mI`Anb{{#VzvVSX(k5w diff --git a/lib/tzdata/zoneinfo/Antarctica/Troll b/lib/tzdata/zoneinfo/Antarctica/Troll index 4e31affb50e350376824492ff086867df62ae130..2359c44bd00ed44d2cdbf4f0aa0d9cea507814ed 100644 GIT binary patch delta 43 ncmdnUIFE5cm?9$s0|OHfa{w`e6R$f1NB{;{bPWt9cBumZa{UC( delta 62 zcmbQoxRG%}mR diff --git a/lib/tzdata/zoneinfo/Antarctica/Vostok b/lib/tzdata/zoneinfo/Antarctica/Vostok index 69ff7f6fb4973efb1185cad9f553f8c770c75934..4ce8f74784e970731f5f44b84f73780087890fa5 100644 GIT binary patch delta 91 zcmZo=T*WvcEQE!Dfq@x_dHw^z%ZSbfAe%wg*HQpP>uqfXQ4Fc|t_F+@j7(q-kVqKmmr31O^r#-w+0E12YCL8z5DOFWFim48m5B~h_Uh2cR$Dbm(XQ3NGQ;6y>9!=6f{Zpyb> zURt%xL+oH0gEGQR=bD?FZf?3M64W6oHYiwZ5F|exl{Gmu?;&p9`IDOrWrDr3Wp@OpWWUuCZxxxFK7?==*P!37 z?(+Ph1%A=JfxErDx}nD8zAz2HI?@V{Hn`!}+8_CQyM8&Y-N1aha~SXO)!l-9|6H)& zQXfKInMesdTRI8P9=7WK+a=T>;GW2#nQ9f9E4E-imuMBr!JaDY9l`OS zKKQ;L2R=_8g+t9jIP?Ia(9%hKVgAuR?867U;jndGzK$sT)&xg-e`6o{u?8>H55Nn< zc{qAyuRKi@^$pl%92CuO3N?uJWN={d)TSaWj@0RnjlfG6Kf>|KI2^zERQFh;P=mxV z7n~@(!+!a6qg_2L_j!bB^1u|FTyrW?YPUyP{+p>5t3KF%h65{8x8ap9mvZW1wYnhC nw7@<+`WQ}o3%awNaOVAWJKr@{vCH_Chm7SMGalmm#qIwAnGLshRN+fdY*g3t#!B-Sp3lc`CxMn v>s|tBV>oh_2h2apQVEtnwsWS3ir diff --git a/lib/tzdata/zoneinfo/Asia/Hebron b/lib/tzdata/zoneinfo/Asia/Hebron index fcf923bd283a4a6f21d789551c29ff5e2680108d..53a3c14312bc770bf9bca1d2e7518763fec7a485 100644 GIT binary patch delta 707 zcmXBQPe{{Y9LMo*{92o3mUxiDnSTxuS`ksZR2Us3l%#Zek|L;~1Wxo49rh5Cb!&XH z^wO+pJH!yn2+{~Q-PYRNbn{P}VI9h%WV5-&B7+rz$>43^$+-F9-k zZ9^-mHVBd*TUk{W&3TF2w*TU0L$OvHAhsFI+SYO6e%W|Rut(N&n>Z-jn;~A8ZFnfy zxWlYfogqFbn|V%LFB`Zd*e1*G1oz8!+K4@}XNv>}Ws7bJUYGq^O>8up&rODL7uOxX zUEO8)o&|o=wT8Pry}YKz!Ps$~DZVJDhlrujU5q`{#oF zmO`Nh{_{QX%&rUA&qNF1+1(TH>>;b}znwx20`8YtG+n7e!2%2R!DORQ4s};x?+A^D z^udpPIPiYr2pp~t!Qs0Ig%?iX3m+cr#XeHr1xKu(Q>a0_JB1RXaOP6+CA8#>1v!{%ktH0|m|h0VA%_Qvd(} delta 308 zcmbOs{z7;{7$e)p@VCsulBXF!AYDf31DO6$)G#@m3QxAleYuu>@5wGuLEhP7>3E;fwUxZ!(?$bS>|U24U^4* z^j-;u$>Bh{tCeALIgn1&X_!16NSj|_n7kcGvpO__%z~YDg&j*`x vSoac08^e*aJYfD&mP)YvvBjIg`i@kELctLbWOPc`j5miL4rvAFy&w?qtnjWSsns)e``rhzcVB diff --git a/lib/tzdata/zoneinfo/Europe/Bucharest b/lib/tzdata/zoneinfo/Europe/Bucharest index efa689ba9e0a782c9367f660a59060d9b1965345..c4a391e73b97e1342d352c5cc15a0bace202deef 100644 GIT binary patch delta 27 jcmbQrI+b;UFcYiM$({#{lNFfsCL1uZGcr&1V{!)oag7H= delta 25 hcmbQrI+b;UFcYh>?QQ|a$qG!mll7T6C;KtE0{~O_1{44Q diff --git a/lib/tzdata/zoneinfo/Europe/Chisinau b/lib/tzdata/zoneinfo/Europe/Chisinau index 6970b14c506c8119341052c2c142ac11b28a4917..9152e68594bb66cc756e0407654a203952fbd4e5 100644 GIT binary patch delta 22 ecmey&`k8fuH4}@`$({$3?U}3@SteIARR92Fc?abH delta 22 dcmey&`k8fuH4}@m?OKM(_Dt4{%#*8`Dgaed2ATi> diff --git a/lib/tzdata/zoneinfo/Europe/Guernsey b/lib/tzdata/zoneinfo/Europe/Guernsey index 323cd3818ac6f77b4394982ea5a342b9ed262777..b9e95d92623c6cc4c35b16f7ceba3006f5ce4934 100644 GIT binary patch delta 30 mcmdnbv!7=}Fe{6}a}|ckiL4rvAFy&w?qtnjWSabq)e``nbqVMI delta 30 mcmdnbv!7=}Fe{5;>`j5miL4rvAFy&w?qtnjWSsns)e``rhzcVB diff --git a/lib/tzdata/zoneinfo/Europe/Isle_of_Man b/lib/tzdata/zoneinfo/Europe/Isle_of_Man index 323cd3818ac6f77b4394982ea5a342b9ed262777..b9e95d92623c6cc4c35b16f7ceba3006f5ce4934 100644 GIT binary patch delta 30 mcmdnbv!7=}Fe{6}a}|ckiL4rvAFy&w?qtnjWSabq)e``nbqVMI delta 30 mcmdnbv!7=}Fe{5;>`j5miL4rvAFy&w?qtnjWSsns)e``rhzcVB diff --git a/lib/tzdata/zoneinfo/Europe/Jersey b/lib/tzdata/zoneinfo/Europe/Jersey index 323cd3818ac6f77b4394982ea5a342b9ed262777..b9e95d92623c6cc4c35b16f7ceba3006f5ce4934 100644 GIT binary patch delta 30 mcmdnbv!7=}Fe{6}a}|ckiL4rvAFy&w?qtnjWSabq)e``nbqVMI delta 30 mcmdnbv!7=}Fe{5;>`j5miL4rvAFy&w?qtnjWSsns)e``rhzcVB diff --git a/lib/tzdata/zoneinfo/Europe/Kiev b/lib/tzdata/zoneinfo/Europe/Kiev index 4e026859fdee32dd3f77a25274c5ccbebe0ee554..753a6c86f38586797589233f4528837f5b09151c 100644 GIT binary patch delta 22 ecmZ3-vW{iLWJVUlX@LQgr!%TCvQOT}SO5T4c?U26 delta 22 ecmZ3-vW{iLWJVUFqAdcGr!%TCGELscSO5T4eg`W6 diff --git a/lib/tzdata/zoneinfo/Europe/Kyiv b/lib/tzdata/zoneinfo/Europe/Kyiv index 4e026859fdee32dd3f77a25274c5ccbebe0ee554..753a6c86f38586797589233f4528837f5b09151c 100644 GIT binary patch delta 22 ecmZ3-vW{iLWJVUlX@LQgr!%TCvQOT}SO5T4c?U26 delta 22 ecmZ3-vW{iLWJVUFqAdcGr!%TCGELscSO5T4eg`W6 diff --git a/lib/tzdata/zoneinfo/Europe/London b/lib/tzdata/zoneinfo/Europe/London index 323cd3818ac6f77b4394982ea5a342b9ed262777..b9e95d92623c6cc4c35b16f7ceba3006f5ce4934 100644 GIT binary patch delta 30 mcmdnbv!7=}Fe{6}a}|ckiL4rvAFy&w?qtnjWSabq)e``nbqVMI delta 30 mcmdnbv!7=}Fe{5;>`j5miL4rvAFy&w?qtnjWSsns)e``rhzcVB diff --git a/lib/tzdata/zoneinfo/Europe/Nicosia b/lib/tzdata/zoneinfo/Europe/Nicosia index c210d0a5989c228886b400ab74c75b6fca94b70a..390347f442a486e296689c189e3346695bba5105 100644 GIT binary patch delta 25 hcmcc0a+PJnV@4L!*L)Wye`Hjj#Kg(SI9YpF diff --git a/lib/tzdata/zoneinfo/Europe/Sofia b/lib/tzdata/zoneinfo/Europe/Sofia index eabc972a22df300bf90b8da8897155cbb9655a3b..89450685cd149950dc6d65d1b4f076d96c3dc9a0 100644 GIT binary patch delta 30 kcmcb>a)D*TVMcDFlRXa@nV4CCaPkF46(%O8$zK>_0G!a)D*TVMcCa+uZ_;Ow24mIQasjGBYa^>*OztF#wC@2k`&^ diff --git a/lib/tzdata/zoneinfo/Europe/Tallinn b/lib/tzdata/zoneinfo/Europe/Tallinn index 5321bbd46e7a035956ac16584b13b397e192b5fe..fbebdc6255b547b1f3a547f0f92cc8148f05f186 100644 GIT binary patch delta 21 dcmZ3?x|nstA4cX9nS{yz8I2iPCL1#q0030o2J`>` delta 21 dcmZ3?x|nstA4cY7@e?NhXEbJHpKQ!j003O72b=%^ diff --git a/lib/tzdata/zoneinfo/Europe/Tiraspol b/lib/tzdata/zoneinfo/Europe/Tiraspol index 6970b14c506c8119341052c2c142ac11b28a4917..9152e68594bb66cc756e0407654a203952fbd4e5 100644 GIT binary patch delta 22 ecmey&`k8fuH4}@`$({$3?U}3@SteIARR92Fc?abH delta 22 dcmey&`k8fuH4}@m?OKM(_Dt4{%#*8`Dgaed2ATi> diff --git a/lib/tzdata/zoneinfo/Europe/Uzhgorod b/lib/tzdata/zoneinfo/Europe/Uzhgorod index 4e026859fdee32dd3f77a25274c5ccbebe0ee554..753a6c86f38586797589233f4528837f5b09151c 100644 GIT binary patch delta 22 ecmZ3-vW{iLWJVUlX@LQgr!%TCvQOT}SO5T4c?U26 delta 22 ecmZ3-vW{iLWJVUFqAdcGr!%TCGELscSO5T4eg`W6 diff --git a/lib/tzdata/zoneinfo/Europe/Vilnius b/lib/tzdata/zoneinfo/Europe/Vilnius index 75b2eebb57c54ab1dba6416fb085cc8b6d3a1405..43c3d7f1089366e1c48297906c2693712ac6d99c 100644 GIT binary patch delta 21 dcmZ3&x`cJZ7e;0w;e^TG8I2fOCTlTO0RT?F273Si delta 21 dcmZ3&x`cJZ7e?kb#|e|aGa4~+Ox9wm0svU=2T=e3 diff --git a/lib/tzdata/zoneinfo/Europe/Zaporozhye b/lib/tzdata/zoneinfo/Europe/Zaporozhye index 4e026859fdee32dd3f77a25274c5ccbebe0ee554..753a6c86f38586797589233f4528837f5b09151c 100644 GIT binary patch delta 22 ecmZ3-vW{iLWJVUlX@LQgr!%TCvQOT}SO5T4c?U26 delta 22 ecmZ3-vW{iLWJVUFqAdcGr!%TCGELscSO5T4eg`W6 diff --git a/lib/tzdata/zoneinfo/GB b/lib/tzdata/zoneinfo/GB index 323cd3818ac6f77b4394982ea5a342b9ed262777..b9e95d92623c6cc4c35b16f7ceba3006f5ce4934 100644 GIT binary patch delta 30 mcmdnbv!7=}Fe{6}a}|ckiL4rvAFy&w?qtnjWSabq)e``nbqVMI delta 30 mcmdnbv!7=}Fe{5;>`j5miL4rvAFy&w?qtnjWSsns)e``rhzcVB diff --git a/lib/tzdata/zoneinfo/GB-Eire b/lib/tzdata/zoneinfo/GB-Eire index 323cd3818ac6f77b4394982ea5a342b9ed262777..b9e95d92623c6cc4c35b16f7ceba3006f5ce4934 100644 GIT binary patch delta 30 mcmdnbv!7=}Fe{6}a}|ckiL4rvAFy&w?qtnjWSabq)e``nbqVMI delta 30 mcmdnbv!7=}Fe{5;>`j5miL4rvAFy&w?qtnjWSsns)e``rhzcVB diff --git a/lib/tzdata/zoneinfo/Mexico/BajaNorte b/lib/tzdata/zoneinfo/Mexico/BajaNorte index e8be26b139e653e1b07a3db14ea34bd290441947..42087af4cceb049f1395cabaaa85b7c39253ed97 100644 GIT binary patch delta 21 ccmZqVXyn-7!^~{Ex?!?Eb1Wm%g*zCy7#P-{U=(0r en6-gH%*Qu`LEF&K&Y=6e}A81B2YecwMg<>pw6uF*C8SGBC`o017h9ZD3$wU|6_=fs27*{Ru_^ o28LN17{nMDRvciI^6?E}&^9zQGy;*v20+pXOd5h%6VE6D02Kli1ONa4 diff --git a/lib/tzdata/zoneinfo/iso3166.tab b/lib/tzdata/zoneinfo/iso3166.tab index be3348d1..402c015e 100644 --- a/lib/tzdata/zoneinfo/iso3166.tab +++ b/lib/tzdata/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/tzdata/zoneinfo/leapseconds b/lib/tzdata/zoneinfo/leapseconds index a6a170aa..ce150bfe 100644 --- a/lib/tzdata/zoneinfo/leapseconds +++ b/lib/tzdata/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/tzdata/zoneinfo/tzdata.zi b/lib/tzdata/zoneinfo/tzdata.zi index b522e395..be1c4085 100644 --- a/lib/tzdata/zoneinfo/tzdata.zi +++ b/lib/tzdata/zoneinfo/tzdata.zi @@ -1,4 +1,4 @@ -# version 2023c +# version 2024a # 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/tzdata/zoneinfo/zone.tab b/lib/tzdata/zoneinfo/zone.tab index dbcb6179..3fa9306a 100644 --- a/lib/tzdata/zoneinfo/zone.tab +++ b/lib/tzdata/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/tzdata/zoneinfo/zone1970.tab b/lib/tzdata/zoneinfo/zone1970.tab index 1f1cecb8..abd94897 100644 --- a/lib/tzdata/zoneinfo/zone1970.tab +++ b/lib/tzdata/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/tzdata/zoneinfo/zonenow.tab b/lib/tzdata/zoneinfo/zonenow.tab new file mode 100644 index 00000000..b6f29109 --- /dev/null +++ b/lib/tzdata/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/tzdata/zones b/lib/tzdata/zones index 9300ebb0..b054f010 100644 --- a/lib/tzdata/zones +++ b/lib/tzdata/zones @@ -1,343 +1,254 @@ -Africa/Algiers -Atlantic/Cape_Verde -Africa/Ndjamena Africa/Abidjan -Africa/Cairo +Africa/Algiers Africa/Bissau -Africa/Nairobi -Africa/Monrovia -Africa/Tripoli -Indian/Mauritius +Africa/Cairo Africa/Casablanca -Africa/El_Aaiun -Africa/Maputo -Africa/Windhoek -Africa/Lagos -Africa/Sao_Tome -Africa/Johannesburg -Africa/Khartoum -Africa/Juba -Africa/Tunis -Antarctica/Casey -Antarctica/Davis -Antarctica/Mawson -Antarctica/Troll -Antarctica/Rothera -Asia/Kabul -Asia/Yerevan -Asia/Baku -Asia/Dhaka -Asia/Thimphu -Indian/Chagos -Asia/Yangon -Asia/Shanghai -Asia/Urumqi -Asia/Hong_Kong -Asia/Taipei -Asia/Macau -Asia/Nicosia -Asia/Famagusta -Asia/Tbilisi -Asia/Dili -Asia/Kolkata -Asia/Jakarta -Asia/Pontianak -Asia/Makassar -Asia/Jayapura -Asia/Tehran -Asia/Baghdad -Asia/Jerusalem -Asia/Tokyo -Asia/Amman -Asia/Almaty -Asia/Qyzylorda -Asia/Qostanay -Asia/Aqtobe -Asia/Aqtau -Asia/Atyrau -Asia/Oral -Asia/Bishkek -Asia/Seoul -Asia/Pyongyang -Asia/Beirut -Asia/Kuching -Indian/Maldives -Asia/Hovd -Asia/Ulaanbaatar -Asia/Choibalsan -Asia/Kathmandu -Asia/Karachi -Asia/Gaza -Asia/Hebron -Asia/Manila -Asia/Qatar -Asia/Riyadh -Asia/Singapore -Asia/Colombo -Asia/Damascus -Asia/Dushanbe -Asia/Bangkok -Asia/Ashgabat -Asia/Dubai -Asia/Samarkand -Asia/Tashkent -Asia/Ho_Chi_Minh -Australia/Darwin -Australia/Perth -Australia/Eucla -Australia/Brisbane -Australia/Lindeman -Australia/Adelaide -Australia/Hobart -Australia/Melbourne -Australia/Sydney -Australia/Broken_Hill -Australia/Lord_Howe -Antarctica/Macquarie -Pacific/Fiji -Pacific/Gambier -Pacific/Marquesas -Pacific/Tahiti -Pacific/Guam -Pacific/Tarawa -Pacific/Kanton -Pacific/Kiritimati -Pacific/Kwajalein -Pacific/Kosrae -Pacific/Nauru -Pacific/Noumea -Pacific/Auckland -Pacific/Chatham -Pacific/Rarotonga -Pacific/Niue -Pacific/Norfolk -Pacific/Palau -Pacific/Port_Moresby -Pacific/Bougainville -Pacific/Pitcairn -Pacific/Pago_Pago -Pacific/Apia -Pacific/Guadalcanal -Pacific/Fakaofo -Pacific/Tongatapu -Pacific/Efate -Europe/London -Europe/Dublin -WET -CET -MET -EET -Europe/Tirane -Europe/Andorra -Europe/Vienna -Europe/Minsk -Europe/Brussels -Europe/Sofia -Europe/Prague -Atlantic/Faroe -America/Danmarkshavn -America/Scoresbysund -America/Nuuk -America/Thule -Europe/Tallinn -Europe/Helsinki -Europe/Paris -Europe/Berlin -Europe/Gibraltar -Europe/Athens -Europe/Budapest -Europe/Rome -Europe/Riga -Europe/Vilnius -Europe/Malta -Europe/Chisinau -Europe/Warsaw -Europe/Lisbon -Atlantic/Azores -Atlantic/Madeira -Europe/Bucharest -Europe/Kaliningrad -Europe/Moscow -Europe/Simferopol -Europe/Astrakhan -Europe/Volgograd -Europe/Saratov -Europe/Kirov -Europe/Samara -Europe/Ulyanovsk -Asia/Yekaterinburg -Asia/Omsk -Asia/Barnaul -Asia/Novosibirsk -Asia/Tomsk -Asia/Novokuznetsk -Asia/Krasnoyarsk -Asia/Irkutsk -Asia/Chita -Asia/Yakutsk -Asia/Vladivostok -Asia/Khandyga -Asia/Sakhalin -Asia/Magadan -Asia/Srednekolymsk -Asia/Ust-Nera -Asia/Kamchatka -Asia/Anadyr -Europe/Belgrade -Europe/Madrid Africa/Ceuta -Atlantic/Canary -Europe/Zurich -Europe/Istanbul -Europe/Kyiv -EST -MST -HST -EST5EDT -CST6CDT -MST7MDT -PST8PDT -America/New_York -America/Chicago -America/North_Dakota/Center -America/North_Dakota/New_Salem -America/North_Dakota/Beulah -America/Denver -America/Los_Angeles -America/Juneau -America/Sitka -America/Metlakatla -America/Yakutat -America/Anchorage -America/Nome +Africa/El_Aaiun +Africa/Johannesburg +Africa/Juba +Africa/Khartoum +Africa/Lagos +Africa/Maputo +Africa/Monrovia +Africa/Nairobi +Africa/Ndjamena +Africa/Sao_Tome +Africa/Tripoli +Africa/Tunis +Africa/Windhoek America/Adak -Pacific/Honolulu -America/Phoenix +America/Anchorage +America/Araguaina +America/Argentina/Buenos_Aires +America/Argentina/Catamarca +America/Argentina/Cordoba +America/Argentina/Jujuy +America/Argentina/La_Rioja +America/Argentina/Mendoza +America/Argentina/Rio_Gallegos +America/Argentina/Salta +America/Argentina/San_Juan +America/Argentina/San_Luis +America/Argentina/Tucuman +America/Argentina/Ushuaia +America/Asuncion +America/Bahia +America/Bahia_Banderas +America/Barbados +America/Belem +America/Belize +America/Boa_Vista +America/Bogota America/Boise +America/Cambridge_Bay +America/Campo_Grande +America/Cancun +America/Caracas +America/Cayenne +America/Chicago +America/Chihuahua +America/Ciudad_Juarez +America/Costa_Rica +America/Cuiaba +America/Danmarkshavn +America/Dawson +America/Dawson_Creek +America/Denver +America/Detroit +America/Edmonton +America/Eirunepe +America/El_Salvador +America/Fort_Nelson +America/Fortaleza +America/Glace_Bay +America/Goose_Bay +America/Grand_Turk +America/Guatemala +America/Guayaquil +America/Guyana +America/Halifax +America/Havana +America/Hermosillo America/Indiana/Indianapolis -America/Indiana/Marengo -America/Indiana/Vincennes -America/Indiana/Tell_City -America/Indiana/Petersburg America/Indiana/Knox -America/Indiana/Winamac +America/Indiana/Marengo +America/Indiana/Petersburg +America/Indiana/Tell_City America/Indiana/Vevay +America/Indiana/Vincennes +America/Indiana/Winamac +America/Inuvik +America/Iqaluit +America/Jamaica +America/Juneau America/Kentucky/Louisville America/Kentucky/Monticello -America/Detroit -America/Menominee -America/St_Johns -America/Goose_Bay -America/Halifax -America/Glace_Bay -America/Moncton -America/Toronto -America/Winnipeg -America/Regina -America/Swift_Current -America/Edmonton -America/Vancouver -America/Dawson_Creek -America/Fort_Nelson -America/Iqaluit -America/Resolute -America/Rankin_Inlet -America/Cambridge_Bay -America/Inuvik -America/Whitehorse -America/Dawson -America/Cancun -America/Merida -America/Matamoros -America/Monterrey -America/Mexico_City -America/Ciudad_Juarez -America/Ojinaga -America/Chihuahua -America/Hermosillo -America/Mazatlan -America/Bahia_Banderas -America/Tijuana -America/Barbados -America/Belize -Atlantic/Bermuda -America/Costa_Rica -America/Havana -America/Santo_Domingo -America/El_Salvador -America/Guatemala -America/Port-au-Prince -America/Tegucigalpa -America/Jamaica -America/Martinique -America/Managua -America/Panama -America/Puerto_Rico -America/Miquelon -America/Grand_Turk -America/Argentina/Buenos_Aires -America/Argentina/Cordoba -America/Argentina/Salta -America/Argentina/Tucuman -America/Argentina/La_Rioja -America/Argentina/San_Juan -America/Argentina/Jujuy -America/Argentina/Catamarca -America/Argentina/Mendoza -America/Argentina/San_Luis -America/Argentina/Rio_Gallegos -America/Argentina/Ushuaia America/La_Paz -America/Noronha -America/Belem -America/Santarem -America/Fortaleza -America/Recife -America/Araguaina -America/Maceio -America/Bahia -America/Sao_Paulo -America/Campo_Grande -America/Cuiaba -America/Porto_Velho -America/Boa_Vista -America/Manaus -America/Eirunepe -America/Rio_Branco -America/Santiago -America/Punta_Arenas -Pacific/Easter -Antarctica/Palmer -America/Bogota -America/Guayaquil -Pacific/Galapagos -Atlantic/Stanley -America/Cayenne -America/Guyana -America/Asuncion America/Lima -Atlantic/South_Georgia -America/Paramaribo +America/Los_Angeles +America/Maceio +America/Managua +America/Manaus +America/Martinique +America/Matamoros +America/Mazatlan +America/Menominee +America/Merida +America/Metlakatla +America/Mexico_City +America/Miquelon +America/Moncton +America/Monterrey America/Montevideo -America/Caracas -Etc/UTC +America/New_York +America/Nome +America/Noronha +America/North_Dakota/Beulah +America/North_Dakota/Center +America/North_Dakota/New_Salem +America/Nuuk +America/Ojinaga +America/Panama +America/Paramaribo +America/Phoenix +America/Port-au-Prince +America/Porto_Velho +America/Puerto_Rico +America/Punta_Arenas +America/Rankin_Inlet +America/Recife +America/Regina +America/Resolute +America/Rio_Branco +America/Santarem +America/Santiago +America/Santo_Domingo +America/Sao_Paulo +America/Scoresbysund +America/Sitka +America/St_Johns +America/Swift_Current +America/Tegucigalpa +America/Thule +America/Tijuana +America/Toronto +America/Vancouver +America/Whitehorse +America/Winnipeg +America/Yakutat +Antarctica/Casey +Antarctica/Davis +Antarctica/Macquarie +Antarctica/Mawson +Antarctica/Palmer +Antarctica/Rothera +Antarctica/Troll +Antarctica/Vostok +Asia/Almaty +Asia/Amman +Asia/Anadyr +Asia/Aqtau +Asia/Aqtobe +Asia/Ashgabat +Asia/Atyrau +Asia/Baghdad +Asia/Baku +Asia/Bangkok +Asia/Barnaul +Asia/Beirut +Asia/Bishkek +Asia/Chita +Asia/Choibalsan +Asia/Colombo +Asia/Damascus +Asia/Dhaka +Asia/Dili +Asia/Dubai +Asia/Dushanbe +Asia/Famagusta +Asia/Gaza +Asia/Hebron +Asia/Ho_Chi_Minh +Asia/Hong_Kong +Asia/Hovd +Asia/Irkutsk +Asia/Jakarta +Asia/Jayapura +Asia/Jerusalem +Asia/Kabul +Asia/Kamchatka +Asia/Karachi +Asia/Kathmandu +Asia/Khandyga +Asia/Kolkata +Asia/Krasnoyarsk +Asia/Kuching +Asia/Macau +Asia/Magadan +Asia/Makassar +Asia/Manila +Asia/Nicosia +Asia/Novokuznetsk +Asia/Novosibirsk +Asia/Omsk +Asia/Oral +Asia/Pontianak +Asia/Pyongyang +Asia/Qatar +Asia/Qostanay +Asia/Qyzylorda +Asia/Riyadh +Asia/Sakhalin +Asia/Samarkand +Asia/Seoul +Asia/Shanghai +Asia/Singapore +Asia/Srednekolymsk +Asia/Taipei +Asia/Tashkent +Asia/Tbilisi +Asia/Tehran +Asia/Thimphu +Asia/Tokyo +Asia/Tomsk +Asia/Ulaanbaatar +Asia/Urumqi +Asia/Ust-Nera +Asia/Vladivostok +Asia/Yakutsk +Asia/Yangon +Asia/Yekaterinburg +Asia/Yerevan +Atlantic/Azores +Atlantic/Bermuda +Atlantic/Canary +Atlantic/Cape_Verde +Atlantic/Faroe +Atlantic/Madeira +Atlantic/South_Georgia +Atlantic/Stanley +Australia/Adelaide +Australia/Brisbane +Australia/Broken_Hill +Australia/Darwin +Australia/Eucla +Australia/Hobart +Australia/Lindeman +Australia/Lord_Howe +Australia/Melbourne +Australia/Perth +Australia/Sydney +CET +CST6CDT +EET +EST +EST5EDT Etc/GMT -GMT -Etc/GMT-14 -Etc/GMT-13 -Etc/GMT-12 -Etc/GMT-11 -Etc/GMT-10 -Etc/GMT-9 -Etc/GMT-8 -Etc/GMT-7 -Etc/GMT-6 -Etc/GMT-5 -Etc/GMT-4 -Etc/GMT-3 -Etc/GMT-2 -Etc/GMT-1 Etc/GMT+1 +Etc/GMT+10 +Etc/GMT+11 +Etc/GMT+12 Etc/GMT+2 Etc/GMT+3 Etc/GMT+4 @@ -346,10 +257,100 @@ Etc/GMT+6 Etc/GMT+7 Etc/GMT+8 Etc/GMT+9 -Etc/GMT+10 -Etc/GMT+11 -Etc/GMT+12 +Etc/GMT-1 +Etc/GMT-10 +Etc/GMT-11 +Etc/GMT-12 +Etc/GMT-13 +Etc/GMT-14 +Etc/GMT-2 +Etc/GMT-3 +Etc/GMT-4 +Etc/GMT-5 +Etc/GMT-6 +Etc/GMT-7 +Etc/GMT-8 +Etc/GMT-9 +Etc/UTC +Europe/Andorra +Europe/Astrakhan +Europe/Athens +Europe/Belgrade +Europe/Berlin +Europe/Brussels +Europe/Bucharest +Europe/Budapest +Europe/Chisinau +Europe/Dublin +Europe/Gibraltar +Europe/Helsinki +Europe/Istanbul +Europe/Kaliningrad +Europe/Kirov +Europe/Kyiv +Europe/Lisbon +Europe/London +Europe/Madrid +Europe/Malta +Europe/Minsk +Europe/Moscow +Europe/Paris +Europe/Prague +Europe/Riga +Europe/Rome +Europe/Samara +Europe/Saratov +Europe/Simferopol +Europe/Sofia +Europe/Tallinn +Europe/Tirane +Europe/Ulyanovsk +Europe/Vienna +Europe/Vilnius +Europe/Volgograd +Europe/Warsaw +Europe/Zurich Factory +HST +Indian/Chagos +Indian/Maldives +Indian/Mauritius +MET +MST +MST7MDT +PST8PDT +Pacific/Apia +Pacific/Auckland +Pacific/Bougainville +Pacific/Chatham +Pacific/Easter +Pacific/Efate +Pacific/Fakaofo +Pacific/Fiji +Pacific/Galapagos +Pacific/Gambier +Pacific/Guadalcanal +Pacific/Guam +Pacific/Honolulu +Pacific/Kanton +Pacific/Kiritimati +Pacific/Kosrae +Pacific/Kwajalein +Pacific/Marquesas +Pacific/Nauru +Pacific/Niue +Pacific/Norfolk +Pacific/Noumea +Pacific/Pago_Pago +Pacific/Palau +Pacific/Pitcairn +Pacific/Port_Moresby +Pacific/Rarotonga +Pacific/Tahiti +Pacific/Tarawa +Pacific/Tongatapu +WET +GMT Australia/ACT Australia/LHI Australia/NSW @@ -497,7 +498,6 @@ America/Tortola Antarctica/DumontDUrville Antarctica/McMurdo Antarctica/Syowa -Antarctica/Vostok Arctic/Longyearbyen Asia/Aden Asia/Bahrain diff --git a/requirements.txt b/requirements.txt index f697ecc8..8dc7afea 100644 --- a/requirements.txt +++ b/requirements.txt @@ -43,7 +43,7 @@ simplejson==3.19.2 six==1.16.0 tempora==5.5.1 tokenize-rt==5.2.0 -tzdata==2023.3 +tzdata==2024.1 tzlocal==5.0.1 urllib3<2 webencodings==0.5.1