iso8601.py
Go to the documentation of this file.00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
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
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
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
00090
00091
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)