C语言 mktime() gmtime()实现——亲测正确性

14 篇文章 1 订阅
11 篇文章 0 订阅

前言

写此文章是因为有的嵌入式设备编程时不支持<time.h>,所以有些时间转换的函数必须自己实现。

关于<time.h>的使用,可以参考我的另一篇博客C语言常用时间相关函数

为了区别标准库函数,我们自定义的函数会加上self。

自定义的函数肯定会有人担心正确性,文末有验证测试代码。

在看代码之前,必须熟悉struct tm结构体

struct tm 
{
    int tm_sec;         /* 秒 – 取值区间为[0,59] */
    int tm_min;         /* 分 - 取值区间为[0,59] */
    int tm_hour;        /* 时 - 取值区间为[0,23] */
    int tm_mday;        /* 一个月中的日期 - 取值区间为[1,31] */
    int tm_mon;         /* 月份(从一月开始,0代表一月) - 取值区间为[0,11] */
    int tm_year;        /* 年份,其值等于实际年份减去1900 */
    int tm_wday;        /* 星期 – 取值区间为[0,6],其中0代表星期天,1代表星期一 */
    int tm_yday;        /* 从每年1月1日开始的天数– 取值区间[0,365],其中0代表1月1日 */
    int tm_isdst;       /* 夏令时标识符,夏令时tm_isdst为正;不实行夏令时tm_isdst为0 */    
};

self_mktime()

unsigned int self_mktime(struct tm tm_now)
{
    const unsigned int year0 = tm_now.tm_year+1900;
    const unsigned int mon0 = tm_now.tm_mon+1;
    const unsigned int day = tm_now.tm_mday;
    const unsigned int hour = tm_now.tm_hour;
    const unsigned int min = tm_now.tm_min;
    const unsigned int sec = tm_now.tm_sec;
	unsigned int mon = mon0, year = year0;
 
	/* 1..12 -> 11,12,1..10 */
	if (0 >= (int) (mon -= 2)) {
		mon += 12;	/* Puts Feb last since it has leap day */
		year -= 1;
	}
 
	return ((((unsigned int)
		  (year/4 - year/100 + year/400 + 367*mon/12 + day) +
		  year*365 - 719499
	    )*24 + hour /* now have hours */
	  )*60 + min /* now have minutes */
	)*60 + sec; /* finally seconds */
}

self_gmtime()

void self_gmtime(struct tm *tm_time, unsigned int timestamp)
{
    unsigned int four_year_num;      // 有多少个四年
    unsigned int one_year_hours;     // 一年的小时数

    const static unsigned char Days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    const static unsigned int ONE_YEAR_HOURS = 8760;        // 8760 = 365 * 24 (一年的小时数 非闰年)
    const static unsigned int FOUR_YEAR_HOURS = 35064;      // 35064 = (365 * 3 + 366) * 24 (四年的小时数)

    if (timestamp > 0x7FFFFFFF)
    {
        return;
    }
        
    tm_time->tm_isdst = 0;

    tm_time->tm_sec = (int)(timestamp % 60);                // 计算秒
    timestamp /= 60;
   
    tm_time->tm_min = (int)(timestamp % 60);                // 计算分
    timestamp /= 60;
   
    tm_time->tm_wday = (int)(timestamp/24 + 4) % 7;         // 计算星期 1970年1月1日星期四
   
    four_year_num = timestamp / FOUR_YEAR_HOURS;    
   
    tm_time->tm_year=(four_year_num << 2) + 70;             // 计算年
   
    timestamp %= FOUR_YEAR_HOURS;                           // 不足四年的小时数
   
    while (1)
    {
        one_year_hours = ONE_YEAR_HOURS;
       
        if ((tm_time->tm_year & 3) == 0)                    // 判断闰年
        {
            one_year_hours += 24;
        }

        if (timestamp < one_year_hours)
        {
            break;
        }

        tm_time->tm_year++;
        timestamp -= one_year_hours;
    }

    tm_time->tm_hour=(int)(timestamp % 24);                 // 计算时

    timestamp /= 24;                                        // 一年中剩下的天数
    timestamp++;
  
    tm_time->tm_yday = timestamp-1;                         // 计算天

    if ((tm_time->tm_year & 3) == 0)                        // 闰年处理
    {
        if (timestamp > 60)
        {
            timestamp--;                                    // 因为有1月29日的存在
        }
        else if (timestamp == 60)
        {
            tm_time->tm_mon = 1;                            // 计算月
            tm_time->tm_mday = 29;                          // 计算日
            return;
        }
    }

    // 计算日月
    for (tm_time->tm_mon = 0; Days[tm_time->tm_mon] < timestamp; tm_time->tm_mon++)
    {
        timestamp -= Days[tm_time->tm_mon];
    }

    tm_time->tm_mday = (int)(timestamp);
}

测试代码

平台:Windows10
CPU:i5-7400
GCC: 8.1.0
耗时:246.441秒
结果:输出passed,通过!

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

int main()
{
    time_t i;
    struct tm test_time1, *test_time2;
    unsigned int test_timestamp;

    for (i = 0; i <= 0x7FFFFFFF; i++)
    {
        self_gmtime(&test_time1, i);
        test_time2 = gmtime(&i);

        if (test_time1.tm_year != test_time2->tm_year ||\
            test_time1.tm_mon != test_time2->tm_mon ||
            test_time1.tm_mday != test_time2->tm_mday ||
            test_time1.tm_hour != test_time2->tm_hour ||
            test_time1.tm_min != test_time2->tm_min ||
            test_time1.tm_sec != test_time2->tm_sec ||
            test_time1.tm_wday != test_time2->tm_wday ||
            test_time1.tm_yday != test_time2->tm_yday)
        {
            printf("gmtime not passed i = %ld\r\n", i);
            break;
        }

        test_timestamp = self_mktime(test_time1);

        if (test_timestamp != i)
        {
            printf("mktime not passed i = %ld\r\n", i);
            break;
        }
    }

    if (i > 0x7FFFFFFF)
    {
        printf("passed\r\n");
    }
    return 0;
}
  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
`mktime` 函数是 C 标准库中的函数,其实现比较复杂,需要考虑多个因素,比如闰年、夏令时、时区等。以下是一个简化版的 `mktime` 函数实现,仅考虑了不跨越夏令时和非闰年的情况: ```c #include <time.h> #define SECS_PER_DAY 86400 #define SECS_PER_HOUR 3600 #define SECS_PER_MINUTE 60 time_t my_mktime(struct tm *timeptr) { int year, month, day, hour, minute, second; int days_in_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; // 获取年、月、日、时、分、秒 year = timeptr->tm_year + 1900; month = timeptr->tm_mon + 1; day = timeptr->tm_mday; hour = timeptr->tm_hour; minute = timeptr->tm_min; second = timeptr->tm_sec; // 计算天数 int days = (year - 1970) * 365; for (int i = 1970; i < year; i++) { if ((i % 4 == 0 && i % 100 != 0) || i % 400 == 0) { days++; } } for (int i = 1; i < month; i++) { days += days_in_month[i - 1]; } if (month > 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)) { days++; } days += day - 1; // 计算秒数 time_t seconds = days * SECS_PER_DAY + hour * SECS_PER_HOUR + minute * SECS_PER_MINUTE + second; return seconds; } ``` 以上代码中,我们使用了一个 `days_in_month` 数组来保存每个月的天数,然后根据年、月、日计算出总共的天数,再将其转换为秒数即可。需要注意的是,由于 `struct tm` 结构体中的年份是从 1900 年开始计算的,因此需要将其加上 1900。此外,由于计算过程中没有考虑夏令时、时区等因素,因此在实际使用时可能会存在一定的误差。
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值