diff --git a/include/asterisk/app.h b/include/asterisk/app.h
index 6d73b7786014389a4fb914b305bf23c6d8da949e..e2ec86328932eb59849b3817aaa612521b679ba5 100644
--- a/include/asterisk/app.h
+++ b/include/asterisk/app.h
@@ -84,6 +84,12 @@ struct ast_ivr_menu {
 	static struct ast_ivr_option __options_##holder[] = foo;\
 	static struct ast_ivr_menu holder = { title, flags, __options_##holder }
 
+typedef enum {
+	TIMELEN_HOURS,
+	TIMELEN_MINUTES,
+	TIMELEN_SECONDS,
+	TIMELEN_MILLISECONDS,
+} ast_timelen;
 
 /*!	\brief Runs an IVR menu
 	\return returns 0 on successful completion, -1 on hangup, or -2 on user error in menu */
@@ -580,6 +586,17 @@ int ast_safe_fork(int stop_reaper);
  */
 void ast_safe_fork_cleanup(void);
 
+/*!
+ * \brief Common routine to parse time lengths, with optional time unit specifier
+ * \param[in] timestr String to parse
+ * \param[in] defunit Default unit type
+ * \param[out] result Resulting value, specified in milliseconds
+ * \retval 0 Success
+ * \retval -1 Failure
+ * \since 1.8
+ */
+int ast_app_parse_timelen(const char *timestr, int *result, ast_timelen defunit);
+
 #if defined(__cplusplus) || defined(c_plusplus)
 }
 #endif
diff --git a/main/app.c b/main/app.c
index 99dcfa6775c5f1928cb214e70deb378561d2a01b..580e91b766bdacd107fca58c71ccf9d1937c086d 100644
--- a/main/app.c
+++ b/main/app.c
@@ -2082,3 +2082,59 @@ void ast_safe_fork_cleanup(void)
 	ast_unreplace_sigchld();
 }
 
+int ast_app_parse_timelen(const char *timestr, int *result, ast_timelen unit)
+{
+	int res;
+	char u[10];
+#ifdef HAVE_LONG_DOUBLE_WIDER
+	long double amount;
+	#define FMT "%30Lf%9s"
+#else
+	double amount;
+	#define FMT "%30lf%9s"
+#endif
+	if (!timestr) {
+		return -1;
+	}
+
+	if ((res = sscanf(timestr, FMT, &amount, u)) == 0) {
+#undef FMT
+		return -1;
+	} else if (res == 2) {
+		switch (u[0]) {
+		case 'h':
+		case 'H':
+			unit = TIMELEN_HOURS;
+			break;
+		case 's':
+		case 'S':
+			unit = TIMELEN_SECONDS;
+			break;
+		case 'm':
+		case 'M':
+			if (toupper(u[1]) == 'S') {
+				unit = TIMELEN_MILLISECONDS;
+			} else if (u[1] == '\0') {
+				unit = TIMELEN_MINUTES;
+			}
+			break;
+		}
+	}
+
+	switch (unit) {
+	case TIMELEN_HOURS:
+		amount *= 60;
+		/* fall-through */
+	case TIMELEN_MINUTES:
+		amount *= 60;
+		/* fall-through */
+	case TIMELEN_SECONDS:
+		amount *= 1000;
+		/* fall-through */
+	case TIMELEN_MILLISECONDS:
+		;
+	}
+	*result = amount > INT_MAX ? INT_MAX : (int) amount;
+	return 0;
+}
+
diff --git a/main/pbx.c b/main/pbx.c
index dd2016ed2c884eeac6402cd04ff34bd45e33bdeb..58e689e54e28e4e6d185cc465dadd29970a4e301 100644
--- a/main/pbx.c
+++ b/main/pbx.c
@@ -9001,12 +9001,10 @@ static int pbx_builtin_execiftime(struct ast_channel *chan, const char *data)
  */
 static int pbx_builtin_wait(struct ast_channel *chan, const char *data)
 {
-	double s;
 	int ms;
 
 	/* Wait for "n" seconds */
-	if (data && (s = atof(data)) > 0.0) {
-		ms = s * 1000.0;
+	if (!ast_app_parse_timelen(data, &ms, TIMELEN_SECONDS) && ms > 0) {
 		return ast_safe_sleep(chan, ms);
 	}
 	return 0;
@@ -9018,7 +9016,6 @@ static int pbx_builtin_wait(struct ast_channel *chan, const char *data)
 static int pbx_builtin_waitexten(struct ast_channel *chan, const char *data)
 {
 	int ms, res;
-	double s;
 	struct ast_flags flags = {0};
 	char *opts[1] = { NULL };
 	char *parse;
@@ -9050,12 +9047,13 @@ static int pbx_builtin_waitexten(struct ast_channel *chan, const char *data)
 		}
 	}
 	/* Wait for "n" seconds */
-	if (args.timeout && (s = atof(args.timeout)) > 0)
-		 ms = s * 1000.0;
-	else if (chan->pbx)
+	if (!ast_app_parse_timelen(args.timeout, &ms, TIMELEN_SECONDS) && ms > 0) {
+		/* Yay! */
+	} else if (chan->pbx) {
 		ms = chan->pbx->rtimeoutms;
-	else
+	} else {
 		ms = 10000;
+	}
 
 	res = ast_waitfordigit(chan, ms);
 	if (!res) {