时间戳转换日期

前言

时间戳转日期是我们最常用的需求, 一般情况我们会采用系统提供的localtime或localtime_r来转换, 可是localtime是线程不安全的, 而且两个都是通过系统调用来实现的, 如果在大量调用的时候, 会导致整个系统性能低下, 这也就是为什么我们要通过数学的方法转换时间戳.

算法

现行的公历又称格里历, 分平年和闰年, 平年二月份28天, 闰年29年, 每四年闰一次, 百年不闰, 四百年再闰, 也就是说给出一个年份, 能被4整除, 就可能是闰年, 如果能被100整除, 同是不被400整除就是平年, 其余都是闰年(不能被4整除, 就一定不是闰年).

算法看上去很简单, 需要处理的就是百年的情况. 众所周知, 我们的unixtime是从1970年开始算, 那么到现在中间只有2000年这一个百年, 然而这个百年是可以被400整除的, 于是算法可以进一步简单的认为我们只需要考虑四年一个周期(2100年的事情就要后人去解决吧).

一些坑

unixtime 是从1970年开始算, 而1970、1971年是平年,1972年是闰年,也就是说我们按4整除求余之前, 应该先踢除前面三年(写之前网上搜了大量的资料, 基本上就没有对的, 这个地方完全就被漏掉了)

源码

// 时间戳转换
static unsigned short date_time_days[2][12] =
{
    { 31,  28,  31, 30, 31, 30, 31, 31, 30, 31, 30, 31},// 平年
    { 31,  29,  31, 30, 31, 30, 31, 31, 30, 31, 30, 31},// 润年
};

struct date_time
{
    unsigned int second; // 0-59
    unsigned int minute; // 0-59
    unsigned int hour;   // 0-23
    unsigned int day;    // 1-31
    unsigned int month;  // 1-12
    unsigned int year;   // 1970 - 2100
    unsigned long long timestamp;

    date_time(unsigned long long unixtime)
    {
        timestamp = unixtime;
        unixtime_to_date();
    }

    date_time(unsigned int syear, unsigned int smouth, unsigned int sday, unsigned int shour, unsigned int sminute, unsigned int ssecond)
    {
        year   = syear;
        month  = smouth;
        day    = sday;
        hour   = shour;
        minute = sminute;
        second = ssecond;
        date_to_unixtime();
    }

    void unixtime_to_date()
    {
        second = timestamp % 60; timestamp /= 60;
        minute = timestamp % 60; timestamp /= 60;
        hour   = timestamp % 24; timestamp /= 24;
        year   = 1970;
        // 1970 1971 是平年
        for (unsigned int i = 0; i < 2; ++i)
        {
            if (timestamp >= 365)
            {
                ++year;
                timestamp -= 365;
            }
            else
            {
                for(unsigned int j = 0; j < 12; ++j)
                {
                    month = j + 1;
                    if (timestamp >= date_time_days[0][j])
                    {
                        timestamp -= date_time_days[0][j];
                    }
                    else
                    {
                        day = timestamp + 1;
                        return;
                    }
                }
            }
        }
        // 1972 闰年
        if (timestamp >= 366)
        {
            ++year;
            timestamp -= 366;
        }
        else
        {
            for(unsigned int j = 0; j < 12; ++j)
            {
                month = j + 1;
                if (timestamp >= date_time_days[1][j])
                {
                    timestamp -= date_time_days[1][j];
                }
                else
                {
                    day = timestamp + 1;
                    return;
                }
            }
        }
        // 中间四年一个阶段
        unsigned int years = timestamp/(365*4+1)*4; timestamp %= 365*4+1;
        year += years;
        for (unsigned int i = 0; i < 3; ++i)
        {
            if (timestamp >= 365)
            {
                ++year;
                timestamp -= 365;
            }
            else
            {
                for(unsigned int j = 0; j < 12; ++j)
                {
                    month = j + 1;
                    if (timestamp >= date_time_days[0][j])
                    {
                        timestamp -= date_time_days[0][j];
                    }
                    else
                    {
                        day = timestamp + 1;
                        return;
                    }
                }
            }
        }
        for(unsigned int j = 0; j < 12; ++j)
        {
            month = j + 1;
            if (timestamp >= date_time_days[1][j])
            {
                timestamp -= date_time_days[1][j];
            }
            else
            {
                day = timestamp + 1;
                return;
            }
        }
    }

    void date_to_unixtime()
    {
        timestamp            = second;  // 0-59
        unsigned int sminute = minute;  // 0-59
        unsigned int shour   = hour;    // 0-23
        unsigned int sday    = day-1;   // 0-30
        unsigned int smonth  = month-1; // 0-11
        unsigned int syear   = year - 1;
        timestamp += (sminute * 60 + shour * (60 * 60) + sday * (24 * 60 * 60));
        int isfour = ((syear + 1)% 4 == 0 ? 1 : 0);
        for( unsigned int i = 0; i < smonth; ++i )
        {
            timestamp += (date_time_days[isfour][i] * (24 * 60 * 60));
        }
        if (syear >= 1970)
        {
            timestamp += (365 * 24 * 60 * 60);
        }
        else
        {
            return ;
        }
        if (syear >= 1971)
        {
            timestamp += (365 * 24 * 60 * 60);
        }
        else
        {
            return ;
        }
        if (syear >= 1972)
        {
            timestamp += (366 * 24 * 60 * 60);
        }
        else
        {
            return ;
        }
        timestamp += (((syear - 1972) / 4)*(365 * 4 + 1) + ((syear - 1972) % 4) * 365) * 24 * 60 * 60;
    }
};

后语

一个会写代码的人, 基本功也是很结实的, 这些小算法特别考验基本功. 业务代码写多了, 对这种基本功的考验往往不屑一顾, 我们应该不忘初心.

补充一个问题

上面的代码用的是标准时区进行计算的, 但实际上我们更多的时候用的是东八区(准确的说应该叫北京时间, 跟国家相关的), 所以我们会先 + 8小时, 然后进行计算。实际这样是不准的, 会有夏时令的问题。如果把这个直接弄成东八区的, 那就得特殊处理夏时令问题.

欢迎大家订阅雀观代码, 我将给你讲述, 中小企业程序员, 淘金路上的故事.



时间戳转换日期》上有1条评论

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注