Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/asn1crypto/util.py: 32%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# coding: utf-8
3"""
4Miscellaneous data helpers, including functions for converting integers to and
5from bytes and UTC timezone. Exports the following items:
7 - OrderedDict()
8 - int_from_bytes()
9 - int_to_bytes()
10 - timezone.utc
11 - utc_with_dst
12 - create_timezone()
13 - inet_ntop()
14 - inet_pton()
15 - uri_to_iri()
16 - iri_to_uri()
17"""
19from __future__ import unicode_literals, division, absolute_import, print_function
21import math
22import sys
23from datetime import datetime, date, timedelta, tzinfo
25from ._errors import unwrap
26from ._iri import iri_to_uri, uri_to_iri # noqa
27from ._ordereddict import OrderedDict # noqa
28from ._types import type_name
30if sys.platform == 'win32':
31 from ._inet import inet_ntop, inet_pton
32else:
33 from socket import inet_ntop, inet_pton # noqa
36# Python 2
37if sys.version_info <= (3,):
39 def int_to_bytes(value, signed=False, width=None):
40 """
41 Converts an integer to a byte string
43 :param value:
44 The integer to convert
46 :param signed:
47 If the byte string should be encoded using two's complement
49 :param width:
50 If None, the minimal possible size (but at least 1),
51 otherwise an integer of the byte width for the return value
53 :return:
54 A byte string
55 """
57 if value == 0 and width == 0:
58 return b''
60 # Handle negatives in two's complement
61 is_neg = False
62 if signed and value < 0:
63 is_neg = True
64 bits = int(math.ceil(len('%x' % abs(value)) / 2.0) * 8)
65 value = (value + (1 << bits)) % (1 << bits)
67 hex_str = '%x' % value
68 if len(hex_str) & 1:
69 hex_str = '0' + hex_str
71 output = hex_str.decode('hex')
73 if signed and not is_neg and ord(output[0:1]) & 0x80:
74 output = b'\x00' + output
76 if width is not None:
77 if len(output) > width:
78 raise OverflowError('int too big to convert')
79 if is_neg:
80 pad_char = b'\xFF'
81 else:
82 pad_char = b'\x00'
83 output = (pad_char * (width - len(output))) + output
84 elif is_neg and ord(output[0:1]) & 0x80 == 0:
85 output = b'\xFF' + output
87 return output
89 def int_from_bytes(value, signed=False):
90 """
91 Converts a byte string to an integer
93 :param value:
94 The byte string to convert
96 :param signed:
97 If the byte string should be interpreted using two's complement
99 :return:
100 An integer
101 """
103 if value == b'':
104 return 0
106 num = long(value.encode("hex"), 16) # noqa
108 if not signed:
109 return num
111 # Check for sign bit and handle two's complement
112 if ord(value[0:1]) & 0x80:
113 bit_len = len(value) * 8
114 return num - (1 << bit_len)
116 return num
118 class timezone(tzinfo): # noqa
119 """
120 Implements datetime.timezone for py2.
121 Only full minute offsets are supported.
122 DST is not supported.
123 """
125 def __init__(self, offset, name=None):
126 """
127 :param offset:
128 A timedelta with this timezone's offset from UTC
130 :param name:
131 Name of the timezone; if None, generate one.
132 """
134 if not timedelta(hours=-24) < offset < timedelta(hours=24):
135 raise ValueError('Offset must be in [-23:59, 23:59]')
137 if offset.seconds % 60 or offset.microseconds:
138 raise ValueError('Offset must be full minutes')
140 self._offset = offset
142 if name is not None:
143 self._name = name
144 elif not offset:
145 self._name = 'UTC'
146 else:
147 self._name = 'UTC' + _format_offset(offset)
149 def __eq__(self, other):
150 """
151 Compare two timezones
153 :param other:
154 The other timezone to compare to
156 :return:
157 A boolean
158 """
160 if type(other) != timezone:
161 return False
162 return self._offset == other._offset
164 def __getinitargs__(self):
165 """
166 Called by tzinfo.__reduce__ to support pickle and copy.
168 :return:
169 offset and name, to be used for __init__
170 """
172 return self._offset, self._name
174 def tzname(self, dt):
175 """
176 :param dt:
177 A datetime object; ignored.
179 :return:
180 Name of this timezone
181 """
183 return self._name
185 def utcoffset(self, dt):
186 """
187 :param dt:
188 A datetime object; ignored.
190 :return:
191 A timedelta object with the offset from UTC
192 """
194 return self._offset
196 def dst(self, dt):
197 """
198 :param dt:
199 A datetime object; ignored.
201 :return:
202 Zero timedelta
203 """
205 return timedelta(0)
207 timezone.utc = timezone(timedelta(0))
209# Python 3
210else:
212 from datetime import timezone # noqa
214 def int_to_bytes(value, signed=False, width=None):
215 """
216 Converts an integer to a byte string
218 :param value:
219 The integer to convert
221 :param signed:
222 If the byte string should be encoded using two's complement
224 :param width:
225 If None, the minimal possible size (but at least 1),
226 otherwise an integer of the byte width for the return value
228 :return:
229 A byte string
230 """
232 if width is None:
233 if signed:
234 if value < 0:
235 bits_required = abs(value + 1).bit_length()
236 else:
237 bits_required = value.bit_length()
238 if bits_required % 8 == 0:
239 bits_required += 1
240 else:
241 bits_required = value.bit_length()
242 width = math.ceil(bits_required / 8) or 1
243 return value.to_bytes(width, byteorder='big', signed=signed)
245 def int_from_bytes(value, signed=False):
246 """
247 Converts a byte string to an integer
249 :param value:
250 The byte string to convert
252 :param signed:
253 If the byte string should be interpreted using two's complement
255 :return:
256 An integer
257 """
259 return int.from_bytes(value, 'big', signed=signed)
262def _format_offset(off):
263 """
264 Format a timedelta into "[+-]HH:MM" format or "" for None
265 """
267 if off is None:
268 return ''
269 mins = off.days * 24 * 60 + off.seconds // 60
270 sign = '-' if mins < 0 else '+'
271 return sign + '%02d:%02d' % divmod(abs(mins), 60)
274class _UtcWithDst(tzinfo):
275 """
276 Utc class where dst does not return None; required for astimezone
277 """
279 def tzname(self, dt):
280 return 'UTC'
282 def utcoffset(self, dt):
283 return timedelta(0)
285 def dst(self, dt):
286 return timedelta(0)
289utc_with_dst = _UtcWithDst()
291_timezone_cache = {}
294def create_timezone(offset):
295 """
296 Returns a new datetime.timezone object with the given offset.
297 Uses cached objects if possible.
299 :param offset:
300 A datetime.timedelta object; It needs to be in full minutes and between -23:59 and +23:59.
302 :return:
303 A datetime.timezone object
304 """
306 try:
307 tz = _timezone_cache[offset]
308 except KeyError:
309 tz = _timezone_cache[offset] = timezone(offset)
310 return tz
313class extended_date(object):
314 """
315 A datetime.datetime-like object that represents the year 0. This is just
316 to handle 0000-01-01 found in some certificates. Python's datetime does
317 not support year 0.
319 The proleptic gregorian calendar repeats itself every 400 years. Therefore,
320 the simplest way to format is to substitute year 2000.
321 """
323 def __init__(self, year, month, day):
324 """
325 :param year:
326 The integer 0
328 :param month:
329 An integer from 1 to 12
331 :param day:
332 An integer from 1 to 31
333 """
335 if year != 0:
336 raise ValueError('year must be 0')
338 self._y2k = date(2000, month, day)
340 @property
341 def year(self):
342 """
343 :return:
344 The integer 0
345 """
347 return 0
349 @property
350 def month(self):
351 """
352 :return:
353 An integer from 1 to 12
354 """
356 return self._y2k.month
358 @property
359 def day(self):
360 """
361 :return:
362 An integer from 1 to 31
363 """
365 return self._y2k.day
367 def strftime(self, format):
368 """
369 Formats the date using strftime()
371 :param format:
372 A strftime() format string
374 :return:
375 A str, the formatted date as a unicode string
376 in Python 3 and a byte string in Python 2
377 """
379 # Format the date twice, once with year 2000, once with year 4000.
380 # The only differences in the result will be in the millennium. Find them and replace by zeros.
381 y2k = self._y2k.strftime(format)
382 y4k = self._y2k.replace(year=4000).strftime(format)
383 return ''.join('0' if (c2, c4) == ('2', '4') else c2 for c2, c4 in zip(y2k, y4k))
385 def isoformat(self):
386 """
387 Formats the date as %Y-%m-%d
389 :return:
390 The date formatted to %Y-%m-%d as a unicode string in Python 3
391 and a byte string in Python 2
392 """
394 return self.strftime('0000-%m-%d')
396 def replace(self, year=None, month=None, day=None):
397 """
398 Returns a new datetime.date or asn1crypto.util.extended_date
399 object with the specified components replaced
401 :return:
402 A datetime.date or asn1crypto.util.extended_date object
403 """
405 if year is None:
406 year = self.year
407 if month is None:
408 month = self.month
409 if day is None:
410 day = self.day
412 if year > 0:
413 cls = date
414 else:
415 cls = extended_date
417 return cls(
418 year,
419 month,
420 day
421 )
423 def __str__(self):
424 """
425 :return:
426 A str representing this extended_date, e.g. "0000-01-01"
427 """
429 return self.strftime('%Y-%m-%d')
431 def __eq__(self, other):
432 """
433 Compare two extended_date objects
435 :param other:
436 The other extended_date to compare to
438 :return:
439 A boolean
440 """
442 # datetime.date object wouldn't compare equal because it can't be year 0
443 if not isinstance(other, self.__class__):
444 return False
445 return self.__cmp__(other) == 0
447 def __ne__(self, other):
448 """
449 Compare two extended_date objects
451 :param other:
452 The other extended_date to compare to
454 :return:
455 A boolean
456 """
458 return not self.__eq__(other)
460 def _comparison_error(self, other):
461 raise TypeError(unwrap(
462 '''
463 An asn1crypto.util.extended_date object can only be compared to
464 an asn1crypto.util.extended_date or datetime.date object, not %s
465 ''',
466 type_name(other)
467 ))
469 def __cmp__(self, other):
470 """
471 Compare two extended_date or datetime.date objects
473 :param other:
474 The other extended_date object to compare to
476 :return:
477 An integer smaller than, equal to, or larger than 0
478 """
480 # self is year 0, other is >= year 1
481 if isinstance(other, date):
482 return -1
484 if not isinstance(other, self.__class__):
485 self._comparison_error(other)
487 if self._y2k < other._y2k:
488 return -1
489 if self._y2k > other._y2k:
490 return 1
491 return 0
493 def __lt__(self, other):
494 return self.__cmp__(other) < 0
496 def __le__(self, other):
497 return self.__cmp__(other) <= 0
499 def __gt__(self, other):
500 return self.__cmp__(other) > 0
502 def __ge__(self, other):
503 return self.__cmp__(other) >= 0
506class extended_datetime(object):
507 """
508 A datetime.datetime-like object that represents the year 0. This is just
509 to handle 0000-01-01 found in some certificates. Python's datetime does
510 not support year 0.
512 The proleptic gregorian calendar repeats itself every 400 years. Therefore,
513 the simplest way to format is to substitute year 2000.
514 """
516 # There are 97 leap days during 400 years.
517 DAYS_IN_400_YEARS = 400 * 365 + 97
518 DAYS_IN_2000_YEARS = 5 * DAYS_IN_400_YEARS
520 def __init__(self, year, *args, **kwargs):
521 """
522 :param year:
523 The integer 0
525 :param args:
526 Other positional arguments; see datetime.datetime.
528 :param kwargs:
529 Other keyword arguments; see datetime.datetime.
530 """
532 if year != 0:
533 raise ValueError('year must be 0')
535 self._y2k = datetime(2000, *args, **kwargs)
537 @property
538 def year(self):
539 """
540 :return:
541 The integer 0
542 """
544 return 0
546 @property
547 def month(self):
548 """
549 :return:
550 An integer from 1 to 12
551 """
553 return self._y2k.month
555 @property
556 def day(self):
557 """
558 :return:
559 An integer from 1 to 31
560 """
562 return self._y2k.day
564 @property
565 def hour(self):
566 """
567 :return:
568 An integer from 1 to 24
569 """
571 return self._y2k.hour
573 @property
574 def minute(self):
575 """
576 :return:
577 An integer from 1 to 60
578 """
580 return self._y2k.minute
582 @property
583 def second(self):
584 """
585 :return:
586 An integer from 1 to 60
587 """
589 return self._y2k.second
591 @property
592 def microsecond(self):
593 """
594 :return:
595 An integer from 0 to 999999
596 """
598 return self._y2k.microsecond
600 @property
601 def tzinfo(self):
602 """
603 :return:
604 If object is timezone aware, a datetime.tzinfo object, else None.
605 """
607 return self._y2k.tzinfo
609 def utcoffset(self):
610 """
611 :return:
612 If object is timezone aware, a datetime.timedelta object, else None.
613 """
615 return self._y2k.utcoffset()
617 def time(self):
618 """
619 :return:
620 A datetime.time object
621 """
623 return self._y2k.time()
625 def date(self):
626 """
627 :return:
628 An asn1crypto.util.extended_date of the date
629 """
631 return extended_date(0, self.month, self.day)
633 def strftime(self, format):
634 """
635 Performs strftime(), always returning a str
637 :param format:
638 A strftime() format string
640 :return:
641 A str of the formatted datetime
642 """
644 # Format the datetime twice, once with year 2000, once with year 4000.
645 # The only differences in the result will be in the millennium. Find them and replace by zeros.
646 y2k = self._y2k.strftime(format)
647 y4k = self._y2k.replace(year=4000).strftime(format)
648 return ''.join('0' if (c2, c4) == ('2', '4') else c2 for c2, c4 in zip(y2k, y4k))
650 def isoformat(self, sep='T'):
651 """
652 Formats the date as "%Y-%m-%d %H:%M:%S" with the sep param between the
653 date and time portions
655 :param set:
656 A single character of the separator to place between the date and
657 time
659 :return:
660 The formatted datetime as a unicode string in Python 3 and a byte
661 string in Python 2
662 """
664 s = '0000-%02d-%02d%c%02d:%02d:%02d' % (self.month, self.day, sep, self.hour, self.minute, self.second)
665 if self.microsecond:
666 s += '.%06d' % self.microsecond
667 return s + _format_offset(self.utcoffset())
669 def replace(self, year=None, *args, **kwargs):
670 """
671 Returns a new datetime.datetime or asn1crypto.util.extended_datetime
672 object with the specified components replaced
674 :param year:
675 The new year to substitute. None to keep it.
677 :param args:
678 Other positional arguments; see datetime.datetime.replace.
680 :param kwargs:
681 Other keyword arguments; see datetime.datetime.replace.
683 :return:
684 A datetime.datetime or asn1crypto.util.extended_datetime object
685 """
687 if year:
688 return self._y2k.replace(year, *args, **kwargs)
690 return extended_datetime.from_y2k(self._y2k.replace(2000, *args, **kwargs))
692 def astimezone(self, tz):
693 """
694 Convert this extended_datetime to another timezone.
696 :param tz:
697 A datetime.tzinfo object.
699 :return:
700 A new extended_datetime or datetime.datetime object
701 """
703 return extended_datetime.from_y2k(self._y2k.astimezone(tz))
705 def timestamp(self):
706 """
707 Return POSIX timestamp. Only supported in python >= 3.3
709 :return:
710 A float representing the seconds since 1970-01-01 UTC. This will be a negative value.
711 """
713 return self._y2k.timestamp() - self.DAYS_IN_2000_YEARS * 86400
715 def __str__(self):
716 """
717 :return:
718 A str representing this extended_datetime, e.g. "0000-01-01 00:00:00.000001-10:00"
719 """
721 return self.isoformat(sep=' ')
723 def __eq__(self, other):
724 """
725 Compare two extended_datetime objects
727 :param other:
728 The other extended_datetime to compare to
730 :return:
731 A boolean
732 """
734 # Only compare against other datetime or extended_datetime objects
735 if not isinstance(other, (self.__class__, datetime)):
736 return False
738 # Offset-naive and offset-aware datetimes are never the same
739 if (self.tzinfo is None) != (other.tzinfo is None):
740 return False
742 return self.__cmp__(other) == 0
744 def __ne__(self, other):
745 """
746 Compare two extended_datetime objects
748 :param other:
749 The other extended_datetime to compare to
751 :return:
752 A boolean
753 """
755 return not self.__eq__(other)
757 def _comparison_error(self, other):
758 """
759 Raises a TypeError about the other object not being suitable for
760 comparison
762 :param other:
763 The object being compared to
764 """
766 raise TypeError(unwrap(
767 '''
768 An asn1crypto.util.extended_datetime object can only be compared to
769 an asn1crypto.util.extended_datetime or datetime.datetime object,
770 not %s
771 ''',
772 type_name(other)
773 ))
775 def __cmp__(self, other):
776 """
777 Compare two extended_datetime or datetime.datetime objects
779 :param other:
780 The other extended_datetime or datetime.datetime object to compare to
782 :return:
783 An integer smaller than, equal to, or larger than 0
784 """
786 if not isinstance(other, (self.__class__, datetime)):
787 self._comparison_error(other)
789 if (self.tzinfo is None) != (other.tzinfo is None):
790 raise TypeError("can't compare offset-naive and offset-aware datetimes")
792 diff = self - other
793 zero = timedelta(0)
794 if diff < zero:
795 return -1
796 if diff > zero:
797 return 1
798 return 0
800 def __lt__(self, other):
801 return self.__cmp__(other) < 0
803 def __le__(self, other):
804 return self.__cmp__(other) <= 0
806 def __gt__(self, other):
807 return self.__cmp__(other) > 0
809 def __ge__(self, other):
810 return self.__cmp__(other) >= 0
812 def __add__(self, other):
813 """
814 Adds a timedelta
816 :param other:
817 A datetime.timedelta object to add.
819 :return:
820 A new extended_datetime or datetime.datetime object.
821 """
823 return extended_datetime.from_y2k(self._y2k + other)
825 def __sub__(self, other):
826 """
827 Subtracts a timedelta or another datetime.
829 :param other:
830 A datetime.timedelta or datetime.datetime or extended_datetime object to subtract.
832 :return:
833 If a timedelta is passed, a new extended_datetime or datetime.datetime object.
834 Else a datetime.timedelta object.
835 """
837 if isinstance(other, timedelta):
838 return extended_datetime.from_y2k(self._y2k - other)
840 if isinstance(other, extended_datetime):
841 return self._y2k - other._y2k
843 if isinstance(other, datetime):
844 return self._y2k - other - timedelta(days=self.DAYS_IN_2000_YEARS)
846 return NotImplemented
848 def __rsub__(self, other):
849 return -(self - other)
851 @classmethod
852 def from_y2k(cls, value):
853 """
854 Revert substitution of year 2000.
856 :param value:
857 A datetime.datetime object which is 2000 years in the future.
858 :return:
859 A new extended_datetime or datetime.datetime object.
860 """
862 year = value.year - 2000
864 if year > 0:
865 new_cls = datetime
866 else:
867 new_cls = cls
869 return new_cls(
870 year,
871 value.month,
872 value.day,
873 value.hour,
874 value.minute,
875 value.second,
876 value.microsecond,
877 value.tzinfo
878 )