59 lines
1.5 KiB
Python
59 lines
1.5 KiB
Python
"""
|
|
Holidays and days off work
|
|
"""
|
|
REQUIRES = [
|
|
'workalendar', # library to determine public holidays
|
|
]
|
|
|
|
from datetime import date, datetime, timedelta
|
|
from functools import lru_cache
|
|
from typing import Union
|
|
|
|
from my.core import Stats
|
|
from my.core.time import zone_to_countrycode
|
|
|
|
|
|
@lru_cache(1)
|
|
def _calendar():
|
|
from workalendar.registry import registry # type: ignore
|
|
# todo switch to using time.tz.main once _get_tz stabilizes?
|
|
from ..time.tz import via_location as LTZ
|
|
# TODO would be nice to do it dynamically depending on the past timezones...
|
|
tz = LTZ._get_tz(datetime.now())
|
|
assert tz is not None
|
|
zone = tz.zone; assert zone is not None
|
|
code = zone_to_countrycode(zone)
|
|
Cal = registry.get_calendars()[code]
|
|
return Cal()
|
|
|
|
# todo move to common?
|
|
DateIsh = Union[datetime, date, str]
|
|
def as_date(dd: DateIsh) -> date:
|
|
if isinstance(dd, datetime):
|
|
return dd.date()
|
|
elif isinstance(dd, date):
|
|
return dd
|
|
else:
|
|
# todo parse isoformat??
|
|
return as_date(datetime.strptime(dd, '%Y%m%d'))
|
|
|
|
|
|
def is_holiday(d: DateIsh) -> bool:
|
|
day = as_date(d)
|
|
return not _calendar().is_working_day(day)
|
|
|
|
|
|
def is_workday(d: DateIsh) -> bool:
|
|
return not is_holiday(d)
|
|
|
|
|
|
def stats() -> Stats:
|
|
# meh, but not sure what would be a better test?
|
|
res = {}
|
|
year = datetime.now().year
|
|
jan1 = date(year=year, month=1, day=1)
|
|
for x in range(-7, 20):
|
|
d = jan1 + timedelta(days=x)
|
|
h = is_holiday(d)
|
|
res[d.isoformat()] = 'holiday' if h else 'workday'
|
|
return res
|