Skip to content

Commit e106559

Browse files
authored
Merge pull request #204 from hf-kklein/re-structure
chore: restructure model classes and functions into separate modules
2 parents a3399e3 + 09c95b0 commit e106559

File tree

10 files changed

+193
-137
lines changed

10 files changed

+193
-137
lines changed

README.md

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ The function `is_bdew_working_day` considers both national **and** state wide ho
5353
```python
5454
from datetime import date
5555

56-
from bdew_datetimes.periods import is_bdew_working_day
56+
from bdew_datetimes import is_bdew_working_day
5757

5858
assert is_bdew_working_day(date(2023, 1, 1)) is False # Neujahr (national holiday)
5959
assert is_bdew_working_day(date(2023, 1, 2)) is True # regular weekday
@@ -66,7 +66,7 @@ You can also get the next or previous working day for any date:
6666
```python
6767
from datetime import date
6868

69-
from bdew_datetimes.periods import get_next_working_day, get_previous_working_day
69+
from bdew_datetimes import get_next_working_day, get_previous_working_day
7070

7171
assert get_next_working_day(date(2023, 1, 1)) == date(2023, 1, 2) # the next working day after Neujahr
7272
assert get_previous_working_day(date(2023, 1, 1)) == date(2022, 12, 30) # the last working day of 2022
@@ -75,31 +75,36 @@ assert get_next_working_day(date(2023, 1, 20)) == date(2023, 1, 23) # the next
7575

7676
### Calculate Statutory Periods
7777
Statutory periods define the maximum time between e.g. the EDIFACT message for the "Anmeldung" and the actual start of supply ("Lieferbeginn").
78+
7879
```python
7980
from datetime import date
8081

81-
from bdew_datetimes.periods import DayType, EndDateType, Period, add_frist
82+
from bdew_datetimes import add_frist
83+
from bdew_datetimes import Period
84+
from bdew_datetimes.enums import DayType, EndDateType
8285

8386
# Eingang der Anmeldung des LFN erfolgt am 04.07.2016. Der Mindestzeitraum von zehn WT
8487
# beginnt am 05.07.2016 und endet am 18.07.2016. Frühestes zulässiges Anmeldedatum
8588
# ist damit der 19.07.2016, sodass die Marktlokation dem LFN frühestens zum Beginn
8689
# des vorgenannten Tages zugeordnet wird.
8790
eingang_der_anmeldung = date(2016, 7, 4)
8891
gesetzliche_frist = Period(
89-
10,
90-
DayType.WORKING_DAY,
91-
end_date_type=EndDateType.EXCLUSIVE
92-
# lieferbeginn is the exclusive end of the previous supply contract
92+
10,
93+
DayType.WORKING_DAY,
94+
end_date_type=EndDateType.EXCLUSIVE
95+
# lieferbeginn is the exclusive end of the previous supply contract
9396
)
9497
fruehest_moeglicher_lieferbeginn = add_frist(eingang_der_anmeldung, gesetzliche_frist)
9598
assert fruehest_moeglicher_lieferbeginn == date(2016, 7, 19)
9699
```
97100
### Calculate "Liefer- and Fristenmonate"
98101
Liefer- and Fristenmonat are concepts used in MaBiS and GPKE:
102+
99103
```python
100104
from datetime import date
101105

102-
from bdew_datetimes.periods import get_nth_working_day_of_month, MonthType
106+
from bdew_datetimes import get_nth_working_day_of_month
107+
from bdew_datetimes.enums import MonthType
103108

104109
# returns the 18th working day of the current month in Germany
105110
get_nth_working_day_of_month(18)

src/bdew_datetimes/__init__.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,25 @@
22
bdew_datetimes is a package that models the BDEW holiday, which is relevant for German utilities
33
"""
44

5-
from pytz import timezone
5+
from .calendar import BdewDefinedHolidays, create_bdew_calendar
6+
from .german_time_zone import GERMAN_TIME_ZONE
7+
from .models import Period
8+
from .periods import (
9+
add_frist,
10+
get_next_working_day,
11+
get_nth_working_day_of_month,
12+
get_previous_working_day,
13+
is_bdew_working_day,
14+
)
615

7-
from .calendar import create_bdew_calendar
8-
9-
GERMAN_TIME_ZONE = timezone("Europe/Berlin")
10-
__all__ = ["GERMAN_TIME_ZONE", "create_bdew_calendar"]
16+
__all__ = [
17+
"create_bdew_calendar",
18+
"BdewDefinedHolidays",
19+
"Period",
20+
"is_bdew_working_day",
21+
"get_next_working_day",
22+
"get_previous_working_day",
23+
"add_frist",
24+
"get_nth_working_day_of_month",
25+
"GERMAN_TIME_ZONE",
26+
]

src/bdew_datetimes/calendar.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,6 @@ def create_bdew_calendar() -> HolidaySum:
7878
# https://github.com/vacanza/python-holidays/blob/v0.53/holidays/holiday_base.py#L1164
7979
result.language = original_language_before_adding_subdivisions
8080
return result
81+
82+
83+
__all__ = ["BdewDefinedHolidays", "create_bdew_calendar"]

src/bdew_datetimes/enums.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""enums used inside the package"""
2+
3+
from enum import Enum
4+
5+
6+
class Division(Enum):
7+
"""
8+
Allows to distinguish divisions used by German utilities, German "Sparte".
9+
"""
10+
11+
STROM = 1 #: electricity
12+
GAS = 2 #: gas
13+
14+
15+
class DayType(str, Enum):
16+
"""
17+
An enum to differentiate between calendar days and working days.
18+
"""
19+
20+
WORKING_DAY = "WT" #: working day, German "Werktag"
21+
CALENDAR_DAY = "KT" #: calendar day, German "Kalendertag"
22+
23+
24+
class EndDateType(Enum):
25+
"""
26+
An enum to distinguish inclusive and exclusive end dates.
27+
"""
28+
29+
INCLUSIVE = 1
30+
"""
31+
If a contract ends with the year 2022 and the end date is denoted as "2022-12-31",
32+
then the end date is inclusive. Most dates in human (spoken) communication are meant
33+
inclusively.
34+
"""
35+
36+
EXCLUSIVE = 2
37+
"""
38+
If a contract ends with the year 2022 and the end date is denoted as "2023-01-01",
39+
then the end date is exclusive. Most end dates handled by technical systems are meant
40+
exclusively.
41+
"""
42+
43+
44+
class MonthType(Enum):
45+
"""
46+
When calculating periods defined as 'nth working day of a month' the
47+
BNetzA regulations distinguish between two types of month which are
48+
modelled in this enum.
49+
Some periods refer to the "Liefermonat", others to the "Fristenmonat".
50+
"""
51+
52+
LIEFERMONAT = 1
53+
"""
54+
The "Liefermonat" is the month in which the supply starts.
55+
"""
56+
FRISTENMONAT = 2
57+
"""
58+
The grid operators prefer a key date based handling of supply contracts.
59+
The key date in these cases is usually expressed as a specific working day
60+
in the so called "Fristenmonat".
61+
The "Fristenmonat" starts at the first day of the month
62+
_before_ the "Liefermonat".
63+
Quote: 'Nach der Festlegung BK6-06-009 (GPKE) der Monat vor dem Liefermonat.'
64+
"""
65+
# pylint:disable=line-too-long
66+
# source: https://www.bundesnetzagentur.de/DE/Beschlusskammern/1_GZ/BK6-GZ/_bis_2010/2006/BK6-06-009/BK6-06-009_Beschluss_download.pdf?__blob=publicationFile&v=5
67+
68+
69+
__all__ = ["Division", "EndDateType", "MonthType", "DayType"]

src/bdew_datetimes/german_strom_and_gas_tag.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
"""
55

66
from datetime import datetime, time
7-
from enum import Enum
87
from typing import Callable
98

109
# The problem with the stdlib zoneinfo is, that the availability of timezones
@@ -14,16 +13,9 @@
1413
# datasource for timezone information.
1514
from pytz import utc
1615

17-
from bdew_datetimes import GERMAN_TIME_ZONE
16+
from bdew_datetimes.enums import Division
1817

19-
20-
class Division(Enum):
21-
"""
22-
Allows to distinguish divisions used by German utilities, German "Sparte".
23-
"""
24-
25-
STROM = 1 #: electricity
26-
GAS = 2 #: gas
18+
from .german_time_zone import GERMAN_TIME_ZONE
2719

2820

2921
def _get_german_local_time(date_time: datetime) -> time:
@@ -102,3 +94,6 @@ def is_xtag_limit(date_time: datetime, division: Division) -> bool:
10294
f"The division must either be 'Strom' or 'Gas': '{division}'"
10395
)
10496
return xtag_evaluator(date_time)
97+
98+
99+
__all__ = ["is_gastag_limit", "is_stromtag_limit"]
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"""static timezone object for Berlin/Germany"""
2+
3+
from pytz import timezone
4+
5+
GERMAN_TIME_ZONE = timezone("Europe/Berlin")
6+
__all__ = ["GERMAN_TIME_ZONE"]

src/bdew_datetimes/models.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"""model classes used in this package"""
2+
3+
from dataclasses import dataclass
4+
from typing import Literal, Union
5+
6+
from bdew_datetimes.enums import DayType, EndDateType
7+
8+
_DayTyp = Union[DayType, Literal["WT", "KT"]]
9+
10+
11+
@dataclass
12+
class Period:
13+
"""
14+
A period is a German "Frist": A tuple that consists of a number of days and a day type.
15+
"""
16+
17+
number_of_days: int
18+
"""
19+
number of days (might be any value <0, >0 or ==0)
20+
"""
21+
day_type: DayType
22+
"""
23+
the kind of days to add/subtract
24+
"""
25+
26+
def __init__(
27+
self,
28+
number_of_days: int,
29+
day_type: _DayTyp,
30+
end_date_type: EndDateType = EndDateType.EXCLUSIVE,
31+
):
32+
"""
33+
Initialize the Period by providing a number of days and a day_type which define the period.
34+
35+
"""
36+
self.number_of_days = number_of_days
37+
# If the Period is about something ending (e.g. a contract), then the user may
38+
# provide an end_date_type.
39+
# Internally we handle all end dates as exclusive, because:
40+
# https://hf-kklein.github.io/exclusive_end_dates.github.io/
41+
if end_date_type == EndDateType.INCLUSIVE:
42+
if self.number_of_days > 0:
43+
self.number_of_days = self.number_of_days - 1
44+
elif self.number_of_days < 0:
45+
self.number_of_days = self.number_of_days + 1
46+
if isinstance(day_type, DayType):
47+
pass
48+
elif isinstance(day_type, str):
49+
day_type = DayType(day_type)
50+
else:
51+
raise ValueError(
52+
f"'{day_type}' is not an allowed value; Check the typing"
53+
)
54+
self.day_type: DayType = day_type
55+
56+
57+
__all__ = ["Period"]

0 commit comments

Comments
 (0)