@@ -28,6 +28,18 @@ def _getlang():
2828 # Figure out what the current language is set to.
2929 return locale .getlocale (locale .LC_TIME )
3030
31+ def _findall (haystack , needle ):
32+ # Find all positions of needle in haystack.
33+ if not needle :
34+ return
35+ i = 0
36+ while True :
37+ i = haystack .find (needle , i )
38+ if i < 0 :
39+ break
40+ yield i
41+ i += len (needle )
42+
3143class LocaleTime (object ):
3244 """Stores and handles locale-specific information related to time.
3345
@@ -102,7 +114,8 @@ def __calc_am_pm(self):
102114 am_pm = []
103115 for hour in (1 , 22 ):
104116 time_tuple = time .struct_time ((1999 ,3 ,17 ,hour ,44 ,55 ,2 ,76 ,0 ))
105- am_pm .append (time .strftime ("%p" , time_tuple ).lower ())
117+ # br_FR has AM/PM info (' ',' ').
118+ am_pm .append (time .strftime ("%p" , time_tuple ).lower ().strip ())
106119 self .am_pm = am_pm
107120
108121 def __calc_date_time (self ):
@@ -114,42 +127,114 @@ def __calc_date_time(self):
114127 # values within the format string is very important; it eliminates
115128 # possible ambiguity for what something represents.
116129 time_tuple = time .struct_time ((1999 ,3 ,17 ,22 ,44 ,55 ,2 ,76 ,0 ))
117- date_time = [None , None , None ]
118- date_time [0 ] = time .strftime ("%c" , time_tuple ).lower ()
119- date_time [1 ] = time .strftime ("%x" , time_tuple ).lower ()
120- date_time [2 ] = time .strftime ("%X" , time_tuple ).lower ()
121- replacement_pairs = [('%' , '%%' ), (self .f_weekday [2 ], '%A' ),
122- (self .f_month [3 ], '%B' ), (self .a_weekday [2 ], '%a' ),
123- (self .a_month [3 ], '%b' ), (self .am_pm [1 ], '%p' ),
130+ time_tuple2 = time .struct_time ((1999 ,1 ,3 ,1 ,1 ,1 ,6 ,3 ,0 ))
131+ replacement_pairs = [
124132 ('1999' , '%Y' ), ('99' , '%y' ), ('22' , '%H' ),
125133 ('44' , '%M' ), ('55' , '%S' ), ('76' , '%j' ),
126134 ('17' , '%d' ), ('03' , '%m' ), ('3' , '%m' ),
127135 # '3' needed for when no leading zero.
128136 ('2' , '%w' ), ('10' , '%I' )]
129- replacement_pairs .extend ([(tz , "%Z" ) for tz_values in self .timezone
130- for tz in tz_values ])
131- for offset ,directive in ((0 ,'%c' ), (1 ,'%x' ), (2 ,'%X' )):
132- current_format = date_time [offset ]
133- for old , new in replacement_pairs :
137+ date_time = []
138+ for directive in ('%c' , '%x' , '%X' ):
139+ current_format = time .strftime (directive , time_tuple ).lower ()
140+ current_format = current_format .replace ('%' , '%%' )
141+ # The month and the day of the week formats are treated specially
142+ # because of a possible ambiguity in some locales where the full
143+ # and abbreviated names are equal or names of different types
144+ # are equal. See doc of __find_month_format for more details.
145+ lst , fmt = self .__find_weekday_format (directive )
146+ if lst :
147+ current_format = current_format .replace (lst [2 ], fmt , 1 )
148+ lst , fmt = self .__find_month_format (directive )
149+ if lst :
150+ current_format = current_format .replace (lst [3 ], fmt , 1 )
151+ if self .am_pm [1 ]:
134152 # Must deal with possible lack of locale info
135153 # manifesting itself as the empty string (e.g., Swedish's
136154 # lack of AM/PM info) or a platform returning a tuple of empty
137155 # strings (e.g., MacOS 9 having timezone as ('','')).
138- if old :
139- current_format = current_format .replace (old , new )
156+ current_format = current_format .replace (self .am_pm [1 ], '%p' )
157+ for tz_values in self .timezone :
158+ for tz in tz_values :
159+ if tz :
160+ current_format = current_format .replace (tz , "%Z" )
161+ for old , new in replacement_pairs :
162+ current_format = current_format .replace (old , new )
140163 # If %W is used, then Sunday, 2005-01-03 will fall on week 0 since
141164 # 2005-01-03 occurs before the first Monday of the year. Otherwise
142165 # %U is used.
143- time_tuple = time .struct_time ((1999 ,1 ,3 ,1 ,1 ,1 ,6 ,3 ,0 ))
144- if '00' in time .strftime (directive , time_tuple ):
166+ if '00' in time .strftime (directive , time_tuple2 ):
145167 U_W = '%W'
146168 else :
147169 U_W = '%U'
148- date_time [offset ] = current_format .replace ('11' , U_W )
170+ current_format = current_format .replace ('11' , U_W )
171+ date_time .append (current_format )
149172 self .LC_date_time = date_time [0 ]
150173 self .LC_date = date_time [1 ]
151174 self .LC_time = date_time [2 ]
152175
176+ def __find_month_format (self , directive ):
177+ """Find the month format appropriate for the current locale.
178+
179+ In some locales (for example French and Hebrew), the default month
180+ used in __calc_date_time has the same name in full and abbreviated
181+ form. Also, the month name can by accident match other part of the
182+ representation: the day of the week name (for example in Morisyen)
183+ or the month number (for example in Japanese). Thus, cycle months
184+ of the year and find all positions that match the month name for
185+ each month, If no common positions are found, the representation
186+ does not use the month name.
187+ """
188+ full_indices = abbr_indices = None
189+ for m in range (1 , 13 ):
190+ time_tuple = time .struct_time ((1999 , m , 17 , 22 , 44 , 55 , 2 , 76 , 0 ))
191+ datetime = time .strftime (directive , time_tuple ).lower ()
192+ indices = set (_findall (datetime , self .f_month [m ]))
193+ if full_indices is None :
194+ full_indices = indices
195+ else :
196+ full_indices &= indices
197+ indices = set (_findall (datetime , self .a_month [m ]))
198+ if abbr_indices is None :
199+ abbr_indices = indices
200+ else :
201+ abbr_indices &= indices
202+ if not full_indices and not abbr_indices :
203+ return None , None
204+ if full_indices :
205+ return self .f_month , '%B'
206+ if abbr_indices :
207+ return self .a_month , '%b'
208+ return None , None
209+
210+ def __find_weekday_format (self , directive ):
211+ """Find the day of the week format appropriate for the current locale.
212+
213+ Similar to __find_month_format().
214+ """
215+ full_indices = abbr_indices = None
216+ for wd in range (7 ):
217+ time_tuple = time .struct_time ((1999 , 3 , 17 , 22 , 44 , 55 , wd , 76 , 0 ))
218+ datetime = time .strftime (directive , time_tuple ).lower ()
219+ indices = set (_findall (datetime , self .f_weekday [wd ]))
220+ if full_indices is None :
221+ full_indices = indices
222+ else :
223+ full_indices &= indices
224+ if self .f_weekday [wd ] != self .a_weekday [wd ]:
225+ indices = set (_findall (datetime , self .a_weekday [wd ]))
226+ if abbr_indices is None :
227+ abbr_indices = indices
228+ else :
229+ abbr_indices &= indices
230+ if not full_indices and not abbr_indices :
231+ return None , None
232+ if full_indices :
233+ return self .f_weekday , '%A'
234+ if abbr_indices :
235+ return self .a_weekday , '%a'
236+ return None , None
237+
153238 def __calc_timezone (self ):
154239 # Set self.timezone by using time.tzname.
155240 # Do not worry about possibility of time.tzname[0] == time.tzname[1]
@@ -187,7 +272,7 @@ def __init__(self, locale_time=None):
187272 'd' : r"(?P<d>3[0-1]|[1-2]\d|0[1-9]|[1-9]| [1-9])" ,
188273 'f' : r"(?P<f>[0-9]{1,6})" ,
189274 'H' : r"(?P<H>2[0-3]|[0-1]\d|\d)" ,
190- 'I' : r"(?P<I>1[0-2]|0[1-9]|[1-9])" ,
275+ 'I' : r"(?P<I>1[0-2]|0[1-9]|[1-9]| [1-9] )" ,
191276 'G' : r"(?P<G>\d\d\d\d)" ,
192277 '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])" ,
193278 'm' : r"(?P<m>1[0-2]|0[1-9]|[1-9])" ,
@@ -349,8 +434,8 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
349434 _regex_cache [format ] = format_regex
350435 found = format_regex .match (data_string )
351436 if not found :
352- raise ValueError ("time data %r does not match format %r" %
353- (data_string , format ))
437+ raise ValueError ("time data %r does not match format %r :: /%s/ " %
438+ (data_string , format , format_regex . pattern ))
354439 if len (data_string ) != found .end ():
355440 raise ValueError ("unconverted data remains: %s" %
356441 data_string [found .end ():])
0 commit comments