time.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. # -*- coding: utf-8 -*-
  2. # Copyright (c) 2021, Brandon Nielsen
  3. # All rights reserved.
  4. #
  5. # This software may be modified and distributed under the terms
  6. # of the BSD license. See the LICENSE file for details.
  7. from aniso8601.builders import TupleBuilder
  8. from aniso8601.builders.python import PythonTimeBuilder
  9. from aniso8601.compat import is_string
  10. from aniso8601.date import parse_date
  11. from aniso8601.decimalfraction import normalize
  12. from aniso8601.exceptions import ISOFormatError
  13. from aniso8601.resolution import TimeResolution
  14. from aniso8601.timezone import parse_timezone
  15. TIMEZONE_DELIMITERS = ["Z", "+", "-"]
  16. def get_time_resolution(isotimestr):
  17. # Valid time formats are:
  18. #
  19. # hh:mm:ss
  20. # hhmmss
  21. # hh:mm
  22. # hhmm
  23. # hh
  24. # hh:mm:ssZ
  25. # hhmmssZ
  26. # hh:mmZ
  27. # hhmmZ
  28. # hhZ
  29. # hh:mm:ss±hh:mm
  30. # hhmmss±hh:mm
  31. # hh:mm±hh:mm
  32. # hhmm±hh:mm
  33. # hh±hh:mm
  34. # hh:mm:ss±hhmm
  35. # hhmmss±hhmm
  36. # hh:mm±hhmm
  37. # hhmm±hhmm
  38. # hh±hhmm
  39. # hh:mm:ss±hh
  40. # hhmmss±hh
  41. # hh:mm±hh
  42. # hhmm±hh
  43. # hh±hh
  44. isotimetuple = parse_time(isotimestr, builder=TupleBuilder)
  45. return _get_time_resolution(isotimetuple)
  46. def get_datetime_resolution(isodatetimestr, delimiter="T"):
  47. # <date>T<time>
  48. #
  49. # Time part cannot be omittted so return time resolution
  50. isotimetuple = parse_datetime(
  51. isodatetimestr, delimiter=delimiter, builder=TupleBuilder
  52. ).time
  53. return _get_time_resolution(isotimetuple)
  54. def _get_time_resolution(isotimetuple):
  55. if isotimetuple.ss is not None:
  56. return TimeResolution.Seconds
  57. if isotimetuple.mm is not None:
  58. return TimeResolution.Minutes
  59. return TimeResolution.Hours
  60. def parse_time(isotimestr, builder=PythonTimeBuilder):
  61. # Given a string in any ISO 8601 time format, return a datetime.time object
  62. # that corresponds to the given time. Fixed offset tzdata will be included
  63. # if UTC offset is given in the input string. Valid time formats are:
  64. #
  65. # hh:mm:ss
  66. # hhmmss
  67. # hh:mm
  68. # hhmm
  69. # hh
  70. # hh:mm:ssZ
  71. # hhmmssZ
  72. # hh:mmZ
  73. # hhmmZ
  74. # hhZ
  75. # hh:mm:ss±hh:mm
  76. # hhmmss±hh:mm
  77. # hh:mm±hh:mm
  78. # hhmm±hh:mm
  79. # hh±hh:mm
  80. # hh:mm:ss±hhmm
  81. # hhmmss±hhmm
  82. # hh:mm±hhmm
  83. # hhmm±hhmm
  84. # hh±hhmm
  85. # hh:mm:ss±hh
  86. # hhmmss±hh
  87. # hh:mm±hh
  88. # hhmm±hh
  89. # hh±hh
  90. if is_string(isotimestr) is False:
  91. raise ValueError("Time must be string.")
  92. if len(isotimestr) == 0:
  93. raise ISOFormatError('"{0}" is not a valid ISO 8601 time.'.format(isotimestr))
  94. timestr = normalize(isotimestr)
  95. hourstr = None
  96. minutestr = None
  97. secondstr = None
  98. tzstr = None
  99. fractionalstr = None
  100. # Split out the timezone
  101. for delimiter in TIMEZONE_DELIMITERS:
  102. delimiteridx = timestr.find(delimiter)
  103. if delimiteridx != -1:
  104. tzstr = timestr[delimiteridx:]
  105. timestr = timestr[0:delimiteridx]
  106. # Split out the fractional component
  107. if timestr.find(".") != -1:
  108. timestr, fractionalstr = timestr.split(".", 1)
  109. if fractionalstr.isdigit() is False:
  110. raise ISOFormatError(
  111. '"{0}" is not a valid ISO 8601 time.'.format(isotimestr)
  112. )
  113. if len(timestr) == 2:
  114. # hh
  115. hourstr = timestr
  116. elif len(timestr) == 4 or len(timestr) == 5:
  117. # hh:mm
  118. # hhmm
  119. if timestr.count(":") == 1:
  120. hourstr, minutestr = timestr.split(":")
  121. else:
  122. hourstr = timestr[0:2]
  123. minutestr = timestr[2:]
  124. elif len(timestr) == 6 or len(timestr) == 8:
  125. # hh:mm:ss
  126. # hhmmss
  127. if timestr.count(":") == 2:
  128. hourstr, minutestr, secondstr = timestr.split(":")
  129. else:
  130. hourstr = timestr[0:2]
  131. minutestr = timestr[2:4]
  132. secondstr = timestr[4:]
  133. else:
  134. raise ISOFormatError('"{0}" is not a valid ISO 8601 time.'.format(isotimestr))
  135. for componentstr in [hourstr, minutestr, secondstr]:
  136. if componentstr is not None and componentstr.isdigit() is False:
  137. raise ISOFormatError(
  138. '"{0}" is not a valid ISO 8601 time.'.format(isotimestr)
  139. )
  140. if fractionalstr is not None:
  141. if secondstr is not None:
  142. secondstr = secondstr + "." + fractionalstr
  143. elif minutestr is not None:
  144. minutestr = minutestr + "." + fractionalstr
  145. else:
  146. hourstr = hourstr + "." + fractionalstr
  147. if tzstr is None:
  148. tz = None
  149. else:
  150. tz = parse_timezone(tzstr, builder=TupleBuilder)
  151. return builder.build_time(hh=hourstr, mm=minutestr, ss=secondstr, tz=tz)
  152. def parse_datetime(isodatetimestr, delimiter="T", builder=PythonTimeBuilder):
  153. # Given a string in ISO 8601 date time format, return a datetime.datetime
  154. # object that corresponds to the given date time.
  155. # By default, the ISO 8601 specified T delimiter is used to split the
  156. # date and time (<date>T<time>). Fixed offset tzdata will be included
  157. # if UTC offset is given in the input string.
  158. if is_string(isodatetimestr) is False:
  159. raise ValueError("Date time must be string.")
  160. if delimiter not in isodatetimestr:
  161. raise ISOFormatError(
  162. 'Delimiter "{0}" is not in combined date time '
  163. 'string "{1}".'.format(delimiter, isodatetimestr)
  164. )
  165. isodatestr, isotimestr = isodatetimestr.split(delimiter, 1)
  166. datepart = parse_date(isodatestr, builder=TupleBuilder)
  167. timepart = parse_time(isotimestr, builder=TupleBuilder)
  168. return builder.build_datetime(datepart, timepart)