blob: 97cb551819f48c6b64296526ef5ae4ac3869efe4 [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 Norwitz5efc50d2002-12-30 22:23:12 +0000309 #XXX: Does 'Y' need to worry about having less or more than 4 digits?
310 base = super(TimeRE, self)
311 base.__init__({
Barry Warsaw35816e62002-08-29 16:24:50 +0000312 # The " \d" option is to make %c from ANSI C work
Neal Norwitz5efc50d2002-12-30 22:23:12 +0000313 'd': r"(?P<d>3[0-1]|[1-2]\d|0[1-9]|[1-9]| [1-9])",
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000314 'H': r"(?P<H>2[0-3]|[0-1]\d|\d)",
Neal Norwitz5efc50d2002-12-30 22:23:12 +0000315 'I': r"(?P<I>1[0-2]|0[1-9]|[1-9])",
316 'j': r"(?P<j>36[0-6]|3[0-5]\d|[1-2]\d\d|0[1-9]\d|00[1-9]|[1-9]\d|0[1-9]|[1-9])",
317 'm': r"(?P<m>1[0-2]|0[1-9]|[1-9])",
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000318 '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])",
Neal Norwitz5efc50d2002-12-30 22:23:12 +0000322 # W is set below by using '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)"})
Neal Norwitz5efc50d2002-12-30 22:23:12 +0000325 base.__setitem__('W', base.__getitem__('U'))
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000326 self.locale_time = locale_time
327
328 def __getitem__(self, fetch):
329 """Try to fetch regex; if it does not exist, construct it."""
330 try:
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000331 return super(TimeRE, self).__getitem__(fetch)
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000332 except KeyError:
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000333 constructors = {
334 'A': lambda: self.__seqToRE(self.locale_time.f_weekday, fetch),
335 'a': lambda: self.__seqToRE(self.locale_time.a_weekday, fetch),
336 'B': lambda: self.__seqToRE(self.locale_time.f_month[1:],
337 fetch),
338 'b': lambda: self.__seqToRE(self.locale_time.a_month[1:],
339 fetch),
340 'c': lambda: self.pattern(self.locale_time.LC_date_time),
341 'p': lambda: self.__seqToRE(self.locale_time.am_pm, fetch),
342 'x': lambda: self.pattern(self.locale_time.LC_date),
343 'X': lambda: self.pattern(self.locale_time.LC_time),
344 'Z': lambda: self.__seqToRE(self.locale_time.timezone, fetch),
345 '%': lambda: '%',
346 }
347 if fetch in constructors:
348 self[fetch] = constructors[fetch]()
349 return self[fetch]
350 else:
351 raise
Tim Peters469cdad2002-08-08 20:19:19 +0000352
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000353 def __seqToRE(self, to_convert, directive):
354 """Convert a list to a regex string for matching directive."""
355 def sorter(a, b):
356 """Sort based on length.
Tim Peters469cdad2002-08-08 20:19:19 +0000357
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000358 Done in case for some strange reason that names in the locale only
359 differ by a suffix and thus want the name with the suffix to match
360 first.
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000361 """
Barry Warsaw35816e62002-08-29 16:24:50 +0000362 try:
363 a_length = len(a)
364 except TypeError:
365 a_length = 0
366 try:
367 b_length = len(b)
368 except TypeError:
369 b_length = 0
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000370 return cmp(b_length, a_length)
Tim Peters469cdad2002-08-08 20:19:19 +0000371
Barry Warsaw35816e62002-08-29 16:24:50 +0000372 to_convert = to_convert[:] # Don't want to change value in-place.
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000373 to_convert.sort(sorter)
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000374 regex = '|'.join(to_convert)
375 regex = '(?P<%s>%s' % (directive, regex)
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000376 return '%s)' % regex
377
378 def pattern(self, format):
379 """Return re pattern for the format string."""
380 processed_format = ''
381 for whitespace in whitespace_string:
382 format = format.replace(whitespace, r'\s*')
383 while format.find('%') != -1:
384 directive_index = format.index('%')+1
Tim Peters469cdad2002-08-08 20:19:19 +0000385 processed_format = "%s%s%s" % (processed_format,
Barry Warsaw35816e62002-08-29 16:24:50 +0000386 format[:directive_index-1],
387 self[format[directive_index]])
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000388 format = format[directive_index+1:]
389 return "%s%s" % (processed_format, format)
390
391 def compile(self, format):
392 """Return a compiled re object for the format string."""
393 format = "(?#%s)%s" % (self.locale_time.lang,format)
394 return re_compile(self.pattern(format), IGNORECASE)
395
396
397def strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
Barry Warsaw35816e62002-08-29 16:24:50 +0000398 """Return a time struct based on the input data and the format string.
Tim Peters469cdad2002-08-08 20:19:19 +0000399
Barry Warsaw35816e62002-08-29 16:24:50 +0000400 The format argument may either be a regular expression object compiled by
401 strptime(), or a format string. If False is passed in for data_string
402 then the re object calculated for format will be returned. The re object
403 must be used with the same locale as was used to compile the re object.
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000404 """
405 locale_time = LocaleTime()
Barry Warsaw35816e62002-08-29 16:24:50 +0000406 if isinstance(format, RegexpType):
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000407 if format.pattern.find(locale_time.lang) == -1:
Barry Warsaw35816e62002-08-29 16:24:50 +0000408 raise TypeError("re object not created with same language as "
409 "LocaleTime instance")
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000410 else:
411 compiled_re = format
412 else:
413 compiled_re = TimeRE(locale_time).compile(format)
414 if data_string is False:
415 return compiled_re
416 else:
417 found = compiled_re.match(data_string)
418 if not found:
419 raise ValueError("time data did not match format")
Barry Warsaw35816e62002-08-29 16:24:50 +0000420 year = month = day = hour = minute = second = weekday = julian = tz =-1
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000421 found_dict = found.groupdict()
422 for group_key in found_dict.iterkeys():
Barry Warsaw35816e62002-08-29 16:24:50 +0000423 if group_key == 'y':
424 year = int("%s%s" %
425 (time.strftime("%Y")[:-2], found_dict['y']))
426 elif group_key == 'Y':
427 year = int(found_dict['Y'])
428 elif group_key == 'm':
429 month = int(found_dict['m'])
430 elif group_key == 'B':
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000431 month = _insensitiveindex(locale_time.f_month, found_dict['B'])
Barry Warsaw35816e62002-08-29 16:24:50 +0000432 elif group_key == 'b':
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000433 month = _insensitiveindex(locale_time.a_month, found_dict['b'])
Barry Warsaw35816e62002-08-29 16:24:50 +0000434 elif group_key == 'd':
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000435 day = int(found_dict['d'])
Barry Warsaw35816e62002-08-29 16:24:50 +0000436 elif group_key is 'H':
437 hour = int(found_dict['H'])
438 elif group_key == 'I':
439 hour = int(found_dict['I'])
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000440 ampm = found_dict.get('p', '').lower()
441 # If there was no AM/PM indicator, we'll treat this like AM
442 if ampm in ('', locale_time.am_pm[0].lower()):
Barry Warsaw35816e62002-08-29 16:24:50 +0000443 # We're in AM so the hour is correct unless we're
444 # looking at 12 midnight.
445 # 12 midnight == 12 AM == hour 0
446 if hour == 12:
447 hour = 0
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000448 elif ampm == locale_time.am_pm[1].lower():
Barry Warsaw35816e62002-08-29 16:24:50 +0000449 # We're in PM so we need to add 12 to the hour unless
450 # we're looking at 12 noon.
451 # 12 noon == 12 PM == hour 12
452 if hour != 12:
453 hour += 12
454 elif group_key == 'M':
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000455 minute = int(found_dict['M'])
Barry Warsaw35816e62002-08-29 16:24:50 +0000456 elif group_key == 'S':
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000457 second = int(found_dict['S'])
Barry Warsaw35816e62002-08-29 16:24:50 +0000458 elif group_key == 'A':
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000459 weekday = _insensitiveindex(locale_time.f_weekday,
460 found_dict['A'])
Barry Warsaw35816e62002-08-29 16:24:50 +0000461 elif group_key == 'a':
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000462 weekday = _insensitiveindex(locale_time.a_weekday,
463 found_dict['a'])
Barry Warsaw35816e62002-08-29 16:24:50 +0000464 elif group_key == 'w':
465 weekday = int(found_dict['w'])
466 if weekday == 0:
467 weekday = 6
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000468 else:
Barry Warsaw35816e62002-08-29 16:24:50 +0000469 weekday -= 1
470 elif group_key == 'j':
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000471 julian = int(found_dict['j'])
Barry Warsaw35816e62002-08-29 16:24:50 +0000472 elif group_key == 'Z':
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000473 found_zone = found_dict['Z'].lower()
Martin v. Löwise16e01f2002-11-27 08:30:25 +0000474 if locale_time.timezone[0] == locale_time.timezone[1]:
475 pass #Deals with bad locale setup where timezone info is
476 # the same; first found on FreeBSD 4.4 -current
477 elif locale_time.timezone[0].lower() == found_zone:
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000478 tz = 0
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000479 elif locale_time.timezone[1].lower() == found_zone:
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000480 tz = 1
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000481 elif locale_time.timezone[2].lower() == found_zone:
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000482 tz = 0
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000483 #XXX <bc>: If calculating fxns are never exposed to the general
484 # populous then just inline calculations.
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000485 if julian == -1 and year != -1 and month != -1 and day != -1:
486 julian = julianday(year, month, day)
487 if (month == -1 or day == -1) and julian != -1 and year != -1:
Barry Warsaw35816e62002-08-29 16:24:50 +0000488 year, month, day = gregorian(julian, year)
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000489 if weekday == -1 and year != -1 and month != -1 and day != -1:
490 weekday = dayofweek(year, month, day)
Barry Warsaw35816e62002-08-29 16:24:50 +0000491 return time.struct_time(
492 (year,month,day,hour,minute,second,weekday, julian,tz))
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000493
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000494def _insensitiveindex(lst, findme):
495 # Perform a case-insensitive index search.
496
497 #XXX <bc>: If LocaleTime is not exposed, then consider removing this and
498 # just lowercase when LocaleTime sets its vars and lowercasing
499 # search values.
500 findme = findme.lower()
501 for key,item in enumerate(lst):
502 if item.lower() == findme:
503 return key
504 else:
505 raise ValueError("value not in list")
506
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000507def firstjulian(year):
508 """Calculate the Julian date up until the first of the year."""
Barry Warsaw35816e62002-08-29 16:24:50 +0000509 return ((146097 * (year + 4799)) // 400) - 31738
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000510
511def julianday(year, month, day):
Barry Warsaw35816e62002-08-29 16:24:50 +0000512 """Calculate the Julian day since the beginning of the year.
513 Calculated from the Gregorian date.
514 """
515 a = (14 - month) // 12
516 return (day - 32045
517 + (((153 * (month + (12 * a) - 3)) + 2) // 5)
518 + ((146097 * (year + 4800 - a)) // 400)) - firstjulian(year) + 1
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000519
520def gregorian(julian, year):
Barry Warsaw35816e62002-08-29 16:24:50 +0000521 """Return 3-item list containing Gregorian date based on the Julian day."""
522 a = 32043 + julian + firstjulian(year)
523 b = ((4 * a) + 3) // 146097
524 c = a - ((146097 * b) // 4)
525 d = ((4 * c) + 3) // 1461
526 e = c - ((1461 * d) // 4)
527 m = ((5 * e) + 2) // 153
528 day = 1 + e - (((153 * m) + 2) // 5)
529 month = m + 3 - (12 * (m // 10))
530 year = (100 * b) + d - 4800 + (m // 10)
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000531 return [year, month, day]
532
533def dayofweek(year, month, day):
534 """Calculate the day of the week (Monday is 0)."""
Barry Warsaw35816e62002-08-29 16:24:50 +0000535 a = (14 - month) // 12
536 y = year - a
537 weekday = (day + y + ((97 * y) // 400)
538 + ((31 * (month + (12 * a) -2 )) // 12)) % 7
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000539 if weekday == 0:
540 return 6
541 else:
542 return weekday-1