summaryrefslogtreecommitdiff
path: root/src/isodate/isotzinfo.py
blob: b7fa41bdb5f8e2f9459cc7c885609f57a507c58d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
"""
This module provides an ISO 8601:2004 time zone info parser.

It offers a function to parse the time zone offset as specified by ISO 8601.
"""
import re

from isodate.isoerror import ISO8601Error
from isodate.tzinfo import UTC, FixedOffset, ZERO

TZ_REGEX = (
    r"(?P<tzname>(Z|(?P<tzsign>[+-])" r"(?P<tzhour>[0-9]{2})(:?(?P<tzmin>[0-9]{2}))?)?)"
)

TZ_RE = re.compile(TZ_REGEX)


def build_tzinfo(tzname, tzsign="+", tzhour=0, tzmin=0):
    """
    create a tzinfo instance according to given parameters.

    tzname:
      'Z'       ... return UTC
      '' | None ... return None
      other     ... return FixedOffset
    """
    if tzname is None or tzname == "":
        return None
    if tzname == "Z":
        return UTC
    tzsign = ((tzsign == "-") and -1) or 1
    return FixedOffset(tzsign * tzhour, tzsign * tzmin, tzname)


def parse_tzinfo(tzstring):
    """
    Parses ISO 8601 time zone designators to tzinfo objecs.

    A time zone designator can be in the following format:
              no designator indicates local time zone
      Z       UTC
      +-hhmm  basic hours and minutes
      +-hh:mm extended hours and minutes
      +-hh    hours
    """
    match = TZ_RE.match(tzstring)
    if match:
        groups = match.groupdict()
        return build_tzinfo(
            groups["tzname"],
            groups["tzsign"],
            int(groups["tzhour"] or 0),
            int(groups["tzmin"] or 0),
        )
    raise ISO8601Error("%s not a valid time zone info" % tzstring)


def tz_isoformat(dt, format="%Z"):
    """
    return time zone offset ISO 8601 formatted.
    The various ISO formats can be chosen with the format parameter.

    if tzinfo is None returns ''
    if tzinfo is UTC returns 'Z'
    else the offset is rendered to the given format.
    format:
        %h ... +-HH
        %z ... +-HHMM
        %Z ... +-HH:MM
    """
    tzinfo = dt.tzinfo
    if (tzinfo is None) or (tzinfo.utcoffset(dt) is None):
        return ""
    if tzinfo.utcoffset(dt) == ZERO and tzinfo.dst(dt) == ZERO:
        return "Z"
    tdelta = tzinfo.utcoffset(dt)
    seconds = tdelta.days * 24 * 60 * 60 + tdelta.seconds
    sign = ((seconds < 0) and "-") or "+"
    seconds = abs(seconds)
    minutes, seconds = divmod(seconds, 60)
    hours, minutes = divmod(minutes, 60)
    if hours > 99:
        raise OverflowError("can not handle differences > 99 hours")
    if format == "%Z":
        return "%s%02d:%02d" % (sign, hours, minutes)
    elif format == "%z":
        return "%s%02d%02d" % (sign, hours, minutes)
    elif format == "%h":
        return "%s%02d" % (sign, hours)
    raise ValueError('unknown format string "%s"' % format)