{"id":25084723,"url":"https://github.com/xi/nominaldelta","last_synced_at":"2025-06-20T04:07:29.655Z","repository":{"id":275271217,"uuid":"850147858","full_name":"xi/nominaldelta","owner":"xi","description":"nominal difference of date/datetime","archived":false,"fork":false,"pushed_at":"2025-02-01T09:23:04.000Z","size":27,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-20T04:07:10.315Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/xi.png","metadata":{"files":{"readme":"README.md","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":"2024-08-31T01:50:40.000Z","updated_at":"2025-02-01T09:23:08.000Z","dependencies_parsed_at":"2025-02-01T10:33:57.167Z","dependency_job_id":null,"html_url":"https://github.com/xi/nominaldelta","commit_stats":null,"previous_names":["xi/nominaldelta"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/xi/nominaldelta","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xi%2Fnominaldelta","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xi%2Fnominaldelta/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xi%2Fnominaldelta/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xi%2Fnominaldelta/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xi","download_url":"https://codeload.github.com/xi/nominaldelta/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xi%2Fnominaldelta/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260878459,"owners_count":23075962,"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":[],"created_at":"2025-02-07T07:18:31.666Z","updated_at":"2025-06-20T04:07:24.644Z","avatar_url":"https://github.com/xi.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# nominaldelta - nominal difference of date/datetime\n\nPython's [`datetime` module][1] is great: It has `ordinal` and `timestamp` as\nabsolute values, and `date` and `datetime` as nominal ones for the Gregorian\ncalendar. The only issue is `timedelta`: It is really an absolute delta, so\nbasically the same as `date1.toordinal() - date2.toordinal()` or\n`dt1.timestamp() - dt2.timestamp()`.\n\nThe third party library [python-dateutil][2] adds `relativedelta`, which is a\nnominal delta. However, it acts as if every day had 24 hours, which is not\nquite true.\n\nSo this is an attempt to add a proper nominal delta.\n\n## Example\n\n```python\n\u003e\u003e\u003e from datetime import date\n\u003e\u003e\u003e from datetime import datetime\n\u003e\u003e\u003e from zoneinfo import ZoneInfo\n\u003e\u003e\u003e from nominaldelta import NominalDelta\n\n# adding months does what you would expect\n\u003e\u003e\u003e datetime(1970, 1, 15, 13) + NominalDelta(months=1)\ndatetime(1970, 2, 15, 13)\n\n# for shorter months, result are clipped to the last day of the month\n\u003e\u003e\u003e datetime(1970, 1, 30, 13) + NominalDelta(months=1)\ndatetime(1970, 2, 28, 13)\n\n# daylight saving time is handled correctly\n\u003e\u003e\u003e tz = ZoneInfo('Europe/Berlin')\n\u003e\u003e\u003e datetime(2019, 3, 31, 3, 1, tzinfo=tz) - NominalDelta(minutes=2)\ndatetime(2019, 3, 31, 1, 59, tzinfo=tz)\n\n# you can compute top-heavy differences\n\u003e\u003e\u003e delta = NominalDelta.diff(date(1970, 1, 1), date.today())\n\u003e\u003e\u003e f'{delta.months // 12} years'\n'54 years'\n```\n\n## Status\n\nThis is just an experiment, so I did not publish it to pypi. Feel free to open\nan issue if you think this should be published.\n\n## Usage\n\n### `class NominalDelta(years, months, weeks, days, hours, minutes, seconds)`\n\nAll arguments are optional and default to 0.\n\n`NominalDelta` only stores months, days, and seconds. All other values are\nconverted to one of them:\n\n-   `years` are converted to 12 months\n-   `weeks` are converted to 7 days\n-   `hours` are converted to 3600 seconds\n-   `minutes` are converted to 60 seconds\n-   `seconds` can be a float to represent milli- and micoseconds\n\nNotably, `NominalDelta` avoids to perpetuate some common misconceptions:\n\n-   `years` are not converted to 365 days because leap years have 366 days\n-   `days` are not converted to 24 hours because of daylight saving time. Also,\n    days with leap seconds have an extra second.\n\nThe three values are independent from each other. Seconds are never converted\nto days, and days are never converted to months.\n\n### `a + NominalDelta()`\n\nWhen a `NominalDelta` is added to a `date` or `datetime`, the months are added\nfirst. If the original day does not exist in that month (e.g. there is no\n1970-02-30), the last day of that month is used instead. Days are added after\nthat and seconds are added last.\n\nWhen adding to a `date`, seconds are ignored.\n\n### `NominalDelta.diff(a, b, allow_months=True)`\n\nCalculate the delta between two `date` or `datetime` objects. This will first\ncheck how many months can be added without overshooting. Then it will check how\nmany days can be added on top of the month. Last, the remaining difference in\nseconds is calculated. This approach is called \"top heavy\" because it\nprioritizes larger units over smaller ones.\n\nSet `allow_months=False` if you only want to get days and seconds. If you only\nwant seconds, use the absolute difference instead:\n\n```python\ndelta = NominalDelta(seconds=dt1.timestamp() - dt2.timestamp())\n```\n\n[1]: https://docs.python.org/3/library/datetime.html\n[2]: https://github.com/dateutil/dateutil\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxi%2Fnominaldelta","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxi%2Fnominaldelta","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxi%2Fnominaldelta/lists"}