diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index b74889039d..245651a778 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -698,6 +698,9 @@ ParseFraction(char *cp, double *frac)
 		/* check for parse failure */
 		if (*cp != '\0' || errno != 0)
 			return DTERR_BAD_FORMAT;
+		/* check for bogus input such as .1e10 */
+		if (*frac >= 1.0)
+			return DTERR_BAD_FORMAT;
 	}
 	return 0;
 }
@@ -3080,6 +3083,9 @@ DecodeNumberField(int len, char *str, int fmask,
 			frac = strtod(cp, NULL);
 			if (errno != 0)
 				return DTERR_BAD_FORMAT;
+			/* check for bogus input such as .1e10 */
+			if (frac >= 1.0)
+				return DTERR_BAD_FORMAT;
 			*fsec = rint(frac * 1000000);
 		}
 		/* Now truncate off the fraction for further processing */
@@ -3702,46 +3708,38 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 static int
 ParseISO8601Number(char *str, char **endptr, int64 *ipart, double *fpart)
 {
-	int			sign = 1;
-
-	*ipart = 0;
-	*fpart = 0.0;
-
-	/* Parse sign if there is any */
-	if (*str == '-')
-	{
-		sign = -1;
-		str++;
-	}
+	double		val;
 
-	*endptr = str;
+	/*
+	 * Historically this has accepted anything that strtod() would take,
+	 * notably including "e" notation, so continue doing that.  This is
+	 * slightly annoying because the precision of double is less than that of
+	 * int64, so we would lose accuracy for inputs larger than 2^53 or so.
+	 * However, historically we rejected inputs outside the int32 range,
+	 * making that concern moot.  What we do now is reject abs(val) above
+	 * 1.0e15 (a round number a bit less than 2^50), so that any accepted
+	 * value will have an exact integer part, and thereby a fraction part with
+	 * abs(*fpart) less than 1.  In the absence of field complaints it doesn't
+	 * seem worth working harder.
+	 */
+	if (!(isdigit((unsigned char) *str) || *str == '-' || *str == '.'))
+		return DTERR_BAD_FORMAT;
 	errno = 0;
-
-	/* Parse int64 part if there is any */
-	if (isdigit((unsigned char) **endptr))
-		*ipart = strtoi64(*endptr, endptr, 10) * sign;
-
-	/* Parse fractional part if there is any */
-	if (**endptr == '.')
-	{
-		/*
-		 * As in ParseFraction, some versions of strtod insist on seeing some
-		 * digits after '.', but some don't.  We want to allow zero digits
-		 * after '.' as long as there were some before it.
-		 */
-		if (isdigit((unsigned char) *(*endptr + 1)))
-			*fpart = strtod(*endptr, endptr) * sign;
-		else
-		{
-			(*endptr)++;		/* advance over '.' */
-			str++;				/* so next test will fail if no digits */
-		}
-	}
-
-	/* did we not see anything that looks like a number? */
+	val = strtod(str, endptr);
+	/* did we not see anything that looks like a double? */
 	if (*endptr == str || errno != 0)
 		return DTERR_BAD_FORMAT;
-
+	/* watch out for overflow, including infinities; reject NaN too */
+	if (isnan(val) || val < -1.0e15 || val > 1.0e15)
+		return DTERR_FIELD_OVERFLOW;
+	/* be very sure we truncate towards zero (cf dtrunc()) */
+	if (val >= 0)
+		*ipart = (int64) floor(val);
+	else
+		*ipart = (int64) -floor(-val);
+	*fpart = val - *ipart;
+	/* Callers expect this to hold */
+	Assert(*fpart > -1.0 && *fpart < 1.0);
 	return 0;
 }
 
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index c7ac408bec..a154840c85 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -975,6 +975,12 @@ select interval 'P.1Y0M3DT4H5M6S';
  1 mon 3 days 04:05:06
 (1 row)
 
+select interval 'P10.5e4Y';  -- not per spec, but we've historically taken it
+   interval   
+--------------
+ 105000 years
+(1 row)
+
 select interval 'P.Y0M3DT4H5M6S';  -- error
 ERROR:  invalid input syntax for type interval: "P.Y0M3DT4H5M6S"
 LINE 1: select interval 'P.Y0M3DT4H5M6S';
@@ -1081,13 +1087,13 @@ select interval 'PT2562047788:00:54.775807';
 select interval 'PT2562047788.0152155019444';
              interval              
 -----------------------------------
- @ 2562047788 hours 54.775807 secs
+ @ 2562047788 hours 54.775429 secs
 (1 row)
 
 select interval 'PT-2562047788.0152155022222';
                interval                
 ---------------------------------------
- @ 2562047788 hours 54.775808 secs ago
+ @ 2562047788 hours 54.775429 secs ago
 (1 row)
 
 -- overflow each date/time field
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 54745c40d6..af8e1ca0f4 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -328,6 +328,7 @@ select interval 'P1.0Y0M3DT4H5M6S';
 select interval 'P1.1Y0M3DT4H5M6S';
 select interval 'P1.Y0M3DT4H5M6S';
 select interval 'P.1Y0M3DT4H5M6S';
+select interval 'P10.5e4Y';  -- not per spec, but we've historically taken it
 select interval 'P.Y0M3DT4H5M6S';  -- error
 
 -- test a couple rounding cases that changed since 8.3 w/ HAVE_INT64_TIMESTAMP.
