time + timedelta

josh at yucs.org josh at yucs.org
Mon Feb 7 11:34:26 EST 2005


sorry, patch really attached to this message.

On Mon, Feb 07, 2005 at 11:24:20AM -0500, josh wrote:
> datetime.time should support timedelta arithmetic, patch attached.
> 
> Times greater than 24 hours should not raise an exception, but always
> wrap around. Any other behavior is too surprising. (People expect to be
> able to call you up at 11pm, and say "meet me in two hours" (if you're a
> night person)).
> 
> Clock time is cyclical. Though it's convenient to assign hours numbers,
> an unqualified hour 23.5 is not naturally "greater than" hour 0.5.
> (Though I'm not suggesting that we break this.)
> 
> The special meaning for hour 0 is only relevant for datetimes. When
> dates are not involved, it shouldn't get any special treatment.
> When dates are involved, it's easy to "assert datetime1.date() ==
> datetime2.date()"
> 
> If this change is made however, maybe datetime.timedelta(hours=-1)
> should no longer normalize to datetime.timedelta(-1, 82800)
> 
-------------- next part --------------
*** Doc/lib/libdatetime.tex1	2005-02-03 21:53:06.000000000 -0500
--- Doc/lib/libdatetime.tex	2005-02-03 22:11:52.000000000 -0500
***************
*** 1059,1069 ****
  
  Supported operations:
  
! \begin{itemize}
!   \item
!     comparison of \class{time} to \class{time},
!     where \var{a} is considered less than \var{b} when \var{a} precedes
!     \var{b} in time.  If one comparand is naive and the other is aware,
      \exception{TypeError} is raised.  If both comparands are aware, and
      have the same \member{tzinfo} member, the common \member{tzinfo}
      member is ignored and the base times are compared.  If both
--- 1059,1122 ----
  
  Supported operations:
  
! \begin{tableii}{c|l}{code}{Operation}{Result}
!   \lineii{\var{time2} = \var{time1} + \var{timedelta}}{(1)}
! 
!   \lineii{\var{time2} = \var{time1} - \var{timedelta}}{(2)}
! 
!   \lineii{\var{timedelta} = \var{time1} - \var{time2}}{(3)}
! 
!   \lineii{\var{time1} < \var{time2}}
!    {Compares \class{time} to \class{time}.
!     (4)}
! 
! \end{tableii}
! 
! \begin{description}
! 
! \item[(1)]
! 
!     time2 is a duration of timedelta removed from time1, moving
!     forward in time if \code{\var{timedelta}.seconds} > 0, or backward if
!     \code{\var{timedelta}.seconds} < 0.  The result has the same \member{tzinfo} member
!     as the input time, and time2 - time1 == timedelta after.
!     Note that no time zone adjustments are done even if the input is an
!     aware object.
! 
! \item[(2)]
!     Computes the time2 such that time2 + timedelta == time1.
!     As for addition, the result has the same \member{tzinfo} member
!     as the input time, and no time zone adjustments are done even
!     if the input is aware.
!     This isn't quite equivalent to time1 + (-timedelta), because
!     -timedelta in isolation can overflow in cases where
!     time1 - timedelta does not.
! 
! \item[(3)]
!     Subtraction of a \class{time} from a
!     \class{time} is defined only if both
!     operands are naive, or if both are aware.  If one is aware and the
!     other is naive, \exception{TypeError} is raised.
! 
!     If both are naive, or both are aware and have the same \member{tzinfo}
!     member, the \member{tzinfo} members are ignored, and the result is
!     a \class{timedelta} object \var{t} such that
!     \code{\var{time2} + \var{t} == \var{time1}}.  No time zone
!     adjustments are done in this case.
! 
!     If both are aware and have different \member{tzinfo} members,
!     \code{a-b} acts as if \var{a} and \var{b} were first converted to
!     naive UTC times first.  The result is
!     \code{(\var{a}.replace(tzinfo=None) - \var{a}.utcoffset()) -
!           (\var{b}.replace(tzinfo=None) - \var{b}.utcoffset())}
!     except that the implementation never overflows.
! 
! \item[(4)]
! 
! \var{time1} is considered less than \var{time2}
! when \var{time1} precedes \var{time2} in time.
! 
!     If one comparand is naive and the other is aware,
      \exception{TypeError} is raised.  If both comparands are aware, and
      have the same \member{tzinfo} member, the common \member{tzinfo}
      member is ignored and the base times are compared.  If both
***************
*** 1076,1093 ****
      raised unless the comparison is \code{==} or \code{!=}.  The latter
      cases return \constant{False} or \constant{True}, respectively.
  
!   \item
!     hash, use as dict key
  
!   \item
!     efficient pickling
! 
!   \item
!     in Boolean contexts, a \class{time} object is considered to be
!     true if and only if, after converting it to minutes and
!     subtracting \method{utcoffset()} (or \code{0} if that's
!     \code{None}), the result is non-zero.
! \end{itemize}
  
  Instance methods:
  
--- 1129,1141 ----
      raised unless the comparison is \code{==} or \code{!=}.  The latter
      cases return \constant{False} or \constant{True}, respectively.
  
! \end{description}
  
! \class{time} objects are hashable (usable as dictionary keys),
! support efficient pickling, and in Boolean contexts, a \class{time}
! object is considered to be true if and only if, after converting it to
! minutes and subtracting \method{utcoffset()} (or \code{0} if that's
! \code{None}), the result is non-zero.
  
  Instance methods:
  
*** Modules/datetimemodule.c1	2005-02-03 21:50:40.000000000 -0500
--- Modules/datetimemodule.c	2005-02-03 21:51:07.000000000 -0500
***************
*** 559,564 ****
--- 559,577 ----
  	return result;
  }
  
+ /* Force all the time fields into range.  The parameters are both
+  * inputs and outputs.  Cannot fail.
+  */
+ static void
+ normalize_time(int *day, int *hour, int *minute, int *second,
+                    int *microsecond)
+ {
+ 	normalize_pair(second, microsecond, 1000000);
+ 	normalize_pair(minute, second, 60);
+ 	normalize_pair(hour, minute, 60);
+ 	normalize_pair(day, hour, 24);
+ }
+ 
  /* Force all the datetime fields into range.  The parameters are both
   * inputs and outputs.  Returns < 0 on error.
   */
***************
*** 567,576 ****
                     int *hour, int *minute, int *second,
                     int *microsecond)
  {
! 	normalize_pair(second, microsecond, 1000000);
! 	normalize_pair(minute, second, 60);
! 	normalize_pair(hour, minute, 60);
! 	normalize_pair(day, hour, 24);
  	return normalize_date(year, month, day);
  }
  
--- 580,586 ----
                     int *hour, int *minute, int *second,
                     int *microsecond)
  {
! 	normalize_time(day, hour, minute, second, microsecond);
  	return normalize_date(year, month, day);
  }
  
***************
*** 3276,3281 ****
--- 3286,3398 ----
  }
  
  /*
+  * Time arithmetic.
+  */
+ 
+ /* time + timedelta -> time.  If arg negate is true, subtract the timedelta
+  * instead.
+  */
+ static PyObject *
+ add_time_timedelta(PyDateTime_Time *time, PyDateTime_Delta *delta, int negate)
+ {
+ 	int delta_s = GET_TD_SECONDS(delta);
+ 	int delta_us = GET_TD_MICROSECONDS(delta);
+ 	int day; /* ignored */
+ 	int hour = TIME_GET_HOUR(time);
+ 	int minute = TIME_GET_MINUTE(time);
+ 	int second = TIME_GET_SECOND(time) + (negate ? -delta_s : delta_s);
+ 	int microsecond = TIME_GET_MICROSECOND(time) + (negate ? -delta_us : delta_us);
+ 	
+ 	normalize_time(&day, &hour, &minute, &second, &microsecond);
+ 	return new_time(hour, minute, second, microsecond,
+ 			    HASTZINFO(time) ? time->tzinfo : Py_None);
+ }
+ 
+ static PyObject *
+ time_add(PyObject *left, PyObject *right)
+ {
+ 	if (PyDateTime_Check(left) || PyDateTime_Check(right)) {
+ 		Py_INCREF(Py_NotImplemented);
+ 		return Py_NotImplemented;
+ 	}
+ 	if (PyTime_Check(left)) {
+ 		/* time + ??? */
+ 		if (PyDelta_Check(right))
+ 			/* time + delta */
+ 			return add_time_timedelta((PyDateTime_Time *) left,
+ 						  (PyDateTime_Delta *) right,
+ 						  0);
+ 	}
+ 	else {
+ 		/* ??? + time
+ 		 * 'right' must be one of us, or we wouldn't have been called
+ 		 */
+ 		if (PyDelta_Check(left))
+ 			/* delta + time */
+ 			return add_time_timedelta((PyDateTime_Time *) right,
+ 						  (PyDateTime_Delta *) left,
+ 						  0);
+ 	}
+ 	Py_INCREF(Py_NotImplemented);
+ 	return Py_NotImplemented;
+ }
+ 
+ static PyObject *
+ time_subtract(PyObject *left, PyObject *right)
+ {
+ 	if (PyDateTime_Check(left) || PyDateTime_Check(right)) {
+ 		Py_INCREF(Py_NotImplemented);
+ 		return Py_NotImplemented;
+ 	}
+ 	if (PyTime_Check(left)) {
+ 		if (PyTime_Check(right)) {
+ 			/* time - time */
+ 			naivety n1, n2;
+ 			int offset1, offset2;
+ 			int delta_s, delta_us;
+ 			if (classify_two_utcoffsets(left, &offset1, &n1, left,
+ 						    right, &offset2, &n2,
+ 						    right) < 0)
+ 				return NULL;
+ 			assert(n1 != OFFSET_UNKNOWN && n2 != OFFSET_UNKNOWN);
+ 			if (n1 != n2) {
+ 				PyErr_SetString(PyExc_TypeError,
+ 					"can't subtract offset-naive and "
+ 					"offset-aware times");
+ 				return NULL;
+ 			}
+ 
+ 			/* These can't overflow, since the values are
+ 			 * normalized.  At most this gives the number of
+ 			 * seconds in one day.
+ 			 */
+ 			delta_s = (TIME_GET_HOUR(left) -
+ 				   TIME_GET_HOUR(right)) * 3600 +
+ 			          (TIME_GET_MINUTE(left) -
+ 			           TIME_GET_MINUTE(right)) * 60 +
+ 				  (TIME_GET_SECOND(left) -
+ 				   TIME_GET_SECOND(right));
+ 			delta_us = TIME_GET_MICROSECOND(left) -
+ 				   TIME_GET_MICROSECOND(right);
+ 			/* (left - offset1) - (right - offset2) =
+ 			 * (left - right) + (offset2 - offset1)
+ 			 */
+ 			delta_s += (offset2 - offset1) * 60;
+ 
+ 			return new_delta(0, delta_s, delta_us, 1);
+ 		}
+ 		if (PyDelta_Check(right)) {
+ 			/* time - delta */
+ 			return add_time_timedelta((PyDateTime_Date *) left,
+ 						  (PyDateTime_Delta *) right,
+ 						  1);
+ 		}
+ 	}
+ 	Py_INCREF(Py_NotImplemented);
+ 	return Py_NotImplemented;
+ }
+ 
+ /*
   * Miscellaneous methods.
   */
  
***************
*** 3502,3509 ****
  a tzinfo subclass. The remaining arguments may be ints or longs.\n");
  
  static PyNumberMethods time_as_number = {
! 	0,					/* nb_add */
! 	0,					/* nb_subtract */
  	0,					/* nb_multiply */
  	0,					/* nb_divide */
  	0,					/* nb_remainder */
--- 3619,3626 ----
  a tzinfo subclass. The remaining arguments may be ints or longs.\n");
  
  static PyNumberMethods time_as_number = {
! 	time_add,				/* nb_add */
! 	time_subtract,				/* nb_subtract */
  	0,					/* nb_multiply */
  	0,					/* nb_divide */
  	0,					/* nb_remainder */


More information about the Python-list mailing list