按月、按天计算失效日期的代码实现

【背景】:在我们计算截止日期、失效时期的时候,可能存在按年、按月、按天统计失效的情况。比如:当前日期是2014-12-22,900天后失效,失效日期是多少?17个月后失效,失效日期是多少。通过本文源码,你都可以得到答案。

为验证程序的正确性,本文对每个接口函数都做了大量的测试用例。

 

// sn_ctrl.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <assert.h>
#include <iostream>
using namespace std;

const int MAX_MONTH_CNTS_IN_YEAR = 12;

/*
**@brief:判定年份是否为闰年.
**@param: iYear当前年份.
**@return:true,闰年; false,平年.
*/
bool IsLeapYear(unsigned int iYear)
{
if ( (iYear%4 == 0 && iYear%100 != 0) || (iYear%400 == 0) )
{
return true;
}
else
{
return false;
}
}

/*
**@brief:判定某年某月的天数.
**@param: iYear当前年份,iMonth当前月份;
**@return:当前年、月的天数.
*/
unsigned int daysInMonth(unsigned int iYear, unsigned int iMonth)
{
assert(iMonth >=1 && iMonth <= MAX_MONTH_CNTS_IN_YEAR);
int iDaysInMonth = 0;
switch(iMonth)
{
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
iDaysInMonth = 31;
break;
case 4:
case 6:
case 9:
case 11:
iDaysInMonth = 30;
break;
case 2:
if (IsLeapYear(iYear))
{
iDaysInMonth = 29;
}
else
{
iDaysInMonth = 28;
}
break;
default:
cout << "Error Month!!" << endl;
break;
}
return iDaysInMonth;
}


struct Date
{
unsigned int date_year;
unsigned int date_month;
unsigned int date_day;
};

class DateOp
{
public:
DateOp(){}
DateOp(Date thisDate):theDate(thisDate){}
virtual ~DateOp(){}
Date DataAddmonths(Date date_init, unsigned int month_cnts);
Date DateAddDays(Date date_init, unsigned int day_cnts);

private:
Date theDate;
};

/*
**@brief:初始月份加几个月后的日期.
**@param: dateInit初始日期,iMonthCnts过几个月;
**@return:计算后的新的日期.
*/
Date DateOp::DataAddmonths(Date dateInit, unsigned int iMonthCnts)
{
assert (iMonthCnts >= 1);
Date dateAfterAdd = dateInit;

unsigned int iMonthNewPos = dateInit.date_month + iMonthCnts;
if (iMonthNewPos > MAX_MONTH_CNTS_IN_YEAR) //求和后超过12
{
unsigned int iYearCnts = (iMonthNewPos)/(MAX_MONTH_CNTS_IN_YEAR);
unsigned int iMonthNew = (iMonthNewPos)%(MAX_MONTH_CNTS_IN_YEAR);
if (0 == iMonthNew) //月份为0特殊处理
{
dateAfterAdd.date_year = dateInit.date_year + iYearCnts -1;
iMonthNew = 12;
}
else
{
dateAfterAdd.date_year = dateInit.date_year + iYearCnts;
}
dateAfterAdd.date_month = iMonthNew;

}
else //求和后不超过12
{
dateAfterAdd.date_year = dateInit.date_year;
dateAfterAdd.date_month = dateInit.date_month + iMonthCnts;
}

//2月份特殊处理
if ( (30 == dateInit.date_day || 31 == dateInit.date_day) && (2 == dateAfterAdd.date_month))
{
dateAfterAdd.date_day = daysInMonth(dateAfterAdd.date_year, dateAfterAdd.date_month);
}
//30天的月份特殊处理
else if ((31 == dateInit.date_day) && ((4 == dateAfterAdd.date_month) || (6 == dateAfterAdd.date_month) ||
(9 == dateAfterAdd.date_month) || (11 == dateAfterAdd.date_month)))
{
dateAfterAdd.date_day = 30;
}
else
{
dateAfterAdd.date_day = dateInit.date_day;
}
return dateAfterAdd;
}

/*
**@brief:初始月份加*天后的日期.
**@param: dateInit初始日期,iMonthCnts过多少天;
**@return:计算后的新的日期.
*/
Date DateOp::DateAddDays(Date dateInit, unsigned int day_cnts)
{
assert(day_cnts >= 1);
cout << "+ " << day_cnts << endl;
Date dateAfterAdd = dateInit;

unsigned int iCurDaysInMonth = daysInMonth(dateInit.date_year, dateInit.date_month); //当前月的总天数.
//cout << "iCurDaysInMonth = " << iCurDaysInMonth << endl;
int iDayTotal = day_cnts;
if (dateInit.date_day + day_cnts <= iCurDaysInMonth)
{
dateAfterAdd.date_day = dateInit.date_day + day_cnts;
return dateAfterAdd;
}

int iLeftDaysInCurMonth = iCurDaysInMonth - dateInit.date_day; //当前月剩余天数.
//cout << endl << "iDayTotal = " << iDayTotal << "\t iLeftDayInCurmonth = " << iLeftDaysInCurMonth << endl << endl;
//cout << "iAvgMonthCnts = " << iAvgMonthCnts << endl << endl;

unsigned int iDaysInMonth = 0;
int iLeftDaysTotal = 0; //统计大约几个月的实际总天数.
unsigned int iMonthCnts = 0;
while (iLeftDaysTotal <= iDayTotal - iLeftDaysInCurMonth)
{
++iMonthCnts;
unsigned int iCurMonth = dateInit.date_month + iMonthCnts;
// cout << "iCurMonth = " << iCurMonth << endl;
unsigned int iYearCnts = 0;
if (iCurMonth > 12)
{
iYearCnts = (iCurMonth)/(MAX_MONTH_CNTS_IN_YEAR);
}

//cout << "iYearCnts = " << iYearCnts << endl;

if (0 == iYearCnts)
{
dateAfterAdd.date_year = dateInit.date_year;
dateAfterAdd.date_month = iCurMonth;
iDaysInMonth = daysInMonth(dateInit.date_year, iCurMonth);
iLeftDaysTotal += iDaysInMonth;
}
else
{
unsigned int iMonthNew = (iCurMonth)%(MAX_MONTH_CNTS_IN_YEAR);
if (0 == iMonthNew) //月份为0特殊处理
{
dateAfterAdd.date_year = dateInit.date_year + iYearCnts -1;
iMonthNew = 12;
}
else
{
dateAfterAdd.date_year = dateInit.date_year + iYearCnts;
}
dateAfterAdd.date_month = iMonthNew;
iDaysInMonth = daysInMonth(dateAfterAdd.date_year, iMonthNew);
iLeftDaysTotal += iDaysInMonth;

}//end else
//对于超出的做特殊处理.
if (iLeftDaysTotal > (iDayTotal - iLeftDaysInCurMonth))
{
iLeftDaysTotal -= iDaysInMonth;
break;
}
}

//cout << "iDayTotal =" << iDayTotal << "\tiLeftDaysInCurMonth = " << iLeftDaysInCurMonth //bug??
//<< "\tiLeftDaysTotal = " << iLeftDaysTotal << endl;
if (iDayTotal - iLeftDaysInCurMonth - iLeftDaysTotal < 0)
{
dateAfterAdd.date_day = iDayTotal - iLeftDaysInCurMonth;
}
else
{
dateAfterAdd.date_day = iDayTotal - iLeftDaysInCurMonth - iLeftDaysTotal;
}

if (dateAfterAdd.date_month > 12)
{
dateAfterAdd.date_year += 1;
dateAfterAdd.date_month = 1;
}
if (dateAfterAdd.date_day == 0)
{
if (1 == dateAfterAdd.date_month)
{
dateAfterAdd.date_month = 12;
dateAfterAdd.date_year -= 1;
}
else
{
dateAfterAdd.date_month -= 1;
}

dateAfterAdd.date_day = daysInMonth(dateAfterAdd.date_year, dateAfterAdd.date_month);
}
assert((dateAfterAdd.date_month >= 1) && (dateAfterAdd.date_month <= 12));
assert((dateAfterAdd.date_day >= 1) && (dateAfterAdd.date_day <= 31));
return dateAfterAdd;
}

/*
**@brief:打印日期格式.
**@param: theDate提供打印的日期
**@return:空.
*/
void DisplayDate(Date& theDate)
{
cout << theDate.date_year << "/" << theDate.date_month
<< "/" << theDate.date_day << "\t";
cout << endl << endl;
}


/*
**@brief:测试用例:测试某年是否为闰年.
**@param: 空.
**@return:空.
*/
void testLeapYearOrNot()
{
unsigned int iBeginYear=1900;
unsigned int iEndYear=3000;

cout << "From " << iBeginYear << " To " << iEndYear << endl;

for (unsigned int iYear = iBeginYear; iYear <= iEndYear; ++iYear)
{
if (IsLeapYear(iYear))
{
cout << iYear << "\t";
}
}
cout << endl;
}


/*
**@brief:单元测试用例,某年某月有多少天.
**@param: 空.
**@return:空.
*/
void testDaysInMonth()
{
unsigned int iBeginYear = 1980;
unsigned int iEndYear = 2020;
unsigned int iBeginMonth = 1;
unsigned int iEndMonth = 12;
unsigned int iDaysOfYear = 0;

for (unsigned int iYear = iBeginYear; iYear <= iEndYear; ++iYear)
{
iDaysOfYear = 0;
cout << "Year:" << iYear << endl;
for (unsigned int iMonth = iBeginMonth; iMonth <= iEndMonth; ++iMonth)
{

unsigned int iDaysInmonth = daysInMonth(iYear,iMonth);
iDaysOfYear += iDaysInmonth;
cout << iDaysInmonth << "\t";
}

cout << endl;
cout << iYear << " has " << iDaysOfYear << " days!" << endl << endl;
}
}

/*
**@brief:单元测试用例,测试几个月后日期.
**@param: 空.
**@return:空.
*/
void testDataAddmonths()
{
unsigned int iBeginYear = 2014;
unsigned int iEndYear = 2015;

unsigned int iBeginMonths = 1;
unsigned int iEndMonths = 30;

Date beginDate;
beginDate.date_year = 1980;
beginDate.date_month = 1;
beginDate.date_day = 31;
Date endDate = beginDate;
DateOp dateOp;

for (unsigned int iYear = iBeginYear; iYear <= iEndYear; ++iYear)
{
beginDate.date_year = iYear;
(void)DisplayDate(beginDate);
cout << endl;
for (unsigned int iMonth = 1; iMonth <= MAX_MONTH_CNTS_IN_YEAR; ++iMonth)
{
beginDate.date_month = iMonth;
cout << iMonth << "\t";
for (unsigned int iMonthCnts = iBeginMonths; iMonthCnts <= iEndMonths; ++iMonthCnts)
{
endDate = dateOp.DataAddmonths(beginDate, iMonthCnts);
cout << " +" << iMonthCnts << " ";
(void)DisplayDate(endDate);
}//end for iMonthCnts;
cout << endl << endl;
}//end for iMonth
cout << endl << endl << endl;
}//end for iYear
}

/*
**@brief:单元测试用例,测试给定天后的日期.
**@param: 空.
**@return:空.
*/
void testDataAddDays()
{
Date beginDate;
beginDate.date_year = 1988;
beginDate.date_month = 7;
beginDate.date_day = 19;

DateOp dateOp;
cout << "Init Date is :" << endl;
(void)DisplayDate(beginDate);

Date endDate = beginDate;



endDate = dateOp.DateAddDays(beginDate, 1); //1988-7-20
(void)DisplayDate(endDate);

endDate = dateOp.DateAddDays(beginDate, 12); //1988-7-31
(void)DisplayDate(endDate);


endDate = dateOp.DateAddDays(beginDate, 13); //1988-8-1
(void)DisplayDate(endDate);


endDate = dateOp.DateAddDays(beginDate, 100); //1988-10-27
(void)DisplayDate(endDate);

endDate = dateOp.DateAddDays(beginDate, 200); //1989-2-4
(void)DisplayDate(endDate);

endDate = dateOp.DateAddDays(beginDate, 500); //1989-12-1
(void)DisplayDate(endDate);

endDate = dateOp.DateAddDays(beginDate, 954); //1991-2-28
(void)DisplayDate(endDate);

endDate = dateOp.DateAddDays(beginDate, 955); //1991-3-1
(void)DisplayDate(endDate);

endDate = dateOp.DateAddDays(beginDate, 1000); //1991年4月15日星期一
(void)DisplayDate(endDate);

endDate = dateOp.DateAddDays(beginDate, 2000); //1994年1月9日星期日
(void)DisplayDate(endDate);

endDate = dateOp.DateAddDays(beginDate, 5000); //2002年3月28日星期四
(void)DisplayDate(endDate);

endDate = dateOp.DateAddDays(beginDate, 10000); //2015年12月5日星期六
(void)DisplayDate(endDate);

endDate = dateOp.DateAddDays(beginDate, 20000); //2043年4月22日星期三
(void)DisplayDate(endDate);

}

int _tmain(int argc, _TCHAR* argv[])
{
Date thisDate;
thisDate.date_year = 2014;
thisDate.date_month = 2;
thisDate.date_day = 21;

Date endDate = thisDate;

DateOp thisDateOp(thisDate);
(void)DisplayDate(thisDate);

//不同类型用例测试.
(void)testLeapYearOrNot();
(void)testDaysInMonth();
(void)testDataAddmonths();
(void)testDataAddDays();
return 0;
}

             【按天计算失效日期流程描述】

              第一步:确认当前月份到月末还剩余几天(记作A),如果传入的失效天数<剩余天数,则返回原有年份、原有月份、新增天数即可;

                            如果传入的失效天数>剩余天数,则记录下失效天数-剩余天数A的差值(记作B),进入第二步;

              第二步:循环判定下个月、下下个月....下Next N个月的总天数之和是否>第一步的差值,如果小于,则继续循环判定;

                            如果大于,则统计记录下当前月份、年份,并将月份减去1,并统计出循环的总天数C,进入第三步;

              第三步:计算失效月的具体哪一天,根据B-C的值就是失效对应的那一天。

                       

              【 注意事项】:

              需要对月份、天数进行特殊判定,如1<=月份<=12;1<=天<=31;特定月份2月的平年、闰年处理;以及4,6,9.11 30天的处理。
        

         代码通过VS2010编译通过,测试案例都没有bug。有发现bug可以留言,一起改进完善,谢谢!

     

作者:铭毅天下