blob: 90665f31bcdd552b3e89262f46134a81226783ca [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')):
Jack Jansen62fe7552003-01-15 22:59:39 +0000261 # Must deal with possible lack of locale info
262 # manifesting itself as the empty string (e.g., Swedish's
263 # lack of AM/PM info) or a platform returning a tuple of empty
264 # strings (e.g., MacOS 9 having timezone as ('','')).
265 if old:
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000266 current_format = current_format.replace(old, new)
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000267 time_tuple = time.struct_time((1999,1,3,1,1,1,6,3,0))
268 if time.strftime(directive, time_tuple).find('00'):
269 U_W = '%U'
270 else:
271 U_W = '%W'
272 date_time[offset] = current_format.replace('11', U_W)
Barry Warsaw35816e62002-08-29 16:24:50 +0000273 if not self.__LC_date_time:
274 self.__LC_date_time = date_time[0]
275 if not self.__LC_date:
276 self.__LC_date = date_time[1]
277 if not self.__LC_time:
278 self.__LC_time = date_time[2]
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000279
280 def __calc_timezone(self):
Barry Warsaw35816e62002-08-29 16:24:50 +0000281 # Set self.__timezone by using time.tzname.
282 #
283 # Empty string used for matching when timezone is not used/needed such
284 # as with UTC.
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000285 self.__timezone = self.__pad(time.tzname, 0)
286
287 def __calc_lang(self):
Martin v. Löwise16e01f2002-11-27 08:30:25 +0000288 # Set self.__lang by using locale.getlocale() or
289 # locale.getdefaultlocale(). If both turn up empty, set the attribute
290 # to ''. This is to stop calls to this method and to make sure
291 # strptime() can produce an re object correctly.
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000292 current_lang = locale.getlocale(locale.LC_TIME)[0]
Barry Warsaw35816e62002-08-29 16:24:50 +0000293 if current_lang:
294 self.__lang = current_lang
295 else:
Martin v. Löwise16e01f2002-11-27 08:30:25 +0000296 current_lang = locale.getdefaultlocale()[0]
297 if current_lang:
298 self.__lang = current_lang
299 else:
300 self.__lang = ''
Barry Warsaw35816e62002-08-29 16:24:50 +0000301
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000302
303class TimeRE(dict):
304 """Handle conversion from format directives to regexes."""
305
306 def __init__(self, locale_time=LocaleTime()):
Barry Warsaw35816e62002-08-29 16:24:50 +0000307 """Init inst with non-locale regexes and store LocaleTime object."""
Neal Norwitz5efc50d2002-12-30 22:23:12 +0000308 #XXX: Does 'Y' need to worry about having less or more than 4 digits?
309 base = super(TimeRE, self)
310 base.__init__({
Barry Warsaw35816e62002-08-29 16:24:50 +0000311 # The " \d" option is to make %c from ANSI C work
Neal Norwitz5efc50d2002-12-30 22:23:12 +0000312 '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 +0000313 'H': r"(?P<H>2[0-3]|[0-1]\d|\d)",
Neal Norwitz5efc50d2002-12-30 22:23:12 +0000314 'I': r"(?P<I>1[0-2]|0[1-9]|[1-9])",
315 '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])",
316 'm': r"(?P<m>1[0-2]|0[1-9]|[1-9])",
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000317 'M': r"(?P<M>[0-5]\d|\d)",
318 'S': r"(?P<S>6[0-1]|[0-5]\d|\d)",
319 'U': r"(?P<U>5[0-3]|[0-4]\d|\d)",
320 'w': r"(?P<w>[0-6])",
Neal Norwitz5efc50d2002-12-30 22:23:12 +0000321 # W is set below by using 'U'
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000322 'y': r"(?P<y>\d\d)",
323 'Y': r"(?P<Y>\d\d\d\d)"})
Neal Norwitz5efc50d2002-12-30 22:23:12 +0000324 base.__setitem__('W', base.__getitem__('U'))
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000325 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):
Jack Jansen62fe7552003-01-15 22:59:39 +0000353 """Convert a list to a regex string for matching a directive."""
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000354 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.
Jack Jansen62fe7552003-01-15 22:59:39 +0000372 for value in to_convert:
373 if value != '':
374 break
375 else:
376 return ''
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000377 to_convert.sort(sorter)
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000378 regex = '|'.join(to_convert)
379 regex = '(?P<%s>%s' % (directive, regex)
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000380 return '%s)' % regex
381
382 def pattern(self, format):
383 """Return re pattern for the format string."""
384 processed_format = ''
385 for whitespace in whitespace_string:
386 format = format.replace(whitespace, r'\s*')
387 while format.find('%') != -1:
388 directive_index = format.index('%')+1
Tim Peters469cdad2002-08-08 20:19:19 +0000389 processed_format = "%s%s%s" % (processed_format,
Barry Warsaw35816e62002-08-29 16:24:50 +0000390 format[:directive_index-1],
391 self[format[directive_index]])
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000392 format = format[directive_index+1:]
393 return "%s%s" % (processed_format, format)
394
395 def compile(self, format):
396 """Return a compiled re object for the format string."""
397 format = "(?#%s)%s" % (self.locale_time.lang,format)
398 return re_compile(self.pattern(format), IGNORECASE)
399
400
401def strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
Barry Warsaw35816e62002-08-29 16:24:50 +0000402 """Return a time struct based on the input data and the format string.
Tim Peters469cdad2002-08-08 20:19:19 +0000403
Barry Warsaw35816e62002-08-29 16:24:50 +0000404 The format argument may either be a regular expression object compiled by
405 strptime(), or a format string. If False is passed in for data_string
406 then the re object calculated for format will be returned. The re object
407 must be used with the same locale as was used to compile the re object.
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000408 """
409 locale_time = LocaleTime()
Barry Warsaw35816e62002-08-29 16:24:50 +0000410 if isinstance(format, RegexpType):
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000411 if format.pattern.find(locale_time.lang) == -1:
Barry Warsaw35816e62002-08-29 16:24:50 +0000412 raise TypeError("re object not created with same language as "
413 "LocaleTime instance")
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000414 else:
415 compiled_re = format
416 else:
417 compiled_re = TimeRE(locale_time).compile(format)
418 if data_string is False:
419 return compiled_re
420 else:
421 found = compiled_re.match(data_string)
422 if not found:
423 raise ValueError("time data did not match format")
Barry Warsaw35816e62002-08-29 16:24:50 +0000424 year = month = day = hour = minute = second = weekday = julian = tz =-1
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000425 found_dict = found.groupdict()
426 for group_key in found_dict.iterkeys():
Barry Warsaw35816e62002-08-29 16:24:50 +0000427 if group_key == 'y':
428 year = int("%s%s" %
429 (time.strftime("%Y")[:-2], found_dict['y']))
430 elif group_key == 'Y':
431 year = int(found_dict['Y'])
432 elif group_key == 'm':
433 month = int(found_dict['m'])
434 elif group_key == 'B':
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000435 month = _insensitiveindex(locale_time.f_month, found_dict['B'])
Barry Warsaw35816e62002-08-29 16:24:50 +0000436 elif group_key == 'b':
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000437 month = _insensitiveindex(locale_time.a_month, found_dict['b'])
Barry Warsaw35816e62002-08-29 16:24:50 +0000438 elif group_key == 'd':
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000439 day = int(found_dict['d'])
Barry Warsaw35816e62002-08-29 16:24:50 +0000440 elif group_key is 'H':
441 hour = int(found_dict['H'])
442 elif group_key == 'I':
443 hour = int(found_dict['I'])
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000444 ampm = found_dict.get('p', '').lower()
445 # If there was no AM/PM indicator, we'll treat this like AM
446 if ampm in ('', locale_time.am_pm[0].lower()):
Barry Warsaw35816e62002-08-29 16:24:50 +0000447 # We're in AM so the hour is correct unless we're
448 # looking at 12 midnight.
449 # 12 midnight == 12 AM == hour 0
450 if hour == 12:
451 hour = 0
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000452 elif ampm == locale_time.am_pm[1].lower():
Barry Warsaw35816e62002-08-29 16:24:50 +0000453 # We're in PM so we need to add 12 to the hour unless
454 # we're looking at 12 noon.
455 # 12 noon == 12 PM == hour 12
456 if hour != 12:
457 hour += 12
458 elif group_key == 'M':
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000459 minute = int(found_dict['M'])
Barry Warsaw35816e62002-08-29 16:24:50 +0000460 elif group_key == 'S':
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000461 second = int(found_dict['S'])
Barry Warsaw35816e62002-08-29 16:24:50 +0000462 elif group_key == 'A':
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000463 weekday = _insensitiveindex(locale_time.f_weekday,
464 found_dict['A'])
Barry Warsaw35816e62002-08-29 16:24:50 +0000465 elif group_key == 'a':
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000466 weekday = _insensitiveindex(locale_time.a_weekday,
467 found_dict['a'])
Barry Warsaw35816e62002-08-29 16:24:50 +0000468 elif group_key == 'w':
469 weekday = int(found_dict['w'])
470 if weekday == 0:
471 weekday = 6
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000472 else:
Barry Warsaw35816e62002-08-29 16:24:50 +0000473 weekday -= 1
474 elif group_key == 'j':
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000475 julian = int(found_dict['j'])
Barry Warsaw35816e62002-08-29 16:24:50 +0000476 elif group_key == 'Z':
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000477 found_zone = found_dict['Z'].lower()
Martin v. Löwise16e01f2002-11-27 08:30:25 +0000478 if locale_time.timezone[0] == locale_time.timezone[1]:
479 pass #Deals with bad locale setup where timezone info is
Jack Jansen62fe7552003-01-15 22:59:39 +0000480 # the same; first found on FreeBSD 4.4.
Martin v. Löwise16e01f2002-11-27 08:30:25 +0000481 elif locale_time.timezone[0].lower() == found_zone:
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000482 tz = 0
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000483 elif locale_time.timezone[1].lower() == found_zone:
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000484 tz = 1
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000485 elif locale_time.timezone[2].lower() == found_zone:
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000486 tz = 0
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000487 #XXX <bc>: If calculating fxns are never exposed to the general
488 # populous then just inline calculations.
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000489 if julian == -1 and year != -1 and month != -1 and day != -1:
490 julian = julianday(year, month, day)
491 if (month == -1 or day == -1) and julian != -1 and year != -1:
Barry Warsaw35816e62002-08-29 16:24:50 +0000492 year, month, day = gregorian(julian, year)
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000493 if weekday == -1 and year != -1 and month != -1 and day != -1:
494 weekday = dayofweek(year, month, day)
Barry Warsaw35816e62002-08-29 16:24:50 +0000495 return time.struct_time(
496 (year,month,day,hour,minute,second,weekday, julian,tz))
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000497
Barry Warsaw4d895fa2002-09-23 22:46:49 +0000498def _insensitiveindex(lst, findme):
499 # Perform a case-insensitive index search.
500
501 #XXX <bc>: If LocaleTime is not exposed, then consider removing this and
502 # just lowercase when LocaleTime sets its vars and lowercasing
503 # search values.
504 findme = findme.lower()
505 for key,item in enumerate(lst):
506 if item.lower() == findme:
507 return key
508 else:
509 raise ValueError("value not in list")
510
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000511def firstjulian(year):
512 """Calculate the Julian date up until the first of the year."""
Barry Warsaw35816e62002-08-29 16:24:50 +0000513 return ((146097 * (year + 4799)) // 400) - 31738
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000514
515def julianday(year, month, day):
Barry Warsaw35816e62002-08-29 16:24:50 +0000516 """Calculate the Julian day since the beginning of the year.
517 Calculated from the Gregorian date.
518 """
519 a = (14 - month) // 12
520 return (day - 32045
521 + (((153 * (month + (12 * a) - 3)) + 2) // 5)
522 + ((146097 * (year + 4800 - a)) // 400)) - firstjulian(year) + 1
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000523
524def gregorian(julian, year):
Barry Warsaw35816e62002-08-29 16:24:50 +0000525 """Return 3-item list containing Gregorian date based on the Julian day."""
526 a = 32043 + julian + firstjulian(year)
527 b = ((4 * a) + 3) // 146097
528 c = a - ((146097 * b) // 4)
529 d = ((4 * c) + 3) // 1461
530 e = c - ((1461 * d) // 4)
531 m = ((5 * e) + 2) // 153
532 day = 1 + e - (((153 * m) + 2) // 5)
533 month = m + 3 - (12 * (m // 10))
534 year = (100 * b) + d - 4800 + (m // 10)
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000535 return [year, month, day]
536
537def dayofweek(year, month, day):
538 """Calculate the day of the week (Monday is 0)."""
Barry Warsaw35816e62002-08-29 16:24:50 +0000539 a = (14 - month) // 12
540 y = year - a
541 weekday = (day + y + ((97 * y) // 400)
542 + ((31 * (month + (12 * a) -2 )) // 12)) % 7
Guido van Rossum00efe7e2002-07-19 17:04:46 +0000543 if weekday == 0:
544 return 6
545 else:
546 return weekday-1