add %f format to datetime - issue 1158
This commit is contained in:
parent
75e51680f1
commit
fc070d2731
|
@ -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'``.
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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'"), "'' ''")
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue