Hi, I noticed some things in the strptime(3) implementing when parsing timezone strings using the %z format string.
1. I noticed the tm_gmtoff value is not correctly set in some cases. It should set tm_gmtoff to the offset from UTC in seconds (as described in mktime(3)). 2. The military/nautical UTC offsets are also reversed. This was also actually a bug in RFC822: RFC5322 (referenced in strptime(3) man page): https://tools.ietf.org/html/rfc5322 Section 4.3, page 34 says: " The 1 character military time zones were defined in a non-standard way in [RFC0822] and are therefore unpredictable in their meaning. The original definitions of the military zones "A" through "I" are equivalent to "+0100" through "+0900", respectively; "K", "L", and "M" are equivalent to "+1000", "+1100", and "+1200", respectively; "N" through "Y" are equivalent to "-0100" through "-1200". respectively; and "Z" is equivalent to "+0000". However, because of the error in [RFC0822], they SHOULD all be considered equivalent to "-0000" unless there is out-of-band information confirming their meaning. " While comparing the other BSD and Linux code-bases I noticed NetBSD has fixed this on 2017-08-24. It is not in NetBSD 8 stable yet, but in NetBSD-current: http://cvsweb.netbsd.org/bsdweb.cgi/src/lib/libc/time/strptime.c 3. The military zone J is also ambiguous. Some standards define it as unused, while others define it as "local observed time". NetBSD handles it as local observed time, but OpenBSD returns NULL in strptime(3). I left this as is. 4. While at it I also fixed some trailing whitespaces. Thanks for not falling asleep and reading the long text :) Patch and test program below: diff --git lib/libc/time/strptime.c lib/libc/time/strptime.c index eaf182dc773..86a0d5353ee 100644 --- lib/libc/time/strptime.c +++ lib/libc/time/strptime.c @@ -116,7 +116,7 @@ _strptime(const char *buf, const char *fmt, struct tm *tm, int initialize) fmt++; continue; } - + if ((c = *fmt++) != '%') goto literal; @@ -142,7 +142,7 @@ literal: _LEGAL_ALT(0); alt_format |= _ALT_O; goto again; - + /* * "Complex" conversion rules, implemented through recursion. */ @@ -380,7 +380,7 @@ literal: * number but without the century. */ if (!(_conv_num(&bp, &i, 0, 99))) - return (NULL); + return (NULL); continue; case 'G': /* The year corresponding to the ISO week @@ -500,7 +500,7 @@ literal: ep = _find_string(bp, &i, nast, NULL, 4); if (ep != NULL) { #ifdef TM_GMTOFF - tm->TM_GMTOFF = -5 - i; + tm->TM_GMTOFF = (-5 - i) * SECSPERHOUR; #endif #ifdef TM_ZONE tm->TM_ZONE = (char *)nast[i]; @@ -512,7 +512,7 @@ literal: if (ep != NULL) { tm->tm_isdst = 1; #ifdef TM_GMTOFF - tm->TM_GMTOFF = -4 - i; + tm->TM_GMTOFF = (-4 - i) * SECSPERHOUR; #endif #ifdef TM_ZONE tm->TM_ZONE = (char *)nadt[i]; @@ -527,11 +527,12 @@ literal: /* Argh! No 'J'! */ if (*bp >= 'A' && *bp <= 'I') tm->TM_GMTOFF = - ('A' - 1) - (int)*bp; + (int)*bp - ('A' - 1); else if (*bp >= 'L' && *bp <= 'M') - tm->TM_GMTOFF = 'A' - (int)*bp; + tm->TM_GMTOFF = (int)*bp - 'A'; else if (*bp >= 'N' && *bp <= 'Y') - tm->TM_GMTOFF = (int)*bp - 'M'; + tm->TM_GMTOFF = 'M' - (int)*bp; + tm->TM_GMTOFF *= SECSPERHOUR; #endif #ifdef TM_ZONE tm->TM_ZONE = NULL; /* XXX */ @@ -556,14 +557,15 @@ literal: } switch (i) { case 2: - offs *= 100; + offs *= SECSPERHOUR; break; case 4: i = offs % 100; - if (i >= 60) + offs /= 100; + if (i >= SECSPERMIN) return NULL; /* Convert minutes into decimal */ - offs = (offs / 100) * 100 + (i * 50) / 30; + offs = offs * SECSPERHOUR + i * SECSPERMIN; break; default: return NULL; @@ -719,7 +721,7 @@ _find_string(const u_char *bp, int *tgt, const char * const *n1, return NULL; } -static int +static int leaps_thru_end_of(const int y) { return (y >= 0) ? (y / 4 - y / 100 + y / 400) : Quick & dirty test program below: #include <sys/types.h> #include <err.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> void testtime(const char *s) { struct tm tm; memset(&tm, 0, sizeof(tm)); if (strptime(s, "%Y-%m-%d %H:%M:%S %z", &tm) == NULL) errx(1, "strptime: s=%s", s); printf("s=%s, gmtoff=%ld, zone=%s\n", s, tm.tm_gmtoff, tm.tm_zone ? tm.tm_zone : "(null)"); } int main(void) { testtime("2000-01-01 00:00:01 Z"); testtime("2000-01-01 00:00:01 UT"); testtime("2000-01-01 00:00:01 GMT"); testtime("2000-01-01 00:00:01 -04:00"); testtime("2000-01-01 00:00:01 -0400"); testtime("2000-01-01 00:00:01 +04:00"); testtime("2000-01-01 00:00:01 +0400"); testtime("2000-01-01 00:00:01 EST"); testtime("2000-01-01 00:00:01 EDT"); testtime("2000-01-01 00:00:01 A"); testtime("2000-01-01 00:00:01 B"); testtime("2000-01-01 00:00:01 C"); //testtime("2000-01-01 00:00:01 J"); // strptime returns NULL. testtime("2000-01-01 00:00:01 Q"); testtime("2000-01-01 00:00:01 R"); return 0; } -- Kind regards, Hiltjo