relativedelta.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  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 excellent
  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,
  159. 366]
  160. for idx, ydays in enumerate(ydayidx):
  161. if yday <= ydays:
  162. self.month = idx + 1
  163. if idx == 0:
  164. self.day = yday
  165. else:
  166. self.day = yday - ydayidx[idx - 1]
  167. break
  168. else:
  169. raise ValueError("invalid year day (%d)" % yday)
  170. self._fix()
  171. def _fix(self):
  172. if abs(self.microseconds) > 999999:
  173. s = self.microseconds // abs(self.microseconds)
  174. div, mod = divmod(self.microseconds * s, 1000000)
  175. self.microseconds = mod * s
  176. self.seconds += div * s
  177. if abs(self.seconds) > 59:
  178. s = self.seconds // abs(self.seconds)
  179. div, mod = divmod(self.seconds * s, 60)
  180. self.seconds = mod * s
  181. self.minutes += div * s
  182. if abs(self.minutes) > 59:
  183. s = self.minutes // abs(self.minutes)
  184. div, mod = divmod(self.minutes * s, 60)
  185. self.minutes = mod * s
  186. self.hours += div * s
  187. if abs(self.hours) > 23:
  188. s = self.hours // abs(self.hours)
  189. div, mod = divmod(self.hours * s, 24)
  190. self.hours = mod * s
  191. self.days += div * s
  192. if abs(self.months) > 11:
  193. s = self.months // abs(self.months)
  194. div, mod = divmod(self.months * s, 12)
  195. self.months = mod * s
  196. self.years += div * s
  197. if (self.hours or self.minutes or self.seconds or self.microseconds or
  198. self.hour is not None or self.minute is not None or
  199. self.second is not None or self.microsecond is not None):
  200. self._has_time = 1
  201. else:
  202. self._has_time = 0
  203. def _set_months(self, months):
  204. self.months = months
  205. if abs(self.months) > 11:
  206. s = self.months // abs(self.months)
  207. div, mod = divmod(self.months * s, 12)
  208. self.months = mod * s
  209. self.years = div * s
  210. else:
  211. self.years = 0
  212. def __radd__(self, other):
  213. if not isinstance(other, datetime.date):
  214. raise TypeError("unsupported type for add operation")
  215. elif self._has_time and not isinstance(other, datetime.datetime):
  216. other = datetime.datetime.fromordinal(other.toordinal())
  217. year = (self.year or other.year) + self.years
  218. month = self.month or other.month
  219. if self.months:
  220. assert 1 <= abs(self.months) <= 12
  221. month += self.months
  222. if month > 12:
  223. year += 1
  224. month -= 12
  225. elif month < 1:
  226. year -= 1
  227. month += 12
  228. day = min(calendar.monthrange(year, month)[1],
  229. self.day or other.day)
  230. repl = {"year": year, "month": month, "day": day}
  231. for attr in ["hour", "minute", "second", "microsecond"]:
  232. value = getattr(self, attr)
  233. if value is not None:
  234. repl[attr] = value
  235. days = self.days
  236. if self.leapdays and month > 2 and calendar.isleap(year):
  237. days += self.leapdays
  238. ret = (other.replace(**repl)
  239. + datetime.timedelta(days=days,
  240. hours=self.hours,
  241. minutes=self.minutes,
  242. seconds=self.seconds,
  243. microseconds=self.microseconds))
  244. if self.weekday:
  245. weekday, nth = self.weekday.weekday, self.weekday.n or 1
  246. jumpdays = (abs(nth) - 1) * 7
  247. if nth > 0:
  248. jumpdays += (7 - ret.weekday() + weekday) % 7
  249. else:
  250. jumpdays += (ret.weekday() - weekday) % 7
  251. jumpdays *= -1
  252. ret += datetime.timedelta(days=jumpdays)
  253. return ret
  254. def __rsub__(self, other):
  255. return self.__neg__().__radd__(other)
  256. def __add__(self, other):
  257. if not isinstance(other, relativedelta):
  258. raise TypeError("unsupported type for add operation")
  259. return relativedelta(years=other.years + self.years,
  260. months=other.months + self.months,
  261. days=other.days + self.days,
  262. hours=other.hours + self.hours,
  263. minutes=other.minutes + self.minutes,
  264. seconds=other.seconds + self.seconds,
  265. microseconds=other.microseconds + self.microseconds,
  266. leapdays=other.leapdays or self.leapdays,
  267. year=other.year or self.year,
  268. month=other.month or self.month,
  269. day=other.day or self.day,
  270. weekday=other.weekday or self.weekday,
  271. hour=other.hour or self.hour,
  272. minute=other.minute or self.minute,
  273. second=other.second or self.second,
  274. microsecond=other.second or self.microsecond)
  275. def __sub__(self, other):
  276. if not isinstance(other, relativedelta):
  277. raise TypeError("unsupported type for sub operation")
  278. return relativedelta(years=other.years - self.years,
  279. months=other.months - self.months,
  280. days=other.days - self.days,
  281. hours=other.hours - self.hours,
  282. minutes=other.minutes - self.minutes,
  283. seconds=other.seconds - self.seconds,
  284. microseconds=other.microseconds - self.microseconds,
  285. leapdays=other.leapdays or self.leapdays,
  286. year=other.year or self.year,
  287. month=other.month or self.month,
  288. day=other.day or self.day,
  289. weekday=other.weekday or self.weekday,
  290. hour=other.hour or self.hour,
  291. minute=other.minute or self.minute,
  292. second=other.second or self.second,
  293. microsecond=other.second or self.microsecond)
  294. def __neg__(self):
  295. return relativedelta(years= -self.years,
  296. months= -self.months,
  297. days= -self.days,
  298. hours= -self.hours,
  299. minutes= -self.minutes,
  300. seconds= -self.seconds,
  301. microseconds= -self.microseconds,
  302. leapdays=self.leapdays,
  303. year=self.year,
  304. month=self.month,
  305. day=self.day,
  306. weekday=self.weekday,
  307. hour=self.hour,
  308. minute=self.minute,
  309. second=self.second,
  310. microsecond=self.microsecond)
  311. def __nonzero__(self):
  312. return not (not self.years and
  313. not self.months and
  314. not self.days and
  315. not self.hours and
  316. not self.minutes and
  317. not self.seconds and
  318. not self.microseconds and
  319. not self.leapdays and
  320. self.year is None and
  321. self.month is None and
  322. self.day is None and
  323. self.weekday is None and
  324. self.hour is None and
  325. self.minute is None and
  326. self.second is None and
  327. self.microsecond is None)
  328. __bool__ = __nonzero__
  329. def __mul__(self, other):
  330. f = float(other)
  331. return relativedelta(years=self.years * f,
  332. months=self.months * f,
  333. days=self.days * f,
  334. hours=self.hours * f,
  335. minutes=self.minutes * f,
  336. seconds=self.seconds * f,
  337. microseconds=self.microseconds * f,
  338. leapdays=self.leapdays,
  339. year=self.year,
  340. month=self.month,
  341. day=self.day,
  342. weekday=self.weekday,
  343. hour=self.hour,
  344. minute=self.minute,
  345. second=self.second,
  346. microsecond=self.microsecond)
  347. def __eq__(self, other):
  348. if not isinstance(other, relativedelta):
  349. return False
  350. if self.weekday or other.weekday:
  351. if not self.weekday or not other.weekday:
  352. return False
  353. if self.weekday.weekday != other.weekday.weekday:
  354. return False
  355. n1, n2 = self.weekday.n, other.weekday.n
  356. if n1 != n2 and not ((not n1 or n1 == 1) and (not n2 or n2 == 1)):
  357. return False
  358. return (self.years == other.years and
  359. self.months == other.months and
  360. self.days == other.days and
  361. self.hours == other.hours and
  362. self.minutes == other.minutes and
  363. self.seconds == other.seconds and
  364. self.leapdays == other.leapdays and
  365. self.year == other.year and
  366. self.month == other.month and
  367. self.day == other.day and
  368. self.hour == other.hour and
  369. self.minute == other.minute and
  370. self.second == other.second and
  371. self.microsecond == other.microsecond)
  372. def __ne__(self, other):
  373. return not self.__eq__(other)
  374. def __div__(self, other):
  375. return self.__mul__(1 / float(other))
  376. def __repr__(self):
  377. l = []
  378. for attr in ["years", "months", "days", "leapdays",
  379. "hours", "minutes", "seconds", "microseconds"]:
  380. value = getattr(self, attr)
  381. if value:
  382. l.append("%s=%+d" % (attr, value))
  383. for attr in ["year", "month", "day", "weekday",
  384. "hour", "minute", "second", "microsecond"]:
  385. value = getattr(self, attr)
  386. if value is not None:
  387. l.append("%s=%s" % (attr, repr(value)))
  388. return "%s(%s)" % (self.__class__.__name__, ", ".join(l))
  389. # vim:ts=4:sw=4:et