小白C++入坑学废之旅(七)

深入学习时间魔法已经几度春秋。虽然中间有很多曲折,但是也学习到很多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的复杂性:

  1. 首先Day是承上启下的关键,它下接Hour, Minute,Second等规律的时间部分,同时也上接Year,Month等不规则的时间部分。
  2. 其次Day和Year,Month等不是完全独立的一部分。闰年闰月的存在,导致在年月的增减时候Day无法完全置身事外。比如 2000年2月29日,如果在这个时间基础上减一年,那么得到的时间应该是什么呢?是不是1999年2月28日更合理呢?所以Day随着Year改变而改变了。
  3. 其次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一致。

然后就是关于日期的减运算。原以为和加运算一样简单,但是我想错。虽然加和减是相反的运算,但是放到时间上就不一定完全相反。但是思路还是比较相似的。首先需要找基准,但是这个基准不能是11日了,而是需要以未来的时间1231日,也就是以当前年份的最后一天时间为基准。之所以选1231日,是因为如果减一天,你发现以11日为基准,那么补全后,差值就是负数。负数让日期运算的方向发生反转,增加了算法复杂度。所以选择了1231号:

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() {}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值