imip-agent

imiptools/dates.py

239:96006c733107
2015-02-03 Paul Boddie Added support for whole-day selection, creating events with date value types. Made get_datetime more flexible about parsing dates when VALUE is undefined. Added a get_datetime_item function to produce iCalendar attributes and values.
     1 #!/usr/bin/env python     2      3 """     4 Date processing functions.     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 date, datetime, timedelta    23 from pytz import timezone, UnknownTimeZoneError    24 import re    25     26 # iCalendar date and datetime parsing (from DateSupport in MoinSupport).    27     28 date_icalendar_regexp_str = ur'(?P<year>[0-9]{4})(?P<month>[0-9]{2})(?P<day>[0-9]{2})'    29 datetime_icalendar_regexp_str = date_icalendar_regexp_str + \    30     ur'(?:' \    31     ur'T(?P<hour>[0-2][0-9])(?P<minute>[0-5][0-9])(?P<second>[0-6][0-9])' \    32     ur'(?P<utc>Z)?' \    33     ur')?'    34     35 match_date_icalendar = re.compile(date_icalendar_regexp_str, re.UNICODE).match    36 match_datetime_icalendar = re.compile(datetime_icalendar_regexp_str, re.UNICODE).match    37     38 def to_utc_datetime(dt):    39     if not dt:    40         return None    41     elif isinstance(dt, datetime):    42         return to_timezone(dt, "UTC")    43     else:    44         return dt    45     46 def to_timezone(dt, name):    47     try:    48         tz = name and timezone(name) or None    49     except UnknownTimeZoneError:    50         tz = None    51     return to_tz(dt, tz)    52     53 def to_tz(dt, tz):    54     if tz is not None and isinstance(dt, datetime):    55         if not dt.tzinfo:    56             return tz.localize(dt)    57         else:    58             return dt.astimezone(tz)    59     else:    60         return dt    61     62 def format_datetime(dt):    63     if not dt:    64         return None    65     elif isinstance(dt, datetime):    66         if dt.tzname() == "UTC":    67             return dt.strftime("%Y%m%dT%H%M%SZ")    68         else:    69             return dt.strftime("%Y%m%dT%H%M%S")    70     else:    71         return dt.strftime("%Y%m%d")    72     73 def get_datetime_item(dt):    74     if not dt:    75         return None, None    76     value = format_datetime(dt)    77     attr = isinstance(dt, datetime) and {"TZID" : dt.tzname(), "VALUE" : "DATE-TIME"} or {"VALUE" : "DATE"}    78     return value, attr    79     80 def get_datetime(value, attr=None):    81     82     """    83     Return a datetime object from the given 'value' in iCalendar format, using    84     the 'attr' mapping (if specified) to control the conversion.    85     """    86     87     if not attr or attr.get("VALUE") in (None, "DATE-TIME"):    88         m = match_datetime_icalendar(value)    89         if m:    90             year, month, day, hour, minute, second = map(m.group, [    91                 "year", "month", "day", "hour", "minute", "second"    92                 ])    93     94             if hour and minute and second:    95                 dt = datetime(    96                     int(year), int(month), int(day), int(hour), int(minute), int(second)    97                     )    98     99                 # Impose the indicated timezone.   100                 # NOTE: This needs an ambiguity policy for DST changes.   101    102                 return to_timezone(dt, m.group("utc") and "UTC" or attr and attr.get("TZID") or None)   103    104     # Permit dates even if the VALUE is not set to DATE.   105    106     if not attr or attr.get("VALUE") in (None, "DATE"):   107         m = match_date_icalendar(value)   108         if m:   109             year, month, day = map(m.group, ["year", "month", "day"])   110             return date(int(year), int(month), int(day))   111    112     return None   113    114 def get_start_of_day(dt, tzid=None):   115     return datetime(dt.year, dt.month, dt.day, 0, 0, tzinfo=(tzid and timezone(tzid) or dt.tzinfo))   116    117 def get_end_of_day(dt, tzid=None):   118     return get_start_of_day(dt + timedelta(1), tzid)   119    120 def get_start_of_next_day(dt, tzid=None):   121     if isinstance(dt, datetime):   122         return get_end_of_day(dt, tzid)   123     else:   124         return dt + timedelta(1)   125    126 def ends_on_same_day(dt, end):   127     return (   128         dt.date() == end.date() or   129         end == get_end_of_day(dt)   130         )   131    132 def get_timestamp():   133     return format_datetime(to_timezone(datetime.utcnow(), "UTC"))   134    135 # vim: tabstop=4 expandtab shiftwidth=4