Files
Django_kube/Webcal-python/venv/lib/python3.11/site-packages/ics/utils.py
T

236 lines
6.3 KiB
Python

import re
from datetime import timedelta
from typing import Dict, List, Optional, Tuple, Union
from uuid import uuid4
import arrow
from arrow.arrow import Arrow
from dateutil.tz import gettz
from ics.grammar import parse
from ics.grammar.parse import Container, ContentLine
tzutc = arrow.utcnow().tzinfo
def remove_x(container: Container) -> None:
for i in reversed(range(len(container))):
item = container[i]
if item.name.startswith('X-'):
del container[i]
def remove_sequence(container: Container) -> None:
for i in reversed(range(len(container))):
item = container[i]
if item.name == 'SEQUENCE':
del container[i]
DATE_FORMATS: Dict[int, str] = dict((len(k), k) for k in (
'YYYYMM',
'YYYYMMDD',
'YYYYMMDDTHH',
'YYYYMMDDTHHmm',
'YYYYMMDDTHHmmss'))
def arrow_get(string: str) -> Arrow:
'''this function exists because ICS uses ISO 8601 without dashes or
colons, i.e. not ISO 8601 at all.'''
# replace slashes with dashes
if '/' in string:
string = string.replace('/', '-')
# if string contains dashes, assume it to be proper ISO 8601
if '-' in string:
return arrow.get(string)
string = string.rstrip('Z')
return arrow.get(string, DATE_FORMATS[len(string)])
def iso_to_arrow(time_container: Optional[ContentLine], available_tz={}) -> Arrow:
if time_container is None:
return None
# TODO : raise if not iso date
tz_list = time_container.params.get('TZID')
# TODO : raise if len(tz_list) > 1 or if tz is not a valid tz
# TODO : see if timezone is registered as a VTIMEZONE
tz: Optional[str]
if tz_list and len(tz_list) > 0:
tz = tz_list[0]
else:
tz = None
if ('T' not in time_container.value) and \
'DATE' in time_container.params.get('VALUE', []):
val = time_container.value + 'T0000'
else:
val = time_container.value
if tz and not (val[-1].upper() == 'Z'):
naive = arrow_get(val).naive
selected_tz = gettz(tz)
if not selected_tz:
selected_tz = available_tz.get(tz, 'UTC')
return arrow.get(naive, selected_tz)
else:
return arrow_get(val)
# TODO : support floating (ie not bound to any time zone) times (cf
# http://www.kanzaki.com/docs/ical/dateTime.html)
def iso_precision(string: str) -> str:
has_time = 'T' in string
if has_time:
date_string, time_string = string.split('T', 1)
time_parts = re.split('[+-]', time_string, 1)
has_seconds = time_parts[0].count(':') > 1
has_seconds = not has_seconds and len(time_parts[0]) == 6
if has_seconds:
return 'second'
else:
return 'minute'
else:
return 'day'
def get_lines(container: Container, name: str, keep: bool = False) -> List[ContentLine]:
lines = []
for i in reversed(range(len(container))):
item = container[i]
if item.name == name:
lines.append(item)
if not keep:
del container[i]
return lines
def parse_duration(line: str) -> timedelta:
"""
Return a timedelta object from a string in the DURATION property format
"""
DAYS = {'D': 1, 'W': 7}
SECS = {'S': 1, 'M': 60, 'H': 3600}
sign, i = 1, 0
if line[i] in '-+':
if line[i] == '-':
sign = -1
i += 1
if line[i] != 'P':
raise parse.ParseError("Error while parsing %s" % line)
i += 1
days, secs = 0, 0
while i < len(line):
if line[i] == 'T':
i += 1
if i == len(line):
break
j = i
while line[j].isdigit():
j += 1
if i == j:
raise parse.ParseError("Error while parsing %s" % line)
val = int(line[i:j])
if line[j] in DAYS:
days += val * DAYS[line[j]]
DAYS.pop(line[j])
elif line[j] in SECS:
secs += val * SECS[line[j]]
SECS.pop(line[j])
else:
raise parse.ParseError("Error while parsing %s" % line)
i = j + 1
return timedelta(sign * days, sign * secs)
def timedelta_to_duration(dt: timedelta) -> str:
"""
Return a string according to the DURATION property format
from a timedelta object
"""
ONE_DAY_IN_SECS = 3600 * 24
total = abs(int(dt.total_seconds()))
days = total // ONE_DAY_IN_SECS
seconds = total % ONE_DAY_IN_SECS
res = 'P'
if days // 7:
res += str(days // 7) + 'W'
days %= 7
if days:
res += str(days) + 'D'
if seconds:
res += 'T'
if seconds // 3600:
res += str(seconds // 3600) + 'H'
seconds %= 3600
if seconds // 60:
res += str(seconds // 60) + 'M'
seconds %= 60
if seconds:
res += str(seconds) + 'S'
if dt.total_seconds() >= 0:
return res
else:
return "-%s" % res
def get_arrow(value: Union[None, Arrow, Tuple, Dict]) -> Arrow:
if value is None:
return None
elif isinstance(value, Arrow):
return value
elif isinstance(value, tuple):
return arrow.get(*value)
elif isinstance(value, dict):
return arrow.get(**value)
else:
return arrow.get(value)
def arrow_to_iso(instant: Arrow) -> str:
# set to utc, make iso, remove timezone
instant = arrow.get(instant.astimezone(tzutc)).format('YYYYMMDDTHHmmss')
return instant + 'Z'
def arrow_date_to_iso(instant: Arrow) -> str:
# date-only for all day events
# set to utc, make iso, remove timezone
instant = arrow.get(instant.astimezone(tzutc)).format('YYYYMMDD')
return instant # no TZ for all days
def uid_gen() -> str:
uid = str(uuid4())
return "{}@{}.org".format(uid, uid[:4])
def escape_string(string: str) -> str:
string = string.replace("\\", "\\\\")
string = string.replace(";", "\\;")
string = string.replace(",", "\\,")
string = string.replace("\n", "\\n")
string = string.replace("\r", "\\r")
return string
def unescape_string(string: str) -> str:
string = string.replace("\\;", ";")
string = string.replace("\\,", ",")
string = string.replace("\\n", "\n")
string = string.replace("\\N", "\n")
string = string.replace("\\r", "\r")
string = string.replace("\\R", "\r")
string = string.replace("\\\\", "\\")
return string