Package: urelativedelta
uRelativeDeltaA small fast implementation of relativedeltaurelativedelta provides the following utilities:relativedeltaa simple replacement fordateutil.relativedeltadaterule: a module of useful iterators yielding regular (e.g. monthly) datesproceedural helper functions for shifting date and datetime values by months and yearsit is a python port of thechronoutillibrary for rust (itself inspired bydateutil).BenchmarksOriginally urelativedelta was used for speeding up complicated cashflow bucketing
computations (where there are lots of relativedeltas). It's pretty successful:benchmarkinterpreterurelativedeltapython-dateutilspeedupshift 5mn dates by 100 yearscpython 3.116.04s20.44s3.38xshift 5mn dates by 100 yearspypy 3.90.37s3.17s8.57xsubtract 5mn date pairscpython 3.116.73s17.77s2.64xsubtract 5mn date pairspypy 3.91.20s3.30s2.74xall of which means that using pypy and switching libraries can buy you a ~50x speed improvement!UsageInstall via:pipinstallurelativedeltathen you can runfromdatetimeimportdatetimefromurelativedeltaimportrelativedeltadelta=relativedelta(years=1,months=1,days=1,hours=1)datetime(2050,1,1)+deltaOverviewrelativedeltaurelativedelta uses arelativedeltatype to represent the magnitude of a time span
which may not be absolute (i.e. which is not simply a fixed number of nanoseconds).
A relativedelta is made up of a number of months together with an absolutetimedeltacomponent.delta=relativedelta(months=1,days=1)start=datetime(2020,1,1)assertstart+delta==datetime(2020,2,2)You can also initialise a relativedelta as the difference between
two datetimes using itsdifferencemethod:delta=relativedelta.difference(date(2020,2,29),date(2020,1,30))assertdelta==relativedelta(months=1)The behaviour ofrelativedeltais consistent and well-defined in edge-cases
(see the Design decisions section for an explanation):delta=relativedelta(months=1,days=1)start=date(2020,1,30)assertstart+delta==date(2020,3,1)dateruleurelativedelta provides adaterulemodule, containing functions
for creating iterators which reliably generate a collection of dates
at regular intervals.
For example, the following will yield onedateon the last day of each
month in 2025:start=date(2025,1,31)rule=daterule.monthly(start,count=12)# yields 2025-1-31, 2025-2-28, 2025-3-31, 2025-4-30, ...the most general rule is constructed from a relativedelta:freq=relativedelta(years=1,months=1,days=-1)rule=daterule.iterator(freq,start,...)shift functionsurelativedelta also exposes useful shift functions which are used internally, namely:shift_monthsto shift a datelike value by a given number of monthsshift_yearsto shift a datelike value by a given number of yearswith_yearto shift a datelike value to a given daywith_monthto shift a datelike value to a given monthwith_yearto shift a datelike value to a given yearDesign decisions and gotchasWe favour simplicity over complexity: we use only the Gregorian calendar and
make no changes e.g. for dates before the 1500s. We also don't try to
replicate some of the complex functionality of dateutil: we're mostly
interested in shifting dates by years, months, days etc.For days between the 1st and 28th, shifting by months has an obvious
unambiguous meaning which we always stick to. One month after Jan 28th is
always Feb 28th. Shifting Feb 28th by another month will give Mar 28th.When shifting a day that has no equivalent in another month (e.g. asking
for one month after Jan 30th), we first compute the target month, and then if
the corresponding day does not exist in that month, we take the final day of the
month as the result. So, on a leap year, one month after Jan 30th is Feb 29th.The order of precidence for arelativedeltais as follows:Work out the target month, if shifting by monthsIf the initial day does not exist in that month, take the final day of the monthExecute any furthertimedeltashiftsSo arelativedeltaof 1 month and 1 day applied to Jan 31st first shifts to the
last day of Feb, and then adds a single day, giving the 1st of Mar. Applying to Jan 30th
gives the same result.Shifted dates have nomemoryof the date they were shifted from. Thus if we shift
Jan 31st by one month and obtain Feb 28th, a further shift of one month will be Mar 28th,notMar 31st.This leads us to an interesting point about therelativedelta: addition is notassociative:start=date(2020,1,31)delta=relativedelta(months=1)d1=(start+delta)+deltad2=start+(delta+delta)assertd1==date(2020,3,29)assertd2==date(2020,3,31)If you want a series of shifted dates, we advise using theDateRule, which takes
account of some of these subtleties:start=date(2020,1,31)delta=relativedelta(months=1)rule=DateRule(delta,start)assertnext(rule)==date(2020,1,31)assertnext(rule)==date(2020,2,29)assertnext(rule)==date(2020,3,31)