iso8601.py

Go to the documentation of this file.
00001 # Copyright (c) 2007 Michael Twomey
00002 #
00003 # Permission is hereby granted, free of charge, to any person obtaining a
00004 # copy of this software and associated documentation files (the
00005 # "Software"), to deal in the Software without restriction, including
00006 # without limitation the rights to use, copy, modify, merge, publish,
00007 # distribute, sublicense, and/or sell copies of the Software, and to
00008 # permit persons to whom the Software is furnished to do so, subject to
00009 # the following conditions:
00010 #
00011 # The above copyright notice and this permission notice shall be included
00012 # in all copies or substantial portions of the Software.
00013 #
00014 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
00015 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
00016 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
00017 # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
00018 # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
00019 # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
00020 # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
00021 
00022 """ISO 8601 date time string parsing
00023 
00024 Basic usage:
00025 >>> import iso8601
00026 >>> iso8601.parse_date("2007-01-25T12:00:00Z")
00027 datetime.datetime(2007, 1, 25, 12, 0, tzinfo=<iso8601.iso8601.Utc ...>)
00028 >>>
00029 
00030 """
00031 
00032 from datetime import datetime, timedelta, tzinfo
00033 import re
00034 
00035 __all__ = ["parse_date", "ParseError"]
00036 
00037 # Adapted from http://delete.me.uk/2005/03/iso8601.html
00038 ISO8601_REGEX = re.compile(r"(?P<year>[0-9]{4})(-(?P<month>[0-9]{1,2})(-(?P<day>[0-9]{1,2})"
00039     r"((?P<separator>.)(?P<hour>[0-9]{2}):(?P<minute>[0-9]{2})(:(?P<second>[0-9]{2})(\.(?P<fraction>[0-9]+))?)?"
00040     r"(?P<timezone>Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?"
00041 )
00042 TIMEZONE_REGEX = re.compile("(?P<prefix>[+-])(?P<hours>[0-9]{2}).(?P<minutes>[0-9]{2})")
00043 
00044 class ParseError(Exception):
00045     """Raised when there is a problem parsing a date string"""
00046 
00047 # Yoinked from python docs
00048 ZERO = timedelta(0)
00049 class Utc(tzinfo):
00050     """UTC
00051     
00052     """
00053     def utcoffset(self, dt):
00054         return ZERO
00055 
00056     def tzname(self, dt):
00057         return "UTC"
00058 
00059     def dst(self, dt):
00060         return ZERO
00061 UTC = Utc()
00062 
00063 class FixedOffset(tzinfo):
00064     """Fixed offset in hours and minutes from UTC
00065     
00066     """
00067     def __init__(self, offset_hours, offset_minutes, name):
00068         self.__offset = timedelta(hours=offset_hours, minutes=offset_minutes)
00069         self.__name = name
00070 
00071     def utcoffset(self, dt):
00072         return self.__offset
00073 
00074     def tzname(self, dt):
00075         return self.__name
00076 
00077     def dst(self, dt):
00078         return ZERO
00079     
00080     def __repr__(self):
00081         return "<FixedOffset %r>" % self.__name
00082 
00083 def parse_timezone(tzstring, default_timezone=UTC):
00084     """Parses ISO 8601 time zone specs into tzinfo offsets
00085     
00086     """
00087     if tzstring == "Z":
00088         return default_timezone
00089     # This isn't strictly correct, but it's common to encounter dates without
00090     # timezones so I'll assume the default (which defaults to UTC).
00091     # Addresses issue 4.
00092     if tzstring is None:
00093         return default_timezone
00094     m = TIMEZONE_REGEX.match(tzstring)
00095     prefix, hours, minutes = m.groups()
00096     hours, minutes = int(hours), int(minutes)
00097     if prefix == "-":
00098         hours = -hours
00099         minutes = -minutes
00100     return FixedOffset(hours, minutes, tzstring)
00101 
00102 def parse_date(datestring, default_timezone=UTC):
00103     """Parses ISO 8601 dates into datetime objects
00104     
00105     The timezone is parsed from the date string. However it is quite common to
00106     have dates without a timezone (not strictly correct). In this case the
00107     default timezone specified in default_timezone is used. This is UTC by
00108     default.
00109     """
00110     if not isinstance(datestring, basestring):
00111         raise ParseError("Expecting a string %r" % datestring)
00112     m = ISO8601_REGEX.match(datestring)
00113     if not m:
00114         raise ParseError("Unable to parse date string %r" % datestring)
00115     groups = m.groupdict()
00116     tz = parse_timezone(groups["timezone"], default_timezone=default_timezone)
00117     if groups["fraction"] is None:
00118         groups["fraction"] = 0
00119     else:
00120         groups["fraction"] = int(float("0.%s" % groups["fraction"]) * 1e6)
00121     return datetime(int(groups["year"]), int(groups["month"]), int(groups["day"]),
00122         int(groups["hour"]), int(groups["minute"]), int(groups["second"]),
00123         int(groups["fraction"]), tz)
Generated on Sat May 25 03:02:02 2013 for Fail2Ban by  doxygen 1.6.3