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

48 statements  

1"""This modules provides a method to parse an ISO 8601:2004 time string to a 

2Python datetime.time instance. 

3 

4It supports all basic and extended formats including time zone specifications 

5as described in the ISO standard. 

6""" 

7 

8import re 

9from datetime import date, time, timedelta 

10from decimal import ROUND_FLOOR, Decimal 

11from typing import Union 

12 

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 

17 

18TIME_REGEX_CACHE: list[re.Pattern[str]] = [] 

19# used to cache regular expressions to parse ISO time strings. 

20 

21 

22def build_time_regexps() -> list[re.Pattern[str]]: 

23 """Build regular expressions to parse ISO time string. 

24 

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")) 

47 

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 

68 

69 

70def parse_time(timestring: str) -> time: 

71 """Parses ISO 8601 times into datetime.time objects. 

72 

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) 

142 

143 

144def time_isoformat( 

145 ttime: Union[timedelta, Duration, time, date], format: str = TIME_EXT_COMPLETE + TZ_EXT 

146) -> str: 

147 """Format time strings. 

148 

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)