shithub: libmujs

Download patch

ref: 5b567dd5cf4e4210fa70110b5fa67b1a9aaf59ef
parent: b4223dfb474ef60d84b1ac30c3048dd46b107b20
author: Tor Andersson <tor@ccxvii.net>
date: Mon Mar 3 13:30:48 EST 2014

Make date formatting and parsing re-entrant and libc independent.

localtime is not re-entrant, and doesn't work with negative (pre-1970)
time stamps on windows. Replace with our own simplified variant.

Parse date string format manually rather than using scanf.
Only accept ISO 8601 format.

--- a/jsdate.c
+++ b/jsdate.c
@@ -223,16 +223,84 @@
 	return t < 0 ? -floor(-t) : floor(t);
 }
 
-static double parseDate(const char *str)
+static int toint(const char *s, int w, int *v)
 {
-	const char *tz;
-	int y = 1970, m = 0, d = 1, H = 0, M = 0, S = 0, ms = 0;
+	char *e;
+	*v = strtol(s, &e, 10);
+	return e == s + w;
+}
+
+static double parseDate(const char *s)
+{
+	char *e;
+	int y = 1970, m = 1, d = 1, H = 0, M = 0, S = 0, ms = 0;
+	int tzs = 0, tzm = 0, tzh = 0;
 	double t;
-	sscanf(str, "%04d-%02d-%02d%*[T ]%02d:%02d:%02d.%d", &y, &m, &d, &H, &M, &S, &ms);
-	t = MakeDate(MakeDay(y, m-1, d), MakeTime(H, M, S, ms));
-	if ((tz = strstr(str, "UTC")) && !strcmp(tz, "UTC")) return t;
-	if ((tz = strstr(str, "GMT")) && !strcmp(tz, "GMT")) return t;
-	if ((tz = strstr(str, "Z")) && !strcmp(tz, "Z")) return t;
+
+	/* Parse ISO 8601 formatted date and time: */
+	/* YYYY("-"mm("-"dd("T"HH":"MM(":"SS("."lll)?)?("Z"|[+-]zz(":"?zz)?)?)?)?)? */
+
+	if (!toint(s+0, 4, &y))
+		return NAN;
+	if (s[4] == '-') {
+		if (!toint(s+5, 2, &m))
+			return NAN;
+		if (s[7] == '-') {
+			if (!toint(s+8, 2, &d))
+				return NAN;
+			if (s[10] == 'T') {
+				if (!toint(s+11, 2, &H))
+					return NAN;
+				if (s[13] != ':')
+					return NAN;
+				if (!toint(s+14, 2, &M))
+					return NAN;
+				if (s[16] == ':') {
+					if (!toint(s + 17, 2, &S))
+						return NAN;
+					if (s[19] == '.') {
+						if (!toint(s + 20, 3, &ms))
+							return NAN;
+						s = s + 23;
+					} else {
+						s = s + 19;
+					}
+				} else {
+					s = s + 16;
+				}
+				if (s[0] == 'Z') {
+					tzs = 1;
+					tzh = tzm = 0;
+					if (s[1])
+						return NAN;
+				} else if (s[0] == '+' || s[0] == '-') {
+					tzs = s[0] == '+' ? 1 : -1;
+					tzh = strtol(s + 1, &e, 10);
+					if (e == s + 3) {
+						if (s[3] == ':') {
+							if (!toint(s + 4, 2, &tzm))
+								return NAN;
+							if (s[6])
+								return NAN;
+						} else if (s[3]) return NAN;
+					} else if (e == s + 5) {
+						tzm = tzh % 100;
+						tzh = tzh / 100;
+						if (s[5])
+							return NAN;
+					} else {
+						return NAN;
+					}
+				} else if (s[0]) {
+					return NAN;
+				}
+			} else if (s[10]) return NAN;
+		} else if (s[7]) return NAN;
+	} else if (s[4]) return NAN;
+
+	t = MakeDate(MakeDay(y, m-1, d-1), MakeTime(H, M, S, ms));
+	if (tzs)
+		return t - tzs * (tzh * msPerHour + tzm + msPerDay);
 	return UTC(t);
 }
 
@@ -240,22 +308,58 @@
 
 #define FMT_DATE "%Y-%m-%d"
 #define FMT_TIME "%H:%M:%S"
-#define FMT_DATETIME "%Y-%m-%d %H:%M:%S %Z"
+#define FMT_DATETIME "%Y-%m-%d %H:%M:%S %z"
 #define FMT_DATETIME_ISO "%Y-%m-%dT%H:%M:%SZ"
 
-static const char *fmtlocal(const char *fmt, double t)
+static void fmtdate(char *buf, const char *fmt, double t, float tza)
 {
-	static char buf[64];
-	time_t tt = t * 0.001;
-	strftime(buf, sizeof buf, fmt, localtime(&tt));
+	char *p = buf;
+	const char *s = fmt;
+	int tzsign, tzhh, tzmm;
+	while (*s) {
+		if (*s == '%') {
+			switch (s[1]) {
+			default: *p++ = s[0]; *p++ = s[1]; break;
+			case 'Y': p += sprintf(p, "%04d", YearFromTime(t)); break;
+			case 'm': p += sprintf(p, "%02d", MonthFromTime(t) + 1); break;
+			case 'd': p += sprintf(p, "%02d", DateFromTime(t)); break;
+			case 'H': p += sprintf(p, "%02d", HourFromTime(t)); break;
+			case 'M': p += sprintf(p, "%02d", MinFromTime(t)); break;
+			case 'S': p += sprintf(p, "%02d", SecFromTime(t)); break;
+			case 'z':
+				if (tza < 0) {
+					tzsign = '-';
+					tza = -tza;
+				} else {
+					tzsign = '+';
+				}
+				tzhh = HourFromTime(tza);
+				tzmm = MinFromTime(tza);
+				if (tzmm != 0)
+					p += sprintf(p, "UTC%c%02d%02d", tzsign, tzhh, tzmm);
+				else if (tzhh != 0)
+					p += sprintf(p, "UTC%c%02d", tzsign, tzhh);
+				else
+					p += sprintf(p, "UTC");
+				break;
+			}
+			s += 2;
+		} else {
+			*p++ = *s++;
+		}
+	}
+	*p = 0;
+}
+
+static const char *fmtlocal(char *buf, const char *fmt, double t)
+{
+	fmtdate(buf, fmt, LocalTime(t), LocalTZA());
 	return buf;
 }
 
-static const char *fmtutc(const char *fmt, double t)
+static const char *fmtutc(char *buf, const char *fmt, double t)
 {
-	static char buf[64];
-	time_t tt = t * 0.001;
-	strftime(buf, sizeof buf, fmt, gmtime(&tt));
+	fmtdate(buf, fmt, t, 0);
 	return buf;
 }
 
@@ -307,7 +411,8 @@
 
 static void jsB_Date(js_State *J, unsigned int argc)
 {
-	js_pushstring(J, fmtlocal(FMT_DATETIME, Now()));
+	char buf[64];
+	js_pushstring(J, fmtlocal(buf, FMT_DATETIME, Now()));
 }
 
 static void jsB_new_Date(js_State *J, unsigned int argc)
@@ -352,32 +457,37 @@
 
 static void Dp_toString(js_State *J, unsigned int argc)
 {
+	char buf[64];
 	double t = js_todate(J, 0);
-	js_pushstring(J, fmtlocal(FMT_DATETIME, t));
+	js_pushstring(J, fmtlocal(buf, FMT_DATETIME, t));
 }
 
 static void Dp_toDateString(js_State *J, unsigned int argc)
 {
+	char buf[64];
 	double t = js_todate(J, 0);
-	js_pushstring(J, fmtlocal(FMT_DATE, t));
+	js_pushstring(J, fmtlocal(buf, FMT_DATE, t));
 }
 
 static void Dp_toTimeString(js_State *J, unsigned int argc)
 {
+	char buf[64];
 	double t = js_todate(J, 0);
-	js_pushstring(J, fmtlocal(FMT_TIME, t));
+	js_pushstring(J, fmtlocal(buf, FMT_TIME, t));
 }
 
 static void Dp_toUTCString(js_State *J, unsigned int argc)
 {
+	char buf[64];
 	double t = js_todate(J, 0);
-	js_pushstring(J, fmtutc(FMT_DATETIME, t));
+	js_pushstring(J, fmtutc(buf, FMT_DATETIME, t));
 }
 
 static void Dp_toISOString(js_State *J, unsigned int argc)
 {
+	char buf[64];
 	double t = js_todate(J, 0);
-	js_pushstring(J, fmtutc(FMT_DATETIME_ISO, t));
+	js_pushstring(J, fmtutc(buf, FMT_DATETIME_ISO, t));
 }
 
 static void Dp_getFullYear(js_State *J, unsigned int argc)