@@ -161,11 +161,42 @@ def test_compile(self):
161161 for directive in ('a' ,'A' ,'b' ,'B' ,'c' ,'d' ,'G' ,'H' ,'I' ,'j' ,'m' ,'M' ,'p' ,
162162 'S' ,'u' ,'U' ,'V' ,'w' ,'W' ,'x' ,'X' ,'y' ,'Y' ,'Z' ,'%' ):
163163 fmt = "%d %Y" if directive == 'd' else "%" + directive
164+ input_string = time .strftime (fmt )
164165 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" ))
169200
170201 def test_blankpattern (self ):
171202 # Make sure when tuple or something has no values no regex is generated.
@@ -299,6 +330,25 @@ def helper(self, directive, position):
299330 (directive , strf_output , strp_output [position ],
300331 self .time_tuple [position ]))
301332
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+
302352 def test_year (self ):
303353 # Test that the year is handled properly
304354 for directive in ('y' , 'Y' ):
@@ -312,6 +362,17 @@ def test_year(self):
312362 "'y' test failed; passed in '%s' "
313363 "and returned '%s'" % (bound , strp_output [0 ]))
314364
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+
315376 def test_month (self ):
316377 # Test for month directives
317378 for directive in ('B' , 'b' , 'm' ):
@@ -454,11 +515,21 @@ def test_date_time(self):
454515 for position in range (6 ):
455516 self .helper ('c' , position )
456517
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+
457523 def test_date (self ):
458524 # Test %x directive
459525 for position in range (0 ,3 ):
460526 self .helper ('x' , position )
461527
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+
462533 def test_time (self ):
463534 # Test %X directive
464535 for position in range (3 ,6 ):
@@ -769,5 +840,55 @@ def test_TimeRE_recreation_timezone(self):
769840 _strptime ._strptime_time (oldtzname [1 ], '%Z' )
770841
771842
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+
772893if __name__ == '__main__' :
773894 unittest .main ()
0 commit comments