Repository navigation
Conversation
gmtime_r() split the timestamp with C division, which truncates toward zero:
work = *timep % (24*60*60); /* negative remainder for *timep < 0 */
work = (int)(*timep / (24*60*60));
For a negative timestamp both the day count and the remainder come out
negative, and the year loop below could only walk forward from 1970, so the
result was returned as a valid struct tm holding impossible fields:
-1 -> 1970-01-01 00:00:-1 (want 1969-12-31 23:59:59)
-86400 -> 1970-01-00 00:00:00 (want 1969-12-31 00:00:00)
-31536000 -> 1970-01--364 00:00:00 (want 1969-01-01 00:00:00)
-2208988800 -> 1970-01--25566 00:00:00 (want 1900-01-01 00:00:00)
Normalise to floor division first (borrow one day when the remainder is
negative), compute tm_wday from the normalised day count while keeping the
modulo result non-negative, and let the year walk run backwards for negative
day counts.
Verified on bsp/qemu-vexpress-a9 (QEMU 11.0.0, arm-none-eabi-gcc 15.2.1,
sizeof(time_t) = 8), same probe run against master and against this patch:
t master patched
0 1970-01-01 00:00:00 wday=4 1970-01-01 00:00:00 wday=4
86399 1970-01-01 23:59:59 wday=4 1970-01-01 23:59:59 wday=4
86400 1970-01-02 00:00:00 wday=5 1970-01-02 00:00:00 wday=5
-1 1970-01-01 00:00:-1 wday=4 1969-12-31 23:59:59 wday=3
-86400 1970-01-00 00:00:00 wday=3 1969-12-31 00:00:00 wday=3
-31536000 1970-01--364 wday=-4 1969-01-01 00:00:00 wday=3
-2208988800 1970-01--25566 wday=-6 1900-01-01 00:00:00 wday=1
Non-negative timestamps are unchanged; 1900-01-01 correctly reports Monday.
Refs RT-Thread#11923
|
👋 感谢您对 RT-Thread 的贡献!Thank you for your contribution to RT-Thread! 为确保代码符合 RT-Thread 的编码规范,请在你的仓库中执行以下步骤运行代码格式化工作流(如果格式化CI运行失败)。 🛠 操作步骤 | Steps
完成后,提交将自动更新至 如有问题欢迎联系我们,再次感谢您的贡献!💐 |
📌 Code Review Assignment🏷️ Tag: componentsReviewers: @Maihuanyi Changed Files (Click to expand)
🏷️ Tag: components_libcReviewers: @GorrayLi @mysterywolf Changed Files (Click to expand)
📊 Current Review Status (Last Updated: 2026-10-26 14:38 CST)
📝 Review Instructions
|
|
|
可以增加一下utest的测试。 |
Requested in the review of RT-Thread#11929: pin down gmtime_r() behaviour before 1970. Adds five cases to the existing components.libc.time testcase and checks every field the function fills in, including tm_wday and tm_yday: timestamp expected 0 1970-01-01 00:00:00 wday=4 yday=0 -1 1969-12-31 23:59:59 wday=3 yday=364 -86400 1969-12-31 00:00:00 wday=3 yday=364 -86401 1969-12-30 23:59:59 wday=2 yday=363 -58060800 1968-02-29 00:00:00 wday=4 yday=59 (1968 leap day) Without the fix in the previous commit the new unit fails on every one of the five cases, with all eight field assertions reported, so the test does pin the behaviour down rather than passing by accident: t = -1 1970-01-01 00:00:-1 wday=4 yday=0 t = -86400 1970-01-00 00:00:00 wday=3 yday=-1 t = -86401 1970-01-00 00:00:-1 wday=3 yday=-1 Verified on bsp/qemu-vexpress-a9 with RT_UTEST_LIBC_TIME enabled: with the fix : [ PASSED ] testcase (components.libc.time) without the fix : all five cases FAILED Refs RT-Thread#11923 Refs RT-Thread#11929
|
已按你的要求补上 utest,提交 加在哪
覆盖的五个点按你说的清单逐条对应:
全部日期字段都断言,不只看年月日:
验证:用例确实有鉴别力这是我觉得更需要说明的一点——新用例在未修复的 master 上会失败,不是恒真断言: 带修复: 不带修复(仅把 五个用例每个都是八条断言全失败,说明用例确实锁住了这个缺陷。 环境: 另外之前 issue 里提到的 |
|
gcc和arm的工具链,定义不一样,一个是有符号一个是无符号的,在ac6的工具链下utest的测试会有问题。 |
…igned
gmtime_r_before_epoch_test() assumed a signed time_t. armclang (AC6) defines
time_t as an unsigned 32-bit type, so (time_t)-1 is 4294967295, which is a
perfectly valid timestamp meaning 2106-02-07 06:28:15 - not the epoch minus one
second. The case therefore failed on that toolchain with every field mismatching:
t = -1 2106-02-07 06:28:15 wday=0 yday=37
t = -86400 2106-02-06 06:28:16 wday=6 yday=36
t = -86401 2106-02-06 06:28:15 wday=6 yday=36
1968 leap 2104-04-06 06:28:16 wday=0 yday=96
Those values are not a defect in gmtime_r(); they are what an unsigned
4294967295-second offset actually is. Only the test's expectation was wrong.
Detect the signedness at compile time by comparing (time_t)-1 against 0: when
time_t is unsigned that expression is false. Keep checking the epoch entry
everywhere, and skip the pre-epoch entries when time_t cannot represent them,
logging which of the two happened so the run is self-explanatory.
Verified on bsp/qemu-vexpress-a9 with arm-none-eabi-gcc, where time_t is signed:
[I/utest] time_t is signed, checking pre-epoch timestamps
[I/utest] epoch zero: 1970-01-01 00:00:00 wday=4 yday=0
[I/utest] t = -1: 1969-12-31 23:59:59 wday=3 yday=364
[I/utest] t = -86400: 1969-12-31 00:00:00 wday=3 yday=364
[I/utest] t = -86401: 1969-12-30 23:59:59 wday=2 yday=363
[I/utest] 1968 leap day: 1968-02-29 00:00:00 wday=4 yday=59
[I/utest] [ PASSED ] testcase (components.libc.time)
The unsigned path was checked against a reference model that reproduces the
AC6 output exactly for all four pre-epoch entries.
Refs RT-Thread#11923
Refs RT-Thread#11929
|
确认了,这是我在用例里引入的缺陷,不是环境差异。已修复,提交 你贴的日志指向的原因
#ifdef _WIN32
typedef __time64_t time_t;
#endif其他工具链用的是各自 libc 的 验证方式我用一个
所以这四行不是 修法在用例里检测 if ((time_t)-1 < (time_t)0)
{
LOG_I("time_t is signed, checking pre-epoch timestamps");
}
else
{
LOG_I("time_t is unsigned, only checking the epoch itself");
}循环里跳过 没有改 我这边重跑的结果(gcc,time_t 有符号)无符号那侧我只能在参考模型上验证,没有真的 AC6 工具链跑过。如果方便的话,麻烦你在 AC6 上再跑一次确认——预期会看到 顺带一个教训我提这个用例时只在 gcc(有符号 time_t)上验证,把"负数时间戳"当成了跨工具链成立的契约。实际上 |
拉取/合并请求描述:(PR description)
[
为什么提交这份PR (why to submit this PR)
修复 #11923。
gmtime_r()用 C 的除法拆时间戳,而 C 的除法向零截断:于是负时间戳会返回一个非 NULL、但字段非法的结构体:
1970-01-01 00:00:-1(wday=4 yday=0)1969-12-31 23:59:591970-01-00 00:00:00(yday=-1)1969-12-31 00:00:001970-01--364 00:00:00(wday=-4 yday=-365)1969-01-01 00:00:001970-01--25566 00:00:00(wday=-6 yday=-25567)1900-01-01 00:00:00不只是秒为负,
tm_mday和tm_wday也会变成明显的非法值。你的解决方案是什么 (what is your solution)
只改
gmtime_r()一个函数,三步:tm_sec/tm_min/tm_hour非负tm_wday用规范化后的天数计算,并保证结果落在 0~6(原来(4 + work) % 7在天数为负时返回负值)while (work < 0) { --i; work += 闰年?366:365; },让早于 1970 的时间戳也能定位到正确年份__isleap()对 1970 之前的年份本身就有效,直接复用。请提供验证的bsp和config (provide the config and bsp)
bsp/qemu-vexpress-a9(sizeof(time_t)=8)1c801efa同一份固件、同一个探针,改前/改后各跑一次:
非负时间戳行为完全未变;1900-01-01 的 wday=1(周一)也对。
两点未包含的内容
int work的类型没改。天数在进入年份循环时仍转int,对 64 位time_t的极端值(超过约 ±580 万年)依然会截断。glibc 在那种情况下返回EOVERFLOW,但这与 [Bug] [libc] gmtime_r 对负时间戳返回非法日历字段 #11923 是两件事,没有混进本次改动。utest 暂未加入。可以在
components/libc/compilers/common/utest/time_tc.c里挂 -1 与 -86400 两个用例,但需要先确认它归属哪个 Kconfig、以及哪些跑 utest 的 BSP 会执行到。若维护者认可当前改法,我可以补上用例后更新本 PR。]
当前拉取/合并请求的状态 Intent for your PR
必须选择一项 Choose one (Mandatory):
代码质量 Code Quality:
我在这个拉取/合并请求中已经考虑了 As part of this pull request, I've considered the following:
#if 0代码,不包含已经被注释了的代码 All redundant code is removed and cleaned up