datetime_math.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835
  1. """!@package grass.temporal
  2. @brief GRASS Python scripting module (temporal GIS functions)
  3. Temporal GIS datetime math functions to be used in library functions and modules.
  4. (C) 2011-2013 by the GRASS Development Team
  5. This program is free software under the GNU General Public
  6. License (>=v2). Read the file COPYING that comes with GRASS
  7. for details.
  8. @author Soeren Gebbert
  9. """
  10. from datetime import datetime, date, time, timedelta
  11. from core import *
  12. import copy
  13. try:
  14. import dateutil.parser as parser
  15. has_dateutil = True
  16. except:
  17. has_dateutil = False
  18. DAY_IN_SECONDS = 86400
  19. SECOND_AS_DAY = 1.1574074074074073e-05
  20. ###############################################################################
  21. def relative_time_to_time_delta(value):
  22. """!Convert the double value representing days
  23. into a timedelta object.
  24. """
  25. days = int(value)
  26. seconds = value % 1
  27. seconds = round(seconds * DAY_IN_SECONDS)
  28. return timedelta(days, seconds)
  29. ###############################################################################
  30. def time_delta_to_relative_time(delta):
  31. """!Convert the time delta into a
  32. double value, representing days.
  33. """
  34. return float(delta.days) + float(delta.seconds * SECOND_AS_DAY)
  35. ###############################################################################
  36. def relative_time_to_time_delta_seconds(value):
  37. """!Convert the double value representing seconds
  38. into a timedelta object.
  39. """
  40. days = (value / 86400)
  41. seconds = int(value % 86400)
  42. return timedelta(days, seconds)
  43. ###############################################################################
  44. def time_delta_to_relative_time_seconds(delta):
  45. """!Convert the time delta into a
  46. double value, representing seconds.
  47. """
  48. return float(delta.days * DAY_IN_SECONDS) + float(delta.seconds)
  49. ###############################################################################
  50. def decrement_datetime_by_string(mydate, increment, mult=1):
  51. """!Return a new datetime object decremented with the provided
  52. relative dates specified as string.
  53. Additional a multiplier can be specified to multiply the increment
  54. before adding to the provided datetime object.
  55. Usage:
  56. @code
  57. >>> dt = datetime(2001, 1, 1, 0, 0, 0)
  58. >>> string = "31 days"
  59. >>> decrement_datetime_by_string(dt, string)
  60. datetime.datetime(2000, 12, 1, 0, 0)
  61. >>> dt = datetime(2001, 1, 1, 0, 0, 0)
  62. >>> string = "1 month"
  63. >>> decrement_datetime_by_string(dt, string)
  64. datetime.datetime(2000, 12, 1, 0, 0)
  65. >>> dt = datetime(2001, 1, 1, 0, 0, 0)
  66. >>> string = "2 month"
  67. >>> decrement_datetime_by_string(dt, string)
  68. datetime.datetime(2000, 11, 1, 0, 0)
  69. >>> dt = datetime(2001, 1, 1, 0, 0, 0)
  70. >>> string = "24 months"
  71. >>> decrement_datetime_by_string(dt, string)
  72. datetime.datetime(1999, 1, 1, 0, 0)
  73. >>> dt = datetime(2001, 1, 1, 0, 0, 0)
  74. >>> string = "48 months"
  75. >>> decrement_datetime_by_string(dt, string)
  76. datetime.datetime(1997, 1, 1, 0, 0)
  77. >>> dt = datetime(2001, 6, 1, 0, 0, 0)
  78. >>> string = "5 months"
  79. >>> decrement_datetime_by_string(dt, string)
  80. datetime.datetime(2001, 1, 1, 0, 0)
  81. >>> dt = datetime(2001, 6, 1, 0, 0, 0)
  82. >>> string = "7 months"
  83. >>> decrement_datetime_by_string(dt, string)
  84. datetime.datetime(2000, 11, 1, 0, 0)
  85. >>> dt = datetime(2001, 1, 1, 0, 0, 0)
  86. >>> string = "1 year"
  87. >>> decrement_datetime_by_string(dt, string)
  88. datetime.datetime(2000, 1, 1, 0, 0)
  89. @endcode
  90. @param mydate A datetime object to incremented
  91. @param increment A string providing increment information:
  92. The string may include comma separated values of type seconds,
  93. minutes, hours, days, weeks, months and years
  94. Example: Increment the datetime 2001-01-01 00:00:00
  95. with "60 seconds, 4 minutes, 12 hours, 10 days, 1 weeks, 5 months, 1 years"
  96. will result in the datetime 2003-02-18 12:05:00
  97. @param mult A multiplier, default is 1
  98. @return The new datetime object or none in case of an error
  99. """
  100. return modify_datetime_by_string(mydate, increment, mult, sign=int(-1))
  101. ###############################################################################
  102. def increment_datetime_by_string(mydate, increment, mult=1):
  103. """!Return a new datetime object incremented with the provided
  104. relative dates specified as string.
  105. Additional a multiplier can be specified to multiply the increment
  106. before adding to the provided datetime object.
  107. Usage:
  108. @code
  109. >>> dt = datetime(2001, 9, 1, 0, 0, 0)
  110. >>> string = "60 seconds, 4 minutes, 12 hours, 10 days, 1 weeks, 5 months, 1 years"
  111. >>> increment_datetime_by_string(dt, string)
  112. datetime.datetime(2003, 2, 18, 12, 5)
  113. >>> dt = datetime(2001, 11, 1, 0, 0, 0)
  114. >>> string = "1 months"
  115. >>> increment_datetime_by_string(dt, string)
  116. datetime.datetime(2001, 12, 1, 0, 0)
  117. >>> dt = datetime(2001, 11, 1, 0, 0, 0)
  118. >>> string = "13 months"
  119. >>> increment_datetime_by_string(dt, string)
  120. datetime.datetime(2002, 12, 1, 0, 0)
  121. >>> dt = datetime(2001, 1, 1, 0, 0, 0)
  122. >>> string = "72 months"
  123. >>> increment_datetime_by_string(dt, string)
  124. datetime.datetime(2007, 1, 1, 0, 0)
  125. >>> dt = datetime(2001, 1, 1, 0, 0, 0)
  126. >>> string = "72 months"
  127. >>> increment_datetime_by_string(dt, string)
  128. datetime.datetime(2007, 1, 1, 0, 0)
  129. >>> dt = datetime(2001, 1, 1, 0, 0, 0)
  130. >>> string = "5 minutes"
  131. >>> increment_datetime_by_string(dt, string)
  132. datetime.datetime(2001, 1, 1, 0, 5)
  133. >>> dt = datetime(2001, 1, 1, 0, 0, 0)
  134. >>> string = "49 hours"
  135. >>> increment_datetime_by_string(dt, string)
  136. datetime.datetime(2001, 1, 3, 1, 0)
  137. >>> dt = datetime(2001, 1, 1, 0, 0, 0)
  138. >>> string = "3600 seconds"
  139. >>> increment_datetime_by_string(dt, string)
  140. datetime.datetime(2001, 1, 1, 1, 0)
  141. >>> dt = datetime(2001, 1, 1, 0, 0, 0)
  142. >>> string = "30 days"
  143. >>> increment_datetime_by_string(dt, string)
  144. datetime.datetime(2001, 1, 31, 0, 0)
  145. @endcode
  146. @param mydate A datetime object to incremented
  147. @param increment A string providing increment information:
  148. The string may include comma separated values of type seconds,
  149. minutes, hours, days, weeks, months and years
  150. Example: Increment the datetime 2001-01-01 00:00:00
  151. with "60 seconds, 4 minutes, 12 hours, 10 days, 1 weeks, 5 months, 1 years"
  152. will result in the datetime 2003-02-18 12:05:00
  153. @param mult A multiplier, default is 1
  154. @return The new datetime object or none in case of an error
  155. """
  156. return modify_datetime_by_string(mydate, increment, mult, sign=int(1))
  157. ###############################################################################
  158. def modify_datetime_by_string(mydate, increment, mult=1, sign=1):
  159. """!Return a new datetime object incremented with the provided
  160. relative dates specified as string.
  161. Additional a multiplier can be specified to multiply the increment
  162. before adding to the provided datetime object.
  163. @param mydate A datetime object to incremented
  164. @param increment A string providing increment information:
  165. The string may include comma separated values of type seconds,
  166. minutes, hours, days, weeks, months and years
  167. Example: Increment the datetime 2001-01-01 00:00:00
  168. with "60 seconds, 4 minutes, 12 hours, 10 days, 1 weeks, 5 months, 1 years"
  169. will result in the datetime 2003-02-18 12:05:00
  170. @param mult A multiplier, default is 1
  171. @param sign Choose 1 for positive sign (incrementing) or -1 for negative
  172. sign (decrementing).
  173. @return The new datetime object or none in case of an error
  174. """
  175. sign = int(sign)
  176. if sign != 1 and sign != -1:
  177. return None
  178. if increment:
  179. seconds = 0
  180. minutes = 0
  181. hours = 0
  182. days = 0
  183. weeks = 0
  184. months = 0
  185. years = 0
  186. inclist = []
  187. # Split the increment string
  188. incparts = increment.split(",")
  189. for incpart in incparts:
  190. inclist.append(incpart.strip().split(" "))
  191. for inc in inclist:
  192. msgr = get_tgis_message_interface()
  193. if len(inc) < 2:
  194. msgr.error(_("Wrong increment format: %s") % (increment))
  195. return None
  196. if inc[1].find("seconds") >= 0 or inc[1].find("second") >= 0:
  197. seconds = sign * mult * int(inc[0])
  198. elif inc[1].find("minutes") >= 0 or inc[1].find("minute") >= 0:
  199. minutes = sign * mult * int(inc[0])
  200. elif inc[1].find("hours") >= 0 or inc[1].find("hour") >= 0:
  201. hours = sign * mult * int(inc[0])
  202. elif inc[1].find("days") >= 0 or inc[1].find("day") >= 0:
  203. days = sign * mult * int(inc[0])
  204. elif inc[1].find("weeks") >= 0 or inc[1].find("week") >= 0:
  205. weeks = sign * mult * int(inc[0])
  206. elif inc[1].find("months") >= 0 or inc[1].find("month") >= 0:
  207. months = sign * mult * int(inc[0])
  208. elif inc[1].find("years") >= 0 or inc[1].find("year") >= 0:
  209. years = sign * mult * int(inc[0])
  210. else:
  211. msgr.error(_("Wrong increment format: %s") % (increment))
  212. return None
  213. return modify_datetime(mydate, years, months, weeks, days, hours, minutes, seconds)
  214. return mydate
  215. ###############################################################################
  216. def modify_datetime(mydate, years=0, months=0, weeks=0, days=0, hours=0,
  217. minutes=0, seconds=0):
  218. """!Return a new datetime object incremented with the provided
  219. relative dates and times"""
  220. tdelta_seconds = timedelta(seconds=seconds)
  221. tdelta_minutes = timedelta(minutes=minutes)
  222. tdelta_hours = timedelta(hours=hours)
  223. tdelta_days = timedelta(days=days)
  224. tdelta_weeks = timedelta(weeks=weeks)
  225. tdelta_months = timedelta(0)
  226. tdelta_years = timedelta(0)
  227. if months > 0:
  228. # Compute the actual number of days in the month to add as timedelta
  229. year = mydate.year
  230. month = mydate.month
  231. all_months = int(months) + int(month)
  232. years_to_add = int(all_months / 12.001)
  233. residual_months = all_months - (years_to_add * 12)
  234. # Make a deep copy of the datetime object
  235. dt1 = copy.copy(mydate)
  236. # Make sure the month starts with a 1
  237. if residual_months == 0:
  238. residual_months = 1
  239. try:
  240. dt1 = dt1.replace(year=year + years_to_add, month=residual_months)
  241. except:
  242. raise
  243. tdelta_months = dt1 - mydate
  244. elif months < 0:
  245. # Compute the actual number of days in the month to add as timedelta
  246. year = mydate.year
  247. month = mydate.month
  248. years_to_remove = 0
  249. all_months = int(months) + int(month)
  250. if all_months <= 0:
  251. years_to_remove = abs(int(all_months / 12.001))
  252. residual_months = all_months + (years_to_remove * 12)
  253. years_to_remove += 1
  254. else:
  255. residual_months = all_months
  256. # Make a deep copy of the datetime object
  257. dt1 = copy.copy(mydate)
  258. # Correct the months
  259. if residual_months <= 0:
  260. residual_months += 12
  261. try:
  262. dt1 = dt1.replace(year=year - years_to_remove, month=residual_months)
  263. except:
  264. raise
  265. tdelta_months = dt1 - mydate
  266. if years != 0:
  267. # Make a deep copy of the datetime object
  268. dt1 = copy.copy(mydate)
  269. # Compute the number of days
  270. dt1 = dt1.replace(year=mydate.year + int(years))
  271. tdelta_years = dt1 - mydate
  272. return mydate + tdelta_seconds + tdelta_minutes + tdelta_hours + \
  273. tdelta_days + tdelta_weeks + tdelta_months + tdelta_years
  274. ###############################################################################
  275. def adjust_datetime_to_granularity(mydate, granularity):
  276. """!Modify the datetime object to fit the given granularity
  277. - Years will start at the first of Januar
  278. - Months will start at the first day of the month
  279. - Days will start at the first Hour of the day
  280. - Hours will start at the first minute of an hour
  281. - Minutes will start at the first second of a minute
  282. Usage:
  283. @code
  284. >>> dt = datetime(2001, 8, 8, 12,30,30)
  285. >>> adjust_datetime_to_granularity(dt, "5 seconds")
  286. datetime.datetime(2001, 8, 8, 12, 30, 30)
  287. >>> adjust_datetime_to_granularity(dt, "20 minutes")
  288. datetime.datetime(2001, 8, 8, 12, 30)
  289. >>> adjust_datetime_to_granularity(dt, "20 minutes")
  290. datetime.datetime(2001, 8, 8, 12, 30)
  291. >>> adjust_datetime_to_granularity(dt, "3 hours")
  292. datetime.datetime(2001, 8, 8, 12, 0)
  293. >>> adjust_datetime_to_granularity(dt, "5 days")
  294. datetime.datetime(2001, 8, 8, 0, 0)
  295. >>> adjust_datetime_to_granularity(dt, "2 weeks")
  296. datetime.datetime(2001, 8, 6, 0, 0)
  297. >>> adjust_datetime_to_granularity(dt, "6 months")
  298. datetime.datetime(2001, 8, 1, 0, 0)
  299. >>> adjust_datetime_to_granularity(dt, "2 years")
  300. datetime.datetime(2001, 1, 1, 0, 0)
  301. >>> adjust_datetime_to_granularity(dt, "2 years, 3 months, 5 days, 3 hours, 3 minutes, 2 seconds")
  302. datetime.datetime(2001, 8, 8, 12, 30, 30)
  303. >>> adjust_datetime_to_granularity(dt, "3 months, 5 days, 3 minutes")
  304. datetime.datetime(2001, 8, 8, 12, 30)
  305. >>> adjust_datetime_to_granularity(dt, "3 weeks, 5 days")
  306. datetime.datetime(2001, 8, 8, 0, 0)
  307. @endcode
  308. """
  309. if granularity:
  310. has_seconds = False
  311. has_minutes = False
  312. has_hours = False
  313. has_days = False
  314. has_weeks = False
  315. has_months = False
  316. has_years = False
  317. seconds = mydate.second
  318. minutes = mydate.minute
  319. hours = mydate.hour
  320. days = mydate.day
  321. weekday = mydate.weekday()
  322. months = mydate.month
  323. years = mydate.year
  324. granlist = []
  325. # Split the increment string
  326. granparts = granularity.split(",")
  327. for granpart in granparts:
  328. granlist.append(granpart.strip().split(" "))
  329. for inc in granlist:
  330. if inc[1].find("seconds") >= 0 or inc[1].find("second") >= 0:
  331. has_seconds = True
  332. elif inc[1].find("minutes") >= 0 or inc[1].find("minute") >= 0:
  333. has_minutes = True
  334. elif inc[1].find("hours") >= 0 or inc[1].find("hour") >= 0:
  335. has_hours = True
  336. elif inc[1].find("days") >= 0 or inc[1].find("day") >= 0:
  337. has_days = True
  338. elif inc[1].find("weeks") >= 0 or inc[1].find("week") >= 0:
  339. has_weeks = True
  340. elif inc[1].find("months") >= 0 or inc[1].find("month") >= 0:
  341. has_months = True
  342. elif inc[1].find("years") >= 0 or inc[1].find("year") >= 0:
  343. has_years = True
  344. else:
  345. msgr = get_tgis_message_interface()
  346. msgr.error(_("Wrong granularity format: %s") % (granularity))
  347. return None
  348. if has_seconds:
  349. pass
  350. elif has_minutes: # Start at 0 seconds
  351. seconds = 0
  352. elif has_hours: # Start at 0 minutes and seconds
  353. seconds = 0
  354. minutes = 0
  355. elif has_days: # Start at 0 hours, minutes and seconds
  356. seconds = 0
  357. minutes = 0
  358. hours = 0
  359. elif has_weeks: # Start at the first day of the week (Monday) at 00:00:00
  360. seconds = 0
  361. minutes = 0
  362. hours = 0
  363. if days > weekday:
  364. days = days - weekday # this needs to be fixed
  365. else:
  366. days = days + weekday # this needs to be fixed
  367. elif has_months: # Start at the first day of the month at 00:00:00
  368. seconds = 0
  369. minutes = 0
  370. hours = 0
  371. days = 1
  372. elif has_years: # Start at the first day of the first month at 00:00:00
  373. seconds = 0
  374. minutes = 0
  375. hours = 0
  376. days = 1
  377. months = 1
  378. dt = copy.copy(mydate)
  379. return dt.replace(year=years, month=months, day=days,
  380. hour=hours, minute=minutes, second=seconds)
  381. ###############################################################################
  382. def compute_datetime_delta(start, end):
  383. """!Return a dictionary with the accumulated delta in year, month, day,
  384. hour, minute and second
  385. Usage:
  386. @code
  387. >>> start = datetime(2001, 1, 1, 00,00,00)
  388. >>> end = datetime(2001, 1, 1, 00,00,00)
  389. >>> compute_datetime_delta(start, end)
  390. {'hour': 0, 'month': 0, 'second': 0, 'max_days': 0, 'year': 0, 'day': 0, 'minute': 0}
  391. >>> start = datetime(2001, 1, 1, 00,00,14)
  392. >>> end = datetime(2001, 1, 1, 00,00,44)
  393. >>> compute_datetime_delta(start, end)
  394. {'hour': 0, 'month': 0, 'second': 30, 'max_days': 0, 'year': 0, 'day': 0, 'minute': 0}
  395. >>> start = datetime(2001, 1, 1, 00,00,44)
  396. >>> end = datetime(2001, 1, 1, 00,01,14)
  397. >>> compute_datetime_delta(start, end)
  398. {'hour': 0, 'month': 0, 'second': 30, 'max_days': 0, 'year': 0, 'day': 0, 'minute': 1}
  399. >>> start = datetime(2001, 1, 1, 00,00,30)
  400. >>> end = datetime(2001, 1, 1, 00,05,30)
  401. >>> compute_datetime_delta(start, end)
  402. {'hour': 0, 'month': 0, 'second': 300, 'max_days': 0, 'year': 0, 'day': 0, 'minute': 5}
  403. >>> start = datetime(2001, 1, 1, 00,00,00)
  404. >>> end = datetime(2001, 1, 1, 00,01,00)
  405. >>> compute_datetime_delta(start, end)
  406. {'hour': 0, 'month': 0, 'second': 0, 'max_days': 0, 'year': 0, 'day': 0, 'minute': 1}
  407. >>> start = datetime(2011,10,31, 00,45,00)
  408. >>> end = datetime(2011,10,31, 01,45,00)
  409. >>> compute_datetime_delta(start, end)
  410. {'hour': 1, 'second': 0, 'max_days': 0, 'year': 0, 'day': 0, 'minute': 60}
  411. >>> start = datetime(2011,10,31, 00,45,00)
  412. >>> end = datetime(2011,10,31, 01,15,00)
  413. >>> compute_datetime_delta(start, end)
  414. {'hour': 1, 'second': 0, 'max_days': 0, 'year': 0, 'day': 0, 'minute': 30}
  415. >>> start = datetime(2011,10,31, 00,45,00)
  416. >>> end = datetime(2011,10,31, 12,15,00)
  417. >>> compute_datetime_delta(start, end)
  418. {'hour': 12, 'second': 0, 'max_days': 0, 'year': 0, 'day': 0, 'minute': 690}
  419. >>> start = datetime(2011,10,31, 00,00,00)
  420. >>> end = datetime(2011,10,31, 01,00,00)
  421. >>> compute_datetime_delta(start, end)
  422. {'hour': 1, 'second': 0, 'max_days': 0, 'year': 0, 'day': 0, 'minute': 0}
  423. >>> start = datetime(2011,10,31, 00,00,00)
  424. >>> end = datetime(2011,11,01, 01,00,00)
  425. >>> compute_datetime_delta(start, end)
  426. {'hour': 25, 'second': 0, 'max_days': 1, 'year': 0, 'day': 1, 'minute': 0}
  427. >>> start = datetime(2011,10,31, 12,00,00)
  428. >>> end = datetime(2011,11,01, 06,00,00)
  429. >>> compute_datetime_delta(start, end)
  430. {'hour': 18, 'second': 0, 'max_days': 0, 'year': 0, 'day': 0, 'minute': 0}
  431. >>> start = datetime(2011,11,01, 00,00,00)
  432. >>> end = datetime(2011,12,01, 01,00,00)
  433. >>> compute_datetime_delta(start, end)
  434. {'hour': 721, 'month': 1, 'second': 0, 'max_days': 30, 'year': 0, 'day': 0, 'minute': 0}
  435. >>> start = datetime(2011,11,01, 00,00,00)
  436. >>> end = datetime(2011,11,05, 00,00,00)
  437. >>> compute_datetime_delta(start, end)
  438. {'hour': 0, 'second': 0, 'max_days': 4, 'year': 0, 'day': 4, 'minute': 0}
  439. >>> start = datetime(2011,10,06, 00,00,00)
  440. >>> end = datetime(2011,11,05, 00,00,00)
  441. >>> compute_datetime_delta(start, end)
  442. {'hour': 0, 'second': 0, 'max_days': 30, 'year': 0, 'day': 30, 'minute': 0}
  443. >>> start = datetime(2011,12,02, 00,00,00)
  444. >>> end = datetime(2012,01,01, 00,00,00)
  445. >>> compute_datetime_delta(start, end)
  446. {'hour': 0, 'second': 0, 'max_days': 30, 'year': 1, 'day': 30, 'minute': 0}
  447. >>> start = datetime(2011,01,01, 00,00,00)
  448. >>> end = datetime(2011,02,01, 00,00,00)
  449. >>> compute_datetime_delta(start, end)
  450. {'hour': 0, 'month': 1, 'second': 0, 'max_days': 31, 'year': 0, 'day': 0, 'minute': 0}
  451. >>> start = datetime(2011,12,01, 00,00,00)
  452. >>> end = datetime(2012,01,01, 00,00,00)
  453. >>> compute_datetime_delta(start, end)
  454. {'hour': 0, 'month': 1, 'second': 0, 'max_days': 31, 'year': 1, 'day': 0, 'minute': 0}
  455. >>> start = datetime(2011,12,01, 00,00,00)
  456. >>> end = datetime(2012,06,01, 00,00,00)
  457. >>> compute_datetime_delta(start, end)
  458. {'hour': 0, 'month': 6, 'second': 0, 'max_days': 183, 'year': 1, 'day': 0, 'minute': 0}
  459. >>> start = datetime(2011,06,01, 00,00,00)
  460. >>> end = datetime(2021,06,01, 00,00,00)
  461. >>> compute_datetime_delta(start, end)
  462. {'hour': 0, 'month': 120, 'second': 0, 'max_days': 3653, 'year': 10, 'day': 0, 'minute': 0}
  463. >>> start = datetime(2011,06,01, 00,00,00)
  464. >>> end = datetime(2012,06,01, 12,00,00)
  465. >>> compute_datetime_delta(start, end)
  466. {'hour': 8796, 'month': 12, 'second': 0, 'max_days': 366, 'year': 1, 'day': 0, 'minute': 0}
  467. >>> start = datetime(2011,06,01, 00,00,00)
  468. >>> end = datetime(2012,06,01, 12,30,00)
  469. >>> compute_datetime_delta(start, end)
  470. {'hour': 8796, 'month': 12, 'second': 0, 'max_days': 366, 'year': 1, 'day': 0, 'minute': 527790}
  471. >>> start = datetime(2011,06,01, 00,00,00)
  472. >>> end = datetime(2012,06,01, 12,00,05)
  473. >>> compute_datetime_delta(start, end)
  474. {'hour': 8796, 'month': 12, 'second': 31665605, 'max_days': 366, 'year': 1, 'day': 0, 'minute': 0}
  475. >>> start = datetime(2011,06,01, 00,00,00)
  476. >>> end = datetime(2012,06,01, 00,30,00)
  477. >>> compute_datetime_delta(start, end)
  478. {'hour': 0, 'month': 12, 'second': 0, 'max_days': 366, 'year': 1, 'day': 0, 'minute': 527070}
  479. >>> start = datetime(2011,06,01, 00,00,00)
  480. >>> end = datetime(2012,06,01, 00,00,05)
  481. >>> compute_datetime_delta(start, end)
  482. {'hour': 0, 'month': 12, 'second': 31622405, 'max_days': 366, 'year': 1, 'day': 0, 'minute': 0}
  483. @endcode
  484. @return A dictionary with year, month, day, hour, minute and second as keys()
  485. """
  486. comp = {}
  487. day_diff = (end - start).days
  488. comp["max_days"] = day_diff
  489. # Date
  490. # Count full years
  491. d = end.year - start.year
  492. comp["year"] = d
  493. # Count full months
  494. if start.month == 1 and end.month == 1:
  495. comp["month"] = 0
  496. elif start.day == 1 and end.day == 1:
  497. d = end.month - start.month
  498. if d < 0:
  499. d = d + 12 * comp["year"]
  500. elif d == 0:
  501. d = 12 * comp["year"]
  502. comp["month"] = d
  503. # Count full days
  504. if start.day == 1 and end.day == 1:
  505. comp["day"] = 0
  506. else:
  507. comp["day"] = day_diff
  508. # Time
  509. # Hours
  510. if start.hour == 0 and end.hour == 0:
  511. comp["hour"] = 0
  512. else:
  513. d = end.hour - start.hour
  514. if d < 0:
  515. d = d + 24 + 24 * day_diff
  516. else:
  517. d = d + 24 * day_diff
  518. comp["hour"] = d
  519. # Minutes
  520. if start.minute == 0 and end.minute == 0:
  521. comp["minute"] = 0
  522. else:
  523. d = end.minute - start.minute
  524. if d != 0:
  525. if comp["hour"]:
  526. d = d + 60 * comp["hour"]
  527. else:
  528. d = d + 24 * 60 * day_diff
  529. elif d == 0:
  530. if comp["hour"]:
  531. d = 60 * comp["hour"]
  532. else:
  533. d = 24 * 60 * day_diff
  534. comp["minute"] = d
  535. # Seconds
  536. if start.second == 0 and end.second == 0:
  537. comp["second"] = 0
  538. else:
  539. d = end.second - start.second
  540. if d != 0:
  541. if comp["minute"]:
  542. d = d + 60 * comp["minute"]
  543. elif comp["hour"]:
  544. d = d + 3600 * comp["hour"]
  545. else:
  546. d = d + 24 * 60 * 60 * day_diff
  547. elif d == 0:
  548. if comp["minute"]:
  549. d = 60 * comp["minute"]
  550. elif comp["hour"]:
  551. d = 3600 * comp["hour"]
  552. else:
  553. d = 24 * 60 * 60 * day_diff
  554. comp["second"] = d
  555. return comp
  556. ###############################################################################
  557. def check_datetime_string(time_string):
  558. """!Check if a string can be converted into a datetime object
  559. Supported ISO string formats are:
  560. - YYYY-mm-dd
  561. - YYYY-mm-dd HH:MM:SS
  562. Time zones are not supported
  563. @param time_string The time string to be checked for conversion
  564. @return datetime object or an error message string in case of an error
  565. """
  566. global has_dateutil
  567. if has_dateutil:
  568. # First check if there is only a single number, which specifies relative time.
  569. # dateutil will interprete a single number as a valid time string, so we have
  570. # to catch this case beforehand
  571. try:
  572. value = int(time_string)
  573. return _("Time string seems to specify relative time")
  574. except ValueError:
  575. pass
  576. try:
  577. time_object = parser.parse(time_string)
  578. except Exception as inst:
  579. time_object = str(inst)
  580. return time_object
  581. # BC is not supported
  582. if time_string.find("bc") > 0:
  583. return _("Dates Before Christ are not supported")
  584. # BC is not supported
  585. if time_string.find("+") > 0:
  586. return _("Time zones are not supported")
  587. if time_string.find(":") > 0:
  588. time_format = "%Y-%m-%d %H:%M:%S"
  589. else:
  590. time_format = "%Y-%m-%d"
  591. try:
  592. return datetime.strptime(time_string, time_format)
  593. except:
  594. return _("Unable to parse time string: %s"%time_string)
  595. ###############################################################################
  596. def string_to_datetime(time_string):
  597. """!Convert a string into a datetime object
  598. In case datutil is not installed the supported ISO string formats are:
  599. - YYYY-mm-dd
  600. - YYYY-mm-dd HH:MM:SS
  601. - Time zones are not supported
  602. If dateutil is installed, all string formats of the dateutil module
  603. are supported, as well as time zones
  604. @param time_string The time string to convert
  605. @return datetime object or None in case the string
  606. could not be converted
  607. """
  608. if not isinstance(time_string, str):
  609. return None
  610. time_object = check_datetime_string(time_string)
  611. if not isinstance(time_object, datetime):
  612. msgr = get_tgis_message_interface()
  613. msgr.error(str(time_object))
  614. return None
  615. return time_object
  616. ###############################################################################
  617. def datetime_to_grass_datetime_string(dt):
  618. """!Convert a python datetime object into a GRASS datetime string
  619. @code
  620. >>> import grass.temporal as tgis
  621. >>> import dateutil.parser as parser
  622. >>> dt = parser.parse("2011-01-01 10:00:00 +01:30")
  623. >>> tgis.datetime_to_grass_datetime_string(dt)
  624. '01 jan 2011 10:00:00 +0090'
  625. >>> dt = parser.parse("2011-01-01 10:00:00 +02:30")
  626. >>> tgis.datetime_to_grass_datetime_string(dt)
  627. '01 jan 2011 10:00:00 +0150'
  628. >>> dt = parser.parse("2011-01-01 10:00:00 +12:00")
  629. >>> tgis.datetime_to_grass_datetime_string(dt)
  630. '01 jan 2011 10:00:00 +0720'
  631. >>> dt = parser.parse("2011-01-01 10:00:00 -01:30")
  632. >>> tgis.datetime_to_grass_datetime_string(dt)
  633. '01 jan 2011 10:00:00 -0090'
  634. @endcode
  635. """
  636. # GRASS datetime month names
  637. month_names = ["", "jan", "feb", "mar", "apr", "may", "jun",
  638. "jul", "aug", "sep", "oct", "nov", "dec"]
  639. # Check for time zone info in the datetime object
  640. if dt.tzinfo is not None:
  641. tz = dt.tzinfo.utcoffset(0)
  642. if tz.seconds > 86400 / 2:
  643. tz = (tz.seconds - 86400) / 60
  644. else:
  645. tz = tz.seconds/60
  646. string = "%.2i %s %.2i %.2i:%.2i:%.2i %+.4i" % (dt.day,
  647. month_names[dt.month], dt.year,
  648. dt.hour, dt.minute, dt.second, tz)
  649. else:
  650. string = "%.2i %s %.4i %.2i:%.2i:%.2i" % (dt.day, month_names[
  651. dt.month], dt.year, dt.hour, dt.minute, dt.second)
  652. return string
  653. ###############################################################################
  654. if __name__ == "__main__":
  655. import doctest
  656. doctest.testmod()