@@ -420,7 +420,7 @@ class DateFormatter(ticker.Formatter):
420420
421421 def __init__ (self , fmt , tz = None ):
422422 """
423- *fmt* is an :func:`strftime` format string; *tz* is the
423+ *fmt* is a :func:`strftime` format string; *tz* is the
424424 :class:`tzinfo` instance.
425425 """
426426 if tz is None :
@@ -440,28 +440,54 @@ def __call__(self, x, pos=0):
440440 def set_tzinfo (self , tz ):
441441 self .tz = tz
442442
443- def _findall (self , text , substr ):
444- # Also finds overlaps
445- sites = []
443+ def _replace_common_substr (self , s1 , s2 , sub1 , sub2 , replacement ):
444+ """Helper function for replacing substrings sub1 and sub2
445+ located at the same indexes in strings s1 and s2 respectively,
446+ with the string replacement. It is expected that sub1 and sub2
447+ have the same length. Returns the pair s1, s2 after the
448+ substitutions.
449+ """
450+ # Find common indexes of substrings sub1 in s1 and sub2 in s2
451+ # and make substitutions inplace. Because this is inplace,
452+ # it is okay if len(replacement) != len(sub1), len(sub2).
446453 i = 0
447- while 1 :
448- j = text .find (substr , i )
454+ while True :
455+ j = s1 .find (sub1 , i )
449456 if j == - 1 :
450457 break
451- sites . append ( j )
458+
452459 i = j + 1
453- return sites
460+ if s2 [j :j + len (sub2 )] != sub2 :
461+ continue
454462
455- # Dalke: I hope I did this math right. Every 28 years the
456- # calendar repeats, except through century leap years excepting
457- # the 400 year leap years. But only if you're using the Gregorian
458- # calendar.
463+ s1 = s1 [:j ] + replacement + s1 [j + len (sub1 ):]
464+ s2 = s2 [:j ] + replacement + s2 [j + len (sub2 ):]
459465
460- def strftime (self , dt , fmt ):
461- fmt = self .illegal_s .sub (r"\1" , fmt )
462- fmt = fmt .replace ("%s" , "s" )
463- if dt .year > 1900 :
464- return cbook .unicode_safe (dt .strftime (fmt ))
466+ return s1 , s2
467+
468+ def strftime_pre_1900 (self , dt , fmt = None ):
469+ """Call time.strftime for years before 1900 by rolling
470+ forward a multiple of 28 years.
471+
472+ *fmt* is a :func:`strftime` format string.
473+
474+ Dalke: I hope I did this math right. Every 28 years the
475+ calendar repeats, except through century leap years excepting
476+ the 400 year leap years. But only if you're using the Gregorian
477+ calendar.
478+ """
479+ if fmt is None :
480+ fmt = self .fmt
481+
482+ # Since python's time module's strftime implementation does not
483+ # support %f microsecond (but the datetime module does), use a
484+ # regular expression substitution to replace instances of %f.
485+ # Note that this can be useful since python's floating-point
486+ # precision representation for datetime causes precision to be
487+ # more accurate closer to year 0 (around the year 2000, precision
488+ # can be at 10s of microseconds).
489+ fmt = re .sub (r'((^|[^%])(%%)*)%f' ,
490+ r'\g<1>{0:06d}' .format (dt .microsecond ), fmt )
465491
466492 year = dt .year
467493 # For every non-leap year century, advance by
@@ -470,26 +496,52 @@ def strftime(self, dt, fmt):
470496 off = 6 * (delta // 100 + delta // 400 )
471497 year = year + off
472498
473- # Move to around the year 2000
474- year = year + ((2000 - year ) // 28 ) * 28
499+ # Move to between the years 1973 and 2000
500+ year1 = year + ((2000 - year ) // 28 ) * 28
501+ year2 = year1 + 28
475502 timetuple = dt .timetuple ()
476- s1 = time .strftime (fmt , (year ,) + timetuple [1 :])
477- sites1 = self ._findall (s1 , str (year ))
478-
479- s2 = time .strftime (fmt , (year + 28 ,) + timetuple [1 :])
480- sites2 = self ._findall (s2 , str (year + 28 ))
481-
482- sites = []
483- for site in sites1 :
484- if site in sites2 :
485- sites .append (site )
486-
487- s = s1
488- syear = "%4d" % (dt .year ,)
489- for site in sites :
490- s = s [:site ] + syear + s [site + 4 :]
503+ # Generate timestamp string for year and year+28
504+ s1 = time .strftime (fmt , (year1 ,) + timetuple [1 :])
505+ s2 = time .strftime (fmt , (year2 ,) + timetuple [1 :])
506+
507+ # Replace instances of respective years (both 2-digit and 4-digit)
508+ # that are located at the same indexes of s1, s2 with dt's year.
509+ # Note that C++'s strftime implementation does not use padded
510+ # zeros or padded whitespace for %y or %Y for years before 100, but
511+ # uses padded zeros for %x. (For example, try the runnable examples
512+ # with .tm_year in the interval [-1900, -1800] on
513+ # http://en.cppreference.com/w/c/chrono/strftime.) For ease of
514+ # implementation, we always use padded zeros for %y, %Y, and %x.
515+ s1 , s2 = self ._replace_common_substr (s1 , s2 ,
516+ "{0:04d}" .format (year1 ),
517+ "{0:04d}" .format (year2 ),
518+ "{0:04d}" .format (dt .year ))
519+ s1 , s2 = self ._replace_common_substr (s1 , s2 ,
520+ "{0:02d}" .format (year1 % 100 ),
521+ "{0:02d}" .format (year2 % 100 ),
522+ "{0:02d}" .format (dt .year % 100 ))
523+ return cbook .unicode_safe (s1 )
524+
525+ def strftime (self , dt , fmt = None ):
526+ """Refer to documentation for datetime.strftime.
527+
528+ *fmt* is a :func:`strftime` format string.
529+
530+ Warning: For years before 1900, depending upon the current
531+ locale it is possible that the year displayed with %x might
532+ be incorrect. For years before 100, %y and %Y will yield
533+ zero-padded strings.
534+ """
535+ if fmt is None :
536+ fmt = self .fmt
537+ fmt = self .illegal_s .sub (r"\1" , fmt )
538+ fmt = fmt .replace ("%s" , "s" )
539+ if dt .year >= 1900 :
540+ # Note: in python 3.3 this is okay for years >= 1000,
541+ # refer to http://bugs.python.org/issue177742
542+ return cbook .unicode_safe (dt .strftime (fmt ))
491543
492- return cbook . unicode_safe ( s )
544+ return self . strftime_pre_1900 ( dt , fmt )
493545
494546
495547class IndexDateFormatter (ticker .Formatter ):
0 commit comments