Module Name:    src
Committed By:   christos
Date:           Thu Oct 29 17:54:49 UTC 2015

Modified Files:
        src/lib/libc/time: strptime.3 strptime.c

Log Message:
PR/50380: Balazs Scheidler: strptime() returns incorrect values in tm_gmtoff
- Always offset in seconds.
- Handle arbitrary timezones.


To generate a diff of this commit:
cvs rdiff -u -r1.31 -r1.32 src/lib/libc/time/strptime.3
cvs rdiff -u -r1.49 -r1.50 src/lib/libc/time/strptime.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/lib/libc/time/strptime.3
diff -u src/lib/libc/time/strptime.3:1.31 src/lib/libc/time/strptime.3:1.32
--- src/lib/libc/time/strptime.3:1.31	Mon Apr  6 10:38:22 2015
+++ src/lib/libc/time/strptime.3	Thu Oct 29 13:54:49 2015
@@ -1,4 +1,4 @@
-.\"	$NetBSD: strptime.3,v 1.31 2015/04/06 14:38:22 ginsbach Exp $
+.\"	$NetBSD: strptime.3,v 1.32 2015/10/29 17:54:49 christos Exp $
 .\"
 .\" Copyright (c) 1997, 1998, 2008 The NetBSD Foundation, Inc.
 .\" All rights reserved.
@@ -223,33 +223,56 @@ the year, including the century (i.e., 1
 .It Cm \&%z
 an ISO 8601 or RFC-2822 time zone specification.
 This is one of the following:
-the offset from
-Coordinated Universal Time
+.Bl -dash -offset indent -compact
+.It
+The offset from Coordinated Universal Time
 .Pq Ql UTC
 specified as:
-.Dq [+-]hhmm ,
-.Dq [+-]hh:mm ,
-or
-.Dq [+-]hh ;
+.Bl -bullet -offset indent -compact
+.It
+[+-]hhmm
+.It
+[+-]hh:mm
+.It
+[+-]hh
+.El
+.It
 .Ql UTC
 specified as:
-.Dq GMT
-.Pq Ql Greenwich Mean Time ,
-.Dq UT
-.Pq Ql Universal Time ,
-or
-.Dq Z
-.Pq Ql Zulu Time ;
-a three character US time zone specified as:
-.Dq EDT ,
-.Dq EST ,
-.Dq CDT ,
-.Dq CST ,
-.Dq MDT ,
-.Dq MST ,
-.Dq PDT ,
-or
-.Dq PST ,
+.Bl -bullet -offset indent -compact
+.It
+UTC
+.Pq Ql Coordinated Universal time
+.It
+GMT
+.Pq Ql Greenwich Mean Time
+.It
+UT
+.Pq Ql Universal Time
+.It
+Z
+.Pq Ql Zulu Time
+.El
+.It
+A three character US time zone specified as:
+.Bl -bullet -offset indent -compact
+.It
+EDT
+.It
+EST
+.It
+CDT
+.It
+CST
+.It
+MDT
+.It
+MST
+.It
+PDT
+.It
+PST
+.El
 with the first letter standing for
 .Ql Eastern
 .Pq Dq E ,
@@ -270,15 +293,22 @@ time
 or
 .Ql Standard
 .Pq Dq S
-time;
+time
+.It 
 a single letter military time zone specified as:
+.Bl -bullet -offset indent -compact
+.It
 .Dq A
 through
 .Dq I
-and
+.It
 .Dq K
 through
 .Dq Y .
+.El
+.It
+An arbirtrary timezone name that can be loaded from the database.
+.El
 .Po
 A
 .Nx

Index: src/lib/libc/time/strptime.c
diff -u src/lib/libc/time/strptime.c:1.49 src/lib/libc/time/strptime.c:1.50
--- src/lib/libc/time/strptime.c:1.49	Fri Oct  9 13:21:45 2015
+++ src/lib/libc/time/strptime.c	Thu Oct 29 13:54:49 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: strptime.c,v 1.49 2015/10/09 17:21:45 christos Exp $	*/
+/*	$NetBSD: strptime.c,v 1.50 2015/10/29 17:54:49 christos Exp $	*/
 
 /*-
  * Copyright (c) 1997, 1998, 2005, 2008 The NetBSD Foundation, Inc.
@@ -31,7 +31,7 @@
 
 #include <sys/cdefs.h>
 #if defined(LIBC_SCCS) && !defined(lint)
-__RCSID("$NetBSD: strptime.c,v 1.49 2015/10/09 17:21:45 christos Exp $");
+__RCSID("$NetBSD: strptime.c,v 1.50 2015/10/29 17:54:49 christos Exp $");
 #endif
 
 #include "namespace.h"
@@ -114,6 +114,34 @@ first_wday_of(int yr)
 	    (isleap(yr) ? 6 : 0) + 1) % 7;
 }
 
+#define delim(p)	((p) == '\0' || isspace((unsigned char)(p)))
+
+static int
+fromzone(const unsigned char **bp, struct tm *tm)
+{
+	timezone_t tz;
+	char buf[512], *p;
+
+	for (p = buf; !delim(**bp) && p < &buf[sizeof(buf) - 1]; (*bp)++)
+		*p++ = **bp;
+	*p = '\0';
+
+	tz = tzalloc(buf);
+	if (tz == NULL)
+		return 0;
+
+	tm->tm_isdst = 0;	/* XXX */
+#ifdef TM_GMTOFF
+	tm->TM_GMTOFF = tzgetgmtoff(tz, tm->tm_isdst);
+#endif
+#ifdef TM_ZONE
+	// Can't use tzgetname() here because we are going to free()
+	tm->TM_ZONE = utc; /* XXX */
+#endif
+	tzfree(tz);
+	return 1;
+}
+
 char *
 strptime(const char *buf, const char *fmt, struct tm *tm)
 {
@@ -124,7 +152,7 @@ char *
 strptime_l(const char *buf, const char *fmt, struct tm *tm, locale_t loc)
 {
 	unsigned char c;
-	const unsigned char *bp, *ep;
+	const unsigned char *bp, *ep, *zname;
 	int alt_format, i, split_year = 0, neg = 0, state = 0,
 	    day_offset = -1, week_offset = 0, offs;
 	const char *new_fmt;
@@ -439,13 +467,18 @@ literal:
 				if (ep != NULL) {
 					tm->tm_isdst = i;
 #ifdef TM_GMTOFF
-					tm->TM_GMTOFF = -(timezone);
+#ifdef USG_COMPAT
+					tm->TM_GMTOFF = -timezone;
+#else
+					tm->TM_GMTOFF = -timezone();
+#endif
 #endif
 #ifdef TM_ZONE
 					tm->TM_ZONE = tzname[i];
+					bp = ep;
 #endif
-				}
-				bp = ep;
+				} else
+					(void)fromzone(&bp, tm);
 			}
 			continue;
 
@@ -470,16 +503,21 @@ literal:
 			while (isspace(*bp))
 				bp++;
 
+			zname = bp;
 			switch (*bp++) {
 			case 'G':
 				if (*bp++ != 'M')
-					return NULL;
+					goto namedzone;
 				/*FALLTHROUGH*/
 			case 'U':
 				if (*bp++ != 'T')
-					return NULL;
+					goto namedzone;
+				else if (!delim(*bp) && *bp++ != 'C')
+					goto namedzone;
 				/*FALLTHROUGH*/
 			case 'Z':
+				if (!delim(*bp))
+					goto namedzone;
 				tm->tm_isdst = 0;
 #ifdef TM_GMTOFF
 				tm->TM_GMTOFF = 0;
@@ -495,11 +533,42 @@ literal:
 				neg = 1;
 				break;
 			default:
-				--bp;
+namedzone:
+				bp = zname;
+
+				/* Military style */
+				if (delim(bp[1]) &&
+				    ((*bp >= 'A' && *bp <= 'I') ||
+				    (*bp >= 'L' && *bp <= 'Y'))) {
+#ifdef TM_GMTOFF
+					/* Argh! No 'J'! */
+					if (*bp >= 'A' && *bp <= 'I')
+						tm->TM_GMTOFF =
+						    ('A' - 1) - (int)*bp;
+					else if (*bp >= 'L' && *bp <= 'M')
+						tm->TM_GMTOFF = 'A' - (int)*bp;
+					else if (*bp >= 'N' && *bp <= 'Y')
+						tm->TM_GMTOFF = (int)*bp - 'M';
+					tm->TM_GMTOFF *= 3600;
+#endif
+#ifdef TM_ZONE
+					tm->TM_ZONE = utc; /* XXX */
+#endif
+					bp++;
+					continue;
+				}
+
+				/*
+				 * From our 3 letter hard-coded table
+				 * XXX: Can be removed, handled by tzload()
+				 */
+				if (delim(bp[0]) || delim(bp[1]) ||
+				    delim(bp[2]) || !delim(bp[3]))
+					goto loadzone;
 				ep = find_string(bp, &i, nast, NULL, 4);
 				if (ep != NULL) {
 #ifdef TM_GMTOFF
-					tm->TM_GMTOFF = -5 - i;
+					tm->TM_GMTOFF = (-5 - i) * 3600;
 #endif
 #ifdef TM_ZONE
 					tm->TM_ZONE = __UNCONST(nast[i]);
@@ -511,7 +580,7 @@ literal:
 				if (ep != NULL) {
 					tm->tm_isdst = 1;
 #ifdef TM_GMTOFF
-					tm->TM_GMTOFF = -4 - i;
+					tm->TM_GMTOFF = (-4 - i) * 3600;
 #endif
 #ifdef TM_ZONE
 					tm->TM_ZONE = __UNCONST(nadt[i]);
@@ -520,24 +589,12 @@ literal:
 					continue;
 				}
 
-				if ((*bp >= 'A' && *bp <= 'I') ||
-				    (*bp >= 'L' && *bp <= 'Y')) {
-#ifdef TM_GMTOFF
-					/* Argh! No 'J'! */
-					if (*bp >= 'A' && *bp <= 'I')
-						tm->TM_GMTOFF =
-						    ('A' - 1) - (int)*bp;
-					else if (*bp >= 'L' && *bp <= 'M')
-						tm->TM_GMTOFF = 'A' - (int)*bp;
-					else if (*bp >= 'N' && *bp <= 'Y')
-						tm->TM_GMTOFF = (int)*bp - 'M';
-#endif
-#ifdef TM_ZONE
-					tm->TM_ZONE = utc; /* XXX */
-#endif
-					bp++;
+loadzone:
+				/*
+				 * The hard way, load the zone!
+				 */
+				if (fromzone(&bp, tm))
 					continue;
-				}
 				return NULL;
 			}
 			offs = 0;
@@ -553,16 +610,18 @@ literal:
 				}
 				break;
 			}
+			if (isdigit(*bp))
+				return NULL;
 			switch (i) {
 			case 2:
-				offs *= 100;
+				offs *= 3600;
 				break;
 			case 4:
 				i = offs % 100;
 				if (i >= 60)
 					return NULL;
 				/* Convert minutes into decimal */
-				offs = (offs / 100) * 100 + (i * 50) / 30;
+				offs = (offs / 100) * 3600 + i * 60;
 				break;
 			default:
 				return NULL;

Reply via email to