25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

869 lines
21 KiB

  1. # coding: utf-8
  2. """
  3. Miscellaneous data helpers, including functions for converting integers to and
  4. from bytes and UTC timezone. Exports the following items:
  5. - OrderedDict()
  6. - int_from_bytes()
  7. - int_to_bytes()
  8. - timezone.utc
  9. - utc_with_dst
  10. - create_timezone()
  11. - inet_ntop()
  12. - inet_pton()
  13. - uri_to_iri()
  14. - iri_to_uri()
  15. """
  16. from __future__ import unicode_literals, division, absolute_import, print_function
  17. import math
  18. import sys
  19. from datetime import datetime, date, timedelta, tzinfo
  20. from ._errors import unwrap
  21. from ._iri import iri_to_uri, uri_to_iri # noqa
  22. from ._ordereddict import OrderedDict # noqa
  23. from ._types import type_name
  24. if sys.platform == 'win32':
  25. from ._inet import inet_ntop, inet_pton
  26. else:
  27. from socket import inet_ntop, inet_pton # noqa
  28. # Python 2
  29. if sys.version_info <= (3,):
  30. def int_to_bytes(value, signed=False, width=None):
  31. """
  32. Converts an integer to a byte string
  33. :param value:
  34. The integer to convert
  35. :param signed:
  36. If the byte string should be encoded using two's complement
  37. :param width:
  38. If None, the minimal possible size (but at least 1),
  39. otherwise an integer of the byte width for the return value
  40. :return:
  41. A byte string
  42. """
  43. if value == 0 and width == 0:
  44. return b''
  45. # Handle negatives in two's complement
  46. is_neg = False
  47. if signed and value < 0:
  48. is_neg = True
  49. bits = int(math.ceil(len('%x' % abs(value)) / 2.0) * 8)
  50. value = (value + (1 << bits)) % (1 << bits)
  51. hex_str = '%x' % value
  52. if len(hex_str) & 1:
  53. hex_str = '0' + hex_str
  54. output = hex_str.decode('hex')
  55. if signed and not is_neg and ord(output[0:1]) & 0x80:
  56. output = b'\x00' + output
  57. if width is not None:
  58. if len(output) > width:
  59. raise OverflowError('int too big to convert')
  60. if is_neg:
  61. pad_char = b'\xFF'
  62. else:
  63. pad_char = b'\x00'
  64. output = (pad_char * (width - len(output))) + output
  65. elif is_neg and ord(output[0:1]) & 0x80 == 0:
  66. output = b'\xFF' + output
  67. return output
  68. def int_from_bytes(value, signed=False):
  69. """
  70. Converts a byte string to an integer
  71. :param value:
  72. The byte string to convert
  73. :param signed:
  74. If the byte string should be interpreted using two's complement
  75. :return:
  76. An integer
  77. """
  78. if value == b'':
  79. return 0
  80. num = long(value.encode("hex"), 16) # noqa
  81. if not signed:
  82. return num
  83. # Check for sign bit and handle two's complement
  84. if ord(value[0:1]) & 0x80:
  85. bit_len = len(value) * 8
  86. return num - (1 << bit_len)
  87. return num
  88. class timezone(tzinfo): # noqa
  89. """
  90. Implements datetime.timezone for py2.
  91. Only full minute offsets are supported.
  92. DST is not supported.
  93. """
  94. def __init__(self, offset, name=None):
  95. """
  96. :param offset:
  97. A timedelta with this timezone's offset from UTC
  98. :param name:
  99. Name of the timezone; if None, generate one.
  100. """
  101. if not timedelta(hours=-24) < offset < timedelta(hours=24):
  102. raise ValueError('Offset must be in [-23:59, 23:59]')
  103. if offset.seconds % 60 or offset.microseconds:
  104. raise ValueError('Offset must be full minutes')
  105. self._offset = offset
  106. if name is not None:
  107. self._name = name
  108. elif not offset:
  109. self._name = 'UTC'
  110. else:
  111. self._name = 'UTC' + _format_offset(offset)
  112. def __eq__(self, other):
  113. """
  114. Compare two timezones
  115. :param other:
  116. The other timezone to compare to
  117. :return:
  118. A boolean
  119. """
  120. if type(other) != timezone:
  121. return False
  122. return self._offset == other._offset
  123. def tzname(self, dt):
  124. """
  125. :param dt:
  126. A datetime object; ignored.
  127. :return:
  128. Name of this timezone
  129. """
  130. return self._name
  131. def utcoffset(self, dt):
  132. """
  133. :param dt:
  134. A datetime object; ignored.
  135. :return:
  136. A timedelta object with the offset from UTC
  137. """
  138. return self._offset
  139. def dst(self, dt):
  140. """
  141. :param dt:
  142. A datetime object; ignored.
  143. :return:
  144. Zero timedelta
  145. """
  146. return timedelta(0)
  147. timezone.utc = timezone(timedelta(0))
  148. # Python 3
  149. else:
  150. from datetime import timezone # noqa
  151. def int_to_bytes(value, signed=False, width=None):
  152. """
  153. Converts an integer to a byte string
  154. :param value:
  155. The integer to convert
  156. :param signed:
  157. If the byte string should be encoded using two's complement
  158. :param width:
  159. If None, the minimal possible size (but at least 1),
  160. otherwise an integer of the byte width for the return value
  161. :return:
  162. A byte string
  163. """
  164. if width is None:
  165. if signed:
  166. if value < 0:
  167. bits_required = abs(value + 1).bit_length()
  168. else:
  169. bits_required = value.bit_length()
  170. if bits_required % 8 == 0:
  171. bits_required += 1
  172. else:
  173. bits_required = value.bit_length()
  174. width = math.ceil(bits_required / 8) or 1
  175. return value.to_bytes(width, byteorder='big', signed=signed)
  176. def int_from_bytes(value, signed=False):
  177. """
  178. Converts a byte string to an integer
  179. :param value:
  180. The byte string to convert
  181. :param signed:
  182. If the byte string should be interpreted using two's complement
  183. :return:
  184. An integer
  185. """
  186. return int.from_bytes(value, 'big', signed=signed)
  187. def _format_offset(off):
  188. """
  189. Format a timedelta into "[+-]HH:MM" format or "" for None
  190. """
  191. if off is None:
  192. return ''
  193. mins = off.days * 24 * 60 + off.seconds // 60
  194. sign = '-' if mins < 0 else '+'
  195. return sign + '%02d:%02d' % divmod(abs(mins), 60)
  196. class _UtcWithDst(tzinfo):
  197. """
  198. Utc class where dst does not return None; required for astimezone
  199. """
  200. def tzname(self, dt):
  201. return 'UTC'
  202. def utcoffset(self, dt):
  203. return timedelta(0)
  204. def dst(self, dt):
  205. return timedelta(0)
  206. utc_with_dst = _UtcWithDst()
  207. _timezone_cache = {}
  208. def create_timezone(offset):
  209. """
  210. Returns a new datetime.timezone object with the given offset.
  211. Uses cached objects if possible.
  212. :param offset:
  213. A datetime.timedelta object; It needs to be in full minutes and between -23:59 and +23:59.
  214. :return:
  215. A datetime.timezone object
  216. """
  217. try:
  218. tz = _timezone_cache[offset]
  219. except KeyError:
  220. tz = _timezone_cache[offset] = timezone(offset)
  221. return tz
  222. class extended_date(object):
  223. """
  224. A datetime.datetime-like object that represents the year 0. This is just
  225. to handle 0000-01-01 found in some certificates. Python's datetime does
  226. not support year 0.
  227. The proleptic gregorian calendar repeats itself every 400 years. Therefore,
  228. the simplest way to format is to substitute year 2000.
  229. """
  230. def __init__(self, year, month, day):
  231. """
  232. :param year:
  233. The integer 0
  234. :param month:
  235. An integer from 1 to 12
  236. :param day:
  237. An integer from 1 to 31
  238. """
  239. if year != 0:
  240. raise ValueError('year must be 0')
  241. self._y2k = date(2000, month, day)
  242. @property
  243. def year(self):
  244. """
  245. :return:
  246. The integer 0
  247. """
  248. return 0
  249. @property
  250. def month(self):
  251. """
  252. :return:
  253. An integer from 1 to 12
  254. """
  255. return self._y2k.month
  256. @property
  257. def day(self):
  258. """
  259. :return:
  260. An integer from 1 to 31
  261. """
  262. return self._y2k.day
  263. def strftime(self, format):
  264. """
  265. Formats the date using strftime()
  266. :param format:
  267. A strftime() format string
  268. :return:
  269. A str, the formatted date as a unicode string
  270. in Python 3 and a byte string in Python 2
  271. """
  272. # Format the date twice, once with year 2000, once with year 4000.
  273. # The only differences in the result will be in the millennium. Find them and replace by zeros.
  274. y2k = self._y2k.strftime(format)
  275. y4k = self._y2k.replace(year=4000).strftime(format)
  276. return ''.join('0' if (c2, c4) == ('2', '4') else c2 for c2, c4 in zip(y2k, y4k))
  277. def isoformat(self):
  278. """
  279. Formats the date as %Y-%m-%d
  280. :return:
  281. The date formatted to %Y-%m-%d as a unicode string in Python 3
  282. and a byte string in Python 2
  283. """
  284. return self.strftime('0000-%m-%d')
  285. def replace(self, year=None, month=None, day=None):
  286. """
  287. Returns a new datetime.date or asn1crypto.util.extended_date
  288. object with the specified components replaced
  289. :return:
  290. A datetime.date or asn1crypto.util.extended_date object
  291. """
  292. if year is None:
  293. year = self.year
  294. if month is None:
  295. month = self.month
  296. if day is None:
  297. day = self.day
  298. if year > 0:
  299. cls = date
  300. else:
  301. cls = extended_date
  302. return cls(
  303. year,
  304. month,
  305. day
  306. )
  307. def __str__(self):
  308. """
  309. :return:
  310. A str representing this extended_date, e.g. "0000-01-01"
  311. """
  312. return self.strftime('%Y-%m-%d')
  313. def __eq__(self, other):
  314. """
  315. Compare two extended_date objects
  316. :param other:
  317. The other extended_date to compare to
  318. :return:
  319. A boolean
  320. """
  321. # datetime.date object wouldn't compare equal because it can't be year 0
  322. if not isinstance(other, self.__class__):
  323. return False
  324. return self.__cmp__(other) == 0
  325. def __ne__(self, other):
  326. """
  327. Compare two extended_date objects
  328. :param other:
  329. The other extended_date to compare to
  330. :return:
  331. A boolean
  332. """
  333. return not self.__eq__(other)
  334. def _comparison_error(self, other):
  335. raise TypeError(unwrap(
  336. '''
  337. An asn1crypto.util.extended_date object can only be compared to
  338. an asn1crypto.util.extended_date or datetime.date object, not %s
  339. ''',
  340. type_name(other)
  341. ))
  342. def __cmp__(self, other):
  343. """
  344. Compare two extended_date or datetime.date objects
  345. :param other:
  346. The other extended_date object to compare to
  347. :return:
  348. An integer smaller than, equal to, or larger than 0
  349. """
  350. # self is year 0, other is >= year 1
  351. if isinstance(other, date):
  352. return -1
  353. if not isinstance(other, self.__class__):
  354. self._comparison_error(other)
  355. if self._y2k < other._y2k:
  356. return -1
  357. if self._y2k > other._y2k:
  358. return 1
  359. return 0
  360. def __lt__(self, other):
  361. return self.__cmp__(other) < 0
  362. def __le__(self, other):
  363. return self.__cmp__(other) <= 0
  364. def __gt__(self, other):
  365. return self.__cmp__(other) > 0
  366. def __ge__(self, other):
  367. return self.__cmp__(other) >= 0
  368. class extended_datetime(object):
  369. """
  370. A datetime.datetime-like object that represents the year 0. This is just
  371. to handle 0000-01-01 found in some certificates. Python's datetime does
  372. not support year 0.
  373. The proleptic gregorian calendar repeats itself every 400 years. Therefore,
  374. the simplest way to format is to substitute year 2000.
  375. """
  376. # There are 97 leap days during 400 years.
  377. DAYS_IN_400_YEARS = 400 * 365 + 97
  378. DAYS_IN_2000_YEARS = 5 * DAYS_IN_400_YEARS
  379. def __init__(self, year, *args, **kwargs):
  380. """
  381. :param year:
  382. The integer 0
  383. :param args:
  384. Other positional arguments; see datetime.datetime.
  385. :param kwargs:
  386. Other keyword arguments; see datetime.datetime.
  387. """
  388. if year != 0:
  389. raise ValueError('year must be 0')
  390. self._y2k = datetime(2000, *args, **kwargs)
  391. @property
  392. def year(self):
  393. """
  394. :return:
  395. The integer 0
  396. """
  397. return 0
  398. @property
  399. def month(self):
  400. """
  401. :return:
  402. An integer from 1 to 12
  403. """
  404. return self._y2k.month
  405. @property
  406. def day(self):
  407. """
  408. :return:
  409. An integer from 1 to 31
  410. """
  411. return self._y2k.day
  412. @property
  413. def hour(self):
  414. """
  415. :return:
  416. An integer from 1 to 24
  417. """
  418. return self._y2k.hour
  419. @property
  420. def minute(self):
  421. """
  422. :return:
  423. An integer from 1 to 60
  424. """
  425. return self._y2k.minute
  426. @property
  427. def second(self):
  428. """
  429. :return:
  430. An integer from 1 to 60
  431. """
  432. return self._y2k.second
  433. @property
  434. def microsecond(self):
  435. """
  436. :return:
  437. An integer from 0 to 999999
  438. """
  439. return self._y2k.microsecond
  440. @property
  441. def tzinfo(self):
  442. """
  443. :return:
  444. If object is timezone aware, a datetime.tzinfo object, else None.
  445. """
  446. return self._y2k.tzinfo
  447. def utcoffset(self):
  448. """
  449. :return:
  450. If object is timezone aware, a datetime.timedelta object, else None.
  451. """
  452. return self._y2k.utcoffset()
  453. def time(self):
  454. """
  455. :return:
  456. A datetime.time object
  457. """
  458. return self._y2k.time()
  459. def date(self):
  460. """
  461. :return:
  462. An asn1crypto.util.extended_date of the date
  463. """
  464. return extended_date(0, self.month, self.day)
  465. def strftime(self, format):
  466. """
  467. Performs strftime(), always returning a str
  468. :param format:
  469. A strftime() format string
  470. :return:
  471. A str of the formatted datetime
  472. """
  473. # Format the datetime twice, once with year 2000, once with year 4000.
  474. # The only differences in the result will be in the millennium. Find them and replace by zeros.
  475. y2k = self._y2k.strftime(format)
  476. y4k = self._y2k.replace(year=4000).strftime(format)
  477. return ''.join('0' if (c2, c4) == ('2', '4') else c2 for c2, c4 in zip(y2k, y4k))
  478. def isoformat(self, sep='T'):
  479. """
  480. Formats the date as "%Y-%m-%d %H:%M:%S" with the sep param between the
  481. date and time portions
  482. :param set:
  483. A single character of the separator to place between the date and
  484. time
  485. :return:
  486. The formatted datetime as a unicode string in Python 3 and a byte
  487. string in Python 2
  488. """
  489. s = '0000-%02d-%02d%c%02d:%02d:%02d' % (self.month, self.day, sep, self.hour, self.minute, self.second)
  490. if self.microsecond:
  491. s += '.%06d' % self.microsecond
  492. return s + _format_offset(self.utcoffset())
  493. def replace(self, year=None, *args, **kwargs):
  494. """
  495. Returns a new datetime.datetime or asn1crypto.util.extended_datetime
  496. object with the specified components replaced
  497. :param year:
  498. The new year to substitute. None to keep it.
  499. :param args:
  500. Other positional arguments; see datetime.datetime.replace.
  501. :param kwargs:
  502. Other keyword arguments; see datetime.datetime.replace.
  503. :return:
  504. A datetime.datetime or asn1crypto.util.extended_datetime object
  505. """
  506. if year:
  507. return self._y2k.replace(year, *args, **kwargs)
  508. return extended_datetime.from_y2k(self._y2k.replace(2000, *args, **kwargs))
  509. def astimezone(self, tz):
  510. """
  511. Convert this extended_datetime to another timezone.
  512. :param tz:
  513. A datetime.tzinfo object.
  514. :return:
  515. A new extended_datetime or datetime.datetime object
  516. """
  517. return extended_datetime.from_y2k(self._y2k.astimezone(tz))
  518. def timestamp(self):
  519. """
  520. Return POSIX timestamp. Only supported in python >= 3.3
  521. :return:
  522. A float representing the seconds since 1970-01-01 UTC. This will be a negative value.
  523. """
  524. return self._y2k.timestamp() - self.DAYS_IN_2000_YEARS * 86400
  525. def __str__(self):
  526. """
  527. :return:
  528. A str representing this extended_datetime, e.g. "0000-01-01 00:00:00.000001-10:00"
  529. """
  530. return self.isoformat(sep=' ')
  531. def __eq__(self, other):
  532. """
  533. Compare two extended_datetime objects
  534. :param other:
  535. The other extended_datetime to compare to
  536. :return:
  537. A boolean
  538. """
  539. # Only compare against other datetime or extended_datetime objects
  540. if not isinstance(other, (self.__class__, datetime)):
  541. return False
  542. # Offset-naive and offset-aware datetimes are never the same
  543. if (self.tzinfo is None) != (other.tzinfo is None):
  544. return False
  545. return self.__cmp__(other) == 0
  546. def __ne__(self, other):
  547. """
  548. Compare two extended_datetime objects
  549. :param other:
  550. The other extended_datetime to compare to
  551. :return:
  552. A boolean
  553. """
  554. return not self.__eq__(other)
  555. def _comparison_error(self, other):
  556. """
  557. Raises a TypeError about the other object not being suitable for
  558. comparison
  559. :param other:
  560. The object being compared to
  561. """
  562. raise TypeError(unwrap(
  563. '''
  564. An asn1crypto.util.extended_datetime object can only be compared to
  565. an asn1crypto.util.extended_datetime or datetime.datetime object,
  566. not %s
  567. ''',
  568. type_name(other)
  569. ))
  570. def __cmp__(self, other):
  571. """
  572. Compare two extended_datetime or datetime.datetime objects
  573. :param other:
  574. The other extended_datetime or datetime.datetime object to compare to
  575. :return:
  576. An integer smaller than, equal to, or larger than 0
  577. """
  578. if not isinstance(other, (self.__class__, datetime)):
  579. self._comparison_error(other)
  580. if (self.tzinfo is None) != (other.tzinfo is None):
  581. raise TypeError("can't compare offset-naive and offset-aware datetimes")
  582. diff = self - other
  583. zero = timedelta(0)
  584. if diff < zero:
  585. return -1
  586. if diff > zero:
  587. return 1
  588. return 0
  589. def __lt__(self, other):
  590. return self.__cmp__(other) < 0
  591. def __le__(self, other):
  592. return self.__cmp__(other) <= 0
  593. def __gt__(self, other):
  594. return self.__cmp__(other) > 0
  595. def __ge__(self, other):
  596. return self.__cmp__(other) >= 0
  597. def __add__(self, other):
  598. """
  599. Adds a timedelta
  600. :param other:
  601. A datetime.timedelta object to add.
  602. :return:
  603. A new extended_datetime or datetime.datetime object.
  604. """
  605. return extended_datetime.from_y2k(self._y2k + other)
  606. def __sub__(self, other):
  607. """
  608. Subtracts a timedelta or another datetime.
  609. :param other:
  610. A datetime.timedelta or datetime.datetime or extended_datetime object to subtract.
  611. :return:
  612. If a timedelta is passed, a new extended_datetime or datetime.datetime object.
  613. Else a datetime.timedelta object.
  614. """
  615. if isinstance(other, timedelta):
  616. return extended_datetime.from_y2k(self._y2k - other)
  617. if isinstance(other, extended_datetime):
  618. return self._y2k - other._y2k
  619. if isinstance(other, datetime):
  620. return self._y2k - other - timedelta(days=self.DAYS_IN_2000_YEARS)
  621. return NotImplemented
  622. def __rsub__(self, other):
  623. return -(self - other)
  624. @classmethod
  625. def from_y2k(cls, value):
  626. """
  627. Revert substitution of year 2000.
  628. :param value:
  629. A datetime.datetime object which is 2000 years in the future.
  630. :return:
  631. A new extended_datetime or datetime.datetime object.
  632. """
  633. year = value.year - 2000
  634. if year > 0:
  635. new_cls = datetime
  636. else:
  637. new_cls = cls
  638. return new_cls(
  639. year,
  640. value.month,
  641. value.day,
  642. value.hour,
  643. value.minute,
  644. value.second,
  645. value.microsecond,
  646. value.tzinfo
  647. )