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 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 dt.astimezone(timezone("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 if tz is not None: 52 if not dt.tzinfo: 53 return tz.localize(dt) 54 else: 55 return dt.astimezone(tz) 56 else: 57 return dt 58 59 def format_datetime(dt): 60 if not dt: 61 return None 62 elif isinstance(dt, datetime): 63 if dt.tzname() == "UTC": 64 return dt.strftime("%Y%m%dT%H%M%SZ") 65 else: 66 return dt.strftime("%Y%m%dT%H%M%S") 67 else: 68 return dt.strftime("%Y%m%d") 69 70 def get_datetime(value, attr=None): 71 72 """ 73 Return a datetime object from the given 'value' in iCalendar format, using 74 the 'attr' mapping (if specified) to control the conversion. 75 """ 76 77 if not attr or attr.get("VALUE") in (None, "DATE-TIME"): 78 m = match_datetime_icalendar(value) 79 if m: 80 dt = datetime( 81 int(m.group("year")), int(m.group("month")), int(m.group("day")), 82 int(m.group("hour")), int(m.group("minute")), int(m.group("second")) 83 ) 84 85 # Impose the indicated timezone. 86 # NOTE: This needs an ambiguity policy for DST changes. 87 88 return to_timezone(dt, m.group("utc") and "UTC" or attr and attr.get("TZID") or None) 89 90 if not attr or attr.get("VALUE") == "DATE": 91 m = match_date_icalendar(value) 92 if m: 93 return date( 94 int(m.group("year")), int(m.group("month")), int(m.group("day")) 95 ) 96 return None 97 98 def get_start_of_day(dt): 99 return datetime(dt.year, dt.month, dt.day, 0, 0, tzinfo=dt.tzinfo) 100 101 # vim: tabstop=4 expandtab shiftwidth=4