# bdew_datetimes

A collection of utils to work with datetimes and holidays in the German energy
market and is based on the [python-holiday](https://github.com/dr-prodigy/python-holidays) package.

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. 

### Current highlights:
* BDEW-holiday calendar
    * allows dict like evaluation of dates and datetimes and contains all holidays considered by the BDEW
* Statutory Periods ("_Gesetzliche Fristen_")
    * calculate dates of the kind "_x Werktage ab Stichtag_"
    * calculate dates of the kind "_nter Werktag des Fristen- bzw. Liefermonats_"
* Gas-Day / Market Day evaluation

### Future Scope:

* providing subdivision holiday calendars to allow granular load profiles


## Quick Start and Examples

Install the package from [pypi](https://pypi.org/project/bdew-datetimes/):
```bash
pip install bdew_datetimes
```

### Check if a date is a _specific_ BDEW Holidays

The `HolidaySum` returned by `create_bdew_calendar` contains the BDEW specific holidays.
This includes Heiligabend and Silvester as well as special days without Marktkommunikation but _not_ the local or nationwide holidays in Germany and its states. 
```python
from datetime import date
from bdew_datetimes import create_bdew_calendar

bdew_holidays = create_bdew_calendar()  # this behaves like a dict

assert date(2022, 12, 31) in bdew_holidays # Silvester is a BDEW holiday
assert date(2022, 8, 8) in bdew_holidays is False # Augsburger Friedensfest is _not_ a BDEW holiday (but a holiday in Augsburg only)
assert date(2022, 12, 2) in bdew_holidays is False # The 12th of February is not a BDEW holiday

print(bdew_holidays.get('2022-01-01'))  # prints "Neujahr"
```

### Check if a given Date is a BDEW Working Day
BDEW working days are those days taken into account for the "Fristenberechnung".
The function `is_bdew_working_day` considers both national and state wide holidays as well as BDEW holidays:
```python
from datetime import date

from bdew_datetimes.periods import is_bdew_working_day

assert is_bdew_working_day(date(2023, 1, 1)) is False  # Neujahr (national holiday)
assert is_bdew_working_day(date(2023, 1, 2)) is True  # regular weekday
assert is_bdew_working_day(date(2023, 1, 6)) is False  # Heilige Drei Könige (local holiday in parts of Germany)
assert is_bdew_working_day(date(2023, 4, 7)) is False  # Karfreitag (national holiday, but based on an astronomical calendar)
assert is_bdew_working_day(date(2023, 12, 24)) is False  # Heiligabend (BDEW holiday)
```

You can also get the next or previous working day for any date:
```python
from datetime import date

from bdew_datetimes.periods import get_next_working_day, get_previous_working_day

assert get_next_working_day(date(2023, 1, 1)) == date(2023, 1, 2)  # the next working day after Neujahr
assert get_previous_working_day(date(2023, 1, 1)) == date(2022, 12, 30)  # the last working day of 2022
assert get_next_working_day(date(2023, 1, 20)) == date(2023, 1, 23)  # the next working day after a friday is the next monday
```

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

from bdew_datetimes.periods import DayType, EndDateType, Period, add_frist

# Eingang der Anmeldung des LFN erfolgt am 04.07.2016. Der Mindestzeitraum von zehn WT
# beginnt am 05.07.2016 und endet am 18.07.2016. Frühestes zulässiges Anmeldedatum
# ist damit der 19.07.2016, sodass die Marktlokation dem LFN frühestens zum Beginn
# des vorgenannten Tages zugeordnet wird.
eingang_der_anmeldung = date(2016, 7, 4)
gesetzliche_frist = Period(
    10,
    DayType.WORKING_DAY,
    end_date_type=EndDateType.EXCLUSIVE
    # lieferbeginn is the exclusive end of the previous supply contract
)
fruehest_moeglicher_lieferbeginn = add_frist(eingang_der_anmeldung, gesetzliche_frist)
assert fruehest_moeglicher_lieferbeginn == date(2016, 7, 19)
```
### Calculate "Liefer- and Fristenmonate"
Liefer- and Fristenmonat are concepts used in MaBiS and GPKE:
```python
from datetime import date

from bdew_datetimes.periods import get_nth_working_day_of_month, MonthType

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

# the 18th working day of November 2023
assert get_nth_working_day_of_month(18, start=date(2023, 11, 1)) == date(2023, 11, 28)

# the 42th working day of Fristenmonat July 2023
assert get_nth_working_day_of_month(42, month_type=MonthType.FRISTENMONAT, start=date(2023, 7, 1)) == date(2023, 9, 29)
```

## Notes

The BDEW considers all days as holidays, which are nationwide holidays and days, which are a holiday in at least one state.
Furthermore, the 24. and the 31. December are holidays as well.
Therefore, this package utilizes a composition of all available german holiday calendars amd adds the two additional days.

Shifting holidays to the next weekday if they fall on a weekend is currently not considered.  


## License

This library is licensed under the *MIT* license, see the [LICENSE file](LICENSE).
