mirror of
https://github.com/python/cpython.git
synced 2025-11-01 02:38:53 +00:00
gh-41431: Add datetime.time.strptime() and datetime.date.strptime() (#120752)
* Python implementation * C implementation * Test `date.strptime` * Test `time.strptime` * 📜🤖 Added by blurb_it. * Update whatsnew * Update documentation * Add leap year note * Update 2024-06-19-19-53-42.gh-issue-41431.gnkUc5.rst * Apply suggestions from code review Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> * Remove parentheses * Use helper function * Remove bad return * Link to github issue * Fix directive * Apply suggestions from code review Co-authored-by: Paul Ganssle <1377457+pganssle@users.noreply.github.com> * Fix test cases --------- Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> Co-authored-by: Paul Ganssle <1377457+pganssle@users.noreply.github.com>
This commit is contained in:
parent
b0c6cf5f17
commit
9968caa0cc
11 changed files with 350 additions and 36 deletions
|
|
@ -1106,6 +1106,85 @@ class TestDateOnly(unittest.TestCase):
|
|||
dt2 = dt - delta
|
||||
self.assertEqual(dt2, dt - days)
|
||||
|
||||
def test_strptime(self):
|
||||
inputs = [
|
||||
# Basic valid cases
|
||||
(date(1998, 2, 3), '1998-02-03', '%Y-%m-%d'),
|
||||
(date(2004, 12, 2), '2004-12-02', '%Y-%m-%d'),
|
||||
|
||||
# Edge cases: Leap year
|
||||
(date(2020, 2, 29), '2020-02-29', '%Y-%m-%d'), # Valid leap year date
|
||||
|
||||
# bpo-34482: Handle surrogate pairs
|
||||
(date(2004, 12, 2), '2004-12\ud80002', '%Y-%m\ud800%d'),
|
||||
(date(2004, 12, 2), '2004\ud80012-02', '%Y\ud800%m-%d'),
|
||||
|
||||
# Month/day variations
|
||||
(date(2004, 2, 1), '2004-02', '%Y-%m'), # No day provided
|
||||
(date(2004, 2, 1), '02-2004', '%m-%Y'), # Month and year swapped
|
||||
|
||||
# Different day-month-year formats
|
||||
(date(2004, 12, 2), '02/12/2004', '%d/%m/%Y'), # Day/Month/Year
|
||||
(date(2004, 12, 2), '12/02/2004', '%m/%d/%Y'), # Month/Day/Year
|
||||
|
||||
# Different separators
|
||||
(date(2023, 9, 24), '24.09.2023', '%d.%m.%Y'), # Dots as separators
|
||||
(date(2023, 9, 24), '24-09-2023', '%d-%m-%Y'), # Dashes
|
||||
(date(2023, 9, 24), '2023/09/24', '%Y/%m/%d'), # Slashes
|
||||
|
||||
# Handling years with fewer digits
|
||||
(date(127, 2, 3), '0127-02-03', '%Y-%m-%d'),
|
||||
(date(99, 2, 3), '0099-02-03', '%Y-%m-%d'),
|
||||
(date(5, 2, 3), '0005-02-03', '%Y-%m-%d'),
|
||||
|
||||
# Variations on ISO 8601 format
|
||||
(date(2023, 9, 25), '2023-W39-1', '%G-W%V-%u'), # ISO week date (Week 39, Monday)
|
||||
(date(2023, 9, 25), '2023-268', '%Y-%j'), # Year and day of the year (Julian)
|
||||
]
|
||||
for expected, string, format in inputs:
|
||||
with self.subTest(string=string, format=format):
|
||||
got = date.strptime(string, format)
|
||||
self.assertEqual(expected, got)
|
||||
self.assertIs(type(got), date)
|
||||
|
||||
def test_strptime_single_digit(self):
|
||||
# bpo-34903: Check that single digit dates are allowed.
|
||||
strptime = date.strptime
|
||||
with self.assertRaises(ValueError):
|
||||
# %y does require two digits.
|
||||
newdate = strptime('01/02/3', '%d/%m/%y')
|
||||
|
||||
d1 = date(2003, 2, 1)
|
||||
d2 = date(2003, 1, 2)
|
||||
d3 = date(2003, 1, 25)
|
||||
inputs = [
|
||||
('%d', '1/02/03', '%d/%m/%y', d1),
|
||||
('%m', '01/2/03', '%d/%m/%y', d1),
|
||||
('%j', '2/03', '%j/%y', d2),
|
||||
('%w', '6/04/03', '%w/%U/%y', d1),
|
||||
# %u requires a single digit.
|
||||
('%W', '6/4/2003', '%u/%W/%Y', d1),
|
||||
('%V', '6/4/2003', '%u/%V/%G', d3),
|
||||
]
|
||||
for reason, string, format, target in inputs:
|
||||
reason = 'test single digit ' + reason
|
||||
with self.subTest(reason=reason,
|
||||
string=string,
|
||||
format=format,
|
||||
target=target):
|
||||
newdate = strptime(string, format)
|
||||
self.assertEqual(newdate, target, msg=reason)
|
||||
|
||||
@warnings_helper.ignore_warnings(category=DeprecationWarning)
|
||||
def test_strptime_leap_year(self):
|
||||
# GH-70647: warns if parsing a format with a day and no year.
|
||||
with self.assertRaises(ValueError):
|
||||
# The existing behavior that GH-70647 seeks to change.
|
||||
date.strptime('02-29', '%m-%d')
|
||||
with self._assertNotWarns(DeprecationWarning):
|
||||
date.strptime('20-03-14', '%y-%m-%d')
|
||||
date.strptime('02-29,2024', '%m-%d,%Y')
|
||||
|
||||
class SubclassDate(date):
|
||||
sub_var = 1
|
||||
|
||||
|
|
@ -2732,7 +2811,8 @@ class TestDateTime(TestDate):
|
|||
def test_strptime(self):
|
||||
string = '2004-12-01 13:02:47.197'
|
||||
format = '%Y-%m-%d %H:%M:%S.%f'
|
||||
expected = _strptime._strptime_datetime(self.theclass, string, format)
|
||||
expected = _strptime._strptime_datetime_datetime(self.theclass, string,
|
||||
format)
|
||||
got = self.theclass.strptime(string, format)
|
||||
self.assertEqual(expected, got)
|
||||
self.assertIs(type(expected), self.theclass)
|
||||
|
|
@ -2746,8 +2826,8 @@ class TestDateTime(TestDate):
|
|||
]
|
||||
for string, format in inputs:
|
||||
with self.subTest(string=string, format=format):
|
||||
expected = _strptime._strptime_datetime(self.theclass, string,
|
||||
format)
|
||||
expected = _strptime._strptime_datetime_datetime(self.theclass,
|
||||
string, format)
|
||||
got = self.theclass.strptime(string, format)
|
||||
self.assertEqual(expected, got)
|
||||
|
||||
|
|
@ -3749,6 +3829,78 @@ class TestTime(HarmlessMixedComparison, unittest.TestCase):
|
|||
derived = loads(data, encoding='latin1')
|
||||
self.assertEqual(derived, expected)
|
||||
|
||||
def test_strptime(self):
|
||||
# bpo-34482: Check that surrogates are handled properly.
|
||||
inputs = [
|
||||
(self.theclass(13, 2, 47, 197000), '13:02:47.197', '%H:%M:%S.%f'),
|
||||
(self.theclass(13, 2, 47, 197000), '13:02\ud80047.197', '%H:%M\ud800%S.%f'),
|
||||
(self.theclass(13, 2, 47, 197000), '13\ud80002:47.197', '%H\ud800%M:%S.%f'),
|
||||
]
|
||||
for expected, string, format in inputs:
|
||||
with self.subTest(string=string, format=format):
|
||||
got = self.theclass.strptime(string, format)
|
||||
self.assertEqual(expected, got)
|
||||
self.assertIs(type(got), self.theclass)
|
||||
|
||||
def test_strptime_tz(self):
|
||||
strptime = self.theclass.strptime
|
||||
self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
|
||||
self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
|
||||
self.assertEqual(
|
||||
strptime("-00:02:01.000003", "%z").utcoffset(),
|
||||
-timedelta(minutes=2, seconds=1, microseconds=3)
|
||||
)
|
||||
# Only local timezone and UTC are supported
|
||||
for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
|
||||
(-_time.timezone, _time.tzname[0])):
|
||||
if tzseconds < 0:
|
||||
sign = '-'
|
||||
seconds = -tzseconds
|
||||
else:
|
||||
sign ='+'
|
||||
seconds = tzseconds
|
||||
hours, minutes = divmod(seconds//60, 60)
|
||||
tstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
|
||||
with self.subTest(tstr=tstr):
|
||||
t = strptime(tstr, "%z %Z")
|
||||
self.assertEqual(t.utcoffset(), timedelta(seconds=tzseconds))
|
||||
self.assertEqual(t.tzname(), tzname)
|
||||
self.assertIs(type(t), self.theclass)
|
||||
|
||||
# Can produce inconsistent time
|
||||
tstr, fmt = "+1234 UTC", "%z %Z"
|
||||
t = strptime(tstr, fmt)
|
||||
self.assertEqual(t.utcoffset(), 12 * HOUR + 34 * MINUTE)
|
||||
self.assertEqual(t.tzname(), 'UTC')
|
||||
# yet will roundtrip
|
||||
self.assertEqual(t.strftime(fmt), tstr)
|
||||
|
||||
# Produce naive time if no %z is provided
|
||||
self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
|
||||
|
||||
def test_strptime_errors(self):
|
||||
for tzstr in ("-2400", "-000", "z"):
|
||||
with self.assertRaises(ValueError):
|
||||
self.theclass.strptime(tzstr, "%z")
|
||||
|
||||
def test_strptime_single_digit(self):
|
||||
# bpo-34903: Check that single digit times are allowed.
|
||||
t = self.theclass(4, 5, 6)
|
||||
inputs = [
|
||||
('%H', '4:05:06', '%H:%M:%S', t),
|
||||
('%M', '04:5:06', '%H:%M:%S', t),
|
||||
('%S', '04:05:6', '%H:%M:%S', t),
|
||||
('%I', '4am:05:06', '%I%p:%M:%S', t),
|
||||
]
|
||||
for reason, string, format, target in inputs:
|
||||
reason = 'test single digit ' + reason
|
||||
with self.subTest(reason=reason,
|
||||
string=string,
|
||||
format=format,
|
||||
target=target):
|
||||
newdate = self.theclass.strptime(string, format)
|
||||
self.assertEqual(newdate, target, msg=reason)
|
||||
|
||||
def test_bool(self):
|
||||
# time is always True.
|
||||
cls = self.theclass
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue