imip-agent

imipweb/data.py

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