1#!/usr/bin/env python
2# -*- encoding: utf-8 -*-
3from datetime import datetime
4try:
5    from datetime import timezone
6    utc_tz = timezone.utc
7except:
8    ## pytz is deprecated - but as of 2021-11, the icalendar library is only
9    ## compatible with pytz (see https://github.com/collective/icalendar/issues/333 https://github.com/collective/icalendar/issues/335 https://github.com/collective/icalendar/issues/336)
10    import pytz
11    utc_tz = pytz.utc
12
13from caldav.lib.namespace import ns
14from .base import BaseElement, NamedBaseElement, ValuedBaseElement
15
16def _to_utc_date_string(ts):
17    # type (Union[date,datetime]]) -> str
18    """coerce datetimes to UTC (assume localtime if nothing is given)"""
19    if (isinstance(ts, datetime)):
20        try:
21            ## for any python version, this should work for a non-native
22            ## timestamp.
23            ## in python 3.6 and higher, ts.astimezone() will assume a
24            ## naive timestamp is localtime (and so do we)
25            ts = ts.astimezone(utc_tz)
26        except:
27            ## native time stamp and the current python version is
28            ## not able to treat it as localtime.
29            import tzlocal
30            ts = ts.replace(tzinfo=tzlocal.get_localzone())
31            ts = ts.astimezone(utc_tz)
32
33    return ts.strftime("%Y%m%dT%H%M%SZ")
34
35# Operations
36class CalendarQuery(BaseElement):
37    tag = ns("C", "calendar-query")
38
39class FreeBusyQuery(BaseElement):
40    tag = ns("C", "free-busy-query")
41
42class Mkcalendar(BaseElement):
43    tag = ns("C", "mkcalendar")
44
45class CalendarMultiGet(BaseElement):
46    tag = ns("C", "calendar-multiget")
47
48class ScheduleInboxURL(BaseElement):
49    tag = ns("C", "schedule-inbox-URL")
50
51class ScheduleOutboxURL(BaseElement):
52    tag = ns("C", "schedule-outbox-URL")
53
54# Filters
55class Filter(BaseElement):
56    tag = ns("C", "filter")
57
58
59class CompFilter(NamedBaseElement):
60    tag = ns("C", "comp-filter")
61
62
63class PropFilter(NamedBaseElement):
64    tag = ns("C", "prop-filter")
65
66
67class ParamFilter(NamedBaseElement):
68    tag = ns("C", "param-filter")
69
70
71# Conditions
72class TextMatch(ValuedBaseElement):
73    tag = ns("C", "text-match")
74
75    def __init__(self, value, collation="i;octet", negate=False):
76        super(TextMatch, self).__init__(value=value)
77        self.attributes['collation'] = collation
78        if negate:
79            self.attributes['negate-condition'] = "yes"
80
81
82class TimeRange(BaseElement):
83    tag = ns("C", "time-range")
84
85    def __init__(self, start=None, end=None):
86        ## start and end should be an icalendar "date with UTC time",
87        ## ref https://tools.ietf.org/html/rfc4791#section-9.9
88        super(TimeRange, self).__init__()
89        if start is not None:
90            self.attributes['start'] = _to_utc_date_string(start)
91        if end is not None:
92            self.attributes['end'] = _to_utc_date_string(end)
93
94
95class NotDefined(BaseElement):
96    tag = ns("C", "is-not-defined")
97
98
99# Components / Data
100class CalendarData(BaseElement):
101    tag = ns("C", "calendar-data")
102
103
104class Expand(BaseElement):
105    tag = ns("C", "expand")
106
107    def __init__(self, start, end=None):
108        super(Expand, self).__init__()
109        if start is not None:
110            self.attributes['start'] = _to_utc_date_string(start)
111        if end is not None:
112            self.attributes['end'] = _to_utc_date_string(end)
113
114
115class Comp(NamedBaseElement):
116    tag = ns("C", "comp")
117
118# Uhhm ... can't find any references to calendar-collection in rfc4791.txt
119# and newer versions of baikal gives 403 forbidden when this one is
120# encountered
121# class CalendarCollection(BaseElement):
122#     tag = ns("C", "calendar-collection")
123
124
125# Properties
126class CalendarUserAddressSet(BaseElement):
127    tag = ns("C", "calendar-user-address-set")
128
129class CalendarUserType(BaseElement):
130    tag = ns("C", "calendar-user-type")
131
132class CalendarHomeSet(BaseElement):
133    tag = ns("C", "calendar-home-set")
134
135# calendar resource type, see rfc4791, sec. 4.2
136class Calendar(BaseElement):
137    tag = ns("C", "calendar")
138
139class CalendarDescription(ValuedBaseElement):
140    tag = ns("C", "calendar-description")
141
142
143class CalendarTimeZone(ValuedBaseElement):
144    tag = ns("C", "calendar-timezone")
145
146
147class SupportedCalendarComponentSet(ValuedBaseElement):
148    tag = ns("C", "supported-calendar-component-set")
149
150
151class SupportedCalendarData(ValuedBaseElement):
152    tag = ns("C", "supported-calendar-data")
153
154
155class MaxResourceSize(ValuedBaseElement):
156    tag = ns("C", "max-resource-size")
157
158
159class MinDateTime(ValuedBaseElement):
160    tag = ns("C", "min-date-time")
161
162
163class MaxDateTime(ValuedBaseElement):
164    tag = ns("C", "max-date-time")
165
166
167class MaxInstances(ValuedBaseElement):
168    tag = ns("C", "max-instances")
169
170
171class MaxAttendeesPerInstance(ValuedBaseElement):
172    tag = ns("C", "max-attendees-per-instance")
173
174class Allprop(BaseElement):
175    tag = ns("C", "allprop")
176
177class ScheduleTag(BaseElement):
178    tag = ns("C", "schedule-tag")
179