1.1 --- a/imiptools/dates.py Sun May 17 19:04:01 2015 +0200
1.2 +++ b/imiptools/dates.py Sun May 17 19:25:22 2015 +0200
1.3 @@ -58,62 +58,7 @@
1.4 match_datetime_icalendar = re.compile(datetime_icalendar_regexp_str, re.UNICODE).match
1.5 match_duration_icalendar = re.compile(duration_icalendar_regexp_str, re.UNICODE).match
1.6
1.7 -def to_utc_datetime(dt, date_tzid=None):
1.8 -
1.9 - """
1.10 - Return a datetime corresponding to 'dt' in the UTC time zone. If 'date_tzid'
1.11 - is specified, dates are converted to datetimes using the time zone
1.12 - information; otherwise, dates remain unconverted.
1.13 - """
1.14 -
1.15 - if not dt:
1.16 - return None
1.17 - elif isinstance(dt, datetime):
1.18 - return to_timezone(dt, "UTC")
1.19 - elif date_tzid:
1.20 - return to_timezone(to_datetime(dt, date_tzid), "UTC")
1.21 - else:
1.22 - return dt
1.23 -
1.24 -def get_default_timezone():
1.25 -
1.26 - "Return the system time regime."
1.27 -
1.28 - filename = "/etc/timezone"
1.29 -
1.30 - if exists(filename):
1.31 - f = open(filename)
1.32 - try:
1.33 - return f.read().strip()
1.34 - finally:
1.35 - f.close()
1.36 - else:
1.37 - return None
1.38 -
1.39 -def to_timezone(dt, name):
1.40 -
1.41 - """
1.42 - Return a datetime corresponding to 'dt' in the time regime having the given
1.43 - 'name'.
1.44 - """
1.45 -
1.46 - try:
1.47 - tz = name and timezone(name) or None
1.48 - except UnknownTimeZoneError:
1.49 - tz = None
1.50 - return to_tz(dt, tz)
1.51 -
1.52 -def to_tz(dt, tz):
1.53 -
1.54 - "Return a datetime corresponding to 'dt' employing the pytz.timezone 'tz'."
1.55 -
1.56 - if tz is not None and isinstance(dt, datetime):
1.57 - if not dt.tzinfo:
1.58 - return tz.localize(dt)
1.59 - else:
1.60 - return dt.astimezone(tz)
1.61 - else:
1.62 - return dt
1.63 +# Datetime formatting.
1.64
1.65 def format_datetime(dt):
1.66
1.67 @@ -143,70 +88,7 @@
1.68 else:
1.69 return None
1.70
1.71 -def get_tzid(dtstart_attr, dtend_attr):
1.72 -
1.73 - """
1.74 - Return any time regime details from the given 'dtstart_attr' and
1.75 - 'dtend_attr' attribute collections.
1.76 - """
1.77 -
1.78 - return dtstart_attr.get("TZID") or dtend_attr.get("TZID")
1.79 -
1.80 -def get_datetime_attributes(dt, tzid=None):
1.81 -
1.82 - "Return attributes for 'dt' and 'tzid'."
1.83 -
1.84 - if isinstance(dt, datetime):
1.85 - attr = {"VALUE" : "DATE-TIME"}
1.86 - if tzid:
1.87 - attr["TZID"] = tzid
1.88 - return attr
1.89 - else:
1.90 - return {"VALUE" : "DATE"}
1.91 -
1.92 - return {}
1.93 -
1.94 -def get_period_attributes(tzid=None):
1.95 -
1.96 - "Return attributes for 'tzid'."
1.97 -
1.98 - attr = {"VALUE" : "PERIOD"}
1.99 - if tzid:
1.100 - attr["TZID"] = tzid
1.101 - return attr
1.102 -
1.103 -def get_datetime_item(dt, tzid=None):
1.104 -
1.105 - "Return an iCalendar-compatible string and attributes for 'dt' and 'tzid'."
1.106 -
1.107 - if not dt:
1.108 - return None, None
1.109 - dt = to_timezone(dt, tzid)
1.110 - value = format_datetime(dt)
1.111 - attr = get_datetime_attributes(dt, tzid)
1.112 - return value, attr
1.113 -
1.114 -def get_period_item(start, end, tzid=None):
1.115 -
1.116 - """
1.117 - Return an iCalendar-compatible string and attributes for 'start', 'end' and
1.118 - 'tzid'.
1.119 - """
1.120 -
1.121 - start = start and to_timezone(start, tzid)
1.122 - end = end and to_timezone(end, tzid)
1.123 -
1.124 - start_value = start and format_datetime(start) or None
1.125 - end_value = end and format_datetime(end) or None
1.126 -
1.127 - if start and end:
1.128 - attr = get_period_attributes(tzid)
1.129 - return "%s/%s" % (start_value, end_value), attr
1.130 - elif start:
1.131 - attr = get_datetime_attributes(start, tzid)
1.132 - return start_value, attr
1.133 - else:
1.134 - return None, None
1.135 +# Parsing of datetime and related information.
1.136
1.137 def get_datetime(value, attr=None):
1.138
1.139 @@ -247,6 +129,30 @@
1.140
1.141 return None
1.142
1.143 +def get_duration(value):
1.144 +
1.145 + "Return a duration for the given 'value'."
1.146 +
1.147 + if not value:
1.148 + return None
1.149 +
1.150 + m = match_duration_icalendar(value)
1.151 + if m:
1.152 + weeks, days, hours, minutes, seconds = 0, 0, 0, 0, 0
1.153 + for s in m.groups():
1.154 + if not s: continue
1.155 + if s[-1] == "W": weeks += int(s[:-1])
1.156 + elif s[-1] == "D": days += int(s[:-1])
1.157 + elif s[-1] == "H": hours += int(s[:-1])
1.158 + elif s[-1] == "M": minutes += int(s[:-1])
1.159 + elif s[-1] == "S": seconds += int(s[:-1])
1.160 + return timedelta(
1.161 + int(weeks) * 7 + int(days),
1.162 + (int(hours) * 60 + int(minutes)) * 60 + int(seconds)
1.163 + )
1.164 + else:
1.165 + return None
1.166 +
1.167 def get_period(value, attr=None):
1.168
1.169 """
1.170 @@ -275,47 +181,51 @@
1.171
1.172 return start, end
1.173
1.174 -def get_duration(value):
1.175 +# Time zone conversions and retrieval.
1.176 +
1.177 +def ends_on_same_day(dt, end, tzid):
1.178
1.179 - "Return a duration for the given 'value'."
1.180 + """
1.181 + Return whether 'dt' ends on the same day as 'end', testing the date
1.182 + components of 'dt' and 'end' against each other, but also testing whether
1.183 + 'end' is the actual end of the day in which 'dt' is positioned.
1.184
1.185 - if not value:
1.186 - return None
1.187 + Since time zone transitions may occur within a day, 'tzid' is required to
1.188 + determine the end of the day in which 'dt' is positioned, using the zone
1.189 + appropriate at that point in time, not necessarily the zone applying to
1.190 + 'dt'.
1.191 + """
1.192
1.193 - m = match_duration_icalendar(value)
1.194 - if m:
1.195 - weeks, days, hours, minutes, seconds = 0, 0, 0, 0, 0
1.196 - for s in m.groups():
1.197 - if not s: continue
1.198 - if s[-1] == "W": weeks += int(s[:-1])
1.199 - elif s[-1] == "D": days += int(s[:-1])
1.200 - elif s[-1] == "H": hours += int(s[:-1])
1.201 - elif s[-1] == "M": minutes += int(s[:-1])
1.202 - elif s[-1] == "S": seconds += int(s[:-1])
1.203 - return timedelta(
1.204 - int(weeks) * 7 + int(days),
1.205 - (int(hours) * 60 + int(minutes)) * 60 + int(seconds)
1.206 - )
1.207 + return (
1.208 + to_timezone(dt, tzid).date() == to_timezone(end, tzid).date() or
1.209 + end == get_end_of_day(dt, tzid)
1.210 + )
1.211 +
1.212 +def get_default_timezone():
1.213 +
1.214 + "Return the system time regime."
1.215 +
1.216 + filename = "/etc/timezone"
1.217 +
1.218 + if exists(filename):
1.219 + f = open(filename)
1.220 + try:
1.221 + return f.read().strip()
1.222 + finally:
1.223 + f.close()
1.224 else:
1.225 return None
1.226
1.227 -def to_date(dt):
1.228 -
1.229 - "Return the date of 'dt'."
1.230 -
1.231 - return date(dt.year, dt.month, dt.day)
1.232 -
1.233 -def to_datetime(dt, tzid):
1.234 +def get_end_of_day(dt, tzid):
1.235
1.236 """
1.237 - Return a datetime for 'dt', using the start of day for dates, and using the
1.238 - 'tzid' for the conversion.
1.239 + Get the end of the day in which 'dt' is positioned, using the given 'tzid'
1.240 + to obtain a datetime in the appropriate time zone. Where time zone
1.241 + transitions occur within a day, the zone of 'dt' may not be the eventual
1.242 + zone of the returned object.
1.243 """
1.244
1.245 - if isinstance(dt, datetime):
1.246 - return dt
1.247 - else:
1.248 - return get_start_of_day(dt, tzid)
1.249 + return get_start_of_day(dt + timedelta(1), tzid)
1.250
1.251 def get_start_of_day(dt, tzid):
1.252
1.253 @@ -329,17 +239,6 @@
1.254 start = datetime(dt.year, dt.month, dt.day, 0, 0)
1.255 return to_timezone(start, tzid)
1.256
1.257 -def get_end_of_day(dt, tzid):
1.258 -
1.259 - """
1.260 - Get the end of the day in which 'dt' is positioned, using the given 'tzid'
1.261 - to obtain a datetime in the appropriate time zone. Where time zone
1.262 - transitions occur within a day, the zone of 'dt' may not be the eventual
1.263 - zone of the returned object.
1.264 - """
1.265 -
1.266 - return get_start_of_day(dt + timedelta(1), tzid)
1.267 -
1.268 def get_start_of_next_day(dt, tzid):
1.269
1.270 """
1.271 @@ -361,29 +260,65 @@
1.272 else:
1.273 return dt + timedelta(1)
1.274
1.275 -def ends_on_same_day(dt, end, tzid):
1.276 +def to_date(dt):
1.277 +
1.278 + "Return the date of 'dt'."
1.279 +
1.280 + return date(dt.year, dt.month, dt.day)
1.281 +
1.282 +def to_datetime(dt, tzid):
1.283 +
1.284 + """
1.285 + Return a datetime for 'dt', using the start of day for dates, and using the
1.286 + 'tzid' for the conversion.
1.287 + """
1.288 +
1.289 + if isinstance(dt, datetime):
1.290 + return dt
1.291 + else:
1.292 + return get_start_of_day(dt, tzid)
1.293 +
1.294 +def to_utc_datetime(dt, date_tzid=None):
1.295
1.296 """
1.297 - Return whether 'dt' ends on the same day as 'end', testing the date
1.298 - components of 'dt' and 'end' against each other, but also testing whether
1.299 - 'end' is the actual end of the day in which 'dt' is positioned.
1.300 -
1.301 - Since time zone transitions may occur within a day, 'tzid' is required to
1.302 - determine the end of the day in which 'dt' is positioned, using the zone
1.303 - appropriate at that point in time, not necessarily the zone applying to
1.304 - 'dt'.
1.305 + Return a datetime corresponding to 'dt' in the UTC time zone. If 'date_tzid'
1.306 + is specified, dates are converted to datetimes using the time zone
1.307 + information; otherwise, dates remain unconverted.
1.308 """
1.309
1.310 - return (
1.311 - to_timezone(dt, tzid).date() == to_timezone(end, tzid).date() or
1.312 - end == get_end_of_day(dt, tzid)
1.313 - )
1.314 + if not dt:
1.315 + return None
1.316 + elif isinstance(dt, datetime):
1.317 + return to_timezone(dt, "UTC")
1.318 + elif date_tzid:
1.319 + return to_timezone(to_datetime(dt, date_tzid), "UTC")
1.320 + else:
1.321 + return dt
1.322 +
1.323 +def to_timezone(dt, name):
1.324 +
1.325 + """
1.326 + Return a datetime corresponding to 'dt' in the time regime having the given
1.327 + 'name'.
1.328 + """
1.329
1.330 -def get_timestamp():
1.331 + try:
1.332 + tz = name and timezone(name) or None
1.333 + except UnknownTimeZoneError:
1.334 + tz = None
1.335 + return to_tz(dt, tz)
1.336 +
1.337 +def to_tz(dt, tz):
1.338
1.339 - "Return the current time as an iCalendar-compatible string."
1.340 + "Return a datetime corresponding to 'dt' employing the pytz.timezone 'tz'."
1.341
1.342 - return format_datetime(to_timezone(datetime.utcnow(), "UTC"))
1.343 + if tz is not None and isinstance(dt, datetime):
1.344 + if not dt.tzinfo:
1.345 + return tz.localize(dt)
1.346 + else:
1.347 + return dt.astimezone(tz)
1.348 + else:
1.349 + return dt
1.350
1.351 def get_freebusy_period(start, end, tzid):
1.352
1.353 @@ -399,14 +334,19 @@
1.354 end = to_utc_datetime(end, tzid)
1.355 return start, end
1.356
1.357 -def to_recurrence_start(recurrenceid, tzid):
1.358 +# iCalendar-related conversions.
1.359 +
1.360 +def end_date_from_calendar(dt):
1.361
1.362 """
1.363 - Return 'recurrenceid' in a form suitable for comparison with free/busy start
1.364 - datetimes, using 'tzid' to convert recurrence identifiers that are dates.
1.365 + Change end dates to refer to the actual dates, not the iCalendar "next day"
1.366 + dates.
1.367 """
1.368
1.369 - return format_datetime(to_utc_datetime(get_datetime(recurrenceid), tzid))
1.370 + if not isinstance(dt, datetime):
1.371 + return dt - timedelta(1)
1.372 + else:
1.373 + return dt
1.374
1.375 def end_date_to_calendar(dt):
1.376
1.377 @@ -420,16 +360,84 @@
1.378 else:
1.379 return dt
1.380
1.381 -def end_date_from_calendar(dt):
1.382 +def get_datetime_attributes(dt, tzid=None):
1.383 +
1.384 + "Return attributes for 'dt' and 'tzid'."
1.385 +
1.386 + if isinstance(dt, datetime):
1.387 + attr = {"VALUE" : "DATE-TIME"}
1.388 + if tzid:
1.389 + attr["TZID"] = tzid
1.390 + return attr
1.391 + else:
1.392 + return {"VALUE" : "DATE"}
1.393 +
1.394 + return {}
1.395 +
1.396 +def get_datetime_item(dt, tzid=None):
1.397 +
1.398 + "Return an iCalendar-compatible string and attributes for 'dt' and 'tzid'."
1.399 +
1.400 + if not dt:
1.401 + return None, None
1.402 + dt = to_timezone(dt, tzid)
1.403 + value = format_datetime(dt)
1.404 + attr = get_datetime_attributes(dt, tzid)
1.405 + return value, attr
1.406 +
1.407 +def get_period_attributes(tzid=None):
1.408 +
1.409 + "Return attributes for 'tzid'."
1.410 +
1.411 + attr = {"VALUE" : "PERIOD"}
1.412 + if tzid:
1.413 + attr["TZID"] = tzid
1.414 + return attr
1.415 +
1.416 +def get_period_item(start, end, tzid=None):
1.417
1.418 """
1.419 - Change end dates to refer to the actual dates, not the iCalendar "next day"
1.420 - dates.
1.421 + Return an iCalendar-compatible string and attributes for 'start', 'end' and
1.422 + 'tzid'.
1.423 """
1.424
1.425 - if not isinstance(dt, datetime):
1.426 - return dt - timedelta(1)
1.427 + start = start and to_timezone(start, tzid)
1.428 + end = end and to_timezone(end, tzid)
1.429 +
1.430 + start_value = start and format_datetime(start) or None
1.431 + end_value = end and format_datetime(end) or None
1.432 +
1.433 + if start and end:
1.434 + attr = get_period_attributes(tzid)
1.435 + return "%s/%s" % (start_value, end_value), attr
1.436 + elif start:
1.437 + attr = get_datetime_attributes(start, tzid)
1.438 + return start_value, attr
1.439 else:
1.440 - return dt
1.441 + return None, None
1.442 +
1.443 +def get_timestamp():
1.444 +
1.445 + "Return the current time as an iCalendar-compatible string."
1.446 +
1.447 + return format_datetime(to_timezone(datetime.utcnow(), "UTC"))
1.448 +
1.449 +def get_tzid(dtstart_attr, dtend_attr):
1.450 +
1.451 + """
1.452 + Return any time regime details from the given 'dtstart_attr' and
1.453 + 'dtend_attr' attribute collections.
1.454 + """
1.455 +
1.456 + return dtstart_attr and dtstart_attr.get("TZID") or dtend_attr and dtend_attr.get("TZID") or None
1.457 +
1.458 +def to_recurrence_start(recurrenceid, tzid):
1.459 +
1.460 + """
1.461 + Return 'recurrenceid' in a form suitable for comparison with free/busy start
1.462 + datetimes, using 'tzid' to convert recurrence identifiers that are dates.
1.463 + """
1.464 +
1.465 + return format_datetime(to_utc_datetime(get_datetime(recurrenceid), tzid))
1.466
1.467 # vim: tabstop=4 expandtab shiftwidth=4