libc: common: implement multiple time functions

Implemented the following:
- `asctime_r()`
- `asctime()`
- `localtime()`
- `localtime_r()`
- `ctime()`
- `ctime_r()`

Specifically:
- the implementation of `localtime()` & `localtime_r()` simply
  wraps around the gmtime() & gmtime_r() functions, the
  results are always expressed as UTC.
- `ctime()` is equivalent to `asctime(localtime(clock))`, it
  inherits the limitation of `localtime()` as well, which only
  supports UTC results currently.

Added tests for these newly implemented functions.

Signed-off-by: Yong Cong Sin <ycsin@meta.com>
Signed-off-by: Yong Cong Sin <yongcong.sin@gmail.com>
This commit is contained in:
Yong Cong Sin 2024-08-15 14:49:32 +08:00 committed by Anas Nashif
commit fe94d4354a
13 changed files with 256 additions and 5 deletions

View file

@ -981,8 +981,8 @@ Enable this option with :kconfig:option:`CONFIG_POSIX_THREAD_SAFE_FUNCTIONS`.
:header: API, Supported :header: API, Supported
:widths: 50,10 :widths: 50,10
asctime_r(), asctime_r(), yes
ctime_r(), ctime_r(), yes (UTC timezone only)
flockfile(), flockfile(),
ftrylockfile(), ftrylockfile(),
funlockfile(), funlockfile(),
@ -993,7 +993,7 @@ Enable this option with :kconfig:option:`CONFIG_POSIX_THREAD_SAFE_FUNCTIONS`.
getpwnam_r(),yes :ref:`†<posix_undefined_behaviour>` getpwnam_r(),yes :ref:`†<posix_undefined_behaviour>`
getpwuid_r(),yes :ref:`†<posix_undefined_behaviour>` getpwuid_r(),yes :ref:`†<posix_undefined_behaviour>`
gmtime_r(), yes gmtime_r(), yes
localtime_r(), localtime_r(), yes (UTC timezone only)
putc_unlocked(), putc_unlocked(),
putchar_unlocked(), putchar_unlocked(),
rand_r(), yes rand_r(), yes

View file

@ -5,7 +5,10 @@ zephyr_system_include_directories(include)
zephyr_library() zephyr_library()
zephyr_library_property(ALLOW_EMPTY TRUE) zephyr_library_property(ALLOW_EMPTY TRUE)
zephyr_library_sources_ifdef(CONFIG_COMMON_LIBC_ABORT source/stdlib/abort.c) zephyr_library_sources_ifdef(CONFIG_COMMON_LIBC_ABORT source/stdlib/abort.c)
zephyr_library_sources_ifdef(CONFIG_COMMON_LIBC_ASCTIME source/time/asctime.c)
zephyr_library_sources_ifdef(CONFIG_COMMON_LIBC_GMTIME_R source/time/gmtime_r.c) zephyr_library_sources_ifdef(CONFIG_COMMON_LIBC_GMTIME_R source/time/gmtime_r.c)
zephyr_library_sources_ifdef(CONFIG_COMMON_LIBC_LOCALTIME_R_UTC source/time/localtime_r_utc.c)
zephyr_library_sources_ifdef(CONFIG_COMMON_LIBC_CTIME source/time/ctime.c)
zephyr_library_sources_ifdef(CONFIG_COMMON_LIBC_TIME source/time/time.c) zephyr_library_sources_ifdef(CONFIG_COMMON_LIBC_TIME source/time/time.c)
zephyr_library_sources_ifdef(CONFIG_COMMON_LIBC_MALLOC source/stdlib/malloc.c) zephyr_library_sources_ifdef(CONFIG_COMMON_LIBC_MALLOC source/stdlib/malloc.c)
zephyr_library_sources_ifdef(CONFIG_COMMON_LIBC_STRNLEN source/string/strnlen.c) zephyr_library_sources_ifdef(CONFIG_COMMON_LIBC_STRNLEN source/string/strnlen.c)

View file

@ -6,11 +6,44 @@ config COMMON_LIBC_ABORT
help help
common implementation of abort(). common implementation of abort().
config COMMON_LIBC_ASCTIME
bool
help
common implementation of asctime().
config COMMON_LIBC_ASCTIME_R
bool "Thread-safe version of asctime()"
default y if POSIX_THREAD_SAFE_FUNCTIONS
select COMMON_LIBC_ASCTIME
help
common implementation of asctime_r().
config COMMON_LIBC_CTIME
bool
select COMMON_LIBC_LOCALTIME_R_UTC
help
common implementation of ctime().
config COMMON_LIBC_CTIME_R
bool "Thread-safe version of ctime()"
default y if POSIX_THREAD_SAFE_FUNCTIONS
select COMMON_LIBC_CTIME
help
common implementation of ctime_r().
config COMMON_LIBC_GMTIME_R config COMMON_LIBC_GMTIME_R
bool bool
help help
common implementation of gmtime_r(). common implementation of gmtime_r().
config COMMON_LIBC_LOCALTIME_R_UTC
bool
select COMMON_LIBC_GMTIME_R
help
Simple implementation of localtime() & localtime_r().
This option just wraps around the gmtime(), the result is always expressed as
Coordinated Universal Time (UTC).
config COMMON_LIBC_TIME config COMMON_LIBC_TIME
bool bool
help help

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2024 Meta Platforms
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <zephyr/sys/util.h>
#define DATE_STRING_BUF_SZ 26U
#define DATE_WDAY_STRING_SZ 7U
#define DATE_MON_STRING_SZ 12U
#define DATE_TM_YEAR_BASE 1900
static char *asctime_impl(const struct tm *tp, char *buf)
{
static const char wday_str[DATE_WDAY_STRING_SZ][3] = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
};
static const char mon_str[DATE_MON_STRING_SZ][3] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
};
if ((buf == NULL) || (tp == NULL) || ((unsigned int)tp->tm_wday >= DATE_WDAY_STRING_SZ) ||
((unsigned int)tp->tm_mon >= DATE_MON_STRING_SZ)) {
return NULL;
}
unsigned int n = (unsigned int)snprintf(
buf, DATE_STRING_BUF_SZ, "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n", wday_str[tp->tm_wday],
mon_str[tp->tm_mon], tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec,
DATE_TM_YEAR_BASE + tp->tm_year);
if (n >= DATE_STRING_BUF_SZ) {
return NULL;
}
return buf;
}
char *asctime(const struct tm *tp)
{
static char buf[DATE_STRING_BUF_SZ];
return asctime_impl(tp, buf);
}
#if defined(CONFIG_COMMON_LIBC_ASCTIME_R)
char *asctime_r(const struct tm *tp, char *buf)
{
return asctime_impl(tp, buf);
}
#endif /* CONFIG_COMMON_LIBC_ASCTIME_R */

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2024 Meta Platforms
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <time.h>
/**
* `ctime()` is equivalent to `asctime(localtime(clock))`
* See: https://pubs.opengroup.org/onlinepubs/009695399/functions/ctime.html
*/
char *ctime(const time_t *clock)
{
return asctime(localtime(clock));
}
#if defined(CONFIG_COMMON_LIBC_CTIME_R)
char *ctime_r(const time_t *clock, char *buf)
{
struct tm tmp;
return asctime_r(localtime_r(clock, &tmp), buf);
}
#endif /* CONFIG_COMMON_LIBC_CTIME_R */

View file

@ -0,0 +1,17 @@
/*
* Copyright (c) 2024 Meta Platforms
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <time.h>
struct tm *localtime_r(const time_t *timer, struct tm *result)
{
return gmtime_r(timer, result);
}
struct tm *localtime(const time_t *timer)
{
return gmtime(timer);
}

View file

@ -39,6 +39,9 @@ config MINIMAL_LIBC_TIME
bool "Time functions" bool "Time functions"
select COMMON_LIBC_TIME if POSIX_TIMERS select COMMON_LIBC_TIME if POSIX_TIMERS
select COMMON_LIBC_GMTIME_R select COMMON_LIBC_GMTIME_R
select COMMON_LIBC_ASCTIME
select COMMON_LIBC_LOCALTIME_R_UTC
select COMMON_LIBC_CTIME
default y default y
help help
Enable time() and gmtime_r() for the minimal libc. Enable time() and gmtime_r() for the minimal libc.

View file

@ -45,12 +45,22 @@ typedef _SUSECONDS_T_ suseconds_t;
/* /*
* Conversion between civil time and UNIX time. The companion * Conversion between civil time and UNIX time. The companion
* localtime() and inverse mktime() are not provided here since they * mktime() is not provided here since it
* require access to time zone information. * require access to time zone information.
*
* The localtime() & localtime_r() simply
* wraps around the gmtime() & gmtime_r() functions, the
* results are always expressed as UTC.
*/ */
struct tm *gmtime(const time_t *timep); struct tm *gmtime(const time_t *timep);
struct tm *gmtime_r(const time_t *ZRESTRICT timep, struct tm *gmtime_r(const time_t *ZRESTRICT timep,
struct tm *ZRESTRICT result); struct tm *ZRESTRICT result);
char *asctime(const struct tm *timeptr);
struct tm *localtime(const time_t *timer);
char *ctime(const time_t *clock);
char *asctime_r(const struct tm *ZRESTRICT tp, char *ZRESTRICT buf);
char *ctime_r(const time_t *clock, char *buf);
struct tm *localtime_r(const time_t *ZRESTRICT timer, struct tm *ZRESTRICT result);
time_t time(time_t *tloc); time_t time(time_t *tloc);

View file

@ -10,6 +10,7 @@ rsource "Kconfig.profile"
rsource "Kconfig.aio" rsource "Kconfig.aio"
rsource "Kconfig.barrier" rsource "Kconfig.barrier"
rsource "Kconfig.c_lang_r"
rsource "Kconfig.c_lib_ext" rsource "Kconfig.c_lib_ext"
rsource "Kconfig.device_io" rsource "Kconfig.device_io"
rsource "Kconfig.fd_mgmt" rsource "Kconfig.fd_mgmt"

View file

@ -0,0 +1,18 @@
# Copyright (c) 2024 Tenstorrent AI ULC
# Copyright (c) 2024 Meta Platforms
#
# SPDX-License-Identifier: Apache-2.0
config POSIX_C_LANG_SUPPORT_R
bool "Thread-Safe General ISO C Library"
select COMMON_LIBC_ASCTIME_R
select COMMON_LIBC_CTIME_R
select COMMON_LIBC_GMTIME_R
select COMMON_LIBC_LOCALTIME_R_UTC
help
Select 'y' here and Zephyr will provide an implementation of the POSIX_C_LANG_SUPPORT_R
Option Group, consisting of asctime_r(), ctime_r(), gmtime_r(), localtime_r(), rand_r(),
strerror_r(), and strtok_r()
For more informnation, please see
https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_subprofiles.html

View file

@ -157,6 +157,7 @@ config POSIX_THREAD_PRIO_PROTECT
config POSIX_THREAD_SAFE_FUNCTIONS config POSIX_THREAD_SAFE_FUNCTIONS
bool "POSIX thread-safe functions" bool "POSIX thread-safe functions"
select POSIX_FILE_SYSTEM_R if POSIX_FILE_SYSTEM select POSIX_FILE_SYSTEM_R if POSIX_FILE_SYSTEM
select POSIX_C_LANG_SUPPORT_R
help help
Select 'y' here to enable POSIX thread-safe functions including asctime_r(), ctime_r(), Select 'y' here to enable POSIX thread-safe functions including asctime_r(), ctime_r(),
flockfile(), ftrylockfile(), funlockfile(), getc_unlocked(), getchar_unlocked(), flockfile(), ftrylockfile(), funlockfile(), getc_unlocked(), getchar_unlocked(),

View file

@ -22,6 +22,7 @@
#include <zephyr/kernel.h> #include <zephyr/kernel.h>
#include <zephyr/sys/__assert.h> #include <zephyr/sys/__assert.h>
#include <zephyr/sys/util.h>
#include <zephyr/ztest.h> #include <zephyr/ztest.h>
#include <limits.h> #include <limits.h>
@ -1075,7 +1076,7 @@ ZTEST(libc_common, test_strtok_r)
* *
* @see gmtime(),gmtime_r(). * @see gmtime(),gmtime_r().
*/ */
ZTEST(libc_common, test_time) ZTEST(libc_common, test_time_gmtime)
{ {
time_t tests1 = 0; time_t tests1 = 0;
time_t tests2 = -5; time_t tests2 = -5;
@ -1092,6 +1093,86 @@ ZTEST(libc_common, test_time)
zassert_not_null(gmtime_r(&tests4, &tp), "gmtime_r failed"); zassert_not_null(gmtime_r(&tests4, &tp), "gmtime_r failed");
} }
/**
* @brief Test time function
*
* @see asctime(), asctime_r().
*/
ZTEST(libc_common, test_time_asctime)
{
char buf[26] = {0};
struct tm tp = {
.tm_sec = 10, /* Seconds */
.tm_min = 30, /* Minutes */
.tm_hour = 14, /* Hour (24-hour format) */
.tm_wday = 5, /* Day of the week (0-6, 0 = Sun) */
.tm_mday = 1, /* Day of the month */
.tm_mon = 5, /* Month (0-11, January = 0) */
.tm_year = 124, /* Year (current year - 1900) */
};
zassert_not_null(asctime_r(&tp, buf));
zassert_equal(strncmp("Fri Jun 1 14:30:10 2024\n", buf, sizeof(buf)), 0);
zassert_not_null(asctime(&tp));
zassert_equal(strncmp("Fri Jun 1 14:30:10 2024\n", asctime(&tp), sizeof(buf)), 0);
if (IS_ENABLED(CONFIG_COMMON_LIBC_ASCTIME_R)) {
zassert_is_null(asctime_r(NULL, buf));
zassert_is_null(asctime(NULL));
zassert_is_null(asctime_r(&tp, NULL));
tp.tm_wday = 8;
zassert_is_null(asctime_r(&tp, buf));
zassert_is_null(asctime(&tp));
tp.tm_wday = 5;
tp.tm_mon = 12;
zassert_is_null(asctime_r(&tp, buf));
zassert_is_null(asctime(&tp));
}
}
/**
* @brief Test time function
*
* @see localtime(), localtime_r().
*/
ZTEST(libc_common, test_time_localtime)
{
time_t tests1 = 0;
time_t tests2 = -5;
time_t tests3 = (time_t) -214748364800;
time_t tests4 = 951868800;
struct tm tp;
zassert_not_null(localtime(&tests1), "localtime failed");
zassert_not_null(localtime(&tests2), "localtime failed");
tp.tm_wday = -5;
zassert_not_null(localtime_r(&tests3, &tp), "localtime_r failed");
zassert_not_null(localtime_r(&tests4, &tp), "localtime_r failed");
}
/**
* @brief Test time function
*
* @see ctime(), ctime_r().
*/
ZTEST(libc_common, test_time_ctime)
{
char buf[26] = {0};
time_t test1 = 1718260000;
zassert_not_null(ctime_r(&test1, buf));
zassert_equal(strncmp("Thu Jun 13 06:26:40 2024\n", buf, sizeof(buf)), 0);
zassert_not_null(ctime(&test1));
zassert_equal(strncmp("Thu Jun 13 06:26:40 2024\n", ctime(&test1), sizeof(buf)), 0);
}
/** /**
* *
* @brief Test rand function * @brief Test rand function

View file

@ -13,6 +13,8 @@ tests:
- CONFIG_MINIMAL_LIBC=y - CONFIG_MINIMAL_LIBC=y
- CONFIG_MINIMAL_LIBC_NON_REENTRANT_FUNCTIONS=y - CONFIG_MINIMAL_LIBC_NON_REENTRANT_FUNCTIONS=y
- CONFIG_MINIMAL_LIBC_RAND=y - CONFIG_MINIMAL_LIBC_RAND=y
- CONFIG_COMMON_LIBC_ASCTIME_R=y
- CONFIG_COMMON_LIBC_CTIME_R=y
libraries.libc.common.newlib: libraries.libc.common.newlib:
filter: CONFIG_NEWLIB_LIBC_SUPPORTED filter: CONFIG_NEWLIB_LIBC_SUPPORTED
min_ram: 32 min_ram: 32