datetime_math.py 34 KB

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