diff --git a/include/sys/timeutil.h b/include/sys/timeutil.h index ddc437b590e..98b2faeaee3 100644 --- a/include/sys/timeutil.h +++ b/include/sys/timeutil.h @@ -11,7 +11,11 @@ * POSIX defines gmtime() to convert from time_t to struct tm, but all * inverse transformations are non-standard or require access to time * zone information. timeutil_timegm() implements the functionality - * of the GNU extension timegm() function. + * of the GNU extension timegm() function, but changes the error value + * as @c EOVERFLOW is not a standard C error identifier. + * + * timeutil_timegm64() is provided to support full precision + * conversion on platforms where @c time_t is limited to 32 bits. */ #ifndef ZEPHYR_INCLUDE_SYS_TIMEUTIL_H_ @@ -32,6 +36,19 @@ extern "C" { * * @see http://man7.org/linux/man-pages/man3/timegm.3.html */ +s64_t timeutil_timegm64(const struct tm *tm); + +/** + * @brief Convert broken-down time to a POSIX epoch offset in seconds. + * + * @param tm pointer to broken down time. + * + * @return the corresponding time in the POSIX epoch time scale. If + * the time cannot be represented then @c (time_t)-1 is returned and + * @c errno is set to @c ERANGE`. + * + * @see http://man7.org/linux/man-pages/man3/timegm.3.html + */ time_t timeutil_timegm(const struct tm *tm); #ifdef __cplusplus diff --git a/lib/os/timeutil.c b/lib/os/timeutil.c index 40a72fc0472..fbaffa0df00 100644 --- a/lib/os/timeutil.c +++ b/lib/os/timeutil.c @@ -11,6 +11,7 @@ */ #include +#include #include /** Convert a civil (proleptic Gregorian) date to days relative to @@ -39,23 +40,31 @@ static s64_t time_days_from_civil(s64_t y, return era * 146097 + (time_t)doe - 719468; } -/** Convert civil time to UNIX time. - * - * @param tvp pointer to a civil time structure. `tm_year`, `tm_mon`, - * `tm_mday`, `tm_hour`, `tm_min`, and `tm_sec` must be valid. All - * other fields are ignored. - * - * @return the signed number of seconds between 1970-01-01T00:00:00 - * and the specified time ignoring leap seconds and DST offsets. - */ -time_t timeutil_timegm(const struct tm *tm) +s64_t timeutil_timegm64(const struct tm *tm) { s64_t y = 1900 + (s64_t)tm->tm_year; unsigned int m = tm->tm_mon + 1; unsigned int d = tm->tm_mday - 1; s64_t ndays = time_days_from_civil(y, m, d); + s64_t time = tm->tm_sec; - return (time_t)tm->tm_sec - + 60 * (tm->tm_min + 60 * tm->tm_hour) - + 86400 * ndays; + time += 60LL * (tm->tm_min + 60LL * tm->tm_hour); + time += 86400LL * ndays; + + return time; +} + +time_t timeutil_timegm(const struct tm *tm) +{ + s64_t time = timeutil_timegm64(tm); + time_t rv = (time_t)time; + + errno = 0; + if ((sizeof(rv) == sizeof(s32_t)) + && ((time < (s64_t)INT32_MIN) + || (time > (s64_t)INT32_MAX))) { + errno = ERANGE; + rv = -1; + } + return rv; } diff --git a/tests/lib/timeutil/src/test_s64.c b/tests/lib/timeutil/src/test_s64.c index a0d0b92cb45..375c31acb14 100644 --- a/tests/lib/timeutil/src/test_s64.c +++ b/tests/lib/timeutil/src/test_s64.c @@ -6,6 +6,7 @@ /* Tests where time_t requires a 64-bit value */ +#include #include #include "timeutil_test.h" @@ -210,10 +211,125 @@ static const struct timeutil_test_data tests[] = { } }, }; +static void test_time32_errno_clear(void) +{ + const struct timeutil_test_data *tp = &(const struct timeutil_test_data){ + .unix = 0, + .civil = "1970-01-01 00:00:00 Thu 001", + .tm = { + .tm_sec = 0, + .tm_min = 0, + .tm_hour = 0, + .tm_mday = 1, + .tm_mon = 0, + .tm_year = 70, + .tm_wday = 4, + .tm_yday = 0, + }, + }; + + errno = EINVAL; + + time_t unix = timeutil_timegm(&tp->tm); + + zassert_equal(unix, tp->unix, + "conversion incorrect"); + zassert_equal(errno, 0, + "errno was not cleared"); +} + +static void test_time32_epochm1(void) +{ + const struct timeutil_test_data *tp = &(const struct timeutil_test_data){ + .unix = -1, + .civil = "1969-12-31 23:59:59 Wed 365", + .tm = { + .tm_sec = 59, + .tm_min = 59, + .tm_hour = 23, + .tm_mday = 31, + .tm_mon = 11, + .tm_year = 69, + .tm_wday = 3, + .tm_yday = 364, + }, + }; + + errno = EINVAL; + + time_t unix = timeutil_timegm(&tp->tm); + + zassert_equal(unix, tp->unix, + "conversion incorrect"); + zassert_equal(errno, 0, + "final errno state bad"); +} + +static void test_time32_underflow(void) +{ + const s64_t unix64 = -2147483649; + const struct timeutil_test_data *tp = &(const struct timeutil_test_data){ + .civil = "1901-12-13 20:45:51 Fri 347", + .tm = { + .tm_sec = 51, + .tm_min = 45, + .tm_hour = 20, + .tm_mday = 13, + .tm_mon = 11, + .tm_year = 1, + .tm_wday = 5, + .tm_yday = 346, + }, + }; + + zassert_equal(timeutil_timegm64(&tp->tm), unix64, + "fullscale failed"); + errno = 0; + + time_t unix = timeutil_timegm(&tp->tm); + + zassert_equal(unix, -1, + "underflow undetected"); + zassert_equal(errno, ERANGE, + "final errno state bad"); +} + +static void test_time32_overflow(void) +{ + const s64_t unix64 = 2147483648; + const struct timeutil_test_data *tp = &(const struct timeutil_test_data){ + .civil = "2038-01-19 03:14:08 Tue 019", + .tm = { + .tm_sec = 8, + .tm_min = 14, + .tm_hour = 3, + .tm_mday = 19, + .tm_mon = 0, + .tm_year = 138, + .tm_wday = 2, + .tm_yday = 18, + }, + }; + + zassert_equal(timeutil_timegm64(&tp->tm), unix64, + "fullscale failed"); + errno = 0; + + time_t unix = timeutil_timegm(&tp->tm); + + zassert_equal(unix, -1, + "overflow undetected"); + zassert_equal(errno, ERANGE, + "final errno state bad"); +} + void test_s64(void) { if (sizeof(time_t) < 8U) { - ztest_test_skip(); + test_time32_errno_clear(); + test_time32_epochm1(); + test_time32_underflow(); + test_time32_overflow(); return; } timeutil_check(tests, sizeof(tests) / sizeof(*tests));