Sync strptime.c with upstream.

We still have local differences, but this minimizes (and documents) them.

Bug: http://b/167569813
Test: treehugger
Change-Id: Ib90e6ccc5ec1224e7ee89224a51b87fc48c9931f
diff --git a/libc/tzcode/strptime.c b/libc/tzcode/strptime.c
index 7e8e234..fe9e10f 100644
--- a/libc/tzcode/strptime.c
+++ b/libc/tzcode/strptime.c
@@ -1,8 +1,7 @@
-/*  $OpenBSD: strptime.c,v 1.11 2005/08/08 08:05:38 espie Exp $ */
-/*  $NetBSD: strptime.c,v 1.12 1998/01/20 21:39:40 mycroft Exp $    */
-
+/*	$OpenBSD: strptime.c,v 1.30 2019/05/12 12:49:52 schwarze Exp $ */
+/*	$NetBSD: strptime.c,v 1.12 1998/01/20 21:39:40 mycroft Exp $	*/
 /*-
- * Copyright (c) 1997, 1998 The NetBSD Foundation, Inc.
+ * Copyright (c) 1997, 1998, 2005, 2008 The NetBSD Foundation, Inc.
  * All rights reserved.
  *
  * This code was contributed to The NetBSD Foundation by Klaus Klein.
@@ -15,13 +14,6 @@
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
- * 3. All advertising materials mentioning features or use of this software
- *    must display the following acknowledgement:
- *        This product includes software developed by the NetBSD
- *        Foundation, Inc. and its contributors.
- * 4. Neither the name of The NetBSD Foundation nor the names of its
- *    contributors may be used to endorse or promote products derived
- *    from this software without specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
@@ -36,65 +28,40 @@
  * POSSIBILITY OF SUCH DAMAGE.
  */
 
-//#include <sys/localedef.h>
 #include <ctype.h>
-#include <errno.h>
 #include <locale.h>
-#include <stdlib.h>
+#include <stdint.h>
 #include <string.h>
 #include <time.h>
+
+#include "localedef.h"
+#include "private.h"
 #include "tzfile.h"
 
-static const struct {
-    const char *abday[7];
-    const char *day[7];
-    const char *abmon[12];
-    const char *mon[12];
-    const char *am_pm[2];
-    const char *d_t_fmt;
-    const char *d_fmt;
-    const char *t_fmt;
-    const char *t_fmt_ampm;
-} _DefaultTimeLocale = {
-    {
-        "Sun","Mon","Tue","Wed","Thu","Fri","Sat",
-    },
-    {
-        "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
-        "Friday", "Saturday"
-    },
-    {
-        "Jan", "Feb", "Mar", "Apr", "May", "Jun",
-        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
-    },
-    {
-        "January", "February", "March", "April", "May", "June", "July",
-        "August", "September", "October", "November", "December"
-    },
-    {
-        "AM", "PM"
-    },
-    "%a %b %d %H:%M:%S %Y",
-    "%m/%d/%y",
-    "%H:%M:%S",
-    "%I:%M:%S %p"
-};
+// Android: ignore OpenBSD's DEF_WEAK() stuff.
+#define DEF_WEAK(sym) /* */
+// Android: this code is not pointer-sign clean.
+#pragma clang diagnostic ignored "-Wpointer-sign"
+#pragma clang diagnostic ignored "-Wunused-function"
 
-#define _ctloc(x) (_DefaultTimeLocale.x)
+#define	_ctloc(x)		(_CurrentTimeLocale->x)
 
 /*
  * We do not implement alternate representations. However, we always
  * check whether a given modifier is allowed for a certain conversion.
  */
-#define _ALT_E          0x01
-#define _ALT_O          0x02
-#define _LEGAL_ALT(x)       { if (alt_format & ~(x)) return (0); }
+#define _ALT_E			0x01
+#define _ALT_O			0x02
+#define	_LEGAL_ALT(x)		{ if (alt_format & ~(x)) return (0); }
 
-
-struct century_relyear {
-    int century;
-    int relyear;
-};
+/*
+ * We keep track of some of the fields we set in order to compute missing ones.
+ */
+#define FIELD_TM_MON	(1 << 0)
+#define FIELD_TM_MDAY	(1 << 1)
+#define FIELD_TM_WDAY	(1 << 2)
+#define FIELD_TM_YDAY	(1 << 3)
+#define FIELD_TM_YEAR	(1 << 4)
 
 static char gmt[] = { "GMT" };
 static char utc[] = { "UTC" };
@@ -106,9 +73,15 @@
        "EDT",    "CDT",    "MDT",    "PDT",    "\0\0\0"
 };
 
-static  int _conv_num(const unsigned char **, int *, int, int);
-static  unsigned char *_strptime(const unsigned char *, const char *, struct tm *,
-        struct century_relyear *);
+static const int mon_lengths[2][MONSPERYEAR] = {
+        { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
+        { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
+};
+
+static	int _conv_num64(const unsigned char **, int64_t *, int64_t, int64_t);
+static	int _conv_num(const unsigned char **, int *, int, int);
+static	int leaps_thru_end_of(const int y);
+static	char *_strptime(const char *, const char *, struct tm *, int);
 static	const u_char *_find_string(const u_char *, int *, const char * const *,
 	    const char * const *, int);
 
@@ -116,335 +89,353 @@
 char *
 strptime(const char *buf, const char *fmt, struct tm *tm)
 {
-    struct century_relyear cr;
-    cr.century = TM_YEAR_BASE;
-    cr.relyear = -1;
-    return (char*)(_strptime((const unsigned char*)buf, fmt, tm, &cr));
+	return(_strptime(buf, fmt, tm, 1));
 }
+DEF_WEAK(strptime);
 
-static unsigned char *
-_strptime(const unsigned char *buf, const char *fmt, struct tm *tm, struct century_relyear *cr)
+static char *
+_strptime(const char *buf, const char *fmt, struct tm *tm, int initialize)
 {
-    unsigned char c;
-    const unsigned char *bp, *ep;
-    size_t len = 0;
-    int alt_format, i, offs;
-    int neg = 0;
+	unsigned char c;
+	const unsigned char *bp, *ep;
+	size_t len;
+	int alt_format, i, offs;
+	int neg = 0;
+	static int century, relyear, fields;
 
-    bp = (unsigned char *)buf;
-    while ((c = *fmt) != '\0') {
-        /* Clear `alternate' modifier prior to new conversion. */
-        alt_format = 0;
+	if (initialize) {
+		century = TM_YEAR_BASE;
+		relyear = -1;
+		fields = 0;
+	}
 
-        /* Eat up white-space. */
-        if (isspace(c)) {
-            while (isspace(*bp))
-                bp++;
+	bp = (const unsigned char *)buf;
+	while ((c = *fmt) != '\0') {
+		/* Clear `alternate' modifier prior to new conversion. */
+		alt_format = 0;
 
-            fmt++;
-            continue;
-        }
+		/* Eat up white-space. */
+		if (isspace(c)) {
+			while (isspace(*bp))
+				bp++;
 
-        if ((c = *fmt++) != '%')
-            goto literal;
+			fmt++;
+			continue;
+		}
+
+		if ((c = *fmt++) != '%')
+			goto literal;
 
 
-again:      switch (c = *fmt++) {
-        case '%':   /* "%%" is converted to "%". */
+again:		switch (c = *fmt++) {
+		case '%':	/* "%%" is converted to "%". */
 literal:
-        if (c != *bp++)
-            return (NULL);
+		if (c != *bp++)
+			return (NULL);
 
-        break;
+		break;
 
-        /*
-         * "Alternative" modifiers. Just set the appropriate flag
-         * and start over again.
-         */
-        case 'E':   /* "%E?" alternative conversion modifier. */
-            _LEGAL_ALT(0);
-            alt_format |= _ALT_E;
-            goto again;
+		/*
+		 * "Alternative" modifiers. Just set the appropriate flag
+		 * and start over again.
+		 */
+		case 'E':	/* "%E?" alternative conversion modifier. */
+			_LEGAL_ALT(0);
+			alt_format |= _ALT_E;
+			goto again;
 
-        case 'O':   /* "%O?" alternative conversion modifier. */
-            _LEGAL_ALT(0);
-            alt_format |= _ALT_O;
-            goto again;
+		case 'O':	/* "%O?" alternative conversion modifier. */
+			_LEGAL_ALT(0);
+			alt_format |= _ALT_O;
+			goto again;
 
-        /*
-         * "Complex" conversion rules, implemented through recursion.
-         */
-        case 'c':   /* Date and time, using the locale's format. */
-            _LEGAL_ALT(_ALT_E);
-            if (!(bp = _strptime(bp, _ctloc(d_t_fmt), tm, cr)))
-                return (NULL);
-            break;
+		/*
+		 * "Complex" conversion rules, implemented through recursion.
+		 */
+		case 'c':	/* Date and time, using the locale's format. */
+			_LEGAL_ALT(_ALT_E);
+			if (!(bp = _strptime(bp, _ctloc(d_t_fmt), tm, 0)))
+				return (NULL);
+			break;
 
-        case 'D':   /* The date as "%m/%d/%y". */
-            _LEGAL_ALT(0);
-            if (!(bp = _strptime(bp, "%m/%d/%y", tm, cr)))
-                return (NULL);
-            break;
+		case 'D':	/* The date as "%m/%d/%y". */
+			_LEGAL_ALT(0);
+			if (!(bp = _strptime(bp, "%m/%d/%y", tm, 0)))
+				return (NULL);
+			break;
 
-        case 'F':  /* The date as "%Y-%m-%d". */
-            _LEGAL_ALT(0);
-            if (!(bp = _strptime(bp, "%Y-%m-%d", tm, cr)))
-                return (NULL);
-            continue;
+		case 'F':	/* The date as "%Y-%m-%d". */
+			_LEGAL_ALT(0);
+			if (!(bp = _strptime(bp, "%Y-%m-%d", tm, 0)))
+				return (NULL);
+			continue;
 
-        case 'R':   /* The time as "%H:%M". */
-            _LEGAL_ALT(0);
-            if (!(bp = _strptime(bp, "%H:%M", tm, cr)))
-                return (NULL);
-            break;
+		case 'R':	/* The time as "%H:%M". */
+			_LEGAL_ALT(0);
+			if (!(bp = _strptime(bp, "%H:%M", tm, 0)))
+				return (NULL);
+			break;
 
-        case 'r':   /* The time as "%I:%M:%S %p". */
-            _LEGAL_ALT(0);
-            if (!(bp = _strptime(bp, "%I:%M:%S %p", tm, cr)))
-                return (NULL);
-            break;
+		case 'r':	/* The time as "%I:%M:%S %p". */
+			_LEGAL_ALT(0);
+			if (!(bp = _strptime(bp, "%I:%M:%S %p", tm, 0)))
+				return (NULL);
+			break;
 
-        case 'T':   /* The time as "%H:%M:%S". */
-            _LEGAL_ALT(0);
-            if (!(bp = _strptime(bp, "%H:%M:%S", tm, cr)))
-                return (NULL);
-            break;
+		case 'T':	/* The time as "%H:%M:%S". */
+			_LEGAL_ALT(0);
+			if (!(bp = _strptime(bp, "%H:%M:%S", tm, 0)))
+				return (NULL);
+			break;
 
-        case 'v':  /* The date as "%e-%b-%Y". */
-            _LEGAL_ALT(0);
-            if (!(bp = _strptime(bp, "%e-%b-%Y", tm, cr)))
-                return (NULL);
-            break;
+		case 'v':	/* Android: the date as "%e-%b-%Y" for strftime() compat; glibc does this too. */
+			_LEGAL_ALT(0);
+			if (!(bp = _strptime(bp, "%e-%b-%Y", tm, 0)))
+				return (NULL);
+			break;
 
-        case 'X':   /* The time, using the locale's format. */
-            _LEGAL_ALT(_ALT_E);
-            if (!(bp = _strptime(bp, _ctloc(t_fmt), tm, cr)))
-                return (NULL);
-            break;
+		case 'X':	/* The time, using the locale's format. */
+			_LEGAL_ALT(_ALT_E);
+			if (!(bp = _strptime(bp, _ctloc(t_fmt), tm, 0)))
+				return (NULL);
+			break;
 
-        case 'x':   /* The date, using the locale's format. */
-            _LEGAL_ALT(_ALT_E);
-            if (!(bp = _strptime(bp, _ctloc(d_fmt), tm, cr)))
-                return (NULL);
-            break;
+		case 'x':	/* The date, using the locale's format. */
+			_LEGAL_ALT(_ALT_E);
+			if (!(bp = _strptime(bp, _ctloc(d_fmt), tm, 0)))
+				return (NULL);
+			break;
 
-        /*
-         * "Elementary" conversion rules.
-         */
-        case 'A':   /* The day of week, using the locale's form. */
-        case 'a':
-            _LEGAL_ALT(0);
-            for (i = 0; i < 7; i++) {
-                /* Full name. */
-                len = strlen(_ctloc(day[i]));
-                if (strncasecmp(_ctloc(day[i]), (const char*)bp, len) == 0)
-                    break;
+		/*
+		 * "Elementary" conversion rules.
+		 */
+		case 'A':	/* The day of week, using the locale's form. */
+		case 'a':
+			_LEGAL_ALT(0);
+			for (i = 0; i < 7; i++) {
+				/* Full name. */
+				len = strlen(_ctloc(day[i]));
+				if (strncasecmp(_ctloc(day[i]), bp, len) == 0)
+					break;
 
-                /* Abbreviated name. */
-                len = strlen(_ctloc(abday[i]));
-                if (strncasecmp(_ctloc(abday[i]), (const char*)bp, len) == 0)
-                    break;
-            }
+				/* Abbreviated name. */
+				len = strlen(_ctloc(abday[i]));
+				if (strncasecmp(_ctloc(abday[i]), bp, len) == 0)
+					break;
+			}
 
-            /* Nothing matched. */
-            if (i == 7)
-                return (NULL);
+			/* Nothing matched. */
+			if (i == 7)
+				return (NULL);
 
-            tm->tm_wday = i;
-            bp += len;
-            break;
+			tm->tm_wday = i;
+			bp += len;
+			fields |= FIELD_TM_WDAY;
+			break;
 
-        case 'B':   /* The month, using the locale's form. */
-        case 'b':
-        case 'h':
-            _LEGAL_ALT(0);
-            for (i = 0; i < 12; i++) {
-                /* Full name. */
-                len = strlen(_ctloc(mon[i]));
-                if (strncasecmp(_ctloc(mon[i]), (const char*)bp, len) == 0)
-                    break;
+		case 'B':	/* The month, using the locale's form. */
+		case 'b':
+		case 'h':
+			_LEGAL_ALT(0);
+			for (i = 0; i < 12; i++) {
+				/* Full name. */
+				len = strlen(_ctloc(mon[i]));
+				if (strncasecmp(_ctloc(mon[i]), bp, len) == 0)
+					break;
 
-                /* Abbreviated name. */
-                len = strlen(_ctloc(abmon[i]));
-                if (strncasecmp(_ctloc(abmon[i]), (const char*)bp, len) == 0)
-                    break;
-            }
+				/* Abbreviated name. */
+				len = strlen(_ctloc(abmon[i]));
+				if (strncasecmp(_ctloc(abmon[i]), bp, len) == 0)
+					break;
+			}
 
-            /* Nothing matched. */
-            if (i == 12)
-                return (NULL);
+			/* Nothing matched. */
+			if (i == 12)
+				return (NULL);
 
-            tm->tm_mon = i;
-            bp += len;
-            break;
+			tm->tm_mon = i;
+			bp += len;
+			fields |= FIELD_TM_MON;
+			break;
 
-        case 'C':   /* The century number. */
-            _LEGAL_ALT(_ALT_E);
-            if (!(_conv_num(&bp, &i, 0, 99)))
-                return (NULL);
+		case 'C':	/* The century number. */
+			_LEGAL_ALT(_ALT_E);
+			if (!(_conv_num(&bp, &i, 0, 99)))
+				return (NULL);
 
-            cr->century = i * 100;
-            break;
+			century = i * 100;
+			break;
 
-        case 'd':   /* The day of month. */
-        case 'e':
-            _LEGAL_ALT(_ALT_O);
-            if (!(_conv_num(&bp, &tm->tm_mday, 1, 31)))
-                return (NULL);
-            break;
+		case 'e':	/* The day of month. */
+			if (isspace(*bp))
+				bp++;
+			/* FALLTHROUGH */
+		case 'd':
+			_LEGAL_ALT(_ALT_O);
+			if (!(_conv_num(&bp, &tm->tm_mday, 1, 31)))
+				return (NULL);
+			fields |= FIELD_TM_MDAY;
+			break;
 
-        case 'k':   /* The hour (24-hour clock representation). */
-            _LEGAL_ALT(0);
-            /* FALLTHROUGH */
-        case 'H':
-            _LEGAL_ALT(_ALT_O);
-            if (!(_conv_num(&bp, &tm->tm_hour, 0, 23)))
-                return (NULL);
-            break;
+		case 'k':	/* The hour (24-hour clock representation). */
+			_LEGAL_ALT(0);
+			/* FALLTHROUGH */
+		case 'H':
+			_LEGAL_ALT(_ALT_O);
+			if (!(_conv_num(&bp, &tm->tm_hour, 0, 23)))
+				return (NULL);
+			break;
 
-        case 'l':   /* The hour (12-hour clock representation). */
-            _LEGAL_ALT(0);
-            /* FALLTHROUGH */
-        case 'I':
-            _LEGAL_ALT(_ALT_O);
-            if (!(_conv_num(&bp, &tm->tm_hour, 1, 12)))
-                return (NULL);
-            break;
+		case 'l':	/* The hour (12-hour clock representation). */
+			_LEGAL_ALT(0);
+			/* FALLTHROUGH */
+		case 'I':
+			_LEGAL_ALT(_ALT_O);
+			if (!(_conv_num(&bp, &tm->tm_hour, 1, 12)))
+				return (NULL);
+			break;
 
-        case 'j':   /* The day of year. */
-            _LEGAL_ALT(0);
-            if (!(_conv_num(&bp, &tm->tm_yday, 1, 366)))
-                return (NULL);
-            tm->tm_yday--;
-            break;
+		case 'j':	/* The day of year. */
+			_LEGAL_ALT(0);
+			if (!(_conv_num(&bp, &tm->tm_yday, 1, 366)))
+				return (NULL);
+			tm->tm_yday--;
+			fields |= FIELD_TM_YDAY;
+			break;
 
-        case 'M':   /* The minute. */
-            _LEGAL_ALT(_ALT_O);
-            if (!(_conv_num(&bp, &tm->tm_min, 0, 59)))
-                return (NULL);
-            break;
+		case 'M':	/* The minute. */
+			_LEGAL_ALT(_ALT_O);
+			if (!(_conv_num(&bp, &tm->tm_min, 0, 59)))
+				return (NULL);
+			break;
 
-        case 'm':   /* The month. */
-            _LEGAL_ALT(_ALT_O);
-            if (!(_conv_num(&bp, &tm->tm_mon, 1, 12)))
-                return (NULL);
-            tm->tm_mon--;
-            break;
+		case 'm':	/* The month. */
+			_LEGAL_ALT(_ALT_O);
+			if (!(_conv_num(&bp, &tm->tm_mon, 1, 12)))
+				return (NULL);
+			tm->tm_mon--;
+			fields |= FIELD_TM_MON;
+			break;
 
-        case 'P':
-        case 'p':   /* The locale's equivalent of AM/PM. */
-            _LEGAL_ALT(0);
-            /* AM? */
-            len = strlen(_ctloc(am_pm[0]));
-            if (strncasecmp(_ctloc(am_pm[0]), (const char*)bp, len) == 0) {
-                if (tm->tm_hour > 12)   /* i.e., 13:00 AM ?! */
-                    return (NULL);
-                else if (tm->tm_hour == 12)
-                    tm->tm_hour = 0;
+		case 'P':	/* Android addition for strftime() compat; glibc does this too. */
+		case 'p':	/* The locale's equivalent of AM/PM. */
+			_LEGAL_ALT(0);
+			/* AM? */
+			len = strlen(_ctloc(am_pm[0]));
+			if (strncasecmp(_ctloc(am_pm[0]), bp, len) == 0) {
+				if (tm->tm_hour > 12)	/* i.e., 13:00 AM ?! */
+					return (NULL);
+				else if (tm->tm_hour == 12)
+					tm->tm_hour = 0;
 
-                bp += len;
-                break;
-            }
-            /* PM? */
-            len = strlen(_ctloc(am_pm[1]));
-            if (strncasecmp(_ctloc(am_pm[1]), (const char*)bp, len) == 0) {
-                if (tm->tm_hour > 12)   /* i.e., 13:00 PM ?! */
-                    return (NULL);
-                else if (tm->tm_hour < 12)
-                    tm->tm_hour += 12;
+				bp += len;
+				break;
+			}
+			/* PM? */
+			len = strlen(_ctloc(am_pm[1]));
+			if (strncasecmp(_ctloc(am_pm[1]), bp, len) == 0) {
+				if (tm->tm_hour > 12)	/* i.e., 13:00 PM ?! */
+					return (NULL);
+				else if (tm->tm_hour < 12)
+					tm->tm_hour += 12;
 
-                bp += len;
-                break;
-            }
+				bp += len;
+				break;
+			}
 
-            /* Nothing matched. */
-            return (NULL);
+			/* Nothing matched. */
+			return (NULL);
 
-        case 'S':   /* The seconds. */
-            _LEGAL_ALT(_ALT_O);
-            if (!(_conv_num(&bp, &tm->tm_sec, 0, 61)))
-                return (NULL);
-            break;
+		case 'S':	/* The seconds. */
+			_LEGAL_ALT(_ALT_O);
+			if (!(_conv_num(&bp, &tm->tm_sec, 0, 60)))
+				return (NULL);
+			break;
+		case 's':	/* Seconds since epoch */
+			{
+				// Android change based on FreeBSD's implementation.
+				int saved_errno = errno;
+				errno = 0;
+				const unsigned char* old_bp = bp;
+				long n = strtol((const char*) bp, (char**) &bp, 10);
+				errno = saved_errno;
+				time_t t = n;
+				if (bp == old_bp || errno == ERANGE ||
+				    ((long) t) != n) return NULL;
 
-        case 's':
-            {
-                // Android addition, based on FreeBSD's implementation.
-                int saved_errno = errno;
-                errno = 0;
-                const unsigned char* old_bp = bp;
-                long n = strtol((const char*) bp, (char**) &bp, 10);
-                time_t t = n;
-                if (bp == old_bp || errno == ERANGE || ((long) t) != n) {
-                    errno = saved_errno;
-                    return NULL;
-                }
-                errno = saved_errno;
+				if (localtime_r(&t, tm) == NULL) return NULL;
 
-                if (localtime_r(&t, tm) == NULL) return NULL;
-            }
-            break;
+				//int64_t i64;
+				//if (!(_conv_num64(&bp, &i64, 0, INT64_MAX)))
+				//	return (NULL);
+				//if (!gmtime_r(&i64, tm))
+				//	return (NULL);
+				fields = 0xffff;	 /* everything */
+			}
+			break;
+		case 'U':	/* The week of year, beginning on sunday. */
+		case 'W':	/* The week of year, beginning on monday. */
+			_LEGAL_ALT(_ALT_O);
+			/*
+			 * XXX This is bogus, as we can not assume any valid
+			 * information present in the tm structure at this
+			 * point to calculate a real value, so just check the
+			 * range for now.
+			 */
+			 if (!(_conv_num(&bp, &i, 0, 53)))
+				return (NULL);
+			 break;
 
+		case 'w':	/* The day of week, beginning on sunday. */
+			_LEGAL_ALT(_ALT_O);
+			if (!(_conv_num(&bp, &tm->tm_wday, 0, 6)))
+				return (NULL);
+			fields |= FIELD_TM_WDAY;
+			break;
 
-        case 'U':   /* The week of year, beginning on sunday. */
-        case 'W':   /* The week of year, beginning on monday. */
-            _LEGAL_ALT(_ALT_O);
-            /*
-             * XXX This is bogus, as we can not assume any valid
-             * information present in the tm structure at this
-             * point to calculate a real value, so just check the
-             * range for now.
-             */
-             if (!(_conv_num(&bp, &i, 0, 53)))
-                return (NULL);
-             break;
+		case 'u':	/* The day of week, monday = 1. */
+			_LEGAL_ALT(_ALT_O);
+			if (!(_conv_num(&bp, &i, 1, 7)))
+				return (NULL);
+			tm->tm_wday = i % 7;
+			fields |= FIELD_TM_WDAY;
+			continue;
 
-        case 'w':   /* The day of week, beginning on sunday. */
-            _LEGAL_ALT(_ALT_O);
-            if (!(_conv_num(&bp, &tm->tm_wday, 0, 6)))
-                return (NULL);
-            break;
+		case 'g':	/* The year corresponding to the ISO week
+				 * number but without the century.
+				 */
+			if (!(_conv_num(&bp, &i, 0, 99)))
+				return (NULL);
+			continue;
 
-        case 'u':  /* The day of week, monday = 1. */
-            _LEGAL_ALT(_ALT_O);
-            if (!(_conv_num(&bp, &i, 1, 7)))
-                return (NULL);
-            tm->tm_wday = i % 7;
-            continue;
+		case 'G':	/* The year corresponding to the ISO week
+				 * number with century.
+				 */
+			do
+				bp++;
+			while (isdigit(*bp));
+			continue;
 
-        case 'g':  /* The year corresponding to the ISO week
-                    * number but without the century.
-                    */
-            if (!(_conv_num(&bp, &i, 0, 99)))
-                return (NULL);
-            continue;
+		case 'V':	/* The ISO 8601:1988 week number as decimal */
+			if (!(_conv_num(&bp, &i, 0, 53)))
+				return (NULL);
+			continue;
 
-        case 'G':  /* The year corresponding to the ISO week
-                    * number with century.
-                    */
-            do
-                bp++;
-            while (isdigit(*bp));
-            continue;
+		case 'Y':	/* The year. */
+			_LEGAL_ALT(_ALT_E);
+			if (!(_conv_num(&bp, &i, 0, 9999)))
+				return (NULL);
 
-        case 'V':  /* The ISO 8601:1988 week number as decimal */
-            if (!(_conv_num(&bp, &i, 0, 53)))
-                return (NULL);
-            continue;
+			relyear = -1;
+			tm->tm_year = i - TM_YEAR_BASE;
+			fields |= FIELD_TM_YEAR;
+			break;
 
-        case 'Y':   /* The year. */
-            _LEGAL_ALT(_ALT_E);
-            if (!(_conv_num(&bp, &i, 0, 9999)))
-                return (NULL);
-
-            cr->relyear = -1;
-            tm->tm_year = i - TM_YEAR_BASE;
-            break;
-
-        case 'y':   /* The year within the century (2 digits). */
-            _LEGAL_ALT(_ALT_E | _ALT_O);
-            if (!(_conv_num(&bp, &cr->relyear, 0, 99)))
-                return (NULL);
-            break;
+		case 'y':	/* The year within the century (2 digits). */
+			_LEGAL_ALT(_ALT_E | _ALT_O);
+			if (!(_conv_num(&bp, &relyear, 0, 99)))
+				return (NULL);
+			break;
 
 		case 'Z':
 			tzset();
@@ -548,42 +539,78 @@
 			tm->tm_zone = NULL;	/* XXX */
 			continue;
 
-        /*
-         * Miscellaneous conversions.
-         */
-        case 'n':   /* Any kind of white-space. */
-        case 't':
-            _LEGAL_ALT(0);
-            while (isspace(*bp))
-                bp++;
-            break;
+		/*
+		 * Miscellaneous conversions.
+		 */
+		case 'n':	/* Any kind of white-space. */
+		case 't':
+			_LEGAL_ALT(0);
+			while (isspace(*bp))
+				bp++;
+			break;
 
 
-        default:    /* Unknown/unsupported conversion. */
-            return (NULL);
-        }
+		default:	/* Unknown/unsupported conversion. */
+			return (NULL);
+		}
 
 
-    }
+	}
 
-    /*
-     * We need to evaluate the two digit year spec (%y)
-     * last as we can get a century spec (%C) at any time.
-     */
-    if (cr->relyear != -1) {
-        if (cr->century == TM_YEAR_BASE) {
-            if (cr->relyear <= 68)
-                tm->tm_year = cr->relyear + 2000 - TM_YEAR_BASE;
-            else
-                tm->tm_year = cr->relyear + 1900 - TM_YEAR_BASE;
-        } else {
-            tm->tm_year = cr->relyear + cr->century - TM_YEAR_BASE;
-        }
-    }
+	/*
+	 * We need to evaluate the two digit year spec (%y)
+	 * last as we can get a century spec (%C) at any time.
+	 */
+	if (relyear != -1) {
+		if (century == TM_YEAR_BASE) {
+			if (relyear <= 68)
+				tm->tm_year = relyear + 2000 - TM_YEAR_BASE;
+			else
+				tm->tm_year = relyear + 1900 - TM_YEAR_BASE;
+		} else {
+			tm->tm_year = relyear + century - TM_YEAR_BASE;
+		}
+		fields |= FIELD_TM_YEAR;
+	}
 
-    return (unsigned char*)bp;
+	/* Compute some missing values when possible. */
+	if (fields & FIELD_TM_YEAR) {
+		const int year = tm->tm_year + TM_YEAR_BASE;
+		const int *mon_lens = mon_lengths[isleap(year)];
+		if (!(fields & FIELD_TM_YDAY) &&
+		    (fields & FIELD_TM_MON) && (fields & FIELD_TM_MDAY)) {
+			tm->tm_yday = tm->tm_mday - 1;
+			for (i = 0; i < tm->tm_mon; i++)
+				tm->tm_yday += mon_lens[i];
+			fields |= FIELD_TM_YDAY;
+		}
+		if (fields & FIELD_TM_YDAY) {
+			int days = tm->tm_yday;
+			if (!(fields & FIELD_TM_WDAY)) {
+				tm->tm_wday = EPOCH_WDAY +
+				    ((year - EPOCH_YEAR) % DAYSPERWEEK) *
+				    (DAYSPERNYEAR % DAYSPERWEEK) +
+				    leaps_thru_end_of(year - 1) -
+				    leaps_thru_end_of(EPOCH_YEAR - 1) +
+				    tm->tm_yday;
+				tm->tm_wday %= DAYSPERWEEK;
+				if (tm->tm_wday < 0)
+					tm->tm_wday += DAYSPERWEEK;
+			}
+			if (!(fields & FIELD_TM_MON)) {
+				tm->tm_mon = 0;
+				while (tm->tm_mon < MONSPERYEAR && days >= mon_lens[tm->tm_mon])
+					days -= mon_lens[tm->tm_mon++];
+			}
+			if (!(fields & FIELD_TM_MDAY))
+				tm->tm_mday = days + 1;
+		}
+	}
+
+	return ((char *)bp);
 }
 
+
 static int
 _conv_num(const unsigned char **buf, int *dest, int llim, int ulim)
 {
@@ -607,6 +634,29 @@
 	return (1);
 }
 
+static int
+_conv_num64(const unsigned char **buf, int64_t *dest, int64_t llim, int64_t ulim)
+{
+	int result = 0;
+	int64_t rulim = ulim;
+
+	if (**buf < '0' || **buf > '9')
+		return (0);
+
+	/* we use rulim to break out of the loop when we run out of digits */
+	do {
+		result *= 10;
+		result += *(*buf)++ - '0';
+		rulim /= 10;
+	} while ((result * 10 <= ulim) && rulim && **buf >= '0' && **buf <= '9');
+
+	if (result < llim || result > ulim)
+		return (0);
+
+	*dest = result;
+	return (1);
+}
+
 static const u_char *
 _find_string(const u_char *bp, int *tgt, const char * const *n1,
 		const char * const *n2, int c)
@@ -629,6 +679,9 @@
 	return NULL;
 }
 
-char* strptime_l(const char* buf, const char* fmt, struct tm* tm, locale_t l) {
-  return strptime(buf, fmt, tm);
+static int
+leaps_thru_end_of(const int y)
+{
+	return (y >= 0) ? (y / 4 - y / 100 + y / 400) :
+		-(leaps_thru_end_of(-(y + 1)) + 1);
 }