mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2025-12-14 17:13:50 +01:00
Merge pull request #21391 from fabian18/pr/fmt_time
sys/fmt: add {fmt,scn}_time_tm_iso8601
This commit is contained in:
commit
db0f497c61
@ -24,6 +24,7 @@
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "container.h"
|
||||
@ -472,6 +473,55 @@ size_t fmt_to_lower(char *out, const char *str)
|
||||
return len;
|
||||
}
|
||||
|
||||
int fmt_time_tm_iso8601(char out[20], const struct tm *tm, char separator)
|
||||
{
|
||||
assert(out);
|
||||
assert(tm);
|
||||
/* The lowest year allowed in ISO 8601 is 0000 (year zero), which represents 1 BCE */
|
||||
if ((tm->tm_year < -1900 || tm->tm_year > 9999 - 1900) ||
|
||||
(tm->tm_mon < -1 || tm->tm_mon > 99 - 1) ||
|
||||
(tm->tm_mday < 0 || tm->tm_mday > 99) ||
|
||||
(tm->tm_hour < 0 || tm->tm_hour > 99) ||
|
||||
(tm->tm_min < 0 || tm->tm_min > 99) ||
|
||||
(tm->tm_sec < 0 || tm->tm_sec > 99)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
char *pos = out;
|
||||
int len;
|
||||
|
||||
len = fmt_u16_dec(pos, tm->tm_year + 1900);
|
||||
fmt_lpad(pos, len, 4, '0');
|
||||
pos += 4;
|
||||
*pos++ = '-';
|
||||
|
||||
len = fmt_u16_dec(pos, tm->tm_mon + 1);
|
||||
fmt_lpad(pos, len, 2, '0');
|
||||
pos += 2;
|
||||
*pos++ = '-';
|
||||
|
||||
len = fmt_u16_dec(pos, tm->tm_mday);
|
||||
fmt_lpad(pos, len, 2, '0');
|
||||
pos += 2;
|
||||
*pos++ = separator;
|
||||
|
||||
len = fmt_u16_dec(pos, tm->tm_hour);
|
||||
fmt_lpad(pos, len, 2, '0');
|
||||
pos += 2;
|
||||
*pos++ = ':';
|
||||
|
||||
len = fmt_u16_dec(pos, tm->tm_min);
|
||||
fmt_lpad(pos, len, 2, '0');
|
||||
pos += 2;
|
||||
*pos++ = ':';
|
||||
|
||||
len = fmt_u16_dec(pos, tm->tm_sec);
|
||||
fmt_lpad(pos, len, 2, '0');
|
||||
pos += 2;
|
||||
*pos = '\0';
|
||||
|
||||
return pos - out;
|
||||
}
|
||||
|
||||
uint32_t scn_u32_dec(const char *str, size_t n)
|
||||
{
|
||||
uint32_t res = 0;
|
||||
@ -567,6 +617,54 @@ ssize_t scn_buf_hex(void *_dest, size_t dest_len, const char *hex, size_t hex_le
|
||||
return len;
|
||||
}
|
||||
|
||||
int scn_time_tm_iso8601(struct tm *tm, const char *str, char separator)
|
||||
{
|
||||
assert(tm);
|
||||
assert(str);
|
||||
memset(tm, 0, sizeof(*tm));
|
||||
tm->tm_isdst = -1; /* undefined */
|
||||
|
||||
if (!fmt_is_digit(str[0]) || !fmt_is_digit(str[1]) ||
|
||||
!fmt_is_digit(str[2]) || !fmt_is_digit(str[3]) ||
|
||||
str[4] != '-' ||
|
||||
!fmt_is_digit(str[5]) || !fmt_is_digit(str[6]) ||
|
||||
str[7] != '-' ||
|
||||
!fmt_is_digit(str[8]) || !fmt_is_digit(str[9])) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
uint32_t num = scn_u32_dec(str, 4);
|
||||
tm->tm_year = num - 1900;
|
||||
num = scn_u32_dec(str + 5, 2);
|
||||
tm->tm_mon = num - 1;
|
||||
num = scn_u32_dec(str + 8, 2);
|
||||
tm->tm_mday = num;
|
||||
|
||||
if (str[10] == '\0') {
|
||||
/* no time, just date */
|
||||
return 10;
|
||||
}
|
||||
if (str[10] != separator) {
|
||||
return -EBADF;
|
||||
}
|
||||
if (!fmt_is_digit(str[11]) || !fmt_is_digit(str[12]) ||
|
||||
str[13] != ':' ||
|
||||
!fmt_is_digit(str[14]) || !fmt_is_digit(str[15]) ||
|
||||
str[16] != ':' ||
|
||||
!fmt_is_digit(str[17]) || !fmt_is_digit(str[18])) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
num = scn_u32_dec(str + 11, 2);
|
||||
tm->tm_hour = num;
|
||||
num = scn_u32_dec(str + 14, 2);
|
||||
tm->tm_min = num;
|
||||
num = scn_u32_dec(str + 17, 2);
|
||||
tm->tm_sec = num;
|
||||
|
||||
return 19;
|
||||
}
|
||||
|
||||
/* native gets special treatment as native's stdio code is ... special.
|
||||
* And when not building for RIOT, there's no `stdio_write()`.
|
||||
* In those cases, just defer to `printf()`.
|
||||
|
||||
@ -43,6 +43,7 @@
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
@ -404,6 +405,25 @@ size_t fmt_str(char *out, const char *str);
|
||||
*/
|
||||
size_t fmt_to_lower(char *out, const char *str);
|
||||
|
||||
/**
|
||||
* @brief Format a time structure to an ISO 8601 string
|
||||
*
|
||||
* This function does only take care of format validity
|
||||
* and not of date/time validity.
|
||||
*
|
||||
* @param[out] out Pointer to output buffer
|
||||
* @param[in] tm Pointer to time structure
|
||||
* @param[in] separator Date and time separator.
|
||||
* Must be 'T' for ISO 8601 or may be ' '
|
||||
*
|
||||
* @return nr of characters written to (or needed in) @p out
|
||||
*
|
||||
* @retval -EINVAL if @p tm is specifying a number for
|
||||
* year, month, day, hour, minute which would yield
|
||||
* to an invalid date/time format
|
||||
*/
|
||||
int fmt_time_tm_iso8601(char out[20], const struct tm *tm, char separator);
|
||||
|
||||
/**
|
||||
* @brief Convert string of decimal digits to uint32
|
||||
*
|
||||
@ -455,6 +475,28 @@ uint32_t scn_u32_hex(const char *str, size_t n);
|
||||
*/
|
||||
ssize_t scn_buf_hex(void *dest, size_t dest_len, const char *hex, size_t hex_len);
|
||||
|
||||
/**
|
||||
* @brief Convert an ISO 8601 string to time structure
|
||||
*
|
||||
* This function parses a string in the format
|
||||
* YYYY-MM-DD\<separator\>HH:MM:SS or YYYY-MM-DD.
|
||||
*
|
||||
* A terminating '\0' is not required.
|
||||
*
|
||||
* This function does only take care of format validity
|
||||
* and not of date/time validity.
|
||||
*
|
||||
* @param[out] tm Pointer to time structure
|
||||
* @param[in] str Pointer to string to read from
|
||||
* @param[in] separator Date and time separator
|
||||
*
|
||||
* @return Number of characters read from @p str on success (19 or 10)
|
||||
*
|
||||
* @retval -EINVAL if @p str has an invalid format
|
||||
* @retval -EBADF if the expected separator does not match
|
||||
*/
|
||||
int scn_time_tm_iso8601(struct tm *tm, const char *str, char separator);
|
||||
|
||||
/**
|
||||
* @brief Print string to stdout
|
||||
*
|
||||
|
||||
@ -6,6 +6,7 @@ FEATURES_OPTIONAL += periph_rtc_mem
|
||||
|
||||
DISABLE_MODULE += periph_init_rtc
|
||||
|
||||
USEMODULE += fmt
|
||||
USEMODULE += xtimer
|
||||
|
||||
# avoid running Kconfig by default
|
||||
|
||||
@ -25,6 +25,7 @@
|
||||
#include <time.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "fmt.h"
|
||||
#include "mutex.h"
|
||||
#include "periph_conf.h"
|
||||
#include "periph/rtc.h"
|
||||
@ -34,31 +35,20 @@
|
||||
#define PERIOD (2U)
|
||||
#define REPEAT (4U)
|
||||
|
||||
#define TM_YEAR_OFFSET (1900)
|
||||
|
||||
static unsigned cnt = 0;
|
||||
|
||||
static void print_time(const char *label, const struct tm *time)
|
||||
{
|
||||
printf("%s %04d-%02d-%02d %02d:%02d:%02d\n", label,
|
||||
time->tm_year + TM_YEAR_OFFSET,
|
||||
time->tm_mon + 1,
|
||||
time->tm_mday,
|
||||
time->tm_hour,
|
||||
time->tm_min,
|
||||
time->tm_sec);
|
||||
char tm_buf[20];
|
||||
fmt_time_tm_iso8601(tm_buf, time, ' ');
|
||||
printf("%s %s\n", label, tm_buf);
|
||||
}
|
||||
|
||||
static void print_time_ms(const char *label, const struct tm *time, uint16_t ms)
|
||||
{
|
||||
printf("%s %04d-%02d-%02d %02d:%02d:%02d.%03d\n", label,
|
||||
time->tm_year + TM_YEAR_OFFSET,
|
||||
time->tm_mon + 1,
|
||||
time->tm_mday,
|
||||
time->tm_hour,
|
||||
time->tm_min,
|
||||
time->tm_sec,
|
||||
ms);
|
||||
char tm_buf[20];
|
||||
fmt_time_tm_iso8601(tm_buf, time, ' ');
|
||||
printf("%s %s.%03d\n", label, tm_buf, ms);
|
||||
}
|
||||
|
||||
static void inc_secs(struct tm *time, unsigned val)
|
||||
@ -115,7 +105,6 @@ static void _get_rtc_mem(void)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
puts("RTC mem OK");
|
||||
}
|
||||
#else
|
||||
@ -149,8 +138,8 @@ int main(void)
|
||||
}
|
||||
|
||||
time = (struct tm){
|
||||
.tm_year = 2020 - TM_YEAR_OFFSET, /* years are counted from 1900 */
|
||||
.tm_mon = 1, /* 0 = January, 11 = December */
|
||||
.tm_year = 2020 - 1900, /* years are counted from 1900 */
|
||||
.tm_mon = 1, /* 0 = January, 11 = December */
|
||||
.tm_mday = 28,
|
||||
.tm_hour = 23,
|
||||
.tm_min = 59,
|
||||
@ -224,7 +213,7 @@ int main(void)
|
||||
|
||||
/* set alarm */
|
||||
rtc_get_time(&time);
|
||||
inc_secs(&time, PERIOD);
|
||||
inc_secs(&time, PERIOD); /* note that this increments the seconds above 60! */
|
||||
rtc_set_alarm(&time, cb, &rtc_mtx);
|
||||
print_time(" Setting alarm to ", &time);
|
||||
puts("");
|
||||
|
||||
@ -931,6 +931,98 @@ static void test_fmt_lpad(void)
|
||||
TEST_ASSERT_EQUAL_STRING((char*)string, "xxxx3333");
|
||||
}
|
||||
|
||||
static void test_fmt_time_iso8601(void)
|
||||
{
|
||||
char out[20] = { 0 };
|
||||
const char *expected = "2025-04-08T17:40:02";
|
||||
|
||||
struct tm time = {
|
||||
.tm_year = 2025 - 1900,
|
||||
.tm_mon = 4 - 1,
|
||||
.tm_mday = 8,
|
||||
.tm_hour = 17,
|
||||
.tm_min = 40,
|
||||
.tm_sec = 2
|
||||
};
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(19, fmt_time_tm_iso8601(out, &time, 'T'));
|
||||
TEST_ASSERT_EQUAL_STRING(expected, out);
|
||||
expected = "2025-04-08 17:40:02";
|
||||
TEST_ASSERT_EQUAL_INT(19, fmt_time_tm_iso8601(out, &time, ' '));
|
||||
TEST_ASSERT_EQUAL_STRING(expected, out);
|
||||
|
||||
time.tm_year = -1901;
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, fmt_time_tm_iso8601(out, &time, 'T'));
|
||||
time.tm_year = 9999 - 1899;
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, fmt_time_tm_iso8601(out, &time, 'T'));
|
||||
}
|
||||
|
||||
static void test_scn_time_iso8601(void)
|
||||
{
|
||||
struct tm time;
|
||||
|
||||
memset(&time, 0, sizeof(time));
|
||||
TEST_ASSERT_EQUAL_INT(19, scn_time_tm_iso8601(&time, "2025-04-08T17:40:02", 'T'));
|
||||
TEST_ASSERT_EQUAL_INT(2025 - 1900, time.tm_year);
|
||||
TEST_ASSERT_EQUAL_INT(4 - 1, time.tm_mon);
|
||||
TEST_ASSERT_EQUAL_INT(8, time.tm_mday);
|
||||
TEST_ASSERT_EQUAL_INT(17, time.tm_hour);
|
||||
TEST_ASSERT_EQUAL_INT(40, time.tm_min);
|
||||
TEST_ASSERT_EQUAL_INT(2, time.tm_sec);
|
||||
|
||||
memset(&time, 0, sizeof(time));
|
||||
TEST_ASSERT_EQUAL_INT(19, scn_time_tm_iso8601(&time, "2025-04-08 17:40:02", ' '));
|
||||
TEST_ASSERT_EQUAL_INT(2025 - 1900, time.tm_year);
|
||||
TEST_ASSERT_EQUAL_INT(4 - 1, time.tm_mon);
|
||||
TEST_ASSERT_EQUAL_INT(8, time.tm_mday);
|
||||
TEST_ASSERT_EQUAL_INT(17, time.tm_hour);
|
||||
TEST_ASSERT_EQUAL_INT(40, time.tm_min);
|
||||
TEST_ASSERT_EQUAL_INT(2, time.tm_sec);
|
||||
|
||||
memset(&time, 0, sizeof(time));
|
||||
TEST_ASSERT_EQUAL_INT(10, scn_time_tm_iso8601(&time, "2025-04-08", 'T'));
|
||||
TEST_ASSERT_EQUAL_INT(2025 - 1900, time.tm_year);
|
||||
TEST_ASSERT_EQUAL_INT(4 - 1, time.tm_mon);
|
||||
TEST_ASSERT_EQUAL_INT(8, time.tm_mday);
|
||||
|
||||
memset(&time, 0, sizeof(time));
|
||||
TEST_ASSERT_EQUAL_INT(19, scn_time_tm_iso8601(&time, "2025-13-08T17:40:02", 'T'));
|
||||
TEST_ASSERT_EQUAL_INT(2025 - 1900, time.tm_year);
|
||||
TEST_ASSERT_EQUAL_INT(13 - 1, time.tm_mon);
|
||||
TEST_ASSERT_EQUAL_INT(8, time.tm_mday);
|
||||
TEST_ASSERT_EQUAL_INT(17, time.tm_hour);
|
||||
TEST_ASSERT_EQUAL_INT(40, time.tm_min);
|
||||
TEST_ASSERT_EQUAL_INT(2, time.tm_sec);
|
||||
|
||||
memset(&time, 0, sizeof(time));
|
||||
TEST_ASSERT_EQUAL_INT(19, scn_time_tm_iso8601(&time, "2025-04-08T17:60:02", 'T'));
|
||||
TEST_ASSERT_EQUAL_INT(2025 - 1900, time.tm_year);
|
||||
TEST_ASSERT_EQUAL_INT(4 - 1, time.tm_mon);
|
||||
TEST_ASSERT_EQUAL_INT(8, time.tm_mday);
|
||||
TEST_ASSERT_EQUAL_INT(17, time.tm_hour);
|
||||
TEST_ASSERT_EQUAL_INT(60, time.tm_min);
|
||||
TEST_ASSERT_EQUAL_INT(2, time.tm_sec);
|
||||
|
||||
memset(&time, 0, sizeof(time));
|
||||
TEST_ASSERT_EQUAL_INT(19, scn_time_tm_iso8601(&time, "1899-04-08T17:40:02", 'T'));
|
||||
TEST_ASSERT_EQUAL_INT(1899 - 1900, time.tm_year);
|
||||
TEST_ASSERT_EQUAL_INT(4 - 1, time.tm_mon);
|
||||
TEST_ASSERT_EQUAL_INT(8, time.tm_mday);
|
||||
TEST_ASSERT_EQUAL_INT(17, time.tm_hour);
|
||||
TEST_ASSERT_EQUAL_INT(40, time.tm_min);
|
||||
TEST_ASSERT_EQUAL_INT(2, time.tm_sec);
|
||||
|
||||
memset(&time, 0, sizeof(time));
|
||||
TEST_ASSERT_EQUAL_INT(-EBADF, scn_time_tm_iso8601(&time, "2025-04-08T", ' '));
|
||||
TEST_ASSERT_EQUAL_INT(-EBADF, scn_time_tm_iso8601(&time, "2025-04-08 17:40:02", 'T'));
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, scn_time_tm_iso8601(&time, "2025-XX-08T17:40:02", 'T'));
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, scn_time_tm_iso8601(&time, "2025-4-08T17:40:02", 'T'));
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, scn_time_tm_iso8601(&time, "2025-04-8T17:40:02", 'T'));
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, scn_time_tm_iso8601(&time, "2025-04-08T17:40:2", 'T'));
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, scn_time_tm_iso8601(&time, "-2025-04-08T17:40:02", 'T'));
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, scn_time_tm_iso8601(&time, "10000-04-08T17:40:02", 'T'));
|
||||
}
|
||||
|
||||
Test *tests_fmt_tests(void)
|
||||
{
|
||||
EMB_UNIT_TESTFIXTURES(fixtures) {
|
||||
@ -965,6 +1057,8 @@ Test *tests_fmt_tests(void)
|
||||
new_TestFixture(test_scn_u32_hex),
|
||||
new_TestFixture(test_scn_buf_hex),
|
||||
new_TestFixture(test_fmt_lpad),
|
||||
new_TestFixture(test_fmt_time_iso8601),
|
||||
new_TestFixture(test_scn_time_iso8601),
|
||||
};
|
||||
|
||||
EMB_UNIT_TESTCALLER(fmt_tests, NULL, NULL, fixtures);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user