blob: b7f75779d7d47faf02b237e11bce723d924f3fe8 [file] [log] [blame]
Guido van Rossum00efe7e2002-07-19 17:04:46 +00001"""Strptime-related classes and functions.
2
3CLASSES:
4 LocaleTime -- Discovers and/or stores locale-specific time information
Barry Warsaw4d895fa2002-09-23 22:46:49 +00005 TimeRE -- Creates regexes for pattern matching a string of text containing
Guido van Rossum00efe7e2002-07-19 17:04:46 +00006 time information as is returned by time.strftime()
7
8FUNCTIONS:
9 firstjulian -- Calculates the Julian date up to the first of the specified
10 year
11 gregorian -- Calculates the Gregorian date based on the Julian day and
12 year
Tim Peters469cdad2002-08-08 20:19:19 +000013 julianday -- Calculates the Julian day since the first of the year based
Guido van Rossum00efe7e2002-07-19 17:04:46 +000014 on the Gregorian date
15 dayofweek -- Calculates the day of the week from the Gregorian date.
16 strptime -- Calculates the time struct represented by the passed-in string
17
18Requires Python 2.2.1 or higher.
19Can be used in Python 2.2 if the following line is added:
20 >>> True = 1; False = 0
Guido van Rossum00efe7e2002-07-19 17:04:46 +000021"""
22import time
23import locale
24import calendar
25from re import compile as re_compile
26from re import IGNORECASE
27from string import whitespace as whitespace_string
28
Guido van Rossum00efe7e2002-07-19 17:04:46 +000029__author__ = "Brett Cannon"
30__email__ = "drifty@bigfoot.com"
31
32__all__ = ['strptime']
33
Barry Warsaw35816e62002-08-29 16:24:50 +000034RegexpType = type(re_compile(''))
35
36
Guido van Rossum00efe7e2002-07-19 17:04:46 +000037class LocaleTime(object):
38 """Stores and handles locale-specific information related to time.
39
40 ATTRIBUTES (all read-only after instance creation! Instance variables that
41 store the values have mangled names):
42 f_weekday -- full weekday names (7-item list)
43 a_weekday -- abbreviated weekday names (7-item list)
Tim Peters469cdad2002-08-08 20:19:19 +000044 f_month -- full weekday names (14-item list; dummy value in [0], which
Guido van Rossum00efe7e2002-07-19 17:04:46 +000045 is added by code)
Tim Peters469cdad2002-08-08 20:19:19 +000046 a_month -- abbreviated weekday names (13-item list, dummy value in
Guido van Rossum00efe7e2002-07-19 17:04:46 +000047 [0], which is added by code)
48 am_pm -- AM/PM representation (2-item list)
49 LC_date_time -- format string for date/time representation (string)
50 LC_date -- format string for date representation (string)
51 LC_time -- format string for time representation (string)
Tim Peters469cdad2002-08-08 20:19:19 +000052 timezone -- daylight- and non-daylight-savings timezone representation
53 (3-item list; code tacks on blank item at end for
Guido van Rossum00efe7e2002-07-19 17:04:46 +000054 possible lack of timezone such as UTC)
55 lang -- Language used by instance (string)
Guido van Rossum00efe7e2002-07-19 17:04:46 +000056 """
57
Tim Peters469cdad2002-08-08 20:19:19 +000058 def __init__(self, f_weekday=None, a_weekday=None, f_month=None,
Barry Warsaw35816e62002-08-29 16:24:50 +000059 a_month=None, am_pm=None, LC_date_time=None, LC_time=None,
60 LC_date=None, timezone=None, lang=None):
Guido van Rossum00efe7e2002-07-19 17:04:46 +000061 """Optionally set attributes with passed-in values."""
Barry Warsaw35816e62002-08-29 16:24:50 +000062 if f_weekday is None:
63 self.__f_weekday = None
64 elif len(f_weekday) == 7:
65 self.__f_weekday = list(f_weekday)
Guido van Rossum00efe7e2002-07-19 17:04:46 +000066 else:
67 raise TypeError("full weekday names must be a 7-item sequence")
Barry Warsaw35816e62002-08-29 16:24:50 +000068 if a_weekday is None:
69 self.__a_weekday = None
70 elif len(a_weekday) == 7:
71 self.__a_weekday = list(a_weekday)
Guido van Rossum00efe7e2002-07-19 17:04:46 +000072 else:
73 raise TypeError(
Barry Warsaw35816e62002-08-29 16:24:50 +000074 "abbreviated weekday names must be a 7-item sequence")
75 if f_month is None:
76 self.__f_month = None
Guido van Rossum00efe7e2002-07-19 17:04:46 +000077 elif len(f_month) == 12:
78 self.__f_month = self.__pad(f_month, True)
79 else:
80 raise TypeError("full month names must be a 12-item sequence")
Barry Warsaw35816e62002-08-29 16:24:50 +000081 if a_month is None:
82 self.__a_month = None
Guido van Rossum00efe7e2002-07-19 17:04:46 +000083 elif len(a_month) == 12:
84 self.__a_month = self.__pad(a_month, True)
85 else:
86 raise TypeError(
Barry Warsaw35816e62002-08-29 16:24:50 +000087 "abbreviated month names must be a 12-item sequence")
Guido van Rossum00efe7e2002-07-19 17:04:46 +000088 if am_pm is None:
89 self.__am_pm = None
90 elif len(am_pm) == 2:
91 self.__am_pm = am_pm
92 else:
93 raise TypeError("AM/PM representation must be a 2-item sequence")
94 self.__LC_date_time = LC_date_time
95 self.__LC_time = LC_time
96 self.__LC_date = LC_date
97 self.__timezone = timezone
98 if timezone:
99 if len(timezone) != 2:
100 raise TypeError("timezone names must contain 2 items")
101 else:
102 self.__timezone = self.__pad(timezone, False)
103 self.__lang = lang
104
105 def __pad(self, seq, front):
Barry Warsaw35816e62002-08-29 16:24:50 +0000106 # Add '' to seq to either front (is True), else the back.
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000107 seq = list(seq)
Barry Warsaw35816e62002-08-29 16:24:50 +0000108 if front:
109 seq.insert(0, '')
110 else:
111 seq.append('')
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000112 return seq
113
114 def __set_nothing(self, stuff):
Barry Warsaw35816e62002-08-29 16:24:50 +0000115 # Raise TypeError when trying to set an attribute.
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000116 raise TypeError("attribute does not support assignment")
117
118 def __get_f_weekday(self):
Barry Warsaw35816e62002-08-29 16:24:50 +0000119 # Fetch self.f_weekday.
120 if not self.__f_weekday:
121 self.__calc_weekday()
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000122 return self.__f_weekday
123
124 def __get_a_weekday(self):
Barry Warsaw35816e62002-08-29 16:24:50 +0000125 # Fetch self.a_weekday.
126 if not self.__a_weekday:
127 self.__calc_weekday()
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000128 return self.__a_weekday
129
Tim Peters469cdad2002-08-08 20:19:19 +0000130 f_weekday = property(__get_f_weekday, __set_nothing,
Barry Warsaw35816e62002-08-29 16:24:50 +0000131 doc="Full weekday names")
Tim Peters469cdad2002-08-08 20:19:19 +0000132 a_weekday = property(__get_a_weekday, __set_nothing,
Barry Warsaw35816e62002-08-29 16:24:50 +0000133 doc="Abbreviated weekday names")
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000134
135 def __get_f_month(self):
Barry Warsaw35816e62002-08-29 16:24:50 +0000136 # Fetch self.f_month.
137 if not self.__f_month:
138 self.__calc_month()
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000139 return self.__f_month
140
141 def __get_a_month(self):
Barry Warsaw35816e62002-08-29 16:24:50 +0000142 # Fetch self.a_month.
143 if not self.__a_month:
144 self.__calc_month()
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000145 return self.__a_month
146
147 f_month = property(__get_f_month, __set_nothing,
Barry Warsaw35816e62002-08-29 16:24:50 +0000148 doc="Full month names (dummy value at index 0)")
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000149 a_month = property(__get_a_month, __set_nothing,
Barry Warsaw35816e62002-08-29 16:24:50 +0000150 doc="Abbreviated month names (dummy value at index 0)")
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000151
152 def __get_am_pm(self):
Barry Warsaw35816e62002-08-29 16:24:50 +0000153 # Fetch self.am_pm.
154 if not self.__am_pm:
155 self.__calc_am_pm()
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000156 return self.__am_pm
157
158 am_pm = property(__get_am_pm, __set_nothing, doc="AM/PM representation")
159
160 def __get_timezone(self):
Barry Warsaw35816e62002-08-29 16:24:50 +0000161 # Fetch self.timezone.
162 if not self.__timezone:
163 self.__calc_timezone()
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000164 return self.__timezone
165
166 timezone = property(__get_timezone, __set_nothing,
167 doc="Timezone representation (dummy value at index 2)")
168
169 def __get_LC_date_time(self):
Barry Warsaw35816e62002-08-29 16:24:50 +0000170 # Fetch self.LC_date_time.
171 if not self.__LC_date_time:
172 self.__calc_date_time()
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000173 return self.__LC_date_time
174
175 def __get_LC_date(self):
Barry Warsaw35816e62002-08-29 16:24:50 +0000176 # Fetch self.LC_date.
177 if not self.__LC_date:
178 self.__calc_date_time()
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000179 return self.__LC_date
180
181 def __get_LC_time(self):
Barry Warsaw35816e62002-08-29 16:24:50 +0000182 # Fetch self.LC_time.
183 if not self.__LC_time:
184 self.__calc_date_time()
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000185 return self.__LC_time
186
Barry Warsaw35816e62002-08-29 16:24:50 +0000187 LC_date_time = property(
188 __get_LC_date_time, __set_nothing,
189 doc=
190 "Format string for locale's date/time representation ('%c' format)")
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000191 LC_date = property(__get_LC_date, __set_nothing,
192 doc="Format string for locale's date representation ('%x' format)")
193 LC_time = property(__get_LC_time, __set_nothing,
194 doc="Format string for locale's time representation ('%X' format)")
195
196 def __get_lang(self):
Barry Warsaw35816e62002-08-29 16:24:50 +0000197 # Fetch self.lang.
198 if not self.__lang:
199 self.__calc_lang()
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000200 return self.__lang
201
Barry Warsaw35816e62002-08-29 16:24:50 +0000202 lang = property(__get_lang, __set_nothing,
203 doc="Language used for instance")
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000204
205 def __calc_weekday(self):
Barry Warsaw35816e62002-08-29 16:24:50 +0000206 # Set self.__a_weekday and self.__f_weekday using the calendar
207 # module.
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000208 a_weekday = [calendar.day_abbr[i] for i in range(7)]
209 f_weekday = [calendar.day_name[i] for i in range(7)]
Barry Warsaw35816e62002-08-29 16:24:50 +0000210 if not self.__a_weekday:
211 self.__a_weekday = a_weekday
212 if not self.__f_weekday:
213 self.__f_weekday = f_weekday
Tim Peters469cdad2002-08-08 20:19:19 +0000214
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000215 def __calc_month(self):
Barry Warsaw35816e62002-08-29 16:24:50 +0000216 # Set self.__f_month and self.__a_month using the calendar module.
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000217 a_month = [calendar.month_abbr[i] for i in range(13)]
218 f_month = [calendar.month_name[i] for i in range(13)]
Barry Warsaw35816e62002-08-29 16:24:50 +0000219 if not self.__a_month:
220 self.__a_month = a_month
221 if not self.__f_month:
222 self.__f_month = f_month
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000223
224 def __calc_am_pm(self):
Barry Warsaw35816e62002-08-29 16:24:50 +0000225 # Set self.__am_pm by using time.strftime().
Tim Peters469cdad2002-08-08 20:19:19 +0000226
Barry Warsaw35816e62002-08-29 16:24:50 +0000227 # The magic date (1999,3,17,hour,44,55,2,76,0) is not really that
228 # magical; just happened to have used it everywhere else where a
229 # static date was needed.
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000230 am_pm = []
231 for hour in (01,22):
232 time_tuple = time.struct_time((1999,3,17,hour,44,55,2,76,0))
233 am_pm.append(time.strftime("%p", time_tuple))
234 self.__am_pm = am_pm
235
236 def __calc_date_time(self):
Barry Warsaw35816e62002-08-29 16:24:50 +0000237 # Set self.__date_time, self.__date, & self.__time by using
238 # time.strftime().
Tim Peters469cdad2002-08-08 20:19:19 +0000239
Barry Warsaw35816e62002-08-29 16:24:50 +0000240 # Use (1999,3,17,22,44,55,2,76,0) for magic date because the amount of
241 # overloaded numbers is minimized. The order in which searches for
242 # values within the format string is very important; it eliminates
243 # possible ambiguity for what something represents.
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000244 time_tuple = time.struct_time((1999,3,17,22,44,55,2,76,0))
245 date_time = [None, None, None]
246 date_time[0] = time.strftime("%c", time_tuple)
247 date_time[1] = time.strftime("%x", time_tuple)
248 date_time[2] = time.strftime("%X", time_tuple)
249 for offset,directive in ((0,'%c'), (1,'%x'), (2,'%X')):
250 current_format = date_time[offset]
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000251 for old, new in (
252 ('%', '%%'), (self.f_weekday[2], '%A'),
253 (self.f_month[3], '%B'), (self.a_weekday[2], '%a'),
254 (self.a_month[3], '%b'), (self.am_pm[1], '%p'),
255 (self.timezone[0], '%Z'), (self.timezone[1], '%Z'),
256 ('1999', '%Y'), ('99', '%y'), ('22', '%H'),
257 ('44', '%M'), ('55', '%S'), ('76', '%j'),
258 ('17', '%d'), ('03', '%m'), ('3', '%m'),
259 # '3' needed for when no leading zero.
260 ('2', '%w'), ('10', '%I')):
261 try:
262 # Done this way to deal with possible lack of locale info
263 # manifesting itself as the empty string (i.e., Swedish's
264 # lack of AM/PM info).
265 current_format = current_format.replace(old, new)
266 except ValueError:
267 pass
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000268 time_tuple = time.struct_time((1999,1,3,1,1,1,6,3,0))
269 if time.strftime(directive, time_tuple).find('00'):
270 U_W = '%U'
271 else:
272 U_W = '%W'
273 date_time[offset] = current_format.replace('11', U_W)
Barry Warsaw35816e62002-08-29 16:24:50 +0000274 if not self.__LC_date_time:
275 self.__LC_date_time = date_time[0]
276 if not self.__LC_date:
277 self.__LC_date = date_time[1]
278 if not self.__LC_time:
279 self.__LC_time = date_time[2]
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000280
281 def __calc_timezone(self):
Barry Warsaw35816e62002-08-29 16:24:50 +0000282 # Set self.__timezone by using time.tzname.
283 #
284 # Empty string used for matching when timezone is not used/needed such
285 # as with UTC.
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000286 self.__timezone = self.__pad(time.tzname, 0)
287
288 def __calc_lang(self):
Martin v. Löwise16e01f2002-11-27 08:30:25 +0000289 # Set self.__lang by using locale.getlocale() or
290 # locale.getdefaultlocale(). If both turn up empty, set the attribute
291 # to ''. This is to stop calls to this method and to make sure
292 # strptime() can produce an re object correctly.
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000293 current_lang = locale.getlocale(locale.LC_TIME)[0]
Barry Warsaw35816e62002-08-29 16:24:50 +0000294 if current_lang:
295 self.__lang = current_lang
296 else:
Martin v. Löwise16e01f2002-11-27 08:30:25 +0000297 current_lang = locale.getdefaultlocale()[0]
298 if current_lang:
299 self.__lang = current_lang
300 else:
301 self.__lang = ''
Barry Warsaw35816e62002-08-29 16:24:50 +0000302
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000303
304class TimeRE(dict):
305 """Handle conversion from format directives to regexes."""
306
307 def __init__(self, locale_time=LocaleTime()):
Barry Warsaw35816e62002-08-29 16:24:50 +0000308 """Init inst with non-locale regexes and store LocaleTime object."""
Neal Norwitz490602d2002-12-26 16:19:52 +0000309 # XXX: should 0 be valid for:
310 # day (d), julian day (j), month (m), and hour12 (I)?
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000311 super(TimeRE,self).__init__({
Barry Warsaw35816e62002-08-29 16:24:50 +0000312 # The " \d" option is to make %c from ANSI C work
313 'd': r"(?P<d>3[0-1]|[0-2]\d|\d| \d)",
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000314 'H': r"(?P<H>2[0-3]|[0-1]\d|\d)",
315 'I': r"(?P<I>0\d|1[0-2]|\d)",
Neal Norwitz490602d2002-12-26 16:19:52 +0000316 'j': r"(?P<j>(?:3[0-5]\d|36[0-6])|[0-2]\d\d|\d\d|\d)",
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000317 'm': r"(?P<m>0\d|1[0-2]|\d)",
318 'M': r"(?P<M>[0-5]\d|\d)",
319 'S': r"(?P<S>6[0-1]|[0-5]\d|\d)",
320 'U': r"(?P<U>5[0-3]|[0-4]\d|\d)",
321 'w': r"(?P<w>[0-6])",
Barry Warsaw35816e62002-08-29 16:24:50 +0000322 'W': r"(?P<W>5[0-3]|[0-4]\d|\d)", # Same as U
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000323 'y': r"(?P<y>\d\d)",
324 'Y': r"(?P<Y>\d\d\d\d)"})
325 self.locale_time = locale_time
326
327 def __getitem__(self, fetch):
328 """Try to fetch regex; if it does not exist, construct it."""
329 try:
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000330 return super(TimeRE, self).__getitem__(fetch)
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000331 except KeyError:
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000332 constructors = {
333 'A': lambda: self.__seqToRE(self.locale_time.f_weekday, fetch),
334 'a': lambda: self.__seqToRE(self.locale_time.a_weekday, fetch),
335 'B': lambda: self.__seqToRE(self.locale_time.f_month[1:],
336 fetch),
337 'b': lambda: self.__seqToRE(self.locale_time.a_month[1:],
338 fetch),
339 'c': lambda: self.pattern(self.locale_time.LC_date_time),
340 'p': lambda: self.__seqToRE(self.locale_time.am_pm, fetch),
341 'x': lambda: self.pattern(self.locale_time.LC_date),
342 'X': lambda: self.pattern(self.locale_time.LC_time),
343 'Z': lambda: self.__seqToRE(self.locale_time.timezone, fetch),
344 '%': lambda: '%',
345 }
346 if fetch in constructors:
347 self[fetch] = constructors[fetch]()
348 return self[fetch]
349 else:
350 raise
Tim Peters469cdad2002-08-08 20:19:19 +0000351
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000352 def __seqToRE(self, to_convert, directive):
353 """Convert a list to a regex string for matching directive."""
354 def sorter(a, b):
355 """Sort based on length.
Tim Peters469cdad2002-08-08 20:19:19 +0000356
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000357 Done in case for some strange reason that names in the locale only
358 differ by a suffix and thus want the name with the suffix to match
359 first.
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000360 """
Barry Warsaw35816e62002-08-29 16:24:50 +0000361 try:
362 a_length = len(a)
363 except TypeError:
364 a_length = 0
365 try:
366 b_length = len(b)
367 except TypeError:
368 b_length = 0
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000369 return cmp(b_length, a_length)
Tim Peters469cdad2002-08-08 20:19:19 +0000370
Barry Warsaw35816e62002-08-29 16:24:50 +0000371 to_convert = to_convert[:] # Don't want to change value in-place.
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000372 to_convert.sort(sorter)
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000373 regex = '|'.join(to_convert)
374 regex = '(?P<%s>%s' % (directive, regex)
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000375 return '%s)' % regex
376
377 def pattern(self, format):
378 """Return re pattern for the format string."""
379 processed_format = ''
380 for whitespace in whitespace_string:
381 format = format.replace(whitespace, r'\s*')
382 while format.find('%') != -1:
383 directive_index = format.index('%')+1
Tim Peters469cdad2002-08-08 20:19:19 +0000384 processed_format = "%s%s%s" % (processed_format,
Barry Warsaw35816e62002-08-29 16:24:50 +0000385 format[:directive_index-1],
386 self[format[directive_index]])
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000387 format = format[directive_index+1:]
388 return "%s%s" % (processed_format, format)
389
390 def compile(self, format):
391 """Return a compiled re object for the format string."""
392 format = "(?#%s)%s" % (self.locale_time.lang,format)
393 return re_compile(self.pattern(format), IGNORECASE)
394
395
396def strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
Barry Warsaw35816e62002-08-29 16:24:50 +0000397 """Return a time struct based on the input data and the format string.
Tim Peters469cdad2002-08-08 20:19:19 +0000398
Barry Warsaw35816e62002-08-29 16:24:50 +0000399 The format argument may either be a regular expression object compiled by
400 strptime(), or a format string. If False is passed in for data_string
401 then the re object calculated for format will be returned. The re object
402 must be used with the same locale as was used to compile the re object.
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000403 """
404 locale_time = LocaleTime()
Barry Warsaw35816e62002-08-29 16:24:50 +0000405 if isinstance(format, RegexpType):
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000406 if format.pattern.find(locale_time.lang) == -1:
Barry Warsaw35816e62002-08-29 16:24:50 +0000407 raise TypeError("re object not created with same language as "
408 "LocaleTime instance")
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000409 else:
410 compiled_re = format
411 else:
412 compiled_re = TimeRE(locale_time).compile(format)
413 if data_string is False:
414 return compiled_re
415 else:
416 found = compiled_re.match(data_string)
417 if not found:
418 raise ValueError("time data did not match format")
Barry Warsaw35816e62002-08-29 16:24:50 +0000419 year = month = day = hour = minute = second = weekday = julian = tz =-1
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000420 found_dict = found.groupdict()
421 for group_key in found_dict.iterkeys():
Barry Warsaw35816e62002-08-29 16:24:50 +0000422 if group_key == 'y':
423 year = int("%s%s" %
424 (time.strftime("%Y")[:-2], found_dict['y']))
425 elif group_key == 'Y':
426 year = int(found_dict['Y'])
427 elif group_key == 'm':
428 month = int(found_dict['m'])
429 elif group_key == 'B':
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000430 month = _insensitiveindex(locale_time.f_month, found_dict['B'])
Barry Warsaw35816e62002-08-29 16:24:50 +0000431 elif group_key == 'b':
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000432 month = _insensitiveindex(locale_time.a_month, found_dict['b'])
Barry Warsaw35816e62002-08-29 16:24:50 +0000433 elif group_key == 'd':
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000434 day = int(found_dict['d'])
Barry Warsaw35816e62002-08-29 16:24:50 +0000435 elif group_key is 'H':
436 hour = int(found_dict['H'])
437 elif group_key == 'I':
438 hour = int(found_dict['I'])
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000439 ampm = found_dict.get('p', '').lower()
440 # If there was no AM/PM indicator, we'll treat this like AM
441 if ampm in ('', locale_time.am_pm[0].lower()):
Barry Warsaw35816e62002-08-29 16:24:50 +0000442 # We're in AM so the hour is correct unless we're
443 # looking at 12 midnight.
444 # 12 midnight == 12 AM == hour 0
445 if hour == 12:
446 hour = 0
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000447 elif ampm == locale_time.am_pm[1].lower():
Barry Warsaw35816e62002-08-29 16:24:50 +0000448 # We're in PM so we need to add 12 to the hour unless
449 # we're looking at 12 noon.
450 # 12 noon == 12 PM == hour 12
451 if hour != 12:
452 hour += 12
453 elif group_key == 'M':
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000454 minute = int(found_dict['M'])
Barry Warsaw35816e62002-08-29 16:24:50 +0000455 elif group_key == 'S':
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000456 second = int(found_dict['S'])
Barry Warsaw35816e62002-08-29 16:24:50 +0000457 elif group_key == 'A':
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000458 weekday = _insensitiveindex(locale_time.f_weekday,
459 found_dict['A'])
Barry Warsaw35816e62002-08-29 16:24:50 +0000460 elif group_key == 'a':
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000461 weekday = _insensitiveindex(locale_time.a_weekday,
462 found_dict['a'])
Barry Warsaw35816e62002-08-29 16:24:50 +0000463 elif group_key == 'w':
464 weekday = int(found_dict['w'])
465 if weekday == 0:
466 weekday = 6
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000467 else:
Barry Warsaw35816e62002-08-29 16:24:50 +0000468 weekday -= 1
469 elif group_key == 'j':
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000470 julian = int(found_dict['j'])
Barry Warsaw35816e62002-08-29 16:24:50 +0000471 elif group_key == 'Z':
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000472 found_zone = found_dict['Z'].lower()
Martin v. Löwise16e01f2002-11-27 08:30:25 +0000473 if locale_time.timezone[0] == locale_time.timezone[1]:
474 pass #Deals with bad locale setup where timezone info is
475 # the same; first found on FreeBSD 4.4 -current
476 elif locale_time.timezone[0].lower() == found_zone:
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000477 tz = 0
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000478 elif locale_time.timezone[1].lower() == found_zone:
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000479 tz = 1
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000480 elif locale_time.timezone[2].lower() == found_zone:
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000481 tz = 0
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000482 #XXX <bc>: If calculating fxns are never exposed to the general
483 # populous then just inline calculations.
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000484 if julian == -1 and year != -1 and month != -1 and day != -1:
485 julian = julianday(year, month, day)
486 if (month == -1 or day == -1) and julian != -1 and year != -1:
Barry Warsaw35816e62002-08-29 16:24:50 +0000487 year, month, day = gregorian(julian, year)
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000488 if weekday == -1 and year != -1 and month != -1 and day != -1:
489 weekday = dayofweek(year, month, day)
Barry Warsaw35816e62002-08-29 16:24:50 +0000490 return time.struct_time(
491 (year,month,day,hour,minute,second,weekday, julian,tz))
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000492
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000493def _insensitiveindex(lst, findme):
494 # Perform a case-insensitive index search.
495
496 #XXX <bc>: If LocaleTime is not exposed, then consider removing this and
497 # just lowercase when LocaleTime sets its vars and lowercasing
498 # search values.
499 findme = findme.lower()
500 for key,item in enumerate(lst):
501 if item.lower() == findme:
502 return key
503 else:
504 raise ValueError("value not in list")
505
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000506def firstjulian(year):
507 """Calculate the Julian date up until the first of the year."""
Barry Warsaw35816e62002-08-29 16:24:50 +0000508 return ((146097 * (year + 4799)) // 400) - 31738
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000509
510def julianday(year, month, day):
Barry Warsaw35816e62002-08-29 16:24:50 +0000511 """Calculate the Julian day since the beginning of the year.
512 Calculated from the Gregorian date.
513 """
514 a = (14 - month) // 12
515 return (day - 32045
516 + (((153 * (month + (12 * a) - 3)) + 2) // 5)
517 + ((146097 * (year + 4800 - a)) // 400)) - firstjulian(year) + 1
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000518
519def gregorian(julian, year):
Barry Warsaw35816e62002-08-29 16:24:50 +0000520 """Return 3-item list containing Gregorian date based on the Julian day."""
521 a = 32043 + julian + firstjulian(year)
522 b = ((4 * a) + 3) // 146097
523 c = a - ((146097 * b) // 4)
524 d = ((4 * c) + 3) // 1461
525 e = c - ((1461 * d) // 4)
526 m = ((5 * e) + 2) // 153
527 day = 1 + e - (((153 * m) + 2) // 5)
528 month = m + 3 - (12 * (m // 10))
529 year = (100 * b) + d - 4800 + (m // 10)
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000530 return [year, month, day]
531
532def dayofweek(year, month, day):
533 """Calculate the day of the week (Monday is 0)."""
Barry Warsaw35816e62002-08-29 16:24:50 +0000534 a = (14 - month) // 12
535 y = year - a
536 weekday = (day + y + ((97 * y) // 400)
537 + ((31 * (month + (12 * a) -2 )) // 12)) % 7
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000538 if weekday == 0:
539 return 6
540 else:
541 return weekday-1