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 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: 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(value, attr=None): 74 75 """ 76 Return a datetime object from the given 'value' in iCalendar format, using 77 the 'attr' mapping (if specified) to control the conversion. 78 """ 79 80 if not attr or attr.get("VALUE") in (None, "DATE-TIME"): 81 m = match_datetime_icalendar(value) 82 if m: 83 dt = datetime( 84 int(m.group("year")), int(m.group("month")), int(m.group("day")), 85 int(m.group("hour")), int(m.group("minute")), int(m.group("second")) 86 ) 87 88 # Impose the indicated timezone. 89 # NOTE: This needs an ambiguity policy for DST changes. 90 91 return to_timezone(dt, m.group("utc") and "UTC" or attr and attr.get("TZID") or None) 92 93 if not attr or attr.get("VALUE") == "DATE": 94 m = match_date_icalendar(value) 95 if m: 96 return date( 97 int(m.group("year")), int(m.group("month")), int(m.group("day")) 98 ) 99 return None 100 101 def get_start_of_day(dt): 102 return datetime(dt.year, dt.month, dt.day, 0, 0, tzinfo=dt.tzinfo) 103 104 # vim: tabstop=4 expandtab shiftwidth=4