深入解析 gmtime_r:揭开时间转换的神秘面纱

深入解析 gmtime_r:揭开时间转换的神秘面纱

前言和声明

  • 在 C/C++ 编程中,时间处理是一个非常常见的需求。本文将详细解析 gmtime_r 函数的实现,逐步讲解其内部工作机制和时间转换的原理,帮助读者深入理解时间处理的核心概念。

  • 本函数源码参考自wolfssl的源代码, github url:https://github.com/wolfSSL/wolfssl, 感兴趣的可以去研究其源码。

函数源码

gmtime_r 是一个线程安全的函数,用于将 UNIX 时间戳(自 1970 年 1 月 1 日 UTC 起经过的秒数)转换为 UTC 时间,并存储在用户提供的 tm 结构体中。与 gmtime 不同,gmtime_r 需要一个用户提供的缓冲区来存储结果,从而避免了多线程环境下的竞争条件。
以下是 gmtime_r 函数的完整实现代码:

struct tm* gmtime_r(const time_t* timer, struct tm *ret)
{
    #define YEAR0          1900
    #define EPOCH_YEAR     1970
    #define SECS_DAY       (24L * 60L * 60L)
    #define LEAPYEAR(year) (!((year) % 4) && (((year) % 100) || !((year) %400)))
    #define YEARSIZE(year) (LEAPYEAR(year) ? 366 : 365)

    static const int _ytab[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}
    };

    time_t secs = *timer;
    unsigned long dayclock, dayno;
    int year = EPOCH_YEAR;

    dayclock = (unsigned long)secs % SECS_DAY;
    dayno    = (unsigned long)secs / SECS_DAY;

    ret->tm_sec  = (int) dayclock % 60;
    ret->tm_min  = (int)(dayclock % 3600) / 60;
    ret->tm_hour = (int) dayclock / 3600;
    ret->tm_wday = (int) (dayno + 4) % 7;        /* day 0 a Thursday */

    while(dayno >= (unsigned long)YEARSIZE(year)) {
        dayno -= YEARSIZE(year);
        year++;
    }

    ret->tm_year = year - YEAR0;
    ret->tm_yday = (int)dayno;
    ret->tm_mon  = 0;

    while(dayno >= (unsigned long)_ytab[LEAPYEAR(year)][ret->tm_mon]) {
        dayno -= _ytab[LEAPYEAR(year)][ret->tm_mon];
        ret->tm_mon++;
    }

    ret->tm_mday  = (int)++dayno;
    ret->tm_isdst = 0;

    return ret;
}

解析

  1. 宏定义
#define YEAR0          1900
#define EPOCH_YEAR     1970
#define SECS_DAY       (24L * 60L * 60L)
#define LEAPYEAR(year) (!((year) % 4) && (((year) % 100) || !((year) %400)))
#define YEARSIZE(year) (LEAPYEAR(year) ? 366 : 365)

这些宏定义了时间处理中的一些常量和宏函数。YEAR0 和 EPOCH_YEAR 定义了基准年份,SECS_DAY 定义了一天的秒数,LEAPYEAR 和 YEARSIZE 用于判断闰年及计算闰年的天数。

  1. 月份天数表
static const int _ytab[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}
};

_ytab 是一个静态常量数组,用于存储平年和闰年每个月的天数。

  1. 时间转换
    time_t secs = *timer;
    unsigned long dayclock, dayno;
    int year = EPOCH_YEAR;
    dayclock = (unsigned long)secs % SECS_DAY;
    dayno = (unsigned long)secs / SECS_DAY;
time_t secs = *timer;
unsigned long dayclock, dayno;
int year = EPOCH_YEAR;
dayclock = (unsigned long)secs % SECS_DAY;
dayno    = (unsigned long)secs / SECS_DAY;
  1. 计算秒、分钟和小时
ret->tm_sec  = (int) dayclock % 60;
ret->tm_min  = (int)(dayclock % 3600) / 60;
ret->tm_hour = (int) dayclock / 3600;
ret->tm_wday = (int) (dayno + 4) % 7;        /* day 0 a Thursday */

计算当前时间的秒、分钟和小时,并计算星期几(1970年1月1日是星期四)。
5. 计算年份

while(dayno >= (unsigned long)YEARSIZE(year)) {
    dayno -= YEARSIZE(year);
    year++;
}
ret->tm_year = year - YEAR0;
ret->tm_yday = (int)dayno;
ret->tm_mon  = 0;
通过逐年递增来计算当前年份,并更新剩余的天数(dayno)。
  1. 计算月份和日期
while(dayno >= (unsigned long)_ytab[LEAPYEAR(year)][ret->tm_mon]) {
    dayno -= _ytab[LEAPYEAR(year)][ret->tm_mon];
    ret->tm_mon++;
}
ret->tm_mday  = (int)++dayno;
ret->tm_isdst = 0;

通过逐月递增来计算当前月份,并更新剩余的天数。最后,计算当前日期。tm_isdst 字段表示夏令时,这里设置为 0。

总结

gmtime_r 函数通过一系列步骤将 UNIX 时间戳转换为 UTC 时间的各个组成部分,并存储在用户提供的 tm 结构体中。理解这个函数的实现有助于我们更好地掌握时间处理的原理和方法,特别是在多线程环境下如何安全地进行时间转换。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值