aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorPedro F. Giffuni <pfg@FreeBSD.org>2014-09-25 18:52:17 +0000
committerPedro F. Giffuni <pfg@FreeBSD.org>2014-09-25 18:52:17 +0000
commitce1d331eeee12278526bac231aff3304944bd4e8 (patch)
treeb9a72fc1d6107ed4686b9089d24bd25dd3e160ca /lib
parent9de2fdafa889404652605d1b78c6ca6515dc1e0e (diff)
downloadsrc-ce1d331eeee12278526bac231aff3304944bd4e8.tar.gz
src-ce1d331eeee12278526bac231aff3304944bd4e8.zip
Add strptime(3) support for %U and %W
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 with important fixes by Andrey Chernov. Additional support for handling tm_wday/tm_yday was written by David Carlier. PR: 137307 MFC after: 1 month
Notes
Notes: svn path=/head/; revision=272122
Diffstat (limited to 'lib')
-rw-r--r--lib/libc/stdtime/strptime.c131
1 files changed, 126 insertions, 5 deletions
diff --git a/lib/libc/stdtime/strptime.c b/lib/libc/stdtime/strptime.c
index 2333ab47e76e..b22aa9a1adbe 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"
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/Calculating_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,17 @@ _strptime(const char *buf, const char *fmt, struct tm *tm, int *GMTp,
{
char c;
const char *ptr;
+ int day_offset = -1, wday_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) {
@@ -119,7 +149,9 @@ label:
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':
@@ -197,6 +229,8 @@ label:
return (NULL);
tm->tm_yday = i - 1;
+ flags |= FLAG_YDAY;
+
break;
case 'M':
@@ -303,7 +337,32 @@ label:
return (NULL);
tm->tm_wday = i;
+ if (day_offset >= 0 && (i - day_offset) != 0) {
+ tm->tm_yday += i - day_offset;
+ 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;
+ tm->tm_mday = tm->tm_yday -
+ start_of_month[isleap(tm->tm_year +
+ TM_YEAR_BASE)]
+ [i - 1] + 1;
+ }
buf += len;
+ flags |= FLAG_YEAR | FLAG_MONTH | FLAG_YDAY |
+ FLAG_MDAY | FLAG_WDAY;
+
break;
case 'U':
@@ -313,6 +372,8 @@ label:
* information present in the tm structure at this
* point to calculate a real value, so just check the
* range for now.
+ * We expect that the year has already been
+ * parsed.
*/
if (!isdigit_l((unsigned char)*buf, locale))
return (NULL);
@@ -327,6 +388,45 @@ label:
if (i > 53)
return (NULL);
+ /* Week numbers are l-origin. So that we can always
+ * return the date of a Sunday (or Monday), treat week
+ * 0 as week 1.
+ */
+
+ if (i == 0)
+ i = 1;
+
+ if (c == 'U')
+ day_offset = TM_SUNDAY;
+ else
+ day_offset = TM_MONDAY;
+
+ /* Set the date to the first Sunday (or Monday)
+ * of the specified week of the year.
+ */
+
+ tm->tm_yday = (7 - first_wday_of(tm->tm_year +
+ TM_YEAR_BASE) + day_offset) % 7 + (i - 1) * 7;
+ 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;
+ tm->tm_mday = tm->tm_yday -
+ start_of_month[isleap(tm->tm_year + TM_YEAR_BASE)]
+ [i - 1] + 1;
+ tm->tm_wday = day_offset;
+ flags |= FLAG_YEAR | FLAG_MONTH | FLAG_YDAY |
+ FLAG_MDAY | FLAG_WDAY;
+
break;
case 'w':
@@ -338,6 +438,7 @@ label:
return (NULL);
tm->tm_wday = i;
+ flags != FLAG_WDAY;
break;
@@ -374,6 +475,7 @@ label:
return (NULL);
tm->tm_mday = i;
+ flags |= FLAG_MDAY;
break;
@@ -413,6 +515,8 @@ label:
tm->tm_mon = i;
buf += len;
+ flags |= FLAG_MONTH;
+
break;
case 'm':
@@ -430,6 +534,7 @@ label:
return (NULL);
tm->tm_mon = i - 1;
+ flags |= FLAG_MONTH;
break;
@@ -471,13 +576,14 @@ label:
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 +649,25 @@ label:
break;
}
}
+
+ if (flags & (FLAG_YEAR | FLAG_MONTH)) {
+ if (!tm->tm_yday && (flags & FLAG_MDAY))
+ tm->tm_yday = start_of_month[isleap(tm->tm_year
+ + TM_YEAR_BASE)][tm->tm_mon] + (tm->tm_mday - 1);
+ if (!tm->tm_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;
+ }
+ }
+
return ((char *)buf);
}
-
char *
strptime_l(const char * __restrict buf, const char * __restrict fmt,
struct tm * __restrict tm, locale_t loc)