Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/isodate/isotime.py: 27%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""This modules provides a method to parse an ISO 8601:2004 time string to a
2Python datetime.time instance.
4It supports all basic and extended formats including time zone specifications
5as described in the ISO standard.
6"""
8import re
9from datetime import date, time, timedelta
10from decimal import ROUND_FLOOR, Decimal
11from typing import Union
13from isodate.duration import Duration
14from isodate.isoerror import ISO8601Error
15from isodate.isostrf import TIME_EXT_COMPLETE, TZ_EXT, strftime
16from isodate.isotzinfo import TZ_REGEX, build_tzinfo
18TIME_REGEX_CACHE: list[re.Pattern[str]] = []
19# used to cache regular expressions to parse ISO time strings.
22def build_time_regexps() -> list[re.Pattern[str]]:
23 """Build regular expressions to parse ISO time string.
25 The regular expressions are compiled and stored in TIME_REGEX_CACHE
26 for later reuse.
27 """
28 if not TIME_REGEX_CACHE:
29 # ISO 8601 time representations allow decimal fractions on least
30 # significant time component. Command and Full Stop are both valid
31 # fraction separators.
32 # The letter 'T' is allowed as time designator in front of a time
33 # expression.
34 # Immediately after a time expression, a time zone definition is
35 # allowed.
36 # a TZ may be missing (local time), be a 'Z' for UTC or a string of
37 # +-hh:mm where the ':mm' part can be skipped.
38 # TZ information patterns:
39 # ''
40 # Z
41 # +-hh:mm
42 # +-hhmm
43 # +-hh =>
44 # isotzinfo.TZ_REGEX
45 def add_re(regex_text: str) -> None:
46 TIME_REGEX_CACHE.append(re.compile(r"\A" + regex_text + TZ_REGEX + r"\Z"))
48 # 1. complete time:
49 # hh:mm:ss.ss ... extended format
50 add_re(
51 r"T?(?P<hour>[0-9]{2}):"
52 r"(?P<minute>[0-9]{2}):"
53 r"(?P<second>[0-9]{2}"
54 r"([,.][0-9]+)?)"
55 )
56 # hhmmss.ss ... basic format
57 add_re(
58 r"T?(?P<hour>[0-9]{2})" r"(?P<minute>[0-9]{2})" r"(?P<second>[0-9]{2}" r"([,.][0-9]+)?)"
59 )
60 # 2. reduced accuracy:
61 # hh:mm.mm ... extended format
62 add_re(r"T?(?P<hour>[0-9]{2}):" r"(?P<minute>[0-9]{2}" r"([,.][0-9]+)?)")
63 # hhmm.mm ... basic format
64 add_re(r"T?(?P<hour>[0-9]{2})" r"(?P<minute>[0-9]{2}" r"([,.][0-9]+)?)")
65 # hh.hh ... basic format
66 add_re(r"T?(?P<hour>[0-9]{2}" r"([,.][0-9]+)?)")
67 return TIME_REGEX_CACHE
70def parse_time(timestring: str) -> time:
71 """Parses ISO 8601 times into datetime.time objects.
73 Following ISO 8601 formats are supported:
74 (as decimal separator a ',' or a '.' is allowed)
75 hhmmss.ssTZD basic complete time
76 hh:mm:ss.ssTZD extended complete time
77 hhmm.mmTZD basic reduced accuracy time
78 hh:mm.mmTZD extended reduced accuracy time
79 hh.hhTZD basic reduced accuracy time
80 TZD is the time zone designator which can be in the following format:
81 no designator indicates local time zone
82 Z UTC
83 +-hhmm basic hours and minutes
84 +-hh:mm extended hours and minutes
85 +-hh hours
86 """
87 isotimes = build_time_regexps()
88 for pattern in isotimes:
89 match = pattern.match(timestring)
90 if match:
91 groups = match.groupdict()
92 for key, value in groups.items():
93 if value is not None:
94 groups[key] = value.replace(",", ".")
95 tzinfo = build_tzinfo(
96 groups["tzname"],
97 groups["tzsign"],
98 int(groups["tzhour"] or 0),
99 int(groups["tzmin"] or 0),
100 )
101 if "second" in groups:
102 second = Decimal(groups["second"]).quantize(
103 Decimal(".000001"), rounding=ROUND_FLOOR
104 )
105 microsecond = (second - int(second)) * int(1e6)
106 # int(...) ... no rounding
107 # to_integral() ... rounding
108 return time(
109 int(groups["hour"]),
110 int(groups["minute"]),
111 int(second),
112 int(microsecond.to_integral()),
113 tzinfo,
114 )
115 if "minute" in groups:
116 minute = Decimal(groups["minute"])
117 second = Decimal((minute - int(minute)) * 60).quantize(
118 Decimal(".000001"), rounding=ROUND_FLOOR
119 )
120 microsecond = (second - int(second)) * int(1e6)
121 return time(
122 int(groups["hour"]),
123 int(minute),
124 int(second),
125 int(microsecond.to_integral()),
126 tzinfo,
127 )
128 else:
129 microsecond, second, minute = Decimal(0), Decimal(0), Decimal(0)
130 hour = Decimal(groups["hour"])
131 minute = (hour - int(hour)) * 60
132 second = Decimal((minute - int(minute)) * 60)
133 microsecond = (second - int(second)) * int(1e6)
134 return time(
135 int(hour),
136 int(minute),
137 int(second),
138 int(microsecond.to_integral()),
139 tzinfo,
140 )
141 raise ISO8601Error("Unrecognised ISO 8601 time format: %r" % timestring)
144def time_isoformat(
145 ttime: Union[timedelta, Duration, time, date], format: str = TIME_EXT_COMPLETE + TZ_EXT
146) -> str:
147 """Format time strings.
149 This method is just a wrapper around isodate.isostrf.strftime and uses
150 Time-Extended-Complete with extended time zone as default format.
151 """
152 return strftime(ttime, format)