@@ -161,11 +161,42 @@ def test_compile(self):
161
161
for directive in ('a' ,'A' ,'b' ,'B' ,'c' ,'d' ,'G' ,'H' ,'I' ,'j' ,'m' ,'M' ,'p' ,
162
162
'S' ,'u' ,'U' ,'V' ,'w' ,'W' ,'x' ,'X' ,'y' ,'Y' ,'Z' ,'%' ):
163
163
fmt = "%d %Y" if directive == 'd' else "%" + directive
164
+ input_string = time .strftime (fmt )
164
165
compiled = self .time_re .compile (fmt )
165
- found = compiled .match (time .strftime (fmt ))
166
- self .assertTrue (found , "Matching failed on '%s' using '%s' regex" %
167
- (time .strftime (fmt ),
168
- compiled .pattern ))
166
+ found = compiled .match (input_string )
167
+ self .assertTrue (found ,
168
+ (f"Matching failed on '{ input_string } ' "
169
+ f"using '{ compiled .pattern } ' regex" ))
170
+ for directive in ('c' , 'x' ):
171
+ fmt = "%" + directive
172
+ with self .subTest (f"{ fmt !r} should match input containing "
173
+ f"year with fewer digits than usual" ):
174
+ # gh-124529
175
+ params = _input_str_and_expected_year_for_few_digits_year (fmt )
176
+ if params is None :
177
+ self .skipTest (f"this subtest needs locale for which "
178
+ f"{ fmt !r} includes year in some variant" )
179
+ input_string , _ = params
180
+ compiled = self .time_re .compile (fmt )
181
+ found = compiled .match (input_string )
182
+ self .assertTrue (found ,
183
+ (f"Matching failed on '{ input_string } ' "
184
+ f"using '{ compiled .pattern } ' regex" ))
185
+ for directive in ('y' , 'Y' ):
186
+ fmt = "%" + directive
187
+ with self .subTest (f"{ fmt !r} should not match input containing "
188
+ f"year with fewer digits than usual" ):
189
+ params = _input_str_and_expected_year_for_few_digits_year (fmt )
190
+ if params is None :
191
+ self .skipTest (f"this subtest needs locale for which "
192
+ f"{ fmt !r} includes year in some variant" )
193
+ input_string , _ = params
194
+ compiled = self .time_re .compile (fmt )
195
+ found = compiled .match (input_string )
196
+ self .assertFalse (found ,
197
+ (f"Matching unexpectedly succeeded "
198
+ f"on '{ input_string } ' using "
199
+ f"'{ compiled .pattern } ' regex" ))
169
200
170
201
def test_blankpattern (self ):
171
202
# Make sure when tuple or something has no values no regex is generated.
@@ -299,6 +330,25 @@ def helper(self, directive, position):
299
330
(directive , strf_output , strp_output [position ],
300
331
self .time_tuple [position ]))
301
332
333
+ def helper_for_directives_accepting_few_digits_year (self , directive ):
334
+ fmt = "%" + directive
335
+ params = _input_str_and_expected_year_for_few_digits_year (fmt )
336
+ if params is None :
337
+ self .skipTest (f"test needs locale for which { fmt !r} "
338
+ f"includes year in some variant" )
339
+ input_string , expected_year = params
340
+ try :
341
+ output_year = _strptime ._strptime (input_string , fmt )[0 ][0 ]
342
+ except ValueError as exc :
343
+ # See: gh-124529
344
+ self .fail (f"testing of { directive !r} directive failed; "
345
+ f"{ input_string !r} -> exception: { exc !r} " )
346
+ else :
347
+ self .assertEqual (output_year , expected_year ,
348
+ (f"testing of { directive !r} directive failed; "
349
+ f"{ input_string !r} -> output including year "
350
+ f"{ output_year !r} != { expected_year !r} " ))
351
+
302
352
def test_year (self ):
303
353
# Test that the year is handled properly
304
354
for directive in ('y' , 'Y' ):
@@ -312,6 +362,17 @@ def test_year(self):
312
362
"'y' test failed; passed in '%s' "
313
363
"and returned '%s'" % (bound , strp_output [0 ]))
314
364
365
+ def test_bad_year (self ):
366
+ for directive , bad_inputs in (
367
+ ('y' , ('9' , '100' , 'ni' )),
368
+ ('Y' , ('7' , '42' , '999' , '10000' , 'SPAM' )),
369
+ ):
370
+ fmt = "%" + directive
371
+ for input_val in bad_inputs :
372
+ with self .subTest (directive = directive , input_val = input_val ):
373
+ with self .assertRaises (ValueError ):
374
+ _strptime ._strptime_time (input_val , fmt )
375
+
315
376
def test_month (self ):
316
377
# Test for month directives
317
378
for directive in ('B' , 'b' , 'm' ):
@@ -454,11 +515,21 @@ def test_date_time(self):
454
515
for position in range (6 ):
455
516
self .helper ('c' , position )
456
517
518
+ def test_date_time_accepting_few_digits_year (self ): # gh-124529
519
+ # Test %c directive with input containing year
520
+ # number consisting of fewer digits than usual
521
+ self .helper_for_directives_accepting_few_digits_year ('c' )
522
+
457
523
def test_date (self ):
458
524
# Test %x directive
459
525
for position in range (0 ,3 ):
460
526
self .helper ('x' , position )
461
527
528
+ def test_date_accepting_few_digits_year (self ): # gh-124529
529
+ # Test %x directive with input containing year
530
+ # number consisting of fewer digits than usual
531
+ self .helper_for_directives_accepting_few_digits_year ('x' )
532
+
462
533
def test_time (self ):
463
534
# Test %X directive
464
535
for position in range (3 ,6 ):
@@ -769,5 +840,55 @@ def test_TimeRE_recreation_timezone(self):
769
840
_strptime ._strptime_time (oldtzname [1 ], '%Z' )
770
841
771
842
843
+ def _input_str_and_expected_year_for_few_digits_year (fmt ):
844
+ # This helper, for the given format string (fmt), returns a 2-tuple:
845
+ # (<strptime input string>, <expected year>)
846
+ # where:
847
+ # * <strptime input string> -- is a `strftime(fmt)`-result-like str
848
+ # containing a year number which is *shorter* than the usual four
849
+ # or two digits (namely: the contained year number consist of just
850
+ # one digit: 7; the choice of this particular digit is arbitrary);
851
+ # * <expected year> -- is an int representing the year number that
852
+ # is expected to be part of the result of a `strptime(<strptime
853
+ # input string>, fmt)` call (namely: either 7 or 2007, depending
854
+ # on the given format string and current locale...); however, it
855
+ # is None if <strptime input string> does *not* contain the year
856
+ # part (for the given format string and current locale).
857
+
858
+ # 1. Prepare auxiliary *magic* time data (note that the magic values
859
+ # we use here are guaranteed to be compatible with `time.strftime()`
860
+ # and also well distinguishable within a formatted string, thanks to
861
+ # the fact that the amount of overloaded numbers is minimized, as in
862
+ # `_strptime.LocaleTime.__calc_date_time()`...):
863
+ magic_year = 1999
864
+ magic_tt = (magic_year , 3 , 17 , 22 , 44 , 55 , 2 , 76 , 0 )
865
+ magic_4digits = str (magic_year )
866
+ magic_2digits = magic_4digits [- 2 :]
867
+
868
+ # 2. Pick our example year whose representation
869
+ # is shorter than the usual four or two digits:
870
+ input_year_str = '7'
871
+
872
+ # 3. Determine the <strptime input string> part of the return value:
873
+ input_string = time .strftime (fmt , magic_tt )
874
+ if (index_4digits := input_string .find (magic_4digits )) != - 1 :
875
+ # `input_string` contains up-to-4-digit year representation
876
+ input_string = input_string .replace (magic_4digits , input_year_str )
877
+ if (index_2digits := input_string .find (magic_2digits )) != - 1 :
878
+ # `input_string` contains up-to-2-digit year representation
879
+ input_string = input_string .replace (magic_2digits , input_year_str )
880
+
881
+ # 4. Determine the <expected year> part of the return value:
882
+ if index_4digits > index_2digits :
883
+ expected_year = int (input_year_str )
884
+ elif index_4digits < index_2digits :
885
+ expected_year = 2000 + int (input_year_str )
886
+ else :
887
+ assert index_4digits == index_2digits == - 1
888
+ expected_year = None
889
+
890
+ return input_string , expected_year
891
+
892
+
772
893
if __name__ == '__main__' :
773
894
unittest .main ()
0 commit comments