深入解析 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;
}
解析
- 宏定义
#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 用于判断闰年及计算闰年的天数。
- 月份天数表
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 是一个静态常量数组,用于存储平年和闰年每个月的天数。
- 时间转换
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;
- 计算秒、分钟和小时
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)。
- 计算月份和日期
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 结构体中。理解这个函数的实现有助于我们更好地掌握时间处理的原理和方法,特别是在多线程环境下如何安全地进行时间转换。