From 083c11ef1a671147fe032b358d8724f63e0f7a23 Mon Sep 17 00:00:00 2001 From: guoshichao Date: Tue, 13 Jun 2023 13:54:18 +0800 Subject: [PATCH] libs/libc/time/strftime: complete the strftime implementation 1. make the strftime implementation can pass the ltp/open_posix_testsuite/strftime/1-1 testcase 2. the modification are referred to https://pubs.opengroup.org/onlinepubs/9699919799/functions/strftime.html Signed-off-by: guoshichao --- libs/libc/time/lib_strftime.c | 253 ++++++++++++++++++++++++++++++++++ 1 file changed, 253 insertions(+) diff --git a/libs/libc/time/lib_strftime.c b/libs/libc/time/lib_strftime.c index 94b0219ecdb..d0417653ab8 100644 --- a/libs/libc/time/lib_strftime.c +++ b/libs/libc/time/lib_strftime.c @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -81,6 +82,115 @@ static const char * const g_monthname[12] = * Private Functions ****************************************************************************/ +/**************************************************************************** + * Name: is_leap + * + * Description: + * determine if the given year is a leap year or not + * + * Input Parameters: + * year - a year value + * + * Returnd value: + * true if current is leap year, false is not a leap year + */ + +static bool is_leap(int year) +{ + return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); +} + +/**************************************************************************** + * Name: get_week_num + * + * Description: + * get the week number in a year based on iso8601 standard + * + * Input Parameters: + * time - the specified time + * + * Returnd value: + * the week numer in a year + */ + +static int get_week_num(FAR const struct tm *time) +{ + /* calculate the total week number in a year */ + + int week = (time->tm_yday + DAYSPERWEEK - + (time->tm_wday + 6) % DAYSPERWEEK) / DAYSPERWEEK; + + /* if xxxx-1-1 is just passed 1-3 days after Monday + * then the previous week will calculated into this year + */ + + if ((time->tm_wday + 371 - time->tm_yday - 2) % DAYSPERWEEK <= 2) + { + week++; + } + + if (week == 0) + { + week = 52; + + /* if xxxx-12-31 is Thursday or Friday, and the previous year is + * leap year, then the previous year has 53 weeks + */ + + int dec31 = (time->tm_wday + DAYSPERWEEK - time->tm_yday - 1) + % DAYSPERWEEK; + if (dec31 == TM_THURSDAY || + (dec31 == TM_FRIDAY && is_leap(time->tm_year % 400 - 1))) + { + week++; + } + } + else if (week == 53) + { + /* If xxxx-1.1 is not a Thursday, and not a Wednesday of a leap year, + * then this year has only 52 weeks + */ + + int jan1 = (time->tm_wday + 371 - time->tm_yday) % DAYSPERWEEK; + if (jan1 != TM_THURSDAY && + (jan1 != TM_WEDNESDAY || !is_leap(time->tm_year))) + { + week = 1; + } + } + + return week; +} + +/**************************************************************************** + * Name: get_week_year + * + * Description: + * get the week year based on iso8601 standard + * + * Input Parameters: + * time - the specified time + * + * Returnd value: + * the year that calculated based on week number + */ + +static int get_week_year(FAR const struct tm *time) +{ + int week_num = get_week_num(time); + int week_year = time->tm_year + TM_YEAR_BASE; + if (time->tm_yday < 3 && week_num != 1) + { + week_year--; + } + else if (time->tm_yday > 360 && week_num == 1) + { + week_year++; + } + + return week_year; +} + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -106,6 +216,10 @@ static const char * const g_monthname[12] = * %d The day of the month as a decimal number (range 01 to 31). * %e Like %d, the day of the month as a decimal number, but a leading * zero is replaced by a space. + * %F The full date format but with no time fields + * %g The last 2 digits of the week-based year as a decimal number + * [00,99] + * %G The full version of %g, display the full year value * %h Equivalent to %b. (SU) * %H The hour as a decimal number using a 24-hour clock * (range 00 to 23). @@ -124,14 +238,25 @@ static const char * const g_monthname[12] = * as "PM" and midnight as "AM". * %P Like %p but in lowercase: "am" or "pm" or a corresponding string * for the current locale. (GNU) + * %r The time in a.m. and p.m. notation + * %R The time in 24-hour notation ( %H : %M ). * %s The number of seconds since the Epoch, that is, since 1970-01-01 * 00:00:00 UTC. (TZ) * %S The second as a decimal number (range 00 to 60). (The range is * up to 60 to allow for occasional leap seconds.) * %t A tab character. (SU) + * %u The weekday as a decimal number [1,7], with 1 representing + * Monday. + * %U The week number of the year as a decimal number [00,53]. + * %V The week number of the year * %w The weekday as a decimal number (range 0 to 6). + * %W The week number of the year as a decimal number [00,53]. + * %x The locale's appropriate date representation, but without time. + * %X The locale's appropriate time representation, but without date. * %y The year as a decimal number without a century (range 00 to 99). * %Y The year as a decimal number including the century. + * %z The timezone name or abbreviation, or by no bytes if no timezone + * information exists. * %% A literal '%' character. * * Returned Value: @@ -251,6 +376,33 @@ size_t strftime(FAR char *s, size_t max, FAR const char *format, } break; + /* %F: ISO 8601 date format: "%Y-%m-%d" */ + + case 'F': + { + len = snprintf(dest, chleft, "%04d-%02d-%02d", + tm->tm_year + TM_YEAR_BASE, tm->tm_mon, + tm->tm_mday); + } + break; + + /* %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; + /* %H: The hour as a decimal number using a 24-hour clock * (range 00 to 23). */ @@ -369,6 +521,28 @@ size_t strftime(FAR char *s, size_t max, FAR const char *format, } break; + /* %r: 12-hour clock time */ + + case 'r': + { + if (tm->tm_hour >= 12) + { + str = "pm"; + } + else + { + str = "am"; + } + + value = tm->tm_hour == 12 ? + tm->tm_hour == 12 : + tm->tm_hour % (HOURSPERDAY / 2); + + len = snprintf(dest, chleft, "%02d:%02d:%02d %s", + value, tm->tm_min, tm->tm_sec, str); + } + break; + /* %R: Shortcut for %H:%M. */ case 'R': @@ -418,6 +592,39 @@ size_t strftime(FAR char *s, size_t max, FAR const char *format, } break; + /* %u: The day of the week as a decimal, (1-7). Monday being 1, + * Sunday being 0. + */ + + case 'u': + { + value = tm->tm_wday == 0 ? 7 : tm->tm_wday; + len = snprintf(dest, chleft, "%d", value); + } + 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': + { + value = get_week_num(tm); + len = snprintf(dest, chleft, "%02d", value); + } + break; + /* %w: The weekday as a decimal number (range 0 to 6). */ case 'w': @@ -426,6 +633,39 @@ size_t strftime(FAR char *s, size_t max, FAR const char *format, } 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 'W': + { + value = (tm->tm_yday + DAYSPERWEEK - + (tm->tm_wday + 6) % DAYSPERWEEK) + / DAYSPERWEEK; + len = snprintf(dest, chleft, "%02d", value); + } + break; + + /* %x Locale date without time */ + + 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 */ + + case 'X': + { + len = snprintf(dest, chleft, "%02d:%02d:%02d", + tm->tm_hour, tm->tm_min, tm->tm_sec); + } + break; + /* %y: The year as a decimal number without a century * (range 00 to 99). */ @@ -445,6 +685,19 @@ size_t strftime(FAR char *s, size_t max, FAR const char *format, } 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. */ case '%':