[Python-checkins] python/nondist/sandbox/datetime datetime.c,1.26,1.27 obj_date.c,1.9,1.10

tim_one@users.sourceforge.net tim_one@users.sourceforge.net
Tue, 26 Nov 2002 14:51:08 -0800


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

Modified Files:
	datetime.c obj_date.c 
Log Message:
Some of the unimplemented routines for datetime and datetimetz require
heftier normalization helpers than have been written so far.  So recoded a
larger part of datetime.py's "struct tmxxx" gimmick in C, and switched
date normalization to use it.  Will switch *all* normalization to use it.
This is slower; I don't care.


Index: datetime.c
===================================================================
RCS file: /cvsroot/python/python/nondist/sandbox/datetime/datetime.c,v
retrieving revision 1.26
retrieving revision 1.27
diff -C2 -d -r1.26 -r1.27
*** datetime.c	26 Nov 2002 05:32:06 -0000	1.26
--- datetime.c	26 Nov 2002 22:51:03 -0000	1.27
***************
*** 364,367 ****
--- 364,505 ----
  }
  
+ /* An internal struct used for miserable normalization tasks. */
+ typedef struct s_tmxxx {
+ 	long year;	/* may be negative on output */
+ 	long month; 	/* in 1..12 on output */
+ 	long day;	/* in 1 .. days_in_month(year, month) on output */
+ 	long hour;	/* in 0..59 on output */
+ 	long minute;	/* in 0..59 on output */
+ 	long second;	/* in 0 .. 59 on output */
+ 	long microsecond;	/* in 0..999999 on output */
+ } tmxxx;
+ 
+ #define TMXXX_CLEAR(PTR_TO_TMXXX) \
+ 	memset(PTR_TO_TMXXX, 0, sizeof(struct s_tmxxx))
+ 
+ /* One step of mixed-radix conversion.  *lower is the lower unit and *higher
+  * the higher unit, such that 1 higher unit is equal to bound lower units.
+  * Normalize *lower to lie in range(bound), adding carries (if needed) to
+  * higher.
+  * If a carry is needed, adding into *higher may overflow.  In that case,
+  * retuns a non-zero value.  If everything is OK, returns 0.
+  */
+ static int
+ norm1(long *higher, long *lower, long bound)
+ {
+ 	assert(bound > 0);
+ 	if (*lower < 0 || *lower >= bound) {
+ 		long carry;
+ 		long new_higher;
+ 
+ 		carry = divmod(*lower, bound, lower);
+ 		assert(0 <= *lower && *lower < bound);
+ 		new_higher = *higher + carry;
+ 		if (SIGNED_ADD_OVERFLOWED(new_higher, *higher, carry))
+ 			return 1;
+ 		*higher = new_higher;
+ 	}
+ 	return 0;
+ }
+ 
+ static int
+ tmxxx_normalize(tmxxx *p)
+ {
+ 	int dim;	/* days in month */
+ 	char *msg;
+ 
+ 	if (norm1(&p->second, &p->microsecond, 1000000)) {
+ 		msg = "second component too large";
+ 		goto Overflow;
+ 	}
+ 	assert(0 <= p->microsecond && p->microsecond < 1000000);
+ 
+ 	if (norm1(&p->minute, &p->second, 60)) {
+ 		msg = "minute component too large";
+ 		goto Overflow;
+ 	}
+ 	assert(0 <= p->second && p->second < 60);
+ 
+ 	if (norm1(&p->hour, &p->minute, 60)) {
+ 		msg = "hour component too large";
+ 		goto Overflow;
+ 	}
+ 	assert(0 <= p->minute && p->minute < 60);
+ 
+ 	if (norm1(&p->day, &p->hour, 60)) {
+ 		msg = "hour component too large";
+ 		goto Overflow;
+ 	}
+ 	assert(0 <= p->hour && p->hour < 24);
+ 
+ 	/* That was easy.  Now it gets muddy:  the proper range for day
+ 	 * can't be determined without knowing the correct month and year,
+ 	 * but if day is, e.g., plus or minus a million, the current month
+ 	 * and year values make no sense (and may also be out of bounds
+ 	 * themselves).
+ 	 * Saying 12 months == 1 year should be non-controversial.
+ 	 */
+ 	if (p->month < 1 || p->month > 12) {
+ 		--p->month;
+ 		if (norm1(&p->year, &p->month, 12)) {
+ 			msg = "year component too large";
+ 			goto Overflow;
+ 		}
+ 		++p->month;
+ 		assert (1 <= p->month && p->month <= 12);
+ 	}
+ 
+ 	/* The other routines don't deal correctly with years < 0, so cut
+ 	 * that off now.
+ 	 */
+ 	if (p->year < 0) {
+ 		msg = "year component is negative";
+ 		goto Overflow;
+ 	}
+ 	/* Now only day can be out of bounds (year may also be out of bounds
+ 	 * for a datetime object, but we don't care about that here).
+ 	 * If day is out of bounds, what to do is arguable, but at least the
+ 	 * method here is principled and explainable.
+ 	 */
+ 	dim = days_in_month(p->year, p->month);
+ 	if (p->day < 1 || p->day > dim) {
+ 		/* Move day-1 days from the first of the month.  First try to
+ 		 * get off cheap if we're only one day out of range
+ 		 * (adjustments for timezone alone can't be worse than that).
+ 		 */
+ 		if (p->day == 0) {
+ 			--p->month;
+ 			if (p->month > 0)
+ 				p->day = days_in_month(p->year, p->month);
+ 			else {
+ 				--p->year;
+ 				p->month = 12;
+ 				p->day = 31;
+ 			}
+ 		}
+ 		else if (p->day == dim + 1) {
+ 			/* move forward a day */
+ 			++p->month;
+ 			p->day = 1;
+ 			if (p->month > 12) {
+ 				p->month = 1;
+ 				++p->year;
+ 			}
+ 		}
+ 		else {
+ 			long ordinal = ymd_to_ord(p->year, p->month, 1) +
+ 						  p->day - 1;
+ 			ord_to_ymd(ordinal, &p->year, &p->month, &p->day);
+ 		}
+ 	}
+ 	assert(p->month > 0);
+ 	assert(p->day > 0);
+ 	return 1;
+ 
+ Overflow:
+ 	PyErr_SetString(PyExc_OverflowError, msg);
+ 	return 0;
+ }
+ 
  static PyObject *
  new_delta(long days, long seconds, long microseconds)

Index: obj_date.c
===================================================================
RCS file: /cvsroot/python/python/nondist/sandbox/datetime/obj_date.c,v
retrieving revision 1.9
retrieving revision 1.10
diff -C2 -d -r1.9 -r1.10
*** obj_date.c	26 Nov 2002 06:27:24 -0000	1.9
--- obj_date.c	26 Nov 2002 22:51:04 -0000	1.10
***************
*** 12,74 ****
  normalize_date(long *year, long *month, long *day)
  {
! 	long carry, dim;
  
! 	/* This is muddy: the proper range for day can't be determined
! 	 * without knowing the correct month and year, but if day is,
! 	 * e.g., plus or minus a million, the current month and year
! 	 * values make no sense (and may also be out of bounds themselves).
! 	 * Saying 12 months == 1 year should be non-controversial.
! 	 */
! 	if (*month < 1 || *month > 12) {
! 		long m = *month;
! 		carry = divmod(m-1, 12, &m);
! 		*month = m+1;
! 		*year += carry;
! 		assert(*month >= 1);
! 		assert(*month <= 12);
! 	}
! 	/*
! 	 * If day is out of bounds, what to do is arguable, but at
! 	 * least the method here is principled and explainable.
! 	 */
! 	dim = days_in_month(*year, *month);
! 	if (*day < 1 || *day > dim) {
! 		/* Move day-1 days from the first of the month.  First try to
! 		 * get off cheap if we're only one day out of range
! 		 * (adjustments for timezone alone can't be worse than that).
! 		 */
! 		if (*day == 0) {
! 			*month -= 1;
! 			if (*month > 0)
! 				*day = days_in_month(*year, *month);
! 			else {
! 				*year -= 1;
! 				*month = 12;
! 				*day = 31;
! 			}
! 		}
! 		else if (*day == dim + 1) {
! 			/* move forward a day */
! 			*month += 1;
! 			*day = 1;
! 			if (*month > 12) {
! 				*month = 1;
! 				*year += 1;
! 			}
! 		}
! 		else {
! 			long ordinal = ymd_to_ord(*year, *month, 1) +
! 						  *day - 1;
! 			ord_to_ymd(ordinal, year, month, day);
  		}
  	}
! 	assert(*month > 0);
! 	assert(*day > 0);
! 	if (*year < MINYEAR || *year > MAXYEAR) {
! 		PyErr_SetString(PyExc_OverflowError,
! 				"date value out of range");
! 		return 0;
! 	}
! 	return 1;
  }
  
--- 12,36 ----
  normalize_date(long *year, long *month, long *day)
  {
! 	tmxxx t;
! 	int result;
  
! 	TMXXX_CLEAR(&t);
! 	t.year = *year;
! 	t.month = *month;
! 	t.day = *day;
! 	result = tmxxx_normalize(&t);
! 	if (result > 0) {
! 		/* looks good */
! 		*year = t.year;
! 		*month = t.month;
! 		*day = t.day;
! 
! 		if (*year < MINYEAR || *year > MAXYEAR) {
! 			PyErr_SetString(PyExc_OverflowError,
! 					"date value out of range");
! 			result = 0;
  		}
  	}
! 	return result;
  }