Viewing file: util.py (17.62 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
# coding: utf-8
""" Miscellaneous data helpers, including functions for converting integers to and from bytes and UTC timezone. Exports the following items:
- OrderedDict() - int_from_bytes() - int_to_bytes() - timezone.utc - inet_ntop() - inet_pton() - uri_to_iri() - iri_to_uri() """
from __future__ import unicode_literals, division, absolute_import, print_function
import math import sys from datetime import datetime, date, time
from ._errors import unwrap from ._iri import iri_to_uri, uri_to_iri # noqa from ._ordereddict import OrderedDict # noqa from ._types import type_name
if sys.platform == 'win32': from ._inet import inet_ntop, inet_pton else: from socket import inet_ntop, inet_pton # noqa
# Python 2 if sys.version_info <= (3,):
from datetime import timedelta, tzinfo
py2 = True
def int_to_bytes(value, signed=False, width=None): """ Converts an integer to a byte string
:param value: The integer to convert
:param signed: If the byte string should be encoded using two's complement
:param width: None == auto, otherwise an integer of the byte width for the return value
:return: A byte string """
# Handle negatives in two's complement is_neg = False if signed and value < 0: is_neg = True bits = int(math.ceil(len('%x' % abs(value)) / 2.0) * 8) value = (value + (1 << bits)) % (1 << bits)
hex_str = '%x' % value if len(hex_str) & 1: hex_str = '0' + hex_str
output = hex_str.decode('hex')
if signed and not is_neg and ord(output[0:1]) & 0x80: output = b'\x00' + output
if width is not None: if is_neg: pad_char = b'\xFF' else: pad_char = b'\x00' output = (pad_char * (width - len(output))) + output elif is_neg and ord(output[0:1]) & 0x80 == 0: output = b'\xFF' + output
return output
def int_from_bytes(value, signed=False): """ Converts a byte string to an integer
:param value: The byte string to convert
:param signed: If the byte string should be interpreted using two's complement
:return: An integer """
if value == b'': return 0
num = long(value.encode("hex"), 16) # noqa
if not signed: return num
# Check for sign bit and handle two's complement if ord(value[0:1]) & 0x80: bit_len = len(value) * 8 return num - (1 << bit_len)
return num
class utc(tzinfo): # noqa
def tzname(self, _): return b'UTC+00:00'
def utcoffset(self, _): return timedelta(0)
def dst(self, _): return timedelta(0)
class timezone(): # noqa
utc = utc()
# Python 3 else:
from datetime import timezone # noqa
py2 = False
def int_to_bytes(value, signed=False, width=None): """ Converts an integer to a byte string
:param value: The integer to convert
:param signed: If the byte string should be encoded using two's complement
:param width: None == auto, otherwise an integer of the byte width for the return value
:return: A byte string """
if width is None: if signed: if value < 0: bits_required = abs(value + 1).bit_length() else: bits_required = value.bit_length() if bits_required % 8 == 0: bits_required += 1 else: bits_required = value.bit_length() width = math.ceil(bits_required / 8) or 1 return value.to_bytes(width, byteorder='big', signed=signed)
def int_from_bytes(value, signed=False): """ Converts a byte string to an integer
:param value: The byte string to convert
:param signed: If the byte string should be interpreted using two's complement
:return: An integer """
return int.from_bytes(value, 'big', signed=signed)
_DAYS_PER_MONTH_YEAR_0 = { 1: 31, 2: 29, # Year 0 was a leap year 3: 31, 4: 30, 5: 31, 6: 30, 7: 31, 8: 31, 9: 30, 10: 31, 11: 30, 12: 31 }
class extended_date(object): """ A datetime.date-like object that can represent the year 0. This is just to handle 0000-01-01 found in some certificates. """
year = None month = None day = None
def __init__(self, year, month, day): """ :param year: The integer 0
:param month: An integer from 1 to 12
:param day: An integer from 1 to 31 """
if year != 0: raise ValueError('year must be 0')
if month < 1 or month > 12: raise ValueError('month is out of range')
if day < 0 or day > _DAYS_PER_MONTH_YEAR_0[month]: raise ValueError('day is out of range')
self.year = year self.month = month self.day = day
def _format(self, format): """ Performs strftime(), always returning a unicode string
:param format: A strftime() format string
:return: A unicode string of the formatted date """
format = format.replace('%Y', '0000') # Year 0 is 1BC and a leap year. Leap years repeat themselves # every 28 years. Because of adjustments and the proleptic gregorian # calendar, the simplest way to format is to substitute year 2000. temp = date(2000, self.month, self.day) if '%c' in format: c_out = temp.strftime('%c') # Handle full years c_out = c_out.replace('2000', '0000') c_out = c_out.replace('%', '%%') format = format.replace('%c', c_out) if '%x' in format: x_out = temp.strftime('%x') # Handle formats such as 08/16/2000 or 16.08.2000 x_out = x_out.replace('2000', '0000') x_out = x_out.replace('%', '%%') format = format.replace('%x', x_out) return temp.strftime(format)
def isoformat(self): """ Formats the date as %Y-%m-%d
:return: The date formatted to %Y-%m-%d as a unicode string in Python 3 and a byte string in Python 2 """
return self.strftime('0000-%m-%d')
def strftime(self, format): """ Formats the date using strftime()
:param format: The strftime() format string
:return: The formatted date as a unicode string in Python 3 and a byte string in Python 2 """
output = self._format(format) if py2: return output.encode('utf-8') return output
def replace(self, year=None, month=None, day=None): """ Returns a new datetime.date or asn1crypto.util.extended_date object with the specified components replaced
:return: A datetime.date or asn1crypto.util.extended_date object """
if year is None: year = self.year if month is None: month = self.month if day is None: day = self.day
if year > 0: cls = date else: cls = extended_date
return cls( year, month, day )
def __str__(self): if py2: return self.__bytes__() else: return self.__unicode__()
def __bytes__(self): return self.__unicode__().encode('utf-8')
def __unicode__(self): return self._format('%Y-%m-%d')
def __eq__(self, other): if not isinstance(other, self.__class__): return False return self.__cmp__(other) == 0
def __ne__(self, other): return not self.__eq__(other)
def _comparison_error(self, other): raise TypeError(unwrap( ''' An asn1crypto.util.extended_date object can only be compared to an asn1crypto.util.extended_date or datetime.date object, not %s ''', type_name(other) ))
def __cmp__(self, other): if isinstance(other, date): return -1
if not isinstance(other, self.__class__): self._comparison_error(other)
st = ( self.year, self.month, self.day ) ot = ( other.year, other.month, other.day )
if st < ot: return -1 if st > ot: return 1 return 0
def __lt__(self, other): return self.__cmp__(other) < 0
def __le__(self, other): return self.__cmp__(other) <= 0
def __gt__(self, other): return self.__cmp__(other) > 0
def __ge__(self, other): return self.__cmp__(other) >= 0
class extended_datetime(object): """ A datetime.datetime-like object that can represent the year 0. This is just to handle 0000-01-01 found in some certificates. """
year = None month = None day = None hour = None minute = None second = None microsecond = None tzinfo = None
def __init__(self, year, month, day, hour=0, minute=0, second=0, microsecond=0, tzinfo=None): """ :param year: The integer 0
:param month: An integer from 1 to 12
:param day: An integer from 1 to 31
:param hour: An integer from 0 to 23
:param minute: An integer from 0 to 59
:param second: An integer from 0 to 59
:param microsecond: An integer from 0 to 999999 """
if year != 0: raise ValueError('year must be 0')
if month < 1 or month > 12: raise ValueError('month is out of range')
if day < 0 or day > _DAYS_PER_MONTH_YEAR_0[month]: raise ValueError('day is out of range')
if hour < 0 or hour > 23: raise ValueError('hour is out of range')
if minute < 0 or minute > 59: raise ValueError('minute is out of range')
if second < 0 or second > 59: raise ValueError('second is out of range')
if microsecond < 0 or microsecond > 999999: raise ValueError('microsecond is out of range')
self.year = year self.month = month self.day = day self.hour = hour self.minute = minute self.second = second self.microsecond = microsecond self.tzinfo = tzinfo
def date(self): """ :return: An asn1crypto.util.extended_date of the date """
return extended_date(self.year, self.month, self.day)
def time(self): """ :return: A datetime.time object of the time """
return time(self.hour, self.minute, self.second, self.microsecond, self.tzinfo)
def utcoffset(self): """ :return: None or a datetime.timedelta() of the offset from UTC """
if self.tzinfo is None: return None return self.tzinfo.utcoffset(self.replace(year=2000))
def dst(self): """ :return: None or a datetime.timedelta() of the daylight savings time offset """
if self.tzinfo is None: return None return self.tzinfo.dst(self.replace(year=2000))
def tzname(self): """ :return: None or the name of the timezone as a unicode string in Python 3 and a byte string in Python 2 """
if self.tzinfo is None: return None return self.tzinfo.tzname(self.replace(year=2000))
def _format(self, format): """ Performs strftime(), always returning a unicode string
:param format: A strftime() format string
:return: A unicode string of the formatted datetime """
format = format.replace('%Y', '0000') # Year 0 is 1BC and a leap year. Leap years repeat themselves # every 28 years. Because of adjustments and the proleptic gregorian # calendar, the simplest way to format is to substitute year 2000. temp = datetime( 2000, self.month, self.day, self.hour, self.minute, self.second, self.microsecond, self.tzinfo ) if '%c' in format: c_out = temp.strftime('%c') # Handle full years c_out = c_out.replace('2000', '0000') c_out = c_out.replace('%', '%%') format = format.replace('%c', c_out) if '%x' in format: x_out = temp.strftime('%x') # Handle formats such as 08/16/2000 or 16.08.2000 x_out = x_out.replace('2000', '0000') x_out = x_out.replace('%', '%%') format = format.replace('%x', x_out) return temp.strftime(format)
def isoformat(self, sep='T'): """ Formats the date as "%Y-%m-%d %H:%M:%S" with the sep param between the date and time portions
:param set: A single character of the separator to place between the date and time
:return: The formatted datetime as a unicode string in Python 3 and a byte string in Python 2 """
if self.microsecond == 0: return self.strftime('0000-%%m-%%d%s%%H:%%M:%%S' % sep) return self.strftime('0000-%%m-%%d%s%%H:%%M:%%S.%%f' % sep)
def strftime(self, format): """ Formats the date using strftime()
:param format: The strftime() format string
:return: The formatted date as a unicode string in Python 3 and a byte string in Python 2 """
output = self._format(format) if py2: return output.encode('utf-8') return output
def replace(self, year=None, month=None, day=None, hour=None, minute=None, second=None, microsecond=None, tzinfo=None): """ Returns a new datetime.datetime or asn1crypto.util.extended_datetime object with the specified components replaced
:return: A datetime.datetime or asn1crypto.util.extended_datetime object """
if year is None: year = self.year if month is None: month = self.month if day is None: day = self.day if hour is None: hour = self.hour if minute is None: minute = self.minute if second is None: second = self.second if microsecond is None: microsecond = self.microsecond if tzinfo is None: tzinfo = self.tzinfo
if year > 0: cls = datetime else: cls = extended_datetime
return cls( year, month, day, hour, minute, second, microsecond, tzinfo )
def __str__(self): if py2: return self.__bytes__() else: return self.__unicode__()
def __bytes__(self): return self.__unicode__().encode('utf-8')
def __unicode__(self): format = '%Y-%m-%d %H:%M:%S' if self.microsecond != 0: format += '.%f' return self._format(format)
def __eq__(self, other): if not isinstance(other, self.__class__): return False return self.__cmp__(other) == 0
def __ne__(self, other): return not self.__eq__(other)
def _comparison_error(self, other): """ Raises a TypeError about the other object not being suitable for comparison
:param other: The object being compared to """
raise TypeError(unwrap( ''' An asn1crypto.util.extended_datetime object can only be compared to an asn1crypto.util.extended_datetime or datetime.datetime object, not %s ''', type_name(other) ))
def __cmp__(self, other): so = self.utcoffset() oo = other.utcoffset()
if (so is not None and oo is None) or (so is None and oo is not None): raise TypeError("can't compare offset-naive and offset-aware datetimes")
if isinstance(other, datetime): return -1
if not isinstance(other, self.__class__): self._comparison_error(other)
st = ( self.year, self.month, self.day, self.hour, self.minute, self.second, self.microsecond, so ) ot = ( other.year, other.month, other.day, other.hour, other.minute, other.second, other.microsecond, oo )
if st < ot: return -1 if st > ot: return 1 return 0
def __lt__(self, other): return self.__cmp__(other) < 0
def __le__(self, other): return self.__cmp__(other) <= 0
def __gt__(self, other): return self.__cmp__(other) > 0
def __ge__(self, other): return self.__cmp__(other) >= 0
|