Skip to content

Commit 74d5bf4

Browse files
committed
[fix][kernel] fix timegm month overflow calculation
The original overflow logic used __spm[tm_mon] as the current month's length when normalizing tm_mday. This produced incorrect dates after crossing month boundaries and left zero or negative days unnormalized. Use the difference between adjacent cumulative day offsets with leap-year handling. Normalize overflow in tm_sec, tm_min, tm_hour, tm_mday, and tm_mon to keep the affected broken-down fields normalized. Add regression tests for month boundaries, leap years, large offsets, zero and negative days, negative time fields, combined overflow, and boundary carry cases. Close #11686 Signed-off-by: Hui Su <3164683437@qq.com>
1 parent 991f0ff commit 74d5bf4

4 files changed

Lines changed: 332 additions & 8 deletions

File tree

components/libc/compilers/common/ctime.c

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,7 @@ int stime(const time_t *t)
390390
}
391391
RTM_EXPORT(stime);
392392

393+
/* Normalize the time fields and convert the result to a UTC timestamp. */
393394
time_t timegm(struct tm * const t)
394395
{
395396
time_t day;
@@ -402,33 +403,63 @@ time_t timegm(struct tm * const t)
402403
return (time_t)-1;
403404
}
404405

405-
if (t->tm_sec > 60) /* seconds after the minute - [0, 60] including leap second */
406+
if (t->tm_sec < 0 || t->tm_sec > 60) /* seconds after the minute - [0, 60] including leap second */
406407
{
407408
t->tm_min += t->tm_sec / 60;
408409
t->tm_sec %= 60;
410+
if (t->tm_sec < 0)
411+
{
412+
t->tm_sec += 60;
413+
--t->tm_min;
414+
}
409415
}
410-
if (t->tm_min >= 60) /* minutes after the hour - [0, 59] */
416+
if (t->tm_min < 0 || t->tm_min >= 60) /* minutes after the hour - [0, 59] */
411417
{
412418
t->tm_hour += t->tm_min / 60;
413419
t->tm_min %= 60;
420+
if (t->tm_min < 0)
421+
{
422+
t->tm_min += 60;
423+
--t->tm_hour;
424+
}
414425
}
415-
if (t->tm_hour >= 24) /* hours since midnight - [0, 23] */
426+
if (t->tm_hour < 0 || t->tm_hour >= 24) /* hours since midnight - [0, 23] */
416427
{
417428
t->tm_mday += t->tm_hour / 24;
418429
t->tm_hour %= 24;
430+
if (t->tm_hour < 0)
431+
{
432+
t->tm_hour += 24;
433+
--t->tm_mday;
434+
}
419435
}
420-
if (t->tm_mon >= 12) /* months since January - [0, 11] */
436+
if (t->tm_mon < 0 || t->tm_mon >= 12) /* months since January - [0, 11] */
421437
{
422438
t->tm_year += t->tm_mon / 12;
423439
t->tm_mon %= 12;
440+
if (t->tm_mon < 0)
441+
{
442+
t->tm_mon += 12;
443+
--t->tm_year;
444+
}
424445
}
425-
while (t->tm_mday > __spm[1 + t->tm_mon])
446+
while (t->tm_mday <= 0)
426447
{
427-
if (t->tm_mon == 1 && __isleap(t->tm_year + 1900))
448+
if (t->tm_mon == 0)
428449
{
429-
--t->tm_mday;
450+
t->tm_mon = 11;
451+
--t->tm_year;
430452
}
431-
t->tm_mday -= __spm[t->tm_mon];
453+
else
454+
{
455+
--t->tm_mon;
456+
}
457+
t->tm_mday += __spm[t->tm_mon + 1] - __spm[t->tm_mon] +
458+
(__isleap(t->tm_year + 1900) && t->tm_mon == 1);
459+
}
460+
while (t->tm_mday > __spm[t->tm_mon + 1] - __spm[t->tm_mon] + (__isleap(t->tm_year + 1900) && t->tm_mon == 1))
461+
{
462+
t->tm_mday -= __spm[t->tm_mon + 1] - __spm[t->tm_mon] + (__isleap(t->tm_year + 1900) && t->tm_mon == 1);
432463
++t->tm_mon;
433464
if (t->tm_mon > 11)
434465
{

src/utest/Kconfig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ menu "Kernel Core"
1010
default n
1111
depends on RT_USING_SMALL_MEM
1212

13+
config RT_UTEST_TIME
14+
bool "Time Conversion Test"
15+
default n
16+
depends on RT_USING_POSIX_CLOCK
17+
1318
config RT_UTEST_OBJECT
1419
select RT_USING_DEVICE
1520
select RT_USING_SEMAPHORE

src/utest/SConscript

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ if GetDepend(['RT_UTEST_MEMHEAP']):
1414
if GetDepend(['RT_UTEST_SMALL_MEM']):
1515
src += ['mem_tc.c']
1616

17+
if GetDepend(['RT_UTEST_TIME']):
18+
src += ['time_tc.c']
19+
1720
if GetDepend(['RT_UTEST_SLAB']):
1821
src += ['slab_tc.c']
1922

src/utest/time_tc.c

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
/*
2+
* Copyright (c) 2006-2026, RT-Thread Development Team
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
/**
8+
* Test Case Name: POSIX Time Conversion Test
9+
*
10+
* Test Objectives:
11+
* - Verify that timegm normalizes dates across month and year boundaries.
12+
* - Verify negative values in the time fields are normalized.
13+
* - Verify leap-year February handling.
14+
* - Prevent regression of GitHub Issue #11686.
15+
*/
16+
17+
#include <rtthread.h>
18+
#include <sys/time.h>
19+
#include "utest.h"
20+
21+
/* Verify that timegm output agrees with a UTC round-trip conversion. */
22+
static void timegm_assert_consistent(struct tm *time_value, time_t expected_timestamp)
23+
{
24+
struct tm normalized = { 0 };
25+
time_t timestamp;
26+
27+
timestamp = timegm(time_value);
28+
29+
uassert_int_equal(timestamp, expected_timestamp);
30+
31+
if (gmtime_r(&timestamp, &normalized) == RT_NULL)
32+
{
33+
uassert_not_null(RT_NULL);
34+
return;
35+
}
36+
37+
uassert_int_equal(normalized.tm_year, time_value->tm_year);
38+
uassert_int_equal(normalized.tm_mon, time_value->tm_mon);
39+
uassert_int_equal(normalized.tm_mday, time_value->tm_mday);
40+
uassert_int_equal(normalized.tm_hour, time_value->tm_hour);
41+
uassert_int_equal(normalized.tm_min, time_value->tm_min);
42+
uassert_int_equal(normalized.tm_sec, time_value->tm_sec);
43+
uassert_int_equal(normalized.tm_yday, time_value->tm_yday);
44+
uassert_int_equal(normalized.tm_wday, time_value->tm_wday);
45+
}
46+
47+
/* Verify date normalization across a regular month boundary. */
48+
static void timegm_month_boundary_test(void)
49+
{
50+
struct tm time_value = { 0 };
51+
52+
time_value.tm_year = 123;
53+
time_value.tm_mon = 0;
54+
time_value.tm_mday = 32;
55+
56+
timegm(&time_value);
57+
58+
uassert_int_equal(time_value.tm_year, 123);
59+
uassert_int_equal(time_value.tm_mon, 1);
60+
uassert_int_equal(time_value.tm_mday, 1);
61+
}
62+
63+
/* Verify date normalization for February in a leap year. */
64+
static void timegm_leap_year_test(void)
65+
{
66+
struct tm time_value = { 0 };
67+
68+
time_value.tm_year = 124;
69+
time_value.tm_mon = 0;
70+
time_value.tm_mday = 60;
71+
72+
timegm(&time_value);
73+
74+
uassert_int_equal(time_value.tm_year, 124);
75+
uassert_int_equal(time_value.tm_mon, 1);
76+
uassert_int_equal(time_value.tm_mday, 29);
77+
}
78+
79+
/* Verify date normalization for a large day offset. */
80+
static void timegm_large_day_test(void)
81+
{
82+
struct tm time_value = { 0 };
83+
84+
time_value.tm_year = 126;
85+
time_value.tm_mon = 7;
86+
time_value.tm_mday = 425;
87+
time_value.tm_hour = 8;
88+
89+
timegm_assert_consistent(&time_value, (time_t)1822204800);
90+
91+
uassert_int_equal(time_value.tm_year, 127);
92+
uassert_int_equal(time_value.tm_mon, 8);
93+
uassert_int_equal(time_value.tm_mday, 29);
94+
uassert_int_equal(time_value.tm_hour, 8);
95+
uassert_int_equal(time_value.tm_yday, 271);
96+
uassert_int_equal(time_value.tm_wday, 3);
97+
}
98+
99+
/* Verify zero-day normalization across a year boundary. */
100+
static void timegm_zero_day_test(void)
101+
{
102+
struct tm time_value = { 0 };
103+
104+
time_value.tm_year = 123;
105+
time_value.tm_mon = 0;
106+
time_value.tm_mday = 0;
107+
108+
timegm_assert_consistent(&time_value, (time_t)1672444800);
109+
110+
uassert_int_equal(time_value.tm_year, 122);
111+
uassert_int_equal(time_value.tm_mon, 11);
112+
uassert_int_equal(time_value.tm_mday, 31);
113+
}
114+
115+
/* Verify negative-day normalization in a leap year. */
116+
static void timegm_negative_day_test(void)
117+
{
118+
struct tm time_value = { 0 };
119+
120+
time_value.tm_year = 124;
121+
time_value.tm_mon = 2;
122+
time_value.tm_mday = -1;
123+
124+
timegm_assert_consistent(&time_value, (time_t)1709078400);
125+
126+
uassert_int_equal(time_value.tm_year, 124);
127+
uassert_int_equal(time_value.tm_mon, 1);
128+
uassert_int_equal(time_value.tm_mday, 28);
129+
}
130+
131+
/* Verify negative-second normalization across a year boundary. */
132+
static void timegm_negative_second_test(void)
133+
{
134+
struct tm time_value = { 0 };
135+
136+
time_value.tm_year = 123;
137+
time_value.tm_mon = 0;
138+
time_value.tm_mday = 1;
139+
time_value.tm_sec = -61;
140+
141+
timegm(&time_value);
142+
143+
uassert_int_equal(time_value.tm_year, 122);
144+
uassert_int_equal(time_value.tm_mon, 11);
145+
uassert_int_equal(time_value.tm_mday, 31);
146+
uassert_int_equal(time_value.tm_hour, 23);
147+
uassert_int_equal(time_value.tm_min, 58);
148+
uassert_int_equal(time_value.tm_sec, 59);
149+
}
150+
151+
/* Verify negative-minute normalization across a year boundary. */
152+
static void timegm_negative_minute_test(void)
153+
{
154+
struct tm time_value = { 0 };
155+
156+
time_value.tm_year = 123;
157+
time_value.tm_mon = 0;
158+
time_value.tm_mday = 1;
159+
time_value.tm_min = -1;
160+
161+
timegm(&time_value);
162+
163+
uassert_int_equal(time_value.tm_year, 122);
164+
uassert_int_equal(time_value.tm_mon, 11);
165+
uassert_int_equal(time_value.tm_mday, 31);
166+
uassert_int_equal(time_value.tm_hour, 23);
167+
uassert_int_equal(time_value.tm_min, 59);
168+
uassert_int_equal(time_value.tm_sec, 0);
169+
}
170+
171+
/* Verify negative-hour normalization across a year boundary. */
172+
static void timegm_negative_hour_test(void)
173+
{
174+
struct tm time_value = { 0 };
175+
176+
time_value.tm_year = 123;
177+
time_value.tm_mon = 0;
178+
time_value.tm_mday = 1;
179+
time_value.tm_hour = -1;
180+
181+
timegm(&time_value);
182+
183+
uassert_int_equal(time_value.tm_year, 122);
184+
uassert_int_equal(time_value.tm_mon, 11);
185+
uassert_int_equal(time_value.tm_mday, 31);
186+
uassert_int_equal(time_value.tm_hour, 23);
187+
uassert_int_equal(time_value.tm_min, 0);
188+
uassert_int_equal(time_value.tm_sec, 0);
189+
}
190+
191+
/* Verify negative-month normalization across a year boundary. */
192+
static void timegm_negative_month_test(void)
193+
{
194+
struct tm time_value = { 0 };
195+
196+
time_value.tm_year = 123;
197+
time_value.tm_mon = -13;
198+
time_value.tm_mday = 1;
199+
200+
timegm(&time_value);
201+
202+
uassert_int_equal(time_value.tm_year, 121);
203+
uassert_int_equal(time_value.tm_mon, 11);
204+
uassert_int_equal(time_value.tm_mday, 1);
205+
}
206+
207+
/* Verify positive overflow across all time fields. */
208+
static void timegm_positive_overflow_test(void)
209+
{
210+
struct tm time_value = { 0 };
211+
212+
time_value.tm_year = 123;
213+
time_value.tm_mon = 12;
214+
time_value.tm_mday = 1;
215+
time_value.tm_hour = 25;
216+
time_value.tm_min = 61;
217+
time_value.tm_sec = 61;
218+
219+
timegm_assert_consistent(&time_value, (time_t)1704160921);
220+
221+
uassert_int_equal(time_value.tm_year, 124);
222+
uassert_int_equal(time_value.tm_mon, 0);
223+
uassert_int_equal(time_value.tm_mday, 2);
224+
uassert_int_equal(time_value.tm_hour, 2);
225+
uassert_int_equal(time_value.tm_min, 2);
226+
uassert_int_equal(time_value.tm_sec, 1);
227+
}
228+
229+
/* Verify negative overflow across all time fields. */
230+
static void timegm_combined_negative_test(void)
231+
{
232+
struct tm time_value = { 0 };
233+
234+
time_value.tm_year = 123;
235+
time_value.tm_mon = -13;
236+
time_value.tm_mday = 1;
237+
time_value.tm_hour = -25;
238+
time_value.tm_min = -61;
239+
time_value.tm_sec = -61;
240+
241+
timegm_assert_consistent(&time_value, (time_t)1638223079);
242+
243+
uassert_int_equal(time_value.tm_year, 121);
244+
uassert_int_equal(time_value.tm_mon, 10);
245+
uassert_int_equal(time_value.tm_mday, 29);
246+
uassert_int_equal(time_value.tm_hour, 21);
247+
uassert_int_equal(time_value.tm_min, 57);
248+
uassert_int_equal(time_value.tm_sec, 59);
249+
}
250+
251+
/* Initialize the time conversion tests. */
252+
static rt_err_t utest_tc_init(void)
253+
{
254+
return RT_EOK;
255+
}
256+
257+
/* Clean up the time conversion tests. */
258+
static rt_err_t utest_tc_cleanup(void)
259+
{
260+
return RT_EOK;
261+
}
262+
263+
/* Run all time conversion tests in one unit so failures are retained. */
264+
static void timegm_all_test(void)
265+
{
266+
timegm_month_boundary_test();
267+
timegm_leap_year_test();
268+
timegm_large_day_test();
269+
timegm_zero_day_test();
270+
timegm_negative_day_test();
271+
timegm_negative_second_test();
272+
timegm_negative_minute_test();
273+
timegm_negative_hour_test();
274+
timegm_negative_month_test();
275+
timegm_positive_overflow_test();
276+
timegm_combined_negative_test();
277+
}
278+
279+
/* Run the time conversion test case. */
280+
static void testcase(void)
281+
{
282+
UTEST_UNIT_RUN(timegm_all_test);
283+
}
284+
285+
UTEST_TC_EXPORT(testcase, "core.time", utest_tc_init, utest_tc_cleanup, 10);

0 commit comments

Comments
 (0)