faori's picture
Upload folder using huggingface_hub
550665c verified
raw
history blame
23.6 kB
from datetime import datetime, date
from tzlocal import get_localzone_name
from .util.date_time_util import ensure_localisation
class Duration:
"""Represents properties that contain a duration of time."""
def __init__(self, w=None, d=None, h=None, m=None, s=None):
"""
:param w: weeks
:param d: days
:param h: hours
:param m: minutes
:param s: seconds
"""
self.w = w
self.d = d
self.h = h
self.m = m
self.s = s
def __str__(self):
res = 'P'
if self.w:
res += '{}W'.format(self.w)
if self.d:
res += '{}D'.format(self.d)
if self.h or self.m or self.s:
res += 'T'
if self.h:
res += '{}H'.format(self.h)
if self.m:
res += '{}M'.format(self.m)
if self.s:
res += '{}S'.format(self.s)
return res
class _DayOfTheWeek:
"""Weekday representation. Optionally includes positive or negative integer
value that indicates the nth occurrence of a specific day within the "MONTHLY"
or "YEARLY" recurrence rules.
>>> str(SU)
'SU'
>>> str(FR)
'FR'
>>> str(SU(4))
'4SU'
>>> str(SU(-1))
'-1SU'
"""
def __init__(self, short, n=None):
self.short = short
self.n = n
def __call__(self, n):
return _DayOfTheWeek(self.short, n)
def __str__(self):
if self.n is None:
return self.short
else:
return str(self.n) + self.short
SU = SUNDAY = _DayOfTheWeek('SU')
MO = MONDAY = _DayOfTheWeek('MO')
TU = TUESDAY = _DayOfTheWeek('TU')
WE = WEDNESDAY = _DayOfTheWeek('WE')
TH = THURSDAY = _DayOfTheWeek('TH')
FR = FRIDAY = _DayOfTheWeek('FR')
SA = SATURDAY = _DayOfTheWeek('SA')
DEFAULT_WEEK_START = SUNDAY
SECONDLY = 'SECONDLY'
MINUTELY = 'MINUTELY'
HOURLY = 'HOURLY'
DAILY = 'DAILY'
WEEKLY = 'WEEKLY'
MONTHLY = 'MONTHLY'
YEARLY = 'YEARLY'
class Recurrence:
@staticmethod
def rule(
freq=DAILY,
interval=None,
count=None,
until=None,
by_second=None,
by_minute=None,
by_hour=None,
by_week_day=None,
by_month_day=None,
by_year_day=None,
by_week=None,
by_month=None,
by_set_pos=None,
week_start=DEFAULT_WEEK_START
):
"""This property defines a rule or repeating pattern for recurring events.
:param freq:
Identifies the type of recurrence rule. Possible values are SECONDLY, HOURLY,
MINUTELY, DAILY, WEEKLY, MONTHLY, YEARLY. Default: DAILY
:param interval:
Positive integer representing how often the recurrence rule repeats
:param count:
Number of occurrences at which to range-bound the recurrence
:param until:
End date of recurrence
:param by_second:
Second or list of seconds within a minute. Valid values are 0 to 60
:param by_minute:
Minute or list of minutes within a hour. Valid values are 0 to 59
:param by_hour:
Hour or list of hours of the day. Valid values are 0 to 23
:param by_week_day:
Day or list of days of the week.
Possible values: :py:obj:`~SUNDAY`, :py:obj:`~MONDAY`, :py:obj:`~TUESDAY`, :py:obj:`~WEDNESDAY`,
:py:obj:`~THURSDAY`, :py:obj:`~FRIDAY`, :py:obj:`~SATURDAY`
:param by_month_day:
Day or list of days of the month. Valid values are 1 to 31 or -31 to -1.
For example, -10 represents the tenth to the last day of the month.
:param by_year_day:
Day or list of days of the year. Valid values are 1 to 366 or -366 to -1.
For example, -1 represents the last day of the year.
:param by_week:
Ordinal or list of ordinals specifying weeks of the year. Valid values are 1 to 53 or -53 to -1.
:param by_month:
Month or list of months of the year. Valid values are 1 to 12.
:param by_set_pos:
Value or list of values which corresponds to the nth occurrence within the set of events
specified by the rule. Valid values are 1 to 366 or -366 to -1.
It can only be used in conjunction with another by_xxx parameter.
:param week_start:
The day on which the workweek starts.
Possible values: :py:obj:`~SUNDAY`, :py:obj:`~MONDAY`, :py:obj:`~TUESDAY`, :py:obj:`~WEDNESDAY`,
:py:obj:`~THURSDAY`, :py:obj:`~FRIDAY`, :py:obj:`~SATURDAY`
:return:
String representing specified recurrence rule in `RRULE format`_.
.. note:: If none of the by_day, by_month_day, or by_year_day are specified, the day is gotten from start date.
.. _`RRULE format`: https://tools.ietf.org/html/rfc5545#section-3.8.5
"""
return 'RRULE:' + Recurrence._rule(freq, interval, count, until, by_second, by_minute, by_hour, by_week_day,
by_month_day, by_year_day, by_week, by_month, by_set_pos, week_start)
@staticmethod
def exclude_rule(
freq=DAILY,
interval=None,
count=None,
until=None,
by_second=None,
by_minute=None,
by_hour=None,
by_week_day=None,
by_month_day=None,
by_year_day=None,
by_week=None,
by_month=None,
by_set_pos=None,
week_start=DEFAULT_WEEK_START
):
"""This property defines an exclusion rule or repeating pattern for recurring events.
:param freq:
Identifies the type of recurrence rule. Possible values are SECONDLY, HOURLY,
MINUTELY, DAILY, WEEKLY, MONTHLY, YEARLY. Default: DAILY
:param interval:
Positive integer representing how often the recurrence rule repeats
:param count:
Number of occurrences at which to range-bound the recurrence
:param until:
End date of recurrence
:param by_second:
Second or list of seconds within a minute. Valid values are 0 to 60
:param by_minute:
Minute or list of minutes within a hour. Valid values are 0 to 59
:param by_hour:
Hour or list of hours of the day. Valid values are 0 to 23
:param by_week_day:
Day or list of days of the week.
Possible values: :py:obj:`~SUNDAY`, :py:obj:`~MONDAY`, :py:obj:`~TUESDAY`, :py:obj:`~WEDNESDAY`,
:py:obj:`~THURSDAY`, :py:obj:`~FRIDAY`, :py:obj:`~SATURDAY`
:param by_month_day:
Day or list of days of the month. Valid values are 1 to 31 or -31 to -1.
For example, -10 represents the tenth to the last day of the month.
:param by_year_day:
Day or list of days of the year. Valid values are 1 to 366 or -366 to -1.
For example, -1 represents the last day of the year.
:param by_week:
Ordinal or list of ordinals specifying weeks of the year. Valid values are 1 to 53 or -53 to -1.
:param by_month:
Month or list of months of the year. Valid values are 1 to 12.
:param by_set_pos:
Value or list of values which corresponds to the nth occurrence within the set of events
specified by the rule. Valid values are 1 to 366 or -366 to -1.
It can only be used in conjunction with another by_xxx parameter.
:param week_start:
The day on which the workweek starts.
Possible values: :py:obj:`~SUNDAY`, :py:obj:`~MONDAY`, :py:obj:`~TUESDAY`, :py:obj:`~WEDNESDAY`,
:py:obj:`~THURSDAY`, :py:obj:`~FRIDAY`, :py:obj:`~SATURDAY`
:return:
String representing specified recurrence rule in `RRULE format`_.
.. note:: If none of the by_day, by_month_day, or by_year_day are specified, the day is gotten from start date.
.. _`RRULE format`: https://tools.ietf.org/html/rfc5545#section-3.8.5
"""
return 'EXRULE:' + Recurrence._rule(freq, interval, count, until, by_second, by_minute, by_hour, by_week_day,
by_month_day, by_year_day, by_week, by_month, by_set_pos, week_start)
@staticmethod
def dates(ds):
"""Converts date(s) set to RDATE format.
:param ds:
date/datetime object or list of date/datetime objects
:return:
RDATE string of dates.
"""
return 'RDATE;' + Recurrence._dates(ds)
@staticmethod
def times(dts, timezone=get_localzone_name()):
"""Converts datetime(s) set to RDATE format.
:param dts:
datetime object or list of datetime objects
:param timezone:
Timezone formatted as an IANA Time Zone Database name, e.g. "Europe/Zurich". By default,
the computers local timezone is used if it is configured. UTC is used otherwise.
:return:
RDATE string of datetimes with specified timezone.
"""
return 'RDATE;' + Recurrence._times(dts, timezone)
@staticmethod
def periods(ps, timezone=get_localzone_name()):
"""Converts date period(s) to RDATE format.
Period is defined as tuple of starting date/datetime and ending date/datetime or duration as Duration object:
(date/datetime, date/datetime/Duration)
:param ps:
Period or list of periods.
:param timezone:
Timezone formatted as an IANA Time Zone Database name, e.g. "Europe/Zurich". By default,
the computers local timezone is used if it is configured. UTC is used otherwise.
:return:
RDATE string of periods.
"""
return 'RDATE;' + Recurrence._periods(ps, timezone)
@staticmethod
def exclude_dates(ds):
"""Converts date(s) set to EXDATE format.
:param ds:
date/datetime object or list of date/datetime objects
:return:
EXDATE string of dates.
"""
return 'EXDATE;' + Recurrence._dates(ds)
@staticmethod
def exclude_times(dts, timezone=get_localzone_name()):
"""Converts datetime(s) set to EXDATE format.
:param dts:
datetime object or list of datetime objects
:param timezone:
Timezone formatted as an IANA Time Zone Database name, e.g. "Europe/Zurich". By default,
the computers local timezone is used if it is configured. UTC is used otherwise.
:return:
EXDATE string of datetimes with specified timezone.
"""
return 'EXDATE;' + Recurrence._times(dts, timezone)
@staticmethod
def exclude_periods(ps, timezone=get_localzone_name()):
"""Converts date period(s) to EXDATE format.
Period is defined as tuple of starting date/datetime and ending date/datetime or duration as Duration object:
(date/datetime, date/datetime/Duration)
:param ps:
Period or list of periods.
:param timezone:
Timezone formatted as an IANA Time Zone Database name, e.g. "Europe/Zurich". By default,
the computers local timezone is used if it is configured. UTC is used otherwise.
:return:
EXDATE string of periods.
"""
return 'EXDATE;' + Recurrence._periods(ps, timezone)
@staticmethod
def _times(dts, timezone=get_localzone_name()):
"""Converts datetime(s) set to RDATE format.
:param dts:
datetime object or list of datetime objects
:param timezone:
Timezone formatted as an IANA Time Zone Database name, e.g. "Europe/Zurich". By default,
the computers local timezone is used if it is configured. UTC is used otherwise.
:return:
RDATE string of datetimes with specified timezone.
"""
if not isinstance(dts, list):
dts = [dts]
localized_datetimes = []
for dt in dts:
if not isinstance(dt, (date, datetime)):
msg = 'The dts object(s) must be date or datetime, not {!r}.'.format(dt.__class__.__name__)
raise TypeError(msg)
localized_datetimes.append(ensure_localisation(dt, timezone))
return 'TZID={}:{}'.format(timezone, ','.join(d.strftime('%Y%m%dT%H%M%S') for d in localized_datetimes))
@staticmethod
def _dates(ds):
"""Converts date(s) set to RDATE format.
:param ds:
date/datetime object or list of date/datetime objects
:return:
RDATE string of dates.
"""
if not isinstance(ds, list):
ds = [ds]
for d in ds:
if not (isinstance(d, (date, datetime))):
msg = 'The dates object(s) must be date or datetime, not {!r}.'.format(d.__class__.__name__)
raise TypeError(msg)
return 'VALUE=DATE:' + ','.join(d.strftime('%Y%m%d') for d in ds)
@staticmethod
def _periods(ps, timezone=get_localzone_name()):
"""Converts date period(s) to RDATE format.
Period is defined as tuple of starting date/datetime and ending date/datetime or duration as Duration object:
(date/datetime, date/datetime/Duration)
:param ps:
Period or list of periods.
:param timezone:
Timezone formatted as an IANA Time Zone Database name, e.g. "Europe/Zurich". By default,
the computers local timezone is used if it is configured. UTC is used otherwise.
:return:
RDATE string of periods.
"""
if not isinstance(ps, list):
ps = [ps]
period_strings = []
for start, end in ps:
if not isinstance(start, (date, datetime)):
msg = 'The start object(s) must be a date or datetime, not {!r}.'.format(end.__class__.__name__)
raise TypeError(msg)
start = ensure_localisation(start, timezone)
if isinstance(end, (date, datetime)):
end = ensure_localisation(end, timezone)
pstr = '{}/{}'.format(start.strftime('%Y%m%dT%H%M%SZ'), end.strftime('%Y%m%dT%H%M%SZ'))
elif isinstance(end, Duration):
pstr = '{}/{}'.format(start.strftime('%Y%m%dT%H%M%SZ'), end)
else:
msg = 'The end object(s) must be a date, datetime or Duration, not {!r}.'.format(end.__class__.__name__)
raise TypeError(msg)
period_strings.append(pstr)
return 'VALUE=PERIOD:' + ','.join(period_strings)
@staticmethod
def _rule(
freq=DAILY,
interval=None,
count=None,
until=None,
by_second=None, # BYSECOND
by_minute=None, # BYMINUTE
by_hour=None, # BYHOUR
by_week_day=None, # BYDAY
by_month_day=None, # BYMONTHDAY
by_year_day=None, # BYYEARDAY
by_week=None, # BYWEEKNO
by_month=None, # BYMONTH
by_set_pos=None, # BYSETPOS
week_start=DEFAULT_WEEK_START # WKST
):
"""This property defines a rule or repeating pattern for recurring events.
:param freq:
Identifies the type of recurrence rule. Possible values are SECONDLY, HOURLY,
MINUTELY, DAILY, WEEKLY, MONTHLY, YEARLY. Default: DAILY
:param interval:
Positive integer representing how often the recurrence rule repeats
:param count:
Number of occurrences at which to range-bound the recurrence
:param until:
End date of recurrence
:param by_second:
Second or list of seconds within a minute. Valid values are 0 to 60
:param by_minute:
Minute or list of minutes within a hour. Valid values are 0 to 59
:param by_hour:
Hour or list of hours of the day. Valid values are 0 to 23
:param by_week_day:
Day or list of days of the week.
Possible values: :py:obj:`~SUNDAY`, :py:obj:`~MONDAY`, :py:obj:`~TUESDAY`, :py:obj:`~WEDNESDAY`,
:py:obj:`~THURSDAY`, :py:obj:`~FRIDAY`, :py:obj:`~SATURDAY`
:param by_month_day:
Day or list of days of the month. Valid values are 1 to 31 or -31 to -1.
For example, -10 represents the tenth to the last day of the month.
:param by_year_day:
Day or list of days of the year. Valid values are 1 to 366 or -366 to -1.
For example, -1 represents the last day of the year.
:param by_week:
Ordinal or list of ordinals specifying weeks of the year. Valid values are 1 to 53 or -53 to -1.
:param by_month:
Month or list of months of the year. Valid values are 1 to 12.
:param by_set_pos:
Value or list of values which corresponds to the nth occurrence within the set of events
specified by the rule. Valid values are 1 to 366 or -366 to -1.
It can only be used in conjunction with another by_xxx parameter.
:param week_start:
The day on which the workweek starts.
Possible values: :py:obj:`~SUNDAY`, :py:obj:`~MONDAY`, :py:obj:`~TUESDAY`, :py:obj:`~WEDNESDAY`,
:py:obj:`~THURSDAY`, :py:obj:`~FRIDAY`, :py:obj:`~SATURDAY`
:return:
String representing specified recurrence rule in `RRULE format`_.
.. note:: If none of the by_day, by_month_day, or by_year_day are specified, the day is gotten from start date.
.. _`RRULE format`: https://tools.ietf.org/html/rfc5545#section-3.8.5
"""
def ensure_iterable(it):
return it if isinstance(it, (list, tuple, set)) else [it] if it is not None else []
def check_all_type(it, type_, name):
if any(not isinstance(o, type_) for o in it):
raise TypeError('"{}" parameter must be a {} or list of {}s.'
.format(name, type_.__name__, type_.__name__))
def check_all_type_and_range(it, type_, range_, name, nonzero=False):
check_all_type(it, type_, name)
low, high = range_
if any(not (low <= o <= high) for o in it):
raise ValueError('"{}" parameter must be in range {}-{}.'
.format(name, low, high))
if nonzero and any(o == 0 for o in it):
raise ValueError('"{}" parameter must be in range {}-{} and nonzero.'
.format(name, low, high))
def to_string(values):
return ','.join(map(str, values)) if values else None
if freq not in (SECONDLY, MINUTELY, HOURLY, DAILY, WEEKLY, MONTHLY, YEARLY):
raise ValueError('"freq" parameter must be one of SECONDLY, HOURLY, MINUTELY, DAILY, '
'WEEKLY, MONTHLY or YEARLY. {} was provided'.format(freq))
if interval is not None and (not isinstance(interval, int) or interval < 1):
raise ValueError('"interval" parameter must be a positive int. '
'{} was provided'.format(interval))
if count is not None and (not isinstance(count, int) or count < 1):
raise ValueError('"count" parameter must be a positive int. '
'{} was provided'.format(count))
if until is not None:
if not isinstance(until, (date, datetime)):
raise TypeError('The until object must be a date or datetime, '
'not {!r}.'.format(until.__class__.__name__))
else:
until = until.strftime("%Y%m%dT%H%M%SZ")
if count is not None and until is not None:
raise ValueError('"count" and "until" may not appear in one recurrence rule.')
by_second = ensure_iterable(by_second)
check_all_type_and_range(by_second, int, (0, 60), "by_second")
by_minute = ensure_iterable(by_minute)
check_all_type_and_range(by_minute, int, (0, 59), "by_minute")
by_hour = ensure_iterable(by_hour)
check_all_type_and_range(by_hour, int, (0, 23), "by_hour")
by_week_day = ensure_iterable(by_week_day)
check_all_type(by_week_day, _DayOfTheWeek, "by_week_day")
by_month_day = ensure_iterable(by_month_day)
check_all_type_and_range(by_month_day, int, (-31, 31), "by_month_day", nonzero=True)
by_year_day = ensure_iterable(by_year_day)
check_all_type_and_range(by_year_day, int, (-366, 366), "by_year_day", nonzero=True)
by_week = ensure_iterable(by_week)
check_all_type_and_range(by_week, int, (-53, 53), "by_week", nonzero=True)
by_month = ensure_iterable(by_month)
check_all_type_and_range(by_month, int, (1, 12), "by_month")
by_set_pos = ensure_iterable(by_set_pos)
check_all_type_and_range(by_set_pos, int, (-366, 366), "by_set_pos", nonzero=True)
if by_set_pos and all(not v for v in (by_second, by_minute, by_hour,
by_week_day, by_month_day, by_year_day,
by_week, by_month)):
raise ValueError('"by_set_pos" parameter can only be used in conjunction with another by_xxx parameter.')
if not isinstance(week_start, _DayOfTheWeek):
raise ValueError('"week_start" parameter must be one of SUNDAY, MONDAY, etc. '
'{} was provided'.format(week_start))
rrule = 'FREQ={}'.format(freq)
rule_properties = (
('INTERVAL', interval),
('COUNT', count),
('UNTIL', until),
('BYSECOND', to_string(by_second)),
('BYMINUTE', to_string(by_minute)),
('BYHOUR', to_string(by_hour)),
('BYDAY', to_string(by_week_day)),
('BYMONTHDAY', to_string(by_month_day)),
('BYYEARDAY', to_string(by_year_day)),
('BYWEEKNO', to_string(by_week)),
('BYMONTH', to_string(by_month)),
('BYSETPOS', to_string(by_set_pos)),
('WKST', week_start)
)
for key, value in rule_properties:
if value:
rrule += ';{}={}'.format(key, value)
return rrule