{"id":13815348,"url":"https://github.com/aaugustin/datedelta","last_synced_at":"2025-05-15T23:05:18.963Z","repository":{"id":62566883,"uuid":"54597145","full_name":"aaugustin/datedelta","owner":"aaugustin","description":"Date arithmetic in Python","archived":false,"fork":false,"pushed_at":"2024-10-21T21:52:34.000Z","size":41,"stargazers_count":150,"open_issues_count":0,"forks_count":9,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-08T10:23:41.881Z","etag":null,"topics":["date","python"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aaugustin.png","metadata":{"files":{"readme":"README.rst","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-03-23T22:25:38.000Z","updated_at":"2025-03-04T10:38:33.000Z","dependencies_parsed_at":"2024-01-18T02:36:31.414Z","dependency_job_id":"c9efadd7-00e3-4189-9711-233dbc2bf108","html_url":"https://github.com/aaugustin/datedelta","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aaugustin%2Fdatedelta","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aaugustin%2Fdatedelta/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aaugustin%2Fdatedelta/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aaugustin%2Fdatedelta/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aaugustin","download_url":"https://codeload.github.com/aaugustin/datedelta/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253365466,"owners_count":21897187,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["date","python"],"created_at":"2024-08-04T04:03:22.021Z","updated_at":"2025-05-15T23:05:13.941Z","avatar_url":"https://github.com/aaugustin.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":"datedelta\n#########\n\n``datedelta.datedelta`` is ``datetime.timedelta`` for date arithmetic.\n\n.. code-block:: pycon\n\n    \u003e\u003e\u003e import datetime\n    \u003e\u003e\u003e import datedelta\n\n    \u003e\u003e\u003e datetime.date(2025, 4, 22) + 2 * datedelta.WEEK\n    datetime.date(2025, 5, 6)\n\n    \u003e\u003e\u003e datetime.date(2025, 4, 22) + 3 * datedelta.MONTH\n    datetime.date(2025, 7, 22)\n\nIt accounts for oddities of the Gregorian calendar.\n\n.. code-block:: pycon\n\n    \u003e\u003e\u003e datetime.date(2024, 2, 29) + datedelta.YEAR\n    datetime.date(2025, 3, 1)\n\n    \u003e\u003e\u003e datetime.date(2024, 2, 29) + 4 * datedelta.YEAR\n    datetime.date(2028, 2, 29)\n\nIt's convenient for computing yearly, monthly, or weekly subscriptions periods.\n\n.. code-block:: pycon\n\n    \u003e\u003e\u003e start_date = datetime.date(2024, 1, 30)\n    \u003e\u003e\u003e for n in range(12):\n    ...     print(repr(start_date + n * datedelta.MONTH))\n    datetime.date(2024, 1, 30)\n    datetime.date(2024, 3, 1)\n    datetime.date(2024, 3, 30)\n    datetime.date(2024, 4, 30)\n    datetime.date(2024, 5, 30)\n    datetime.date(2024, 6, 30)\n    datetime.date(2024, 7, 30)\n    datetime.date(2024, 8, 30)\n    datetime.date(2024, 9, 30)\n    datetime.date(2024, 10, 30)\n    datetime.date(2024, 11, 30)\n    datetime.date(2024, 12, 30)\n\n    \u003e\u003e\u003e start_date = datetime.date(2024, 1, 31)\n    \u003e\u003e\u003e for n in range(12):\n    ...     print(repr(start_date + n * datedelta.MONTH))\n    datetime.date(2024, 1, 31)\n    datetime.date(2024, 3, 1)\n    datetime.date(2024, 3, 31)\n    datetime.date(2024, 5, 1)\n    datetime.date(2024, 5, 31)\n    datetime.date(2024, 7, 1)\n    datetime.date(2024, 7, 31)\n    datetime.date(2024, 8, 31)\n    datetime.date(2024, 10, 1)\n    datetime.date(2024, 10, 31)\n    datetime.date(2024, 12, 1)\n    datetime.date(2024, 12, 31)\n\nIt guarantees consistent results on arithmetic operations that it supports.\n\nBehavior\n========\n\nThere are two date arithmetic traps in the Gregorian calendar:\n\n1. Leap years. Problems arise when adding years to a February 29th gives a\n   result in a non-leap year.\n\n2. Variable number of days in months. Problems arise when adding months to a\n   29th, 30th or 31st gives a result in a month where that day doesn't exist.\n\nIn both cases, datedelta changes the result to the first day of the next month.\n\nThis method gives consistent results provided periods are represented by\n(start date inclusive, end date exclusive) — that's [start date, end date) if\nyou prefer the mathematical notation. This representation of periods is akin\nto 0-based indexing, which is the convention Python uses.\n\nFor example:\n\n* If someone subscribes for a year starting on 2020-02-29 inclusive, the end\n  date must be 2021-03-01 exclusive. If it was 2020-02-28 exclusive, that day\n  would be missing from the subscription period.\n\n* If someone subscribes for three months starting on 2020-03-31 inclusive, the\n  end date must be 2020-07-01 exclusive. If it was 2020-06-30 exclusive, that\n  day would be missing from the subscription period.\n\nOperations are always performed on years, then months, then days. This order\nusually provides the expected behavior. It also minimizes loss of precision.\n\nInstallation\n============\n\n.. code-block:: bash\n\n    pip install datedelta\n\nUsage\n=====\n\nThe most common operations are adding a ``datedelta`` to a ``date`` and\nsubtracting a ``datedelta`` from a ``date``.\n\nBasic intervals\n---------------\n\nThe ``YEAR``, ``MONTH``, ``WEEK``, and ``DAY`` constants support common\ncalculations with little code.\n\n.. code-block:: pycon\n\n    \u003e\u003e\u003e import datetime\n    \u003e\u003e\u003e import datedelta\n\n    \u003e\u003e\u003e datetime.date(2022, 1, 1) + datedelta.YEAR\n    datetime.date(2023, 1, 1)\n\n    \u003e\u003e\u003e datetime.date(2023, 1, 1) - datedelta.YEAR\n    datetime.date(2022, 1, 1)\n\n    \u003e\u003e\u003e datetime.date(2024, 2, 29) + datedelta.YEAR\n    datetime.date(2025, 3, 1)\n\n    \u003e\u003e\u003e datetime.date(2025, 3, 1) - datedelta.YEAR\n    datetime.date(2024, 3, 1)\n\n    \u003e\u003e\u003e datetime.date(2022, 1, 1) + datedelta.MONTH\n    datetime.date(2022, 2, 1)\n\n    \u003e\u003e\u003e datetime.date(2022, 2, 1) - datedelta.MONTH\n    datetime.date(2022, 1, 1)\n\n    \u003e\u003e\u003e datetime.date(2022, 1, 31) + datedelta.MONTH\n    datetime.date(2022, 3, 1)\n\n    \u003e\u003e\u003e datetime.date(2022, 3, 1) - datedelta.MONTH\n    datetime.date(2022, 2, 1)\n\n    \u003e\u003e\u003e datetime.date(2022, 1, 1) + datedelta.WEEK\n    datetime.date(2022, 1, 8)\n\n    \u003e\u003e\u003e datetime.date(2022, 1, 1) - datedelta.WEEK\n    datetime.date(2021, 12, 25)\n\n    \u003e\u003e\u003e datetime.date(2022, 1, 1) + datedelta.DAY\n    datetime.date(2022, 1, 2)\n\n    \u003e\u003e\u003e datetime.date(2022, 1, 1) - datedelta.DAY\n    datetime.date(2021, 12, 31)\n\n``datedelta.DAY`` behaves exactly like ``datetime.timedelta(1)``. It's only\nprovided for consistency.\n\nArbitrary intervals\n-------------------\n\n``datedelta`` objects provide support for arbitrary calculations.\n\n.. code-block:: pycon\n\n    \u003e\u003e\u003e import datetime\n    \u003e\u003e\u003e import datedelta\n\n    \u003e\u003e\u003e datetime.date(2022, 3, 23) + datedelta.datedelta(years=1, months=1, days=-1)\n    datetime.date(2023, 4, 22)\n\n    \u003e\u003e\u003e datetime.date(2022, 3, 23) - datedelta.datedelta(years=-1, months=-1, days=1)\n    datetime.date(2023, 4, 22)\n\n    \u003e\u003e\u003e datetime.date(2024, 2, 29) + datedelta.datedelta(years=2)\n    datetime.date(2026, 3, 1)\n\n    \u003e\u003e\u003e datetime.date(2024, 2, 29) - datedelta.datedelta(years=2)\n    datetime.date(2022, 3, 1)\n\n    \u003e\u003e\u003e datetime.date(2024, 2, 29) + datedelta.datedelta(years=2, days=-1)\n    datetime.date(2026, 2, 28)\n\n    \u003e\u003e\u003e datetime.date(2024, 2, 29) - datedelta.datedelta(years=2, days=1)\n    datetime.date(2022, 2, 28)\n\n    \u003e\u003e\u003e datetime.date(2024, 2, 29) + datedelta.datedelta(years=2, months=6)\n    datetime.date(2026, 9, 1)\n\n    \u003e\u003e\u003e datetime.date(2024, 2, 29) - datedelta.datedelta(years=2, months=-6)\n    datetime.date(2022, 9, 1)\n\n    \u003e\u003e\u003e datetime.date(2024, 2, 29) + datedelta.datedelta(years=4)\n    datetime.date(2028, 2, 29)\n\n    \u003e\u003e\u003e datetime.date(2024, 2, 29) - datedelta.datedelta(years=4)\n    datetime.date(2020, 2, 29)\n\n    \u003e\u003e\u003e datetime.date(2024, 2, 29) + datedelta.datedelta(years=4, days=1)\n    datetime.date(2028, 3, 1)\n\n    \u003e\u003e\u003e datetime.date(2024, 2, 29) - datedelta.datedelta(years=4, days=-1)\n    datetime.date(2020, 3, 1)\n\n    \u003e\u003e\u003e datetime.date(2024, 2, 29) + datedelta.datedelta(years=4, months=6)\n    datetime.date(2028, 8, 29)\n\n    \u003e\u003e\u003e datetime.date(2024, 2, 29) - datedelta.datedelta(years=4, months=-6)\n    datetime.date(2020, 8, 29)\n\nThese results are mathematically consistent, as explained in \"Behavior\" above.\n\nOther operations\n----------------\n\n``datedelta`` instances can be added, subtracted, and multiplied with an\ninteger. However, there are some restrictions on addition and subtraction.\n\nAdding then subtracting a given datedelta to a date doesn't always return the\noriginal date. In order to prevent bugs caused by this behavior, when the result\nof adding or subtracting two ``datedelta`` isn't well defined, a ``ValueError``\nis raised.\n\n.. code-block:: pycon\n\n    \u003e\u003e\u003e import datedelta\n\n    \u003e\u003e\u003e datedelta.YEAR + datedelta.YEAR\n    datedelta.datedelta(years=2)\n\n    \u003e\u003e\u003e 3 * datedelta.YEAR\n    datedelta.datedelta(years=3)\n\n    \u003e\u003e\u003e datedelta.YEAR - datedelta.DAY\n    datedelta.datedelta(years=1, days=-1)\n\n    \u003e\u003e\u003e datedelta.YEAR - datedelta.YEAR\n    Traceback (most recent call last):\n        ...\n    ValueError: cannot subtract datedeltas with same signs\n\n    \u003e\u003e\u003e datedelta.datedelta(months=6) + datedelta.datedelta(months=-3)\n    Traceback (most recent call last):\n        ...\n    ValueError: cannot add datedeltas with opposite signs\n\nLimitations\n===========\n\nAdditions involving ``datedelta`` are neither associative nor commutative in\ngeneral.\n\nHere are two examples where adding a ``datedelta`` then subtracting it doesn't\nreturn the original value:\n\n.. code-block:: pycon\n\n    \u003e\u003e\u003e import datetime\n    \u003e\u003e\u003e import datedelta\n\n    \u003e\u003e\u003e datetime.date(2024, 2, 29) + datedelta.datedelta(years=1)\n    datetime.date(2025, 3, 1)\n\n    \u003e\u003e\u003e datetime.date(2025, 3, 1) - datedelta.datedelta(years=1)\n    datetime.date(2024, 3, 1)\n\n    \u003e\u003e\u003e datetime.date(2024, 1, 31) + datedelta.datedelta(months=1)\n    datetime.date(2024, 3, 1)\n\n    \u003e\u003e\u003e datetime.date(2024, 3, 1) - datedelta.datedelta(months=1)\n    datetime.date(2024, 2, 1)\n\nHere are two examples where adding two ``datedelta`` gives a different result\ndepending on the order of operations:\n\n.. code-block:: pycon\n\n    \u003e\u003e\u003e import datetime\n    \u003e\u003e\u003e import datedelta\n\n    \u003e\u003e\u003e datetime.date(2024, 2, 29) + datedelta.datedelta(months=6) + datedelta.datedelta(years=1)\n    datetime.date(2025, 8, 29)\n\n    \u003e\u003e\u003e datetime.date(2024, 2, 29) + datedelta.datedelta(years=1) + datedelta.datedelta(months=6)\n    datetime.date(2025, 9, 1)\n\n    \u003e\u003e\u003e datetime.date(2024, 1, 31) + datedelta.datedelta(months=2) + datedelta.datedelta(months=5)\n    datetime.date(2024, 8, 31)\n\n    \u003e\u003e\u003e datetime.date(2024, 1, 31) + datedelta.datedelta(months=5) + datedelta.datedelta(months=2)\n    datetime.date(2024, 9, 1)\n\nTo avoid problems, you should always start from the same reference date and add\na single ``datedelta``. Don't chain additions or subtractions.\n\nTo minimize the risk of incorrect results, ``datedelta`` only implements\noperations that have unambiguous semantics:\n\n* Adding a datedelta to a date\n* Subtracting a datedelta from a date\n* Adding a datedelta to a datedelta when components have the same sign\n* Subtracting a datedelta from a datedelta when components have opposite signs\n\n(PEP 20 says: \"In the face of ambiguity, refuse the temptation to guess.\")\n\nAlternatives\n============\n\n``datedelta.datedelta`` is smarter than ``datetime.timedelta`` because it knows\nabout years and months in addition to days.\n\nCompared to ``pendulum.Duration`` or ``dateutil.relativedelta.relativedelta``,\n``datedelta.datedelta`` has a few benefits:\n\n* It handles non-existing results in a mathematically consistent fashion: it\n  adjusts to the first day of the next month while pendulum and dateutil adjust\n  to the last day of the current month.\n* It provides an API designed to prevent programming mistakes: it requires\n  keyword arguments, rejects operations expressing incorrect business logic, and\n  omits error-prone features of dateutil like the \"replace\" behavior or explicit\n  control of leap days.\n* It has very small footprint, by virtue of providing of very small subset of\n  the features found in pendulum or dateutil. That makes it a good choice if\n  you're otherwise happy with the standard library's datetime module.\n\nChangelog\n=========\n\n1.4\n---\n\n* Update supported Python versions.\n\n1.3\n---\n\n* Add ``WEEK`` constant.\n\n1.2\n---\n\n* Optimize hashing and pickling.\n\n1.1\n---\n\n* Add ``YEAR``, ``MONTH``, and ``DAY`` constants.\n\n1.0\n---\n\n* Initial stable release.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faaugustin%2Fdatedelta","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faaugustin%2Fdatedelta","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faaugustin%2Fdatedelta/lists"}