File: data.py

package info (click to toggle)
imip-agent 0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 2,056 kB
  • sloc: python: 9,888; sh: 4,480; sql: 144; makefile: 8
file content (326 lines) | stat: -rw-r--r-- 10,498 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
#!/usr/bin/env python

"""
Web interface data abstractions.

Copyright (C) 2014, 2015 Paul Boddie <paul@boddie.org.uk>

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 3 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
details.

You should have received a copy of the GNU General Public License along with
this program.  If not, see <http://www.gnu.org/licenses/>.
"""

from datetime import datetime, timedelta
from imiptools.dates import end_date_from_calendar, end_date_to_calendar, \
                            format_datetime, get_datetime, get_end_of_day, \
                            to_date
from imiptools.period import RecurringPeriod

class PeriodError(Exception):
    pass

class EventPeriod(RecurringPeriod):

    """
    A simple period plus attribute details, compatible with RecurringPeriod, and
    intended to represent information obtained from an iCalendar resource.
    """

    def __init__(self, start, end, tzid=None, origin=None, start_attr=None, end_attr=None, form_start=None, form_end=None, replaced=False):

        """
        Initialise a period with the given 'start' and 'end' datetimes, together
        with optional 'start_attr' and 'end_attr' metadata, 'form_start' and
        'form_end' values provided as textual input, and with an optional
        'origin' indicating the kind of period this object describes.
        """

        RecurringPeriod.__init__(self, start, end, tzid, origin, start_attr, end_attr)
        self.form_start = form_start
        self.form_end = form_end
        self.replaced = replaced

    def as_tuple(self):
        return self.start, self.end, self.tzid, self.origin, self.start_attr, self.end_attr, self.form_start, self.form_end, self.replaced

    def __repr__(self):
        return "EventPeriod%r" % (self.as_tuple(),)

    def as_event_period(self):
        return self

    def get_start_item(self):
        return self.get_start(), self.get_start_attr()

    def get_end_item(self):
        return self.get_end(), self.get_end_attr()

    # Form data compatibility methods.

    def get_form_start(self):
        if not self.form_start:
            self.form_start = self.get_form_date(self.get_start(), self.start_attr)
        return self.form_start

    def get_form_end(self):
        if not self.form_end:
            self.form_end = self.get_form_date(end_date_from_calendar(self.get_end()), self.end_attr)
        return self.form_end

    def as_form_period(self):
        return FormPeriod(
            self.get_form_start(),
            self.get_form_end(),
            isinstance(self.end, datetime) or self.get_start() != self.get_end() - timedelta(1),
            isinstance(self.start, datetime) or isinstance(self.end, datetime),
            self.tzid,
            self.origin,
            self.replaced
            )

    def get_form_date(self, dt, attr=None):
        return FormDate(
            format_datetime(to_date(dt)),
            isinstance(dt, datetime) and str(dt.hour) or None,
            isinstance(dt, datetime) and str(dt.minute) or None,
            isinstance(dt, datetime) and str(dt.second) or None,
            attr and attr.get("TZID") or None,
            dt, attr
            )

class FormPeriod(RecurringPeriod):

    "A period whose information originates from a form."

    def __init__(self, start, end, end_enabled=True, times_enabled=True, tzid=None, origin=None, replaced=False):
        self.start = start
        self.end = end
        self.end_enabled = end_enabled
        self.times_enabled = times_enabled
        self.tzid = tzid
        self.origin = origin
        self.replaced = replaced

    def as_tuple(self):
        return self.start, self.end, self.end_enabled, self.times_enabled, self.tzid, self.origin, self.replaced

    def __repr__(self):
        return "FormPeriod%r" % (self.as_tuple(),)

    def as_event_period(self, index=None):

        """
        Return a converted version of this object as an event period suitable
        for iCalendar usage. If 'index' is indicated, include it in any error
        raised in the conversion process.
        """

        dtstart, dtstart_attr = self.get_start_item()
        if not dtstart:
            raise PeriodError(*[index is not None and ("dtstart", index) or "dtstart"])

        dtend, dtend_attr = self.get_end_item()
        if not dtend:
            raise PeriodError(*[index is not None and ("dtend", index) or "dtend"])

        if dtstart > dtend:
            raise PeriodError(*[
                index is not None and ("dtstart", index) or "dtstart",
                index is not None and ("dtend", index) or "dtend"
                ])

        return EventPeriod(dtstart, end_date_to_calendar(dtend), self.tzid, self.origin, dtstart_attr, dtend_attr, self.start, self.end, self.replaced)

    # Period data methods.

    def get_start(self):
        return self.start.as_datetime(self.times_enabled)

    def get_end(self):

        # Handle specified end datetimes.

        if self.end_enabled:
            dtend = self.end.as_datetime(self.times_enabled)
            if not dtend:
                return None

        # Handle same day times.

        elif self.times_enabled:
            formdate = FormDate(self.start.date, self.end.hour, self.end.minute, self.end.second, self.end.tzid)
            dtend = formdate.as_datetime(self.times_enabled)
            if not dtend:
                return None

        # Otherwise, treat the end date as the start date. Datetimes are
        # handled by making the event occupy the rest of the day.

        else:
            dtstart, dtstart_attr = self.get_start_item()
            if dtstart:
                if isinstance(dtstart, datetime):
                    dtend = get_end_of_day(dtstart, dtstart_attr["TZID"])
                else:
                    dtend = dtstart
            else:
                return None

        return dtend

    def get_start_attr(self):
        return self.start.get_attributes(self.times_enabled)

    def get_end_attr(self):
        return self.end.get_attributes(self.times_enabled)

    # Form data methods.

    def get_form_start(self):
        return self.start

    def get_form_end(self):
        return self.end

    def as_form_period(self):
        return self

class FormDate:

    "Date information originating from form information."

    def __init__(self, date=None, hour=None, minute=None, second=None, tzid=None, dt=None, attr=None):
        self.date = date
        self.hour = hour
        self.minute = minute
        self.second = second
        self.tzid = tzid
        self.dt = dt
        self.attr = attr

    def as_tuple(self):
        return self.date, self.hour, self.minute, self.second, self.tzid, self.dt, self.attr

    def __repr__(self):
        return "FormDate%r" % (self.as_tuple(),)

    def get_component(self, value):
        return (value or "").rjust(2, "0")[:2]

    def get_hour(self):
        return self.get_component(self.hour)

    def get_minute(self):
        return self.get_component(self.minute)

    def get_second(self):
        return self.get_component(self.second)

    def get_date_string(self):
        return self.date or ""

    def get_datetime_string(self):
        if not self.date:
            return ""

        hour = self.hour; minute = self.minute; second = self.second

        if hour or minute or second:
            time = "T%s%s%s" % tuple(map(self.get_component, (hour, minute, second)))
        else:
            time = ""
            
        return "%s%s" % (self.date, time)

    def get_tzid(self):
        return self.tzid

    def as_datetime(self, with_time=True):

        "Return a datetime for this object."

        # Return any original datetime details.

        if self.dt:
            return self.dt

        # Otherwise, construct a datetime.

        s, attr = self.as_datetime_item(with_time)
        if s:
            return get_datetime(s, attr)
        else:
            return None

    def as_datetime_item(self, with_time=True):

        """
        Return a (datetime string, attr) tuple for the datetime information
        provided by this object, where both tuple elements will be None if no
        suitable date or datetime information exists.
        """

        s = None
        if with_time:
            s = self.get_datetime_string()
            attr = self.get_attributes(True)
        if not s:
            s = self.get_date_string()
            attr = self.get_attributes(False)
        if not s:
            return None, None
        return s, attr

    def get_attributes(self, with_time=True):

        "Return attributes for the date or datetime represented by this object."

        if with_time:
            return {"TZID" : self.get_tzid(), "VALUE" : "DATE-TIME"}
        else:
            return {"VALUE" : "DATE"}

def event_period_from_period(period):

    """
    Convert a 'period' to one suitable for use in an iCalendar representation.
    In an "event period" representation, the end day of any date-level event is
    encoded as the "day after" the last day actually involved in the event.
    """

    if isinstance(period, EventPeriod):
        return period
    elif isinstance(period, FormPeriod):
        return period.as_event_period()
    else:
        dtstart, dtstart_attr = period.get_start_item()
        dtend, dtend_attr = period.get_end_item()
        if not isinstance(period, RecurringPeriod):
            dtend = end_date_to_calendar(dtend)
        return EventPeriod(dtstart, dtend, period.tzid, period.origin, dtstart_attr, dtend_attr)

def form_period_from_period(period):

    """
    Convert a 'period' into a representation usable in a user-editable form.
    In a "form period" representation, the end day of any date-level event is
    presented in a "natural" form, not the iCalendar "day after" form.
    """

    if isinstance(period, EventPeriod):
        return period.as_form_period()
    elif isinstance(period, FormPeriod):
        return period
    else:
        return event_period_from_period(period).as_form_period()

# vim: tabstop=4 expandtab shiftwidth=4