Remove unnecessary python path updates for bundled subunit/testtools.
[nivanova/samba-autobuild/.git] / lib / subunit / python / subunit / iso8601.py
1 # Copyright (c) 2007 Michael Twomey
2
3 # Permission is hereby granted, free of charge, to any person obtaining a
4 # copy of this software and associated documentation files (the
5 # "Software"), to deal in the Software without restriction, including
6 # without limitation the rights to use, copy, modify, merge, publish,
7 # distribute, sublicense, and/or sell copies of the Software, and to
8 # permit persons to whom the Software is furnished to do so, subject to
9 # the following conditions:
10
11 # The above copyright notice and this permission notice shall be included
12 # in all copies or substantial portions of the Software.
13
14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18 # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19 # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20 # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22 """ISO 8601 date time string parsing
23
24 Basic usage:
25 >>> import iso8601
26 >>> iso8601.parse_date("2007-01-25T12:00:00Z")
27 datetime.datetime(2007, 1, 25, 12, 0, tzinfo=<iso8601.iso8601.Utc ...>)
28 >>>
29
30 """
31
32 from datetime import datetime, timedelta, tzinfo
33 import re
34 import sys
35
36 __all__ = ["parse_date", "ParseError"]
37
38 # Adapted from http://delete.me.uk/2005/03/iso8601.html
39 ISO8601_REGEX_PATTERN = (r"(?P<year>[0-9]{4})(-(?P<month>[0-9]{1,2})(-(?P<day>[0-9]{1,2})"
40     r"((?P<separator>.)(?P<hour>[0-9]{2}):(?P<minute>[0-9]{2})(:(?P<second>[0-9]{2})(\.(?P<fraction>[0-9]+))?)?"
41     r"(?P<timezone>Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?"
42 )
43 TIMEZONE_REGEX_PATTERN = "(?P<prefix>[+-])(?P<hours>[0-9]{2}).(?P<minutes>[0-9]{2})"
44 ISO8601_REGEX = re.compile(ISO8601_REGEX_PATTERN.encode('utf8'))
45 TIMEZONE_REGEX = re.compile(TIMEZONE_REGEX_PATTERN.encode('utf8'))
46
47 zulu = "Z".encode('latin-1')
48 minus = "-".encode('latin-1')
49
50 if sys.version_info < (3, 0):
51     bytes = str
52
53
54 class ParseError(Exception):
55     """Raised when there is a problem parsing a date string"""
56
57 # Yoinked from python docs
58 ZERO = timedelta(0)
59 class Utc(tzinfo):
60     """UTC
61     
62     """
63     def utcoffset(self, dt):
64         return ZERO
65
66     def tzname(self, dt):
67         return "UTC"
68
69     def dst(self, dt):
70         return ZERO
71 UTC = Utc()
72
73 class FixedOffset(tzinfo):
74     """Fixed offset in hours and minutes from UTC
75     
76     """
77     def __init__(self, offset_hours, offset_minutes, name):
78         self.__offset = timedelta(hours=offset_hours, minutes=offset_minutes)
79         self.__name = name
80
81     def utcoffset(self, dt):
82         return self.__offset
83
84     def tzname(self, dt):
85         return self.__name
86
87     def dst(self, dt):
88         return ZERO
89     
90     def __repr__(self):
91         return "<FixedOffset %r>" % self.__name
92
93 def parse_timezone(tzstring, default_timezone=UTC):
94     """Parses ISO 8601 time zone specs into tzinfo offsets
95     
96     """
97     if tzstring == zulu:
98         return default_timezone
99     # This isn't strictly correct, but it's common to encounter dates without
100     # timezones so I'll assume the default (which defaults to UTC).
101     # Addresses issue 4.
102     if tzstring is None:
103         return default_timezone
104     m = TIMEZONE_REGEX.match(tzstring)
105     prefix, hours, minutes = m.groups()
106     hours, minutes = int(hours), int(minutes)
107     if prefix == minus:
108         hours = -hours
109         minutes = -minutes
110     return FixedOffset(hours, minutes, tzstring)
111
112 def parse_date(datestring, default_timezone=UTC):
113     """Parses ISO 8601 dates into datetime objects
114     
115     The timezone is parsed from the date string. However it is quite common to
116     have dates without a timezone (not strictly correct). In this case the
117     default timezone specified in default_timezone is used. This is UTC by
118     default.
119     """
120     if not isinstance(datestring, bytes):
121         raise ParseError("Expecting bytes %r" % datestring)
122     m = ISO8601_REGEX.match(datestring)
123     if not m:
124         raise ParseError("Unable to parse date string %r" % datestring)
125     groups = m.groupdict()
126     tz = parse_timezone(groups["timezone"], default_timezone=default_timezone)
127     if groups["fraction"] is None:
128         groups["fraction"] = 0
129     else:
130         groups["fraction"] = int(float("0.%s" % groups["fraction"].decode()) * 1e6)
131     return datetime(int(groups["year"]), int(groups["month"]), int(groups["day"]),
132         int(groups["hour"]), int(groups["minute"]), int(groups["second"]),
133         int(groups["fraction"]), tz)