[Python-checkins] python/nondist/sandbox/datetime datetime.c,1.23,1.24 obj_delta.c,1.5,1.6 test_datetime.py,1.47,1.48

tim_one@users.sourceforge.net tim_one@users.sourceforge.net
Mon, 25 Nov 2002 21:11:28 -0800


Update of /cvsroot/python/python/nondist/sandbox/datetime
In directory sc8-pr-cvs1:/tmp/cvs-serv24895

Modified Files:
	datetime.c obj_delta.c test_datetime.py 
Log Message:
Deeper into the weeds!  Moved the timedelta tests from the Python
test suite into test_both.  The C test suite didn't have any tests of
this, and there were, and still are, lots of problems.  Added a bunch
of new numeric methods to the timedelta C implementation, removed
needless bounds checks, added needed bounds checks, and generally
recovered from that C "/" doesn't compute the floor as these algorithms
need.  Much more is still needed here, but I'm hitting the wall for
one day.


Index: datetime.c
===================================================================
RCS file: /cvsroot/python/python/nondist/sandbox/datetime/datetime.c,v
retrieving revision 1.23
retrieving revision 1.24
diff -C2 -d -r1.23 -r1.24
*** datetime.c	22 Nov 2002 03:34:42 -0000	1.23
--- datetime.c	26 Nov 2002 05:11:25 -0000	1.24
***************
*** 19,24 ****
  	long hashcode;
  	long days;
! 	long seconds;
! 	long microseconds;
  } PyDateTime_Delta;
  
--- 19,24 ----
  	long hashcode;
  	long days;
! 	long seconds;		/* 0 <= seconds < 24*3600 is invariant */
! 	long microseconds;	/* 0 <= microseconds < 1000000 is invariant */
  } PyDateTime_Delta;
  
***************
*** 64,67 ****
--- 64,91 ----
   */
  
+ /* Compute Python divmod(x, y), returning the quotient and storing the
+  * remainder into *r.  The quotient is the floor of x/y, and that's
+  * the real point of this.  C will probably truncate instead (C89
+  * requires truncation; C89 left it implementation-defined).
+  * Simplification:  we *require* that y > 0 here.  That's appropriate
+  * for all the uses made of it.  This simplifies the code and makes
+  * the overflow case impossible.
+  */
+ static long
+ divmod(long x, long y, long *r)
+ {
+ 	long quo;
+ 
+ 	assert(y > 0);
+ 	quo = x / y;
+ 	*r = x - quo * y;
+ 	if (*r < 0) {
+ 		--quo;
+ 		*r += y;
+ 	}
+ 	assert(0 <= *r && *r < y);
+ 	return quo;
+ }
+ 
  /* For each month ordinal in 1..12, the number of days in that month,
   * and the number of days before that month in the same year.  These
***************
*** 333,347 ****
  
  static PyObject *
! new_delta(long int days, long int seconds, long int microseconds)
  {
! 	PyDateTime_Delta *self;
  
! 	if (microseconds >= 1000000 || microseconds <= -1000000) {
! 		seconds += microseconds / 1000000;
! 		microseconds %= 1000000;
  	}
! 	if (seconds >= 24*3600 || seconds <= 24*3600) {
! 		days += seconds / (24*3600);
! 		seconds %= (24*3600);
  	}
  	self = PyObject_New(PyDateTime_Delta, &PyDateTime_DeltaType);
--- 357,396 ----
  
  static PyObject *
! new_delta(long days, long seconds, long microseconds)
  {
! 	PyDateTime_Delta *self = NULL;
  
! 	/* k = i+j overflows iff k differs in sign from both inputs,
! 	 * iff k^i has sign bit set and k^j has sign bit set,
! 	 * iff (k^i)&(k^j) has sign bit set.
! 	 */
! 	if (microseconds < 0 || microseconds >= 1000000) {
! 		long whole_seconds;
! 		long new_seconds;
! 
! 		whole_seconds = divmod(microseconds, 1000000, &microseconds);
! 		assert(microseconds >= 0);
! 		new_seconds = seconds + whole_seconds;
! 		if (((new_seconds ^ seconds) &
! 		     (new_seconds ^ whole_seconds)) < 0) {
! 			PyErr_SetString(PyExc_OverflowError, "timedelta "
! 					"seconds component too large");
! 			goto done;
! 		}
! 		seconds = new_seconds;
  	}
! 	if (seconds < 0 || seconds >= 24*3600) {
! 		long whole_days;
! 		long new_days;
! 
! 		whole_days = divmod(seconds, 24*3600, &seconds);
! 		assert(seconds >= 0);
! 		new_days = days + whole_days;
! 		if (((new_days ^ days) & (new_days ^ whole_days)) < 0) {
! 			PyErr_SetString(PyExc_OverflowError, "timedelta "
! 					"days component too large");
! 			goto done;
! 		}
! 		days = new_days;
  	}
  	self = PyObject_New(PyDateTime_Delta, &PyDateTime_DeltaType);
***************
*** 352,355 ****
--- 401,405 ----
  		SET_TD_MICROSECONDS(self, microseconds);
  	}
+ done:
  	return (PyObject *) self;
  }

Index: obj_delta.c
===================================================================
RCS file: /cvsroot/python/python/nondist/sandbox/datetime/obj_delta.c,v
retrieving revision 1.5
retrieving revision 1.6
diff -C2 -d -r1.5 -r1.6
*** obj_delta.c	26 Nov 2002 03:29:17 -0000	1.5
--- obj_delta.c	26 Nov 2002 05:11:25 -0000	1.6
***************
*** 19,22 ****
--- 19,23 ----
  		if (PyType_IsSubtype(right_type, &PyDateTime_DeltaType)) {
  			/* delta + delta */
+ 			/* XXX Overflow checking is needed here. */
  			const long days = GET_TD_DAYS(delta) +
  					  GET_TD_DAYS(right);
***************
*** 42,45 ****
--- 43,103 ----
  }
  
+ static PyObject *
+ delta_negative(PyDateTime_Delta *self)
+ {
+ 	return new_delta(-GET_TD_DAYS(self),
+ 			 -GET_TD_SECONDS(self),
+ 			 -GET_TD_MICROSECONDS(self));
+ }
+ 
+ static PyObject *
+ delta_positive(PyDateTime_Delta *self)
+ {
+ 	/* Could optimize this (by returning self) if this isn't a
+ 	 * subclass -- but who uses unary + ?  Approximately nobody.
+ 	 */
+ 	return new_delta(GET_TD_DAYS(self),
+ 			 GET_TD_SECONDS(self),
+ 			 GET_TD_MICROSECONDS(self));
+ }
+ 
+ static PyObject *
+ delta_abs(PyDateTime_Delta *self)
+ {
+ 	PyObject *result;
+ 
+ 	assert(GET_TD_MICROSECONDS(self) >= 0);
+ 	assert(GET_TD_SECONDS(self) >= 0);
+ 
+ 	if (GET_TD_DAYS(self) < 0)
+ 		result = delta_negative(self);
+ 	else
+ 		result = delta_positive(self);
+ 
+ 	return result;
+ }
+ 
+ static PyObject *
+ delta_subtract(PyObject *left, PyObject *right)
+ {
+ 	PyObject *result = NULL;
+ 
+ 	/* XXX It's unclear to me exactly which rules we intend here. */
+ 	if (PyType_IsSubtype(left->ob_type, &PyDateTime_DeltaType) &&
+ 	    PyType_IsSubtype(right->ob_type, &PyDateTime_DeltaType)) {
+ 	    	/* delta - delta */
+ 	    	PyObject *minus_right = PyNumber_Negative(right);
+ 	    	if (minus_right) {
+ 	    		result = delta_add(left, minus_right);
+ 	    		Py_DECREF(minus_right);
+ 	    	}
+ 	}
+ 	else
+ 		result = Py_NotImplemented;
+ 
+ 	Py_XINCREF(result);
+ 	return result;
+ }
+ 
  static int
  delta_compare(PyDateTime_Delta *self, PyObject *other)
***************
*** 107,118 ****
  
  static PyObject *
- delta_negative(PyDateTime_Delta *self)
- {
- 	return new_delta(-GET_TD_DAYS(self),
- 			 -GET_TD_SECONDS(self),
- 			 -GET_TD_MICROSECONDS(self));
- }
- 
- static PyObject *
  delta_new(PyTypeObject *type, PyObject *args, PyObject *kw)
  {
--- 165,168 ----
***************
*** 120,123 ****
--- 170,175 ----
  	long int days, seconds = 0, microseconds = 0;
  
+ 	/* XXX We're missing 4 keyword args from the Python version. */
+ 	/* XXX The Python version doesn't require a days argument. */
  	static char *keywords[] = {
  		"days", "seconds", "microseconds", NULL
***************
*** 126,139 ****
  	if (PyArg_ParseTupleAndKeywords(args, kw, "l|ll:__new__", keywords,
  					&days, &seconds, &microseconds)) {
- 		if (seconds < 0 || seconds >= (24 * 3600)) {
- 			PyErr_SetString(PyExc_ValueError,
- 					"seconds must be in 0..86399");
- 			return NULL;
- 		}
- 		if (microseconds < 0 || microseconds >= 1000000) {
- 			PyErr_SetString(PyExc_ValueError,
- 					"microseconds must be in 0..999999");
- 			return NULL;
- 		}
  		self = new_delta(days, seconds, microseconds);
  	}
--- 178,181 ----
***************
*** 141,151 ****
  }
  
- static PyObject *
- delta_subtract(PyObject *left, PyObject *right)
- {
- 	Py_INCREF(Py_NotImplemented);
- 	return Py_NotImplemented;
- }
- 
  static int
  delta_nonzero(PyDateTime_Delta *self)
--- 183,186 ----
***************
*** 200,205 ****
  	0,						/* nb_power */
  	(unaryfunc)delta_negative,			/* nb_negative */
! 	0,						/* nb_positive */
! 	0,						/* nb_absolute */
  	(inquiry)delta_nonzero,				/* nb_nonzero */
  };
--- 235,240 ----
  	0,						/* nb_power */
  	(unaryfunc)delta_negative,			/* nb_negative */
! 	(unaryfunc)delta_positive,			/* nb_positive */
! 	(unaryfunc)delta_abs,				/* nb_absolute */
  	(inquiry)delta_nonzero,				/* nb_nonzero */
  };

Index: test_datetime.py
===================================================================
RCS file: /cvsroot/python/python/nondist/sandbox/datetime/test_datetime.py,v
retrieving revision 1.47
retrieving revision 1.48
diff -C2 -d -r1.47 -r1.48
*** test_datetime.py	26 Nov 2002 01:43:03 -0000	1.47
--- test_datetime.py	26 Nov 2002 05:11:25 -0000	1.48
***************
*** 52,119 ****
                      n += 1
  
- class TestTimeDelta(unittest.TestCase):
- 
-     def test_timedelta(self):
-         a = timedelta(7) # One week
-         b = timedelta(0, 60) # One minute
-         c = timedelta(0, 0, 1000) # One millisecond
-         self.assertEqual(a+b+c, timedelta(7, 60, 1000))
-         self.assertEqual(a-b, timedelta(6, 24*3600 - 60))
-         self.assertEqual(-a, timedelta(-7))
-         self.assertEqual(+a, timedelta(7))
-         self.assertEqual(-b, timedelta(-1, 24*3600 - 60))
-         self.assertEqual(-c, timedelta(-1, 24*3600 - 1, 999000))
-         self.assertEqual(abs(a), a)
-         self.assertEqual(abs(-a), a)
-         self.assertEqual(timedelta(6, 24*3600), a)
-         self.assertEqual(timedelta(0, 0, 60*1000000), b)
-         self.assertEqual(a*10, timedelta(70))
-         self.assertEqual(a*10, 10*a)
-         self.assertEqual(a*10L, 10*a)
-         self.assertEqual(b*10, timedelta(0, 600))
-         self.assertEqual(10*b, timedelta(0, 600))
-         self.assertEqual(b*10L, timedelta(0, 600))
-         self.assertEqual(c*10, timedelta(0, 0, 10000))
-         self.assertEqual(10*c, timedelta(0, 0, 10000))
-         self.assertEqual(c*10L, timedelta(0, 0, 10000))
-         self.assertEqual(a*-1, -a)
-         self.assertEqual(b*-2, -b-b)
-         self.assertEqual(c*-2, -c+-c)
-         self.assertEqual(b*(60*24), (b*60)*24)
-         self.assertEqual(b*(60*24), (60*b)*24)
-         self.assertEqual(c*1000, timedelta(0, 1))
-         self.assertEqual(1000*c, timedelta(0, 1))
-         self.assertEqual(a//7, timedelta(1))
-         self.assertEqual(b//10, timedelta(0, 6))
-         self.assertEqual(c//1000, timedelta(0, 0, 1))
-         self.assertEqual(a//10, timedelta(0, 7*24*360))
-         self.assertEqual(a//3600000, timedelta(0, 0, 7*24*1000))
-         # Add/sub ints, longs, floats should be illegal
-         for i in 1, 1L, 1.0:
-             self.assertRaises(TypeError, lambda: a+i)
-             self.assertRaises(TypeError, lambda: a-i)
-             self.assertRaises(TypeError, lambda: i+a)
-             self.assertRaises(TypeError, lambda: i-a)
-         # Check keyword args to constructor
-         eq = self.assertEqual
-         td = timedelta
-         eq(td(1), td(days=1))
-         eq(td(0, 1), td(seconds=1))
-         eq(td(0, 0, 1), td(microseconds=1))
-         eq(td(weeks=1), td(days=7))
-         eq(td(days=1), td(hours=24))
-         eq(td(hours=1), td(minutes=60))
-         eq(td(minutes=1), td(seconds=60))
-         eq(td(seconds=1), td(milliseconds=1000))
-         eq(td(milliseconds=1), td(microseconds=1000))
-         # Check float args to constructor
-         eq(td(weeks=1.0/7), td(days=1))
-         eq(td(days=1.0/24), td(hours=1))
-         eq(td(hours=1.0/60), td(minutes=1))
-         eq(td(minutes=1.0/60), td(seconds=1))
-         eq(td(seconds=0.001), td(milliseconds=1))
-         eq(td(milliseconds=0.001), td(microseconds=1))
- 
- 
  
  class TestTime(unittest.TestCase):
--- 52,55 ----
***************
*** 558,562 ****
  
  def test_suite():
-     s1 = unittest.makeSuite(TestTimeDelta, 'test')
      s2 = unittest.makeSuite(TestDate, 'test')
      s3 = unittest.makeSuite(TestTime, 'test')
--- 494,497 ----
***************
*** 564,568 ****
      s5 = unittest.makeSuite(TestDateTime, 'test')
      s6 = unittest.makeSuite(TestDateTimeTZ, 'test')
!     return unittest.TestSuite([s1, s2, s3, s4, s5, s6])
  
  def test_main():
--- 499,503 ----
      s5 = unittest.makeSuite(TestDateTime, 'test')
      s6 = unittest.makeSuite(TestDateTimeTZ, 'test')
!     return unittest.TestSuite([s2, s3, s4, s5, s6])
  
  def test_main():