当前位置: 首页 > news >正文

Ubuntu 下 nginx-1.24.0 源码分析 - ngx_gmtime 函数

声明 

src\core\ngx_times.h 中:

void ngx_gmtime(time_t t, ngx_tm_t *tp);

实现 

src\core\ngx_times.c

void
ngx_gmtime(time_t t, ngx_tm_t *tp)
{
    ngx_int_t   yday;
    ngx_uint_t  sec, min, hour, mday, mon, year, wday, days, leap;

    /* the calculation is valid for positive time_t only */

    if (t < 0) {
        t = 0;
    }

    days = t / 86400;
    sec = t % 86400;

    /*
     * no more than 4 year digits supported,
     * truncate to December 31, 9999, 23:59:59
     */

    if (days > 2932896) {
        days = 2932896;
        sec = 86399;
    }

    /* January 1, 1970 was Thursday */

    wday = (4 + days) % 7;

    hour = sec / 3600;
    sec %= 3600;
    min = sec / 60;
    sec %= 60;

    /*
     * the algorithm based on Gauss' formula,
     * see src/core/ngx_parse_time.c
     */

    /* days since March 1, 1 BC */
    days = days - (31 + 28) + 719527;

    /*
     * The "days" should be adjusted to 1 only, however, some March 1st's go
     * to previous year, so we adjust them to 2.  This causes also shift of the
     * last February days to next year, but we catch the case when "yday"
     * becomes negative.
     */

    year = (days + 2) * 400 / (365 * 400 + 100 - 4 + 1);

    yday = days - (365 * year + year / 4 - year / 100 + year / 400);

    if (yday < 0) {
        leap = (year % 4 == 0) && (year % 100 || (year % 400 == 0));
        yday = 365 + leap + yday;
        year--;
    }

    /*
     * The empirical formula that maps "yday" to month.
     * There are at least 10 variants, some of them are:
     *     mon = (yday + 31) * 15 / 459
     *     mon = (yday + 31) * 17 / 520
     *     mon = (yday + 31) * 20 / 612
     */

    mon = (yday + 31) * 10 / 306;

    /* the Gauss' formula that evaluates days before the month */

    mday = yday - (367 * mon / 12 - 30) + 1;

    if (yday >= 306) {

        year++;
        mon -= 10;

        /*
         * there is no "yday" in Win32 SYSTEMTIME
         *
         * yday -= 306;
         */

    } else {

        mon += 2;

        /*
         * there is no "yday" in Win32 SYSTEMTIME
         *
         * yday += 31 + 28 + leap;
         */
    }

    tp->ngx_tm_sec = (ngx_tm_sec_t) sec;
    tp->ngx_tm_min = (ngx_tm_min_t) min;
    tp->ngx_tm_hour = (ngx_tm_hour_t) hour;
    tp->ngx_tm_mday = (ngx_tm_mday_t) mday;
    tp->ngx_tm_mon = (ngx_tm_mon_t) mon;
    tp->ngx_tm_year = (ngx_tm_year_t) year;
    tp->ngx_tm_wday = (ngx_tm_wday_t) wday;
}

将时间戳(从 1970 年 1 月 1 日开始的秒数)解析为年、月、日、时、分、秒等具体的日期和时间信息 

函数输入与输出

  • 输入

    • t:一个 time_t 类型的时间戳,表示从 1970 年 1 月 1 日 00:00:00 UTC 开始经过的秒数。
    • tp:指向一个 ngx_tm_t 结构体的指针,用于存储解析后的日期和时间信息。
  • 输出

    • t 转换为分解的时间结构体 ngx_tm_t,包含秒、分、小时、天、月、年、星期几等字段。

 逻辑

(1) 时间戳范围校验

if (t < 0) {
    t = 0;
}

如果时间戳 t 是负数,直接将其设置为 0。

这是因为该函数只支持正数时间戳(即从 1970 年 1 月 1 日之后的时间)

if (days > 2932896) {
    days = 2932896;
    sec = 86399;
}

如果天数超过 2932896(对应到 9999 年 12 月 31 日),则将其截断到该日期的最大值,并将秒数设置为一天的最后一秒(86399 秒)

这是为了限制日期范围在 1970 年到 9999 年之间

截断到该日期的23:59:59,避免溢出


(2)计算天数和秒数

days = t / 86400;
sec = t % 86400;

将时间戳 t 分解为整天天数 days 和剩余的秒数 sec

86400 是一天的秒数(24 小时 × 60 分钟 × 60 秒)。


(3) 计算星期几

wday = (4 + days) % 7;

1970 年 1 月 1 日是星期四,因此通过 (4 + days) % 7 计算出给定日期是星期几。

星期四对应数字 4,星期五对应 5,依此类推


(4) 分解小时、分钟和秒

hour = sec / 3600;
sec %= 3600;
min = sec / 60;
sec %= 60;

将剩余的秒数 sec 分解为小时、分钟和秒。

3600 是一小时的秒数,60 是一分钟的秒数。


(5) 基于 Gauss 公式的日期计算

基于 Gauss 公式的日期计算-CSDN博客

days = days - (31 + 28) + 719527;

将天数调整为从公元前 1 年 3 月 1 日开始的天数。

31 + 28 是 1970 年 1 月和 2 月的天数。

719527 是从公元前 1 年 3 月 1 日到 1970 年 3 月 1 日的天数。

year = (days + 2) * 400 / (365 * 400 + 100 - 4 + 1);
yday = days - (365 * year + year / 4 - year / 100 + year / 400);

使用 Gauss 公式计算年份和一年中的第几天(yday)。

365 * year + year / 4 - year / 100 + year / 400 是闰年规则下的总天数。

如果 yday 小于 0,说明当前年份需要调整到前一年。


(6) 确定月份和日期

mon = (yday + 31) * 10 / 306;
mday = yday - (367 * mon / 12 - 30) + 1;

使用经验公式将一年中的第几天映射到月份和日期。

mon 是月份(从 0 开始,0 表示 3 月,1 表示 4 月,依此类推)。

mday 是一个月中的具体日期。

if (yday >= 306) {
    year++;
    mon -= 10;
} else {
    mon += 2;
}

根据 yday 的值调整年份和月份:

如果 yday >= 306,说明日期在下一年的 1 月或 2 月。

否则,日期在当前年的 3 月至 12 月。


(7)结果填充

将计算后的秒、分、时、日、月、年、星期等字段赋值给ngx_tm_t结构体。

 详解

ngx_int_t   yday;
ngx_uint_t  sec, min, hour, mday, mon, year, wday, days, leap;

声明一系列变量,用于存储中间计算结果和最终分解时间的各个字段。

  • yday:一年中的第几天(从 0 开始计数)。
  • secminhour:秒、分、小时。
  • mday:一个月中的第几天。
  • mon:月份(从 0 开始计数,0 表示 1 月)。
  • year:年份(从 0 开始计数,0 表示 1 BC)。
  • wday:一周中的第几天(0 表示星期日)。
  • days:自 1970-01-01 起的总天数。
  • leap:是否为闰年。

处理负时间戳

if (t < 0) {
    t = 0;
}

如果输入的时间戳 t 小于 0,则将其设置为 0。这表明该函数只支持正的时间戳(即 1970 年之后的时间)

负时间戳可能表示 1970 年之前的时间,但该函数不支持这种场景


计算总天数和秒数

days = t / 86400;
sec = t % 86400;
  • days:计算自 1970-01-01 起的总天数(一天有 86400 秒)。
  • sec:计算当天剩余的秒数

截断最大时间范围

if (days > 2932896) {
    days = 2932896;
    sec = 86399;
}

限制时间范围,确保不会超出支持的最大日期(9999-12-31 23:59:59)

2932896 是从 1970-01-01 到 9999-12-31 的总天数。

如果超出此范围,将日期截断到 9999-12-31,并将时间设置为 23:59:59


计算星期几

wday = (4 + days) % 7;

计算当前日期是一周中的第几天

1970-01-01 是星期四(4),因此 (4 + days) % 7 可以正确计算出星期几。

结果范围为 0(星期日)到 6(星期六)


计算小时、分钟和秒

hour = sec / 3600;
sec %= 3600;
min = sec / 60;
sec %= 60;

将秒数分解为小时、分钟和秒。

hour:计算当天的小时数(一小时有 3600 秒)。

min:计算剩余的分钟数(一分钟有 60 秒)。

sec:计算剩余的秒数。


调整天数基准

days = days - (31 + 28) + 719527;

将天数调整为从公元前 1 年 3 月 1 日起的天数

31 + 28:1970 年的前两个月(1 月和 2 月)的天数。

719527:表示从公元前1年(1 BC)的3月1日到1970年3月1日的总天数


计算年份和一年中的第几天

year = (days + 2) * 400 / (365 * 400 + 100 - 4 + 1);
yday = days - (365 * year + year / 4 - year / 100 + year / 400);

  • year:计算当前年份。
  • yday:计算当前年份中已经过去的天数

 year 是以 400年为一个周期,计算经过了多少个 400 年来计算的,它是相对于基准日的偏移量。

这里看出 以公元前1 年作为基准的好处:

假如以 公元1年作为基准,那么当偏移量为1时,实际含义是从公元1年这个基准经过了1年之后,那么应该是公元2年,我们需要 2 这个数值,但这时 year 的值是1,需要再加上1。

假如以 公元前1年作为基准,那么当偏移量为1时,实际含义是从公元前1年这个基准经过了1年之后,那么应该是公元1年,我们需要 1 这个数值,这时 year 就是 1。

days - (365 * year + year / 4 - year / 100 + year / 400);

从总天数中减去已知年份的天数

days 是从基准日开始经过的天数,year 的含义是从基准日开始过去了多少年,

days 减去的那部分的含义是,year 这么多年所包含的天数,减去之后的数值就是不满一年的余量

调整负值情况

if (yday < 0) {
    leap = (year % 4 == 0) && (year % 100 || (year % 400 == 0));
    yday = 365 + leap + yday;
    year--;
}

如果 yday < 0,说明当前年份需要调整到前一年

leap 判断当前年是否是闰年

365 + leap 表示上一年的总天数

yday 是负值,若 yday 是 -1 ,加上 yday 就是 -1,上一年要回退1天

最后year减一

leap 是用当前年 year 计算出的,为什么代表上一年是否是闰年?

我们选用的基准日是公元前 3月1 日,year=1 时表示从基准日开始过去了一年,这个一年是从3月1日开始的,2月是末尾

所以 leap 代表的这个2月是否为闰月,这个2月是归属于上一年的

这也是选择3月1日作为基准日的原因

闰日(2月29日)位于年末(二月是最后一个月),因此计算年份和月份时,无需在年中处理闰日的影响

计算月份

mon = (yday + 31) * 10 / 306;

306 是从 3 月到 12 月的总天数

通过使用 306,我们可以将一年分为两个部分:

前两个月(1 月和 2 月)单独处理。

后十个月(3 月至 12 月)作为一个整体处理。

yday / 306 是一个比值,假如比值是 十分之一,表示在10个月中过去了一个月,这时值是 0.1,当然需要乘以10才能得到我们需要的数值,乘以这个 10 必须先在分子上进行运算,如果在 除以 306 之后在乘以 10,数学上是一样的,但这里整数运算会舍弃小数部分,必须在舍弃之前 乘以 10

+31 是要向上取整,一个月最多31天

若从1月1日起,过去了1天,我们需要的值是1(第一个月),若过去了一个月零一天就到了第二个月,需要的数值是2

计算一个月中的第几天

mday = yday - (367 * mon / 12 - 30) + 1;

计算当前月份中的第几天

使用高斯公式计算每个月的天数:367 * mon / 12 - 30

这是一个近似公式,用于估计某个月之前的所有天数

367 * mon / 12 表示从年初到第 mon 个月之前的累计天数

只是一个近似值,可能会导致误差,因此减去一个常数 30来进行校正

加上 1 是因为日期从 1 开始计数(而不是从 0 开始,1月1日经过1天是 1月2日)

调整年份和月份

if (yday >= 306) {
    year++;
    mon -= 10;
} else {
    mon += 2;
}

由于我们的基准日在 3月1日,所以当 mon=1 时,代表的是 3 月

当 yday 大于306(mon > 10,mon =10实际代表的12月),说明日期在下一年的1,2月

填充分解时间结构体

 

tp->ngx_tm_sec = (ngx_tm_sec_t) sec;
tp->ngx_tm_min = (ngx_tm_min_t) min;
tp->ngx_tm_hour = (ngx_tm_hour_t) hour;
tp->ngx_tm_mday = (ngx_tm_mday_t) mday;
tp->ngx_tm_mon = (ngx_tm_mon_t) mon;
tp->ngx_tm_year = (ngx_tm_year_t) year;
tp->ngx_tm_wday = (ngx_tm_wday_t) wday;

计算结果填充到 ngx_tm_t 结构体中

相关文章:

  • 每日Attention学习23——KAN-Block
  • 低空经济:开启未来空中生活的全新蓝海
  • 【动态规划】--- 斐波那契数模型
  • Golang关于结构体组合赋值的问题
  • React源码解读
  • 尚硅谷爬虫note004
  • Unity3D 类MOBA角色控制器 开箱即用
  • 《安富莱嵌入式周报》第350期:Google开源Pebble智能手表,开源模块化机器人平台,开源万用表,支持10GHz HRTIM的单片机,开源CNC控制器
  • JVM ②-双亲委派模型 || 垃圾回收GC
  • vscode使用常见问题处理合集
  • 2025-02-13 学习记录--C/C++-PTA 7-14 求整数段和
  • ChatGPT vs DeepSeek详细对比
  • 字玩FontPlayer开发笔记13 Vue3实现钢笔工具
  • 【设计模式】【行为型模式】访问者模式(Visitor)
  • 《Stable Diffusion绘画完全指南:从入门到精通的Prompt设计艺术》 第二章
  • Python的那些事第十八篇:框架与算法应用研究,人工智能与机器学习
  • 深度学习算法​:ocr营业执照识别可提取字段、接口识别
  • Hello Robot 推出Stretch 3移动操作机器人,赋能研究与商业应用
  • vue3常见面试题
  • Python 面向对象(类,对象,方法,属性,魔术方法)
  • 晶圆销量上升,中芯国际一季度营收增长近三成,净利增超1.6倍
  • 股价两天涨超30%,中航成飞:不存在应披露而未披露的重大事项
  • 司法部:民营经济促进法明确禁止违规异地执法、利用行政或者刑事手段违法干预经济纠纷
  • 鸿蒙电脑正式亮相,五年布局积累超2700项核心专利
  • 4月深圳新房、二手房成交同比均上涨,“5月有望延续积极向好的发展态势”
  • 观察|印巴交火开始升级,是否会升级为第四次印巴战争?