Date patch

tetherow at nol.org tetherow at nol.org
Tue Jul 18 15:04:23 EDT 2000


As eluded to weeks ago, here is the date fix for the tds layer.  It
should be transparent to users except that dates < 1902 and > 2036
should now be working.  I have also added tdsset_date_format which will
allow you to change the string format for dates as returned by the
tds_convert functions.  The tdsset_date_format function takes the same
type of format definition as strftime (as that is how it formats the
output).


------------------------------------------------------------------------
Sam Tetherow                           tetherow at nol.org
Director of Development
Nebrask@ Online                        http://www.nol.org/
-------------- next part --------------
Index: src/tds/convert.c
===================================================================
RCS file: /Repository/freetds/src/tds/convert.c,v
retrieving revision 1.41
diff -c -r1.41 convert.c
*** convert.c	2000/06/06 04:33:10	1.41
--- convert.c	2000/07/18 18:53:08
***************
*** 197,202 ****
--- 197,223 ----
     }
  }
  
+ /**
+  * tds_convert_char - Convert/copy SYBCHAR/SYBVARCHAR.
+  * @srctype: Source element type.  Should be SYBCHAR, SYBVARCHAR, SYBNVARCHAR
+  * @src: Source element 
+  * @srclen: Length of source element
+  * @desttype: Destination element type
+  * @dest: Destination element
+  * @destlen: The size of the destination element
+  * 
+  * Convert/copy SYBCHAR, SYBVARCHAR, SYBNVARCHAR to either another SYBCHAR,
+  * SYBVARCHAR, SYBNVARCHAR or SYBINT1, SYBINT2, SYBINT4, SYBFLOAT8, SYBREAL,
+  * SYBBIT, SYBDATETIME, SYBDATETIME4
+  *
+  * TODO:
+  * Various bounds checking.
+  * Do we want to return TDS_FAIL if srctype not SYBCHAR, SYBVARCHAR, SYBNVARCHAR
+  * Do we want to return TDS_FAIL if desttype not covered
+  * Implementations for SYBTEXT, SYBBINARY, SYBIMAGE, SYBMONEY4, SYBMONEY
+  *  SYBNUMERIC, SYBDECIMAL
+  **/
+ 
  TDS_INT tds_convert_char(int srctype,unsigned char *src,
  	int desttype,unsigned char *dest,TDS_INT destlen)
  {
***************
*** 238,254 ****
        case SYBMONEY4:
           break;
        case SYBDATETIME:
! 	 _string_to_tm(src, &t);
! 	 secs_from_epoch = mktime(&t);
! 	 any.dt.dtdays = (secs_from_epoch/60/60/24)+25567;
! 	 any.dt.dttime = (secs_from_epoch%60%60%24)*300;
!          break;
!       case SYBDATETIME4:
! 	 _string_to_tm(src, &t);
! 	 secs_from_epoch = mktime(&t);
! 	 any.dt4.days = (secs_from_epoch/60/60/24)+25567;
! 	 any.dt4.minutes = (secs_from_epoch%60%60%24)/60;
!          break;
        case SYBNUMERIC:
        case SYBDECIMAL:
           break;
--- 259,276 ----
        case SYBMONEY4:
           break;
        case SYBDATETIME:
! 				/* Current implementation is off by one day for dates < 1970-01-01 */
! 				/* This converts from MM.DD.YY HH.mm.SS.sss to SYBDATETIME */
! 				_string_to_tm(src, &t);
! 				any.dt.dtdays=t.tm_year*365+number_of_leap_years(t.tm_year+1899)+julian(t.tm_year+1900, t.tm_mon, t.tm_mday);
! 				any.dt.dttime=t.tm_hour*60*60+t.tm_min*60+t.tm_sec*300;
! 				break;
! 			case SYBDATETIME4:
! 				/* TODO: Need to bounds check here */
! 				_string_to_tm(src, &t);
! 				any.dt4.days=t.tm_year*365+number_of_leap_years(t.tm_year-1899)+julian(t.tm_year+1900, t.tm_mon, t.tm_mday);
! 				any.dt4.minutes=t.tm_hour*60*60+t.tm_min*60+t.tm_sec*300;
! 				break;
        case SYBNUMERIC:
        case SYBDECIMAL:
           break;
***************
*** 479,566 ****
  			break;
  	}
  }
  TDS_INT tds_convert_datetime(int srctype,unsigned char *src,int desttype,unsigned char *dest,TDS_INT destlen)
  {
!    TDS_INT dtdays, dttime;
!    time_t tmp_secs_from_epoch;
!    
!    switch(desttype) {
!       case SYBCHAR:
!       case SYBVARCHAR:
! 	/* FIX ME -- This fails for dates before 1902 or after 2038 */
! 	 if (destlen<0) {
! 	 	memset(dest,' ',30);
! 	 } else {
! 	 	memset(dest,' ',destlen);
! 	 }
! 	 if (!src) {
! 		*dest='\0';
! 		return 0;
  	 }
-          memcpy(&dtdays, src, 4);
-          memcpy(&dttime, src+4, 4);
-          tmp_secs_from_epoch = ((dtdays - 25567)*24*60*60) + (dttime/300);
-          if (strlen(src)>destlen) {
-             strftime(dest, destlen-1, "%b %d %Y %I:%M%p",
-                      (struct tm*)gmtime(&tmp_secs_from_epoch));
-             return destlen;
-          } else {
-             strftime(dest, 20, "%b %d %Y %I:%M%p",
-                      (struct tm*)gmtime(&tmp_secs_from_epoch));
-             return (strlen(dest));
-          }
-          break;
-       case SYBDATETIME:
- 	memcpy(dest,src,sizeof(TDS_DATETIME));
- 	return sizeof(TDS_DATETIME);
- 	break;
-       case SYBDATETIME4:
- 	break;
-       default:
-          return TDS_FAIL;
-          break;
-    }
  }
  TDS_INT tds_convert_datetime4(int srctype,unsigned char *src,int desttype,unsigned char *dest,TDS_INT destlen)
  {
!    TDS_USMALLINT days, minutes;
!    time_t tmp_secs_from_epoch;
!    
!    switch(desttype) {
!       case SYBCHAR:
!       case SYBVARCHAR:
! 	if (destlen<0) {
! 	 	memset(dest,' ',30);
! 	} else {
! 		memset(dest,' ',destlen);
! 	}
! 	if (!src) {
! 		*dest='\0';
! 		return 0;
! 	}
! 	memcpy(&days, src, 2);
! 	memcpy(&minutes, src+2, 2);
! 	tdsdump_log("%L inside tds_convert_datetime4() days = %d minutes = %d\n", days, minutes);
!         tmp_secs_from_epoch = (days - 25567)*(24*60*60) + (minutes*60);
!         if (strlen(src)>destlen) {
!            strftime(dest, destlen-1, "%b %d %Y %I:%M%p",
!                     (struct tm*)gmtime(&tmp_secs_from_epoch));
!            return destlen;
!         } else {
!            strftime(dest, 20, "%b %d %Y %I:%M%p",
!                     (struct tm*)gmtime(&tmp_secs_from_epoch));
!            return (strlen(dest));
  	}
- 	break;
-       case SYBDATETIME:
- 	break;
-       case SYBDATETIME4:
- 	memcpy(dest,src,sizeof(TDS_DATETIME4));
- 	return(sizeof(TDS_DATETIME4));
-       default:
-          return TDS_FAIL;
- 	break;
-    }
  }
  TDS_INT tds_convert_real(int srctype,unsigned char *src,int desttype,unsigned
  char *dest,TDS_INT destlen)
--- 501,804 ----
  			break;
  	}
  }
+ 
+ /**
+  * is_leap_year - Determine if a given year is a leap year
+  * @year: Year in century format CCYY
+  *
+  * If year is a leap year return one otherwise return zero
+  **/
+ int is_leap_year(int year) {
+ 	if (year%4!=0) return(0);
+ 	if (year%400==0) return(1);
+ 	if (year%100==0) return(0);
+ 	return(1);
+ }
+ 
+ 
+ /**
+  * number_of_leap_years - Determine number of leap years between 1900 and year
+  * @year: Year in century format CCYY
+  *
+  * Return the number of leap years between 1900 and year
+  **/
+ int number_of_leap_years(int year) {
+ 	int leapyears;
+ 	int actualyear;
+ 
+ 	actualyear=year+1900;
+ 	leapyears=(int)(actualyear/4)-(int)(actualyear/100)+(int)(actualyear/400);
+ 	return(leapyears);
+ }
+ 
+ /**
+  * days_in_month - Determine the number of days in a month
+  * @month: month [0..11]
+  * @leapyear: 1 if a leapyear, 0 otherwise
+  *
+  * Return the number of days in a given month
+  **/
+ int days_in_month(int month, int leapyear) {
+ 	switch(month+1) {
+ 		case 1:
+ 		case 3:
+ 		case 5:
+ 		case 7:
+ 		case 8:
+ 		case 10:
+ 		case 12:
+ 			return(31);
+ 			break;
+ 		case 2: return(28+leapyear);
+ 			break;
+ 		case 4:
+ 		case 6:
+ 		case 9:
+ 		case 11:
+ 			return(30);
+ 			break;
+ 		default:
+ 			return(0);
+ 	}
+ }
+ 
+ /**
+  * julian - return the julian date for a give date
+  * @year: year in century format CCYY
+  * @month: month [0..11]
+  * @day: day [1..31]
+  *
+  * Return the julian day portion for a given date
+  **/
+ int julian(int year, int month, int day) {
+ 	int days=0;
+ 	int leapyear;
+ 	int x;
+ 	const int jday[]={0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
+ 	const int ljday[]={0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335};
+ 
+ 	if (is_leap_year(year)) {
+ 		return(ljday[month]+day-1);
+ 	} else {
+ 		return(ljday[month]+day-1);
+ 	}
+ }
+ 
+ /**
+  * compute_date - compute date from number of days since 1900
+  * @days: number of days since 1900
+  * @datetime: struct tm to contain date
+  *
+  * Compute the date give the number of days since 1900.  Will handle negative
+  * days as well for dates < 1900.  Will handle full percision DATETIME8 from
+  * 0000-01-01 to 5881510-07-12
+  *
+  * TODO: - Figure out what standard date format for years BC is and we can
+  *         handle back to about 6 million BC while we are at it.
+  *       - Need to look into division to speed things up but not sure where the
+  *         tradeoff is between looping and division plus leapyear calculation.
+  **/
+ int compute_date(TDS_INT days, struct tm *datetime) {
+ 	int leapyear=0;
+ 	int dom;
+ 	int year;
+ 	const int t[]={0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
+ 
+ 	if (days>=0) {
+ 		datetime->tm_year=1900;
+ 		leapyear=0;
+ 		datetime->tm_mon=0;
+ 		datetime->tm_mday=1;
+ 
+ 		while(days>=365+leapyear) {
+ 			days-=(365+leapyear);
+ 			datetime->tm_year++;
+ 			leapyear=is_leap_year(datetime->tm_year);
+ 		}
+ 		while(days>0) {
+ 			dom=days_in_month(datetime->tm_mon, leapyear);
+ 			if (days<dom) break;
+ 			days-=dom;
+ 			datetime->tm_mon++;
+ 		}
+ 		datetime->tm_mday+=days;
+ 	}
+ 	else {
+ 		datetime->tm_year=1899;
+ 		leapyear=0;
+ 		datetime->tm_mon=11;
+ 		datetime->tm_mday=31;
+ 
+ 		days*=-1;
+ 		days-=1;
+ 		while(days>365+leapyear) {
+ 			days-=(365+leapyear);
+ 			datetime->tm_year--;
+ 			leapyear=is_leap_year(datetime->tm_year);
+ 		}
+ 		while(1) {
+ 			dom=days_in_month(datetime->tm_mon, leapyear);
+ 			if (days<dom) break;
+ 			days-=dom;
+ 			datetime->tm_mon--;
+ 		}
+ 		datetime->tm_mday=dom-days;
+ 	}
+ 	/* Compute day of week */
+ 	
+ 	year=datetime->tm_year;
+ 	year-=datetime->tm_mon<2;
+ 	datetime->tm_wday=((year+year/4-year/100+year/400+t[datetime->tm_mon]+datetime->tm_mday)%7);
+ 	datetime->tm_yday=julian(datetime->tm_year, datetime->tm_mon, datetime->tm_mday);
+ 	datetime->tm_year-=1900;
+ 	return(1);
+ }
+ 
+ /**
+  * compute_time - compute time from 300ths seconds since midnight
+  * @secs: 300ths seconds since midnight
+  * @datetime: struct tm to contain time
+  *
+  * Convert 300th's seconds since midnight to standard hour, minute, seconds
+  * and store it in a struct tm
+  *
+  * TODO: Check to see if division and mod would be faster.
+  **/
+ int compute_time(TDS_INT secs, struct tm *datetime) {
+ 	datetime->tm_hour=0;
+ 	datetime->tm_min=0;
+ 	datetime->tm_sec=0;
+ 
+ 	secs/=300;    /* Reported in 300th's of second */
+ 	if (secs>=0) {
+ 		while(secs>=3600) {
+ 			secs-=3600;
+ 			datetime->tm_hour++;
+ 		}
+ 		while(secs>=60) {
+ 			secs-=60;
+ 			datetime->tm_min++;
+ 		}
+ 		datetime->tm_sec=secs;
+ 	}
+ 	else {
+ 		datetime->tm_hour=23;
+ 		datetime->tm_min=59;
+ 		secs*=-1;
+ 		while(secs>=3600) {
+ 			secs-=3600;
+ 			datetime->tm_hour--;
+ 		}
+ 		while(secs>=60) {
+ 			secs-=60;
+ 			datetime->tm_min--;
+ 		}
+ 		datetime->tm_sec=60-secs;
+ 	}
+ }
+ 
+ /**
+  * tds_convert_datetime - Convert/copy a SYBDATETIME element.
+  * @srctype: Source element type. Should be SYBDATETIME
+  * @src: Source element
+  * @desttype: Destination element type
+  * @dest: Destination element
+  * @destlen: The size of the destination element
+  *
+  * Convert/copy SYBDATETIME to SYBCHAR, SYBVARCHAR, SYBDATETIME or SYBDATETIME4
+  * 
+  * TODO:
+  * Do we want to return TDS_FAIL if srctype is not SYBDATETIME
+  * SYBDATETIME4 should do a bounds check and copy if fits fail otherwise.
+  **/
  TDS_INT tds_convert_datetime(int srctype,unsigned char *src,int desttype,unsigned char *dest,TDS_INT destlen)
  {
! 	TDS_INT dtdays, dttime;
! 	struct tm datetime;
! 	char datestr[20];
! 	 
! 	switch(desttype) {
! 		case SYBCHAR:
! 		case SYBVARCHAR:
! 			if (destlen<0) {
! 				/* What is valid for destlen?  One would think this would core */
! 				memset(dest,' ',30);
! 			} else {
! 				memset(dest,' ',destlen);
! 			}
! 			if (!src) {
! 				*dest='\0';
! 				return 0;
! 			}
! 			memcpy(&dtdays, src, 4);
! 			memcpy(&dttime, src+4, 4);
! 			compute_date(dtdays, &datetime);
! 			compute_time(dttime, &datetime);
! 			strftime(dest, destlen, tdsget_date_format(), &datetime);
! 			/* Return includes terminating NULL */
! 			return(strlen(dest)+1);
! 		case SYBDATETIME:
! 			memcpy(dest,src,sizeof(TDS_DATETIME));
! 			return sizeof(TDS_DATETIME);
! 			break;
! 		case SYBDATETIME4:
! 			/* Bounds check and copy here ? */
! 			break;
! 		default:
! 			return TDS_FAIL;
! 			break;
  	 }
  }
+ /**
+  * tds_convert_datetime4 - Convert/copy a SYBDATETIME4 element.
+  * @srctype: Source element type. Should be SYBDATETIME4
+  * @src: Source element
+  * @desttype: Destination element type
+  * @dest: Destination element
+  * @destlen: The size of the destination element
+  *
+  * Convert/copy SYBDATETIME4 to SYBCHAR, SYBVARCHAR, SYBDATETIME or SYBDATETIME4
+  *
+  * TODO:
+  * Do we want to return TDS_FAIL if srctype is not SYBDATETIME4
+  **/
  TDS_INT tds_convert_datetime4(int srctype,unsigned char *src,int desttype,unsigned char *dest,TDS_INT destlen)
  {
! 	TDS_USMALLINT days, minutes;
! 	struct tm datetime;
! 
! 	switch(desttype) {
! 		case SYBCHAR:
! 		case SYBVARCHAR:
! 			if (destlen<0) {
! 				/* What is valid for destlen?  One would think this would core */
! 				memset(dest,' ',30);
! 			} else {
! 				memset(dest,' ',destlen);
! 			}
! 			if (!src) {
! 				*dest='\0';
! 				return 0;
! 			}
! 			memcpy(&days, src, 2);
! 			memcpy(&minutes, src+2, 2);
! 			tdsdump_log("%L inside tds_convert_datetime4() days = %d minutes = %d\n", days, minutes);
! 			compute_date(days, &datetime);
! 			compute_time(minutes, &datetime);
! 			strftime(dest, destlen-1, tdsget_date_format(), &datetime);
! 			/* Return includes terminating NULL */
! 			return(strlen(dest)+1);
! 		case SYBDATETIME:
! 			/* Shouldn't we be able to copy here since we are going to higher
! 			   percision */
! 			break;
! 		case SYBDATETIME4:
! 			memcpy(dest,src,sizeof(TDS_DATETIME4));
! 			return(sizeof(TDS_DATETIME4));
! 		default:
! 			return TDS_FAIL;
! 			break;
  	}
  }
  TDS_INT tds_convert_real(int srctype,unsigned char *src,int desttype,unsigned
  char *dest,TDS_INT destlen)
***************
*** 676,681 ****
--- 914,936 ----
  	}
  	return TDS_FAIL;
  }
+ 
+ /**
+  * tds_convert - Convert/copy a SYB element
+  * @srctype: Source element type
+  * @src: Source element
+  * @desttype: Destination element type
+  * @dest: Destination element
+  * @destlen: The size of the destination element
+  *
+  * Convienence function to conver from one type to another.  Currently handles
+  * source types of SYBCHAR, SYBVARCHAR, SYBNVARCHAR, SYBMONEY, SYBMONEY4,
+  * SYBNUMERIC, SYBBIT, SYBINT1, SYBINT2, SYBINT4, SYBREAL, SYBFLT8, SYBDATETIME,
+  * SYBDATETIME4, SYBVARBINARY, SYBBINARY, SYBTEXT
+  *
+  * TODO:
+  * Complete mappings for all types to other possible types.
+  **/
  TDS_INT tds_convert(int srctype,
  		TDS_CHAR *src,
  		TDS_UINT srclen,
***************
*** 700,705 ****
--- 955,972 ----
  			return tds_convert_money(srctype,src,
  				desttype,dest,destlen);
  			break;
+ 		case SYBMONEYN:
+ 			if (srclen==4) {
+ 				return(tds_convert_money4(SYBMONEY4, src, srclen, desttype, dest, destlen));
+ 			}
+ 			else if (srclen==8) {
+ 				return(tds_convert_money(SYBMONEY, src, desttype, dest, destlen));
+ 			}
+ 			else if (srclen==0) {
+ 				memset(dest, 0, destlen);
+ 				return(TDS_FAIL);
+ 			}
+ 			else return(TDS_FAIL);
  		case SYBNUMERIC:
  			return tds_convert_numeric(srctype,(TDS_NUMERIC *) src,srclen,
  				desttype,dest,destlen);
***************
*** 720,725 ****
--- 987,1010 ----
  			return tds_convert_int4(srctype,src,
  				desttype,dest,destlen);
  			break;
+ 		case SYBINTN:
+ 			if (srclen==1) {
+ 				return tds_convert_int1(SYBINT1, src, desttype, dest, destlen);
+ 			}
+ 			else if (srclen==2) {
+ 				return tds_convert_int2(SYBINT2, src, desttype, dest, destlen);
+ 			}
+ 			else if (srclen==4) {
+ 				return tds_convert_int4(SYBINT4, src, desttype, dest, destlen);
+ 			}
+ 			else if (srclen==0) {
+ 				memset(dest, 0, destlen);
+ 				return(TDS_FAIL);
+ 			}
+ 			else {
+ 				return(TDS_FAIL);
+ 			}
+ 			break;
  		case SYBREAL:
  			return tds_convert_real(srctype,src,
  				desttype,dest,destlen);
***************
*** 736,741 ****
--- 1021,1038 ----
  			return tds_convert_datetime4(srctype,src,
  				desttype,dest,destlen);
  			break;
+ 		case SYBDATETIMN:
+ 			if (srclen==8) {
+ 				return(tds_convert_datetime(SYBDATETIME, src, desttype, dest, destlen));
+ 			}
+ 			else if (srclen==4) {
+ 				return(tds_convert_datetime4(SYBDATETIME4, src, desttype, dest, destlen));
+ 			}
+ 			else if (srclen==0) {
+ 				memset(dest, 0, destlen);
+ 				return(TDS_FAIL);
+ 			}
+ 			else return(TDS_FAIL);
  		case SYBVARBINARY:
  			varbin = (TDS_VARBINARY *)src;
  			return tds_convert_binary(srctype,varbin->array,
Index: src/tds/login.c
===================================================================
RCS file: /Repository/freetds/src/tds/login.c,v
retrieving revision 1.24
diff -c -r1.24 login.c
*** login.c	2000/03/13 12:56:47	1.24
--- login.c	2000/07/18 18:53:15
***************
*** 139,144 ****
--- 139,147 ----
  	/* end */
  
  
+ 	/* Set the default date format.  I don't think this should really occur
+ 	** here but it had to go someplace */
+ 	tdsset_date_format("%b %d %Y %i:%M%p");
  	memcpy(tds->capabilities,login->capabilities,TDS_MAX_CAPABILITY);
  	if (!login->port) {
  		get_server_info(login->server_name, ip_addr, ip_port, tds_ver);
Index: src/tds/util.c
===================================================================
RCS file: /Repository/freetds/src/tds/util.c,v
retrieving revision 1.14
diff -c -r1.14 util.c
*** util.c	2000/03/13 12:56:48	1.14
--- util.c	2000/07/18 18:53:17
***************
*** 50,55 ****
--- 50,57 ----
  
  
  static char interf_file[MAXPATH];
+ /* More of the evil static variables - Not thread safe should go in a context */
+ static char *tds_date_format=NULL;
  
  void tds_set_parent(TDSSOCKET* tds, void* the_parent)
  {
***************
*** 587,591 ****
--- 589,615 ----
  #endif
      };
  /* Jeff's hack ***NEW CODE END**** */
+ 
+ /**
+  * tdsset_date_format - Set the strftime style date format string.
+  * @datefmt: date format
+  *
+  * Set the strftime style date format string to be used for datetime to string
+  * conversions.
+  **/
+ void tdsset_date_format(const char *datefmt) {
+ 	if (tds_date_format!=NULL) free(tds_date_format);
+ 	tds_date_format=strdup(datefmt);
+ }
+ 
+ /**
+  * tdsget_date_format - Return the strftime style date format string
+  * 
+  * Return the strftime style date format string.  The format can be set with
+  * tdsset_date_format(datefmt)
+  **/
+ char *tdsget_date_format() {
+ 	return(tds_date_format);
+ }
  
  


More information about the FreeTDS mailing list