2025-01-15 19:04:41 +01:00
|
|
|
import datetime as dt
|
|
|
|
import re, logging
|
2025-01-16 15:43:06 +01:00
|
|
|
import zoneinfo
|
2025-01-15 19:04:41 +01:00
|
|
|
|
|
|
|
from .sync import Event
|
|
|
|
|
|
|
|
class IcalHelper:
|
|
|
|
|
|
|
|
_WEEKDAY_MAP = {
|
|
|
|
'Mon': 0,
|
|
|
|
'Tue': 1,
|
|
|
|
'Wed': 2,
|
|
|
|
'Thu': 3,
|
|
|
|
'Fri': 4,
|
|
|
|
'Sat': 5,
|
|
|
|
'Sun': 6
|
|
|
|
}
|
|
|
|
|
2025-01-16 15:43:06 +01:00
|
|
|
tzDE = zoneinfo.ZoneInfo('Europe/Berlin')
|
|
|
|
deltaWeek = dt.timedelta(weeks=1)
|
|
|
|
deltaDay = dt.timedelta(days=1)
|
|
|
|
firstDay = dt.date(year=dt.datetime.now().year, month=1, day=1)
|
|
|
|
|
2025-01-15 19:04:41 +01:00
|
|
|
def __init__(self):
|
|
|
|
self._l = logging.getLogger(__name__)
|
|
|
|
self.reTime = re.compile(r'(\d{1,2}):(\d{2})')
|
|
|
|
|
|
|
|
def getStart(self, event: Event, holidays):
|
2025-01-16 15:43:06 +01:00
|
|
|
firstDay = self._getFirstOccurence(event, holidays)
|
|
|
|
|
|
|
|
if firstDay is None:
|
|
|
|
return None, None
|
|
|
|
return firstDay, dt.datetime.combine(firstDay, self._getTime(event.start), tzinfo=self.tzDE)
|
2025-01-15 19:04:41 +01:00
|
|
|
|
|
|
|
def _getFirstWeekdayInYear(self, weekday):
|
|
|
|
candidate = self.firstDay
|
|
|
|
while candidate.weekday() != self._WEEKDAY_MAP[weekday]:
|
2025-01-16 15:43:06 +01:00
|
|
|
candidate += self.deltaDay
|
2025-01-15 19:04:41 +01:00
|
|
|
|
|
|
|
self._l.log(5, 'First %s in year is %s', weekday, candidate)
|
|
|
|
|
|
|
|
return candidate
|
|
|
|
|
|
|
|
def _getSortedHolidays(self, holidays):
|
|
|
|
return sorted(holidays['holidays'], key=lambda h: h['from'])
|
|
|
|
|
|
|
|
def _getSortedFeasts(self, holidays):
|
|
|
|
return sorted(holidays['feasts'])
|
2025-01-16 15:43:06 +01:00
|
|
|
|
|
|
|
def _isFeast(self, candidate, feasts):
|
|
|
|
return candidate in feasts
|
|
|
|
|
|
|
|
def _isHoliday(self, candidate, holidays):
|
|
|
|
for h in holidays:
|
|
|
|
if h['from'] <= candidate <= h['to']:
|
|
|
|
return True
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
def _getLastHolidayException(self, holidays):
|
|
|
|
holidayList = list(self._getSortedHolidays(holidays))
|
|
|
|
holidayList.reverse()
|
|
|
|
lastHoliday = holidayList[0]
|
|
|
|
lastHolidayDay = lastHoliday['to']
|
|
|
|
|
|
|
|
feastList = list(self._getSortedFeasts(holidays))
|
|
|
|
feastList.reverse()
|
|
|
|
lastFeast = feastList[0]
|
|
|
|
lastFeastDay = lastFeast
|
|
|
|
|
|
|
|
return max(lastHolidayDay, lastFeastDay)
|
2025-01-15 19:04:41 +01:00
|
|
|
|
|
|
|
def _getFirstOccurence(self, event: Event, holidays):
|
|
|
|
firstWeekday = self._getFirstWeekdayInYear(event.day)
|
2025-01-16 15:43:06 +01:00
|
|
|
|
|
|
|
|
|
|
|
candidate = firstWeekday
|
|
|
|
while candidate.year == firstWeekday.year:
|
|
|
|
if self._isFeast(candidate, holidays['feasts']):
|
|
|
|
self._l.log(5, 'Found feast %s', candidate)
|
|
|
|
candidate += self.deltaWeek
|
|
|
|
continue
|
|
|
|
|
|
|
|
if self._isHoliday(candidate, holidays['holidays']):
|
|
|
|
self._l.log(5, 'Found holiday %s', candidate)
|
|
|
|
candidate += self.deltaWeek
|
|
|
|
continue
|
|
|
|
|
|
|
|
self._l.log(5, 'Found first occurence %s', candidate)
|
|
|
|
return candidate
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
# firstWeekday.tzinfo = dt.timezone.tzname('Europe/Berlin')
|
|
|
|
|
|
|
|
def _getTime(self, startStr):
|
|
|
|
match = self.reTime.match(startStr)
|
|
|
|
return dt.time(int(match.group(1)), int(match.group(2)))
|
|
|
|
|
|
|
|
def getHolidayExceptions(self, event: Event, startDay, holidays):
|
|
|
|
lastHolidayDay = self._getLastHolidayException(holidays)
|
|
|
|
|
|
|
|
pointer = startDay
|
|
|
|
exeptions = []
|
|
|
|
while pointer <= lastHolidayDay:
|
|
|
|
ex = dt.datetime.combine(pointer, self._getTime(event.start), tzinfo=self.tzDE)
|
|
|
|
|
|
|
|
if self._isFeast(pointer, holidays['feasts']):
|
|
|
|
exeptions.append(ex)
|
|
|
|
|
|
|
|
if self._isHoliday(pointer, holidays['holidays']) and not event.external:
|
|
|
|
exeptions.append(ex)
|
|
|
|
|
|
|
|
pointer += self.deltaWeek
|
|
|
|
|
|
|
|
if len(exeptions) > 0:
|
|
|
|
return exeptions
|
|
|
|
return None
|