1"""
2This module provides an ISO 8601:2004 time zone info parser.
3
4It offers a function to parse the time zone offset as specified by ISO 8601.
5"""
6
7import re
8
9from isodate.isoerror import ISO8601Error
10from isodate.tzinfo import UTC, ZERO, FixedOffset
11
12TZ_REGEX = (
13 r"(?P<tzname>(Z|(?P<tzsign>[+-])" r"(?P<tzhour>[0-9]{2})(:?(?P<tzmin>[0-9]{2}))?)?)"
14)
15
16TZ_RE = re.compile(TZ_REGEX)
17
18
19def build_tzinfo(tzname, tzsign="+", tzhour=0, tzmin=0):
20 """
21 create a tzinfo instance according to given parameters.
22
23 tzname:
24 'Z' ... return UTC
25 '' | None ... return None
26 other ... return FixedOffset
27 """
28 if tzname is None or tzname == "":
29 return None
30 if tzname == "Z":
31 return UTC
32 tzsign = ((tzsign == "-") and -1) or 1
33 return FixedOffset(tzsign * tzhour, tzsign * tzmin, tzname)
34
35
36def parse_tzinfo(tzstring):
37 """
38 Parses ISO 8601 time zone designators to tzinfo objects.
39
40 A time zone designator can be in the following format:
41 no designator indicates local time zone
42 Z UTC
43 +-hhmm basic hours and minutes
44 +-hh:mm extended hours and minutes
45 +-hh hours
46 """
47 match = TZ_RE.match(tzstring)
48 if match:
49 groups = match.groupdict()
50 return build_tzinfo(
51 groups["tzname"],
52 groups["tzsign"],
53 int(groups["tzhour"] or 0),
54 int(groups["tzmin"] or 0),
55 )
56 raise ISO8601Error("%s not a valid time zone info" % tzstring)
57
58
59def tz_isoformat(dt, format="%Z"):
60 """
61 return time zone offset ISO 8601 formatted.
62 The various ISO formats can be chosen with the format parameter.
63
64 if tzinfo is None returns ''
65 if tzinfo is UTC returns 'Z'
66 else the offset is rendered to the given format.
67 format:
68 %h ... +-HH
69 %z ... +-HHMM
70 %Z ... +-HH:MM
71 """
72 tzinfo = dt.tzinfo
73 if (tzinfo is None) or (tzinfo.utcoffset(dt) is None):
74 return ""
75 if tzinfo.utcoffset(dt) == ZERO and tzinfo.dst(dt) == ZERO:
76 return "Z"
77 tdelta = tzinfo.utcoffset(dt)
78 seconds = tdelta.days * 24 * 60 * 60 + tdelta.seconds
79 sign = ((seconds < 0) and "-") or "+"
80 seconds = abs(seconds)
81 minutes, seconds = divmod(seconds, 60)
82 hours, minutes = divmod(minutes, 60)
83 if hours > 99:
84 raise OverflowError("can not handle differences > 99 hours")
85 if format == "%Z":
86 return "%s%02d:%02d" % (sign, hours, minutes)
87 elif format == "%z":
88 return "%s%02d%02d" % (sign, hours, minutes)
89 elif format == "%h":
90 return "%s%02d" % (sign, hours)
91 raise ValueError('unknown format string "%s"' % format)