Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/isodate/isodates.py: 16%

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

57 statements  

1############################################################################## 

2# Copyright 2009, Gerhard Weis 

3# All rights reserved. 

4# 

5# Redistribution and use in source and binary forms, with or without 

6# modification, are permitted provided that the following conditions are met: 

7# 

8# * Redistributions of source code must retain the above copyright notice, 

9# this list of conditions and the following disclaimer. 

10# * Redistributions in binary form must reproduce the above copyright notice, 

11# this list of conditions and the following disclaimer in the documentation 

12# and/or other materials provided with the distribution. 

13# * Neither the name of the authors nor the names of its contributors 

14# may be used to endorse or promote products derived from this software 

15# without specific prior written permission. 

16# 

17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 

18# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 

19# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 

20# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 

21# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 

22# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 

23# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 

24# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 

25# CONTRACT, STRICT LIABILITY, OR TORT 

26############################################################################## 

27''' 

28This modules provides a method to parse an ISO 8601:2004 date string to a 

29python datetime.date instance. 

30 

31It supports all basic, extended and expanded formats as described in the ISO 

32standard. The only limitations it has, are given by the Python datetime.date 

33implementation, which does not support dates before 0001-01-01. 

34''' 

35import re 

36from datetime import date, timedelta 

37 

38from isodate.isostrf import strftime, DATE_EXT_COMPLETE 

39from isodate.isoerror import ISO8601Error 

40 

41DATE_REGEX_CACHE = {} 

42# A dictionary to cache pre-compiled regular expressions. 

43# A set of regular expressions is identified, by number of year digits allowed 

44# and whether a plus/minus sign is required or not. (This option is changeable 

45# only for 4 digit years). 

46 

47 

48def build_date_regexps(yeardigits=4, expanded=False): 

49 ''' 

50 Compile set of regular expressions to parse ISO dates. The expressions will 

51 be created only if they are not already in REGEX_CACHE. 

52 

53 It is necessary to fix the number of year digits, else it is not possible 

54 to automatically distinguish between various ISO date formats. 

55 

56 ISO 8601 allows more than 4 digit years, on prior agreement, but then a +/- 

57 sign is required (expanded format). To support +/- sign for 4 digit years, 

58 the expanded parameter needs to be set to True. 

59 ''' 

60 if yeardigits != 4: 

61 expanded = True 

62 if (yeardigits, expanded) not in DATE_REGEX_CACHE: 

63 cache_entry = [] 

64 # ISO 8601 expanded DATE formats allow an arbitrary number of year 

65 # digits with a leading +/- sign. 

66 if expanded: 

67 sign = 1 

68 else: 

69 sign = 0 

70 # 1. complete dates: 

71 # YYYY-MM-DD or +- YYYYYY-MM-DD... extended date format 

72 cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" 

73 r"-(?P<month>[0-9]{2})-(?P<day>[0-9]{2})" 

74 % (sign, yeardigits))) 

75 # YYYYMMDD or +- YYYYYYMMDD... basic date format 

76 cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" 

77 r"(?P<month>[0-9]{2})(?P<day>[0-9]{2})" 

78 % (sign, yeardigits))) 

79 # 2. complete week dates: 

80 # YYYY-Www-D or +-YYYYYY-Www-D ... extended week date 

81 cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" 

82 r"-W(?P<week>[0-9]{2})-(?P<day>[0-9]{1})" 

83 % (sign, yeardigits))) 

84 # YYYYWwwD or +-YYYYYYWwwD ... basic week date 

85 cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})W" 

86 r"(?P<week>[0-9]{2})(?P<day>[0-9]{1})" 

87 % (sign, yeardigits))) 

88 # 3. ordinal dates: 

89 # YYYY-DDD or +-YYYYYY-DDD ... extended format 

90 cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" 

91 r"-(?P<day>[0-9]{3})" 

92 % (sign, yeardigits))) 

93 # YYYYDDD or +-YYYYYYDDD ... basic format 

94 cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" 

95 r"(?P<day>[0-9]{3})" 

96 % (sign, yeardigits))) 

97 # 4. week dates: 

98 # YYYY-Www or +-YYYYYY-Www ... extended reduced accuracy week date 

99 cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" 

100 r"-W(?P<week>[0-9]{2})" 

101 % (sign, yeardigits))) 

102 # YYYYWww or +-YYYYYYWww ... basic reduced accuracy week date 

103 cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})W" 

104 r"(?P<week>[0-9]{2})" 

105 % (sign, yeardigits))) 

106 # 5. month dates: 

107 # YYY-MM or +-YYYYYY-MM ... reduced accuracy specific month 

108 cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" 

109 r"-(?P<month>[0-9]{2})" 

110 % (sign, yeardigits))) 

111 # YYYMM or +-YYYYYYMM ... basic incomplete month date format 

112 cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" 

113 r"(?P<month>[0-9]{2})" 

114 % (sign, yeardigits))) 

115 # 6. year dates: 

116 # YYYY or +-YYYYYY ... reduced accuracy specific year 

117 cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}(?P<year>[0-9]{%d})" 

118 % (sign, yeardigits))) 

119 # 7. century dates: 

120 # YY or +-YYYY ... reduced accuracy specific century 

121 cache_entry.append(re.compile(r"(?P<sign>[+-]){%d}" 

122 r"(?P<century>[0-9]{%d})" 

123 % (sign, yeardigits - 2))) 

124 

125 DATE_REGEX_CACHE[(yeardigits, expanded)] = cache_entry 

126 return DATE_REGEX_CACHE[(yeardigits, expanded)] 

127 

128 

129def parse_date( 

130 datestring, 

131 yeardigits=4, expanded=False, defaultmonth=1, defaultday=1): 

132 ''' 

133 Parse an ISO 8601 date string into a datetime.date object. 

134 

135 As the datetime.date implementation is limited to dates starting from 

136 0001-01-01, negative dates (BC) and year 0 can not be parsed by this 

137 method. 

138 

139 For incomplete dates, this method chooses the first day for it. For 

140 instance if only a century is given, this method returns the 1st of 

141 January in year 1 of this century. 

142 

143 supported formats: (expanded formats are shown with 6 digits for year) 

144 YYYYMMDD +-YYYYYYMMDD basic complete date 

145 YYYY-MM-DD +-YYYYYY-MM-DD extended complete date 

146 YYYYWwwD +-YYYYYYWwwD basic complete week date 

147 YYYY-Www-D +-YYYYYY-Www-D extended complete week date 

148 YYYYDDD +-YYYYYYDDD basic ordinal date 

149 YYYY-DDD +-YYYYYY-DDD extended ordinal date 

150 YYYYWww +-YYYYYYWww basic incomplete week date 

151 YYYY-Www +-YYYYYY-Www extended incomplete week date 

152 YYYMM +-YYYYYYMM basic incomplete month date 

153 YYY-MM +-YYYYYY-MM incomplete month date 

154 YYYY +-YYYYYY incomplete year date 

155 YY +-YYYY incomplete century date 

156 

157 @param datestring: the ISO date string to parse 

158 @param yeardigits: how many digits are used to represent a year 

159 @param expanded: if True then +/- signs are allowed. This parameter 

160 is forced to True, if yeardigits != 4 

161 

162 @return: a datetime.date instance represented by datestring 

163 @raise ISO8601Error: if this function can not parse the datestring 

164 @raise ValueError: if datestring can not be represented by datetime.date 

165 ''' 

166 if yeardigits != 4: 

167 expanded = True 

168 isodates = build_date_regexps(yeardigits, expanded) 

169 for pattern in isodates: 

170 match = pattern.match(datestring) 

171 if match: 

172 groups = match.groupdict() 

173 # sign, century, year, month, week, day, 

174 # FIXME: negative dates not possible with python standard types 

175 sign = (groups['sign'] == '-' and -1) or 1 

176 if 'century' in groups: 

177 return date( 

178 sign * (int(groups['century']) * 100 + 1), 

179 defaultmonth, defaultday) 

180 if 'month' not in groups: # weekdate or ordinal date 

181 ret = date(sign * int(groups['year']), 1, 1) 

182 if 'week' in groups: 

183 isotuple = ret.isocalendar() 

184 if 'day' in groups: 

185 days = int(groups['day'] or 1) 

186 else: 

187 days = 1 

188 # if first week in year, do weeks-1 

189 return ret + timedelta(weeks=int(groups['week']) - 

190 (((isotuple[1] == 1) and 1) or 0), 

191 days=-isotuple[2] + days) 

192 elif 'day' in groups: # ordinal date 

193 return ret + timedelta(days=int(groups['day']) - 1) 

194 else: # year date 

195 return ret.replace(month=defaultmonth, day=defaultday) 

196 # year-, month-, or complete date 

197 if 'day' not in groups or groups['day'] is None: 

198 day = defaultday 

199 else: 

200 day = int(groups['day']) 

201 return date(sign * int(groups['year']), 

202 int(groups['month']) or defaultmonth, day) 

203 raise ISO8601Error('Unrecognised ISO 8601 date format: %r' % datestring) 

204 

205 

206def date_isoformat(tdate, format=DATE_EXT_COMPLETE, yeardigits=4): 

207 ''' 

208 Format date strings. 

209 

210 This method is just a wrapper around isodate.isostrf.strftime and uses 

211 Date-Extended-Complete as default format. 

212 ''' 

213 return strftime(tdate, format, yeardigits)