This is an automated email from the ASF dual-hosted git repository.
lupyuen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git
The following commit(s) were added to refs/heads/master by this push:
new 116a3257637 libs/libc/time: Add configuration options for the strftime
116a3257637 is described below
commit 116a325763749f5649e49d63356770d763aae6cb
Author: Jukka Laitinen <[email protected]>
AuthorDate: Tue Jun 2 09:48:36 2026 +0300
libs/libc/time: Add configuration options for the strftime
This adds 3 configuration options for the lib_strftime, which can be
used to save flash memory when all the formatters are not needed by an
embedded application.
There is always a minimal set of formatters supported:
"%a, %b/%h, %d, %H, %m, %M, %S, %Y, %%". To add on top of that one can
specify:
- LIBC_STRFTIME_C_STANDARD_FORMATS : All ISO-C conversion specifiers
- LIBC_STRFTIME_POSIX_FORMATS : Additional posix formats
- LIBC_STRFTIME_NONSTANDARD_FORMATS : Additional GNU nonstandard formats
All of these are enabled by default unless building for
CONFIG_DEFAULT_SMALL.
Disabling these options can save over 3KB of flash on an 32-bit
ARM system, when all the format specifiers are not needed.
Signed-off-by: Jukka Laitinen <[email protected]>
---
libs/libc/time/Kconfig | 35 ++++
libs/libc/time/lib_strftime.c | 419 ++++++++++++++++++++++--------------------
2 files changed, 258 insertions(+), 196 deletions(-)
diff --git a/libs/libc/time/Kconfig b/libs/libc/time/Kconfig
index 20abdfc3bc3..d59119e47eb 100644
--- a/libs/libc/time/Kconfig
+++ b/libs/libc/time/Kconfig
@@ -5,6 +5,41 @@
menu "Time/Time Zone Support"
+config LIBC_STRFTIME_C_STANDARD_FORMATS
+ bool "strftime ISO C conversion specifiers"
+ default !DEFAULT_SMALL
+ ---help---
+ Enable ISO C strftime conversion specifiers that are not part of
+ the small always-enabled subset. Disabling this option
+ removes support for %A, %B, %I, %j, %p, %U, %w, %W, %x, %X, and
+ %y. The always-enabled subset is %a, %b/%h, %d, %H, %m, %M,
+ %S, %Y, and %%.
+
+ Disabling this option makes strftime smaller, but the
implementation
+ no longer supports all conversion specifiers required by ISO C.
+
+config LIBC_STRFTIME_POSIX_FORMATS
+ bool "strftime POSIX conversion specifiers"
+ default !DEFAULT_SMALL
+ depends on LIBC_STRFTIME_C_STANDARD_FORMATS
+ ---help---
+ Enable POSIX strftime conversion specifiers beyond ISO C that
are
+ implemented by NuttX. Disabling this option removes support for
+ %C, %e, %F, %g, %G, %n, %r, %R, %t, %T, %u, %V, and %z.
+ Enabling this option also enables the ISO C conversion
specifiers.
+
+ Note that %h is kept in the always-enabled subset as an alias
for
+ %b because it shares the same implementation. Some POSIX
+ specifiers may be controlled by other options.
+
+config LIBC_STRFTIME_NONSTANDARD_FORMATS
+ bool "strftime non-standard conversion specifiers"
+ default !DEFAULT_SMALL
+ ---help---
+ Enable NuttX/GNU-style strftime conversion specifiers that are
not
+ required by ISO C or POSIX. Disabling this option removes
support
+ for %k, %l, %P, and %s.
+
config LIBC_LOCALTIME
bool "localtime API call support"
default "n"
diff --git a/libs/libc/time/lib_strftime.c b/libs/libc/time/lib_strftime.c
index 5e169d4e3bf..c16a8611d64 100644
--- a/libs/libc/time/lib_strftime.c
+++ b/libs/libc/time/lib_strftime.c
@@ -62,11 +62,13 @@ static const char * const g_abbrev_wdayname[7] =
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};
+#ifdef CONFIG_LIBC_STRFTIME_C_STANDARD_FORMATS
static const char * const g_wdayname[7] =
{
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday",
"Saturday"
};
+#endif
static const char * const g_abbrev_monthname[12] =
{
@@ -74,11 +76,13 @@ static const char * const g_abbrev_monthname[12] =
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
+#ifdef CONFIG_LIBC_STRFTIME_C_STANDARD_FORMATS
static const char * const g_monthname[12] =
{
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
};
+#endif
/****************************************************************************
* Private Functions
@@ -97,10 +101,12 @@ static const char * const g_monthname[12] =
* true if current is leap year, false is not a leap year
*/
+#ifdef CONFIG_LIBC_STRFTIME_POSIX_FORMATS
static bool is_leap(int year)
{
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
}
+#endif
/****************************************************************************
* Name: get_week_num
@@ -115,6 +121,7 @@ static bool is_leap(int year)
* the week number in a year
*/
+#ifdef CONFIG_LIBC_STRFTIME_POSIX_FORMATS
static int get_week_num(FAR const struct tm *time)
{
/* calculate the total week number in a year */
@@ -163,6 +170,7 @@ static int get_week_num(FAR const struct tm *time)
return week;
}
+#endif
/****************************************************************************
* Name: get_week_year
@@ -177,6 +185,7 @@ static int get_week_num(FAR const struct tm *time)
* the year that calculated based on week number
*/
+#ifdef CONFIG_LIBC_STRFTIME_POSIX_FORMATS
static int get_week_year(FAR const struct tm *time)
{
int week_num = get_week_num(time);
@@ -192,6 +201,7 @@ static int get_week_year(FAR const struct tm *time)
return week_year;
}
+#endif
/****************************************************************************
* Public Functions
@@ -275,7 +285,10 @@ size_t strftime(FAR char *s, size_t max, FAR const char
*format,
FAR const char *str;
FAR char *dest = s;
int chleft = max;
+#if defined(CONFIG_LIBC_STRFTIME_C_STANDARD_FORMATS) || \
+ defined(CONFIG_LIBC_STRFTIME_POSIX_FORMATS)
int value;
+#endif
int len;
while (*format && chleft > 0)
@@ -297,6 +310,8 @@ size_t strftime(FAR char *s, size_t max, FAR const char
*format,
process_next:
switch (*format++)
{
+ /* Always-enabled conversion specifiers. */
+
/* %a: A three-letter abbreviation for the day of the week. */
case 'a':
@@ -309,18 +324,6 @@ process_next:
}
break;
- /* %A: The full name for the day of the week. */
-
- case 'A':
- {
- if (tm->tm_wday < 7)
- {
- str = g_wdayname[tm->tm_wday];
- len = snprintf(dest, chleft, "%s", str);
- }
- }
- break;
-
/* %h: Equivalent to %b */
case 'h':
@@ -339,26 +342,6 @@ process_next:
}
break;
- /* %B: The full month name according to the current locale. */
-
- case 'B':
- {
- if (tm->tm_mon < 12)
- {
- str = g_monthname[tm->tm_mon];
- len = snprintf(dest, chleft, "%s", str);
- }
- }
- break;
-
- /* %C: The century number (year/100) as a 2-digit integer. */
-
- case 'C':
- {
- len = snprintf(dest, chleft, "%02d", tm->tm_year / 100);
- }
- break;
-
/* %d: The day of the month as a decimal number
* (range 01 to 31).
*/
@@ -387,54 +370,88 @@ process_next:
goto process_next;
}
- /* %e: Like %d, the day of the month as a decimal number, but
- * a leading zero is replaced by a space.
+ /* %H: The hour as a decimal number using a 24-hour clock
+ * (range 00 to 23).
*/
- case 'e':
+ case 'H':
{
- len = snprintf(dest, chleft, "%2d", tm->tm_mday);
+ len = snprintf(dest, chleft, "%02d", tm->tm_hour);
}
break;
- /* %F: ISO 8601 date format: "%Y-%m-%d" */
+ /* %m: The month as a decimal number (range 01 to 12). */
- case 'F':
- {
- len = snprintf(dest, chleft, "%04d-%02d-%02d",
- tm->tm_year + TM_YEAR_BASE, tm->tm_mon + 1,
- tm->tm_mday);
- }
- break;
+ case 'm':
+ {
+ len = snprintf(dest, chleft, "%02d", tm->tm_mon + 1);
+ }
+ break;
- /* %g: 2-digit year version of %G, (00-99) */
+ /* %M: The minute as a decimal number (range 00 to 59). */
- case 'g':
- {
- value = get_week_year(tm) % 100;
- len = snprintf(dest, chleft, "%02d", value);
- }
- break;
+ case 'M':
+ {
+ len = snprintf(dest, chleft, "%02d", tm->tm_min);
+ }
+ break;
- /* %G: ISO 8601 week based year */
+ /* %S: The second as a decimal number (range 00 to 60).
+ * (The range is up to 60 to allow for occasional leap seconds.)
+ */
- case 'G':
- {
- len = snprintf(dest, chleft, "%04d", get_week_year(tm));
- }
- break;
+ case 'S':
+ {
+ len = snprintf(dest, chleft, "%02d", tm->tm_sec);
+ }
+ break;
- /* %H: The hour as a decimal number using a 24-hour clock
- * (range 00 to 23).
- */
+ /* %Y: The year as a decimal number including the century. */
- case 'H':
+ case 'Y':
{
- len = snprintf(dest, chleft, "%02d", tm->tm_hour);
+ len = snprintf(dest, chleft, "%04d",
+ tm->tm_year + TM_YEAR_BASE);
+ }
+ break;
+
+ /* %%: A literal '%' character. */
+
+ case '%':
+ {
+ *dest = '%';
+ len = 1;
+ }
+ break;
+
+#ifdef CONFIG_LIBC_STRFTIME_C_STANDARD_FORMATS
+ /* ISO C conversion specifiers. */
+
+ /* %A: The full name for the day of the week. */
+
+ case 'A':
+ {
+ if (tm->tm_wday < 7)
+ {
+ str = g_wdayname[tm->tm_wday];
+ len = snprintf(dest, chleft, "%s", str);
+ }
+ }
+ break;
+
+ /* %B: The full month name according to the current locale. */
+
+ case 'B':
+ {
+ if (tm->tm_mon < 12)
+ {
+ str = g_monthname[tm->tm_mon];
+ len = snprintf(dest, chleft, "%s", str);
+ }
}
break;
- /* %I: The hour as a decimal number using a 12-hour clock
+ /* %I: The hour as a decimal number using a 12-hour clock
* (range 01 to 12).
*/
@@ -460,85 +477,142 @@ process_next:
}
break;
- /* %k: The hour (24-hour clock) as a decimal number
- * (range 0 to 23);
- * single digits are preceded by a blank.
+ /* %p: Either "AM" or "PM" according to the given time value. */
+
+ case 'p':
+ {
+ if (tm->tm_hour >= 12)
+ {
+ str = "PM";
+ }
+ else
+ {
+ str = "AM";
+ }
+
+ len = snprintf(dest, chleft, "%s", str);
+ }
+ break;
+
+ /* %U: Week number of the current year as a decimal number,
+ * (00-53). Starting with the first Sunday as the first day
+ * of week 01.
*/
- case 'k':
+ case 'U':
{
- len = snprintf(dest, chleft, "%2d", tm->tm_hour);
+ value = (tm->tm_yday + DAYSPERWEEK - tm->tm_wday)
+ / DAYSPERWEEK;
+ len = snprintf(dest, chleft, "%02d", value);
}
break;
- /* %l: The hour (12-hour clock) as a decimal number
- * (range 1 to 12);
- * single digits are preceded by a blank.
+ /* %w: The weekday as a decimal number (range 0 to 6). */
+
+ case 'w':
+ {
+ len = snprintf(dest, chleft, "%d", tm->tm_wday);
+ }
+ break;
+
+ /* %W: Week number of the current year as a decimal number,
+ * (00-53). Starting with the first Monday as the first day
+ * of week 01.
*/
- case 'l':
+ case 'W':
{
- len = snprintf(dest, chleft, "%2d", (tm->tm_hour % 12) != 0 ?
- (tm->tm_hour % 12) : 12);
+ value = (tm->tm_yday + DAYSPERWEEK -
+ (tm->tm_wday + 6) % DAYSPERWEEK)
+ / DAYSPERWEEK;
+ len = snprintf(dest, chleft, "%02d", value);
}
break;
- /* %m: The month as a decimal number (range 01 to 12). */
+ /* %x Locale date without time */
- case 'm':
+ case 'x':
{
- len = snprintf(dest, chleft, "%02d", tm->tm_mon + 1);
+ len = snprintf(dest, chleft, "%02d/%02d/%04d",
+ tm->tm_mon, tm->tm_mday,
+ tm->tm_year + TM_YEAR_BASE);
}
break;
- /* %M: The minute as a decimal number (range 00 to 59). */
+ /* %X: Locale time without date */
- case 'M':
+ case 'X':
{
- len = snprintf(dest, chleft, "%02d", tm->tm_min);
+ len = snprintf(dest, chleft, "%02d:%02d:%02d",
+ tm->tm_hour, tm->tm_min, tm->tm_sec);
}
break;
- /* %n: A newline character. */
+ /* %y: The year as a decimal number without a century
+ * (range 00 to 99).
+ */
- case 'n':
+ case 'y':
{
- *dest = '\n';
- len = 1;
+ len = snprintf(dest, chleft, "%02d", tm->tm_year % 100);
}
break;
+#endif
- /* %p: Either "AM" or "PM" according to the given time value. */
+#ifdef CONFIG_LIBC_STRFTIME_POSIX_FORMATS
+ /* POSIX conversion specifiers beyond ISO C. */
- case 'p':
+ /* %C: The century number (year/100) as a 2-digit integer. */
+
+ case 'C':
{
- if (tm->tm_hour >= 12)
- {
- str = "PM";
- }
- else
- {
- str = "AM";
- }
+ len = snprintf(dest, chleft, "%02d", tm->tm_year / 100);
+ }
+ break;
- len = snprintf(dest, chleft, "%s", str);
+ /* %e: Like %d, the day of the month as a decimal number, but
+ * a leading zero is replaced by a space.
+ */
+
+ case 'e':
+ {
+ len = snprintf(dest, chleft, "%2d", tm->tm_mday);
}
break;
- /* %P: Like %p but in lowercase: "am" or "pm" */
+ /* %F: ISO 8601 date format: "%Y-%m-%d" */
- case 'P':
+ case 'F':
{
- if (tm->tm_hour >= 12)
- {
- str = "pm";
- }
- else
- {
- str = "am";
- }
+ len = snprintf(dest, chleft, "%04d-%02d-%02d",
+ tm->tm_year + TM_YEAR_BASE, tm->tm_mon + 1,
+ tm->tm_mday);
+ }
+ break;
- len = snprintf(dest, chleft, "%s", str);
+ /* %g: 2-digit year version of %G, (00-99) */
+
+ case 'g':
+ {
+ value = get_week_year(tm) % 100;
+ len = snprintf(dest, chleft, "%02d", value);
+ }
+ break;
+
+ /* %G: ISO 8601 week based year */
+
+ case 'G':
+ {
+ len = snprintf(dest, chleft, "%04d", get_week_year(tm));
+ }
+ break;
+
+ /* %n: A newline character. */
+
+ case 'n':
+ {
+ *dest = '\n';
+ len = 1;
}
break;
@@ -564,7 +638,7 @@ process_next:
}
break;
- /* %R: Shortcut for %H:%M. */
+ /* %R: Shortcut for %H:%M. */
case 'R':
{
@@ -573,28 +647,6 @@ process_next:
}
break;
- /* %s: The number of seconds since the Epoch, that is,
- * since 1970-01-01 00:00:00 UTC.
- * Hmmm... mktime argume is not 'const'.
- */
-
- case 's':
- {
- struct tm tmp = *tm;
- len = snprintf(dest, chleft, "%ju", (uintmax_t)mktime(&tmp));
- }
- break;
-
- /* %S: The second as a decimal number (range 00 to 60).
- * (The range is up to 60 to allow for occasional leap seconds.)
- */
-
- case 'S':
- {
- len = snprintf(dest, chleft, "%02d", tm->tm_sec);
- }
- break;
-
/* %t: A tab character. */
case 't':
@@ -624,19 +676,6 @@ process_next:
}
break;
- /* %U: week number of the current year as a decimal number,
- * (00-53). Starting with the first Sunday as the first day
- * of week 01.
- */
-
- case 'U':
- {
- value = (tm->tm_yday + DAYSPERWEEK - tm->tm_wday)
- / DAYSPERWEEK;
- len = snprintf(dest, chleft, "%02d", value);
- }
- break;
-
/* %V: ISO 8601 week number */
case 'V':
@@ -646,87 +685,75 @@ process_next:
}
break;
- /* %w: The weekday as a decimal number (range 0 to 6). */
-
- case 'w':
- {
- len = snprintf(dest, chleft, "%d", tm->tm_wday);
- }
- break;
-
- /* %W: Week number of the current year as a decimal number,
- * (00-53). Starting with the first Monday as the first day
- * of week 01.
+ /* %z: Numeric timezone as hour and minute offset from UTC
+ * "+hhmm" or "-hhmm"
*/
- case 'W':
+ case 'z':
{
- value = (tm->tm_yday + DAYSPERWEEK -
- (tm->tm_wday + 6) % DAYSPERWEEK)
- / DAYSPERWEEK;
- len = snprintf(dest, chleft, "%02d", value);
+ int hour = tm->tm_gmtoff / 3600;
+ int min = tm->tm_gmtoff % 3600 / 60;
+ int utc_val = hour * 100 + min;
+ len = snprintf(dest, chleft, "+%04d", utc_val);
}
break;
+#endif
- /* %x Locale date without time */
+#ifdef CONFIG_LIBC_STRFTIME_NONSTANDARD_FORMATS
+ /* Non-standard conversion specifiers. */
- case 'x':
- {
- len = snprintf(dest, chleft, "%02d/%02d/%04d",
- tm->tm_mon, tm->tm_mday,
- tm->tm_year + TM_YEAR_BASE);
- }
- break;
-
- /* %X: Locale time without date */
+ /* %k: The hour (24-hour clock) as a decimal number
+ * (range 0 to 23);
+ * single digits are preceded by a blank.
+ */
- case 'X':
+ case 'k':
{
- len = snprintf(dest, chleft, "%02d:%02d:%02d",
- tm->tm_hour, tm->tm_min, tm->tm_sec);
+ len = snprintf(dest, chleft, "%2d", tm->tm_hour);
}
break;
- /* %y: The year as a decimal number without a century
- * (range 00 to 99).
+ /* %l: The hour (12-hour clock) as a decimal number
+ * (range 1 to 12);
+ * single digits are preceded by a blank.
*/
- case 'y':
+ case 'l':
{
- len = snprintf(dest, chleft, "%02d", tm->tm_year % 100);
+ len = snprintf(dest, chleft, "%2d", (tm->tm_hour % 12) != 0 ?
+ (tm->tm_hour % 12) : 12);
}
break;
- /* %Y: The year as a decimal number including the century. */
+ /* %P: Like %p but in lowercase: "am" or "pm" */
- case 'Y':
+ case 'P':
{
- len = snprintf(dest, chleft, "%04d",
- tm->tm_year + TM_YEAR_BASE);
+ if (tm->tm_hour >= 12)
+ {
+ str = "pm";
+ }
+ else
+ {
+ str = "am";
+ }
+
+ len = snprintf(dest, chleft, "%s", str);
}
break;
- /* %z: Numeric timezone as hour and minute offset from UTC
- * "+hhmm" or "-hhmm"
- */
-
- case 'z':
- {
- int hour = tm->tm_gmtoff / 3600;
- int min = tm->tm_gmtoff % 3600 / 60;
- int utc_val = hour * 100 + min;
- len = snprintf(dest, chleft, "+%04d", utc_val);
- }
- break;
-
- /* %%: A literal '%' character. */
+ /* %s: The number of seconds since the Epoch, that is,
+ * since 1970-01-01 00:00:00 UTC.
+ * Hmmm... mktime argume is not 'const'.
+ */
- case '%':
+ case 's':
{
- *dest = '%';
- len = 1;
+ struct tm tmp = *tm;
+ len = snprintf(dest, chleft, "%ju", (uintmax_t)mktime(&tmp));
}
break;
+#endif
}
/* Update counts and pointers */