# HG changeset patch
# Node ID 399e5c942afe
# Parent  cb21c7484e4c
diff --git a/libpurple/tests/test_util.c b/libpurple/tests/test_util.c
--- a/libpurple/tests/test_util.c
+++ b/libpurple/tests/test_util.c
@@ -162,13 +162,21 @@
 	time_t timestamp;
 
 	fail_unless(377182200 == purple_str_to_time("19811214T12:50:00", TRUE, NULL, NULL, NULL));
+	fail_unless(377203800 == purple_str_to_time("19811214T12:50:00-06", FALSE, NULL, NULL, NULL));
 	fail_unless(1175919261 == purple_str_to_time("20070407T04:14:21", TRUE, NULL, NULL, NULL));
 	fail_unless(1282941722 == purple_str_to_time("2010-08-27.204202", TRUE, NULL, NULL, NULL));
+	fail_unless(1175919261 == purple_str_to_time("20070407T04:14:21.3234", TRUE, NULL, NULL, NULL));
+	fail_unless(1175919261 == purple_str_to_time("20070407T04:14:21Z", TRUE, NULL, NULL, NULL));
+	fail_unless(1631512800 == purple_str_to_time("09-13-2021", TRUE, NULL, NULL, NULL));
 
 	timestamp = purple_str_to_time("2010-08-27.134202-0700PDT", FALSE, &tm, &tz_off, &rest);
 	fail_unless(1282941722 == timestamp);
 	fail_unless((-7 * 60 * 60) == tz_off);
 	assert_string_equal("PDT", rest);
+
+	timestamp = purple_str_to_time("09/13/202115:34:34", TRUE, NULL, NULL, &rest);
+	fail_unless(1631512800 == timestamp);
+	assert_string_equal("15:34:34", rest);
 }
 END_TEST
 
@@ -253,6 +261,8 @@
 Suite *
 util_suite(void)
 {
+	purple_util_init();
+
 	Suite *s = suite_create("Utility Functions");
 
 	TCase *tc = tcase_create("Base16");
@@ -306,5 +316,7 @@
 	tcase_add_test(tc, test_uri_escape_for_open);
 	suite_add_tcase(s, tc);
 
+	purple_util_uninit();
+
 	return s;
 }
diff --git a/libpurple/util.c b/libpurple/util.c
--- a/libpurple/util.c
+++ b/libpurple/util.c
@@ -84,6 +84,40 @@
 static char *custom_user_dir = NULL;
 static char *user_dir = NULL;
 
+static GRegex *str_to_time_regex = NULL;
+static const gchar *str_to_time_pattern =
+	"\\s*"
+	"(?P<year>\\d{4})? # look for a leading year\n"
+	"(?:[-\\/]?) # an optional separator of - or /\n"
+	"(?P<month>\\d{2}) # the two digit month\n"
+	"(?:[-\\/]?) # another optional separator of - or /\n"
+	"(?P<day>\\d{2}) # the two digit day\n"
+	"# we now have an optional trailing year or seconds\n"
+	"(?:\n"
+	"  (?:[-\\/]?(?P<trailingyear>\\d{4})) # the trailing year may have a - or / separator\n"
+	"|\n"
+	" [T.] # T signifies that this is an iso8601 time\n"
+	"  (?:\n"
+	"    (?P<hours>\\d{2}) # two digit hour\n"
+	"    :? # optional : separator\n"
+	"    (?P<minutes>\\d{2}) # two digit minutes\n"
+	"    :? # optional : separator\n"
+	"    (?P<seconds>\\d{2}) # two digit seconds\n"
+	"    (?:\\.(?P<microseconds>\\d+))? # optional microseconds\n"
+	"    (?:\n"
+	"      (?:\n"
+	"        (?P<tzsign>[+-]) # required tz sign\n"
+	"        (?P<tzhour>\\d{2}) # required tz hour\n"
+	"        (?:\n"
+	"          :? # optional : separator\n"
+	"          (?<tzminute>\\d{2}))? # optional tz minutes\n"
+	"        )\n"
+	"    |\n"
+	"      (?P<utc>Z) # utc time\n"
+	"    )?\n"
+	"  )?\n"
+	")\n"
+	"\\s* # remove trailing whitespace\n";
 
 PurpleMenuAction *
 purple_menu_action_new(const char *label, PurpleCallback callback, gpointer data,
@@ -109,8 +143,18 @@
 void
 purple_util_init(void)
 {
-	/* This does nothing right now.  It exists for symmetry with
-	 * purple_util_uninit() and forwards compatibility. */
+	GError *error = NULL;
+
+	str_to_time_regex = g_regex_new(str_to_time_pattern, G_REGEX_EXTENDED, 0,
+	                                &error);
+
+	if(error != NULL) {
+		purple_debug_fatal("util",
+		                   "Failed to compile the regex for purple_str_to_time"
+		                   ": %s",
+		                   error->message ? error->message : "unknown");
+		g_clear_error(&error);
+	}
 }
 
 void
@@ -123,6 +167,9 @@
 
 	g_free(user_dir);
 	user_dir = NULL;
+
+	g_regex_unref(str_to_time_regex);
+	str_to_time_regex = NULL;
 }
 
 /**************************************************************************
@@ -705,169 +752,155 @@
 purple_str_to_time(const char *timestamp, gboolean utc,
 	struct tm *tm, long *tz_off, const char **rest)
 {
+	GMatchInfo *info = NULL;
+	gboolean mktime_with_utc = FALSE, matched = FALSE;
+	gchar *match = NULL;
 	struct tm t;
-	const gchar *str;
-	gint year = 0;
 	long tzoff = PURPLE_NO_TZ_OFF;
 	time_t retval;
-	gboolean mktime_with_utc = FALSE;
-
-	if (rest != NULL)
-		*rest = NULL;
+	gint end = 0;
 
 	g_return_val_if_fail(timestamp != NULL, 0);
 
 	memset(&t, 0, sizeof(struct tm));
 
-	str = timestamp;
-
-	/* Strip leading whitespace */
-	while (g_ascii_isspace(*str))
-		str++;
-
-	if (*str == '\0') {
-		if (rest != NULL && *str != '\0')
-			*rest = str;
-
-		return 0;
-	}
-
-	if (!g_ascii_isdigit(*str) && *str != '-' && *str != '+') {
-		if (rest != NULL && *str != '\0')
-			*rest = str;
-
-		return 0;
-	}
-
-	/* 4 digit year */
-	if (sscanf(str, "%04d", &year) && year >= 1900) {
-		str += 4;
-
-		if (*str == '-' || *str == '/')
-			str++;
-
-		t.tm_year = year - 1900;
-	}
-
-	/* 2 digit month */
-	if (!sscanf(str, "%02d", &t.tm_mon)) {
-		if (rest != NULL && *str != '\0')
-			*rest = str;
-
-		return 0;
-	}
-
-	str += 2;
-	t.tm_mon -= 1;
-
-	if (*str == '-' || *str == '/')
-		str++;
-
-	/* 2 digit day */
-	if (!sscanf(str, "%02d", &t.tm_mday)) {
-		if (rest != NULL && *str != '\0')
-			*rest = str;
+	matched = g_regex_match(str_to_time_regex, timestamp, 0, &info);
+	if(!matched) {
+		if(rest != NULL) {
+			*rest = timestamp;
+		}
 
 		return 0;
 	}
 
-	str += 2;
-
-	/* Grab the year off the end if there's still stuff */
-	if (*str == '/' || *str == '-') {
-		/* But make sure we don't read the year twice */
-		if (year >= 1900) {
-			if (rest != NULL && *str != '\0')
-				*rest = str;
-
-			return 0;
+	/* Set rest to the end of the match in timestamp. */
+	if(rest != NULL) {
+		if(g_match_info_fetch_pos(info, 0, NULL, &end)) {
+			*rest = timestamp + end;
 		}
-
-		str++;
-
-		if (!sscanf(str, "%04d", &t.tm_year)) {
-			if (rest != NULL && *str != '\0')
-				*rest = str;
-
-			return 0;
+	}
+
+	/* check the year or the trailingyear if year is not set */
+	match = g_match_info_fetch_named(info, "year");
+	if(*match != '\0') {
+		t.tm_year = atoi(match) - 1900;
+	} else {
+		match = g_match_info_fetch_named(info, "trailingyear");
+		if(match != NULL) {
+			t.tm_year = atoi(match) - 1900;
 		}
-
-		t.tm_year -= 1900;
-	} else if (*str == 'T' || *str == '.') {
-		str++;
-
-		/* Continue grabbing the hours/minutes/seconds */
-		if ((sscanf(str, "%02d:%02d:%02d", &t.tm_hour, &t.tm_min, &t.tm_sec) == 3 &&
-				(str += 8)) ||
-		    (sscanf(str, "%02d%02d%02d", &t.tm_hour, &t.tm_min, &t.tm_sec) == 3 &&
-				(str += 6)))
-		{
-			gint sign, tzhrs, tzmins;
-
-			if (*str == '.') {
-				/* Cut off those pesky micro-seconds */
-				do {
-					str++;
-				} while (*str >= '0' && *str <= '9');
+	}
+	g_free(match);
+
+	/* get the month */
+	match = g_match_info_fetch_named(info, "month");
+	if(*match != '\0') {
+		t.tm_mon = atoi(match) - 1;
+	}
+	g_free(match);
+
+	/* get the day */
+	match = g_match_info_fetch_named(info, "day");
+	if(*match != '\0') {
+		t.tm_mday = atoi(match);
+	}
+	g_free(match);
+
+	/* get the hour */
+	match = g_match_info_fetch_named(info, "hours");
+	if(match != NULL && *match != '\0') {
+		t.tm_hour = atoi(match);
+	}
+	g_free(match);
+
+	/* get the minutes */
+	match = g_match_info_fetch_named(info, "minutes");
+	if(match != NULL && *match != '\0') {
+		t.tm_min = atoi(match);
+	}
+	g_free(match);
+
+	/* get the seconds */
+	match = g_match_info_fetch_named(info, "seconds");
+	if(match != NULL && *match != '\0') {
+		t.tm_sec = atoi(match);
+	}
+	g_free(match);
+
+	/* check if this is utc time */
+	match = g_match_info_fetch_named(info, "utc");
+	if(match != NULL && *match != '\0') {
+		tzoff = 0;
+		g_free(match);
+	} else {
+		/* free match if it was just an empty string */
+		g_free(match);
+
+		/* if match is null, check if we have tzsign which is required */
+		match = g_match_info_fetch_named(info, "tzsign");
+		if(match != NULL) {
+			gint tzsign = -1, tzhour = 0, tzminute = 0;
+
+			/* Figure out if we're ahead or behind of utc, assuming ahead by
+			 * default.
+			 */
+			if(*match == '+') {
+				tzsign = -1;
 			}
-
-			sign = (*str == '+') ? 1 : -1;
-
-			/* Process the timezone */
-			if (*str == '+' || *str == '-') {
-				str++;
-
-				if (((sscanf(str, "%02d:%02d", &tzhrs, &tzmins) == 2 && (str += 5)) ||
-					(sscanf(str, "%02d%02d", &tzhrs, &tzmins) == 2 && (str += 4))))
-				{
-					mktime_with_utc = TRUE;
-					tzoff = tzhrs * 60 * 60 + tzmins * 60;
-					tzoff *= sign;
-				}
-			} else if (*str == 'Z') {
-				/* 'Z' = Zulu = UTC */
-				str++;
+			g_free(match);
+
+			/* get the tz hour */
+			match = g_match_info_fetch_named(info, "tzhour");
+			if(match != NULL && *match != '\0') {
+				tzhour = atoi(match);
+			}
+			g_free(match);
+
+			/* get the tz minute */
+			match = g_match_info_fetch_named(info, "tzminute");
+			if(match != NULL && *match != '\0') {
+				tzminute = atoi(match);
+			}
+			g_free(match);
+
+			/* we need at least either an hour or minute to offset the timezone */
+			if(tzhour > 0 || tzminute > 0) {
 				mktime_with_utc = TRUE;
-				tzoff = 0;
-			}
-
-			if (!mktime_with_utc)
-			{
-				/* No timezone specified. */
-
-				if (utc) {
-					mktime_with_utc = TRUE;
-					tzoff = 0;
-				} else {
-					/* Local Time */
-					t.tm_isdst = -1;
-				}
+				tzoff = tzsign * ((tzhour * 3600) + (tzminute * 60));
 			}
 		}
 	}
 
-	if (rest != NULL && *str != '\0') {
-		/* Strip trailing whitespace */
-		while (g_ascii_isspace(*str))
-			str++;
-
-		if (*str != '\0')
-			*rest = str;
+	/* If we have a time, figure out if we need to adjust our tz offset. */
+	if(t.tm_hour > 0 || t.tm_min > 0 || t.tm_sec > 0) {
+		if(!mktime_with_utc) {
+			if(utc) {
+				mktime_with_utc = TRUE;
+				tzoff = 0;
+			} else {
+				/* Local Time */
+				t.tm_isdst = -1;
+			}
+		}
 	}
 
-	if (mktime_with_utc)
+	if(mktime_with_utc) {
 		retval = mktime_utc(&t);
-	else
+	} else {
 		retval = mktime(&t);
-
-	if (tm != NULL)
+	}
+
+	if(tm != NULL) {
 		*tm = t;
-
-	if (tzoff != PURPLE_NO_TZ_OFF)
+	}
+
+	if(tzoff != PURPLE_NO_TZ_OFF) {
 		retval -= tzoff;
-
-	if (tz_off != NULL)
+	}
+
+	if(tz_off != NULL) {
 		*tz_off = tzoff;
+	}
 
 	return retval;
 }
