深入学习时间魔法已经几度春秋。虽然中间有很多曲折,但是也学习到很多C++的知识点,也让我对C++中的面向对象编程有了一个初步的了解。但是也同时遇到了更多的困惑,比如const,也燃起了我对深入学习C++的热情。
前面几章我们提出了时间刻度的概念,并设计定义了Year,Month等具体实现,同时让时间刻度具备了自我成长的能力。虽然我们现在还只是具备了最基本的咒语,但是完整的时间魔法不知不觉中也慢慢成型了。期待看到它大放异彩的时刻。当然,我们还有最后一片关键的咒语需要补全——Day。
首先我们声明了如下Day的方法结构:
class Day : public TimeScale
{
private:
/// @brief 获取当前年的总天数
/// @return
int daysOfYear();
/// @brief 日期增量
/// @param increment
/// @param yc
/// @param mc
/// @param day
void _dateIncrement(const int increment, int &yc, int &mc, int &day);
/// @brief 日期减量
/// @param decrement
/// @param yc
/// @param mc
/// @param day
void _dateDecrement(const int decrement, int &yc, int &mc, int &day);
protected:
void setValue(int result) override;
void recheckChild() override;
public:
Day(TimeScale *month, int day, int max);
void operator+(const int i) override;
void operator-(const int i) override;
const int lengthOfMonth();
~Day();
};
在现实具体的Day咒语之前,我们需要知道为什么Day的重要性,当然我们也可以说是明白Day的复杂性:
- 首先Day是承上启下的关键,它下接Hour, Minute,Second等规律的时间部分,同时也上接Year,Month等不规则的时间部分。
- 其次Day和Year,Month等不是完全独立的一部分。闰年闰月的存在,导致在年月的增减时候Day无法完全置身事外。比如 2000年2月29日,如果在这个时间基础上减一年,那么得到的时间应该是什么呢?是不是1999年2月28日更合理呢?所以Day随着Year改变而改变了。
- 其次Day本身也是相当复杂的,因为不同的月份,它的天数也是可能不一样的。尤其在Day的增减运算的时候,这一点就体现的非常明显。如果只是加减一天,那么非常简单,但是事实上如果你要加减成千上万天呢?这个恐怕很难想象了!
那么如上所述Day是一个异常复杂的怪物,也是我们时间魔法的关键所在。那么看看我怎么填坑吧。
如果熟悉前面几章的内容的,很多朋友应该知道在时间刻度模型中我们定义了handleCarry的方法来承接进位,这就是我们所谓的承上。那么如何启下呢?也就是说怎么帮助Year告诉Day处理闰年闰月的日期呢?这就需要通过recheckChild实现。当Year改变时间接通过Month的recheckChild来告诉Day你需要自查自纠。同样Month改变时亦是如此。
首先回顾一下Year和Month的recheckChild实现,他们都是使用父级的实现,即:
void datetime::TimeScale::recheckChild()
{
if (child)
{
child->recheckChild();
}
}
其实很简单就是告诉子级刻度,父级刻度有变化。那么Day收到变化后怎么处理呢:
void datetime::Day::recheckChild()
{
int year = 0;
int month = 0;
int day = value;
int daysInMonths[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (parent)
{
month = (*parent);
if (parent->getParent())
{
datetime::TimeScale *grandParent = parent->getParent();
year = (*grandParent);
}
}
if (datetime::isLeapYear(year))
{
daysInMonths[1] = 29;
}
if (day > daysInMonths[month - 1])
{
value = daysInMonths[month - 1];
}
max = daysInMonths[month - 1];
}
Day的recheckChild需要做两件事。一是检查day是否超出当前月份的最大天数,如果是那么需要调整日期为最大天数,用于解决闰年闰月或者从31天月份变道其他月份的问题。二是调整Day所在月份的最大天数,即max的值,虽然max在这里并没有实际的用途。
然后就是实现Day比较复杂的加减运算。首先是Day的加运算,通过实现operator+重载运算,并委托dateIncrement来实现:
void datetime::Day::_dateIncrement(const int increment, int &yc, int &mc, int &day)
{
int year = 0;
int month = 0;
int daysInMonths[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (parent)
{
month = (*parent);
if (parent->getParent())
{
datetime::TimeScale *grandParent = parent->getParent();
year = (*grandParent);
}
}
yc = 0;
mc = 0;
int totalDays = increment + daysOfYear() - 1;
int n400 = totalDays / DAYS_IN_400_YEARS;
totalDays %= DAYS_IN_400_YEARS;
int n100 = totalDays / DAYS_IN_100_YEARS;
totalDays %= DAYS_IN_100_YEARS;
int n4 = totalDays / DAYS_IN_4_YEARS;
totalDays %= DAYS_IN_4_YEARS;
int n1 = 0;
int daysInYear = datetime::isLeapYear(year - n1) ? DAYS_IN_YEAR + 1 : DAYS_IN_YEAR;
while (totalDays >= daysInYear)
{
n1++;
totalDays -= daysInYear;
daysInYear = datetime::isLeapYear(year - n1) ? DAYS_IN_YEAR + 1 : DAYS_IN_YEAR;
}
yc = 400 * n400 + 100 * n100 + 4 * n4 + n1;
if (datetime::isLeapYear(year + yc))
{
daysInMonths[1] = 29;
}
for (int i = 0; i < 12; i++)
{
if (daysInMonths[i] > totalDays)
{
mc = i + 1;
break;
}
totalDays -= daysInMonths[i];
}
mc -= month;
day = totalDays + 1;
}
Day的加法运算与根据毫秒数获取日期的算法很相似,我也曾考虑是否需要提取代码。但是发现实现的细节充斥着很多不同。
首先是关于n1的计算。不在是简单的totalDays / DAYS_IN_YEAR + 1。因为首先时间差并不是从1开始的,所以没有需要+1。其次,存在n1这几年真好存在闰年这种情况,那么就不能简单通过除以365获得,否则将在边界值附近出错。如果是闰年的话,你会发现除以365就会进位,但是事实上闰年有366天,完全不需要进位。所以通过循环减出来实现:
int n1 = 0;
int daysInYear = datetime::isLeapYear(year - n1) ? DAYS_IN_YEAR + 1 : DAYS_IN_YEAR;
while (totalDays >= daysInYear)
{
n1++;
totalDays -= daysInYear;
daysInYear = datetime::isLeapYear(year - n1) ? DAYS_IN_YEAR + 1 : DAYS_IN_YEAR;
}
同时因为从某个具体的日期比如2月28日开始做加减很容易造成错误,所以这里我通常将现有的日期调整为基准日期,比如1月1日。同时将差值补充到加减运算的参数部分:
int datetime::Day::daysOfYear()
{
int year = 0;
int month = 0;
if (parent)
{
month = (*parent);
if (parent->getParent())
{
datetime::TimeScale *grandParent = parent->getParent();
year = (*grandParent);
}
}
int daysInMonths[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (datetime::isLeapYear(year))
{
daysInMonths[1] = 29;
}
int days = 0;
for (int i = 1; i < month; i++)
{
days += daysInMonths[i - 1];
}
days += value;
return days;
}
int totalDays = increment + daysOfYear() - 1;
在获取年数后,剩下的就是月份和日期,这个基本就是getGregorianDatePeriod一致。
然后就是关于日期的减运算。原以为和加运算一样简单,但是我想错。虽然加和减是相反的运算,但是放到时间上就不一定完全相反。但是思路还是比较相似的。首先需要找基准,但是这个基准不能是1月1日了,而是需要以未来的时间12月31日,也就是以当前年份的最后一天时间为基准。之所以选12月31日,是因为如果减一天,你发现以1月1日为基准,那么补全后,差值就是负数。负数让日期运算的方向发生反转,增加了算法复杂度。所以选择了12月31号:
int totalDays = decrement + DAYS_IN_YEAR - daysOfYear();
年数的获取与加运算一样,在这里不再重复。剩下月份和日期获取就不一样了,需要将逻辑倒过来,从最大的月份开始。而日期也是要从最大天开始:
int i = 11;
for (; i >= 0; i--)
{
if (daysInMonths[i] > totalDays)
{
mc = i + 1;
break;
}
totalDays -= daysInMonths[i];
}
day = daysInMonths[mc - 1] - totalDays;
mc -= month;
以上,我们就基本实现了我们关键的时间魔法咒语——Day。下面是完整的实现:
int datetime::Day::daysOfYear()
{
int year = 0;
int month = 0;
if (parent)
{
month = (*parent);
if (parent->getParent())
{
datetime::TimeScale *grandParent = parent->getParent();
year = (*grandParent);
}
}
int daysInMonths[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (datetime::isLeapYear(year))
{
daysInMonths[1] = 29;
}
int days = 0;
for (int i = 1; i < month; i++)
{
days += daysInMonths[i - 1];
}
days += value;
return days;
}
void datetime::Day::_dateIncrement(const int increment, int &yc, int &mc, int &day)
{
int year = 0;
int month = 0;
int daysInMonths[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (parent)
{
month = (*parent);
if (parent->getParent())
{
datetime::TimeScale *grandParent = parent->getParent();
year = (*grandParent);
}
}
yc = 0;
mc = 0;
int totalDays = increment + daysOfYear() - 1;
int n400 = totalDays / DAYS_IN_400_YEARS;
totalDays %= DAYS_IN_400_YEARS;
int n100 = totalDays / DAYS_IN_100_YEARS;
totalDays %= DAYS_IN_100_YEARS;
int n4 = totalDays / DAYS_IN_4_YEARS;
totalDays %= DAYS_IN_4_YEARS;
int n1 = 0;
int daysInYear = datetime::isLeapYear(year - n1) ? DAYS_IN_YEAR + 1 : DAYS_IN_YEAR;
while (totalDays >= daysInYear)
{
n1++;
totalDays -= daysInYear;
daysInYear = datetime::isLeapYear(year - n1) ? DAYS_IN_YEAR + 1 : DAYS_IN_YEAR;
}
yc = 400 * n400 + 100 * n100 + 4 * n4 + n1;
if (datetime::isLeapYear(year + yc))
{
daysInMonths[1] = 29;
}
for (int i = 0; i < 12; i++)
{
if (daysInMonths[i] > totalDays)
{
mc = i + 1;
break;
}
totalDays -= daysInMonths[i];
}
mc -= month;
day = totalDays + 1;
}
void datetime::Day::_dateDecrement(const int decrement, int &yc, int &mc, int &day)
{
int year = 0;
int month = 0;
int daysInMonths[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (parent)
{
month = (*parent);
if (parent->getParent())
{
datetime::TimeScale *grandParent = parent->getParent();
year = (*grandParent);
}
}
yc = 0;
mc = 0;
day = 0;
int totalDays = decrement + DAYS_IN_YEAR - daysOfYear();
if (datetime::isLeapYear(year))
{
totalDays++;
}
int n400 = totalDays / DAYS_IN_400_YEARS;
totalDays %= DAYS_IN_400_YEARS;
int n100 = totalDays / DAYS_IN_100_YEARS;
totalDays %= DAYS_IN_100_YEARS;
int n4 = totalDays / DAYS_IN_4_YEARS;
totalDays %= DAYS_IN_4_YEARS;
int n1 = 0;
int daysInYear = datetime::isLeapYear(year - n1) ? DAYS_IN_YEAR + 1 : DAYS_IN_YEAR;
while (totalDays >= daysInYear)
{
n1++;
totalDays -= daysInYear;
daysInYear = datetime::isLeapYear(year - n1) ? DAYS_IN_YEAR + 1 : DAYS_IN_YEAR;
}
yc = 400 * n400 + 100 * n100 + 4 * n4 + n1;
if (datetime::isLeapYear(year - yc))
{
daysInMonths[1] = 29;
}
int i = 11;
for (; i >= 0; i--)
{
if (daysInMonths[i] > totalDays)
{
mc = i + 1;
break;
}
totalDays -= daysInMonths[i];
}
day = daysInMonths[mc - 1] - totalDays;
mc -= month;
}
void datetime::Day::setValue(int result)
{
value = result;
}
void datetime::Day::recheckChild()
{
int year = 0;
int month = 0;
int day = value;
int daysInMonths[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (parent)
{
month = (*parent);
if (parent->getParent())
{
datetime::TimeScale *grandParent = parent->getParent();
year = (*grandParent);
}
}
if (datetime::isLeapYear(year))
{
daysInMonths[1] = 29;
}
if (day > daysInMonths[month - 1])
{
value = daysInMonths[month - 1];
}
max = daysInMonths[month - 1];
}
void datetime::Day::operator+(int i)
{
int yc;
int mc;
int day;
_dateIncrement(i, yc, mc, day);
setValue(day);
int carry = yc * 12 + mc;
if (carry > 0)
{
this->handleCarry(carry);
}
}
void datetime::Day::operator-(int i)
{
int yc;
int mc;
int day;
_dateDecrement(i, yc, mc, day);
setValue(day);
int carry = yc * 12 - mc;
if (carry > 0)
{
this->handleCarry(0 - carry);
}
}
const int datetime::Day::lengthOfMonth()
{
return max;
}
datetime::Day::Day(TimeScale *month, int day, int max) : datetime::TimeScale{month, day, max} {}
datetime::Day::~Day() {}