Translate human time ranges to a machine-readable way

trange.py 9.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. #!/usr/bin/python
  2. import datetime
  3. import re
  4. import string
  5. _valid_keywords = ['last', 'this', 'next']
  6. class Unit(object):
  7. def __str__(self):
  8. return self.ref.isoformat()
  9. def __init__(self, ref):
  10. self.ref = ref if ref else datetime.datetime.now()
  11. def seekb(self):
  12. return self._seek(-1)
  13. def seekf(self):
  14. return self._seek(1)
  15. def nseekb(self):
  16. return self._nseek(-1)
  17. def nseekf(self):
  18. return self._nseek(1)
  19. @staticmethod
  20. def make(name, ref=None):
  21. _valid = {
  22. 'day': Day,
  23. 'hour': Hour,
  24. 'minute': Minute,
  25. 'month': Month,
  26. 'second': Second,
  27. 'week': Week,
  28. 'year': Year,
  29. }
  30. if name not in _valid.keys():
  31. raise ValueError("invalid unit name: %s" % name)
  32. return _valid[name](ref)
  33. def apply_offset(self, offset):
  34. new = self
  35. while offset:
  36. if offset > 0:
  37. new = new.seekf()
  38. offset = offset - 1
  39. elif offset < 0:
  40. new = new.seekb()
  41. offset = offset + 1
  42. return new
  43. class Second(Unit):
  44. def start(self):
  45. return Second(ref=datetime.datetime(
  46. self.ref.year,
  47. self.ref.month,
  48. self.ref.day,
  49. self.ref.hour,
  50. self.ref.minute,
  51. self.ref.second,
  52. ))
  53. def end(self):
  54. return Second(ref=datetime.datetime(
  55. self.ref.year,
  56. self.ref.month,
  57. self.ref.day,
  58. self.ref.hour,
  59. self.ref.minute,
  60. self.ref.second,
  61. ))
  62. def _seek(self, step):
  63. #
  64. # Seek to next/previous second
  65. #
  66. if step > 0:
  67. otherref = self.ref + datetime.timedelta(seconds=abs(step))
  68. elif step < 0:
  69. otherref = self.ref - datetime.timedelta(seconds=abs(step))
  70. else:
  71. otherref = self.ref
  72. return Second(ref=datetime.datetime(
  73. otherref.year,
  74. otherref.month,
  75. otherref.day,
  76. otherref.hour,
  77. otherref.minute,
  78. otherref.second,
  79. ))
  80. class Minute(Unit):
  81. def start(self):
  82. return Minute(ref=datetime.datetime(
  83. self.ref.year,
  84. self.ref.month,
  85. self.ref.day,
  86. self.ref.hour,
  87. self.ref.minute,
  88. datetime.datetime.min.second,
  89. ))
  90. def end(self):
  91. return Minute(ref=datetime.datetime(
  92. self.ref.year,
  93. self.ref.month,
  94. self.ref.day,
  95. self.ref.hour,
  96. self.ref.minute,
  97. datetime.datetime.max.second,
  98. ))
  99. def _seek(self, step):
  100. #
  101. # Seek to next/previous minute
  102. #
  103. if step > 0:
  104. otherref = self.ref + datetime.timedelta(seconds=abs(step) * 60)
  105. elif step < 0:
  106. otherref = self.ref - datetime.timedelta(seconds=abs(step) * 60)
  107. else:
  108. otherref = self.ref
  109. return Minute(ref=datetime.datetime(
  110. otherref.year,
  111. otherref.month,
  112. otherref.day,
  113. otherref.hour,
  114. otherref.minute,
  115. otherref.second,
  116. ))
  117. class Hour(Unit):
  118. def start(self):
  119. return Hour(ref=datetime.datetime(
  120. self.ref.year,
  121. self.ref.month,
  122. self.ref.day,
  123. self.ref.hour,
  124. datetime.datetime.min.minute,
  125. datetime.datetime.min.second,
  126. ))
  127. def end(self):
  128. return Hour(ref=datetime.datetime(
  129. self.ref.year,
  130. self.ref.month,
  131. self.ref.day,
  132. self.ref.hour,
  133. datetime.datetime.max.minute,
  134. datetime.datetime.max.second,
  135. ))
  136. def _seek(self, step):
  137. #
  138. # Seek to next/previous minute
  139. #
  140. if step > 0:
  141. otherref = self.ref + datetime.timedelta(seconds=abs(step) * 3600)
  142. elif step < 0:
  143. otherref = self.ref - datetime.timedelta(seconds=abs(step) * 3600)
  144. else:
  145. otherref = self.ref
  146. return Hour(ref=datetime.datetime(
  147. otherref.year,
  148. otherref.month,
  149. otherref.day,
  150. otherref.hour,
  151. otherref.minute,
  152. otherref.second,
  153. ))
  154. class Day(Unit):
  155. def start(self):
  156. return Day(ref=datetime.datetime(
  157. self.ref.year,
  158. self.ref.month,
  159. self.ref.day,
  160. datetime.datetime.min.hour,
  161. datetime.datetime.min.minute,
  162. datetime.datetime.min.second,
  163. ))
  164. def end(self):
  165. return Day(ref=datetime.datetime(
  166. self.ref.year,
  167. self.ref.month,
  168. self.ref.day,
  169. datetime.datetime.max.hour,
  170. datetime.datetime.max.minute,
  171. datetime.datetime.max.second,
  172. ))
  173. def _seek(self, step):
  174. #
  175. # Seek to next/previous day
  176. #
  177. bigdelta = datetime.timedelta(days=step)
  178. return Day(ref=self.ref + bigdelta)
  179. def _nseek(self, step):
  180. #
  181. # Almost seek to next/previous day
  182. #
  183. return Day(ref=datetime.datetime(
  184. self.ref.year,
  185. self.ref.month,
  186. self.ref.day,
  187. self.ref.hour,
  188. self.ref.minute,
  189. self.ref.second,
  190. ))
  191. class Week(Unit):
  192. def start(self):
  193. early = self.nseekb()
  194. return Week(ref=datetime.datetime(
  195. early.ref.year,
  196. early.ref.month,
  197. early.ref.day,
  198. datetime.datetime.min.hour,
  199. datetime.datetime.min.minute,
  200. datetime.datetime.min.second,
  201. ))
  202. def end(self):
  203. late = self.nseekf()
  204. return Week(ref=datetime.datetime(
  205. late.ref.year,
  206. late.ref.month,
  207. late.ref.day,
  208. datetime.datetime.max.hour,
  209. datetime.datetime.max.minute,
  210. datetime.datetime.max.second,
  211. ))
  212. def _seek(self, step):
  213. #
  214. # Seek to next/previous week
  215. #
  216. delta = datetime.timedelta(days=step)
  217. newref = self.ref
  218. while newref.isocalendar()[1] == self.ref.isocalendar()[1]:
  219. newref = newref + delta
  220. return Week(ref=newref)
  221. def _nseek(self, step):
  222. #
  223. # Almost seek to next/previous week
  224. #
  225. delta = datetime.timedelta(days=step)
  226. newref = self.ref
  227. while newref.isocalendar()[1] == self.ref.isocalendar()[1]:
  228. newref = newref + delta
  229. return Month(ref=newref - delta)
  230. class Month(Unit):
  231. def start(self):
  232. early = self.nseekb()
  233. return Month(ref=datetime.datetime(
  234. early.ref.year,
  235. early.ref.month,
  236. early.ref.day,
  237. datetime.datetime.min.hour,
  238. datetime.datetime.min.minute,
  239. datetime.datetime.min.second,
  240. ))
  241. def end(self):
  242. late = self.nseekf()
  243. return Month(ref=datetime.datetime(
  244. late.ref.year,
  245. late.ref.month,
  246. late.ref.day,
  247. datetime.datetime.max.hour,
  248. datetime.datetime.max.minute,
  249. datetime.datetime.max.second,
  250. ))
  251. def _seek(self, step):
  252. #
  253. # Seek to next/previous month
  254. #
  255. delta = datetime.timedelta(days=step)
  256. newref = self.ref
  257. while newref.month == self.ref.month:
  258. newref = newref + delta
  259. return Month(ref=newref)
  260. def _nseek(self, step):
  261. #
  262. # Almost seek to next/previous month
  263. #
  264. delta = datetime.timedelta(days=step)
  265. newref = self.ref
  266. while newref.month == self.ref.month:
  267. newref = newref + delta
  268. return Month(ref=newref - delta)
  269. class Year(Unit):
  270. def start(self):
  271. return Year(ref=datetime.datetime(
  272. self.ref.year,
  273. datetime.datetime.min.month,
  274. datetime.datetime.min.day,
  275. datetime.datetime.min.hour,
  276. datetime.datetime.min.minute,
  277. datetime.datetime.min.second,
  278. ))
  279. def end(self):
  280. return Year(ref=datetime.datetime(
  281. self.ref.year,
  282. datetime.datetime.max.month,
  283. datetime.datetime.max.day,
  284. datetime.datetime.max.hour,
  285. datetime.datetime.max.minute,
  286. datetime.datetime.max.second,
  287. ))
  288. def apply_offset(self, offset):
  289. return Year(ref=datetime.datetime(
  290. self.ref.year + offset,
  291. self.ref.month,
  292. self.ref.day,
  293. self.ref.hour,
  294. self.ref.minute,
  295. self.ref.second,
  296. ))
  297. def parse_offset(text):
  298. m = re.match('^(last|this|next)([+-]+|[+-][0-9]+)?$', text)
  299. if not m:
  300. raise ValueError('bad offset phrase: %s' % text)
  301. direction, offphrase = m.groups()
  302. if direction == 'this':
  303. amount = 0
  304. elif direction == 'next':
  305. amount = 1
  306. elif direction == 'last':
  307. amount = -1
  308. if not offphrase:
  309. pass
  310. elif offphrase[-1] in string.digits:
  311. sign, number = re.match('([+-])([0-9]+)', offphrase).groups()
  312. if sign == '+':
  313. amount = amount + int(number)
  314. else:
  315. amount = amount - int(number)
  316. else:
  317. for char in offphrase:
  318. if char == '+':
  319. amount = amount + 1
  320. else:
  321. amount = amount - 1
  322. return amount
  323. class Range(object):
  324. def start(self):
  325. return self.unit.start()
  326. def end(self):
  327. return self.unit.end()
  328. def __init__(self, offset, unitname):
  329. self.unit = Unit.make(unitname).apply_offset(parse_offset(offset))