From c40729136d76ac63297e28c8515d679451219e7e Mon Sep 17 00:00:00 2001 From: pfg Date: Sun, 28 Sep 2014 21:20:20 +0000 Subject: [PATCH] Add strptime(3) support for %U and %W (take 2) Add support for the missing POSIX-2001 %U and %W features: the existing FreeBSD strptime code recognizes both directives and validates that the week number lies in the permitted range, but then simply discards the value. Initial support for the feature was written by Paul Green. David Carlier added the initial handling of tm_wday/tm_yday. Major credit goes to Andrey Chernov for detecting much of the brokenness, and rewriting/cleaning most of the code, making it much more robust. Tested independently with the strptime test from the GNU C library. PR: 137307 MFC after: 1 month Relnotes: yes --- lib/libc/stdtime/strptime.c | 126 ++++++++++++++++++++++++++++++++++-- 1 file changed, 119 insertions(+), 7 deletions(-) diff --git a/lib/libc/stdtime/strptime.c b/lib/libc/stdtime/strptime.c index 2333ab47e76e..ab0801729342 100644 --- a/lib/libc/stdtime/strptime.c +++ b/lib/libc/stdtime/strptime.c @@ -55,10 +55,32 @@ __FBSDID("$FreeBSD$"); #include "un-namespace.h" #include "libc_private.h" #include "timelocal.h" - +#include "tzfile.h" +#include static char * _strptime(const char *, const char *, struct tm *, int *, locale_t); -#define asizeof(a) (sizeof (a) / sizeof ((a)[0])) +#define asizeof(a) (sizeof(a) / sizeof((a)[0])) + +#define FLAG_NONE (1 << 0) +#define FLAG_YEAR (1 << 1) +#define FLAG_MONTH (1 << 2) +#define FLAG_YDAY (1 << 3) +#define FLAG_MDAY (1 << 4) +#define FLAG_WDAY (1 << 5) + +/* + * Calculate the week day of the first day of a year. Valid for + * the Gregorian calendar, which began Sept 14, 1752 in the UK + * and its colonies. Ref: + * http://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week + */ + +static int +first_wday_of(int year) +{ + return (((2 * (3 - (year / 100) % 4)) + (year % 100) + + ((year % 100) / 4) + (isleap(year) ? 6 : 0) + 1) % 7); +} static char * _strptime(const char *buf, const char *fmt, struct tm *tm, int *GMTp, @@ -66,9 +88,18 @@ _strptime(const char *buf, const char *fmt, struct tm *tm, int *GMTp, { char c; const char *ptr; + int day_offset = -1, wday_offset; + int week_offset; int i, len; + int flags; int Ealternative, Oalternative; - struct lc_time_T *tptr = __get_current_time_locale(locale); + const struct lc_time_T *tptr = __get_current_time_locale(locale); + static int start_of_month[2][13] = { + {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}, + {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366} + }; + + flags = FLAG_NONE; ptr = fmt; while (*ptr != 0) { @@ -102,6 +133,7 @@ _strptime(const char *buf, const char *fmt, struct tm *tm, int *GMTp, buf = _strptime(buf, tptr->date_fmt, tm, GMTp, locale); if (buf == NULL) return (NULL); + flags |= FLAG_WDAY | FLAG_MONTH | FLAG_MDAY | FLAG_YEAR; break; case 'C': @@ -119,19 +151,23 @@ _strptime(const char *buf, const char *fmt, struct tm *tm, int *GMTp, if (i < 19) return (NULL); - tm->tm_year = i * 100 - 1900; + tm->tm_year = i * 100 - TM_YEAR_BASE; + flags |= FLAG_YEAR; + break; case 'c': buf = _strptime(buf, tptr->c_fmt, tm, GMTp, locale); if (buf == NULL) return (NULL); + flags |= FLAG_WDAY | FLAG_MONTH | FLAG_MDAY | FLAG_YEAR; break; case 'D': buf = _strptime(buf, "%m/%d/%y", tm, GMTp, locale); if (buf == NULL) return (NULL); + flags |= FLAG_MONTH | FLAG_MDAY | FLAG_YEAR; break; case 'E': @@ -150,6 +186,7 @@ _strptime(const char *buf, const char *fmt, struct tm *tm, int *GMTp, buf = _strptime(buf, "%Y-%m-%d", tm, GMTp, locale); if (buf == NULL) return (NULL); + flags |= FLAG_MONTH | FLAG_MDAY | FLAG_YEAR; break; case 'R': @@ -180,6 +217,7 @@ _strptime(const char *buf, const char *fmt, struct tm *tm, int *GMTp, buf = _strptime(buf, tptr->x_fmt, tm, GMTp, locale); if (buf == NULL) return (NULL); + flags |= FLAG_MONTH | FLAG_MDAY | FLAG_YEAR; break; case 'j': @@ -197,6 +235,8 @@ _strptime(const char *buf, const char *fmt, struct tm *tm, int *GMTp, return (NULL); tm->tm_yday = i - 1; + flags |= FLAG_YDAY; + break; case 'M': @@ -303,7 +343,7 @@ _strptime(const char *buf, const char *fmt, struct tm *tm, int *GMTp, return (NULL); tm->tm_wday = i; - buf += len; + flags |= FLAG_WDAY; break; case 'U': @@ -327,6 +367,14 @@ _strptime(const char *buf, const char *fmt, struct tm *tm, int *GMTp, if (i > 53) return (NULL); + if (c == 'U') + day_offset = TM_SUNDAY; + else + day_offset = TM_MONDAY; + + + week_offset = i; + break; case 'w': @@ -338,6 +386,7 @@ _strptime(const char *buf, const char *fmt, struct tm *tm, int *GMTp, return (NULL); tm->tm_wday = i; + flags |= FLAG_WDAY; break; @@ -374,6 +423,7 @@ _strptime(const char *buf, const char *fmt, struct tm *tm, int *GMTp, return (NULL); tm->tm_mday = i; + flags |= FLAG_MDAY; break; @@ -413,6 +463,8 @@ _strptime(const char *buf, const char *fmt, struct tm *tm, int *GMTp, tm->tm_mon = i; buf += len; + flags |= FLAG_MONTH; + break; case 'm': @@ -430,6 +482,7 @@ _strptime(const char *buf, const char *fmt, struct tm *tm, int *GMTp, return (NULL); tm->tm_mon = i - 1; + flags |= FLAG_MONTH; break; @@ -471,13 +524,14 @@ _strptime(const char *buf, const char *fmt, struct tm *tm, int *GMTp, len--; } if (c == 'Y') - i -= 1900; + i -= TM_YEAR_BASE; if (c == 'y' && i < 69) i += 100; if (i < 0) return (NULL); tm->tm_year = i; + flags |= FLAG_YEAR; break; @@ -543,10 +597,67 @@ _strptime(const char *buf, const char *fmt, struct tm *tm, int *GMTp, break; } } + + if (!(flags & FLAG_YDAY) && (flags & FLAG_YEAR)) { + if ((flags & (FLAG_MONTH | FLAG_MDAY)) == + (FLAG_MONTH | FLAG_MDAY)) { + tm->tm_yday = start_of_month[isleap(tm->tm_year + + TM_YEAR_BASE)][tm->tm_mon] + (tm->tm_mday - 1); + flags |= FLAG_YDAY; + } else if (day_offset != -1) { + /* Set the date to the first Sunday (or Monday) + * of the specified week of the year. + */ + if (!(flags & FLAG_WDAY)) { + tm->tm_wday = day_offset; + flags |= FLAG_WDAY; + } + tm->tm_yday = (7 - + first_wday_of(tm->tm_year + TM_YEAR_BASE) + + day_offset) % 7 + (week_offset - 1) * 7 + + tm->tm_wday - day_offset; + flags |= FLAG_YDAY; + } + } + + if ((flags & (FLAG_YEAR | FLAG_YDAY)) == (FLAG_YEAR | FLAG_YDAY)) { + if (!(flags & FLAG_MONTH)) { + i = 0; + while (tm->tm_yday >= + start_of_month[isleap(tm->tm_year + + TM_YEAR_BASE)][i]) + i++; + if (i > 12) { + i = 1; + tm->tm_yday -= + start_of_month[isleap(tm->tm_year + + TM_YEAR_BASE)][12]; + tm->tm_year++; + } + tm->tm_mon = i - 1; + flags |= FLAG_MONTH; + } + if (!(flags & FLAG_MDAY)) { + tm->tm_mday = tm->tm_yday - + start_of_month[isleap(tm->tm_year + TM_YEAR_BASE)] + [tm->tm_mon] + 1; + flags |= FLAG_MDAY; + } + if (!(flags & FLAG_WDAY)) { + i = 0; + wday_offset = first_wday_of(tm->tm_year); + while (i++ <= tm->tm_yday) { + if (wday_offset++ >= 6) + wday_offset = 0; + } + tm->tm_wday = wday_offset; + flags |= FLAG_WDAY; + } + } + return ((char *)buf); } - char * strptime_l(const char * __restrict buf, const char * __restrict fmt, struct tm * __restrict tm, locale_t loc) @@ -564,6 +675,7 @@ strptime_l(const char * __restrict buf, const char * __restrict fmt, return (ret); } + char * strptime(const char * __restrict buf, const char * __restrict fmt, struct tm * __restrict tm)