[底层原理] C/C++获取时间(将时间戳转换为年月日)?
前言
大家都知道,计算机中存储的时间是一个整数,在现在的编程语言中,可以很方便地将时间戳(整数)转换为字符串,但是如果没有这些我们该如何自己计算出呢?
刚好以前研究过Nginx的源代码,我以他的代码为例,说明其背后的数学原理。当然在工程实践中,没有必要花时间自己实现转换的函数,所以本文用作一些底层原理的研究,说明计算机的背后其实是数学的原理组成的。
我这里采用C语言,如果是其他语言,也是类似的转换方法。
本文分为两个部分源代码和源代码解析。
环境:Visual Studio 2022,Win11
为了方便后面解析,说明我写代码当天时间是:2024年08月24日,周六
源代码
为了能够在一个单独的console环境中展示,那么我对源代码的命名放在了一起,以下是一个演示的完整代码:
#include <iostream>
#include <ctime>
using namespace std;
typedef struct tm ngx_tm_t;
typedef intptr_t ngx_int_t;
void
ngx_gmtime(time_t t, ngx_tm_t* tp)
{
ngx_int_t sec, min, hour, mday, mon, year, wday, yday, days;
days = t / 86400;
/* Jaunary 1, 1970 was Thursday */
wday = (4 + days) % 7;
t %= 86400;
hour = t / 3600;
t %= 3600;
min = t / 60;
sec = t % 60;
/* the algorithm based on Gauss's formula */
days = days - (31 + 28) + 719527;
year = days * 400 / (365 * 400 + 100 - 4 + 1);
yday = days - (365 * year + year / 4 - year / 100 + year / 400);
mon = (yday + 31) * 12 / 367;
mday = yday - (mon * 367 / 12 - 31);
mon += 2;
if (yday >= 306) {
/*
* there is no "yday" in Win32 SYSTEMTIME
*
* yday -= 306;
*/
year++;
mon -= 12;
if (mday == 0) {
/* Jaunary 31 */
mon = 1;
mday = 31;
}
else if (mon == 2) {
if ((year % 4 == 0) && (year % 100 || (year % 400 == 0))) {
if (mday > 29) {
mon = 3;
mday -= 29;
}
}
else if (mday > 28) {
mon = 3;
mday -= 28;
}
}
/*
* there is no "yday" in Win32 SYSTEMTIME
*
* } else {
* yday += 31 + 28;
*
* if ((year % 4 == 0) && (year % 100 || (year % 400 == 0))) {
* yday++;
* }
*/
}
tp->tm_sec = (int)sec;
tp->tm_min = (int)min;
tp->tm_hour = (int)hour;
tp->tm_mday = (int)mday;
tp->tm_mon = (int)mon;
tp->tm_year = (int)year;
tp->tm_wday = (int)wday;
}
int main()
{
std::time_t now = std::time(nullptr);
ngx_tm_t t;
ngx_gmtime(now, &t);
// 中国属于东八区,所以我在hour加了一个8
std::cout << t.tm_year << "年" << t.tm_mon << "月" << t.tm_mday << "日 "
<< t.tm_hour + 8 << ":" << t.tm_min << ":" << t.tm_sec;
getchar();
}
效果如下,和我的预期一致:
源代码解析
先看第11行如下,86400属于什么呢?我直接问gpt,说明是一天的秒数,经过计算确实如此。1 天 = 24 × 60 × 60 秒 = 86400秒。第一步nginx算出了当天到1970年的第一天差距了多少天,
days = t / 86400;
debug查看days的值为19959,也就是说2024年8月24日减去1970年1月1日为19959天:
14行:
wday代表今天是星期几,因为1970年的7月1日为星期四,所以使用上面得到的天数加上4然后余7,得到了wday为6。
wday = (4 + days) % 7;
16-17行,执行16行代码后,t
的值会被更新为 t
除以 86400
的余数。这实际上是将时间戳 t
转换为了从当天午夜(00:00:00)开始计算的秒数。换句话说,这行代码去除了时间戳中的日期部分,只保留了时间部分(以秒为单位)。
所以17行除一个小时的秒数,1600秒,得到今天是几点钟了,
t %= 86400;
hour = t / 3600;
debug发现是10点,因为计算机内部时间戳是UTC格式,中国属于东八区,所以其实是18点 ,我后续输出的时候对小时加了一个8。
18-20行,这个时候,t已经是当天过了多少秒了,分钟和秒数以此类推
t %= 3600; // 获取除开小时的秒数
min = t / 60; // 获取当前的分钟数
sec = t % 60; // 获取秒数
到这里已经获取了小时,分钟,秒数、星期几,接下来就是获取年、月、日。
24行
days = days - (31 + 28) + 719527;
经过查找资料才明白 719527 是从公元前1年3月1日到1970年3月1日的天数。所以这行代码是将days改为从公元当天开始到现在的天数。
后续的代码,我后面再继续研究,因为哪个公式看不看懂,也搜索不到。