my.calendar.holidays: cleanup + ci/stats + split off private data handling to https://github.com/karlicoss/hpi-personal-overlay

This commit is contained in:
Dima Gerasimov 2020-10-09 20:09:56 +01:00 committed by karlicoss
parent 1f9be2c236
commit bdb5dcd221
5 changed files with 40 additions and 69 deletions

View file

@ -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'))

View file

@ -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
View file

@ -0,0 +1,6 @@
from my.calendar.holidays import is_holiday
def test() -> None:
assert is_holiday('20190101')
assert not is_holiday('20180601')

View file

@ -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}