mirror of
https://github.com/ansible-collections/community.crypto.git
synced 2026-05-06 13:22:58 +00:00
Remove Python 2 specific code (#877)
* Get rid of Python 2 special handling. * Get rid of more Python 2 specific handling. * Stop using six. * ipaddress is part of the standard library since Python 3. * Add changelog. * Fix import. * Remove unneeded imports.
This commit is contained in:
@@ -10,11 +10,9 @@ import datetime
|
||||
import json
|
||||
import locale
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import missing_required_lib
|
||||
from ansible.module_utils.common.text.converters import to_bytes
|
||||
from ansible.module_utils.six import PY3
|
||||
from ansible.module_utils.urls import fetch_url
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.backend_cryptography import (
|
||||
CRYPTOGRAPHY_ERROR,
|
||||
@@ -43,16 +41,6 @@ from ansible_collections.community.crypto.plugins.module_utils.argspec import (
|
||||
)
|
||||
|
||||
|
||||
try:
|
||||
import ipaddress # noqa: F401, pylint: disable=unused-import
|
||||
except ImportError:
|
||||
HAS_IPADDRESS = False
|
||||
IPADDRESS_IMPORT_ERROR = traceback.format_exc()
|
||||
else:
|
||||
HAS_IPADDRESS = True
|
||||
IPADDRESS_IMPORT_ERROR = None
|
||||
|
||||
|
||||
# -1 usually means connection problems
|
||||
RETRY_STATUS_CODES = (-1, 408, 429, 503)
|
||||
|
||||
@@ -345,7 +333,7 @@ class ACMEClient:
|
||||
try:
|
||||
# In Python 2, reading from a closed response yields a TypeError.
|
||||
# In Python 3, read() simply returns ''
|
||||
if PY3 and resp.closed:
|
||||
if resp.closed:
|
||||
raise TypeError
|
||||
content = resp.read()
|
||||
except (AttributeError, TypeError):
|
||||
@@ -440,7 +428,7 @@ class ACMEClient:
|
||||
try:
|
||||
# In Python 2, reading from a closed response yields a TypeError.
|
||||
# In Python 3, read() simply returns ''
|
||||
if PY3 and resp.closed:
|
||||
if resp.closed:
|
||||
raise TypeError
|
||||
content = resp.read()
|
||||
except (AttributeError, TypeError):
|
||||
@@ -557,11 +545,6 @@ def create_default_argspec(
|
||||
|
||||
|
||||
def create_backend(module, needs_acme_v2=True):
|
||||
if not HAS_IPADDRESS:
|
||||
module.fail_json(
|
||||
msg=missing_required_lib("ipaddress"), exception=IPADDRESS_IMPORT_ERROR
|
||||
)
|
||||
|
||||
backend = module.params["select_crypto_backend"]
|
||||
|
||||
# Backend autodetect
|
||||
|
||||
@@ -8,6 +8,7 @@ from __future__ import annotations
|
||||
import base64
|
||||
import binascii
|
||||
import datetime
|
||||
import ipaddress
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
@@ -33,12 +34,6 @@ from ansible_collections.community.crypto.plugins.module_utils.time import (
|
||||
)
|
||||
|
||||
|
||||
try:
|
||||
import ipaddress
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
_OPENSSL_ENVIRONMENT_UPDATE = dict(LANG="C", LC_ALL="C", LC_MESSAGES="C", LC_CTYPE="C")
|
||||
|
||||
|
||||
|
||||
@@ -74,7 +74,6 @@ def _parse_acme_timestamp(timestamp_str, with_timezone):
|
||||
"%Y-%m-%dT%H:%M:%S%z",
|
||||
"%Y-%m-%dT%H:%M:%S.%f%z",
|
||||
):
|
||||
# Note that %z will not work with Python 2... https://stackoverflow.com/a/27829491
|
||||
try:
|
||||
result = datetime.datetime.strptime(timestamp_str, format)
|
||||
except ValueError:
|
||||
|
||||
@@ -7,6 +7,7 @@ from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import ipaddress
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
@@ -22,12 +23,6 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.utils import
|
||||
)
|
||||
|
||||
|
||||
try:
|
||||
import ipaddress
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def create_key_authorization(client, token):
|
||||
"""
|
||||
Returns the key authorization for the given token
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from http.client import responses as http_responses
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_text
|
||||
from ansible.module_utils.six import PY3, binary_type
|
||||
from ansible.module_utils.six.moves.http_client import responses as http_responses
|
||||
|
||||
|
||||
def format_http_status(status_code):
|
||||
@@ -67,7 +67,7 @@ class ACMEProtocolException(ModuleFailException):
|
||||
try:
|
||||
# In Python 2, reading from a closed response yields a TypeError.
|
||||
# In Python 3, read() simply returns ''
|
||||
if PY3 and response.closed:
|
||||
if response.closed:
|
||||
raise TypeError
|
||||
content = response.read()
|
||||
except (AttributeError, TypeError):
|
||||
@@ -75,7 +75,7 @@ class ACMEProtocolException(ModuleFailException):
|
||||
|
||||
# Make sure that content_json is None or a dictionary
|
||||
if content_json is not None and not isinstance(content_json, dict):
|
||||
if content is None and isinstance(content_json, binary_type):
|
||||
if content is None and isinstance(content_json, bytes):
|
||||
content = content_json
|
||||
content_json = None
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@ import datetime
|
||||
import re
|
||||
import textwrap
|
||||
import traceback
|
||||
from urllib.parse import unquote
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
from ansible.module_utils.six.moves.urllib.parse import unquote
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.errors import (
|
||||
ModuleFailException,
|
||||
)
|
||||
|
||||
@@ -6,16 +6,16 @@ from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import binascii
|
||||
import ipaddress
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
|
||||
from ansible.module_utils.six.moves.urllib.parse import (
|
||||
from urllib.parse import (
|
||||
ParseResult,
|
||||
urlparse,
|
||||
urlunparse,
|
||||
)
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
|
||||
from ansible_collections.community.crypto.plugins.module_utils.version import (
|
||||
LooseVersion,
|
||||
)
|
||||
@@ -24,8 +24,6 @@ from ._asn1 import serialize_asn1_string_as_der
|
||||
|
||||
|
||||
try:
|
||||
import ipaddress
|
||||
|
||||
import cryptography
|
||||
from cryptography import x509
|
||||
from cryptography.exceptions import InvalidSignature
|
||||
@@ -286,12 +284,8 @@ DN_COMPONENT_START_RE = re.compile(b"^ *([a-zA-z0-9.]+) *= *")
|
||||
DN_HEX_LETTER = b"0123456789abcdef"
|
||||
|
||||
|
||||
if sys.version_info[0] < 3:
|
||||
_int_to_byte = chr
|
||||
else:
|
||||
|
||||
def _int_to_byte(value):
|
||||
return bytes((value,))
|
||||
def _int_to_byte(value):
|
||||
return bytes((value,))
|
||||
|
||||
|
||||
def _parse_dn_component(name, sep=b",", decode_remainder=True):
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
def binary_exp_mod(f, e, m):
|
||||
"""Computes f^e mod m in O(log e) multiplications modulo m."""
|
||||
@@ -99,82 +97,36 @@ def quick_is_not_prime(n):
|
||||
return False
|
||||
|
||||
|
||||
python_version = (sys.version_info[0], sys.version_info[1])
|
||||
if python_version >= (2, 7) or python_version >= (3, 1):
|
||||
# Ansible still supports Python 2.6 on remote nodes
|
||||
|
||||
def count_bytes(no):
|
||||
"""
|
||||
Given an integer, compute the number of bytes necessary to store its absolute value.
|
||||
"""
|
||||
no = abs(no)
|
||||
if no == 0:
|
||||
return 0
|
||||
return (no.bit_length() + 7) // 8
|
||||
|
||||
def count_bits(no):
|
||||
"""
|
||||
Given an integer, compute the number of bits necessary to store its absolute value.
|
||||
"""
|
||||
no = abs(no)
|
||||
if no == 0:
|
||||
return 0
|
||||
return no.bit_length()
|
||||
|
||||
else:
|
||||
# Slow, but works
|
||||
def count_bytes(no):
|
||||
"""
|
||||
Given an integer, compute the number of bytes necessary to store its absolute value.
|
||||
"""
|
||||
no = abs(no)
|
||||
count = 0
|
||||
while no > 0:
|
||||
no >>= 8
|
||||
count += 1
|
||||
return count
|
||||
|
||||
def count_bits(no):
|
||||
"""
|
||||
Given an integer, compute the number of bits necessary to store its absolute value.
|
||||
"""
|
||||
no = abs(no)
|
||||
count = 0
|
||||
while no > 0:
|
||||
no >>= 1
|
||||
count += 1
|
||||
return count
|
||||
def count_bytes(no):
|
||||
"""
|
||||
Given an integer, compute the number of bytes necessary to store its absolute value.
|
||||
"""
|
||||
no = abs(no)
|
||||
if no == 0:
|
||||
return 0
|
||||
return (no.bit_length() + 7) // 8
|
||||
|
||||
|
||||
if sys.version_info[0] >= 3:
|
||||
# Python 3 (and newer)
|
||||
def _convert_int_to_bytes(count, no):
|
||||
return no.to_bytes(count, byteorder="big")
|
||||
def count_bits(no):
|
||||
"""
|
||||
Given an integer, compute the number of bits necessary to store its absolute value.
|
||||
"""
|
||||
no = abs(no)
|
||||
if no == 0:
|
||||
return 0
|
||||
return no.bit_length()
|
||||
|
||||
def _convert_bytes_to_int(data):
|
||||
return int.from_bytes(data, byteorder="big", signed=False)
|
||||
|
||||
def _to_hex(no):
|
||||
return hex(no)[2:]
|
||||
def _convert_int_to_bytes(count, no):
|
||||
return no.to_bytes(count, byteorder="big")
|
||||
|
||||
else:
|
||||
# Python 2
|
||||
def _convert_int_to_bytes(count, n):
|
||||
if n == 0 and count == 0:
|
||||
return ""
|
||||
h = f"{n:x}"
|
||||
if len(h) > 2 * count:
|
||||
raise Exception(f"Number {n} needs more than {count} bytes!")
|
||||
return ("0" * (2 * count - len(h)) + h).decode("hex")
|
||||
|
||||
def _convert_bytes_to_int(data):
|
||||
v = 0
|
||||
for x in data:
|
||||
v = (v << 8) | ord(x)
|
||||
return v
|
||||
def _convert_bytes_to_int(data):
|
||||
return int.from_bytes(data, byteorder="big", signed=False)
|
||||
|
||||
def _to_hex(no):
|
||||
return f"{no:x}"
|
||||
|
||||
def _to_hex(no):
|
||||
return f"{no:x}"
|
||||
|
||||
|
||||
def convert_int_to_bytes(no, count=None):
|
||||
|
||||
@@ -14,11 +14,11 @@ import json
|
||||
import os
|
||||
import re
|
||||
import traceback
|
||||
from urllib.error import HTTPError
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from ansible.module_utils.basic import missing_required_lib
|
||||
from ansible.module_utils.common.text.converters import to_native, to_text
|
||||
from ansible.module_utils.six.moves.urllib.error import HTTPError
|
||||
from ansible.module_utils.six.moves.urllib.parse import urlencode
|
||||
from ansible.module_utils.urls import Request
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import abc
|
||||
import binascii
|
||||
import datetime as _datetime
|
||||
import os
|
||||
import sys
|
||||
from base64 import b64encode
|
||||
from datetime import datetime
|
||||
from hashlib import sha256
|
||||
@@ -68,13 +67,8 @@ _ECDSA_CURVE_IDENTIFIERS_LOOKUP = {
|
||||
b"nistp521": "ecdsa-nistp521",
|
||||
}
|
||||
|
||||
_USE_TIMEZONE = sys.version_info >= (3, 6)
|
||||
|
||||
|
||||
_ALWAYS = _add_or_remove_timezone(datetime(1970, 1, 1), with_timezone=_USE_TIMEZONE)
|
||||
_FOREVER = (
|
||||
datetime(9999, 12, 31, 23, 59, 59, 999999, _UTC) if _USE_TIMEZONE else datetime.max
|
||||
)
|
||||
_ALWAYS = _add_or_remove_timezone(datetime(1970, 1, 1), with_timezone=True)
|
||||
_FOREVER = datetime(9999, 12, 31, 23, 59, 59, 999999, _UTC)
|
||||
|
||||
_CRITICAL_OPTIONS = (
|
||||
"force-command",
|
||||
@@ -99,9 +93,6 @@ _EXTENSIONS = (
|
||||
"permit-user-rc",
|
||||
)
|
||||
|
||||
if six.PY3:
|
||||
long = int
|
||||
|
||||
|
||||
class OpensshCertificateTimeParameters:
|
||||
def __init__(self, valid_from, valid_to):
|
||||
@@ -172,13 +163,13 @@ class OpensshCertificateTimeParameters:
|
||||
result = OpensshCertificateTimeParameters._time_string_to_datetime(
|
||||
time_string_or_timestamp.strip()
|
||||
)
|
||||
elif isinstance(time_string_or_timestamp, (long, int)):
|
||||
elif isinstance(time_string_or_timestamp, int):
|
||||
result = OpensshCertificateTimeParameters._timestamp_to_datetime(
|
||||
time_string_or_timestamp
|
||||
)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Value must be of type (str, unicode, int, long) not {type(time_string_or_timestamp)}"
|
||||
f"Value must be of type (str, unicode, int) not {type(time_string_or_timestamp)}"
|
||||
)
|
||||
except ValueError:
|
||||
raise
|
||||
@@ -192,12 +183,7 @@ class OpensshCertificateTimeParameters:
|
||||
result = _FOREVER
|
||||
else:
|
||||
try:
|
||||
if _USE_TIMEZONE:
|
||||
result = datetime.fromtimestamp(
|
||||
timestamp, tz=_datetime.timezone.utc
|
||||
)
|
||||
else:
|
||||
result = datetime.utcfromtimestamp(timestamp)
|
||||
result = datetime.fromtimestamp(timestamp, tz=_datetime.timezone.utc)
|
||||
except OverflowError:
|
||||
raise ValueError
|
||||
return result
|
||||
@@ -210,15 +196,13 @@ class OpensshCertificateTimeParameters:
|
||||
elif time_string == "forever":
|
||||
result = _FOREVER
|
||||
elif is_relative_time_string(time_string):
|
||||
result = convert_relative_to_datetime(
|
||||
time_string, with_timezone=_USE_TIMEZONE
|
||||
)
|
||||
result = convert_relative_to_datetime(time_string, with_timezone=True)
|
||||
else:
|
||||
for time_format in ("%Y-%m-%d", "%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S"):
|
||||
try:
|
||||
result = _add_or_remove_timezone(
|
||||
datetime.strptime(time_string, time_format),
|
||||
with_timezone=_USE_TIMEZONE,
|
||||
with_timezone=True,
|
||||
)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
@@ -10,8 +10,6 @@ import re
|
||||
from contextlib import contextmanager
|
||||
from struct import Struct
|
||||
|
||||
from ansible.module_utils.six import PY3
|
||||
|
||||
|
||||
# Protocol References
|
||||
# -------------------
|
||||
@@ -25,9 +23,6 @@ from ansible.module_utils.six import PY3
|
||||
# https://github.com/pyca/cryptography/blob/main/src/cryptography/hazmat/primitives/serialization/ssh.py
|
||||
# https://github.com/paramiko/paramiko/blob/master/paramiko/message.py
|
||||
|
||||
if PY3:
|
||||
long = int
|
||||
|
||||
# 0 (False) or 1 (True) encoded as a single byte
|
||||
_BOOLEAN = Struct(b"?")
|
||||
# Unsigned 8-bit integer in network-byte-order
|
||||
@@ -93,7 +88,7 @@ class OpensshParser:
|
||||
if not isinstance(data, (bytes, bytearray)):
|
||||
raise TypeError(f"Data must be bytes-like not {type(data)}")
|
||||
|
||||
self._data = memoryview(data) if PY3 else data
|
||||
self._data = memoryview(data)
|
||||
self._pos = 0
|
||||
|
||||
def boolean(self):
|
||||
@@ -125,7 +120,7 @@ class OpensshParser:
|
||||
value = self._data[self._pos : next_pos]
|
||||
self._pos = next_pos
|
||||
# Cast to bytes is required as a memoryview slice is itself a memoryview
|
||||
return value if not PY3 else bytes(value)
|
||||
return bytes(value)
|
||||
|
||||
def mpint(self):
|
||||
return self._big_int(self.string(), "big", signed=True)
|
||||
@@ -223,47 +218,7 @@ class OpensshParser:
|
||||
f"Byte_order must be one of (big, little) not {byte_order}"
|
||||
)
|
||||
|
||||
if PY3:
|
||||
return int.from_bytes(raw_string, byte_order, signed=signed)
|
||||
|
||||
result = 0
|
||||
byte_length = len(raw_string)
|
||||
|
||||
if byte_length > 0:
|
||||
# Check sign-bit
|
||||
msb = raw_string[0] if byte_order == "big" else raw_string[-1]
|
||||
negative = bool(ord(msb) & 0x80)
|
||||
# Match pad value for two's complement
|
||||
pad = b"\xff" if signed and negative else b"\x00"
|
||||
# The definition of ``mpint`` enforces that unnecessary bytes are not encoded so they are added back
|
||||
pad_length = 4 - byte_length % 4
|
||||
if pad_length < 4:
|
||||
raw_string = (
|
||||
pad * pad_length + raw_string
|
||||
if byte_order == "big"
|
||||
else raw_string + pad * pad_length
|
||||
)
|
||||
byte_length += pad_length
|
||||
# Accumulate arbitrary precision integer bytes in the appropriate order
|
||||
if byte_order == "big":
|
||||
for i in range(0, byte_length, cls.UINT32_OFFSET):
|
||||
left_shift = result << cls.UINT32_OFFSET * 8
|
||||
result = (
|
||||
left_shift
|
||||
+ _UINT32.unpack(raw_string[i : i + cls.UINT32_OFFSET])[0]
|
||||
)
|
||||
else:
|
||||
for i in range(byte_length, 0, -cls.UINT32_OFFSET):
|
||||
left_shift = result << cls.UINT32_OFFSET * 8
|
||||
result = (
|
||||
left_shift
|
||||
+ _UINT32_LE.unpack(raw_string[i - cls.UINT32_OFFSET : i])[0]
|
||||
)
|
||||
# Adjust for two's complement
|
||||
if signed and negative:
|
||||
result -= 1 << (8 * byte_length)
|
||||
|
||||
return result
|
||||
return int.from_bytes(raw_string, byte_order, signed=signed)
|
||||
|
||||
|
||||
class _OpensshWriter:
|
||||
@@ -307,8 +262,8 @@ class _OpensshWriter:
|
||||
return self
|
||||
|
||||
def uint64(self, value):
|
||||
if not isinstance(value, (long, int)):
|
||||
raise TypeError(f"Value must be of type (long, int) not {type(value)}")
|
||||
if not isinstance(value, int):
|
||||
raise TypeError(f"Value must be of type int not {type(value)}")
|
||||
if value < 0 or value > _UINT64_MAX:
|
||||
raise ValueError(
|
||||
f"Value must be a positive integer less than {_UINT64_MAX}"
|
||||
@@ -327,8 +282,8 @@ class _OpensshWriter:
|
||||
return self
|
||||
|
||||
def mpint(self, value):
|
||||
if not isinstance(value, (int, long)):
|
||||
raise TypeError(f"Value must be of type (long, int) not {type(value)}")
|
||||
if not isinstance(value, int):
|
||||
raise TypeError(f"Value must be of type int not {type(value)}")
|
||||
|
||||
self.string(self._int_to_mpint(value))
|
||||
|
||||
@@ -373,42 +328,12 @@ class _OpensshWriter:
|
||||
|
||||
@staticmethod
|
||||
def _int_to_mpint(num):
|
||||
if PY3:
|
||||
byte_length = (num.bit_length() + 7) // 8
|
||||
try:
|
||||
result = num.to_bytes(byte_length, "big", signed=True)
|
||||
# Handles values which require \x00 or \xFF to pad sign-bit
|
||||
except OverflowError:
|
||||
result = num.to_bytes(byte_length + 1, "big", signed=True)
|
||||
else:
|
||||
result = bytes()
|
||||
# 0 and -1 are treated as special cases since they are used as sentinels for all other values
|
||||
if num == 0:
|
||||
result += b"\x00"
|
||||
elif num == -1:
|
||||
result += b"\xff"
|
||||
elif num > 0:
|
||||
while num >> 32:
|
||||
result = _UINT32.pack(num & _UINT32_MAX) + result
|
||||
num = num >> 32
|
||||
# Pack last 4 bytes individually to discard insignificant bytes
|
||||
while num:
|
||||
result = _UBYTE.pack(num & _UBYTE_MAX) + result
|
||||
num = num >> 8
|
||||
# Zero pad final byte if most-significant bit is 1 as per mpint definition
|
||||
if ord(result[0]) & 0x80:
|
||||
result = b"\x00" + result
|
||||
else:
|
||||
while (num >> 32) < -1:
|
||||
result = _UINT32.pack(num & _UINT32_MAX) + result
|
||||
num = num >> 32
|
||||
while num < -1:
|
||||
result = _UBYTE.pack(num & _UBYTE_MAX) + result
|
||||
num = num >> 8
|
||||
if not ord(result[0]) & 0x80:
|
||||
result = b"\xff" + result
|
||||
|
||||
return result
|
||||
byte_length = (num.bit_length() + 7) // 8
|
||||
try:
|
||||
return num.to_bytes(byte_length, "big", signed=True)
|
||||
# Handles values which require \x00 or \xFF to pad sign-bit
|
||||
except OverflowError:
|
||||
return num.to_bytes(byte_length + 1, "big", signed=True)
|
||||
|
||||
def bytes(self):
|
||||
return bytes(self._buff)
|
||||
|
||||
@@ -6,7 +6,6 @@ from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import re
|
||||
import sys
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||
@@ -68,29 +67,11 @@ def add_or_remove_timezone(timestamp, with_timezone):
|
||||
)
|
||||
|
||||
|
||||
if sys.version_info < (3, 3):
|
||||
|
||||
def get_epoch_seconds(timestamp):
|
||||
epoch = datetime.datetime(
|
||||
1970, 1, 1, tzinfo=UTC if timestamp.tzinfo is not None else None
|
||||
)
|
||||
delta = timestamp - epoch
|
||||
try:
|
||||
return delta.total_seconds()
|
||||
except AttributeError:
|
||||
# Python 2.6 and earlier: total_seconds() does not yet exist, so we use the formula from
|
||||
# https://docs.python.org/2/library/datetime.html#datetime.timedelta.total_seconds
|
||||
return (
|
||||
delta.microseconds + (delta.seconds + delta.days * 24 * 3600) * 10**6
|
||||
) / 10**6
|
||||
|
||||
else:
|
||||
|
||||
def get_epoch_seconds(timestamp):
|
||||
if timestamp.tzinfo is None:
|
||||
# timestamp.timestamp() is offset by the local timezone if timestamp has no timezone
|
||||
timestamp = ensure_utc_timezone(timestamp)
|
||||
return timestamp.timestamp()
|
||||
def get_epoch_seconds(timestamp):
|
||||
if timestamp.tzinfo is None:
|
||||
# timestamp.timestamp() is offset by the local timezone if timestamp has no timezone
|
||||
timestamp = ensure_utc_timezone(timestamp)
|
||||
return timestamp.timestamp()
|
||||
|
||||
|
||||
def from_epoch_seconds(timestamp, with_timezone):
|
||||
|
||||
Reference in New Issue
Block a user