Update datetime to 3.14.3 (#7084)

This commit is contained in:
Shahar Naveh authored and GitHub committed 2026-02-24 01:07:30 +00:00
1 parent 061c709434
commit d412c10e6b
5 files changed
+582 -129

No files matched your search

+138 -54
View File
@@ -1,12 +1,10 @@
"""Concrete date/time and related types.
See http://www.iana.org/time-zones/repository/tz-link.html for
time zone and DST data sources.
"""
"""Pure Python implementation of the datetime module."""
__all__ = ("date", "datetime", "time", "timedelta", "timezone", "tzinfo",
"MINYEAR", "MAXYEAR", "UTC")
__name__ = "datetime"
import time as _time
import math as _math
@@ -18,10 +16,10 @@ def _cmp(x, y):
def _get_class_module(self):
module_name = self.__class__.__module__
if module_name == '_pydatetime':
return 'datetime'
if module_name == 'datetime':
return 'datetime.'
else:
return module_name
return ''
MINYEAR = 1
MAXYEAR = 9999
@@ -64,14 +62,14 @@ def _days_in_month(year, month):
def _days_before_month(year, month):
"year, month -> number of days in year preceding first day of month."
assert 1 <= month <= 12, 'month must be in 1..12'
assert 1 <= month <= 12, f"month must be in 1..12, not {month}"
return _DAYS_BEFORE_MONTH[month] + (month > 2 and _is_leap(year))
def _ymd2ord(year, month, day):
"year, month, day -> ordinal, considering 01-Jan-0001 as day 1."
assert 1 <= month <= 12, 'month must be in 1..12'
assert 1 <= month <= 12, f"month must be in 1..12, not {month}"
dim = _days_in_month(year, month)
assert 1 <= day <= dim, ('day must be in 1..%d' % dim)
assert 1 <= day <= dim, f"day must be in 1..{dim}, not {day}"
return (_days_before_year(year) +
_days_before_month(year, month) +
day)
@@ -204,6 +202,17 @@ def _format_offset(off, sep=':'):
s += '.%06d' % ss.microseconds
return s
_normalize_century = None
def _need_normalize_century():
global _normalize_century
if _normalize_century is None:
try:
_normalize_century = (
_time.strftime("%Y", (99, 1, 1, 0, 0, 0, 0, 1, 0)) != "0099")
except ValueError:
_normalize_century = True
return _normalize_century
# Correctly substitute for %z and %Z escapes in strftime formats.
def _wrap_strftime(object, format, timetuple):
# Don't call utcoffset() or tzname() unless actually needed.
@@ -261,6 +270,20 @@ def _wrap_strftime(object, format, timetuple):
# strftime is going to have at this: escape %
Zreplace = s.replace('%', '%%')
newformat.append(Zreplace)
# Note that datetime(1000, 1, 1).strftime('%G') == '1000' so
# year 1000 for %G can go on the fast path.
elif ((ch in 'YG' or ch in 'FC') and
object.year < 1000 and _need_normalize_century()):
if ch == 'G':
year = int(_time.strftime("%G", timetuple))
else:
year = object.year
if ch == 'C':
push('{:02}'.format(year // 100))
else:
push('{:04}'.format(year))
if ch == 'F':
push('-{:02}-{:02}'.format(*timetuple[1:3]))
else:
push('%')
push(ch)
@@ -399,7 +422,7 @@ def _parse_hh_mm_ss_ff(tstr):
if pos < len_str:
if tstr[pos] not in '.,':
raise ValueError("Invalid microsecond component")
raise ValueError("Invalid microsecond separator")
else:
pos += 1
if not all(map(_is_ascii_digit, tstr[pos:])):
@@ -430,6 +453,17 @@ def _parse_isoformat_time(tstr):
time_comps = _parse_hh_mm_ss_ff(timestr)
hour, minute, second, microsecond = time_comps
became_next_day = False
error_from_components = False
if (hour == 24):
if all(time_comp == 0 for time_comp in time_comps[1:]):
hour = 0
time_comps[0] = hour
became_next_day = True
else:
error_from_components = True
tzi = None
if tz_pos == len_str and tstr[-1] == 'Z':
tzi = timezone.utc
@@ -445,7 +479,7 @@ def _parse_isoformat_time(tstr):
# HH:MM:SS len: 8
# HH:MM:SS.f+ len: 10+
if len(tzstr) in (0, 1, 3):
if len(tzstr) in (0, 1, 3) or tstr[tz_pos-1] == 'Z':
raise ValueError("Malformed time zone string")
tz_comps = _parse_hh_mm_ss_ff(tzstr)
@@ -462,13 +496,13 @@ def _parse_isoformat_time(tstr):
time_comps.append(tzi)
return time_comps
return time_comps, became_next_day, error_from_components
# tuple[int, int, int] -> tuple[int, int, int] version of date.fromisocalendar
def _isoweek_to_gregorian(year, week, day):
# Year is bounded this way because 9999-12-31 is (9999, 52, 5)
if not MINYEAR <= year <= MAXYEAR:
raise ValueError(f"Year is out of range: {year}")
raise ValueError(f"year must be in {MINYEAR}..{MAXYEAR}, not {year}")
if not 0 < week < 53:
out_of_range = True
@@ -501,7 +535,7 @@ def _isoweek_to_gregorian(year, week, day):
def _check_tzname(name):
if name is not None and not isinstance(name, str):
raise TypeError("tzinfo.tzname() must return None or string, "
"not '%s'" % type(name))
f"not {type(name).__name__!r}")
# name is the offset-producing method, "utcoffset" or "dst".
# offset is what it returned.
@@ -514,24 +548,24 @@ def _check_utc_offset(name, offset):
if offset is None:
return
if not isinstance(offset, timedelta):
raise TypeError("tzinfo.%s() must return None "
"or timedelta, not '%s'" % (name, type(offset)))
raise TypeError(f"tzinfo.{name}() must return None "
f"or timedelta, not {type(offset).__name__!r}")
if not -timedelta(1) < offset < timedelta(1):
raise ValueError("%s()=%s, must be strictly between "
"-timedelta(hours=24) and timedelta(hours=24)" %
(name, offset))
raise ValueError("offset must be a timedelta "
"strictly between -timedelta(hours=24) and "
f"timedelta(hours=24), not {offset!r}")
def _check_date_fields(year, month, day):
year = _index(year)
month = _index(month)
day = _index(day)
if not MINYEAR <= year <= MAXYEAR:
raise ValueError('year must be in %d..%d' % (MINYEAR, MAXYEAR), year)
raise ValueError(f"year must be in {MINYEAR}..{MAXYEAR}, not {year}")
if not 1 <= month <= 12:
raise ValueError('month must be in 1..12', month)
raise ValueError(f"month must be in 1..12, not {month}")
dim = _days_in_month(year, month)
if not 1 <= day <= dim:
raise ValueError('day must be in 1..%d' % dim, day)
raise ValueError(f"day {day} must be in range 1..{dim} for month {month} in year {year}")
return year, month, day
def _check_time_fields(hour, minute, second, microsecond, fold):
@@ -540,20 +574,23 @@ def _check_time_fields(hour, minute, second, microsecond, fold):
second = _index(second)
microsecond = _index(microsecond)
if not 0 <= hour <= 23:
raise ValueError('hour must be in 0..23', hour)
raise ValueError(f"hour must be in 0..23, not {hour}")
if not 0 <= minute <= 59:
raise ValueError('minute must be in 0..59', minute)
raise ValueError(f"minute must be in 0..59, not {minute}")
if not 0 <= second <= 59:
raise ValueError('second must be in 0..59', second)
raise ValueError(f"second must be in 0..59, not {second}")
if not 0 <= microsecond <= 999999:
raise ValueError('microsecond must be in 0..999999', microsecond)
raise ValueError(f"microsecond must be in 0..999999, not {microsecond}")
if fold not in (0, 1):
raise ValueError('fold must be either 0 or 1', fold)
raise ValueError(f"fold must be either 0 or 1, not {fold}")
return hour, minute, second, microsecond, fold
def _check_tzinfo_arg(tz):
if tz is not None and not isinstance(tz, tzinfo):
raise TypeError("tzinfo argument must be None or of a tzinfo subclass")
raise TypeError(
"tzinfo argument must be None or of a tzinfo subclass, "
f"not {type(tz).__name__!r}"
)
def _divide_and_round(a, b):
"""divide a by b and round result to the nearest integer
@@ -607,7 +644,19 @@ class timedelta:
# guide the C implementation; it's way more convoluted than speed-
# ignoring auto-overflow-to-long idiomatic Python could be.
# XXX Check that all inputs are ints or floats.
for name, value in (
("days", days),
("seconds", seconds),
("microseconds", microseconds),
("milliseconds", milliseconds),
("minutes", minutes),
("hours", hours),
("weeks", weeks)
):
if not isinstance(value, (int, float)):
raise TypeError(
f"unsupported type for timedelta {name} component: {type(value).__name__}"
)
# Final values, all integer.
# s and us fit in 32-bit signed ints; d isn't bounded.
@@ -708,9 +757,9 @@ class timedelta:
args.append("microseconds=%d" % self._microseconds)
if not args:
args.append('0')
return "%s.%s(%s)" % (_get_class_module(self),
self.__class__.__qualname__,
', '.join(args))
return "%s%s(%s)" % (_get_class_module(self),
self.__class__.__qualname__,
', '.join(args))
def __str__(self):
mm, ss = divmod(self._seconds, 60)
@@ -907,6 +956,7 @@ class date:
fromtimestamp()
today()
fromordinal()
strptime()
Operators:
@@ -989,8 +1039,12 @@ class date:
@classmethod
def fromisoformat(cls, date_string):
"""Construct a date from a string in ISO 8601 format."""
if not isinstance(date_string, str):
raise TypeError('fromisoformat: argument must be str')
raise TypeError('Argument must be a str')
if not date_string.isascii():
raise ValueError('Argument must be an ASCII str')
if len(date_string) not in (7, 8, 10):
raise ValueError(f'Invalid isoformat string: {date_string!r}')
@@ -1007,6 +1061,12 @@ class date:
This is the inverse of the date.isocalendar() function"""
return cls(*_isoweek_to_gregorian(year, week, day))
@classmethod
def strptime(cls, date_string, format):
"""Parse a date string according to the given format (like time.strptime())."""
import _strptime
return _strptime._strptime_datetime_date(cls, date_string, format)
# Conversions to string
def __repr__(self):
@@ -1016,11 +1076,11 @@ class date:
>>> repr(d)
'datetime.date(2010, 1, 1)'
"""
return "%s.%s(%d, %d, %d)" % (_get_class_module(self),
self.__class__.__qualname__,
self._year,
self._month,
self._day)
return "%s%s(%d, %d, %d)" % (_get_class_module(self),
self.__class__.__qualname__,
self._year,
self._month,
self._day)
# XXX These shouldn't depend on time.localtime(), because that
# clips the usable dates to [1970 .. 2038). At least ctime() is
# easily done without using strftime() -- that's better too because
@@ -1327,6 +1387,7 @@ class time:
Constructors:
__new__()
strptime()
Operators:
@@ -1385,6 +1446,12 @@ class time:
self._fold = fold
return self
@classmethod
def strptime(cls, date_string, format):
"""string, format -> new time parsed from a string (like time.strptime())."""
import _strptime
return _strptime._strptime_datetime_time(cls, date_string, format)
# Read-only field accessors
@property
def hour(self):
@@ -1513,7 +1580,7 @@ class time:
s = ", %d" % self._second
else:
s = ""
s= "%s.%s(%d, %d%s)" % (_get_class_module(self),
s = "%s%s(%d, %d%s)" % (_get_class_module(self),
self.__class__.__qualname__,
self._hour, self._minute, s)
if self._tzinfo is not None:
@@ -1555,7 +1622,7 @@ class time:
time_string = time_string.removeprefix('T')
try:
return cls(*_parse_isoformat_time(time_string))
return cls(*_parse_isoformat_time(time_string)[0])
except Exception:
raise ValueError(f'Invalid isoformat string: {time_string!r}')
@@ -1869,10 +1936,27 @@ class datetime(date):
if tstr:
try:
time_components = _parse_isoformat_time(tstr)
time_components, became_next_day, error_from_components = _parse_isoformat_time(tstr)
except ValueError:
raise ValueError(
f'Invalid isoformat string: {date_string!r}') from None
else:
if error_from_components:
raise ValueError("minute, second, and microsecond must be 0 when hour is 24")
if became_next_day:
year, month, day = date_components
# Only wrap day/month when it was previously valid
if month <= 12 and day <= (days_in_month := _days_in_month(year, month)):
# Calculate midnight of the next day
day += 1
if day > days_in_month:
day = 1
month += 1
if month > 12:
month = 1
year += 1
date_components = [year, month, day]
else:
time_components = [0, 0, 0, 0, None]
@@ -2072,9 +2156,9 @@ class datetime(date):
del L[-1]
if L[-1] == 0:
del L[-1]
s = "%s.%s(%s)" % (_get_class_module(self),
self.__class__.__qualname__,
", ".join(map(str, L)))
s = "%s%s(%s)" % (_get_class_module(self),
self.__class__.__qualname__,
", ".join(map(str, L)))
if self._tzinfo is not None:
assert s[-1:] == ")"
s = s[:-1] + ", tzinfo=%r" % self._tzinfo + ")"
@@ -2091,7 +2175,7 @@ class datetime(date):
def strptime(cls, date_string, format):
'string, format -> new datetime parsed from a string (like time.strptime()).'
import _strptime
return _strptime._strptime_datetime(cls, date_string, format)
return _strptime._strptime_datetime_datetime(cls, date_string, format)
def utcoffset(self):
"""Return the timezone offset as timedelta positive east of UTC (negative west of
@@ -2331,7 +2415,7 @@ class timezone(tzinfo):
if not cls._minoffset <= offset <= cls._maxoffset:
raise ValueError("offset must be a timedelta "
"strictly between -timedelta(hours=24) and "
"timedelta(hours=24).")
f"timedelta(hours=24), not {offset!r}")
return cls._create(offset, name)
def __init_subclass__(cls):
@@ -2371,12 +2455,12 @@ class timezone(tzinfo):
if self is self.utc:
return 'datetime.timezone.utc'
if self._name is None:
return "%s.%s(%r)" % (_get_class_module(self),
self.__class__.__qualname__,
self._offset)
return "%s.%s(%r, %r)" % (_get_class_module(self),
self.__class__.__qualname__,
self._offset, self._name)
return "%s%s(%r)" % (_get_class_module(self),
self.__class__.__qualname__,
self._offset)
return "%s%s(%r, %r)" % (_get_class_module(self),
self.__class__.__qualname__,
self._offset, self._name)
def __str__(self):
return self.tzname(None)
+34 -12
View File
@@ -472,7 +472,7 @@ class TimeRE(dict):
if day_of_month_in_format and not year_in_format:
import warnings
warnings.warn("""\
Parsing dates involving a day of month without a year specified is ambiguious
Parsing dates involving a day of month without a year specified is ambiguous
and fails to parse leap day. The default behavior will change in Python 3.15
to either always raise an exception or to use a different default year (TBD).
To avoid trouble, add a specific year to the input & format.
@@ -783,18 +783,40 @@ def _strptime_time(data_string, format="%a %b %d %H:%M:%S %Y"):
tt = _strptime(data_string, format)[0]
return time.struct_time(tt[:time._STRUCT_TM_ITEMS])
def _strptime_datetime(cls, data_string, format="%a %b %d %H:%M:%S %Y"):
"""Return a class cls instance based on the input string and the
def _strptime_datetime_date(cls, data_string, format="%a %b %d %Y"):
"""Return a date instance based on the input string and the
format string."""
tt, _, _ = _strptime(data_string, format)
args = tt[:3]
return cls(*args)
def _parse_tz(tzname, gmtoff, gmtoff_fraction):
tzdelta = datetime_timedelta(seconds=gmtoff, microseconds=gmtoff_fraction)
if tzname:
return datetime_timezone(tzdelta, tzname)
else:
return datetime_timezone(tzdelta)
def _strptime_datetime_time(cls, data_string, format="%H:%M:%S"):
"""Return a time instance based on the input string and the
format string."""
tt, fraction, gmtoff_fraction = _strptime(data_string, format)
tzname, gmtoff = tt[-2:]
args = tt[3:6] + (fraction,)
if gmtoff is None:
return cls(*args)
else:
tz = _parse_tz(tzname, gmtoff, gmtoff_fraction)
return cls(*args, tz)
def _strptime_datetime_datetime(cls, data_string, format="%a %b %d %H:%M:%S %Y"):
"""Return a datetime instance based on the input string and the
format string."""
tt, fraction, gmtoff_fraction = _strptime(data_string, format)
tzname, gmtoff = tt[-2:]
args = tt[:6] + (fraction,)
if gmtoff is not None:
tzdelta = datetime_timedelta(seconds=gmtoff, microseconds=gmtoff_fraction)
if tzname:
tz = datetime_timezone(tzdelta, tzname)
else:
tz = datetime_timezone(tzdelta)
args += (tz,)
return cls(*args)
if gmtoff is None:
return cls(*args)
else:
tz = _parse_tz(tzname, gmtoff, gmtoff_fraction)
return cls(*args, tz)
+6 -2
View File
@@ -1,9 +1,13 @@
"""Specific date/time and related types.
See https://data.iana.org/time-zones/tz-link.html for
time zone and DST data sources.
"""
try:
from _datetime import *
from _datetime import __doc__
except ImportError:
from _pydatetime import *
from _pydatetime import __doc__
__all__ = ("date", "datetime", "time", "timedelta", "timezone", "tzinfo",
"MINYEAR", "MAXYEAR", "UTC")
+404 -60
View File
@@ -1,11 +1,6 @@
# TODO: RustPython
# Skipping some tests because time module has no attribute tzset
# implementation detail specific to cpython
"""Test date/time type.
See https://www.zope.dev/Members/fdrake/DateTimeWiki/TestCases
"""
"""Test the datetime module."""
import bisect
import contextlib
import copy
import decimal
import io
@@ -26,7 +21,7 @@ from operator import lt, le, gt, ge, eq, ne, truediv, floordiv, mod
from test import support
from test.support import is_resource_enabled, ALWAYS_EQ, LARGEST, SMALLEST
from test.support import script_helper, warnings_helper
from test.support import os_helper, script_helper, warnings_helper
import datetime as datetime_module
from datetime import MINYEAR, MAXYEAR
@@ -188,7 +183,7 @@ class TestTZInfo(unittest.TestCase):
def __init__(self, offset, name):
self.__offset = offset
self.__name = name
self.assertTrue(issubclass(NotEnough, tzinfo))
self.assertIsSubclass(NotEnough, tzinfo)
ne = NotEnough(3, "NotByALongShot")
self.assertIsInstance(ne, tzinfo)
@@ -237,7 +232,7 @@ class TestTZInfo(unittest.TestCase):
self.assertIs(type(derived), otype)
self.assertEqual(derived.utcoffset(None), offset)
self.assertEqual(derived.tzname(None), oname)
self.assertFalse(hasattr(derived, 'spam'))
self.assertNotHasAttr(derived, 'spam')
def test_issue23600(self):
DSTDIFF = DSTOFFSET = timedelta(hours=1)
@@ -510,12 +505,16 @@ class HarmlessMixedComparison:
#############################################################################
# timedelta tests
class SubclassTimeDelta(timedelta):
sub_var = 1
class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
theclass = timedelta
def test_constructor(self):
eq = self.assertEqual
ra = self.assertRaises
td = timedelta
# Check keyword args to constructor
@@ -539,6 +538,15 @@ class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
eq(td(seconds=0.001), td(milliseconds=1))
eq(td(milliseconds=0.001), td(microseconds=1))
# Check type of args to constructor
ra(TypeError, lambda: td(weeks='1'))
ra(TypeError, lambda: td(days='1'))
ra(TypeError, lambda: td(hours='1'))
ra(TypeError, lambda: td(minutes='1'))
ra(TypeError, lambda: td(seconds='1'))
ra(TypeError, lambda: td(milliseconds='1'))
ra(TypeError, lambda: td(microseconds='1'))
def test_computations(self):
eq = self.assertEqual
td = timedelta
@@ -787,6 +795,15 @@ class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
self.assertEqual(repr(self.theclass(seconds=1, microseconds=100)),
"%s(seconds=1, microseconds=100)" % name)
def test_repr_subclass(self):
"""Subclasses should have bare names in the repr (gh-107773)."""
td = SubclassTimeDelta(days=1)
self.assertEqual(repr(td), "SubclassTimeDelta(days=1)")
td = SubclassTimeDelta(seconds=30)
self.assertEqual(repr(td), "SubclassTimeDelta(seconds=30)")
td = SubclassTimeDelta(weeks=2)
self.assertEqual(repr(td), "SubclassTimeDelta(days=14)")
def test_roundtrip(self):
for td in (timedelta(days=999999999, hours=23, minutes=59,
seconds=59, microseconds=999999),
@@ -796,7 +813,7 @@ class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
# Verify td -> string -> td identity.
s = repr(td)
self.assertTrue(s.startswith('datetime.'))
self.assertStartsWith(s, 'datetime.')
s = s[9:]
td2 = eval(s)
self.assertEqual(td, td2)
@@ -1115,6 +1132,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
@@ -1135,7 +1231,7 @@ class TestDate(HarmlessMixedComparison, unittest.TestCase):
self.theclass.today()):
# Verify dt -> string -> date identity.
s = repr(dt)
self.assertTrue(s.startswith('datetime.'))
self.assertStartsWith(s, 'datetime.')
s = s[9:]
dt2 = eval(s)
self.assertEqual(dt, dt2)
@@ -1144,6 +1240,15 @@ class TestDate(HarmlessMixedComparison, unittest.TestCase):
dt2 = self.theclass(dt.year, dt.month, dt.day)
self.assertEqual(dt, dt2)
def test_repr_subclass(self):
"""Subclasses should have bare names in the repr (gh-107773)."""
td = SubclassDate(1, 2, 3)
self.assertEqual(repr(td), "SubclassDate(1, 2, 3)")
td = SubclassDate(2014, 1, 1)
self.assertEqual(repr(td), "SubclassDate(2014, 1, 1)")
td = SubclassDate(2010, 10, day=10)
self.assertEqual(repr(td), "SubclassDate(2010, 10, 10)")
def test_ordinal_conversions(self):
# Check some fixed values.
for y, m, d, n in [(1, 1, 1, 1), # calendar origin
@@ -1702,19 +1807,42 @@ class TestDate(HarmlessMixedComparison, unittest.TestCase):
self.assertTrue(self.theclass.min)
self.assertTrue(self.theclass.max)
def check_strftime_y2k(self, specifier):
# Test that years less than 1000 are 0-padded; note that the beginning
# of an ISO 8601 year may fall in an ISO week of the year before, and
# therefore needs an offset of -1 when formatting with '%G'.
dataset = (
(1, 0),
(49, -1),
(70, 0),
(99, 0),
(100, -1),
(999, 0),
(1000, 0),
(1970, 0),
)
for year, g_offset in dataset:
with self.subTest(year=year, specifier=specifier):
d = self.theclass(year, 1, 1)
if specifier == 'G':
year += g_offset
if specifier == 'C':
expected = f"{year // 100:02d}"
else:
expected = f"{year:04d}"
if specifier == 'F':
expected += f"-01-01"
self.assertEqual(d.strftime(f"%{specifier}"), expected)
def test_strftime_y2k(self):
for y in (1, 49, 70, 99, 100, 999, 1000, 1970):
d = self.theclass(y, 1, 1)
# Issue 13305: For years < 1000, the value is not always
# padded to 4 digits across platforms. The C standard
# assumes year >= 1900, so it does not specify the number
# of digits.
if d.strftime("%Y") != '%04d' % y:
# Year 42 returns '42', not padded
self.assertEqual(d.strftime("%Y"), '%d' % y)
# '0042' is obtained anyway
if support.has_strftime_extensions:
self.assertEqual(d.strftime("%4Y"), '%04d' % y)
self.check_strftime_y2k('Y')
self.check_strftime_y2k('G')
def test_strftime_y2k_c99(self):
# CPython requires C11; specifiers new in C99 must work.
# (Other implementations may want to disable this test.)
self.check_strftime_y2k('F')
self.check_strftime_y2k('C')
def test_replace(self):
cls = self.theclass
@@ -1865,6 +1993,26 @@ class TestDate(HarmlessMixedComparison, unittest.TestCase):
# blow up because other fields are insane.
self.theclass(base[:2] + bytes([ord_byte]) + base[3:])
def test_valuerror_messages(self):
pattern = re.compile(
r"(year|month|day) must be in \d+\.\.\d+, not \d+"
)
test_cases = [
(2009, 13, 1), # Month out of range
(2009, 0, 1), # Month out of range
(10000, 12, 31), # Year out of range
(0, 12, 31), # Year out of range
]
for case in test_cases:
with self.subTest(case):
with self.assertRaisesRegex(ValueError, pattern):
self.theclass(*case)
# days out of range have their own error message, see issue 70647
with self.assertRaises(ValueError) as msg:
self.theclass(2009, 1, 32)
self.assertIn(f"day 32 must be in range 1..31 for month 1 in year 2009", str(msg.exception))
def test_fromisoformat(self):
# Test that isoformat() is reversible
base_dates = [
@@ -1948,6 +2096,7 @@ class TestDate(HarmlessMixedComparison, unittest.TestCase):
'10000-W25-1', # Invalid year
'2020-W25-0', # Invalid day-of-week
'2020-W25-8', # Invalid day-of-week
'٢025-03-09' # Unicode characters
'2009\ud80002\ud80028', # Separators are surrogate codepoints
]
@@ -2075,7 +2224,7 @@ class TestDateTime(TestDate):
self.theclass.now()):
# Verify dt -> string -> datetime identity.
s = repr(dt)
self.assertTrue(s.startswith('datetime.'))
self.assertStartsWith(s, 'datetime.')
s = s[9:]
dt2 = eval(s)
self.assertEqual(dt, dt2)
@@ -2724,7 +2873,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)
@@ -2738,8 +2888,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)
@@ -2861,8 +3011,7 @@ class TestDateTime(TestDate):
self.assertEqual(t.strftime("%z"), "-0200" + z)
self.assertEqual(t.strftime("%:z"), "-02:00:" + z)
@unittest.skip('TODO: RUSTPYTHON')
# UnicodeEncodeError: 'utf-8' codec can't encode character '\ud83d' in position 0: surrogates not allowed
@unittest.skip("TODO: RUSTPYTHON")
def test_strftime_special(self):
t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
s1 = t.strftime('%c')
@@ -3116,6 +3265,28 @@ class TestDateTime(TestDate):
self.assertEqual(res.year, 2013)
self.assertEqual(res.fold, fold)
def test_valuerror_messages(self):
pattern = re.compile(
r"(year|month|day|hour|minute|second) must "
r"be in \d+\.\.\d+, not \d+"
)
test_cases = [
(2009, 4, 1, 12, 30, 90), # Second out of range
(2009, 4, 1, 12, 90, 45), # Minute out of range
(2009, 4, 1, 25, 30, 45), # Hour out of range
(2009, 13, 1, 24, 0, 0), # Month out of range
(9999, 12, 31, 24, 0, 0), # Year out of range
]
for case in test_cases:
with self.subTest(case):
with self.assertRaisesRegex(ValueError, pattern):
self.theclass(*case)
# days out of range have their own error message, see issue 70647
with self.assertRaises(ValueError) as msg:
self.theclass(2009, 4, 32, 24, 0, 0)
self.assertIn(f"day 32 must be in range 1..30 for month 4 in year 2009", str(msg.exception))
def test_fromisoformat_datetime(self):
# Test that isoformat() is reversible
base_dates = [
@@ -3357,6 +3528,9 @@ class TestDateTime(TestDate):
('2025-01-02T03:04:05,678+00:00:10',
self.theclass(2025, 1, 2, 3, 4, 5, 678000,
tzinfo=timezone(timedelta(seconds=10)))),
('2025-01-02T24:00:00', self.theclass(2025, 1, 3, 0, 0, 0)),
('2025-01-31T24:00:00', self.theclass(2025, 2, 1, 0, 0, 0)),
('2025-12-31T24:00:00', self.theclass(2026, 1, 1, 0, 0, 0))
]
for input_str, expected in examples:
@@ -3379,7 +3553,7 @@ class TestDateTime(TestDate):
'2009-04-19T03:15:4500:00', # Bad time zone separator
'2009-04-19T03:15:45.123456+24:30', # Invalid time zone offset
'2009-04-19T03:15:45.123456-24:30', # Invalid negative offset
'2009-04-10ᛇᛇᛇᛇᛇ12:15', # Too many unicode separators
'2009-04-10ᛇᛇᛇᛇᛇ12:15', # Unicode chars
'2009-04\ud80010T12:15', # Surrogate char in date
'2009-04-10T12\ud80015', # Surrogate char in time
'2009-04-19T1', # Incomplete hours
@@ -3388,11 +3562,19 @@ class TestDateTime(TestDate):
'2009-04-19T12:', # Ends with time separator
'2009-04-19T12:30:', # Ends with time separator
'2009-04-19T12:30:45.', # Ends with time separator
'2009-04-19T12:30:45.123456+', # Ends with timzone separator
'2009-04-19T12:30:45.123456-', # Ends with timzone separator
'2009-04-19T12:30:45.123456+', # Ends with timezone separator
'2009-04-19T12:30:45.123456-', # Ends with timezone separator
'2009-04-19T12:30:45.123456-05:00a', # Extra text
'2009-04-19T12:30:45.123-05:00a', # Extra text
'2009-04-19T12:30:45-05:00a', # Extra text
'2009-04-19T24:00:00.000001', # Has non-zero microseconds on 24:00
'2009-04-19T24:00:01.000000', # Has non-zero seconds on 24:00
'2009-04-19T24:01:00.000000', # Has non-zero minutes on 24:00
'2009-04-32T24:00:00.000000', # Day is invalid before wrapping due to 24:00
'2009-13-01T24:00:00.000000', # Month is invalid before wrapping due to 24:00
'9999-12-31T24:00:00.000000', # Year is invalid after wrapping due to 24:00
'2009-04-19T12:30Z12:00', # Extra time zone info after Z
'2009-04-19T12:30:45:334034', # Invalid microsecond separator
'2009-04-19T12:30:45.400 +02:30', # Space between ms and timezone (gh-130959)
'2009-04-19T12:30:45.400 ', # Trailing space (gh-130959)
'2009-04-19T12:30:45. 400', # Space before fraction (gh-130959)
@@ -3403,6 +3585,29 @@ class TestDateTime(TestDate):
with self.assertRaises(ValueError):
self.theclass.fromisoformat(bad_str)
def test_fromisoformat_fails_datetime_valueerror(self):
pattern = re.compile(
r"(year|month|day|hour|minute|second) must "
r"be in \d+\.\.\d+, not \d+"
)
bad_strs = [
"2009-04-01T12:30:90", # Second out of range
"2009-04-01T12:90:45", # Minute out of range
"2009-04-01T25:30:45", # Hour out of range
"2009-13-01T24:00:00", # Month out of range
"9999-12-31T24:00:00", # Year out of range
]
for bad_str in bad_strs:
with self.subTest(bad_str=bad_str):
with self.assertRaisesRegex(ValueError, pattern):
self.theclass.fromisoformat(bad_str)
# days out of range have their own error message, see issue 70647
with self.assertRaises(ValueError) as msg:
self.theclass.fromisoformat("2009-04-32T24:00:00")
self.assertIn(f"day 32 must be in range 1..30 for month 4 in year 2009", str(msg.exception))
def test_fromisoformat_fails_surrogate(self):
# Test that when fromisoformat() fails with a surrogate character as
# the separator, the error message contains the original string
@@ -3429,6 +3634,15 @@ class TestDateTime(TestDate):
self.assertEqual(dt, dt_rt)
self.assertIsInstance(dt_rt, DateTimeSubclass)
def test_repr_subclass(self):
"""Subclasses should have bare names in the repr (gh-107773)."""
td = SubclassDatetime(2014, 1, 1)
self.assertEqual(repr(td), "SubclassDatetime(2014, 1, 1, 0, 0)")
td = SubclassDatetime(2010, 10, day=10)
self.assertEqual(repr(td), "SubclassDatetime(2010, 10, 10, 0, 0)")
td = SubclassDatetime(2010, 10, 2, second=3)
self.assertEqual(repr(td), "SubclassDatetime(2010, 10, 2, 0, 0, 3)")
class TestSubclassDateTime(TestDateTime):
theclass = SubclassDatetime
@@ -3465,7 +3679,7 @@ class TestTime(HarmlessMixedComparison, unittest.TestCase):
# Verify t -> string -> time identity.
s = repr(t)
self.assertTrue(s.startswith('datetime.'))
self.assertStartsWith(s, 'datetime.')
s = s[9:]
t2 = eval(s)
self.assertEqual(t, t2)
@@ -3665,8 +3879,7 @@ class TestTime(HarmlessMixedComparison, unittest.TestCase):
# gh-85432: The parameter was named "fmt" in the pure-Python impl.
t.strftime(format="%f")
@unittest.skip('TODO: RUSTPYTHON')
# UnicodeEncodeError: 'utf-8' codec can't encode character '\ud83d' in position 0: surrogates not allowed
@unittest.skip("TODO: RUSTPYTHON")
def test_strftime_special(self):
t = self.theclass(1, 2, 3, 4)
s1 = t.strftime('%I%p%Z')
@@ -3741,6 +3954,19 @@ class TestTime(HarmlessMixedComparison, unittest.TestCase):
self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
"%s(23, 15)" % name)
def test_repr_subclass(self):
"""Subclasses should have bare names in the repr (gh-107773)."""
td = SubclassTime(hour=1)
self.assertEqual(repr(td), "SubclassTime(1, 0)")
td = SubclassTime(hour=2, minute=30)
self.assertEqual(repr(td), "SubclassTime(2, 30)")
td = SubclassTime(hour=2, minute=30, second=11)
self.assertEqual(repr(td), "SubclassTime(2, 30, 11)")
td = SubclassTime(minute=30, second=11, fold=0)
self.assertEqual(repr(td), "SubclassTime(0, 30, 11)")
td = SubclassTime(minute=30, second=11, fold=1)
self.assertEqual(repr(td), "SubclassTime(0, 30, 11, fold=1)")
def test_resolution_info(self):
self.assertIsInstance(self.theclass.min, self.theclass)
self.assertIsInstance(self.theclass.max, self.theclass)
@@ -3787,6 +4013,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
@@ -4062,8 +4360,7 @@ class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
self.assertEqual(t.microsecond, 0)
self.assertIsNone(t.tzinfo)
@unittest.skip('TODO: RUSTPYTHON')
# UnicodeEncodeError: 'utf-8' codec can't encode character '\ud800' in position 0: surrogates not allowed
@unittest.skip("TODO: RUSTPYTHON")
def test_zones(self):
est = FixedOffset(-300, "EST", 1)
utc = FixedOffset(0, "UTC", -2)
@@ -4311,6 +4608,21 @@ class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
t2 = t2.replace(tzinfo=Varies())
self.assertTrue(t1 < t2) # t1's offset counter still going up
def test_valuerror_messages(self):
pattern = re.compile(
r"(hour|minute|second|microsecond) must be in \d+\.\.\d+, not \d+"
)
test_cases = [
(12, 30, 90, 9999991), # Microsecond out of range
(12, 30, 90, 000000), # Second out of range
(25, 30, 45, 000000), # Hour out of range
(12, 90, 45, 000000), # Minute out of range
]
for case in test_cases:
with self.subTest(case):
with self.assertRaisesRegex(ValueError, pattern):
self.theclass(*case)
def test_fromisoformat(self):
time_examples = [
(0, 0, 0, 0),
@@ -4360,7 +4672,7 @@ class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
with self.subTest(tstr=tstr):
t_rt = self.theclass.fromisoformat(tstr)
assert t == t_rt, t_rt
assert t == t_rt
def test_fromisoformat_timespecs(self):
time_bases = [
@@ -4419,6 +4731,7 @@ class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
('00:00:00.000', self.theclass(0, 0)),
('000000.000000', self.theclass(0, 0)),
('00:00:00.000000', self.theclass(0, 0)),
('00:00:00,100000', self.theclass(0, 0, 0, 100000)),
('1200', self.theclass(12, 0)),
('12:00', self.theclass(12, 0)),
('120000', self.theclass(12, 0)),
@@ -4484,6 +4797,10 @@ class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
'12:30:45.123456-', # Extra at end of microsecond time
'12:30:45.123456+', # Extra at end of microsecond time
'12:30:45.123456+12:00:30a', # Extra at end of full time
'12.5', # Decimal mark at end of hour
'12:30,5', # Decimal mark at end of minute
'12:30:45.123456Z12:00', # Extra time zone info after Z
'12:30:45:334034', # Invalid microsecond separator
'12:30:45.400 +02:30', # Space between ms and timezone (gh-130959)
'12:30:45.400 ', # Trailing space (gh-130959)
'12:30:45. 400', # Space before fraction (gh-130959)
@@ -5823,21 +6140,21 @@ class TestLocalTimeDisambiguation(unittest.TestCase):
gdt = datetime(1941, 6, 23, 20, 59, 59, tzinfo=timezone.utc)
ldt = gdt.astimezone(Vilnius)
self.assertEqual(ldt.strftime("%c %Z%z"),
self.assertEqual(ldt.strftime("%a %b %d %H:%M:%S %Y %Z%z"),
'Mon Jun 23 23:59:59 1941 MSK+0300')
self.assertEqual(ldt.fold, 0)
self.assertFalse(ldt.dst())
gdt = datetime(1941, 6, 23, 21, tzinfo=timezone.utc)
ldt = gdt.astimezone(Vilnius)
self.assertEqual(ldt.strftime("%c %Z%z"),
self.assertEqual(ldt.strftime("%a %b %d %H:%M:%S %Y %Z%z"),
'Mon Jun 23 23:00:00 1941 CEST+0200')
self.assertEqual(ldt.fold, 1)
self.assertTrue(ldt.dst())
gdt = datetime(1941, 6, 23, 22, tzinfo=timezone.utc)
ldt = gdt.astimezone(Vilnius)
self.assertEqual(ldt.strftime("%c %Z%z"),
self.assertEqual(ldt.strftime("%a %b %d %H:%M:%S %Y %Z%z"),
'Tue Jun 24 00:00:00 1941 CEST+0200')
self.assertEqual(ldt.fold, 0)
self.assertTrue(ldt.dst())
@@ -5847,22 +6164,22 @@ class TestLocalTimeDisambiguation(unittest.TestCase):
ldt = datetime(1941, 6, 23, 22, 59, 59, tzinfo=Vilnius)
gdt = ldt.astimezone(timezone.utc)
self.assertEqual(gdt.strftime("%c %Z"),
self.assertEqual(gdt.strftime("%a %b %d %H:%M:%S %Y %Z"),
'Mon Jun 23 19:59:59 1941 UTC')
ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius)
gdt = ldt.astimezone(timezone.utc)
self.assertEqual(gdt.strftime("%c %Z"),
self.assertEqual(gdt.strftime("%a %b %d %H:%M:%S %Y %Z"),
'Mon Jun 23 20:59:59 1941 UTC')
ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius, fold=1)
gdt = ldt.astimezone(timezone.utc)
self.assertEqual(gdt.strftime("%c %Z"),
self.assertEqual(gdt.strftime("%a %b %d %H:%M:%S %Y %Z"),
'Mon Jun 23 21:59:59 1941 UTC')
ldt = datetime(1941, 6, 24, 0, tzinfo=Vilnius)
gdt = ldt.astimezone(timezone.utc)
self.assertEqual(gdt.strftime("%c %Z"),
self.assertEqual(gdt.strftime("%a %b %d %H:%M:%S %Y %Z"),
'Mon Jun 23 22:00:00 1941 UTC')
def test_constructors(self):
@@ -6388,6 +6705,17 @@ class ZoneInfoTest(unittest.TestCase):
ldt = tz.fromutc(udt.replace(tzinfo=tz))
self.assertEqual(ldt.fold, 0)
@classmethod
@contextlib.contextmanager
def _change_tz(cls, new_tzinfo):
try:
with os_helper.EnvironmentVarGuard() as env:
env["TZ"] = new_tzinfo
_time.tzset()
yield
finally:
_time.tzset()
@unittest.skipUnless(
hasattr(_time, "tzset"), "time module has no attribute tzset"
)
@@ -6402,10 +6730,7 @@ class ZoneInfoTest(unittest.TestCase):
self.zonename.startswith('right/')):
self.skipTest("Skipping %s" % self.zonename)
tz = self.tz
TZ = os.environ.get('TZ')
os.environ['TZ'] = self.zonename
try:
_time.tzset()
with self._change_tz(self.zonename):
for udt, shift in tz.transitions():
if udt.year >= 2037:
# System support for times around the end of 32-bit time_t
@@ -6413,7 +6738,7 @@ class ZoneInfoTest(unittest.TestCase):
break
s0 = (udt - datetime(1970, 1, 1)) // SEC
ss = shift // SEC # shift seconds
for x in [-40 * 3600, -20*3600, -1, 0,
for x in [-40 * 3600, -20 * 3600, -1, 0,
ss - 1, ss + 20 * 3600, ss + 40 * 3600]:
s = s0 + x
sdt = datetime.fromtimestamp(s)
@@ -6432,12 +6757,6 @@ class ZoneInfoTest(unittest.TestCase):
utc0 = dt.astimezone(timezone.utc)
utc1 = dt.replace(fold=1).astimezone(timezone.utc)
self.assertEqual(utc0, utc1 + timedelta(0, ss))
finally:
if TZ is None:
del os.environ['TZ']
else:
os.environ['TZ'] = TZ
_time.tzset()
class ZoneInfoCompleteTest(unittest.TestSuite):
@@ -6933,9 +7252,6 @@ class ExtensionModuleTests(unittest.TestCase):
""")
script_helper.assert_python_ok('-c', script)
# TODO: RUSTPYTHON
# AssertionError: Process return code is 1
@unittest.expectedFailure
def test_update_type_cache(self):
# gh-120782
script = textwrap.dedent("""
@@ -6968,6 +7284,34 @@ class ExtensionModuleTests(unittest.TestCase):
""")
script_helper.assert_python_ok('-c', script)
def test_concurrent_initialization_subinterpreter(self):
# gh-136421: Concurrent initialization of _datetime across multiple
# interpreters wasn't thread-safe due to its static types.
# Run in a subprocess to ensure we get a clean version of _datetime
script = """if True:
from concurrent.futures import InterpreterPoolExecutor
def func():
import _datetime
print('a', end='')
with InterpreterPoolExecutor() as executor:
for _ in range(8):
executor.submit(func)
"""
rc, out, err = script_helper.assert_python_ok("-c", script)
self.assertEqual(rc, 0)
self.assertEqual(out, b"a" * 8)
self.assertEqual(err, b"")
# Now test against concurrent reinitialization
script = "import _datetime\n" + script
rc, out, err = script_helper.assert_python_ok("-c", script)
self.assertEqual(rc, 0)
self.assertEqual(out, b"a" * 8)
self.assertEqual(err, b"")
def load_tests(loader, standard_tests, pattern):
standard_tests.addTest(ZoneInfoCompleteTest())
-1
View File
@@ -1635,7 +1635,6 @@ class TestDescriptions(unittest.TestCase):
self.assertEqual(self._get_summary_line(datetime.datetime.__dict__['utcnow']),
"utcnow(type, /) unbound datetime.datetime method")
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_bound_builtin_classmethod_noargs(self):
self.assertEqual(self._get_summary_line(datetime.datetime.utcnow),
"utcnow() class method of datetime.datetime")