imip-agent

Annotated imipweb/data.py

508:b317a8937754
2015-04-07 Paul Boddie Store the final state of a cancelled event.
paul@497 1
#!/usr/bin/env python
paul@497 2
paul@497 3
"""
paul@497 4
Web interface data abstractions.
paul@497 5
paul@497 6
Copyright (C) 2014, 2015 Paul Boddie <paul@boddie.org.uk>
paul@497 7
paul@497 8
This program is free software; you can redistribute it and/or modify it under
paul@497 9
the terms of the GNU General Public License as published by the Free Software
paul@497 10
Foundation; either version 3 of the License, or (at your option) any later
paul@497 11
version.
paul@497 12
paul@497 13
This program is distributed in the hope that it will be useful, but WITHOUT
paul@497 14
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@497 15
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@497 16
details.
paul@497 17
paul@497 18
You should have received a copy of the GNU General Public License along with
paul@497 19
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@497 20
"""
paul@497 21
paul@497 22
from datetime import datetime, timedelta
paul@498 23
from imiptools.dates import format_datetime, get_datetime, get_start_of_day, to_date
paul@497 24
from imiptools.period import Period
paul@497 25
paul@498 26
class PeriodError(Exception):
paul@498 27
    pass
paul@498 28
paul@497 29
class EventPeriod(Period):
paul@497 30
paul@498 31
    """
paul@498 32
    A simple period plus attribute details, compatible with RecurringPeriod, and
paul@498 33
    intended to represent information obtained from an iCalendar resource.
paul@498 34
    """
paul@497 35
paul@499 36
    def __init__(self, start, end, start_attr=None, end_attr=None, form_start=None, form_end=None, origin=None):
paul@497 37
        Period.__init__(self, start, end)
paul@497 38
        self.start_attr = start_attr
paul@497 39
        self.end_attr = end_attr
paul@498 40
        self.form_start = form_start
paul@498 41
        self.form_end = form_end
paul@499 42
        self.origin = origin
paul@498 43
paul@498 44
    def as_tuple(self):
paul@499 45
        return self.start, self.end, self.start_attr, self.end_attr, self.form_start, self.form_end, self.origin
paul@498 46
paul@498 47
    def __repr__(self):
paul@499 48
        return "EventPeriod(%r, %r, %r, %r, %r, %r, %r)" % self.as_tuple()
paul@499 49
paul@499 50
    def as_event_period(self):
paul@499 51
        return self
paul@499 52
paul@499 53
    # Period data methods.
paul@498 54
paul@498 55
    def get_start(self):
paul@498 56
        return self.start
paul@498 57
paul@498 58
    def get_end(self):
paul@499 59
        return end_date_from_calendar(self.end)
paul@498 60
paul@499 61
    # Form data compatibility methods.
paul@498 62
paul@498 63
    def get_form_start(self):
paul@498 64
        if not self.form_start:
paul@499 65
            self.form_start = self.get_form_date(self.get_start(), self.start_attr)
paul@498 66
        return self.form_start
paul@498 67
paul@498 68
    def get_form_end(self):
paul@498 69
        if not self.form_end:
paul@499 70
            self.form_end = self.get_form_date(self.get_end(), self.end_attr)
paul@498 71
        return self.form_end
paul@498 72
paul@498 73
    def as_form_period(self):
paul@498 74
        return FormPeriod(
paul@499 75
            self.get_form_start(),
paul@499 76
            self.get_form_end(),
paul@499 77
            isinstance(self.end, datetime) or self.get_start() != self.get_end(),
paul@498 78
            isinstance(self.start, datetime) or isinstance(self.end, datetime)
paul@498 79
            )
paul@498 80
paul@498 81
    def get_form_date(self, dt, attr=None):
paul@498 82
        return FormDate(
paul@498 83
            format_datetime(to_date(dt)),
paul@498 84
            isinstance(dt, datetime) and str(dt.hour) or None,
paul@498 85
            isinstance(dt, datetime) and str(dt.minute) or None,
paul@498 86
            isinstance(dt, datetime) and str(dt.second) or None,
paul@498 87
            attr and attr.get("TZID") or None,
paul@498 88
            dt, attr
paul@498 89
            )
paul@498 90
paul@498 91
class FormPeriod:
paul@498 92
paul@498 93
    "A period whose information originates from a form."
paul@498 94
paul@499 95
    def __init__(self, start, end, end_enabled=True, times_enabled=True, origin=None):
paul@498 96
        self.start = start
paul@498 97
        self.end = end
paul@498 98
        self.end_enabled = end_enabled
paul@498 99
        self.times_enabled = times_enabled
paul@499 100
        self.origin = origin
paul@497 101
paul@497 102
    def as_tuple(self):
paul@499 103
        return self.start, self.end, self.end_enabled, self.times_enabled, self.origin
paul@497 104
paul@497 105
    def __repr__(self):
paul@499 106
        return "FormPeriod(%r, %r, %r, %r, %r)" % self.as_tuple()
paul@498 107
paul@498 108
    def _get_start(self):
paul@498 109
        t = self.start.as_datetime_item(self.times_enabled)
paul@498 110
        if t:
paul@498 111
            return t
paul@498 112
        else:
paul@498 113
            return None
paul@498 114
paul@499 115
    def _get_end(self, adjust=False):
paul@498 116
paul@498 117
        # Handle specified end datetimes.
paul@498 118
paul@498 119
        if self.end_enabled:
paul@498 120
            t = self.end.as_datetime_item(self.times_enabled)
paul@498 121
            if t:
paul@498 122
                dtend, dtend_attr = t
paul@499 123
                dtend = adjust and end_date_to_calendar(dtend) or dtend
paul@498 124
            else:
paul@498 125
                return None
paul@498 126
paul@498 127
        # Otherwise, treat the end date as the start date. Datetimes are
paul@498 128
        # handled by making the event occupy the rest of the day.
paul@498 129
paul@498 130
        else:
paul@498 131
            t = self._get_start()
paul@498 132
            if t:
paul@498 133
                dtstart, dtstart_attr = t
paul@498 134
                dtend = dtstart + timedelta(1)
paul@498 135
                dtend_attr = dtstart_attr
paul@498 136
paul@498 137
                if isinstance(dtstart, datetime):
paul@498 138
                    dtend = get_start_of_day(dtend, dtend_attr["TZID"])
paul@498 139
            else:
paul@498 140
                return None
paul@498 141
paul@498 142
        return dtend, dtend_attr
paul@498 143
paul@499 144
    def as_event_period(self, index=None):
paul@499 145
        t = self._get_start()
paul@499 146
        if t:
paul@499 147
            dtstart, dtstart_attr = t
paul@499 148
        else:
paul@499 149
            raise PeriodError(*[index is not None and ("dtstart", index) or "dtstart"])
paul@499 150
paul@499 151
        t = self._get_end(adjust=True)
paul@499 152
        if t:
paul@499 153
            dtend, dtend_attr = t
paul@499 154
        else:
paul@499 155
            raise PeriodError(*[index is not None and ("dtend", index) or "dtend"])
paul@499 156
paul@499 157
        if dtstart > dtend:
paul@499 158
            raise PeriodError(*[
paul@499 159
                index is not None and ("dtstart", index) or "dtstart",
paul@499 160
                index is not None and ("dtend", index) or "dtend"
paul@499 161
                ])
paul@499 162
paul@499 163
        return EventPeriod(dtstart, dtend, dtstart_attr, dtend_attr, self.start, self.end)
paul@499 164
paul@499 165
    # Period data methods.
paul@499 166
paul@498 167
    def get_start(self):
paul@498 168
        t = self._get_start()
paul@498 169
        if t:
paul@498 170
            dtstart, dtstart_attr = t
paul@498 171
            return dtstart
paul@498 172
        else:
paul@498 173
            return None
paul@498 174
paul@498 175
    def get_end(self):
paul@498 176
        t = self._get_end()
paul@498 177
        if t:
paul@498 178
            dtend, dtend_attr = t
paul@498 179
            return dtend
paul@498 180
        else:
paul@498 181
            return None
paul@498 182
paul@499 183
    # Form data methods.
paul@498 184
paul@498 185
    def get_form_start(self):
paul@498 186
        return self.start
paul@498 187
paul@498 188
    def get_form_end(self):
paul@498 189
        return self.end
paul@498 190
paul@498 191
    def as_form_period(self):
paul@498 192
        return self
paul@497 193
paul@498 194
class FormDate:
paul@498 195
paul@498 196
    "Date information originating from form information."
paul@498 197
paul@498 198
    def __init__(self, date=None, hour=None, minute=None, second=None, tzid=None, dt=None, attr=None):
paul@498 199
        self.date = date
paul@498 200
        self.hour = hour
paul@498 201
        self.minute = minute
paul@498 202
        self.second = second
paul@498 203
        self.tzid = tzid
paul@498 204
        self.dt = dt
paul@498 205
        self.attr = attr
paul@498 206
paul@498 207
    def as_tuple(self):
paul@498 208
        return self.date, self.hour, self.minute, self.second, self.tzid, self.dt, self.attr
paul@498 209
paul@498 210
    def __repr__(self):
paul@498 211
        return "FormDate(%r, %r, %r, %r, %r, %r, %r)" % self.as_tuple()
paul@498 212
paul@498 213
    def get_component(self, value):
paul@498 214
        return (value or "").rjust(2, "0")[:2]
paul@498 215
paul@498 216
    def get_hour(self):
paul@498 217
        return self.get_component(self.hour)
paul@498 218
paul@498 219
    def get_minute(self):
paul@498 220
        return self.get_component(self.minute)
paul@498 221
paul@498 222
    def get_second(self):
paul@498 223
        return self.get_component(self.second)
paul@498 224
paul@498 225
    def get_date_string(self):
paul@498 226
        return self.date or ""
paul@498 227
paul@498 228
    def get_datetime_string(self):
paul@498 229
        if not self.date:
paul@498 230
            return ""
paul@498 231
paul@498 232
        hour = self.hour; minute = self.minute; second = self.second
paul@498 233
paul@498 234
        if hour or minute or second:
paul@498 235
            time = "T%s%s%s" % tuple(map(self.get_component, (hour, minute, second)))
paul@498 236
        else:
paul@498 237
            time = ""
paul@498 238
            
paul@498 239
        return "%s%s" % (self.date, time)
paul@498 240
paul@498 241
    def get_tzid(self):
paul@498 242
        return self.tzid
paul@498 243
paul@498 244
    def as_datetime_item(self, with_time=True):
paul@498 245
paul@498 246
        """
paul@498 247
        Return a (datetime, attr) tuple for the datetime information provided by
paul@498 248
        this object, or None if the fields cannot be used to construct a
paul@498 249
        datetime object.
paul@498 250
        """
paul@498 251
paul@498 252
        # Return any original datetime details.
paul@498 253
paul@498 254
        if self.dt:
paul@498 255
            return self.dt, self.attr
paul@498 256
paul@498 257
        # Otherwise, construct a datetime and attributes.
paul@498 258
paul@498 259
        if not self.date:
paul@498 260
            return None
paul@498 261
        elif with_time:
paul@498 262
            attr = {"TZID" : self.get_tzid(), "VALUE" : "DATE-TIME"}
paul@498 263
            dt = get_datetime(self.get_datetime_string(), attr)
paul@498 264
        else:
paul@498 265
            dt = None
paul@498 266
paul@498 267
        # Interpret incomplete datetimes as dates.
paul@498 268
paul@498 269
        if not dt:
paul@498 270
            attr = {"VALUE" : "DATE"}
paul@498 271
            dt = get_datetime(self.get_date_string(), attr)
paul@498 272
paul@498 273
        if dt:
paul@498 274
            return dt, attr
paul@498 275
paul@498 276
        return None
paul@498 277
paul@498 278
def end_date_to_calendar(dt):
paul@497 279
paul@497 280
    """
paul@498 281
    Change end dates to refer to the actual dates, not the iCalendar "next day"
paul@498 282
    dates.
paul@497 283
    """
paul@497 284
paul@498 285
    if not isinstance(dt, datetime):
paul@498 286
        return dt + timedelta(1)
paul@497 287
    else:
paul@498 288
        return dt
paul@497 289
paul@498 290
def end_date_from_calendar(dt):
paul@497 291
paul@498 292
    """
paul@498 293
    Change end dates to refer to the actual dates, not the iCalendar "next day"
paul@498 294
    dates.
paul@498 295
    """
paul@497 296
paul@498 297
    if not isinstance(dt, datetime):
paul@498 298
        return dt - timedelta(1)
paul@497 299
    else:
paul@498 300
        return dt
paul@497 301
paul@499 302
def event_period_from_period(period):
paul@499 303
    if isinstance(period, EventPeriod):
paul@499 304
        return period
paul@499 305
    elif isinstance(period, FormPeriod):
paul@499 306
        return period.as_event_period()
paul@499 307
    else:
paul@499 308
        return EventPeriod(period.start, period.end, period.start_attr, period.end_attr, origin=period.origin)
paul@499 309
paul@499 310
def form_period_from_period(period):
paul@499 311
    if isinstance(period, EventPeriod):
paul@499 312
        return period.as_form_period()
paul@499 313
    elif isinstance(period, FormPeriod):
paul@499 314
        return period
paul@499 315
    else:
paul@499 316
        return event_period_from_period(period).as_form_period()
paul@499 317
paul@497 318
# vim: tabstop=4 expandtab shiftwidth=4