A REST API for cloud embedded board reservation.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

241 lines
7.7 KiB

  1. # From:
  2. # https://github.com/micktwomey/pyiso8601
  3. #
  4. #
  5. # This file is licensed:
  6. # Copyright (c) 2007 - 2015 Michael Twomey
  7. #
  8. # Permission is hereby granted, free of charge, to any person obtaining a
  9. # copy of this software and associated documentation files (the
  10. # "Software"), to deal in the Software without restriction, including
  11. # without limitation the rights to use, copy, modify, merge, publish,
  12. # distribute, sublicense, and/or sell copies of the Software, and to
  13. # permit persons to whom the Software is furnished to do so, subject to
  14. # the following conditions:
  15. #
  16. # The above copyright notice and this permission notice shall be included
  17. # in all copies or substantial portions of the Software.
  18. #
  19. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  20. # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  21. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  22. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
  23. # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
  24. # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  25. # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  26. """ISO 8601 date time string parsing
  27. Basic usage:
  28. >>> import iso8601
  29. >>> iso8601.parse_date("2007-01-25T12:00:00Z")
  30. datetime.datetime(2007, 1, 25, 12, 0, tzinfo=<iso8601.Utc ...>)
  31. >>>
  32. """
  33. import datetime
  34. from decimal import Decimal
  35. import sys
  36. import re
  37. __all__ = ["parse_date", "ParseError", "UTC",
  38. "FixedOffset"]
  39. if sys.version_info >= (3, 0, 0):
  40. _basestring = str
  41. else:
  42. _basestring = basestring
  43. # Adapted from http://delete.me.uk/2005/03/iso8601.html
  44. ISO8601_REGEX = re.compile(
  45. r"""
  46. (?P<year>[0-9]{4})
  47. (
  48. (
  49. (-(?P<monthdash>[0-9]{1,2}))
  50. |
  51. (?P<month>[0-9]{2})
  52. (?!$) # Don't allow YYYYMM
  53. )
  54. (
  55. (
  56. (-(?P<daydash>[0-9]{1,2}))
  57. |
  58. (?P<day>[0-9]{2})
  59. )
  60. (
  61. (
  62. (?P<separator>[ T])
  63. (?P<hour>[0-9]{2})
  64. (:{0,1}(?P<minute>[0-9]{2})){0,1}
  65. (
  66. :{0,1}(?P<second>[0-9]{1,2})
  67. ([.,](?P<second_fraction>[0-9]+)){0,1}
  68. ){0,1}
  69. (?P<timezone>
  70. Z
  71. |
  72. (
  73. (?P<tz_sign>[-+])
  74. (?P<tz_hour>[0-9]{2})
  75. :{0,1}
  76. (?P<tz_minute>[0-9]{2}){0,1}
  77. )
  78. ){0,1}
  79. ){0,1}
  80. )
  81. ){0,1} # YYYY-MM
  82. ){0,1} # YYYY only
  83. $
  84. """,
  85. re.VERBOSE
  86. )
  87. class ParseError(Exception):
  88. """Raised when there is a problem parsing a date string"""
  89. if sys.version_info >= (3, 2, 0):
  90. UTC = datetime.timezone.utc
  91. def FixedOffset(offset_hours, offset_minutes, name):
  92. return datetime.timezone(
  93. datetime.timedelta(
  94. hours=offset_hours, minutes=offset_minutes),
  95. name)
  96. else:
  97. # Yoinked from python docs
  98. ZERO = datetime.timedelta(0)
  99. class Utc(datetime.tzinfo):
  100. """UTC Timezone
  101. """
  102. def utcoffset(self, dt):
  103. return ZERO
  104. def tzname(self, dt):
  105. return "UTC"
  106. def dst(self, dt):
  107. return ZERO
  108. def __repr__(self):
  109. return "<iso8601.Utc>"
  110. UTC = Utc()
  111. class FixedOffset(datetime.tzinfo):
  112. """Fixed offset in hours and minutes from UTC
  113. """
  114. def __init__(self, offset_hours, offset_minutes, name):
  115. self.__offset_hours = offset_hours # Keep for later __getinitargs__
  116. self.__offset_minutes = offset_minutes # Keep for later __getinitargs__
  117. self.__offset = datetime.timedelta(
  118. hours=offset_hours, minutes=offset_minutes)
  119. self.__name = name
  120. def __eq__(self, other):
  121. if isinstance(other, FixedOffset):
  122. return (
  123. (other.__offset == self.__offset)
  124. and
  125. (other.__name == self.__name)
  126. )
  127. return NotImplemented
  128. def __getinitargs__(self):
  129. return (self.__offset_hours, self.__offset_minutes, self.__name)
  130. def utcoffset(self, dt):
  131. return self.__offset
  132. def tzname(self, dt):
  133. return self.__name
  134. def dst(self, dt):
  135. return ZERO
  136. def __repr__(self):
  137. return "<FixedOffset %r %r>" % (self.__name, self.__offset)
  138. def to_int(d, key, default_to_zero=False, default=None, required=True):
  139. """Pull a value from the dict and convert to int
  140. :param default_to_zero: If the value is None or empty, treat it as zero
  141. :param default: If the value is missing in the dict use this default
  142. """
  143. value = d.get(key) or default
  144. if (value in ["", None]) and default_to_zero:
  145. return 0
  146. if value is None:
  147. if required:
  148. raise ParseError("Unable to read %s from %s" % (key, d))
  149. else:
  150. return int(value)
  151. def parse_timezone(matches, default_timezone=UTC):
  152. """Parses ISO 8601 time zone specs into tzinfo offsets
  153. """
  154. if matches["timezone"] == "Z":
  155. return UTC
  156. # This isn't strictly correct, but it's common to encounter dates without
  157. # timezones so I'll assume the default (which defaults to UTC).
  158. # Addresses issue 4.
  159. if matches["timezone"] is None:
  160. return default_timezone
  161. sign = matches["tz_sign"]
  162. hours = to_int(matches, "tz_hour")
  163. minutes = to_int(matches, "tz_minute", default_to_zero=True)
  164. description = "%s%02d:%02d" % (sign, hours, minutes)
  165. if sign == "-":
  166. hours = -hours
  167. minutes = -minutes
  168. return FixedOffset(hours, minutes, description)
  169. def parse_date(datestring, default_timezone=UTC):
  170. """Parses ISO 8601 dates into datetime objects
  171. The timezone is parsed from the date string. However it is quite common to
  172. have dates without a timezone (not strictly correct). In this case the
  173. default timezone specified in default_timezone is used. This is UTC by
  174. default.
  175. :param datestring: The date to parse as a string
  176. :param default_timezone: A datetime tzinfo instance to use when no timezone
  177. is specified in the datestring. If this is set to
  178. None then a naive datetime object is returned.
  179. :returns: A datetime.datetime instance
  180. :raises: ParseError when there is a problem parsing the date or
  181. constructing the datetime instance.
  182. """
  183. if not isinstance(datestring, _basestring):
  184. raise ParseError("Expecting a string %r" % datestring)
  185. m = ISO8601_REGEX.match(datestring)
  186. if not m:
  187. raise ParseError("Unable to parse date string %r" % datestring)
  188. groups = m.groupdict()
  189. tz = parse_timezone(groups, default_timezone=default_timezone)
  190. groups["second_fraction"] = int(Decimal("0.%s" % (groups["second_fraction"] or 0)) * Decimal("1000000.0"))
  191. try:
  192. return datetime.datetime(
  193. year=to_int(groups, "year"),
  194. month=to_int(groups, "month", default=to_int(groups, "monthdash", required=False, default=1)),
  195. day=to_int(groups, "day", default=to_int(groups, "daydash", required=False, default=1)),
  196. hour=to_int(groups, "hour", default_to_zero=True),
  197. minute=to_int(groups, "minute", default_to_zero=True),
  198. second=to_int(groups, "second", default_to_zero=True),
  199. microsecond=groups["second_fraction"],
  200. tzinfo=tz,
  201. )
  202. except Exception as e:
  203. raise ParseError(e)