mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Update datetime to 3.14.3 (#7084)
This commit is contained in:
1 parent
061c709434
commit
d412c10e6b
5 files changed
+582
-129
No files matched your search
Vendored
+138
-54
@@ -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)
|
||||
|
||||
Vendored
+34
-12
@@ -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)
|
||||
Vendored
+6
-2
@@ -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")
|
||||
Vendored
+404
-60
@@ -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())
|
||||
|
||||
Vendored
-1
@@ -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")
|
||||
|
||||
Reference in new issue
Block a user