1.1 --- a/imipweb/data.py Mon Apr 06 16:39:52 2015 +0200
1.2 +++ b/imipweb/data.py Mon Apr 06 22:57:23 2015 +0200
1.3 @@ -20,93 +20,275 @@
1.4 """
1.5
1.6 from datetime import datetime, timedelta
1.7 -from imiptools.dates import get_datetime, get_start_of_day
1.8 +from imiptools.dates import format_datetime, get_datetime, get_start_of_day, to_date
1.9 from imiptools.period import Period
1.10
1.11 +class PeriodError(Exception):
1.12 + pass
1.13 +
1.14 class EventPeriod(Period):
1.15
1.16 - "A simple period plus attribute details, compatible with RecurringPeriod."
1.17 + """
1.18 + A simple period plus attribute details, compatible with RecurringPeriod, and
1.19 + intended to represent information obtained from an iCalendar resource.
1.20 + """
1.21
1.22 - def __init__(self, start, end, start_attr=None, end_attr=None):
1.23 + def __init__(self, start, end, start_attr=None, end_attr=None, form_start=None, form_end=None):
1.24 Period.__init__(self, start, end)
1.25 self.start_attr = start_attr
1.26 self.end_attr = end_attr
1.27 + self.form_start = form_start
1.28 + self.form_end = form_end
1.29 +
1.30 + def as_tuple(self):
1.31 + return self.start, self.end, self.start_attr, self.end_attr, self.form_start, self.form_end
1.32 +
1.33 + def __repr__(self):
1.34 + return "EventPeriod(%r, %r, %r, %r, %r, %r)" % self.as_tuple()
1.35 +
1.36 + def get_start(self):
1.37 + return self.start
1.38 +
1.39 + def get_end(self):
1.40 + return self.end
1.41 +
1.42 + def as_event_period(self):
1.43 + return self
1.44 +
1.45 + def get_form_start(self):
1.46 + if not self.form_start:
1.47 + self.form_start = self.get_form_date(self.start, self.start_attr)
1.48 + return self.form_start
1.49 +
1.50 + def get_form_end(self):
1.51 + if not self.form_end:
1.52 + self.form_end = self.get_form_date(self.end, self.end_attr)
1.53 + return self.form_end
1.54 +
1.55 + def as_form_period(self):
1.56 + return FormPeriod(
1.57 + self.get_form_date(self.start, self.start_attr),
1.58 + self.get_form_date(self.end, self.end_attr),
1.59 + isinstance(self.end, datetime) or self.start != self.end - timedelta(1),
1.60 + isinstance(self.start, datetime) or isinstance(self.end, datetime)
1.61 + )
1.62 +
1.63 + def get_form_date(self, dt, attr=None):
1.64 + return FormDate(
1.65 + format_datetime(to_date(dt)),
1.66 + isinstance(dt, datetime) and str(dt.hour) or None,
1.67 + isinstance(dt, datetime) and str(dt.minute) or None,
1.68 + isinstance(dt, datetime) and str(dt.second) or None,
1.69 + attr and attr.get("TZID") or None,
1.70 + dt, attr
1.71 + )
1.72 +
1.73 +def event_period_from_recurrence_period(period):
1.74 + return EventPeriod(period.start, period.end, period.start_attr, period.end_attr)
1.75 +
1.76 +class FormPeriod:
1.77 +
1.78 + "A period whose information originates from a form."
1.79 +
1.80 + def __init__(self, start, end, end_enabled=True, times_enabled=True):
1.81 + self.start = start
1.82 + self.end = end
1.83 + self.end_enabled = end_enabled
1.84 + self.times_enabled = times_enabled
1.85
1.86 def as_tuple(self):
1.87 - return self.start, self.end, self.start_attr, self.end_attr
1.88 + return self.start, self.end, self.end_enabled, self.times_enabled
1.89
1.90 def __repr__(self):
1.91 - return "EventPeriod(%r, %r, %r, %r)" % self.as_tuple()
1.92 + return "FormPeriod(%r, %r, %r, %r)" % self.as_tuple()
1.93 +
1.94 + def _get_start(self):
1.95 + t = self.start.as_datetime_item(self.times_enabled)
1.96 + if t:
1.97 + return t
1.98 + else:
1.99 + return None
1.100 +
1.101 + def _get_end(self):
1.102 +
1.103 + # Handle specified end datetimes.
1.104 +
1.105 + if self.end_enabled:
1.106 + t = self.end.as_datetime_item(self.times_enabled)
1.107 + if t:
1.108 + dtend, dtend_attr = t
1.109 + else:
1.110 + return None
1.111 +
1.112 + # Otherwise, treat the end date as the start date. Datetimes are
1.113 + # handled by making the event occupy the rest of the day.
1.114 +
1.115 + else:
1.116 + t = self._get_start()
1.117 + if t:
1.118 + dtstart, dtstart_attr = t
1.119 + dtend = dtstart + timedelta(1)
1.120 + dtend_attr = dtstart_attr
1.121 +
1.122 + if isinstance(dtstart, datetime):
1.123 + dtend = get_start_of_day(dtend, dtend_attr["TZID"])
1.124 + else:
1.125 + return None
1.126 +
1.127 + return dtend, dtend_attr
1.128 +
1.129 + def get_start(self):
1.130 + t = self._get_start()
1.131 + if t:
1.132 + dtstart, dtstart_attr = t
1.133 + return dtstart
1.134 + else:
1.135 + return None
1.136 +
1.137 + def get_end(self):
1.138 + t = self._get_end()
1.139 + if t:
1.140 + dtend, dtend_attr = t
1.141 + return dtend
1.142 + else:
1.143 + return None
1.144 +
1.145 + def as_event_period(self, index=None):
1.146 + t = self._get_start()
1.147 + if t:
1.148 + dtstart, dtstart_attr = t
1.149 + else:
1.150 + raise PeriodError(*[index is not None and ("dtstart", index) or "dtstart"])
1.151 +
1.152 + t = self._get_end()
1.153 + if t:
1.154 + dtend, dtend_attr = t
1.155 + else:
1.156 + raise PeriodError(*[index is not None and ("dtend", index) or "dtend"])
1.157 +
1.158 + if dtstart > dtend:
1.159 + raise PeriodError(*[
1.160 + index is not None and ("dtstart", index) or "dtstart",
1.161 + index is not None and ("dtend", index) or "dtend"
1.162 + ])
1.163 +
1.164 + return EventPeriod(dtstart, dtend, dtstart_attr, dtend_attr, self.start, self.end)
1.165 +
1.166 + def get_form_start(self):
1.167 + return self.start
1.168 +
1.169 + def get_form_end(self):
1.170 + return self.end
1.171 +
1.172 + def as_form_period(self):
1.173 + return self
1.174
1.175 -def handle_date_control_values(values, with_time=True):
1.176 +class FormDate:
1.177 +
1.178 + "Date information originating from form information."
1.179 +
1.180 + def __init__(self, date=None, hour=None, minute=None, second=None, tzid=None, dt=None, attr=None):
1.181 + self.date = date
1.182 + self.hour = hour
1.183 + self.minute = minute
1.184 + self.second = second
1.185 + self.tzid = tzid
1.186 + self.dt = dt
1.187 + self.attr = attr
1.188 +
1.189 + def as_tuple(self):
1.190 + return self.date, self.hour, self.minute, self.second, self.tzid, self.dt, self.attr
1.191 +
1.192 + def __repr__(self):
1.193 + return "FormDate(%r, %r, %r, %r, %r, %r, %r)" % self.as_tuple()
1.194 +
1.195 + def get_component(self, value):
1.196 + return (value or "").rjust(2, "0")[:2]
1.197 +
1.198 + def get_hour(self):
1.199 + return self.get_component(self.hour)
1.200 +
1.201 + def get_minute(self):
1.202 + return self.get_component(self.minute)
1.203 +
1.204 + def get_second(self):
1.205 + return self.get_component(self.second)
1.206 +
1.207 + def get_date_string(self):
1.208 + return self.date or ""
1.209 +
1.210 + def get_datetime_string(self):
1.211 + if not self.date:
1.212 + return ""
1.213 +
1.214 + hour = self.hour; minute = self.minute; second = self.second
1.215 +
1.216 + if hour or minute or second:
1.217 + time = "T%s%s%s" % tuple(map(self.get_component, (hour, minute, second)))
1.218 + else:
1.219 + time = ""
1.220 +
1.221 + return "%s%s" % (self.date, time)
1.222 +
1.223 + def get_tzid(self):
1.224 + return self.tzid
1.225 +
1.226 + def as_datetime_item(self, with_time=True):
1.227 +
1.228 + """
1.229 + Return a (datetime, attr) tuple for the datetime information provided by
1.230 + this object, or None if the fields cannot be used to construct a
1.231 + datetime object.
1.232 + """
1.233 +
1.234 + # Return any original datetime details.
1.235 +
1.236 + if self.dt:
1.237 + return self.dt, self.attr
1.238 +
1.239 + # Otherwise, construct a datetime and attributes.
1.240 +
1.241 + if not self.date:
1.242 + return None
1.243 + elif with_time:
1.244 + attr = {"TZID" : self.get_tzid(), "VALUE" : "DATE-TIME"}
1.245 + dt = get_datetime(self.get_datetime_string(), attr)
1.246 + else:
1.247 + dt = None
1.248 +
1.249 + # Interpret incomplete datetimes as dates.
1.250 +
1.251 + if not dt:
1.252 + attr = {"VALUE" : "DATE"}
1.253 + dt = get_datetime(self.get_date_string(), attr)
1.254 +
1.255 + if dt:
1.256 + return dt, attr
1.257 +
1.258 + return None
1.259 +
1.260 +def end_date_to_calendar(dt):
1.261
1.262 """
1.263 - Handle date control information for the given 'values', returning a
1.264 - (datetime, attr) tuple, or None if the fields cannot be used to
1.265 - construct a datetime object.
1.266 - """
1.267 -
1.268 - if not values or not values["date"]:
1.269 - return None
1.270 - elif with_time:
1.271 - value = "%s%s" % (values["date"], values["time"])
1.272 - attr = {"TZID" : values["tzid"], "VALUE" : "DATE-TIME"}
1.273 - dt = get_datetime(value, attr)
1.274 - else:
1.275 - attr = {"VALUE" : "DATE"}
1.276 - dt = get_datetime(values["date"])
1.277 -
1.278 - if dt:
1.279 - return dt, attr
1.280 -
1.281 - return None
1.282 -
1.283 -def handle_period_controls(start_values, end_values, dtend_enabled, dttimes_enabled, index=None):
1.284 -
1.285 - """
1.286 - Handle datetime controls for a particular period, described by the given
1.287 - 'start_values' and 'end_values', with 'dtend_enabled' and
1.288 - 'dttimes_enabled' affecting the usage of the provided values.
1.289 -
1.290 - If 'index' is specified, incorporate it into any error indicator.
1.291 + Change end dates to refer to the actual dates, not the iCalendar "next day"
1.292 + dates.
1.293 """
1.294
1.295 - t = handle_date_control_values(start_values, dttimes_enabled)
1.296 - if t:
1.297 - dtstart, dtstart_attr = t
1.298 + if not isinstance(dt, datetime):
1.299 + return dt + timedelta(1)
1.300 else:
1.301 - return None, [index is not None and ("dtstart", index) or "dtstart"]
1.302 + return dt
1.303
1.304 - # Handle specified end datetimes.
1.305 -
1.306 - if dtend_enabled:
1.307 - t = handle_date_control_values(end_values, dttimes_enabled)
1.308 - if t:
1.309 - dtend, dtend_attr = t
1.310 -
1.311 - # Convert end dates to iCalendar "next day" dates.
1.312 +def end_date_from_calendar(dt):
1.313
1.314 - if not isinstance(dtend, datetime):
1.315 - dtend += timedelta(1)
1.316 - else:
1.317 - return None, [index is not None and ("dtend", index) or "dtend"]
1.318 + """
1.319 + Change end dates to refer to the actual dates, not the iCalendar "next day"
1.320 + dates.
1.321 + """
1.322
1.323 - # Otherwise, treat the end date as the start date. Datetimes are
1.324 - # handled by making the event occupy the rest of the day.
1.325 -
1.326 + if not isinstance(dt, datetime):
1.327 + return dt - timedelta(1)
1.328 else:
1.329 - dtend = dtstart + timedelta(1)
1.330 - dtend_attr = dtstart_attr
1.331 -
1.332 - if isinstance(dtstart, datetime):
1.333 - dtend = get_start_of_day(dtend, attr["TZID"])
1.334 -
1.335 - if dtstart > dtend:
1.336 - return None, [
1.337 - index is not None and ("dtstart", index) or "dtstart",
1.338 - index is not None and ("dtend", index) or "dtend"
1.339 - ]
1.340 -
1.341 - return EventPeriod(dtstart, dtend, dtstart_attr, dtend_attr), None
1.342 + return dt
1.343
1.344 # vim: tabstop=4 expandtab shiftwidth=4