summaryrefslogtreecommitdiffhomepage
path: root/libs/aniso8601/time.py
blob: 31fab048eef03762da941e2f70d6f443a444d920 (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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# -*- coding: utf-8 -*-

# Copyright (c) 2021, Brandon Nielsen
# All rights reserved.
#
# This software may be modified and distributed under the terms
# of the BSD license.  See the LICENSE file for details.

from aniso8601.builders import TupleBuilder
from aniso8601.builders.python import PythonTimeBuilder
from aniso8601.compat import is_string
from aniso8601.date import parse_date
from aniso8601.decimalfraction import normalize
from aniso8601.exceptions import ISOFormatError
from aniso8601.resolution import TimeResolution
from aniso8601.timezone import parse_timezone

TIMEZONE_DELIMITERS = ["Z", "+", "-"]


def get_time_resolution(isotimestr):
    # Valid time formats are:
    #
    # hh:mm:ss
    # hhmmss
    # hh:mm
    # hhmm
    # hh
    # hh:mm:ssZ
    # hhmmssZ
    # hh:mmZ
    # hhmmZ
    # hhZ
    # hh:mm:ss±hh:mm
    # hhmmss±hh:mm
    # hh:mm±hh:mm
    # hhmm±hh:mm
    # hh±hh:mm
    # hh:mm:ss±hhmm
    # hhmmss±hhmm
    # hh:mm±hhmm
    # hhmm±hhmm
    # hh±hhmm
    # hh:mm:ss±hh
    # hhmmss±hh
    # hh:mm±hh
    # hhmm±hh
    # hh±hh
    isotimetuple = parse_time(isotimestr, builder=TupleBuilder)

    return _get_time_resolution(isotimetuple)


def get_datetime_resolution(isodatetimestr, delimiter="T"):
    # <date>T<time>
    #
    # Time part cannot be omittted so return time resolution
    isotimetuple = parse_datetime(
        isodatetimestr, delimiter=delimiter, builder=TupleBuilder
    ).time

    return _get_time_resolution(isotimetuple)


def _get_time_resolution(isotimetuple):
    if isotimetuple.ss is not None:
        return TimeResolution.Seconds

    if isotimetuple.mm is not None:
        return TimeResolution.Minutes

    return TimeResolution.Hours


def parse_time(isotimestr, builder=PythonTimeBuilder):
    # Given a string in any ISO 8601 time format, return a datetime.time object
    # that corresponds to the given time. Fixed offset tzdata will be included
    # if UTC offset is given in the input string. Valid time formats are:
    #
    # hh:mm:ss
    # hhmmss
    # hh:mm
    # hhmm
    # hh
    # hh:mm:ssZ
    # hhmmssZ
    # hh:mmZ
    # hhmmZ
    # hhZ
    # hh:mm:ss±hh:mm
    # hhmmss±hh:mm
    # hh:mm±hh:mm
    # hhmm±hh:mm
    # hh±hh:mm
    # hh:mm:ss±hhmm
    # hhmmss±hhmm
    # hh:mm±hhmm
    # hhmm±hhmm
    # hh±hhmm
    # hh:mm:ss±hh
    # hhmmss±hh
    # hh:mm±hh
    # hhmm±hh
    # hh±hh
    if is_string(isotimestr) is False:
        raise ValueError("Time must be string.")

    if len(isotimestr) == 0:
        raise ISOFormatError('"{0}" is not a valid ISO 8601 time.'.format(isotimestr))

    timestr = normalize(isotimestr)

    hourstr = None
    minutestr = None
    secondstr = None
    tzstr = None

    fractionalstr = None

    # Split out the timezone
    for delimiter in TIMEZONE_DELIMITERS:
        delimiteridx = timestr.find(delimiter)

        if delimiteridx != -1:
            tzstr = timestr[delimiteridx:]
            timestr = timestr[0:delimiteridx]

    # Split out the fractional component
    if timestr.find(".") != -1:
        timestr, fractionalstr = timestr.split(".", 1)

        if fractionalstr.isdigit() is False:
            raise ISOFormatError(
                '"{0}" is not a valid ISO 8601 time.'.format(isotimestr)
            )

    if len(timestr) == 2:
        # hh
        hourstr = timestr
    elif len(timestr) == 4 or len(timestr) == 5:
        # hh:mm
        # hhmm
        if timestr.count(":") == 1:
            hourstr, minutestr = timestr.split(":")
        else:
            hourstr = timestr[0:2]
            minutestr = timestr[2:]
    elif len(timestr) == 6 or len(timestr) == 8:
        # hh:mm:ss
        # hhmmss
        if timestr.count(":") == 2:
            hourstr, minutestr, secondstr = timestr.split(":")
        else:
            hourstr = timestr[0:2]
            minutestr = timestr[2:4]
            secondstr = timestr[4:]
    else:
        raise ISOFormatError('"{0}" is not a valid ISO 8601 time.'.format(isotimestr))

    for componentstr in [hourstr, minutestr, secondstr]:
        if componentstr is not None and componentstr.isdigit() is False:
            raise ISOFormatError(
                '"{0}" is not a valid ISO 8601 time.'.format(isotimestr)
            )

    if fractionalstr is not None:
        if secondstr is not None:
            secondstr = secondstr + "." + fractionalstr
        elif minutestr is not None:
            minutestr = minutestr + "." + fractionalstr
        else:
            hourstr = hourstr + "." + fractionalstr

    if tzstr is None:
        tz = None
    else:
        tz = parse_timezone(tzstr, builder=TupleBuilder)

    return builder.build_time(hh=hourstr, mm=minutestr, ss=secondstr, tz=tz)


def parse_datetime(isodatetimestr, delimiter="T", builder=PythonTimeBuilder):
    # Given a string in ISO 8601 date time format, return a datetime.datetime
    # object that corresponds to the given date time.
    # By default, the ISO 8601 specified T delimiter is used to split the
    # date and time (<date>T<time>). Fixed offset tzdata will be included
    # if UTC offset is given in the input string.
    if is_string(isodatetimestr) is False:
        raise ValueError("Date time must be string.")

    if delimiter not in isodatetimestr:
        raise ISOFormatError(
            'Delimiter "{0}" is not in combined date time '
            'string "{1}".'.format(delimiter, isodatetimestr)
        )

    isodatestr, isotimestr = isodatetimestr.split(delimiter, 1)

    datepart = parse_date(isodatestr, builder=TupleBuilder)

    timepart = parse_time(isotimestr, builder=TupleBuilder)

    return builder.build_datetime(datepart, timepart)