datetime.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. from __future__ import absolute_import
  2. from __future__ import division
  3. # Copyright (c) 2010-2019 openpyxl
  4. """Manage Excel date weirdness."""
  5. # Python stdlib imports
  6. import datetime
  7. from datetime import timedelta, tzinfo
  8. from math import isnan
  9. import re
  10. from jdcal import (
  11. gcal2jd,
  12. jd2gcal,
  13. MJD_0
  14. )
  15. # constants
  16. MAC_EPOCH = datetime.date(1904, 1, 1)
  17. WINDOWS_EPOCH = datetime.date(1899, 12, 30)
  18. CALENDAR_WINDOWS_1900 = sum(gcal2jd(WINDOWS_EPOCH.year, WINDOWS_EPOCH.month, WINDOWS_EPOCH.day))
  19. CALENDAR_MAC_1904 = sum(gcal2jd(MAC_EPOCH.year, MAC_EPOCH.month, MAC_EPOCH.day))
  20. SECS_PER_DAY = 86400
  21. EPOCH = datetime.datetime.utcfromtimestamp(0)
  22. ISO_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
  23. ISO_REGEX = re.compile(r'''
  24. (?P<date>(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2}))?T?
  25. (?P<time>(?P<hour>\d{2}):(?P<minute>\d{2}):(?P<second>\d{2})(.(?P<ms>\d{2}))?)?Z?''',
  26. re.VERBOSE)
  27. def to_ISO8601(dt):
  28. """Convert from a datetime to a timestamp string."""
  29. return datetime.datetime.strftime(dt, ISO_FORMAT)
  30. def from_ISO8601(formatted_string):
  31. """Convert from a timestamp string to a datetime object. According to
  32. 18.17.4 in the specification the following ISO 8601 formats are
  33. supported.
  34. Dates B.1.1 and B.2.1
  35. Times B.1.2 and B.2.2
  36. Datetimes B.1.3 and B.2.3
  37. There is no concept of timedeltas
  38. """
  39. match = ISO_REGEX.match(formatted_string)
  40. if not match:
  41. raise ValueError("Invalid datetime value {}".format(formatted_string))
  42. parts = {k:int(v) for k, v in match.groupdict().items() if v is not None and v.isdigit()}
  43. if 'year' not in parts:
  44. dt = datetime.time(parts['hour'], parts['minute'], parts['second'])
  45. elif 'hour' not in parts:
  46. dt = datetime.date(parts['year'], parts['month'], parts['day'])
  47. else:
  48. dt = datetime.datetime(year=parts['year'], month=parts['month'],
  49. day=parts['day'], hour=parts['hour'], minute=parts['minute'],
  50. second=parts['second'])
  51. if 'ms' in parts:
  52. dt += timedelta(microseconds=parts['ms'])
  53. return dt
  54. def to_excel(dt, offset=CALENDAR_WINDOWS_1900):
  55. if isinstance(dt, datetime.time):
  56. return time_to_days(dt)
  57. if isinstance(dt, datetime.timedelta):
  58. return timedelta_to_days(dt)
  59. if isnan(dt.year): # Pandas supports Not a Date
  60. return
  61. jul = sum(gcal2jd(dt.year, dt.month, dt.day)) - offset
  62. if jul <= 60 and offset == CALENDAR_WINDOWS_1900:
  63. jul -= 1
  64. if hasattr(dt, 'time'):
  65. jul += time_to_days(dt)
  66. return jul
  67. def from_excel(value, offset=CALENDAR_WINDOWS_1900):
  68. if value is None:
  69. return
  70. if 1 < value < 60 and offset == CALENDAR_WINDOWS_1900:
  71. value += 1
  72. parts = list(jd2gcal(MJD_0, value + offset - MJD_0))
  73. _, fraction = divmod(value, 1)
  74. jumped = (parts[-1] == 0 and fraction > 0)
  75. diff = datetime.timedelta(days=fraction)
  76. if 0 < abs(value) < 1:
  77. return days_to_time(diff)
  78. if not jumped:
  79. return datetime.datetime(*parts[:3]) + diff
  80. else:
  81. return datetime.datetime(*parts[:3] + [0])
  82. class GMT(tzinfo):
  83. def utcoffset(self, dt):
  84. return timedelta(0)
  85. def dst(self, dt):
  86. return timedelta(0)
  87. def tzname(self,dt):
  88. return "GMT"
  89. try:
  90. from datetime import timezone
  91. UTC = timezone(timedelta(0))
  92. except ImportError:
  93. # Python 2
  94. UTC = GMT()
  95. def time_to_days(value):
  96. """Convert a time value to fractions of day"""
  97. if value.tzinfo is not None:
  98. value = value.astimezone(UTC)
  99. return (
  100. (value.hour * 3600)
  101. + (value.minute * 60)
  102. + value.second
  103. + value.microsecond / 10**6
  104. ) / SECS_PER_DAY
  105. def timedelta_to_days(value):
  106. """Convert a timedelta value to fractions of a day"""
  107. if not hasattr(value, 'total_seconds'):
  108. secs = (value.microseconds +
  109. (value.seconds + value.days * SECS_PER_DAY) * 10**6) / 10**6
  110. else:
  111. secs = value.total_seconds()
  112. return secs / SECS_PER_DAY
  113. def days_to_time(value):
  114. mins, seconds = divmod(value.seconds, 60)
  115. hours, mins = divmod(mins, 60)
  116. return datetime.time(hours, mins, seconds, value.microseconds)