Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Doc/c-api/long.rst
Original file line number Diff line number Diff line change
Expand Up @@ -453,8 +453,8 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate.

Otherwise, returns the number of bytes required to store the value.
If this is equal to or less than *n_bytes*, the entire value was copied.
All *n_bytes* of the buffer are written: large buffers are padded with
zeroes.
All *n_bytes* of the buffer are written: remaining bytes filled by
copies of the sign bit.

If the returned value is greater than *n_bytes*, the value was
truncated: as many of the lowest bits of the value as could fit are written,
Expand Down
88 changes: 38 additions & 50 deletions Lib/test/datetimetester.py
Original file line number Diff line number Diff line change
Expand Up @@ -2706,24 +2706,20 @@ def utcfromtimestamp(*args, **kwargs):
self.assertEqual(zero.second, 0)
self.assertEqual(zero.microsecond, 0)
one = fts(1e-6)
try:
minus_one = fts(-1e-6)
except OSError:
# localtime(-1) and gmtime(-1) is not supported on Windows
pass
else:
self.assertEqual(minus_one.second, 59)
self.assertEqual(minus_one.microsecond, 999999)

t = fts(-1e-8)
self.assertEqual(t, zero)
t = fts(-9e-7)
self.assertEqual(t, minus_one)
t = fts(-1e-7)
self.assertEqual(t, zero)
t = fts(-1/2**7)
self.assertEqual(t.second, 59)
self.assertEqual(t.microsecond, 992188)
minus_one = fts(-1e-6)

self.assertEqual(minus_one.second, 59)
self.assertEqual(minus_one.microsecond, 999999)

t = fts(-1e-8)
self.assertEqual(t, zero)
t = fts(-9e-7)
self.assertEqual(t, minus_one)
t = fts(-1e-7)
self.assertEqual(t, zero)
t = fts(-1/2**7)
self.assertEqual(t.second, 59)
self.assertEqual(t.microsecond, 992188)

t = fts(1e-7)
self.assertEqual(t, zero)
Expand Down Expand Up @@ -2752,22 +2748,18 @@ def utcfromtimestamp(*args, **kwargs):
self.assertEqual(zero.second, 0)
self.assertEqual(zero.microsecond, 0)
one = fts(D('0.000_001'))
try:
minus_one = fts(D('-0.000_001'))
except OSError:
# localtime(-1) and gmtime(-1) is not supported on Windows
pass
else:
self.assertEqual(minus_one.second, 59)
self.assertEqual(minus_one.microsecond, 999_999)
minus_one = fts(D('-0.000_001'))

self.assertEqual(minus_one.second, 59)
self.assertEqual(minus_one.microsecond, 999_999)

t = fts(D('-0.000_000_1'))
self.assertEqual(t, zero)
t = fts(D('-0.000_000_9'))
self.assertEqual(t, minus_one)
t = fts(D(-1)/2**7)
self.assertEqual(t.second, 59)
self.assertEqual(t.microsecond, 992188)
t = fts(D('-0.000_000_1'))
self.assertEqual(t, zero)
t = fts(D('-0.000_000_9'))
self.assertEqual(t, minus_one)
t = fts(D(-1)/2**7)
self.assertEqual(t.second, 59)
self.assertEqual(t.microsecond, 992188)

t = fts(D('0.000_000_1'))
self.assertEqual(t, zero)
Expand Down Expand Up @@ -2803,22 +2795,18 @@ def utcfromtimestamp(*args, **kwargs):
self.assertEqual(zero.second, 0)
self.assertEqual(zero.microsecond, 0)
one = fts(F(1, 1_000_000))
try:
minus_one = fts(F(-1, 1_000_000))
except OSError:
# localtime(-1) and gmtime(-1) is not supported on Windows
pass
else:
self.assertEqual(minus_one.second, 59)
self.assertEqual(minus_one.microsecond, 999_999)
minus_one = fts(F(-1, 1_000_000))

t = fts(F(-1, 10_000_000))
self.assertEqual(t, zero)
t = fts(F(-9, 10_000_000))
self.assertEqual(t, minus_one)
t = fts(F(-1, 2**7))
self.assertEqual(t.second, 59)
self.assertEqual(t.microsecond, 992188)
self.assertEqual(minus_one.second, 59)
self.assertEqual(minus_one.microsecond, 999_999)

t = fts(F(-1, 10_000_000))
self.assertEqual(t, zero)
t = fts(F(-9, 10_000_000))
self.assertEqual(t, minus_one)
t = fts(F(-1, 2**7))
self.assertEqual(t.second, 59)
self.assertEqual(t.microsecond, 992188)

t = fts(F(1, 10_000_000))
self.assertEqual(t, zero)
Expand Down Expand Up @@ -2860,6 +2848,7 @@ def test_timestamp_limits(self):
# If that assumption changes, this value can change as well
self.assertEqual(max_ts, 253402300799.0)

@unittest.skipIf(sys.platform == "win32", "Windows doesn't support min timestamp")
def test_fromtimestamp_limits(self):
try:
self.theclass.fromtimestamp(-2**32 - 1)
Expand Down Expand Up @@ -2899,6 +2888,7 @@ def test_fromtimestamp_limits(self):
# OverflowError, especially on 32-bit platforms.
self.theclass.fromtimestamp(ts)

@unittest.skipIf(sys.platform == "win32", "Windows doesn't support min timestamp")
def test_utcfromtimestamp_limits(self):
with self.assertWarns(DeprecationWarning):
try:
Expand Down Expand Up @@ -2960,13 +2950,11 @@ def test_insane_utcfromtimestamp(self):
self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
insane)

@unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
def test_negative_float_fromtimestamp(self):
# The result is tz-dependent; at least test that this doesn't
# fail (like it did before bug 1646728 was fixed).
self.theclass.fromtimestamp(-1.05)

@unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
def test_negative_float_utcfromtimestamp(self):
with self.assertWarns(DeprecationWarning):
d = self.theclass.utcfromtimestamp(-1.05)
Expand Down
32 changes: 32 additions & 0 deletions Lib/test/test_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@
from test.support import import_helper


class CustomHash:
def __init__(self, hash):
self.hash = hash
def __hash__(self):
return self.hash
def __repr__(self):
return f'<CustomHash {self.hash} at {id(self):#x}>'


class DictTest(unittest.TestCase):

def test_invalid_keyword_arguments(self):
Expand Down Expand Up @@ -1648,6 +1657,29 @@ class MyClass: pass
d[MyStr("attr1")] = 2
self.assertIsInstance(list(d)[0], MyStr)

def test_hash_collision_remove_add(self):
self.maxDiff = None
# There should be enough space, so all elements with unique hash
# will be placed in corresponding cells without collision.
n = 64
items = [(CustomHash(h), h) for h in range(n)]
# Keys with hash collision.
a = CustomHash(n)
b = CustomHash(n)
items += [(a, 'a'), (b, 'b')]
d = dict(items)
self.assertEqual(len(d), len(items), d)
del d[a]
# "a" has been replaced with a dummy.
del items[n]
self.assertEqual(len(d), len(items), d)
self.assertEqual(d, dict(items))
d[b] = 'c'
# "b" should not replace the dummy.
items[n] = (b, 'c')
self.assertEqual(len(d), len(items), d)
self.assertEqual(d, dict(items))


class CAPITest(unittest.TestCase):

Expand Down
30 changes: 30 additions & 0 deletions Lib/test/test_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ def check_pass_thru():
raise PassThru
yield 1

class CustomHash:
def __init__(self, hash):
self.hash = hash
def __hash__(self):
return self.hash
def __repr__(self):
return f'<CustomHash {self.hash} at {id(self):#x}>'

class BadCmp:
def __hash__(self):
return 1
Expand Down Expand Up @@ -675,6 +683,28 @@ def __hash__(self):
with self.assertRaises(KeyError):
myset.discard(elem2)

def test_hash_collision_remove_add(self):
self.maxDiff = None
# There should be enough space, so all elements with unique hash
# will be placed in corresponding cells without collision.
n = 64
elems = [CustomHash(h) for h in range(n)]
# Elements with hash collision.
a = CustomHash(n)
b = CustomHash(n)
elems += [a, b]
s = self.thetype(elems)
self.assertEqual(len(s), len(elems), s)
s.remove(a)
# "a" has been replaced with a dummy.
del elems[n]
self.assertEqual(len(s), len(elems), s)
self.assertEqual(s, set(elems))
s.add(b)
# "b" should not replace the dummy.
self.assertEqual(len(s), len(elems), s)
self.assertEqual(s, set(elems))


class SetSubclass(set):
pass
Expand Down
37 changes: 35 additions & 2 deletions Lib/test/test_time.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,38 @@ def test_epoch(self):
# Only test the date and time, ignore other gmtime() members
self.assertEqual(tuple(epoch)[:6], (1970, 1, 1, 0, 0, 0), epoch)

def test_gmtime(self):
# expected format:
# (tm_year, tm_mon, tm_mday,
# tm_hour, tm_min, tm_sec,
# tm_wday, tm_yday)
tests = [
(-13262400, (1969, 7, 31, 12, 0, 0, 3, 212)),
(-6177600, (1969, 10, 21, 12, 0, 0, 1, 294)),
# leap years (pre epoch)
(-2077660800, (1904, 3, 1, 0, 0, 0, 1, 61)),
(-2077833600, (1904, 2, 28, 0, 0, 0, 6, 59)),
]

try:
from _testinternalcapi import SIZEOF_TIME_T
except ImportError:
pass
else:
if SIZEOF_TIME_T >= 8:
tests.extend((
# non-leap years (pre epoch)
(-2203891200, (1900, 3, 1, 0, 0, 0, 3, 60)),
(-2203977600, (1900, 2, 28, 0, 0, 0, 2, 59)),
(-5359564800, (1800, 3, 1, 0, 0, 0, 5, 60)),
(-5359651200, (1800, 2, 28, 0, 0, 0, 4, 59)),
))

for t, expected in tests:
with self.subTest(t=t, expected=expected):
res = time.gmtime(t)
self.assertEqual(tuple(res)[:8], expected, res)

def test_strftime(self):
tt = time.gmtime(self.t)
for directive in ('a', 'A', 'b', 'B', 'c', 'd', 'H', 'I',
Expand Down Expand Up @@ -501,12 +533,13 @@ def test_localtime_without_arg(self):
def test_mktime(self):
# Issue #1726687
for t in (-2, -1, 0, 1):
t_struct = time.localtime(t)
try:
tt = time.localtime(t)
t1 = time.mktime(t_struct)
except (OverflowError, OSError):
pass
else:
self.assertEqual(time.mktime(tt), t)
self.assertEqual(t1, t)

# Issue #13309: passing extreme values to mktime() or localtime()
# borks the glibc's internal timezone data.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support negative timestamps in :func:`time.gmtime`, :func:`time.localtime`, and various :mod:`datetime` functions.
17 changes: 1 addition & 16 deletions Modules/_datetimemodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -5584,22 +5584,7 @@ datetime_from_timet_and_us(PyTypeObject *cls, TM_FUNC f, time_t timet, int us,
second = Py_MIN(59, tm.tm_sec);

/* local timezone requires to compute fold */
if (tzinfo == Py_None && f == _PyTime_localtime
/* On Windows, passing a negative value to local results
* in an OSError because localtime_s on Windows does
* not support negative timestamps. Unfortunately this
* means that fold detection for time values between
* 0 and max_fold_seconds will result in an identical
* error since we subtract max_fold_seconds to detect a
* fold. However, since we know there haven't been any
* folds in the interval [0, max_fold_seconds) in any
* timezone, we can hackily just forego fold detection
* for this time range.
*/
#ifdef MS_WINDOWS
&& (timet - max_fold_seconds > 0)
#endif
) {
if (tzinfo == Py_None && f == _PyTime_localtime) {
long long probe_seconds, result_seconds, transition;

result_seconds = utc_to_seconds(year, month, day,
Expand Down
Loading
Loading