由于原来写的日期相加函数,处理的日期来自ORACLE数据库,得到的结果最后也得存到数据库里。
ORACLE里是分开相加的,可以先加年,再加月,再加天。年的结果与天的结果相加都相同,但是加月的时候,结果就不相同了!造成这个情况的原因是函数没有像数据库那样处理月末这天的特殊性。
ORACLE数据库是这样处理月末的:
1.如果加上某个月份前(就是源日期),当天是该月的月末,则不管加上几个月,都把结果设置为此月的月末这天。
如:020131加上一个月,则其结果是020128;
另:020228加上一个月,结果为:020331。
2.如果加上某个月份后(就是结果日期),原来的月里的天数已经超过了当月的最大天数,则自动把天数写成当月的月末。
如:020129,加上一个月,不是020229,而是020228,虽然020129不是一月份的月末,但是29号超过了2月的最大天数。
根据这两点,我在程序里加入了一点判断,得到的结果就与ORACLE的相同了!
改进后函数如下:
#define TRUE 1
#define true 1
#define false 0
#define SUCCESS 0
#include <stdio.h>
unsigned char *asc_bcd(unsigned char *Ptd ,unsigned char Lgd,unsigned char *Pts,unsigned char Lgs)
{
unsigned char I;
if( Lgd > Lgs/2)
{
memset( Ptd, 0x00, Lgd ) ;
Ptd = Ptd + Lgd - ((Lgs + 1) / 2) ;
}
for ( I = 0 ; I < ((Lgs+1) / 2) ; I++)
{
if ( (!(Lgs % 2) && !I) || I ) *Ptd = (*Pts++ << 4) & 0xF0 ;
*Ptd = *Ptd + (*Pts++ & 0x0F) ;
Ptd++ ;
}
return((unsigned char*)Ptd);
}
unsigned long bcd_long( unsigned char *Pts, unsigned char Ls )
{
unsigned char I,Oct;
unsigned long Lg1,Lg2;
Lg1 = 0 ;
Lg2 = 1 ;
Pts += (Ls+1)/2;
for (I = 0; I< Ls ; I++)
{
if ( I % 2) Oct = (*Pts >> 4 ) & 0x0F;
else Oct = *--Pts & 0x0F;
Lg1 += Lg2 * Oct ;
if (Lg2 == 1000000000L ) Lg2 = 0 ;
else Lg2 *= 10 ;
}
return (Lg1) ;
}
unsigned char *long_bcd(unsigned char *Ptd ,unsigned char Lgd,unsigned long *Pts)
{
unsigned int I;
unsigned char *Pt0,Tb[5];
unsigned long Lg1,Lg2;
Lg1 = *Pts;
Lg2 = 100000000L ;
for (I = 0; I< 5; I++)
{
Tb[I] = (unsigned char)(Lg1 / Lg2) ;
Tb[I] = ((Tb[I] / 10 ) << 4 ) + (Tb[I] % 10);
Lg1 = Lg1 % Lg2;
Lg2 = Lg2 / 100;
}
memset( Ptd, 0x00, Lgd ) ;
Ptd += Lgd ;
Pt0 = Ptd ;
if ( Lgd > 5) Lgd = 5 ;
for ( I=0; I < Lgd;I++) *--Ptd = Tb[4-I] ;
return((unsigned char*)Pt0);
}
unsigned char UTIL_IsLeapYear(unsigned short Year)
{
if( (Year%4) != 0)
{
return false;
}else
{
if( (Year%100) != 0)
{
return true;
}else
{
if( (Year%400) != 0)
return false;
else
return true;
}
}
}
unsigned char UTIL_Date_Add(unsigned char *pucSrcDate,unsigned char *pucAddDate,unsigned char *pucDesDate)
{
unsigned char ucYear,ucMonth,ucDay;
unsigned char ucAddYear,ucAddMonth,ucAddDay;
unsigned char ucYearResult,ucMonthResult,ucDayResult;
unsigned char ucMonthTmp,ucDayTmp;
unsigned char ucDayPerMonth,ucMonthToYear;
unsigned short uiYear;
unsigned long ulYear,ulMonth,ulDay;
unsigned char ucFirstFlag;
unsigned char ucLastDayFlag;
ucYear = bcd_long(pucSrcDate,2);
ucMonth = bcd_long(pucSrcDate+1,2);
ucDay = bcd_long(pucSrcDate+2,2);
printf("Source Date: Year=%2d,Month=%2d,Day=%2d/n",ucYear,ucMonth,ucDay);
ucAddYear = bcd_long(pucAddDate,2);
ucAddMonth = bcd_long(pucAddDate+1,2);
ucAddDay = bcd_long(pucAddDate+2,2);
printf("Dest Date: AddYear=%2d,AddMonth=%2d,AddDay=%2d/n",ucAddYear,ucAddMonth,ucAddDay);
if(ucMonth>12)
return 1;
if(ucDay>31)
return 2;
ucYearResult = ucYear + ucAddYear;
ucMonthResult = ucMonth + ucAddMonth;
if(ucMonthResult > 12)
{
ucYearResult += (ucMonthResult / 12); //如果月相加的结果大于12,则向前进位一年
}
uiYear = 2000 + ucYear;//原始年的数据
ucMonthTmp = ucMonth;//第一次计算原月份的天数,看是否最后一天
ucFirstFlag = 1;
ucLastDayFlag = false;
do{
switch(ucMonthTmp)
{
case 0://12月
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
ucDayPerMonth = 31;
break;
case 4:
case 6:
case 9:
case 11:
ucDayPerMonth = 30;
break;
case 2:
if(UTIL_IsLeapYear(uiYear)==TRUE)//用完整表示的年计算是不是闰年
ucDayPerMonth = 29;
else
ucDayPerMonth = 28;
break;
default:
break;
}
if(ucFirstFlag == 1)
{//输入的年份的情况
ucFirstFlag = 2;
if(ucDay == ucDayPerMonth)//2月28号加一个月的情况
ucLastDayFlag = true;
uiYear = 2000 + ucYearResult;
continue;
}else if(ucFirstFlag == 2)
{//年份相加,月份相加后的情况
ucFirstFlag = 3;
ucMonthTmp = ucMonthResult % 12;
if(ucDay > ucDayPerMonth)//1月29号加一个月的情况
ucDay = ucDayPerMonth;
if(ucLastDayFlag == true)//2月28号加一个月的情况
ucDay = ucDayPerMonth;
ucDayTmp = ucDay + ucAddDay;//最后算得的天数
continue;
}
if(ucDayTmp > ucDayPerMonth)
{
ucDayTmp = ucDayTmp - ucDayPerMonth;
ucMonthTmp ++;
ucMonthTmp %= 12;
if(ucMonthTmp == 1)
{
uiYear++;//如果当月是12月,且天数大于31,则年进位
if(uiYear>2099)
return 3;
}
}
else
{
break;
}
}
while(1);
ucMonthResult = ucMonthTmp;
if(ucMonthResult == 0)//如果是0表示是12月
ucMonthResult = 12;
ucDayResult = ucDayTmp;
ucYearResult = uiYear - 2000;
printf("/nResult: Year=%2d,Month=%2d,Day=%2d/n",ucYearResult,ucMonthResult,ucDayResult);
ulYear = ucYearResult;
ulMonth = ucMonthResult;
ulDay = ucDayResult;
long_bcd(pucDesDate,1,&ulYear);
long_bcd(pucDesDate+1,1,&ulMonth);
long_bcd(pucDesDate+2,1,&ulDay);
return SUCCESS;
}
int main()
{
unsigned char aucSrcDate[7],aucAddDate[7],aucSrcDateTmp[4],aucAddDateTmp[4],aucDesDate[4];
unsigned char ucFlag = 1;
unsigned char ucResult,ucI;
printf("/n----------------------------------------------------/n");
printf(" Add Year Function Test/n");
printf("----------------------------------------------------/n");
while(ucFlag)
{
memset(aucSrcDate,0,sizeof(aucSrcDate));
memset(aucAddDate,0,sizeof(aucAddDate));
memset(aucSrcDateTmp,0,sizeof(aucSrcDateTmp));
memset(aucAddDateTmp,0,sizeof(aucAddDateTmp));
memset(aucDesDate,0,sizeof(aucDesDate));
printf("/nPlease Input Now Date with Format YYMMDD:/n");
scanf("%s",aucSrcDate);
printf("/n");
printf("Please Input You Want Add Date with Format YYMMDD:/n");
scanf("%s",aucAddDate);
printf("/n");
asc_bcd(aucSrcDateTmp,3,aucSrcDate,6);
asc_bcd(aucAddDateTmp,3,aucAddDate,6);
ucResult = UTIL_Date_Add(aucSrcDateTmp,aucAddDateTmp,aucDesDate);
printf("/n/n====================================/n");
for(ucI=0;ucI<4;ucI++)
printf("%02x ",aucDesDate[ucI]);
printf("/n/n====================================/n");
if(ucResult == 0)
printf("/n==============================================/nPlease Manual Count The Result is Right or Not!/n");
if(ucResult == 1)
printf("/n!!!Error!! Error Input Month!Please Check!/n");
if(ucResult ==2)
printf("/n!!!Error !! Error Input Day Value,Please Check!/n");
if(ucResult == 3)
printf("/n!!!Error!! Error Year Value,Please Re Input!/n");
printf("/n-----------------------------------------------/n");
printf("If You Want To Continue?(Y/N)");
ucFlag = getch();
if(ucFlag == 'Y'|| ucFlag=='y')
ucFlag = 1;
else
ucFlag = 0;
}
return 0;
}
虽然这样处理后,误差会小很些,但是,由于数据库每次只能加天或加月,这样的结果就与相加的顺序有关。
比如:060130 + 000102
如果先加月:060130 加一个月结果是:060228,再加2天,结果是:060302
如果反过来,先加天:060130 加两天结果:060201,再加一个月:060301,这样就会有一天的误差了!
但是上面改进过的程序,对于加年来说,就不会产生误差,因为每次加过年后会检查日期有效性,把产生误差的因素消除了!
ORACLE里是分开相加的,可以先加年,再加月,再加天。年的结果与天的结果相加都相同,但是加月的时候,结果就不相同了!造成这个情况的原因是函数没有像数据库那样处理月末这天的特殊性。
ORACLE数据库是这样处理月末的:
1.如果加上某个月份前(就是源日期),当天是该月的月末,则不管加上几个月,都把结果设置为此月的月末这天。
如:020131加上一个月,则其结果是020128;
另:020228加上一个月,结果为:020331。
2.如果加上某个月份后(就是结果日期),原来的月里的天数已经超过了当月的最大天数,则自动把天数写成当月的月末。
如:020129,加上一个月,不是020229,而是020228,虽然020129不是一月份的月末,但是29号超过了2月的最大天数。
根据这两点,我在程序里加入了一点判断,得到的结果就与ORACLE的相同了!
改进后函数如下:
#define TRUE 1
#define true 1
#define false 0
#define SUCCESS 0
#include <stdio.h>
unsigned char *asc_bcd(unsigned char *Ptd ,unsigned char Lgd,unsigned char *Pts,unsigned char Lgs)
{
unsigned char I;
if( Lgd > Lgs/2)
{
memset( Ptd, 0x00, Lgd ) ;
Ptd = Ptd + Lgd - ((Lgs + 1) / 2) ;
}
for ( I = 0 ; I < ((Lgs+1) / 2) ; I++)
{
if ( (!(Lgs % 2) && !I) || I ) *Ptd = (*Pts++ << 4) & 0xF0 ;
*Ptd = *Ptd + (*Pts++ & 0x0F) ;
Ptd++ ;
}
return((unsigned char*)Ptd);
}
unsigned long bcd_long( unsigned char *Pts, unsigned char Ls )
{
unsigned char I,Oct;
unsigned long Lg1,Lg2;
Lg1 = 0 ;
Lg2 = 1 ;
Pts += (Ls+1)/2;
for (I = 0; I< Ls ; I++)
{
if ( I % 2) Oct = (*Pts >> 4 ) & 0x0F;
else Oct = *--Pts & 0x0F;
Lg1 += Lg2 * Oct ;
if (Lg2 == 1000000000L ) Lg2 = 0 ;
else Lg2 *= 10 ;
}
return (Lg1) ;
}
unsigned char *long_bcd(unsigned char *Ptd ,unsigned char Lgd,unsigned long *Pts)
{
unsigned int I;
unsigned char *Pt0,Tb[5];
unsigned long Lg1,Lg2;
Lg1 = *Pts;
Lg2 = 100000000L ;
for (I = 0; I< 5; I++)
{
Tb[I] = (unsigned char)(Lg1 / Lg2) ;
Tb[I] = ((Tb[I] / 10 ) << 4 ) + (Tb[I] % 10);
Lg1 = Lg1 % Lg2;
Lg2 = Lg2 / 100;
}
memset( Ptd, 0x00, Lgd ) ;
Ptd += Lgd ;
Pt0 = Ptd ;
if ( Lgd > 5) Lgd = 5 ;
for ( I=0; I < Lgd;I++) *--Ptd = Tb[4-I] ;
return((unsigned char*)Pt0);
}
unsigned char UTIL_IsLeapYear(unsigned short Year)
{
if( (Year%4) != 0)
{
return false;
}else
{
if( (Year%100) != 0)
{
return true;
}else
{
if( (Year%400) != 0)
return false;
else
return true;
}
}
}
unsigned char UTIL_Date_Add(unsigned char *pucSrcDate,unsigned char *pucAddDate,unsigned char *pucDesDate)
{
unsigned char ucYear,ucMonth,ucDay;
unsigned char ucAddYear,ucAddMonth,ucAddDay;
unsigned char ucYearResult,ucMonthResult,ucDayResult;
unsigned char ucMonthTmp,ucDayTmp;
unsigned char ucDayPerMonth,ucMonthToYear;
unsigned short uiYear;
unsigned long ulYear,ulMonth,ulDay;
unsigned char ucFirstFlag;
unsigned char ucLastDayFlag;
ucYear = bcd_long(pucSrcDate,2);
ucMonth = bcd_long(pucSrcDate+1,2);
ucDay = bcd_long(pucSrcDate+2,2);
printf("Source Date: Year=%2d,Month=%2d,Day=%2d/n",ucYear,ucMonth,ucDay);
ucAddYear = bcd_long(pucAddDate,2);
ucAddMonth = bcd_long(pucAddDate+1,2);
ucAddDay = bcd_long(pucAddDate+2,2);
printf("Dest Date: AddYear=%2d,AddMonth=%2d,AddDay=%2d/n",ucAddYear,ucAddMonth,ucAddDay);
if(ucMonth>12)
return 1;
if(ucDay>31)
return 2;
ucYearResult = ucYear + ucAddYear;
ucMonthResult = ucMonth + ucAddMonth;
if(ucMonthResult > 12)
{
ucYearResult += (ucMonthResult / 12); //如果月相加的结果大于12,则向前进位一年
}
uiYear = 2000 + ucYear;//原始年的数据
ucMonthTmp = ucMonth;//第一次计算原月份的天数,看是否最后一天
ucFirstFlag = 1;
ucLastDayFlag = false;
do{
switch(ucMonthTmp)
{
case 0://12月
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
ucDayPerMonth = 31;
break;
case 4:
case 6:
case 9:
case 11:
ucDayPerMonth = 30;
break;
case 2:
if(UTIL_IsLeapYear(uiYear)==TRUE)//用完整表示的年计算是不是闰年
ucDayPerMonth = 29;
else
ucDayPerMonth = 28;
break;
default:
break;
}
if(ucFirstFlag == 1)
{//输入的年份的情况
ucFirstFlag = 2;
if(ucDay == ucDayPerMonth)//2月28号加一个月的情况
ucLastDayFlag = true;
uiYear = 2000 + ucYearResult;
continue;
}else if(ucFirstFlag == 2)
{//年份相加,月份相加后的情况
ucFirstFlag = 3;
ucMonthTmp = ucMonthResult % 12;
if(ucDay > ucDayPerMonth)//1月29号加一个月的情况
ucDay = ucDayPerMonth;
if(ucLastDayFlag == true)//2月28号加一个月的情况
ucDay = ucDayPerMonth;
ucDayTmp = ucDay + ucAddDay;//最后算得的天数
continue;
}
if(ucDayTmp > ucDayPerMonth)
{
ucDayTmp = ucDayTmp - ucDayPerMonth;
ucMonthTmp ++;
ucMonthTmp %= 12;
if(ucMonthTmp == 1)
{
uiYear++;//如果当月是12月,且天数大于31,则年进位
if(uiYear>2099)
return 3;
}
}
else
{
break;
}
}
while(1);
ucMonthResult = ucMonthTmp;
if(ucMonthResult == 0)//如果是0表示是12月
ucMonthResult = 12;
ucDayResult = ucDayTmp;
ucYearResult = uiYear - 2000;
printf("/nResult: Year=%2d,Month=%2d,Day=%2d/n",ucYearResult,ucMonthResult,ucDayResult);
ulYear = ucYearResult;
ulMonth = ucMonthResult;
ulDay = ucDayResult;
long_bcd(pucDesDate,1,&ulYear);
long_bcd(pucDesDate+1,1,&ulMonth);
long_bcd(pucDesDate+2,1,&ulDay);
return SUCCESS;
}
int main()
{
unsigned char aucSrcDate[7],aucAddDate[7],aucSrcDateTmp[4],aucAddDateTmp[4],aucDesDate[4];
unsigned char ucFlag = 1;
unsigned char ucResult,ucI;
printf("/n----------------------------------------------------/n");
printf(" Add Year Function Test/n");
printf("----------------------------------------------------/n");
while(ucFlag)
{
memset(aucSrcDate,0,sizeof(aucSrcDate));
memset(aucAddDate,0,sizeof(aucAddDate));
memset(aucSrcDateTmp,0,sizeof(aucSrcDateTmp));
memset(aucAddDateTmp,0,sizeof(aucAddDateTmp));
memset(aucDesDate,0,sizeof(aucDesDate));
printf("/nPlease Input Now Date with Format YYMMDD:/n");
scanf("%s",aucSrcDate);
printf("/n");
printf("Please Input You Want Add Date with Format YYMMDD:/n");
scanf("%s",aucAddDate);
printf("/n");
asc_bcd(aucSrcDateTmp,3,aucSrcDate,6);
asc_bcd(aucAddDateTmp,3,aucAddDate,6);
ucResult = UTIL_Date_Add(aucSrcDateTmp,aucAddDateTmp,aucDesDate);
printf("/n/n====================================/n");
for(ucI=0;ucI<4;ucI++)
printf("%02x ",aucDesDate[ucI]);
printf("/n/n====================================/n");
if(ucResult == 0)
printf("/n==============================================/nPlease Manual Count The Result is Right or Not!/n");
if(ucResult == 1)
printf("/n!!!Error!! Error Input Month!Please Check!/n");
if(ucResult ==2)
printf("/n!!!Error !! Error Input Day Value,Please Check!/n");
if(ucResult == 3)
printf("/n!!!Error!! Error Year Value,Please Re Input!/n");
printf("/n-----------------------------------------------/n");
printf("If You Want To Continue?(Y/N)");
ucFlag = getch();
if(ucFlag == 'Y'|| ucFlag=='y')
ucFlag = 1;
else
ucFlag = 0;
}
return 0;
}
虽然这样处理后,误差会小很些,但是,由于数据库每次只能加天或加月,这样的结果就与相加的顺序有关。
比如:060130 + 000102
如果先加月:060130 加一个月结果是:060228,再加2天,结果是:060302
如果反过来,先加天:060130 加两天结果:060201,再加一个月:060301,这样就会有一天的误差了!
但是上面改进过的程序,对于加年来说,就不会产生误差,因为每次加过年后会检查日期有效性,把产生误差的因素消除了!