Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 35 additions & 9 deletions components/libc/compilers/common/ctime.c
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ struct tm *gmtime_r(const time_t *timep, struct tm *r)
{
int i;
int work;
time_t days;
time_t seconds_of_day;

if(timep == RT_NULL || r == RT_NULL)
{
Expand All @@ -176,20 +178,44 @@ struct tm *gmtime_r(const time_t *timep, struct tm *r)

rt_memset(r, RT_NULL, sizeof(struct tm));

work = *timep % (24*60*60);
/*
* C division truncates toward zero, so a negative timestamp yields a
* negative day count and a negative remainder. Normalise to floor division
* first, otherwise tm_sec / tm_min / tm_hour come out negative (for
* example time_t -1 used to produce 1970-01-01 00:00:-1).
*/
days = *timep / (24 * 60 * 60);
seconds_of_day = *timep % (24 * 60 * 60);
if (seconds_of_day < 0)
{
seconds_of_day += 24 * 60 * 60;
days -= 1;
}

work = (int)seconds_of_day;
r->tm_sec = work % 60;
work /= 60;
r->tm_min = work % 60;
r->tm_hour = work / 60;
work = (int)(*timep / (24*60*60));
r->tm_wday = (4 + work) % 7;
for (i = 1970;; ++i)

work = (int)days;
/* 1970-01-01 was a Thursday; keep the result positive for days < 0 too */
r->tm_wday = (int)(((4 + days % 7) % 7 + 7) % 7);

/*
* Walk to the year. The day count can be negative (timestamps before the
* epoch), so the loop has to be able to go backwards as well.
*/
i = 1970;
while (work < 0)
{
int k = __isleap(i) ? 366 : 365;
if (work >= k)
work -= k;
else
break;
--i;
work += __isleap(i) ? 366 : 365;
}
while (work >= (__isleap(i) ? 366 : 365))
{
work -= __isleap(i) ? 366 : 365;
++i;
}
r->tm_year = i - 1900;
r->tm_yday = work;
Expand Down
91 changes: 91 additions & 0 deletions components/libc/compilers/common/utest/time_tc.c
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,96 @@ static void timegm_combined_negative_test(void)
uassert_int_equal(time_value.tm_sec, 59);
}

/*
* gmtime_r() against timestamps before the epoch.
*
* Every field is checked, not only the date: the defective implementation returned
* a non-NULL struct tm holding impossible values (negative tm_sec, zero tm_mday,
* negative tm_wday / tm_yday), so tm_wday and tm_yday are part of the contract
* that has to be pinned down here.
*/
struct gmtime_r_case
{
const char *name;
time_t timestamp;
int year; /* tm_year, i.e. years since 1900 */
int mon; /* tm_mon, 0 based */
int mday;
int hour;
int min;
int sec;
int wday; /* 0 = Sunday */
int yday; /* 0 based */
};

static const struct gmtime_r_case gmtime_r_cases[] =
{
/* 1970-01-01 00:00:00, Thursday */
{ "epoch zero", (time_t)0, 70, 0, 1, 0, 0, 0, 4, 0 },
/* 1969-12-31 23:59:59, Wednesday */
{ "t = -1", (time_t)-1, 69, 11, 31, 23, 59, 59, 3, 364 },
/* 1969-12-31 00:00:00, Wednesday */
{ "t = -86400", (time_t)-86400, 69, 11, 31, 0, 0, 0, 3, 364 },
/* 1969-12-30 23:59:59, Tuesday */
{ "t = -86401", (time_t)-86401, 69, 11, 30, 23, 59, 59, 2, 363 },
/* 1968-02-29 00:00:00, Thursday, 672 days before the epoch (1968 is a leap year) */
{ "1968 leap day", (time_t)-58060800, 68, 1, 29, 0, 0, 0, 4, 59 },
};

/* Check every field gmtime_r() fills in, for timestamps before the epoch. */
static void gmtime_r_before_epoch_test(void)
{
size_t i;

/*
* A timestamp before the epoch only exists when time_t is signed. Some
* toolchains (armclang/AC6 among them) define time_t as an unsigned 32-bit
* type; there (time_t)-1 is 4294967295, i.e. 2106-02-07, so the negative
* cases below do not describe anything and the first entry has to be
* checked on its own.
*/
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");
}

for (i = 0; i < sizeof(gmtime_r_cases) / sizeof(gmtime_r_cases[0]); i++)
{
const struct gmtime_r_case *c = &gmtime_r_cases[i];
struct tm r;
time_t timestamp = c->timestamp;

/* the pre-epoch cases are meaningless when time_t cannot hold them */
if (i > 0 && !((time_t)-1 < (time_t)0))
{
continue;
}

rt_memset(&r, 0, sizeof(r));

uassert_not_null(gmtime_r(&timestamp, &r));

LOG_I("%s: %04d-%02d-%02d %02d:%02d:%02d wday=%d yday=%d",
c->name,
r.tm_year + 1900, r.tm_mon + 1, r.tm_mday,
r.tm_hour, r.tm_min, r.tm_sec,
r.tm_wday, r.tm_yday);

uassert_int_equal(r.tm_year, c->year);
uassert_int_equal(r.tm_mon, c->mon);
uassert_int_equal(r.tm_mday, c->mday);
uassert_int_equal(r.tm_hour, c->hour);
uassert_int_equal(r.tm_min, c->min);
uassert_int_equal(r.tm_sec, c->sec);
uassert_int_equal(r.tm_wday, c->wday);
uassert_int_equal(r.tm_yday, c->yday);
}
}

/* Initialize the time conversion tests. */
static rt_err_t utest_tc_init(void)
{
Expand Down Expand Up @@ -301,6 +391,7 @@ static void timegm_all_test(void)
static void testcase(void)
{
UTEST_UNIT_RUN(timegm_all_test);
UTEST_UNIT_RUN(gmtime_r_before_epoch_test);
}

UTEST_TC_EXPORT(testcase, "components.libc.time", utest_tc_init, utest_tc_cleanup, 10);
Loading