relativedelta.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. """
  2. Copyright (c) 2003-2010 Gustavo Niemeyer <gustavo@niemeyer.net>
  3. This module offers extensions to the standard python 2.3+
  4. datetime module.
  5. """
  6. __author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
  7. __license__ = "PSF License"
  8. import datetime
  9. import calendar
  10. __all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
  11. class weekday(object):
  12. __slots__ = ["weekday", "n"]
  13. def __init__(self, weekday, n=None):
  14. self.weekday = weekday
  15. self.n = n
  16. def __call__(self, n):
  17. if n == self.n:
  18. return self
  19. else:
  20. return self.__class__(self.weekday, n)
  21. def __eq__(self, other):
  22. try:
  23. if self.weekday != other.weekday or self.n != other.n:
  24. return False
  25. except AttributeError:
  26. return False
  27. return True
  28. def __repr__(self):
  29. s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday]
  30. if not self.n:
  31. return s
  32. else:
  33. return "%s(%+d)" % (s, self.n)
  34. MO, TU, WE, TH, FR, SA, SU = weekdays = tuple([weekday(x) for x in range(7)])
  35. class relativedelta:
  36. """
  37. The relativedelta type is based on the specification of the excelent
  38. work done by M.-A. Lemburg in his mx.DateTime extension. However,
  39. notice that this type does *NOT* implement the same algorithm as
  40. his work. Do *NOT* expect it to behave like mx.DateTime's counterpart.
  41. There's two different ways to build a relativedelta instance. The
  42. first one is passing it two date/datetime classes:
  43. relativedelta(datetime1, datetime2)
  44. And the other way is to use the following keyword arguments:
  45. year, month, day, hour, minute, second, microsecond:
  46. Absolute information.
  47. years, months, weeks, days, hours, minutes, seconds, microseconds:
  48. Relative information, may be negative.
  49. weekday:
  50. One of the weekday instances (MO, TU, etc). These instances may
  51. receive a parameter N, specifying the Nth weekday, which could
  52. be positive or negative (like MO(+1) or MO(-2). Not specifying
  53. it is the same as specifying +1. You can also use an integer,
  54. where 0=MO.
  55. leapdays:
  56. Will add given days to the date found, if year is a leap
  57. year, and the date found is post 28 of february.
  58. yearday, nlyearday:
  59. Set the yearday or the non-leap year day (jump leap days).
  60. These are converted to day/month/leapdays information.
  61. Here is the behavior of operations with relativedelta:
  62. 1) Calculate the absolute year, using the 'year' argument, or the
  63. original datetime year, if the argument is not present.
  64. 2) Add the relative 'years' argument to the absolute year.
  65. 3) Do steps 1 and 2 for month/months.
  66. 4) Calculate the absolute day, using the 'day' argument, or the
  67. original datetime day, if the argument is not present. Then,
  68. subtract from the day until it fits in the year and month
  69. found after their operations.
  70. 5) Add the relative 'days' argument to the absolute day. Notice
  71. that the 'weeks' argument is multiplied by 7 and added to
  72. 'days'.
  73. 6) Do steps 1 and 2 for hour/hours, minute/minutes, second/seconds,
  74. microsecond/microseconds.
  75. 7) If the 'weekday' argument is present, calculate the weekday,
  76. with the given (wday, nth) tuple. wday is the index of the
  77. weekday (0-6, 0=Mon), and nth is the number of weeks to add
  78. forward or backward, depending on its signal. Notice that if
  79. the calculated date is already Monday, for example, using
  80. (0, 1) or (0, -1) won't change the day.
  81. """
  82. def __init__(self, dt1=None, dt2=None,
  83. years=0, months=0, days=0, leapdays=0, weeks=0,
  84. hours=0, minutes=0, seconds=0, microseconds=0,
  85. year=None, month=None, day=None, weekday=None,
  86. yearday=None, nlyearday=None,
  87. hour=None, minute=None, second=None, microsecond=None):
  88. if dt1 and dt2:
  89. if not isinstance(dt1, datetime.date) or \
  90. not isinstance(dt2, datetime.date):
  91. raise TypeError, "relativedelta only diffs datetime/date"
  92. if type(dt1) is not type(dt2):
  93. if not isinstance(dt1, datetime.datetime):
  94. dt1 = datetime.datetime.fromordinal(dt1.toordinal())
  95. elif not isinstance(dt2, datetime.datetime):
  96. dt2 = datetime.datetime.fromordinal(dt2.toordinal())
  97. self.years = 0
  98. self.months = 0
  99. self.days = 0
  100. self.leapdays = 0
  101. self.hours = 0
  102. self.minutes = 0
  103. self.seconds = 0
  104. self.microseconds = 0
  105. self.year = None
  106. self.month = None
  107. self.day = None
  108. self.weekday = None
  109. self.hour = None
  110. self.minute = None
  111. self.second = None
  112. self.microsecond = None
  113. self._has_time = 0
  114. months = (dt1.year*12+dt1.month)-(dt2.year*12+dt2.month)
  115. self._set_months(months)
  116. dtm = self.__radd__(dt2)
  117. if dt1 < dt2:
  118. while dt1 > dtm:
  119. months += 1
  120. self._set_months(months)
  121. dtm = self.__radd__(dt2)
  122. else:
  123. while dt1 < dtm:
  124. months -= 1
  125. self._set_months(months)
  126. dtm = self.__radd__(dt2)
  127. delta = dt1 - dtm
  128. self.seconds = delta.seconds+delta.days*86400
  129. self.microseconds = delta.microseconds
  130. else:
  131. self.years = years
  132. self.months = months
  133. self.days = days+weeks*7
  134. self.leapdays = leapdays
  135. self.hours = hours
  136. self.minutes = minutes
  137. self.seconds = seconds
  138. self.microseconds = microseconds
  139. self.year = year
  140. self.month = month
  141. self.day = day
  142. self.hour = hour
  143. self.minute = minute
  144. self.second = second
  145. self.microsecond = microsecond
  146. if type(weekday) is int:
  147. self.weekday = weekdays[weekday]
  148. else:
  149. self.weekday = weekday
  150. yday = 0
  151. if nlyearday:
  152. yday = nlyearday
  153. elif yearday:
  154. yday = yearday
  155. if yearday > 59:
  156. self.leapdays = -1
  157. if yday:
  158. ydayidx = [31,59,90,120,151,181,212,243,273,304,334,366]
  159. for idx, ydays in enumerate(ydayidx):
  160. if yday <= ydays:
  161. self.month = idx+1
  162. if idx == 0:
  163. self.day = yday
  164. else:
  165. self.day = yday-ydayidx[idx-1]
  166. break
  167. else:
  168. raise ValueError, "invalid year day (%d)" % yday
  169. self._fix()
  170. def _fix(self):
  171. if abs(self.microseconds) > 999999:
  172. s = self.microseconds//abs(self.microseconds)
  173. div, mod = divmod(self.microseconds*s, 1000000)
  174. self.microseconds = mod*s
  175. self.seconds += div*s
  176. if abs(self.seconds) > 59:
  177. s = self.seconds//abs(self.seconds)
  178. div, mod = divmod(self.seconds*s, 60)
  179. self.seconds = mod*s
  180. self.minutes += div*s
  181. if abs(self.minutes) > 59:
  182. s = self.minutes//abs(self.minutes)
  183. div, mod = divmod(self.minutes*s, 60)
  184. self.minutes = mod*s
  185. self.hours += div*s
  186. if abs(self.hours) > 23:
  187. s = self.hours//abs(self.hours)
  188. div, mod = divmod(self.hours*s, 24)
  189. self.hours = mod*s
  190. self.days += div*s
  191. if abs(self.months) > 11:
  192. s = self.months//abs(self.months)
  193. div, mod = divmod(self.months*s, 12)
  194. self.months = mod*s
  195. self.years += div*s
  196. if (self.hours or self.minutes or self.seconds or self.microseconds or
  197. self.hour is not None or self.minute is not None or
  198. self.second is not None or self.microsecond is not None):
  199. self._has_time = 1
  200. else:
  201. self._has_time = 0
  202. def _set_months(self, months):
  203. self.months = months
  204. if abs(self.months) > 11:
  205. s = self.months//abs(self.months)
  206. div, mod = divmod(self.months*s, 12)
  207. self.months = mod*s
  208. self.years = div*s
  209. else:
  210. self.years = 0
  211. def __radd__(self, other):
  212. if not isinstance(other, datetime.date):
  213. raise TypeError, "unsupported type for add operation"
  214. elif self._has_time and not isinstance(other, datetime.datetime):
  215. other = datetime.datetime.fromordinal(other.toordinal())
  216. year = (self.year or other.year)+self.years
  217. month = self.month or other.month
  218. if self.months:
  219. assert 1 <= abs(self.months) <= 12
  220. month += self.months
  221. if month > 12:
  222. year += 1
  223. month -= 12
  224. elif month < 1:
  225. year -= 1
  226. month += 12
  227. day = min(calendar.monthrange(year, month)[1],
  228. self.day or other.day)
  229. repl = {"year": year, "month": month, "day": day}
  230. for attr in ["hour", "minute", "second", "microsecond"]:
  231. value = getattr(self, attr)
  232. if value is not None:
  233. repl[attr] = value
  234. days = self.days
  235. if self.leapdays and month > 2 and calendar.isleap(year):
  236. days += self.leapdays
  237. ret = (other.replace(**repl)
  238. + datetime.timedelta(days=days,
  239. hours=self.hours,
  240. minutes=self.minutes,
  241. seconds=self.seconds,
  242. microseconds=self.microseconds))
  243. if self.weekday:
  244. weekday, nth = self.weekday.weekday, self.weekday.n or 1
  245. jumpdays = (abs(nth)-1)*7
  246. if nth > 0:
  247. jumpdays += (7-ret.weekday()+weekday)%7
  248. else:
  249. jumpdays += (ret.weekday()-weekday)%7
  250. jumpdays *= -1
  251. ret += datetime.timedelta(days=jumpdays)
  252. return ret
  253. def __rsub__(self, other):
  254. return self.__neg__().__radd__(other)
  255. def __add__(self, other):
  256. if not isinstance(other, relativedelta):
  257. raise TypeError, "unsupported type for add operation"
  258. return relativedelta(years=other.years+self.years,
  259. months=other.months+self.months,
  260. days=other.days+self.days,
  261. hours=other.hours+self.hours,
  262. minutes=other.minutes+self.minutes,
  263. seconds=other.seconds+self.seconds,
  264. microseconds=other.microseconds+self.microseconds,
  265. leapdays=other.leapdays or self.leapdays,
  266. year=other.year or self.year,
  267. month=other.month or self.month,
  268. day=other.day or self.day,
  269. weekday=other.weekday or self.weekday,
  270. hour=other.hour or self.hour,
  271. minute=other.minute or self.minute,
  272. second=other.second or self.second,
  273. microsecond=other.second or self.microsecond)
  274. def __sub__(self, other):
  275. if not isinstance(other, relativedelta):
  276. raise TypeError, "unsupported type for sub operation"
  277. return relativedelta(years=other.years-self.years,
  278. months=other.months-self.months,
  279. days=other.days-self.days,
  280. hours=other.hours-self.hours,
  281. minutes=other.minutes-self.minutes,
  282. seconds=other.seconds-self.seconds,
  283. microseconds=other.microseconds-self.microseconds,
  284. leapdays=other.leapdays or self.leapdays,
  285. year=other.year or self.year,
  286. month=other.month or self.month,
  287. day=other.day or self.day,
  288. weekday=other.weekday or self.weekday,
  289. hour=other.hour or self.hour,
  290. minute=other.minute or self.minute,
  291. second=other.second or self.second,
  292. microsecond=other.second or self.microsecond)
  293. def __neg__(self):
  294. return relativedelta(years=-self.years,
  295. months=-self.months,
  296. days=-self.days,
  297. hours=-self.hours,
  298. minutes=-self.minutes,
  299. seconds=-self.seconds,
  300. microseconds=-self.microseconds,
  301. leapdays=self.leapdays,
  302. year=self.year,
  303. month=self.month,
  304. day=self.day,
  305. weekday=self.weekday,
  306. hour=self.hour,
  307. minute=self.minute,
  308. second=self.second,
  309. microsecond=self.microsecond)
  310. def __nonzero__(self):
  311. return not (not self.years and
  312. not self.months and
  313. not self.days and
  314. not self.hours and
  315. not self.minutes and
  316. not self.seconds and
  317. not self.microseconds and
  318. not self.leapdays and
  319. self.year is None and
  320. self.month is None and
  321. self.day is None and
  322. self.weekday is None and
  323. self.hour is None and
  324. self.minute is None and
  325. self.second is None and
  326. self.microsecond is None)
  327. def __mul__(self, other):
  328. f = float(other)
  329. return relativedelta(years=self.years*f,
  330. months=self.months*f,
  331. days=self.days*f,
  332. hours=self.hours*f,
  333. minutes=self.minutes*f,
  334. seconds=self.seconds*f,
  335. microseconds=self.microseconds*f,
  336. leapdays=self.leapdays,
  337. year=self.year,
  338. month=self.month,
  339. day=self.day,
  340. weekday=self.weekday,
  341. hour=self.hour,
  342. minute=self.minute,
  343. second=self.second,
  344. microsecond=self.microsecond)
  345. def __eq__(self, other):
  346. if not isinstance(other, relativedelta):
  347. return False
  348. if self.weekday or other.weekday:
  349. if not self.weekday or not other.weekday:
  350. return False
  351. if self.weekday.weekday != other.weekday.weekday:
  352. return False
  353. n1, n2 = self.weekday.n, other.weekday.n
  354. if n1 != n2 and not ((not n1 or n1 == 1) and (not n2 or n2 == 1)):
  355. return False
  356. return (self.years == other.years and
  357. self.months == other.months and
  358. self.days == other.days and
  359. self.hours == other.hours and
  360. self.minutes == other.minutes and
  361. self.seconds == other.seconds and
  362. self.leapdays == other.leapdays and
  363. self.year == other.year and
  364. self.month == other.month and
  365. self.day == other.day and
  366. self.hour == other.hour and
  367. self.minute == other.minute and
  368. self.second == other.second and
  369. self.microsecond == other.microsecond)
  370. def __ne__(self, other):
  371. return not self.__eq__(other)
  372. def __div__(self, other):
  373. return self.__mul__(1/float(other))
  374. def __repr__(self):
  375. l = []
  376. for attr in ["years", "months", "days", "leapdays",
  377. "hours", "minutes", "seconds", "microseconds"]:
  378. value = getattr(self, attr)
  379. if value:
  380. l.append("%s=%+d" % (attr, value))
  381. for attr in ["year", "month", "day", "weekday",
  382. "hour", "minute", "second", "microsecond"]:
  383. value = getattr(self, attr)
  384. if value is not None:
  385. l.append("%s=%s" % (attr, `value`))
  386. return "%s(%s)" % (self.__class__.__name__, ", ".join(l))
  387. # vim:ts=4:sw=4:et