]> granicus.if.org Git - python/commitdiff
add %f format to datetime - issue 1158
authorSkip Montanaro <skip@pobox.com>
Sat, 15 Mar 2008 16:04:45 +0000 (16:04 +0000)
committerSkip Montanaro <skip@pobox.com>
Sat, 15 Mar 2008 16:04:45 +0000 (16:04 +0000)
Doc/library/datetime.rst
Lib/_strptime.py
Lib/test/test_datetime.py
Lib/test/test_strptime.py
Modules/datetimemodule.c
Modules/timemodule.c

index 34772503003e63eb3dda9997d5cce7165f384686..32813b98b70f9b39f30a00ac5a0a652a831e821e 100644 (file)
@@ -1489,9 +1489,31 @@ For :class:`time` objects, the format codes for year, month, and day should not
 be used, as time objects have no such values.  If they're used anyway, ``1900``
 is substituted for the year, and ``0`` for the month and day.
 
-For :class:`date` objects, the format codes for hours, minutes, and seconds
-should not be used, as :class:`date` objects have no such values.  If they're
-used anyway, ``0`` is substituted for them.
+For :class:`date` objects, the format codes for hours, minutes, seconds, and
+microseconds should not be used, as :class:`date` objects have no such
+values.  If they're used anyway, ``0`` is substituted for them.
+
+:class:`time` and :class:`datetime` objects support a ``%f`` format code
+which expands to the number of microseconds in the object, zero-padded on
+the left to six places.
+
+.. versionadded:: 2.6
+
+For a naive object, the ``%z`` and ``%Z`` format codes are replaced by empty
+strings.
+
+For an aware object:
+
+``%z``
+   :meth:`utcoffset` is transformed into a 5-character string of the form +HHMM or
+   -HHMM, where HH is a 2-digit string giving the number of UTC offset hours, and
+   MM is a 2-digit string giving the number of UTC offset minutes.  For example, if
+   :meth:`utcoffset` returns ``timedelta(hours=-3, minutes=-30)``, ``%z`` is
+   replaced with the string ``'-0330'``.
+
+``%Z``
+   If :meth:`tzname` returns ``None``, ``%Z`` is replaced by an empty string.
+   Otherwise ``%Z`` is replaced by the returned value, which must be a string.
 
 The full set of format codes supported varies across platforms, because Python
 calls the platform C library's :func:`strftime` function, and platform
@@ -1524,6 +1546,10 @@ platforms.  Regardless of platform, years before 1900 cannot be used.
 | ``%d``    | Day of the month as a decimal  |       |
 |           | number [01,31].                |       |
 +-----------+--------------------------------+-------+
+| ``%f``    | Microsecond as a decimal       | \(1)  |
+|           | number [0,999999], zero-padded |       |
+|           | on the left                    |       |
++-----------+--------------------------------+-------+
 | ``%H``    | Hour (24-hour clock) as a      |       |
 |           | decimal number [00,23].        |       |
 +-----------+--------------------------------+-------+
@@ -1539,13 +1565,13 @@ platforms.  Regardless of platform, years before 1900 cannot be used.
 | ``%M``    | Minute as a decimal number     |       |
 |           | [00,59].                       |       |
 +-----------+--------------------------------+-------+
-| ``%p``    | Locale's equivalent of either  | \(1)  |
+| ``%p``    | Locale's equivalent of either  | \(2)  |
 |           | AM or PM.                      |       |
 +-----------+--------------------------------+-------+
-| ``%S``    | Second as a decimal number     | \(2)  |
+| ``%S``    | Second as a decimal number     | \(3)  |
 |           | [00,61].                       |       |
 +-----------+--------------------------------+-------+
-| ``%U``    | Week number of the year        | \(3)  |
+| ``%U``    | Week number of the year        | \(4)  |
 |           | (Sunday as the first day of    |       |
 |           | the week) as a decimal number  |       |
 |           | [00,53].  All days in a new    |       |
@@ -1556,7 +1582,7 @@ platforms.  Regardless of platform, years before 1900 cannot be used.
 | ``%w``    | Weekday as a decimal number    |       |
 |           | [0(Sunday),6].                 |       |
 +-----------+--------------------------------+-------+
-| ``%W``    | Week number of the year        | \(3)  |
+| ``%W``    | Week number of the year        | \(4)  |
 |           | (Monday as the first day of    |       |
 |           | the week) as a decimal number  |       |
 |           | [00,53].  All days in a new    |       |
@@ -1576,7 +1602,7 @@ platforms.  Regardless of platform, years before 1900 cannot be used.
 | ``%Y``    | Year with century as a decimal |       |
 |           | number.                        |       |
 +-----------+--------------------------------+-------+
-| ``%z``    | UTC offset in the form +HHMM   | \(4)  |
+| ``%z``    | UTC offset in the form +HHMM   | \(5)  |
 |           | or -HHMM (empty string if the  |       |
 |           | the object is naive).          |       |
 +-----------+--------------------------------+-------+
@@ -1589,17 +1615,22 @@ platforms.  Regardless of platform, years before 1900 cannot be used.
 Notes:
 
 (1)
+   When used with the :func:`strptime` function, the ``%f`` directive
+   accepts from one to six digits and zero pads on the right.  ``%f`` is
+   an extension to the set of format characters in the C standard.
+
+(2)
    When used with the :func:`strptime` function, the ``%p`` directive only affects
    the output hour field if the ``%I`` directive is used to parse the hour.
 
-(2)
+(3)
    The range really is ``0`` to ``61``; this accounts for leap seconds and the
    (very rare) double leap seconds.
 
-(3)
+(4)
    When used with the :func:`strptime` function, ``%U`` and ``%W`` are only used in
    calculations when the day of the week and the year are specified.
 
-(4)
+(5)
    For example, if :meth:`utcoffset` returns ``timedelta(hours=-3, minutes=-30)``,
    ``%z`` is replaced with the string ``'-0330'``.
index 166cf82203656e57a5f2e128444db0d9b715dd3a..d9563b9e2b81a2368a9bad7fe68a2142e44c53a6 100644 (file)
@@ -22,7 +22,7 @@ try:
 except:
     from dummy_thread import allocate_lock as _thread_allocate_lock
 
-__all__ = ['strptime']
+__all__ = []
 
 def _getlang():
     # Figure out what the current language is set to.
@@ -190,6 +190,7 @@ class TimeRE(dict):
         base.__init__({
             # The " \d" part of the regex is to make %c from ANSI C work
             'd': r"(?P<d>3[0-1]|[1-2]\d|0[1-9]|[1-9]| [1-9])",
+            'f': r"(?P<f>[0-9]{1,6})",
             'H': r"(?P<H>2[0-3]|[0-1]\d|\d)",
             'I': r"(?P<I>1[0-2]|0[1-9]|[1-9])",
             '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])",
@@ -291,7 +292,7 @@ def _calc_julian_from_U_or_W(year, week_of_year, day_of_week, week_starts_Mon):
         return 1 + days_to_week + day_of_week
 
 
-def strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
+def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
     """Return a time struct based on the input string and the format string."""
     global _TimeRE_cache, _regex_cache
     with _cache_lock:
@@ -327,7 +328,7 @@ def strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
                           data_string[found.end():])
     year = 1900
     month = day = 1
-    hour = minute = second = 0
+    hour = minute = second = fraction = 0
     tz = -1
     # Default to -1 to signify that values not known; not critical to have,
     # though
@@ -384,6 +385,11 @@ def strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
             minute = int(found_dict['M'])
         elif group_key == 'S':
             second = int(found_dict['S'])
+        elif group_key == 'f':
+            s = found_dict['f']
+            # Pad to always return microseconds.
+            s += "0" * (6 - len(s))
+            fraction = int(s)
         elif group_key == 'A':
             weekday = locale_time.f_weekday.index(found_dict['A'].lower())
         elif group_key == 'a':
@@ -440,6 +446,9 @@ def strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
         day = datetime_result.day
     if weekday == -1:
         weekday = datetime_date(year, month, day).weekday()
-    return time.struct_time((year, month, day,
-                             hour, minute, second,
-                             weekday, julian, tz))
+    return (time.struct_time((year, month, day,
+                              hour, minute, second,
+                              weekday, julian, tz)), fraction)
+
+def _strptime_time(data_string, format="%a %b %d %H:%M:%S %Y"):
+    return _strptime(data_string, format)[0]
index ee104e45ed5813c135763b574764a76b05ad7bdb..84f45b46824d8e4144d43f3f722365aab1170260 100644 (file)
@@ -1507,11 +1507,12 @@ class TestDateTime(TestDate):
         self.failUnless(abs(from_timestamp - from_now) <= tolerance)
 
     def test_strptime(self):
-        import time
+        import _strptime
 
-        string = '2004-12-01 13:02:47'
-        format = '%Y-%m-%d %H:%M:%S'
-        expected = self.theclass(*(time.strptime(string, format)[0:6]))
+        string = '2004-12-01 13:02:47.197'
+        format = '%Y-%m-%d %H:%M:%S.%f'
+        result, frac = _strptime._strptime(string, format)
+        expected = self.theclass(*(result[0:6]+(frac,)))
         got = self.theclass.strptime(string, format)
         self.assertEqual(expected, got)
 
@@ -1539,9 +1540,9 @@ class TestDateTime(TestDate):
 
     def test_more_strftime(self):
         # This tests fields beyond those tested by the TestDate.test_strftime.
-        t = self.theclass(2004, 12, 31, 6, 22, 33)
-        self.assertEqual(t.strftime("%m %d %y %S %M %H %j"),
-                                    "12 31 04 33 22 06 366")
+        t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
+        self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
+                                    "12 31 04 000047 33 22 06 366")
 
     def test_extract(self):
         dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
@@ -1814,7 +1815,7 @@ class TestTime(HarmlessMixedComparison, unittest.TestCase):
 
     def test_strftime(self):
         t = self.theclass(1, 2, 3, 4)
-        self.assertEqual(t.strftime('%H %M %S'), "01 02 03")
+        self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
         # A naive object replaces %z and %Z with empty strings.
         self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
 
index 92c722ab1551ed65f7cec2314ee9236030d45e0f..2aa4bc90246bffb48af5db3589dda79497b9dba5 100644 (file)
@@ -208,11 +208,11 @@ class StrptimeTests(unittest.TestCase):
 
     def test_ValueError(self):
         # Make sure ValueError is raised when match fails or format is bad
-        self.assertRaises(ValueError, _strptime.strptime, data_string="%d",
+        self.assertRaises(ValueError, _strptime._strptime_time, data_string="%d",
                           format="%A")
         for bad_format in ("%", "% ", "%e"):
             try:
-                _strptime.strptime("2005", bad_format)
+                _strptime._strptime_time("2005", bad_format)
             except ValueError:
                 continue
             except Exception, err:
@@ -223,12 +223,12 @@ class StrptimeTests(unittest.TestCase):
 
     def test_unconverteddata(self):
         # Check ValueError is raised when there is unconverted data
-        self.assertRaises(ValueError, _strptime.strptime, "10 12", "%m")
+        self.assertRaises(ValueError, _strptime._strptime_time, "10 12", "%m")
 
     def helper(self, directive, position):
         """Helper fxn in testing."""
         strf_output = time.strftime("%" + directive, self.time_tuple)
-        strp_output = _strptime.strptime(strf_output, "%" + directive)
+        strp_output = _strptime._strptime_time(strf_output, "%" + directive)
         self.failUnless(strp_output[position] == self.time_tuple[position],
                         "testing of '%s' directive failed; '%s' -> %s != %s" %
                          (directive, strf_output, strp_output[position],
@@ -241,7 +241,7 @@ class StrptimeTests(unittest.TestCase):
         # Must also make sure %y values are correct for bounds set by Open Group
         for century, bounds in ((1900, ('69', '99')), (2000, ('00', '68'))):
             for bound in bounds:
-                strp_output = _strptime.strptime(bound, '%y')
+                strp_output = _strptime._strptime_time(bound, '%y')
                 expected_result = century + int(bound)
                 self.failUnless(strp_output[0] == expected_result,
                                 "'y' test failed; passed in '%s' "
@@ -260,7 +260,7 @@ class StrptimeTests(unittest.TestCase):
         # Test hour directives
         self.helper('H', 3)
         strf_output = time.strftime("%I %p", self.time_tuple)
-        strp_output = _strptime.strptime(strf_output, "%I %p")
+        strp_output = _strptime._strptime_time(strf_output, "%I %p")
         self.failUnless(strp_output[3] == self.time_tuple[3],
                         "testing of '%%I %%p' directive failed; '%s' -> %s != %s" %
                          (strf_output, strp_output[3], self.time_tuple[3]))
@@ -273,6 +273,12 @@ class StrptimeTests(unittest.TestCase):
         # Test second directives
         self.helper('S', 5)
 
+    def test_fraction(self):
+        import datetime
+        now = datetime.datetime.now()
+        tup, frac = _strptime._strptime(str(now), format="%Y-%m-%d %H:%M:%S.%f")
+        self.assertEqual(frac, now.microsecond)
+
     def test_weekday(self):
         # Test weekday directives
         for directive in ('A', 'a', 'w'):
@@ -287,16 +293,16 @@ class StrptimeTests(unittest.TestCase):
         # When gmtime() is used with %Z, entire result of strftime() is empty.
         # Check for equal timezone names deals with bad locale info when this
         # occurs; first found in FreeBSD 4.4.
-        strp_output = _strptime.strptime("UTC", "%Z")
+        strp_output = _strptime._strptime_time("UTC", "%Z")
         self.failUnlessEqual(strp_output.tm_isdst, 0)
-        strp_output = _strptime.strptime("GMT", "%Z")
+        strp_output = _strptime._strptime_time("GMT", "%Z")
         self.failUnlessEqual(strp_output.tm_isdst, 0)
         if sys.platform == "mac":
             # Timezones don't really work on MacOS9
             return
         time_tuple = time.localtime()
         strf_output = time.strftime("%Z")  #UTC does not have a timezone
-        strp_output = _strptime.strptime(strf_output, "%Z")
+        strp_output = _strptime._strptime_time(strf_output, "%Z")
         locale_time = _strptime.LocaleTime()
         if time.tzname[0] != time.tzname[1] or not time.daylight:
             self.failUnless(strp_output[8] == time_tuple[8],
@@ -320,7 +326,7 @@ class StrptimeTests(unittest.TestCase):
             original_daylight = time.daylight
             time.tzname = (tz_name, tz_name)
             time.daylight = 1
-            tz_value = _strptime.strptime(tz_name, "%Z")[8]
+            tz_value = _strptime._strptime_time(tz_name, "%Z")[8]
             self.failUnlessEqual(tz_value, -1,
                     "%s lead to a timezone value of %s instead of -1 when "
                     "time.daylight set to %s and passing in %s" %
@@ -347,7 +353,7 @@ class StrptimeTests(unittest.TestCase):
     def test_percent(self):
         # Make sure % signs are handled properly
         strf_output = time.strftime("%m %% %Y", self.time_tuple)
-        strp_output = _strptime.strptime(strf_output, "%m %% %Y")
+        strp_output = _strptime._strptime_time(strf_output, "%m %% %Y")
         self.failUnless(strp_output[0] == self.time_tuple[0] and
                          strp_output[1] == self.time_tuple[1],
                         "handling of percent sign failed")
@@ -355,17 +361,17 @@ class StrptimeTests(unittest.TestCase):
     def test_caseinsensitive(self):
         # Should handle names case-insensitively.
         strf_output = time.strftime("%B", self.time_tuple)
-        self.failUnless(_strptime.strptime(strf_output.upper(), "%B"),
+        self.failUnless(_strptime._strptime_time(strf_output.upper(), "%B"),
                         "strptime does not handle ALL-CAPS names properly")
-        self.failUnless(_strptime.strptime(strf_output.lower(), "%B"),
+        self.failUnless(_strptime._strptime_time(strf_output.lower(), "%B"),
                         "strptime does not handle lowercase names properly")
-        self.failUnless(_strptime.strptime(strf_output.capitalize(), "%B"),
+        self.failUnless(_strptime._strptime_time(strf_output.capitalize(), "%B"),
                         "strptime does not handle capword names properly")
 
     def test_defaults(self):
         # Default return value should be (1900, 1, 1, 0, 0, 0, 0, 1, 0)
         defaults = (1900, 1, 1, 0, 0, 0, 0, 1, -1)
-        strp_output = _strptime.strptime('1', '%m')
+        strp_output = _strptime._strptime_time('1', '%m')
         self.failUnless(strp_output == defaults,
                         "Default values for strptime() are incorrect;"
                         " %s != %s" % (strp_output, defaults))
@@ -377,7 +383,7 @@ class StrptimeTests(unittest.TestCase):
         # escaped.
         # Test instigated by bug #796149 .
         need_escaping = ".^$*+?{}\[]|)("
-        self.failUnless(_strptime.strptime(need_escaping, need_escaping))
+        self.failUnless(_strptime._strptime_time(need_escaping, need_escaping))
 
 class Strptime12AMPMTests(unittest.TestCase):
     """Test a _strptime regression in '%I %p' at 12 noon (12 PM)"""
@@ -386,8 +392,8 @@ class Strptime12AMPMTests(unittest.TestCase):
         eq = self.assertEqual
         eq(time.strptime('12 PM', '%I %p')[3], 12)
         eq(time.strptime('12 AM', '%I %p')[3], 0)
-        eq(_strptime.strptime('12 PM', '%I %p')[3], 12)
-        eq(_strptime.strptime('12 AM', '%I %p')[3], 0)
+        eq(_strptime._strptime_time('12 PM', '%I %p')[3], 12)
+        eq(_strptime._strptime_time('12 AM', '%I %p')[3], 0)
 
 
 class JulianTests(unittest.TestCase):
@@ -397,7 +403,7 @@ class JulianTests(unittest.TestCase):
         eq = self.assertEqual
         for i in range(1, 367):
             # use 2004, since it is a leap year, we have 366 days
-            eq(_strptime.strptime('%d 2004' % i, '%j %Y')[7], i)
+            eq(_strptime._strptime_time('%d 2004' % i, '%j %Y')[7], i)
 
 class CalculationTests(unittest.TestCase):
     """Test that strptime() fills in missing info correctly"""
@@ -408,7 +414,7 @@ class CalculationTests(unittest.TestCase):
     def test_julian_calculation(self):
         # Make sure that when Julian is missing that it is calculated
         format_string = "%Y %m %d %H %M %S %w %Z"
-        result = _strptime.strptime(time.strftime(format_string, self.time_tuple),
+        result = _strptime._strptime_time(time.strftime(format_string, self.time_tuple),
                                     format_string)
         self.failUnless(result.tm_yday == self.time_tuple.tm_yday,
                         "Calculation of tm_yday failed; %s != %s" %
@@ -417,7 +423,7 @@ class CalculationTests(unittest.TestCase):
     def test_gregorian_calculation(self):
         # Test that Gregorian date can be calculated from Julian day
         format_string = "%Y %H %M %S %w %j %Z"
-        result = _strptime.strptime(time.strftime(format_string, self.time_tuple),
+        result = _strptime._strptime_time(time.strftime(format_string, self.time_tuple),
                                     format_string)
         self.failUnless(result.tm_year == self.time_tuple.tm_year and
                          result.tm_mon == self.time_tuple.tm_mon and
@@ -431,7 +437,7 @@ class CalculationTests(unittest.TestCase):
     def test_day_of_week_calculation(self):
         # Test that the day of the week is calculated as needed
         format_string = "%Y %m %d %H %S %j %Z"
-        result = _strptime.strptime(time.strftime(format_string, self.time_tuple),
+        result = _strptime._strptime_time(time.strftime(format_string, self.time_tuple),
                                     format_string)
         self.failUnless(result.tm_wday == self.time_tuple.tm_wday,
                         "Calculation of day of the week failed;"
@@ -445,7 +451,7 @@ class CalculationTests(unittest.TestCase):
                 format_string = "%%Y %%%s %%w" % directive
                 dt_date = datetime_date(*ymd_tuple)
                 strp_input = dt_date.strftime(format_string)
-                strp_output = _strptime.strptime(strp_input, format_string)
+                strp_output = _strptime._strptime_time(strp_input, format_string)
                 self.failUnless(strp_output[:3] == ymd_tuple,
                         "%s(%s) test failed w/ '%s': %s != %s (%s != %s)" %
                             (test_reason, directive, strp_input,
@@ -484,11 +490,11 @@ class CacheTests(unittest.TestCase):
     def test_time_re_recreation(self):
         # Make sure cache is recreated when current locale does not match what
         # cached object was created with.
-        _strptime.strptime("10", "%d")
-        _strptime.strptime("2005", "%Y")
+        _strptime._strptime_time("10", "%d")
+        _strptime._strptime_time("2005", "%Y")
         _strptime._TimeRE_cache.locale_time.lang = "Ni"
         original_time_re = id(_strptime._TimeRE_cache)
-        _strptime.strptime("10", "%d")
+        _strptime._strptime_time("10", "%d")
         self.failIfEqual(original_time_re, id(_strptime._TimeRE_cache))
         self.failUnlessEqual(len(_strptime._regex_cache), 1)
 
@@ -502,7 +508,7 @@ class CacheTests(unittest.TestCase):
         while len(_strptime._regex_cache) <= _strptime._CACHE_MAX_SIZE:
             _strptime._regex_cache[bogus_key] = None
             bogus_key += 1
-        _strptime.strptime("10", "%d")
+        _strptime._strptime_time("10", "%d")
         self.failUnlessEqual(len(_strptime._regex_cache), 1)
 
     def test_new_localetime(self):
@@ -510,7 +516,7 @@ class CacheTests(unittest.TestCase):
         # is created.
         locale_time_id = id(_strptime._TimeRE_cache.locale_time)
         _strptime._TimeRE_cache.locale_time.lang = "Ni"
-        _strptime.strptime("10", "%d")
+        _strptime._strptime_time("10", "%d")
         self.failIfEqual(locale_time_id,
                          id(_strptime._TimeRE_cache.locale_time))
 
@@ -522,13 +528,13 @@ class CacheTests(unittest.TestCase):
         except locale.Error:
             return
         try:
-            _strptime.strptime('10', '%d')
+            _strptime._strptime_time('10', '%d')
             # Get id of current cache object.
             first_time_re_id = id(_strptime._TimeRE_cache)
             try:
                 # Change the locale and force a recreation of the cache.
                 locale.setlocale(locale.LC_TIME, ('de_DE', 'UTF8'))
-                _strptime.strptime('10', '%d')
+                _strptime._strptime_time('10', '%d')
                 # Get the new cache object's id.
                 second_time_re_id = id(_strptime._TimeRE_cache)
                 # They should not be equal.
index fb11e35cb6c3d2faefc8c4c50b5d279d84d2183a..89423abc6260ec75a3b1cdf43924579d160da1c3 100644 (file)
@@ -1130,10 +1130,24 @@ format_utcoffset(char *buf, size_t buflen, const char *sep,
        return 0;
 }
 
+static PyObject *
+make_freplacement(PyObject *object)
+{
+       char freplacement[7];
+       if (PyTime_Check(object))
+           sprintf(freplacement, "%06d", TIME_GET_MICROSECOND(object));
+       else if (PyDateTime_Check(object))
+           sprintf(freplacement, "%06d", DATE_GET_MICROSECOND(object));
+       else
+           sprintf(freplacement, "%06d", 0);
+
+       return PyString_FromStringAndSize(freplacement, strlen(freplacement));
+}
+
 /* I sure don't want to reproduce the strftime code from the time module,
  * so this imports the module and calls it.  All the hair is due to
- * giving special meanings to the %z and %Z format codes via a preprocessing
- * step on the format string.
+ * giving special meanings to the %z, %Z and %f format codes via a
+ * preprocessing step on the format string.
  * tzinfoarg is the argument to pass to the object's tzinfo method, if
  * needed.
  */
@@ -1145,6 +1159,7 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
 
        PyObject *zreplacement = NULL;  /* py string, replacement for %z */
        PyObject *Zreplacement = NULL;  /* py string, replacement for %Z */
+       PyObject *freplacement = NULL;  /* py string, replacement for %f */
 
        char *pin;      /* pointer to next char in input format */
        char ch;        /* next char in input format */
@@ -1186,11 +1201,11 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
                }
        }
 
-       /* Scan the input format, looking for %z and %Z escapes, building
+       /* Scan the input format, looking for %z/%Z/%f escapes, building
         * a new format.  Since computing the replacements for those codes
         * is expensive, don't unless they're actually used.
         */
-       totalnew = PyString_Size(format) + 1;   /* realistic if no %z/%Z */
+       totalnew = PyString_Size(format) + 1;   /* realistic if no %z/%Z/%f */
        newfmt = PyString_FromStringAndSize(NULL, totalnew);
        if (newfmt == NULL) goto Done;
        pnew = PyString_AsString(newfmt);
@@ -1272,6 +1287,18 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
                        ptoappend = PyString_AS_STRING(Zreplacement);
                        ntoappend = PyString_GET_SIZE(Zreplacement);
                }
+               else if (ch == 'f') {
+                       /* format microseconds */
+                       if (freplacement == NULL) {
+                               freplacement = make_freplacement(object);
+                               if (freplacement == NULL)
+                                       goto Done;
+                       }
+                       assert(freplacement != NULL);
+                       assert(PyString_Check(freplacement));
+                       ptoappend = PyString_AS_STRING(freplacement);
+                       ntoappend = PyString_GET_SIZE(freplacement);
+               }
                else {
                        /* percent followed by neither z nor Z */
                        ptoappend = pin - 2;
@@ -1313,6 +1340,7 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
                Py_DECREF(time);
        }
  Done:
+       Py_XDECREF(freplacement);
        Py_XDECREF(zreplacement);
        Py_XDECREF(Zreplacement);
        Py_XDECREF(newfmt);
@@ -3853,43 +3881,69 @@ datetime_utcfromtimestamp(PyObject *cls, PyObject *args)
 static PyObject *
 datetime_strptime(PyObject *cls, PyObject *args)
 {
-       PyObject *result = NULL, *obj, *module;
+       static PyObject *module = NULL;
+       PyObject *result = NULL, *obj, *st = NULL, *frac = NULL;
        const char *string, *format;
 
        if (!PyArg_ParseTuple(args, "ss:strptime", &string, &format))
                return NULL;
 
-       if ((module = PyImport_ImportModuleNoBlock("time")) == NULL)
+       if (module == NULL &&
+           (module = PyImport_ImportModuleNoBlock("_strptime")) == NULL)
                return NULL;
-       obj = PyObject_CallMethod(module, "strptime", "ss", string, format);
-       Py_DECREF(module);
 
+       /* _strptime._strptime returns a two-element tuple.  The first
+          element is a time.struct_time object.  The second is the
+          microseconds (which are not defined for time.struct_time). */
+       obj = PyObject_CallMethod(module, "_strptime", "ss", string, format);
        if (obj != NULL) {
                int i, good_timetuple = 1;
-               long int ia[6];
-               if (PySequence_Check(obj) && PySequence_Size(obj) >= 6)
-                       for (i=0; i < 6; i++) {
-                               PyObject *p = PySequence_GetItem(obj, i);
-                               if (p == NULL) {
-                                       Py_DECREF(obj);
-                                       return NULL;
+               long int ia[7];
+               if (PySequence_Check(obj) && PySequence_Size(obj) == 2) {
+                       st = PySequence_GetItem(obj, 0);
+                       frac = PySequence_GetItem(obj, 1);
+                       if (st == NULL || frac == NULL)
+                               good_timetuple = 0;
+                       /* copy y/m/d/h/m/s values out of the
+                          time.struct_time */
+                       if (good_timetuple &&
+                           PySequence_Check(st) &&
+                           PySequence_Size(st) >= 6) {
+                               for (i=0; i < 6; i++) {
+                                       PyObject *p = PySequence_GetItem(st, i);
+                                       if (p == NULL) {
+                                               good_timetuple = 0;
+                                               break;
+                                       }
+                                       if (PyInt_Check(p))
+                                               ia[i] = PyInt_AsLong(p);
+                                       else
+                                               good_timetuple = 0;
+                                       Py_DECREF(p);
                                }
-                               if (PyInt_Check(p))
-                                       ia[i] = PyInt_AsLong(p);
-                               else
-                                       good_timetuple = 0;
-                               Py_DECREF(p);
                        }
+                       else
+                               good_timetuple = 0;
+                       /* follow that up with a little dose of microseconds */
+                       if (PyInt_Check(frac))
+                               ia[6] = PyInt_AsLong(frac);
+                       else
+                               good_timetuple = 0;
+               }
                else
                        good_timetuple = 0;
                if (good_timetuple)
-                       result = PyObject_CallFunction(cls, "iiiiii",
-                               ia[0], ia[1], ia[2], ia[3], ia[4], ia[5]);
+                       result = PyObject_CallFunction(cls, "iiiiiii",
+                                                      ia[0], ia[1], ia[2],
+                                                      ia[3], ia[4], ia[5],
+                                                      ia[6]);
                else
                        PyErr_SetString(PyExc_ValueError,
-                               "unexpected value from time.strptime");
-               Py_DECREF(obj);
+                               "unexpected value from _strptime._strptime");
        }
+       Py_XDECREF(obj);
+       Py_XDECREF(st);
+       Py_XDECREF(frac);
        return result;
 }
 
index 5b4d21031fcea0ef4e186b66c5179d9b53b56915..bd1ad06f49f48ee7bcb2e268673c09ba137f5d05 100644 (file)
@@ -520,7 +520,7 @@ time_strptime(PyObject *self, PyObject *args)
 
     if (!strptime_module)
         return NULL;
-    strptime_result = PyObject_CallMethod(strptime_module, "strptime", "O", args);
+    strptime_result = PyObject_CallMethod(strptime_module, "_strptime_time", "O", args);
     Py_DECREF(strptime_module);
     return strptime_result;
 }