bdew_datetimes 0.12.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,20 @@
1
+ # To get started with Dependabot version updates, you'll need to specify which
2
+ # package ecosystems to update and where the package manifests are located.
3
+ # Please see the documentation for all configuration options:
4
+ # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5
+
6
+ version: 2
7
+ updates:
8
+ - package-ecosystem: "uv" # See documentation for possible values
9
+ directory: "/" # Location of package manifests
10
+ schedule:
11
+ interval: "weekly"
12
+ cooldown:
13
+ default-days: 7
14
+ # Maintain dependencies for GitHub Actions
15
+ - package-ecosystem: "github-actions"
16
+ directory: "/"
17
+ schedule:
18
+ interval: "daily"
19
+ cooldown:
20
+ default-days: 7
@@ -0,0 +1,4 @@
1
+ changelog:
2
+ exclude:
3
+ authors:
4
+ - dependabot[bot]
@@ -0,0 +1,29 @@
1
+ name: Dependabot auto-approve / -merge
2
+ on: pull_request
3
+
4
+ permissions: {}
5
+
6
+ jobs:
7
+ dependabot:
8
+ if: ${{ github.actor == 'dependabot[bot]' }}
9
+ runs-on: ubuntu-latest
10
+ timeout-minutes: 5
11
+ permissions:
12
+ contents: write
13
+ pull-requests: write
14
+ env:
15
+ PR_URL: ${{github.event.pull_request.html_url}}
16
+ GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
17
+ steps:
18
+ - uses: actions/checkout@v7.0.1
19
+ - name: Verify commit signature.
20
+ run: |
21
+ curl -s https://github.com/web-flow.gpg | gpg --import
22
+ echo "Imported GitHub keys."
23
+
24
+ echo "Verifying signature of commit: $GITHUB_SHA"
25
+ git verify-commit "$GITHUB_SHA"
26
+ - name: Approve a PR
27
+ run: gh pr review --approve "$PR_URL"
28
+ - name: Enable auto-merge for Dependabot PRs
29
+ run: gh pr merge --auto --squash "$PR_URL"
@@ -0,0 +1,28 @@
1
+ name: "Format"
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches:
7
+ - main
8
+
9
+ permissions: {}
10
+
11
+ env:
12
+ UV_LOCKED: true
13
+
14
+ jobs:
15
+ format:
16
+ runs-on: ubuntu-latest
17
+ timeout-minutes: 5
18
+ permissions:
19
+ contents: read
20
+ steps:
21
+ - uses: actions/checkout@v7.0.1
22
+ - name: Install uv
23
+ uses: astral-sh/setup-uv@v10.3.0
24
+ with:
25
+ python-version: "3.15"
26
+ - name: ruff ci-format
27
+ run: |
28
+ make ci-format
@@ -0,0 +1,41 @@
1
+ name: "Linters"
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches:
7
+ - main
8
+
9
+ permissions: {}
10
+
11
+ env:
12
+ UV_LOCKED: true
13
+
14
+ jobs:
15
+ linters:
16
+ name: Python Code Quality and Lint
17
+ runs-on: ubuntu-latest
18
+ timeout-minutes: 5
19
+ permissions:
20
+ contents: read
21
+ strategy:
22
+ matrix:
23
+ linter-env: ["linting", "type_check", "spell_check"]
24
+ steps:
25
+ - uses: actions/checkout@v7.0.1
26
+ - name: Install uv
27
+ uses: astral-sh/setup-uv@v10.3.0
28
+ with:
29
+ python-version: "3.15"
30
+ - name: Run linting
31
+ if: matrix.linter-env == 'linting'
32
+ run: |
33
+ make lint
34
+ - name: Run type_check
35
+ if: matrix.linter-env == 'type_check'
36
+ run: |
37
+ make typing
38
+ - name: Run spell_check
39
+ if: matrix.linter-env == 'spell_check'
40
+ run: |
41
+ make spell-check
@@ -0,0 +1,35 @@
1
+ name: "Packaging Test"
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches:
7
+ - main
8
+
9
+ permissions: {}
10
+
11
+ env:
12
+ UV_LOCKED: true
13
+
14
+ jobs:
15
+ test_packaging:
16
+ runs-on: ${{ matrix.os }}
17
+ timeout-minutes: 5
18
+ permissions:
19
+ contents: read
20
+ strategy:
21
+ matrix:
22
+ python-version: ["3.11", "3.12", "3.13", "3.14", "3.15"]
23
+ os: [ubuntu-latest]
24
+ steps:
25
+ - uses: actions/checkout@v7.0.1
26
+ - name: Install uv
27
+ uses: astral-sh/setup-uv@v10.3.0
28
+ with:
29
+ python-version: ${{ matrix.python-version }}
30
+ - name: Build the package
31
+ run: |
32
+ make build
33
+ - name: Check the built distributions
34
+ run: |
35
+ make build-check
@@ -0,0 +1,73 @@
1
+ # This GitHub workflow is only needed for python package releases which are supposed to be published on pypi.
2
+ # It requires the Github "environments" feature (see instructions below) it might not be available for private free accounts (but works for public or organization repos).
3
+ # After creating the "release" environment in the Github repo settings, you need to enter your Github organization/username + repo name + "release.yml" workflow file name in the PyPI UI to make this work.
4
+
5
+ # This workflow uploads a Python Package using uv when a release is created.
6
+ # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
7
+
8
+ name: Upload Python Package
9
+
10
+ on:
11
+ release:
12
+ types: [created, edited]
13
+
14
+ permissions: {}
15
+
16
+ env:
17
+ UV_LOCKED: true
18
+
19
+ jobs:
20
+ tests:
21
+ if: startsWith(github.ref, 'refs/tags/v')
22
+ runs-on: ubuntu-latest
23
+ timeout-minutes: 5
24
+ permissions:
25
+ contents: read
26
+ steps:
27
+ - uses: actions/checkout@v7.0.1
28
+ - name: Install uv
29
+ uses: astral-sh/setup-uv@v10.3.0
30
+ with:
31
+ python-version: "3.15"
32
+ enable-cache: false # avoid cache poisoning
33
+ - name: Run the tests
34
+ run: |
35
+ make tests
36
+ - name: Run the type checks
37
+ run: |
38
+ make typing
39
+ - name: Run the linter
40
+ run: |
41
+ make lint
42
+ - name: Run the formatters (check only)
43
+ run: |
44
+ make ci-format
45
+ - name: Run the spell check
46
+ run: |
47
+ make spell-check
48
+
49
+ build-n-publish:
50
+ name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI
51
+ runs-on: ubuntu-latest
52
+ timeout-minutes: 5
53
+ # Specifying a GitHub environment, # Specifying a GitHub environment, which is strongly recommended by PyPI: https://docs.pypi.org/trusted-publishers/adding-a-publisher/
54
+ # you have to create an environment in your repository settings and add the environment name here
55
+ environment: release
56
+ permissions:
57
+ # IMPORTANT: this permission is mandatory for trusted publishing
58
+ id-token: write
59
+ needs: tests
60
+ steps:
61
+ - uses: actions/checkout@v7.0.1
62
+ - name: Install uv
63
+ uses: astral-sh/setup-uv@v10.3.0
64
+ with:
65
+ python-version: "3.15"
66
+ enable-cache: false # # avoid cache poisoning
67
+ - name: Build wheel and source distributions
68
+ run: |
69
+ make build
70
+ - name: Publish distribution 📦 to PyPI
71
+ if: startsWith(github.ref, 'refs/tags/v')
72
+ run: |
73
+ make release
@@ -0,0 +1,31 @@
1
+ name: "Unittests"
2
+ on:
3
+ pull_request:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ permissions: {}
9
+
10
+ env:
11
+ UV_LOCKED: true
12
+
13
+ jobs:
14
+ pytest:
15
+ runs-on: ${{ matrix.os }}
16
+ timeout-minutes: 5
17
+ permissions:
18
+ contents: read
19
+ strategy:
20
+ matrix:
21
+ python-version: ["3.11", "3.12", "3.13", "3.14", "3.15"]
22
+ os: [ubuntu-latest]
23
+ steps:
24
+ - uses: actions/checkout@v7.0.1
25
+ - name: Install uv
26
+ uses: astral-sh/setup-uv@v10.3.0
27
+ with:
28
+ python-version: ${{ matrix.python-version }}
29
+ - name: Run the Unit Tests
30
+ run: |
31
+ make tests
@@ -0,0 +1,60 @@
1
+ # Python artifacts
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.egg-info/
6
+ *.egg
7
+ build/
8
+ dist/
9
+ sdist/
10
+
11
+ # Test artifacts
12
+ .coverage
13
+ .coverage.*.*
14
+ .prefect-results
15
+ .pytest_cache/
16
+
17
+ # Type checking artifacts
18
+ .mypy_cache/
19
+ .dmypy.json
20
+ dmypy.json
21
+ .pyre/
22
+
23
+ # IPython
24
+ profile_default/
25
+ ipython_config.py
26
+ *.ipynb_checkpoints/*
27
+
28
+ # Profiling
29
+ /prof
30
+
31
+ # Environments
32
+ .env
33
+ .venv
34
+ env/
35
+ venv/
36
+
37
+ # Documentation artifacts
38
+ docs/api-ref/schema.json
39
+ site/
40
+ .cache/
41
+
42
+ # UI artifacts
43
+ src/prefect/orion/ui/*
44
+ **/node_modules
45
+
46
+ # Databases
47
+ *.db
48
+
49
+ # MacOS
50
+ .DS_Store
51
+
52
+ # Dask
53
+ dask-worker-space/
54
+
55
+ # Editors
56
+ .idea/
57
+ .vscode/
58
+ *.code-workspace
59
+
60
+ src/_bdew_datetimes_version.py
@@ -0,0 +1 @@
1
+ 3.15
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 mj0nez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,43 @@
1
+
2
+ dev:
3
+ uv sync
4
+ .PHONY: dev
5
+
6
+ build:
7
+ uv build
8
+ .PHONY: build
9
+
10
+
11
+ build-check:
12
+ uv publish --dry-run -t _token_ dist/*
13
+ .PHONY: build-check
14
+
15
+ release:
16
+ uv publish
17
+ .PHONY: release
18
+
19
+ tests:
20
+ uv run pytest tests
21
+ .PHONY: tests
22
+
23
+ typing:
24
+ uv run mypy --strict src/bdew_datetimes tests
25
+ .PHONY: typing
26
+
27
+ lint:
28
+ uv run ruff check .
29
+ .PHONY: lint
30
+
31
+ format:
32
+ uv run ruff format .
33
+ .PHONY: format
34
+
35
+ spell-check:
36
+ uv run codespell --ignore-words=domain-specific-terms.txt src/bdew_datetimes README.md
37
+ .PHONY: spell-check
38
+
39
+ # ci-only targets
40
+ ci-format:
41
+ uv run ruff format --check .
42
+ uv run ruff check --select I .
43
+ .PHONY: format
@@ -0,0 +1,216 @@
1
+ Metadata-Version: 2.5
2
+ Name: bdew_datetimes
3
+ Version: 0.12.0
4
+ Summary: Generate and work with holidays of the BDEW-Calendar for power and gas in Germany
5
+ Project-URL: Changelog, https://github.com/mj0nez/bdew-datetimes/releases
6
+ Project-URL: Homepage, https://github.com/mj0nez/bdew-datetimes/
7
+ Author: Konstantin Klein
8
+ Author-email: Marcel Johannesmann <mj0nez@fn.de>
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: bdew,edi@energy
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Programming Language :: Python :: 3.15
24
+ Requires-Python: >=3.11
25
+ Requires-Dist: holidays>=0.84; python_version >= '3.10'
26
+ Requires-Dist: python-dateutil
27
+ Requires-Dist: pytz>=2022.7.1
28
+ Description-Content-Type: text/markdown
29
+
30
+ # bdew_datetimes
31
+
32
+ ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/mj0nez/bdew-datetimes/packaging_test.yml?style=plastic)![PyPI - Python Version](https://img.shields.io/pypi/pyversions/bdew-datetimes?style=plastic)![PyPI - License](https://img.shields.io/pypi/l/bdew-datetimes?style=plastic)![PyPI](https://img.shields.io/pypi/v/bdew-datetimes?style=plastic)
33
+
34
+ A collection of utils to work with datetimes and holidays in the German energy
35
+ market and is based on the [python-holiday](https://github.com/vacanza/holidays) package.
36
+
37
+ The implementation considers the publications of the **BDEW** (Bundesverband der Energie- und Wasserwirtschaft e. V.) and **EDI@Energy**, which provide boundaries and guidance for the data exchange on the german energy market.
38
+
39
+ ### Current highlights:
40
+
41
+ - BDEW-holiday calendar
42
+ - allows dict like evaluation of dates and datetimes and contains all holidays considered by the BDEW
43
+ - Statutory Periods ("_Gesetzliche Fristen_")
44
+ - calculate dates of the kind "_x Werktage ab Stichtag_"
45
+ - calculate dates of the kind "_nter Werktag des Fristen- bzw. Liefermonats_"
46
+ - Gas-Day / Market Day evaluation
47
+
48
+ ### Future Scope:
49
+
50
+ - providing subdivision holiday calendars to allow granular load profiles
51
+
52
+ ## Quick Start and Examples
53
+
54
+ Install the package from [pypi](https://pypi.org/project/bdew-datetimes/):
55
+
56
+ ```bash
57
+ pip install bdew-datetimes
58
+ ```
59
+
60
+ ### Check if a date is a _specific_ BDEW Holidays
61
+
62
+ > [!NOTE]
63
+ > By "specific" we mean: Holidays that are neither nation- nor statewide holidays (those are defined in the [upstream package holidays](https://github.com/vacanza/holidays)) but defined by the BDEW directly.
64
+ > In 2025 those are: Sonderfeiertag 24h Lieferantenwechsel, Heiligabend and Silvester only.
65
+
66
+ The `HolidaySum` returned by `create_bdew_calendar` contains the BDEW specific holidays.
67
+ This means it contains those holidays which are _defined_ by BDEW which includes Heiligabend and Silvester as well as special days without Marktkommunikation but _not_ the local or nationwide holidays in Germany and its states.
68
+
69
+ ```python
70
+ from datetime import date
71
+ from bdew_datetimes import create_bdew_calendar
72
+
73
+ bdew_holidays = create_bdew_calendar() # this behaves like a dict
74
+
75
+ assert date(2022, 12, 31) in bdew_holidays # Silvester is a BDEW holiday
76
+ assert date(2022, 8, 8) in bdew_holidays is False # Augsburger Friedensfest is _not_ a BDEW holiday (but a holiday in Augsburg only)
77
+ assert date(2022, 12, 2) in bdew_holidays is False # The 12th of February is not a BDEW holiday
78
+
79
+ print(bdew_holidays.get('2022-01-01')) # prints "Neujahr"
80
+ ```
81
+
82
+ The **union** (type `HolidaySum`) of both nation and state wide holidays **and** the BDEW holidays (only the latter is returned by `create_bdew_calendar`) is the relevant calendar for German utilities.
83
+
84
+ ### Check if a given Date is a BDEW Working Day
85
+
86
+ BDEW working days are those days taken into account for the "Fristenberechnung".
87
+ The function `is_bdew_working_day` considers both national **and** state wide holidays **and** BDEW holidays:
88
+
89
+ ```python
90
+ from datetime import date
91
+
92
+ from bdew_datetimes import is_bdew_working_day
93
+
94
+ assert is_bdew_working_day(date(2023, 1, 1)) is False # Neujahr (national holiday)
95
+ assert is_bdew_working_day(date(2023, 1, 2)) is True # regular weekday
96
+ assert is_bdew_working_day(date(2023, 1, 6)) is False # Heilige Drei Könige (local holiday in parts of Germany)
97
+ assert is_bdew_working_day(date(2023, 4, 7)) is False # Karfreitag (national holiday, but based on an astronomical calendar)
98
+ assert is_bdew_working_day(date(2023, 12, 24)) is False # Heiligabend (BDEW holiday)
99
+ ```
100
+
101
+ You can also get the next or previous working day for any date:
102
+
103
+ ```python
104
+ from datetime import date
105
+
106
+ from bdew_datetimes import get_next_working_day, get_previous_working_day
107
+
108
+ assert get_next_working_day(date(2023, 1, 1)) == date(2023, 1, 2) # the next working day after Neujahr
109
+ assert get_previous_working_day(date(2023, 1, 1)) == date(2022, 12, 30) # the last working day of 2022
110
+ assert get_next_working_day(date(2023, 1, 20)) == date(2023, 1, 23) # the next working day after a friday is the next monday
111
+ ```
112
+
113
+ ### Calculate Statutory Periods
114
+
115
+ Statutory periods define the maximum time between e.g. the EDIFACT message for the "Anmeldung" and the actual start of supply ("Lieferbeginn").
116
+
117
+ ```python
118
+ from datetime import date
119
+
120
+ from bdew_datetimes import add_frist
121
+ from bdew_datetimes import Period
122
+ from bdew_datetimes.enums import DayType, EndDateType
123
+
124
+ # Eingang der Anmeldung des LFN erfolgt am 04.07.2016. Der Mindestzeitraum von zehn WT
125
+ # beginnt am 05.07.2016 und endet am 18.07.2016. Frühestes zulässiges Anmeldedatum
126
+ # ist damit der 19.07.2016, sodass die Marktlokation dem LFN frühestens zum Beginn
127
+ # des vorgenannten Tages zugeordnet wird.
128
+ eingang_der_anmeldung = date(2016, 7, 4)
129
+ gesetzliche_frist = Period(
130
+ 10,
131
+ DayType.WORKING_DAY,
132
+ end_date_type=EndDateType.EXCLUSIVE
133
+ # lieferbeginn is the exclusive end of the previous supply contract
134
+ )
135
+ fruehest_moeglicher_lieferbeginn = add_frist(eingang_der_anmeldung, gesetzliche_frist)
136
+ assert fruehest_moeglicher_lieferbeginn == date(2016, 7, 19)
137
+ ```
138
+
139
+ ### Calculate "Liefer- and Fristenmonate"
140
+
141
+ Liefer- and Fristenmonat are concepts used in MaBiS and GPKE:
142
+
143
+ ```python
144
+ from datetime import date
145
+
146
+ from bdew_datetimes import get_nth_working_day_of_month
147
+ from bdew_datetimes.enums import MonthType
148
+
149
+ # returns the 18th working day of the current month in Germany
150
+ get_nth_working_day_of_month(18)
151
+
152
+ # the 18th working day of November 2023
153
+ assert get_nth_working_day_of_month(18, start=date(2023, 11, 1)) == date(2023, 11, 28)
154
+
155
+ # the 42th working day of Fristenmonat July 2023
156
+ assert get_nth_working_day_of_month(42, month_type=MonthType.FRISTENMONAT, start=date(2023, 7, 1)) == date(2023, 9, 29)
157
+ ```
158
+
159
+ ## Notes
160
+
161
+ The BDEW considers all days as holidays, which are nationwide holidays and days, which are a holiday in at least one state.
162
+ Furthermore, the 24. and the 31. December are holidays as well.
163
+ Therefore, this package utilizes the composition of all available german holiday calendars and adds the two additional days.
164
+
165
+ Shifting holidays to the next weekday if they fall on a weekend is currently not considered.
166
+
167
+ ## Development
168
+
169
+ This project uses [uv](https://docs.astral.sh/uv/) for dependency management & tooling and a [GNU Makefile](https://makefiletutorial.com/) to standardize common development commands. If you want to avoid `make` you can run each command manually, in this case please still refer to the [Makefile](./Makefile).
170
+
171
+ After installing uv & make, set up the development environment with:
172
+
173
+ ```bash
174
+ make dev
175
+ ```
176
+
177
+ Run the tests and the individual tooling via the corresponding target:
178
+
179
+ ```bash
180
+ make tests
181
+
182
+ make typing
183
+
184
+ make lint
185
+
186
+ make format
187
+
188
+ make spell-check
189
+ ```
190
+
191
+ ## Development
192
+
193
+ This project uses [uv](https://docs.astral.sh/uv/) for dependency management and tooling.
194
+ After installing uv, set up the development environment with:
195
+ ```bash
196
+ uv sync --group dev
197
+ ```
198
+
199
+ Run the tests and the individual tooling via the corresponding dependency groups:
200
+ ```bash
201
+ uv run --group tests pytest
202
+ uv run --group type_check mypy --strict src/bdew_datetimes tests
203
+ uv run --group linting ruff check .
204
+ uv run --group linting ruff format .
205
+ uv run --group spell_check codespell --ignore-words=domain-specific-terms.txt src/bdew_datetimes README.md
206
+ ```
207
+
208
+ ## License
209
+
210
+ This library is licensed under the _MIT_ license, see the [LICENSE file](LICENSE).
211
+
212
+ ## Users
213
+
214
+ This library is used by the following projects:
215
+
216
+ - [Hochfrequenz Fristenkalender](https://fristenkalender.hochfrequenz.de); the respective backend is also available [online](https://fristenkalender-api.happyfield-64ecc075.westeurope.azurecontainerapps.io/docs) and as [Docker image](https://github.com/Hochfrequenz/fristenkalender-functions/pkgs/container/fristenkalender-functions)