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
23 changes: 10 additions & 13 deletions Lib/_pydatetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -1570,10 +1570,11 @@ def _cmp(self, other, allow_mixed=False):
return 2 # arbitrary non-zero value
else:
raise TypeError("cannot compare naive and aware times")
myhhmm = self._hour * 60 + self._minute - myoff//timedelta(minutes=1)
othhmm = other._hour * 60 + other._minute - otoff//timedelta(minutes=1)
return _cmp((myhhmm, self._second, self._microsecond),
(othhmm, other._second, other._microsecond))
myus = (((self._hour * 60 + self._minute) * 60 + self._second) * 1000000
+ self._microsecond - myoff._to_microseconds())
otus = (((other._hour * 60 + other._minute) * 60 + other._second) * 1000000
+ other._microsecond - otoff._to_microseconds())
return _cmp(myus, otus)

def __hash__(self):
"""Hash."""
Expand All @@ -1583,17 +1584,13 @@ def __hash__(self):
else:
t = self
tzoff = t.utcoffset()
if not tzoff: # zero or None
if tzoff is None:
self._hashcode = hash(t._getstate()[0])
else:
h, m = divmod(timedelta(hours=self.hour, minutes=self.minute) - tzoff,
timedelta(hours=1))
assert not m % timedelta(minutes=1), "whole minute"
m //= timedelta(minutes=1)
if 0 <= h < 24:
self._hashcode = hash(time(h, m, self.second, self.microsecond))
else:
self._hashcode = hash((h, m, self.second, self.microsecond))
self._hashcode = hash(timedelta(hours=t.hour,
minutes=t.minute,
seconds=t.second,
microseconds=t.microsecond) - tzoff)
return self._hashcode

# Conversion to string
Expand Down
27 changes: 27 additions & 0 deletions Lib/test/datetimetester.py
Original file line number Diff line number Diff line change
Expand Up @@ -4693,6 +4693,33 @@ def tzname(self, dt): return self.tz
Badtzname.tz = '\ud800'
self.assertEqual(t.strftime("%Z"), '\ud800')

def test_subminute_offset_equality(self):
t1 = self.theclass(12, tzinfo=timezone.utc)
t2 = self.theclass(12, 0, 1, tzinfo=timezone(timedelta(seconds=1)))
self.assertEqual(t1, t2)
t2 = self.theclass(12, 0, 0, 1, tzinfo=timezone(timedelta(microseconds=1)))
self.assertEqual(t1, t2)
t2 = self.theclass(11, 59, 59, 999999, tzinfo=timezone(timedelta(microseconds=-1)))
self.assertEqual(t1, t2)

def test_subminute_offset_ordering(self):
t1 = self.theclass(0, tzinfo=timezone.utc)
t2 = self.theclass(0, tzinfo=timezone(timedelta(microseconds=1)))
self.assertGreater(t1, t2)

t1 = self.theclass(13, 59, 59, 900000, tzinfo=timezone(timedelta(hours=2)))
t2 = self.theclass(14, tzinfo=timezone(timedelta(hours=2, microseconds=900000)))
self.assertGreater(t1, t2)

def test_subminute_offset_hash(self):
t1 = self.theclass(12, tzinfo=timezone.utc)
t2 = self.theclass(12, 0, 1, tzinfo=timezone(timedelta(seconds=1)))
self.assertEqual(hash(t1), hash(t2))
t2 = self.theclass(12, 0, 0, 1, tzinfo=timezone(timedelta(microseconds=1)))
self.assertEqual(hash(t1), hash(t2))
t2 = self.theclass(11, 59, 59, 999999, tzinfo=timezone(timedelta(microseconds=-1)))
self.assertEqual(hash(t1), hash(t2))

def test_hash_edge_cases(self):
# Offsets that overflow a basic time.
t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
Expand Down
44 changes: 42 additions & 2 deletions Lib/test/test_curses.py
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,9 @@ def test_complexstr(self):
self.assertEqual(str(s[1:]), 'bc')
self.assertEqual(str(s[::-1]), 'cbA')
self.assertEqual(str(s + curses.complexstr(['Z'])), 'AbcZ')
# Concatenating anything else raises instead of returning NotImplemented.
self.assertRaises(TypeError, lambda: s + 'Z')
self.assertRaises(TypeError, lambda: s + cc('Z'))
# The empty complexstr.
self.assertEqual(len(curses.complexstr([])), 0)
self.assertEqual(str(curses.complexstr('')), '')
Expand Down Expand Up @@ -3383,6 +3386,23 @@ def test_close(self):
# close() is idempotent.
screen.close()

def test_close_then_write_with_attr_keeps_no_reference(self):
# A write with an *attr* argument on a detached window fails while
# setting the rendition, and has to release the bytes it converted.
s = self.make_pty()
screen = curses.newterm('xterm', s, s)
win = screen.stdscr
screen.close()
writes = [lambda b: win.addstr(b, curses.A_BOLD),
lambda b: win.addnstr(b, 4, curses.A_BOLD),
lambda b: win.insstr(b, curses.A_BOLD),
lambda b: win.insnstr(b, 4, curses.A_BOLD)]
data = b'x' * 8
nrefs = sys.getrefcount(data)
for write in writes:
self.assertRaises(curses.error, write, data)
self.assertEqual(sys.getrefcount(data), nrefs)

@requires_curses_func('panel')
def test_close_then_panel_replace(self):
# A detached window has no underlying curses window, so replace()
Expand Down Expand Up @@ -3479,10 +3499,10 @@ class SLKTests(NewtermTestBase):
# slk_init() must run before newterm()/initscr(), so each test sets up its
# own screen rather than reusing the one TestCurses builds in setUp().

def make_slk_screen(self, fmt=0):
def make_slk_screen(self, fmt=0, term='xterm'):
s = self.make_pty()
curses.slk_init(fmt)
return curses.newterm('xterm', s, s)
return curses.newterm(term, s, s)

def test_init_reserves_a_line(self):
# Every layout takes the bottom line for the labels; the index-line
Expand Down Expand Up @@ -3565,6 +3585,26 @@ def test_color(self):
curses.slk_attr_set(curses.A_BOLD, 0)
curses.slk_color(0)

def test_color_wide_pair(self):
# Drive a terminal with enough color pairs to reach past a short,
# rather than relying on whatever $TERM happens to be.
try:
self.make_slk_screen(term='xterm-256color')
except curses.error:
self.skipTest('no xterm-256color terminfo entry')
if not curses.has_colors():
self.skipTest('requires colors support')
curses.start_color()
if not (curses.has_extended_color_support()
and curses.COLOR_PAIRS > SHORT_MAX + 1):
self.skipTest('requires extended color support')
# A pair that does not fit in a short is still a valid pair here.
curses.slk_color(SHORT_MAX + 1)
# The low 16 bits of this are pair 5, but the pair itself is out of
# range, so it must raise instead of selecting pair 5.
self.assertRaises(curses.error, curses.slk_color,
curses.COLOR_PAIRS * 2 + 5)


@unittest.skipUnless(hasattr(curses, 'newterm'), 'requires curses.newterm()')
@unittest.skipIf(BROKEN_NEWTERM, 'ncurses < 6.5 mishandles repeated newterm()')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix comparisons and hashing of :class:`datetime.time` objects with sub-minute
UTC offsets.
14 changes: 12 additions & 2 deletions Modules/_cursesmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1590,7 +1590,10 @@ complexstr_concat(PyObject *a, PyObject *b)
{
cursesmodule_state *state = get_cursesmodule_state_by_cls(Py_TYPE(a));
if (!Py_IS_TYPE(b, state->complexstr_type)) {
Py_RETURN_NOTIMPLEMENTED;
PyErr_Format(PyExc_TypeError,
"can only concatenate complexstr to complexstr, not %T",
b);
return NULL;
}
PyCursesComplexStrObject *sa = _PyCursesComplexStrObject_CAST(a);
PyCursesComplexStrObject *sb = _PyCursesComplexStrObject_CAST(b);
Expand Down Expand Up @@ -4361,6 +4364,7 @@ _curses_window_insnstr_impl(PyCursesWindowObject *self, int group_left_1,
curses_wattrset(self, attr, "insnstr") < 0)
{
curses_release_wstr(strtype, wstr);
Py_XDECREF(bytesobj);
return NULL;
}
}
Expand Down Expand Up @@ -8863,7 +8867,13 @@ _curses_slk_color_impl(PyObject *module, int pair)
/*[clinic end generated code: output=ffe4de805f9c65f5 input=b1e691a9cc6177ee]*/
{
PyCursesStatefulInitialised(module);
return curses_check_err(module, slk_color((short)pair), "slk_color", NULL);
int rtn;
#if _NCURSES_EXTENDED_COLOR_FUNCS
rtn = extended_slk_color(pair);
#else
rtn = slk_color((short)pair);
#endif
return curses_check_err(module, rtn, "slk_color", NULL);
}
#endif /* HAVE_CURSES_SLK_COLOR */

Expand Down
41 changes: 26 additions & 15 deletions Modules/_datetimemodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -5051,22 +5051,33 @@ time_richcompare(PyObject *self, PyObject *other, int op)
}
/* The hard case: both aware with different UTC offsets */
else if (offset1 != Py_None && offset2 != Py_None) {
int offsecs1, offsecs2;
long long norm_us1, norm_us2;
assert(offset1 != offset2); /* else last "if" handled it */
offsecs1 = TIME_GET_HOUR(self) * 3600 +
TIME_GET_MINUTE(self) * 60 +
TIME_GET_SECOND(self) -
GET_TD_DAYS(offset1) * 86400 -
GET_TD_SECONDS(offset1);
offsecs2 = TIME_GET_HOUR(other) * 3600 +
TIME_GET_MINUTE(other) * 60 +
TIME_GET_SECOND(other) -
GET_TD_DAYS(offset2) * 86400 -
GET_TD_SECONDS(offset2);
diff = offsecs1 - offsecs2;
if (diff == 0)
diff = TIME_GET_MICROSECOND(self) -
TIME_GET_MICROSECOND(other);
norm_us1 =
((TIME_GET_HOUR(self) * 3600 +
TIME_GET_MINUTE(self) * 60 +
TIME_GET_SECOND(self)) * 1000000LL +
TIME_GET_MICROSECOND(self)) -
((GET_TD_DAYS(offset1) * 86400LL +
GET_TD_SECONDS(offset1)) * 1000000LL +
GET_TD_MICROSECONDS(offset1));
norm_us2 =
((TIME_GET_HOUR(other) * 3600 +
TIME_GET_MINUTE(other) * 60 +
TIME_GET_SECOND(other)) * 1000000LL +
TIME_GET_MICROSECOND(other)) -
((GET_TD_DAYS(offset2) * 86400LL +
GET_TD_SECONDS(offset2)) * 1000000LL +
GET_TD_MICROSECONDS(offset2));
if (norm_us1 < norm_us2) {
diff = -1;
}
else if (norm_us1 > norm_us2) {
diff = 1;
}
else {
diff = 0;
}
result = diff_to_bool(diff, op);
}
else if (op == Py_EQ) {
Expand Down
4 changes: 3 additions & 1 deletion Modules/_struct.c
Original file line number Diff line number Diff line change
Expand Up @@ -2424,7 +2424,9 @@ s_pack_internal(PyStructObject *soself, PyObject *const *args,
memcpy(res + 1, p, n);
if (n > 255)
n = 255;
*res = Py_SAFE_DOWNCAST(n, Py_ssize_t, unsigned char);
if (n > 0) {
*res = Py_SAFE_DOWNCAST(n, Py_ssize_t, unsigned char);
}
} else {
if (e->pack(state, res, v, e) < 0) {
if (PyLong_Check(v) && PyErr_ExceptionMatches(PyExc_OverflowError))
Expand Down
Loading