Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 25 Sep 2014 18:52:18 +0000 (UTC)
From:      "Pedro F. Giffuni" <pfg@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r272122 - head/lib/libc/stdtime
Message-ID:  <201409251852.s8PIqIRD033822@svn.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: pfg
Date: Thu Sep 25 18:52:17 2014
New Revision: 272122
URL: http://svnweb.freebsd.org/changeset/base/272122

Log:
  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

Modified:
  head/lib/libc/stdtime/strptime.c

Modified: head/lib/libc/stdtime/strptime.c
==============================================================================
--- head/lib/libc/stdtime/strptime.c	Thu Sep 25 18:43:52 2014	(r272121)
+++ head/lib/libc/stdtime/strptime.c	Thu Sep 25 18:52:17 2014	(r272122)
@@ -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 *f
 {
 	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)



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201409251852.s8PIqIRD033822>