@@ -394,7 +394,7 @@ class DateFormatter(ticker.Formatter):
394394
395395 def __init__ (self , fmt , tz = None ):
396396 """
397- *fmt* is an :func:`strftime` format string; *tz* is the
397+ *fmt* is a :func:`strftime` format string; *tz* is the
398398 :class:`tzinfo` instance.
399399 """
400400 if tz is None :
@@ -414,28 +414,54 @@ def __call__(self, x, pos=0):
414414 def set_tzinfo (self , tz ):
415415 self .tz = tz
416416
417- def _findall (self , text , substr ):
418- # Also finds overlaps
419- sites = []
417+ def _replace_common_substr (self , s1 , s2 , sub1 , sub2 , replacement ):
418+ """Helper function for replacing substrings sub1 and sub2
419+ located at the same indexes in strings s1 and s2 respectively,
420+ with the string replacement. It is expected that sub1 and sub2
421+ have the same length. Returns the pair s1, s2 after the
422+ substitutions.
423+ """
424+ # Find common indexes of substrings sub1 in s1 and sub2 in s2
425+ # and make substitutions inplace. Because this is inplace,
426+ # it is okay if len(replacement) != len(sub1), len(sub2).
420427 i = 0
421- while 1 :
422- j = text .find (substr , i )
428+ while True :
429+ j = s1 .find (sub1 , i )
423430 if j == - 1 :
424431 break
425- sites . append ( j )
432+
426433 i = j + 1
427- return sites
434+ if s2 [j :j + len (sub2 )] != sub2 :
435+ continue
428436
429- # Dalke: I hope I did this math right. Every 28 years the
430- # calendar repeats, except through century leap years excepting
431- # the 400 year leap years. But only if you're using the Gregorian
432- # calendar.
437+ s1 = s1 [:j ] + replacement + s1 [j + len (sub1 ):]
438+ s2 = s2 [:j ] + replacement + s2 [j + len (sub2 ):]
433439
434- def strftime (self , dt , fmt ):
435- fmt = self .illegal_s .sub (r"\1" , fmt )
436- fmt = fmt .replace ("%s" , "s" )
437- if dt .year > 1900 :
438- return cbook .unicode_safe (dt .strftime (fmt ))
440+ return s1 , s2
441+
442+ def strftime_pre_1900 (self , dt , fmt = None ):
443+ """Call time.strftime for years before 1900 by rolling
444+ forward a multiple of 28 years.
445+
446+ *fmt* is a :func:`strftime` format string.
447+
448+ Dalke: I hope I did this math right. Every 28 years the
449+ calendar repeats, except through century leap years excepting
450+ the 400 year leap years. But only if you're using the Gregorian
451+ calendar.
452+ """
453+ if fmt is None :
454+ fmt = self .fmt
455+
456+ # Since python's time module's strftime implementation does not
457+ # support %f microsecond (but the datetime module does), use a
458+ # regular expression substitution to replace instances of %f.
459+ # Note that this can be useful since python's floating-point
460+ # precision representation for datetime causes precision to be
461+ # more accurate closer to year 0 (around the year 2000, precision
462+ # can be at 10s of microseconds).
463+ fmt = re .sub (r'((^|[^%])(%%)*)%f' ,
464+ r'\g<1>{0:06d}' .format (dt .microsecond ), fmt )
439465
440466 year = dt .year
441467 # For every non-leap year century, advance by
@@ -444,26 +470,52 @@ def strftime(self, dt, fmt):
444470 off = 6 * (delta // 100 + delta // 400 )
445471 year = year + off
446472
447- # Move to around the year 2000
448- year = year + ((2000 - year ) // 28 ) * 28
473+ # Move to between the years 1973 and 2000
474+ year1 = year + ((2000 - year ) // 28 ) * 28
475+ year2 = year1 + 28
449476 timetuple = dt .timetuple ()
450- s1 = time .strftime (fmt , (year ,) + timetuple [1 :])
451- sites1 = self ._findall (s1 , str (year ))
452-
453- s2 = time .strftime (fmt , (year + 28 ,) + timetuple [1 :])
454- sites2 = self ._findall (s2 , str (year + 28 ))
455-
456- sites = []
457- for site in sites1 :
458- if site in sites2 :
459- sites .append (site )
460-
461- s = s1
462- syear = "%4d" % (dt .year ,)
463- for site in sites :
464- s = s [:site ] + syear + s [site + 4 :]
477+ # Generate timestamp string for year and year+28
478+ s1 = time .strftime (fmt , (year1 ,) + timetuple [1 :])
479+ s2 = time .strftime (fmt , (year2 ,) + timetuple [1 :])
480+
481+ # Replace instances of respective years (both 2-digit and 4-digit)
482+ # that are located at the same indexes of s1, s2 with dt's year.
483+ # Note that C++'s strftime implementation does not use padded
484+ # zeros or padded whitespace for %y or %Y for years before 100, but
485+ # uses padded zeros for %x. (For example, try the runnable examples
486+ # with .tm_year in the interval [-1900, -1800] on
487+ # http://en.cppreference.com/w/c/chrono/strftime.) For ease of
488+ # implementation, we always use padded zeros for %y, %Y, and %x.
489+ s1 , s2 = self ._replace_common_substr (s1 , s2 ,
490+ "{0:04d}" .format (year1 ),
491+ "{0:04d}" .format (year2 ),
492+ "{0:04d}" .format (dt .year ))
493+ s1 , s2 = self ._replace_common_substr (s1 , s2 ,
494+ "{0:02d}" .format (year1 % 100 ),
495+ "{0:02d}" .format (year2 % 100 ),
496+ "{0:02d}" .format (dt .year % 100 ))
497+ return cbook .unicode_safe (s1 )
498+
499+ def strftime (self , dt , fmt = None ):
500+ """Refer to documentation for datetime.strftime.
501+
502+ *fmt* is a :func:`strftime` format string.
503+
504+ Warning: For years before 1900, depending upon the current
505+ locale it is possible that the year displayed with %x might
506+ be incorrect. For years before 100, %y and %Y will yield
507+ zero-padded strings.
508+ """
509+ if fmt is None :
510+ fmt = self .fmt
511+ fmt = self .illegal_s .sub (r"\1" , fmt )
512+ fmt = fmt .replace ("%s" , "s" )
513+ if dt .year >= 1900 :
514+ # Note: in python 3.3 this is okay for years >= 1000,
515+ # refer to http://bugs.python.org/issue177742
516+ return cbook .unicode_safe (dt .strftime (fmt ))
465517
466- return cbook . unicode_safe ( s )
518+ return self . strftime_pre_1900 ( dt , fmt )
467519
468520
469521class IndexDateFormatter (ticker .Formatter ):
0 commit comments