datetime_math.py 34 KB

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