mini_dateutil.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. # This module is a very stripped down version of the dateutil
  2. # package for when dateutil has not been installed. As a replacement
  3. # for dateutil.parser.parse, the parsing methods from
  4. # http://blog.mfabrik.com/2008/06/30/relativity-of-time-shortcomings-in-python-datetime-and-workaround/
  5. #As such, the following copyrights and licenses applies:
  6. # dateutil - Extensions to the standard python 2.3+ datetime module.
  7. #
  8. # Copyright (c) 2003-2011 - Gustavo Niemeyer <gustavo@niemeyer.net>
  9. #
  10. # All rights reserved.
  11. #
  12. # Redistribution and use in source and binary forms, with or without
  13. # modification, are permitted provided that the following conditions are met:
  14. #
  15. # * Redistributions of source code must retain the above copyright notice,
  16. # this list of conditions and the following disclaimer.
  17. # * Redistributions in binary form must reproduce the above copyright notice,
  18. # this list of conditions and the following disclaimer in the documentation
  19. # and/or other materials provided with the distribution.
  20. # * Neither the name of the copyright holder nor the names of its
  21. # contributors may be used to endorse or promote products derived from
  22. # this software without specific prior written permission.
  23. #
  24. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  25. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  26. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  27. # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  28. # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  29. # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  30. # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  31. # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  32. # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  33. # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  34. # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  35. # fixed_dateime
  36. #
  37. # Copyright (c) 2008, Red Innovation Ltd., Finland
  38. # All rights reserved.
  39. #
  40. # Redistribution and use in source and binary forms, with or without
  41. # modification, are permitted provided that the following conditions are met:
  42. # * Redistributions of source code must retain the above copyright
  43. # notice, this list of conditions and the following disclaimer.
  44. # * Redistributions in binary form must reproduce the above copyright
  45. # notice, this list of conditions and the following disclaimer in the
  46. # documentation and/or other materials provided with the distribution.
  47. # * Neither the name of Red Innovation nor the names of its contributors
  48. # may be used to endorse or promote products derived from this software
  49. # without specific prior written permission.
  50. #
  51. # THIS SOFTWARE IS PROVIDED BY RED INNOVATION ``AS IS'' AND ANY
  52. # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  53. # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  54. # DISCLAIMED. IN NO EVENT SHALL RED INNOVATION BE LIABLE FOR ANY
  55. # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  56. # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  57. # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  58. # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  59. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  60. # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  61. import re
  62. import math
  63. import datetime
  64. ZERO = datetime.timedelta(0)
  65. try:
  66. from dateutil.parser import parse as parse_iso
  67. from dateutil.tz import tzoffset, tzutc
  68. except:
  69. # As a stopgap, define the two timezones here based
  70. # on the dateutil code.
  71. class tzutc(datetime.tzinfo):
  72. def utcoffset(self, dt):
  73. return ZERO
  74. def dst(self, dt):
  75. return ZERO
  76. def tzname(self, dt):
  77. return "UTC"
  78. def __eq__(self, other):
  79. return (isinstance(other, tzutc) or
  80. (isinstance(other, tzoffset) and other._offset == ZERO))
  81. def __ne__(self, other):
  82. return not self.__eq__(other)
  83. def __repr__(self):
  84. return "%s()" % self.__class__.__name__
  85. __reduce__ = object.__reduce__
  86. class tzoffset(datetime.tzinfo):
  87. def __init__(self, name, offset):
  88. self._name = name
  89. self._offset = datetime.timedelta(minutes=offset)
  90. def utcoffset(self, dt):
  91. return self._offset
  92. def dst(self, dt):
  93. return ZERO
  94. def tzname(self, dt):
  95. return self._name
  96. def __eq__(self, other):
  97. return (isinstance(other, tzoffset) and
  98. self._offset == other._offset)
  99. def __ne__(self, other):
  100. return not self.__eq__(other)
  101. def __repr__(self):
  102. return "%s(%s, %s)" % (self.__class__.__name__,
  103. repr(self._name),
  104. self._offset.days*86400+self._offset.seconds)
  105. __reduce__ = object.__reduce__
  106. _fixed_offset_tzs = { }
  107. UTC = tzutc()
  108. def _get_fixed_offset_tz(offsetmins):
  109. """For internal use only: Returns a tzinfo with
  110. the given fixed offset. This creates only one instance
  111. for each offset; the zones are kept in a dictionary"""
  112. if offsetmins == 0:
  113. return UTC
  114. if not offsetmins in _fixed_offset_tzs:
  115. if offsetmins < 0:
  116. sign = '-'
  117. absoff = -offsetmins
  118. else:
  119. sign = '+'
  120. absoff = offsetmins
  121. name = "UTC%s%02d:%02d" % (sign, int(absoff / 60), absoff % 60)
  122. inst = tzoffset(name,offsetmins)
  123. _fixed_offset_tzs[offsetmins] = inst
  124. return _fixed_offset_tzs[offsetmins]
  125. _iso8601_parser = re.compile("""
  126. ^
  127. (?P<year> [0-9]{4})?(?P<ymdsep>-?)?
  128. (?P<month>[0-9]{2})?(?P=ymdsep)?
  129. (?P<day> [0-9]{2})?
  130. (?P<time>
  131. (?: # time part... optional... at least hour must be specified
  132. (?:T|\s+)?
  133. (?P<hour>[0-9]{2})
  134. (?:
  135. # minutes, separated with :, or none, from hours
  136. (?P<hmssep>[:]?)
  137. (?P<minute>[0-9]{2})
  138. (?:
  139. # same for seconds, separated with :, or none, from hours
  140. (?P=hmssep)
  141. (?P<second>[0-9]{2})
  142. )?
  143. )?
  144. # fractions
  145. (?: [,.] (?P<frac>[0-9]{1,10}))?
  146. # timezone, Z, +-hh or +-hh:?mm. MUST BE, but complain if not there.
  147. (
  148. (?P<tzempty>Z)
  149. |
  150. (?P<tzh>[+-][0-9]{2})
  151. (?: :? # optional separator
  152. (?P<tzm>[0-9]{2})
  153. )?
  154. )?
  155. )
  156. )?
  157. $
  158. """, re.X) # """
  159. def parse_iso(timestamp):
  160. """Internal function for parsing a timestamp in
  161. ISO 8601 format"""
  162. timestamp = timestamp.strip()
  163. m = _iso8601_parser.match(timestamp)
  164. if not m:
  165. raise ValueError("Not a proper ISO 8601 timestamp!: %s" % timestamp)
  166. vals = m.groupdict()
  167. def_vals = {'year': 1970, 'month': 1, 'day': 1}
  168. for key in vals:
  169. if vals[key] is None:
  170. vals[key] = def_vals.get(key, 0)
  171. elif key not in ['time', 'ymdsep', 'hmssep', 'tzempty']:
  172. vals[key] = int(vals[key])
  173. year = vals['year']
  174. month = vals['month']
  175. day = vals['day']
  176. if m.group('time') is None:
  177. return datetime.date(year, month, day)
  178. h, min, s, us = None, None, None, 0
  179. frac = 0
  180. if m.group('tzempty') == None and m.group('tzh') == None:
  181. raise ValueError("Not a proper ISO 8601 timestamp: " +
  182. "missing timezone (Z or +hh[:mm])!")
  183. if m.group('frac'):
  184. frac = m.group('frac')
  185. power = len(frac)
  186. frac = int(frac) / 10.0 ** power
  187. if m.group('hour'):
  188. h = vals['hour']
  189. if m.group('minute'):
  190. min = vals['minute']
  191. if m.group('second'):
  192. s = vals['second']
  193. if frac != None:
  194. # ok, fractions of hour?
  195. if min == None:
  196. frac, min = math.modf(frac * 60.0)
  197. min = int(min)
  198. # fractions of second?
  199. if s == None:
  200. frac, s = math.modf(frac * 60.0)
  201. s = int(s)
  202. # and extract microseconds...
  203. us = int(frac * 1000000)
  204. if m.group('tzempty') == 'Z':
  205. offsetmins = 0
  206. else:
  207. # timezone: hour diff with sign
  208. offsetmins = vals['tzh'] * 60
  209. tzm = m.group('tzm')
  210. # add optional minutes
  211. if tzm != None:
  212. tzm = int(tzm)
  213. offsetmins += tzm if offsetmins > 0 else -tzm
  214. tz = _get_fixed_offset_tz(offsetmins)
  215. return datetime.datetime(year, month, day, h, min, s, us, tz)