my.calendar.holidays: cleanup + ci/stats + split off private data handling to https://github.com/karlicoss/hpi-personal-overlay
This commit is contained in:
parent
1f9be2c236
commit
bdb5dcd221
5 changed files with 40 additions and 69 deletions
|
@ -1,91 +1,49 @@
|
||||||
"""
|
"""
|
||||||
Public holidays (automatic) and days off work (manual inputs)
|
Holidays and days off work
|
||||||
"""
|
"""
|
||||||
|
REQUIRES = [
|
||||||
|
'workalendar', # library to determine public holidays
|
||||||
|
]
|
||||||
|
|
||||||
from functools import lru_cache
|
|
||||||
from datetime import date, datetime, timedelta
|
from datetime import date, datetime, timedelta
|
||||||
import re
|
from typing import Union
|
||||||
from typing import Tuple, Iterator, List, Union
|
|
||||||
|
|
||||||
|
|
||||||
from my.config.holidays_data import HOLIDAYS_DATA
|
# TODO would be nice to do it dynamically depending on the past timezones...
|
||||||
|
|
||||||
|
|
||||||
# pip3 install workalendar
|
|
||||||
from workalendar.europe import UnitedKingdom # type: ignore
|
from workalendar.europe import UnitedKingdom # type: ignore
|
||||||
cal = UnitedKingdom() # TODO
|
cal = UnitedKingdom()
|
||||||
# TODO that should depend on country/'location' of residence I suppose?
|
# TODO that should depend on country/'location' of residence I suppose?
|
||||||
|
|
||||||
|
|
||||||
Dateish = Union[datetime, date, str]
|
# todo move to common?
|
||||||
|
DateIsh = Union[datetime, date, str]
|
||||||
|
def as_date(dd: DateIsh) -> date:
|
||||||
def as_date(dd: Dateish) -> date:
|
|
||||||
if isinstance(dd, datetime):
|
if isinstance(dd, datetime):
|
||||||
return dd.date()
|
return dd.date()
|
||||||
elif isinstance(dd, date):
|
elif isinstance(dd, date):
|
||||||
return dd
|
return dd
|
||||||
else:
|
else:
|
||||||
|
# todo parse isoformat??
|
||||||
return as_date(datetime.strptime(dd, '%Y%m%d'))
|
return as_date(datetime.strptime(dd, '%Y%m%d'))
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(1)
|
def is_holiday(d: DateIsh) -> bool:
|
||||||
def get_days_off_work() -> List[date]:
|
|
||||||
return list(iter_days_off_work())
|
|
||||||
|
|
||||||
|
|
||||||
def is_day_off_work(d: date) -> bool:
|
|
||||||
return d in get_days_off_work()
|
|
||||||
|
|
||||||
|
|
||||||
def is_working_day(d: Dateish) -> bool:
|
|
||||||
day = as_date(d)
|
day = as_date(d)
|
||||||
if not cal.is_working_day(day):
|
return not cal.is_working_day(day)
|
||||||
# public holiday -- def holiday
|
|
||||||
return False
|
|
||||||
# otherwise rely on work data
|
|
||||||
return not is_day_off_work(day)
|
|
||||||
|
|
||||||
|
|
||||||
def is_holiday(d: Dateish) -> bool:
|
def is_workday(d: DateIsh) -> bool:
|
||||||
return not(is_working_day(d))
|
return not is_holiday(d)
|
||||||
|
|
||||||
|
|
||||||
def _iter_work_data() -> Iterator[Tuple[date, int]]:
|
from ..core.common import Stats
|
||||||
emitted = 0
|
def stats() -> Stats:
|
||||||
for x in HOLIDAYS_DATA.splitlines():
|
# meh, but not sure what would be a better test?
|
||||||
m = re.search(r'(\d\d/\d\d/\d\d\d\d)(.*)-(\d+.\d+) days \d+.\d+ days', x)
|
res = {}
|
||||||
if m is None:
|
year = datetime.now().year
|
||||||
continue
|
jan1 = date(year=year, month=1, day=1)
|
||||||
(ds, cmnt, dayss) = m.groups()
|
for x in range(-7, 20):
|
||||||
if 'carry over' in cmnt:
|
d = jan1 + timedelta(days=x)
|
||||||
continue
|
h = is_holiday(d)
|
||||||
|
res[d.isoformat()] = 'holiday' if h else 'workday'
|
||||||
d = datetime.strptime(ds, '%d/%m/%Y').date()
|
return res
|
||||||
dd, u = dayss.split('.')
|
|
||||||
assert u == '00' # TODO meh
|
|
||||||
|
|
||||||
yield d, int(dd)
|
|
||||||
emitted += 1
|
|
||||||
assert emitted > 5 # arbitrary, just a sanity check.. (TODO move to tests?)
|
|
||||||
|
|
||||||
|
|
||||||
def iter_days_off_work() -> Iterator[date]:
|
|
||||||
for d, span in _iter_work_data():
|
|
||||||
dd = d
|
|
||||||
while span > 0:
|
|
||||||
# only count it if it wasnt' a public holiday/weekend already
|
|
||||||
if cal.is_working_day(dd):
|
|
||||||
yield dd
|
|
||||||
span -= 1
|
|
||||||
dd += timedelta(days=1)
|
|
||||||
|
|
||||||
|
|
||||||
def test():
|
|
||||||
assert is_holiday('20190101')
|
|
||||||
assert not is_holiday('20180601')
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
for d in iter_days_off_work():
|
|
||||||
print(d, ' | ', d.strftime('%d %b'))
|
|
||||||
|
|
|
@ -156,6 +156,8 @@ def modules_check(args):
|
||||||
module: Optional[str] = args.module
|
module: Optional[str] = args.module
|
||||||
vw = '' if verbose else '; pass --verbose to print more information'
|
vw = '' if verbose else '; pass --verbose to print more information'
|
||||||
|
|
||||||
|
# todo force verbose if it's single module?
|
||||||
|
|
||||||
from . import common
|
from . import common
|
||||||
common.QUICK_STATS = quick # dirty, but hopefully OK for cli
|
common.QUICK_STATS = quick # dirty, but hopefully OK for cli
|
||||||
|
|
||||||
|
|
6
tests/calendar.py
Normal file
6
tests/calendar.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from my.calendar.holidays import is_holiday
|
||||||
|
|
||||||
|
|
||||||
|
def test() -> None:
|
||||||
|
assert is_holiday('20190101')
|
||||||
|
assert not is_holiday('20180601')
|
7
tox.ini
7
tox.ini
|
@ -20,6 +20,9 @@ commands =
|
||||||
# my.time.tz.via_location dep
|
# my.time.tz.via_location dep
|
||||||
pip install timezonefinder
|
pip install timezonefinder
|
||||||
|
|
||||||
|
# my.calendar.holidays dep
|
||||||
|
pip install workalendar
|
||||||
|
|
||||||
python3 -m pytest \
|
python3 -m pytest \
|
||||||
tests/core.py \
|
tests/core.py \
|
||||||
tests/misc.py \
|
tests/misc.py \
|
||||||
|
@ -29,7 +32,8 @@ commands =
|
||||||
tests/demo.py \
|
tests/demo.py \
|
||||||
tests/bluemaestro.py \
|
tests/bluemaestro.py \
|
||||||
tests/location.py \
|
tests/location.py \
|
||||||
tests/tz.py
|
tests/tz.py \
|
||||||
|
tests/calendar.py
|
||||||
# TODO add; once I figure out porg depdencency?? tests/config.py
|
# TODO add; once I figure out porg depdencency?? tests/config.py
|
||||||
# TODO run demo.py? just make sure with_my is a bit cleverer?
|
# TODO run demo.py? just make sure with_my is a bit cleverer?
|
||||||
# TODO e.g. under CI, rely on installing
|
# TODO e.g. under CI, rely on installing
|
||||||
|
@ -68,6 +72,7 @@ commands =
|
||||||
-p my.bluemaestro \
|
-p my.bluemaestro \
|
||||||
-p my.location.google \
|
-p my.location.google \
|
||||||
-p my.time.tz.via_location \
|
-p my.time.tz.via_location \
|
||||||
|
-p my.calendar.holidays \
|
||||||
--txt-report .mypy-coverage \
|
--txt-report .mypy-coverage \
|
||||||
--html-report .mypy-coverage \
|
--html-report .mypy-coverage \
|
||||||
{posargs}
|
{posargs}
|
||||||
|
|
Loading…
Add table
Reference in a new issue