timeutils.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. """
  2. utility functions for converting between
  3. datetime, iso date and 14-digit timestamp
  4. """
  5. import re
  6. import time
  7. import datetime
  8. import calendar
  9. from email.utils import parsedate, formatdate
  10. #=================================================================
  11. # str <-> datetime conversion
  12. #=================================================================
  13. DATE_TIMESPLIT = re.compile(r'[^\d]')
  14. TIMESTAMP_14 = '%Y%m%d%H%M%S'
  15. ISO_DT = '%Y-%m-%dT%H:%M:%SZ'
  16. PAD_14_DOWN = '10000101000000'
  17. PAD_14_UP = '29991231235959'
  18. PAD_6_UP = '299912'
  19. PAD_MICRO = '000000'
  20. def iso_date_to_datetime(string):
  21. """
  22. >>> iso_date_to_datetime('2013-12-26T10:11:12Z')
  23. datetime.datetime(2013, 12, 26, 10, 11, 12)
  24. >>> iso_date_to_datetime('2013-12-26T10:11:12.456789Z')
  25. datetime.datetime(2013, 12, 26, 10, 11, 12, 456789)
  26. >>> iso_date_to_datetime('2013-12-26T10:11:12.30Z')
  27. datetime.datetime(2013, 12, 26, 10, 11, 12, 300000)
  28. >>> iso_date_to_datetime('2013-12-26T10:11:12.00001Z')
  29. datetime.datetime(2013, 12, 26, 10, 11, 12, 10)
  30. >>> iso_date_to_datetime('2013-12-26T10:11:12.000001Z')
  31. datetime.datetime(2013, 12, 26, 10, 11, 12, 1)
  32. >>> iso_date_to_datetime('2013-12-26T10:11:12.0000001Z')
  33. datetime.datetime(2013, 12, 26, 10, 11, 12)
  34. >>> iso_date_to_datetime('2013-12-26T10:11:12.000000Z')
  35. datetime.datetime(2013, 12, 26, 10, 11, 12)
  36. """
  37. nums = DATE_TIMESPLIT.split(string)
  38. if nums[-1] == '':
  39. nums = nums[:-1]
  40. if len(nums) == 7:
  41. nums[6] = nums[6][:6]
  42. nums[6] += PAD_MICRO[len(nums[6]):]
  43. the_datetime = datetime.datetime(*(int(num) for num in nums))
  44. return the_datetime
  45. def http_date_to_datetime(string):
  46. """
  47. >>> http_date_to_datetime('Thu, 26 Dec 2013 09:50:10 GMT')
  48. datetime.datetime(2013, 12, 26, 9, 50, 10)
  49. """
  50. return datetime.datetime(*parsedate(string)[:6])
  51. def datetime_to_http_date(the_datetime):
  52. """
  53. >>> datetime_to_http_date(datetime.datetime(2013, 12, 26, 9, 50, 10))
  54. 'Thu, 26 Dec 2013 09:50:10 GMT'
  55. # Verify inverses
  56. >>> x = 'Thu, 26 Dec 2013 09:50:10 GMT'
  57. >>> datetime_to_http_date(http_date_to_datetime(x)) == x
  58. True
  59. """
  60. timeval = calendar.timegm(the_datetime.utctimetuple())
  61. return formatdate(timeval=timeval,
  62. localtime=False,
  63. usegmt=True)
  64. def datetime_to_iso_date(the_datetime, use_micros=False):
  65. """
  66. >>> datetime_to_iso_date(datetime.datetime(2013, 12, 26, 10, 11, 12))
  67. '2013-12-26T10:11:12Z'
  68. >>> datetime_to_iso_date(datetime.datetime(2013, 12, 26, 10, 11, 12, 456789))
  69. '2013-12-26T10:11:12Z'
  70. >>> datetime_to_iso_date(datetime.datetime(2013, 12, 26, 10, 11, 12), use_micros=True)
  71. '2013-12-26T10:11:12Z'
  72. >>> datetime_to_iso_date(datetime.datetime(2013, 12, 26, 10, 11, 12, 456789), use_micros=True)
  73. '2013-12-26T10:11:12.456789Z'
  74. >>> datetime_to_iso_date(datetime.datetime(2013, 12, 26, 10, 11, 12, 1), use_micros=True)
  75. '2013-12-26T10:11:12.000001Z'
  76. """
  77. if not use_micros:
  78. return the_datetime.strftime(ISO_DT)
  79. else:
  80. return the_datetime.isoformat() + 'Z'
  81. def datetime_to_timestamp(the_datetime):
  82. """
  83. >>> datetime_to_timestamp(datetime.datetime(2013, 12, 26, 10, 11, 12))
  84. '20131226101112'
  85. """
  86. return the_datetime.strftime(TIMESTAMP_14)
  87. def timestamp_now():
  88. """
  89. >>> len(timestamp_now())
  90. 14
  91. """
  92. return datetime_to_timestamp(datetime.datetime.utcnow())
  93. def timestamp20_now():
  94. """
  95. Create 20-digit timestamp, useful to timestamping temp files
  96. >>> n = timestamp20_now()
  97. >>> timestamp20_now() >= n
  98. True
  99. >>> len(n)
  100. 20
  101. """
  102. now = datetime.datetime.utcnow()
  103. return now.strftime('%Y%m%d%H%M%S%f')
  104. def iso_date_to_timestamp(string):
  105. """
  106. >>> iso_date_to_timestamp('2013-12-26T10:11:12Z')
  107. '20131226101112'
  108. >>> iso_date_to_timestamp('2013-12-26T10:11:12')
  109. '20131226101112'
  110. """
  111. return datetime_to_timestamp(iso_date_to_datetime(string))
  112. def timestamp_to_iso_date(string):
  113. """
  114. >>> timestamp_to_iso_date('20131226101112')
  115. '2013-12-26T10:11:12Z'
  116. >>> timestamp_to_iso_date('20131226101112')
  117. '2013-12-26T10:11:12Z'
  118. """
  119. return datetime_to_iso_date(timestamp_to_datetime(string))
  120. def http_date_to_timestamp(string):
  121. """
  122. >>> http_date_to_timestamp('Thu, 26 Dec 2013 09:50:00 GMT')
  123. '20131226095000'
  124. >>> http_date_to_timestamp('Sun, 26 Jan 2014 20:08:04 GMT')
  125. '20140126200804'
  126. """
  127. return datetime_to_timestamp(http_date_to_datetime(string))
  128. # pad to certain length (default 6)
  129. def pad_timestamp(string, pad_str=PAD_6_UP):
  130. """
  131. >>> pad_timestamp('20')
  132. '209912'
  133. >>> pad_timestamp('2014')
  134. '201412'
  135. >>> pad_timestamp('20141011')
  136. '20141011'
  137. >>> pad_timestamp('201410110010')
  138. '201410110010'
  139. """
  140. str_len = len(string)
  141. pad_len = len(pad_str)
  142. if str_len < pad_len:
  143. string = string + pad_str[str_len:]
  144. return string
  145. def timestamp_to_datetime(string):
  146. """
  147. # >14-digit -- rest ignored
  148. >>> timestamp_to_datetime('2014122609501011')
  149. datetime.datetime(2014, 12, 26, 9, 50, 10)
  150. # 14-digit
  151. >>> timestamp_to_datetime('20141226095010')
  152. datetime.datetime(2014, 12, 26, 9, 50, 10)
  153. # 13-digit padding
  154. >>> timestamp_to_datetime('2014122609501')
  155. datetime.datetime(2014, 12, 26, 9, 50, 59)
  156. # 12-digit padding
  157. >>> timestamp_to_datetime('201412260950')
  158. datetime.datetime(2014, 12, 26, 9, 50, 59)
  159. # 11-digit padding
  160. >>> timestamp_to_datetime('20141226095')
  161. datetime.datetime(2014, 12, 26, 9, 59, 59)
  162. # 10-digit padding
  163. >>> timestamp_to_datetime('2014122609')
  164. datetime.datetime(2014, 12, 26, 9, 59, 59)
  165. # 9-digit padding
  166. >>> timestamp_to_datetime('201412260')
  167. datetime.datetime(2014, 12, 26, 23, 59, 59)
  168. # 8-digit padding
  169. >>> timestamp_to_datetime('20141226')
  170. datetime.datetime(2014, 12, 26, 23, 59, 59)
  171. # 7-digit padding
  172. >>> timestamp_to_datetime('2014122')
  173. datetime.datetime(2014, 12, 31, 23, 59, 59)
  174. # 6-digit padding
  175. >>> timestamp_to_datetime('201410')
  176. datetime.datetime(2014, 10, 31, 23, 59, 59)
  177. # 5-digit padding
  178. >>> timestamp_to_datetime('20141')
  179. datetime.datetime(2014, 12, 31, 23, 59, 59)
  180. # 4-digit padding
  181. >>> timestamp_to_datetime('2014')
  182. datetime.datetime(2014, 12, 31, 23, 59, 59)
  183. # 3-digit padding
  184. >>> timestamp_to_datetime('201')
  185. datetime.datetime(2019, 12, 31, 23, 59, 59)
  186. # 2-digit padding
  187. >>> timestamp_to_datetime('20')
  188. datetime.datetime(2099, 12, 31, 23, 59, 59)
  189. # 1-digit padding
  190. >>> timestamp_to_datetime('2')
  191. datetime.datetime(2999, 12, 31, 23, 59, 59)
  192. # 1-digit out-of-range padding
  193. >>> timestamp_to_datetime('3')
  194. datetime.datetime(2999, 12, 31, 23, 59, 59)
  195. # 0-digit padding
  196. >>> timestamp_to_datetime('')
  197. datetime.datetime(2999, 12, 31, 23, 59, 59)
  198. # bad month
  199. >>> timestamp_to_datetime('20131709005601')
  200. datetime.datetime(2013, 12, 9, 0, 56, 1)
  201. # all out of range except minutes
  202. >>> timestamp_to_datetime('40001965252477')
  203. datetime.datetime(2999, 12, 31, 23, 24, 59)
  204. # not a number!
  205. >>> timestamp_to_datetime('2010abc')
  206. datetime.datetime(2010, 12, 31, 23, 59, 59)
  207. """
  208. # pad to 6 digits
  209. string = pad_timestamp(string, PAD_6_UP)
  210. def clamp(val, min_, max_):
  211. try:
  212. val = int(val)
  213. val = max(min_, min(val, max_))
  214. return val
  215. except:
  216. return max_
  217. def extract(string, start, end, min_, max_):
  218. if len(string) >= end:
  219. return clamp(string[start:end], min_, max_)
  220. else:
  221. return max_
  222. # now parse, clamp to boundary
  223. year = extract(string, 0, 4, 1900, 2999)
  224. month = extract(string, 4, 6, 1, 12)
  225. day = extract(string, 6, 8, 1, calendar.monthrange(year, month)[1])
  226. hour = extract(string, 8, 10, 0, 23)
  227. minute = extract(string, 10, 12, 0, 59)
  228. second = extract(string, 12, 14, 0, 59)
  229. return datetime.datetime(year=year,
  230. month=month,
  231. day=day,
  232. hour=hour,
  233. minute=minute,
  234. second=second)
  235. #return time.strptime(pad_timestamp(string), TIMESTAMP_14)
  236. def timestamp_to_sec(string):
  237. """
  238. >>> timestamp_to_sec('20131226095010')
  239. 1388051410
  240. # rounds to end of 2014
  241. >>> timestamp_to_sec('2014')
  242. 1420070399
  243. """
  244. return calendar.timegm(timestamp_to_datetime(string).utctimetuple())
  245. def sec_to_timestamp(secs):
  246. """
  247. >>> sec_to_timestamp(1388051410)
  248. '20131226095010'
  249. >>> sec_to_timestamp(1420070399)
  250. '20141231235959'
  251. """
  252. return datetime_to_timestamp(datetime.datetime.utcfromtimestamp(secs))
  253. def timestamp_to_http_date(string):
  254. """
  255. >>> timestamp_to_http_date('20131226095000')
  256. 'Thu, 26 Dec 2013 09:50:00 GMT'
  257. >>> timestamp_to_http_date('20140126200804')
  258. 'Sun, 26 Jan 2014 20:08:04 GMT'
  259. """
  260. return datetime_to_http_date(timestamp_to_datetime(string))
  261. if __name__ == "__main__": #pragma: no cover
  262. import doctest
  263. doctest.testmod()