Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/utils/dateparse.py: 20%

57 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1"""Functions to parse datetime objects.""" 

2 

3# We're using regular expressions rather than time.strptime because: 

4# - They provide both validation and parsing. 

5# - They're more flexible for datetimes. 

6# - The date/datetime/time constructors produce friendlier error messages. 

7 

8import datetime 

9 

10from django.utils.regex_helper import _lazy_re_compile 

11from django.utils.timezone import get_fixed_timezone, utc 

12 

13date_re = _lazy_re_compile(r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})$") 

14 

15time_re = _lazy_re_compile( 

16 r"(?P<hour>\d{1,2}):(?P<minute>\d{1,2})" 

17 r"(?::(?P<second>\d{1,2})(?:[\.,](?P<microsecond>\d{1,6})\d{0,6})?)?$" 

18) 

19 

20datetime_re = _lazy_re_compile( 

21 r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})" 

22 r"[T ](?P<hour>\d{1,2}):(?P<minute>\d{1,2})" 

23 r"(?::(?P<second>\d{1,2})(?:[\.,](?P<microsecond>\d{1,6})\d{0,6})?)?" 

24 r"\s*(?P<tzinfo>Z|[+-]\d{2}(?::?\d{2})?)?$" 

25) 

26 

27standard_duration_re = _lazy_re_compile( 

28 r"^" 

29 r"(?:(?P<days>-?\d+) (days?, )?)?" 

30 r"(?P<sign>-?)" 

31 r"((?:(?P<hours>\d+):)(?=\d+:\d+))?" 

32 r"(?:(?P<minutes>\d+):)?" 

33 r"(?P<seconds>\d+)" 

34 r"(?:[\.,](?P<microseconds>\d{1,6})\d{0,6})?" 

35 r"$" 

36) 

37 

38# Support the sections of ISO 8601 date representation that are accepted by 

39# timedelta 

40iso8601_duration_re = _lazy_re_compile( 

41 r"^(?P<sign>[-+]?)" 

42 r"P" 

43 r"(?:(?P<days>\d+(.\d+)?)D)?" 

44 r"(?:T" 

45 r"(?:(?P<hours>\d+(.\d+)?)H)?" 

46 r"(?:(?P<minutes>\d+(.\d+)?)M)?" 

47 r"(?:(?P<seconds>\d+(.\d+)?)S)?" 

48 r")?" 

49 r"$" 

50) 

51 

52# Support PostgreSQL's day-time interval format, e.g. "3 days 04:05:06". The 

53# year-month and mixed intervals cannot be converted to a timedelta and thus 

54# aren't accepted. 

55postgres_interval_re = _lazy_re_compile( 

56 r"^" 

57 r"(?:(?P<days>-?\d+) (days? ?))?" 

58 r"(?:(?P<sign>[-+])?" 

59 r"(?P<hours>\d+):" 

60 r"(?P<minutes>\d\d):" 

61 r"(?P<seconds>\d\d)" 

62 r"(?:\.(?P<microseconds>\d{1,6}))?" 

63 r")?$" 

64) 

65 

66 

67def parse_date(value): 

68 """Parse a string and return a datetime.date. 

69 

70 Raise ValueError if the input is well formatted but not a valid date. 

71 Return None if the input isn't well formatted. 

72 """ 

73 try: 

74 return datetime.date.fromisoformat(value) 

75 except ValueError: 

76 if match := date_re.match(value): 

77 kw = {k: int(v) for k, v in match.groupdict().items()} 

78 return datetime.date(**kw) 

79 

80 

81def parse_time(value): 

82 """Parse a string and return a datetime.time. 

83 

84 This function doesn't support time zone offsets. 

85 

86 Raise ValueError if the input is well formatted but not a valid time. 

87 Return None if the input isn't well formatted, in particular if it 

88 contains an offset. 

89 """ 

90 try: 

91 # The fromisoformat() method takes time zone info into account and 

92 # returns a time with a tzinfo component, if possible. However, there 

93 # are no circumstances where aware datetime.time objects make sense, so 

94 # remove the time zone offset. 

95 return datetime.time.fromisoformat(value).replace(tzinfo=None) 

96 except ValueError: 

97 if match := time_re.match(value): 

98 kw = match.groupdict() 

99 kw["microsecond"] = kw["microsecond"] and kw["microsecond"].ljust(6, "0") 

100 kw = {k: int(v) for k, v in kw.items() if v is not None} 

101 return datetime.time(**kw) 

102 

103 

104def parse_datetime(value): 

105 """Parse a string and return a datetime.datetime. 

106 

107 This function supports time zone offsets. When the input contains one, 

108 the output uses a timezone with a fixed offset from UTC. 

109 

110 Raise ValueError if the input is well formatted but not a valid datetime. 

111 Return None if the input isn't well formatted. 

112 """ 

113 try: 

114 return datetime.datetime.fromisoformat(value) 

115 except ValueError: 

116 if match := datetime_re.match(value): 

117 kw = match.groupdict() 

118 kw["microsecond"] = kw["microsecond"] and kw["microsecond"].ljust(6, "0") 

119 tzinfo = kw.pop("tzinfo") 

120 if tzinfo == "Z": 

121 tzinfo = utc 

122 elif tzinfo is not None: 

123 offset_mins = int(tzinfo[-2:]) if len(tzinfo) > 3 else 0 

124 offset = 60 * int(tzinfo[1:3]) + offset_mins 

125 if tzinfo[0] == "-": 

126 offset = -offset 

127 tzinfo = get_fixed_timezone(offset) 

128 kw = {k: int(v) for k, v in kw.items() if v is not None} 

129 return datetime.datetime(**kw, tzinfo=tzinfo) 

130 

131 

132def parse_duration(value): 

133 """Parse a duration string and return a datetime.timedelta. 

134 

135 The preferred format for durations in Django is '%d %H:%M:%S.%f'. 

136 

137 Also supports ISO 8601 representation and PostgreSQL's day-time interval 

138 format. 

139 """ 

140 match = ( 

141 standard_duration_re.match(value) 

142 or iso8601_duration_re.match(value) 

143 or postgres_interval_re.match(value) 

144 ) 

145 if match: 

146 kw = match.groupdict() 

147 sign = -1 if kw.pop("sign", "+") == "-" else 1 

148 if kw.get("microseconds"): 

149 kw["microseconds"] = kw["microseconds"].ljust(6, "0") 

150 if ( 

151 kw.get("seconds") 

152 and kw.get("microseconds") 

153 and kw["seconds"].startswith("-") 

154 ): 

155 kw["microseconds"] = "-" + kw["microseconds"] 

156 kw = {k: float(v.replace(",", ".")) for k, v in kw.items() if v is not None} 

157 days = datetime.timedelta(kw.pop("days", 0.0) or 0.0) 

158 if match.re == iso8601_duration_re: 

159 days *= sign 

160 return days + sign * datetime.timedelta(**kw)