Skip to main content

A small fast implementation of relativedelta

Project description

uRelativeDelta

A small fast implementation of relativedelta

urelativedelta GitHub Actions urelativedelta on PyPI License: MIT

urelativedelta provides the following utilities:

  • relativedelta a simple replacement for dateutil.relativedelta
  • daterule: a module of useful iterators yielding regular (e.g. monthly) dates
  • proceedural helper functions for shifting date and datetime values by months and years

it is a python port of the chronoutil library for rust (itself inspired by dateutil).

Benchmarks

Originally urelativedelta was used for speeding up complicated cashflow bucketing computations (where there are lots of relativedeltas). It's pretty successful. Shifting 5 million datetime objects by 100 years gives us:

interpreter urelativedelta python-dateutil speedup
cpython 3.11 6.72s 20.36s 3.03x
pypy 3.9 0.72s 3.13s 4.34x

Usage

Install via:

pip install urelativedelta

then you can run

from datetime import datetime
from urelativedelta import relativedelta

delta = relativedelta(years=1, months=1, days=1, hours=1)
datetime(2050, 1, 1) + delta

Overview

relativedelta

urelativedelta uses a relativedelta type 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 absolute timedelta component.

delta = relativedelta(months=1, days=1)
start = datetime(2020, 1, 1)
assert start + delta == datetime(2020, 2, 2)

The behaviour of relativedelta is 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)
assert start + delta == date(2020, 3, 1)

daterule

urelativedelta provides a daterule module, containing functions for creating iterators which reliably generate a collection of dates at regular intervals. For example, the following will yield one date on 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 functions

urelativedelta also exposes useful shift functions which are used internally, namely:

  • shift_months to shift a datelike value by a given number of months
  • shift_years to shift a datelike value by a given number of years
  • with_year to shift a datelike value to a given day
  • with_month to shift a datelike value to a given month
  • with_year to shift a datelike value to a given year

Design decisions and gotchas

We favour simplicity over complexity: we use only the Gregorian calendar and make no changes e.g. for dates before the 1500s.

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 a relativedelta is as follows:

  1. Work out the target month, if shifting by months
  2. If the initial day does not exist in that month, take the final day of the month
  3. Execute any further timedelta shifts

So a relativedelta of 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 no memory of 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, not Mar 31st.

This leads us to an interesting point about the relativedelta: addition is not associative:

start = date(2020, 1, 31)
delta = relativedelta(months=1)

d1 = (start + delta) + delta
d2 = start + (delta + delta)

assert d1 == date(2020, 3, 29)
assert d2 == date(2020, 3, 31)

If you want a series of shifted dates, we advise using the DateRule, which takes account of some of these subtleties:

start = date(2020, 1, 31)
delta = relativedelta(months=1)
rule = DateRule(delta, start)
assert next(rule) == date(2020, 1, 31)
assert next(rule) == date(2020, 2, 29)
assert next(rule) == date(2020, 3, 31)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

urelativedelta-0.2.0.tar.gz (11.3 kB view details)

Uploaded Source

Built Distribution

urelativedelta-0.2.0-py3-none-any.whl (7.5 kB view details)

Uploaded Python 3

File details

Details for the file urelativedelta-0.2.0.tar.gz.

File metadata

  • Download URL: urelativedelta-0.2.0.tar.gz
  • Upload date:
  • Size: 11.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.9.17

File hashes

Hashes for urelativedelta-0.2.0.tar.gz
Algorithm Hash digest
SHA256 f9b9669d1face9560f618e4197b035dd4128abeb9dd7529a724b70f1542a5df3
MD5 20c25d9f96da17ca1ee9e98e2f4517e7
BLAKE2b-256 9c245fc37bf02ae64a106162b4c709186d24097a988eba3338733350bb4d90bc

See more details on using hashes here.

File details

Details for the file urelativedelta-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for urelativedelta-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d8b741d0f3656dc7806f1be22c894ad3f21c3171ece59f50e4caedfeb6793a36
MD5 7600cb0da4cc28804b5212fb7d749283
BLAKE2b-256 0eb619213fdc43f2e361fa95b6e9d7eaebe1299f5a98b09b7750408b1e159312

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page