imip-agent

imipweb/data.py

700:aab77b1ce459
2015-09-06 Paul Boddie Fixed obsolete API usage for start and end items.
     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.dates import end_date_from_calendar, end_date_to_calendar, \    24                             format_datetime, get_datetime, get_end_of_day, \    25                             to_date    26 from imiptools.period import RecurringPeriod    27     28 class PeriodError(Exception):    29     pass    30     31 class EventPeriod(RecurringPeriod):    32     33     """    34     A simple period plus attribute details, compatible with RecurringPeriod, and    35     intended to represent information obtained from an iCalendar resource.    36     """    37     38     def __init__(self, start, end, tzid=None, origin=None, start_attr=None, end_attr=None, form_start=None, form_end=None):    39     40         """    41         Initialise a period with the given 'start' and 'end' datetimes, together    42         with optional 'start_attr' and 'end_attr' metadata, 'form_start' and    43         'form_end' values provided as textual input, and with an optional    44         'origin' indicating the kind of period this object describes.    45         """    46     47         RecurringPeriod.__init__(self, start, end, tzid, origin, start_attr, end_attr)    48         self.form_start = form_start    49         self.form_end = form_end    50     51     def as_tuple(self):    52         return self.start, self.end, self.tzid, self.origin, self.start_attr, self.end_attr, self.form_start, self.form_end    53     54     def __repr__(self):    55         return "EventPeriod(%r)" % (self.as_tuple(),)    56     57     def as_event_period(self):    58         return self    59     60     def get_start_item(self):    61         return self.get_start(), self.get_start_attr()    62     63     def get_end_item(self):    64         return self.get_end(), self.get_end_attr()    65     66     # Form data compatibility methods.    67     68     def get_form_start(self):    69         if not self.form_start:    70             self.form_start = self.get_form_date(self.get_start(), self.start_attr)    71         return self.form_start    72     73     def get_form_end(self):    74         if not self.form_end:    75             self.form_end = self.get_form_date(end_date_from_calendar(self.get_end()), self.end_attr)    76         return self.form_end    77     78     def as_form_period(self):    79         return FormPeriod(    80             self.get_form_start(),    81             self.get_form_end(),    82             isinstance(self.end, datetime) or self.get_start() != self.get_end() - timedelta(1),    83             isinstance(self.start, datetime) or isinstance(self.end, datetime),    84             self.tzid,    85             self.origin    86             )    87     88     def get_form_date(self, dt, attr=None):    89         return FormDate(    90             format_datetime(to_date(dt)),    91             isinstance(dt, datetime) and str(dt.hour) or None,    92             isinstance(dt, datetime) and str(dt.minute) or None,    93             isinstance(dt, datetime) and str(dt.second) or None,    94             attr and attr.get("TZID") or None,    95             dt, attr    96             )    97     98 class FormPeriod(RecurringPeriod):    99    100     "A period whose information originates from a form."   101    102     def __init__(self, start, end, end_enabled=True, times_enabled=True, tzid=None, origin=None):   103         self.start = start   104         self.end = end   105         self.end_enabled = end_enabled   106         self.times_enabled = times_enabled   107         self.tzid = tzid   108         self.origin = origin   109    110     def as_tuple(self):   111         return self.start, self.end, self.end_enabled, self.times_enabled, self.tzid, self.origin   112    113     def __repr__(self):   114         return "FormPeriod(%r)" % (self.as_tuple(),)   115    116     def as_event_period(self, index=None):   117    118         """   119         Return a converted version of this object as an event period suitable   120         for iCalendar usage. If 'index' is indicated, include it in any error   121         raised in the conversion process.   122         """   123    124         dtstart, dtstart_attr = self.get_start_item()   125         if not dtstart:   126             raise PeriodError(*[index is not None and ("dtstart", index) or "dtstart"])   127    128         dtend, dtend_attr = self.get_end_item()   129         if not dtend:   130             raise PeriodError(*[index is not None and ("dtend", index) or "dtend"])   131    132         if dtstart > dtend:   133             raise PeriodError(*[   134                 index is not None and ("dtstart", index) or "dtstart",   135                 index is not None and ("dtend", index) or "dtend"   136                 ])   137    138         return EventPeriod(dtstart, end_date_to_calendar(dtend), self.tzid, self.origin, dtstart_attr, dtend_attr, self.start, self.end)   139    140     # Period data methods.   141    142     def get_start(self):   143         return self.start.as_datetime(self.times_enabled)   144    145     def get_end(self):   146    147         # Handle specified end datetimes.   148    149         if self.end_enabled:   150             dtend = self.end.as_datetime(self.times_enabled)   151             if not dtend:   152                 return None   153    154         # Otherwise, treat the end date as the start date. Datetimes are   155         # handled by making the event occupy the rest of the day.   156    157         else:   158             dtstart, dtstart_attr = self.get_start_item()   159             if dtstart:   160                 if isinstance(dtstart, datetime):   161                     dtend = get_end_of_day(dtstart, dtstart_attr["TZID"])   162                 else:   163                     dtend = dtstart   164             else:   165                 return None   166    167         return dtend   168    169     def get_start_attr(self):   170         return self.start.get_attributes(self.times_enabled)   171    172     def get_end_attr(self):   173         return self.end.get_attributes(self.times_enabled)   174    175     # Form data methods.   176    177     def get_form_start(self):   178         return self.start   179    180     def get_form_end(self):   181         return self.end   182    183     def as_form_period(self):   184         return self   185    186 class FormDate:   187    188     "Date information originating from form information."   189    190     def __init__(self, date=None, hour=None, minute=None, second=None, tzid=None, dt=None, attr=None):   191         self.date = date   192         self.hour = hour   193         self.minute = minute   194         self.second = second   195         self.tzid = tzid   196         self.dt = dt   197         self.attr = attr   198    199     def as_tuple(self):   200         return self.date, self.hour, self.minute, self.second, self.tzid, self.dt, self.attr   201    202     def __repr__(self):   203         return "FormDate(%r)" % (self.as_tuple(),)   204    205     def get_component(self, value):   206         return (value or "").rjust(2, "0")[:2]   207    208     def get_hour(self):   209         return self.get_component(self.hour)   210    211     def get_minute(self):   212         return self.get_component(self.minute)   213    214     def get_second(self):   215         return self.get_component(self.second)   216    217     def get_date_string(self):   218         return self.date or ""   219    220     def get_datetime_string(self):   221         if not self.date:   222             return ""   223    224         hour = self.hour; minute = self.minute; second = self.second   225    226         if hour or minute or second:   227             time = "T%s%s%s" % tuple(map(self.get_component, (hour, minute, second)))   228         else:   229             time = ""   230                231         return "%s%s" % (self.date, time)   232    233     def get_tzid(self):   234         return self.tzid   235    236     def as_datetime(self, with_time=True):   237    238         "Return a datetime for this object."   239    240         # Return any original datetime details.   241    242         if self.dt:   243             return self.dt   244    245         # Otherwise, construct a datetime.   246    247         s, attr = self.as_datetime_item(with_time)   248         if s:   249             return get_datetime(s, attr)   250         else:   251             return None   252    253     def as_datetime_item(self, with_time=True):   254    255         """   256         Return a (datetime string, attr) tuple for the datetime information   257         provided by this object, where both tuple elements will be None if no   258         suitable date or datetime information exists.   259         """   260    261         s = None   262         if with_time:   263             s = self.get_datetime_string()   264             attr = self.get_attributes(True)   265         if not s:   266             s = self.get_date_string()   267             attr = self.get_attributes(False)   268         if not s:   269             return None, None   270         return s, attr   271    272     def get_attributes(self, with_time=True):   273    274         "Return attributes for the date or datetime represented by this object."   275    276         if with_time:   277             return {"TZID" : self.get_tzid(), "VALUE" : "DATE-TIME"}   278         else:   279             return {"VALUE" : "DATE"}   280    281 def event_period_from_period(period):   282    283     """   284     Convert a 'period' to one suitable for use in an iCalendar representation.   285     In an "event period" representation, the end day of any date-level event is   286     encoded as the "day after" the last day actually involved in the event.   287     """   288    289     if isinstance(period, EventPeriod):   290         return period   291     elif isinstance(period, FormPeriod):   292         return period.as_event_period()   293     else:   294         dtstart, dtstart_attr = period.get_start_item()   295         dtend, dtend_attr = period.get_end_item()   296         if not isinstance(period, RecurringPeriod):   297             dtend = end_date_to_calendar(dtend)   298         return EventPeriod(dtstart, dtend, period.tzid, period.origin, dtstart_attr, dtend_attr)   299    300 def form_period_from_period(period):   301    302     """   303     Convert a 'period' into a representation usable in a user-editable form.   304     In a "form period" representation, the end day of any date-level event is   305     presented in a "natural" form, not the iCalendar "day after" form.   306     """   307    308     if isinstance(period, EventPeriod):   309         return period.as_form_period()   310     elif isinstance(period, FormPeriod):   311         return period   312     else:   313         return event_period_from_period(period).as_form_period()   314    315 # vim: tabstop=4 expandtab shiftwidth=4