From 73dd55775b4ed2609b6b052046423db6241e6a54 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Mon, 29 Aug 2022 23:54:45 +0300
Subject: [PATCH v9 5/9] Add safe input and type conversion functions for
 datetime types

---
 contrib/adminpack/adminpack.c     |   2 +-
 src/backend/utils/adt/date.c      | 277 ++++++++++++++++++++++++------
 src/backend/utils/adt/datetime.c  |  16 +-
 src/backend/utils/adt/timestamp.c | 126 +++++++++++---
 src/backend/utils/misc/guc.c      |   2 +-
 src/include/utils/date.h          |  13 ++
 src/include/utils/datetime.h      |   6 +-
 src/include/utils/timestamp.h     |   6 +
 8 files changed, 363 insertions(+), 85 deletions(-)

diff --git a/contrib/adminpack/adminpack.c b/contrib/adminpack/adminpack.c
index 03addf1dc5f..4bf795d42a4 100644
--- a/contrib/adminpack/adminpack.c
+++ b/contrib/adminpack/adminpack.c
@@ -571,7 +571,7 @@ pg_logdir_ls_internal(FunctionCallInfo fcinfo)
 		if (ParseDateTime(timestampbuf, lowstr, MAXDATELEN, field, ftype, MAXDATEFIELDS, &nf))
 			continue;
 
-		if (DecodeDateTime(field, ftype, nf, &dtype, &date, &fsec, &tz))
+		if (DecodeDateTime(field, ftype, nf, &dtype, &date, &fsec, &tz, true))
 			continue;
 
 		/* Seems the timestamp is OK; prepare and return tuple */
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 081dfa2450f..43a96fc5142 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -108,10 +108,9 @@ anytime_typmodout(bool istz, int32 typmod)
 /* date_in()
  * Given date text string, convert to internal date format.
  */
-Datum
-date_in(PG_FUNCTION_ARGS)
+DateADT
+date_in_opt_error(char *str, bool *error)
 {
-	char	   *str = PG_GETARG_CSTRING(0);
 	DateADT		date;
 	fsec_t		fsec;
 	struct pg_tm tt,
@@ -127,9 +126,18 @@ date_in(PG_FUNCTION_ARGS)
 	dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
 						  field, ftype, MAXDATEFIELDS, &nf);
 	if (dterr == 0)
-		dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tzp);
+		dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tzp,
+							   !error);
 	if (dterr != 0)
+	{
+		if (error)
+		{
+			*error = true;
+			return 0;
+		}
+
 		DateTimeParseError(dterr, str, "date");
+	}
 
 	switch (dtype)
 	{
@@ -149,25 +157,56 @@ date_in(PG_FUNCTION_ARGS)
 			PG_RETURN_DATEADT(date);
 
 		default:
+			if (*error)
+			{
+				*error = true;
+				return 0;
+			}
+
 			DateTimeParseError(DTERR_BAD_FORMAT, str, "date");
 			break;
 	}
 
 	/* Prevent overflow in Julian-day routines */
 	if (!IS_VALID_JULIAN(tm->tm_year, tm->tm_mon, tm->tm_mday))
+	{
+		if (error)
+		{
+			*error = true;
+			return 0;
+		}
+
 		ereport(ERROR,
 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 				 errmsg("date out of range: \"%s\"", str)));
+	}
 
 	date = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE;
 
 	/* Now check for just-out-of-range dates */
 	if (!IS_VALID_DATE(date))
+	{
+		if (error)
+		{
+			*error = true;
+			return 0;
+		}
+
 		ereport(ERROR,
 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 				 errmsg("date out of range: \"%s\"", str)));
+	}
 
-	PG_RETURN_DATEADT(date);
+	return date;
+}
+
+/* date_in()
+ * Given date text string, convert to internal date format.
+ */
+Datum
+date_in(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATEADT(date_in_opt_error(PG_GETARG_CSTRING(0), NULL));
 }
 
 /* date_out()
@@ -1286,10 +1325,9 @@ date_timestamp(PG_FUNCTION_ARGS)
 /* timestamp_date()
  * Convert timestamp to date data type.
  */
-Datum
-timestamp_date(PG_FUNCTION_ARGS)
+DateADT
+timestamp_date_opt_error(Timestamp timestamp, bool *error)
 {
-	Timestamp	timestamp = PG_GETARG_TIMESTAMP(0);
 	DateADT		result;
 	struct pg_tm tt,
 			   *tm = &tt;
@@ -1302,16 +1340,32 @@ timestamp_date(PG_FUNCTION_ARGS)
 	else
 	{
 		if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
+		{
+			if (error)
+			{
+				*error = true;
+				return 0;
+			}
+
 			ereport(ERROR,
 					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 					 errmsg("timestamp out of range")));
+		}
 
 		result = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE;
 	}
 
-	PG_RETURN_DATEADT(result);
+	return result;
 }
 
+/* timestamp_date()
+ * Convert timestamp to date data type.
+ */
+Datum
+timestamp_date(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATEADT(timestamp_date_opt_error(PG_GETARG_TIMESTAMP(0), NULL));
+}
 
 /* date_timestamptz()
  * Convert date to timestamp with time zone data type.
@@ -1327,14 +1381,9 @@ date_timestamptz(PG_FUNCTION_ARGS)
 	PG_RETURN_TIMESTAMP(result);
 }
 
-
-/* timestamptz_date()
- * Convert timestamp with time zone to date data type.
- */
-Datum
-timestamptz_date(PG_FUNCTION_ARGS)
+DateADT
+timestamptz_date_opt_error(TimestampTz timestamp, bool *error)
 {
-	TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
 	DateADT		result;
 	struct pg_tm tt,
 			   *tm = &tt;
@@ -1348,14 +1397,31 @@ timestamptz_date(PG_FUNCTION_ARGS)
 	else
 	{
 		if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
+		{
+			if (error)
+			{
+				*error = true;
+				return 0;
+			}
+
 			ereport(ERROR,
 					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 					 errmsg("timestamp out of range")));
+		}
 
 		result = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE;
 	}
 
-	PG_RETURN_DATEADT(result);
+	return result;
+}
+
+/* timestamptz_date()
+ * Convert timestamp with time zone to date data type.
+ */
+Datum
+timestamptz_date(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATEADT(timestamptz_date_opt_error(PG_GETARG_TIMESTAMP(0), NULL));
 }
 
 
@@ -1363,15 +1429,9 @@ timestamptz_date(PG_FUNCTION_ARGS)
  *	 Time ADT
  *****************************************************************************/
 
-Datum
-time_in(PG_FUNCTION_ARGS)
+TimeADT
+time_in_opt_error(char *str, int32 typmod, bool *error)
 {
-	char	   *str = PG_GETARG_CSTRING(0);
-
-#ifdef NOT_USED
-	Oid			typelem = PG_GETARG_OID(1);
-#endif
-	int32		typmod = PG_GETARG_INT32(2);
 	TimeADT		result;
 	fsec_t		fsec;
 	struct pg_tm tt,
@@ -1387,14 +1447,35 @@ time_in(PG_FUNCTION_ARGS)
 	dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
 						  field, ftype, MAXDATEFIELDS, &nf);
 	if (dterr == 0)
-		dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz);
+		dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz,
+							   !error);
 	if (dterr != 0)
+	{
+		if (error)
+		{
+			*error = true;
+			return 0;
+		}
+
 		DateTimeParseError(dterr, str, "time");
+	}
 
 	tm2time(tm, fsec, &result);
 	AdjustTimeForTypmod(&result, typmod);
 
-	PG_RETURN_TIMEADT(result);
+	return result;
+}
+
+Datum
+time_in(PG_FUNCTION_ARGS)
+{
+	char	   *str = PG_GETARG_CSTRING(0);
+#ifdef NOT_USED
+	Oid			typelem = PG_GETARG_OID(1);
+#endif
+	int32		typmod = PG_GETARG_INT32(2);
+
+	PG_RETURN_TIMEADT(time_in_opt_error(str, typmod, NULL));
 }
 
 /* tm2time()
@@ -1889,22 +1970,32 @@ overlaps_time(PG_FUNCTION_ARGS)
 /* timestamp_time()
  * Convert timestamp to time data type.
  */
-Datum
-timestamp_time(PG_FUNCTION_ARGS)
+TimeADT
+timestamp_time_opt_error(Timestamp timestamp, bool *isnull, bool *error)
 {
-	Timestamp	timestamp = PG_GETARG_TIMESTAMP(0);
 	TimeADT		result;
 	struct pg_tm tt,
 			   *tm = &tt;
 	fsec_t		fsec;
 
 	if (TIMESTAMP_NOT_FINITE(timestamp))
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
 	if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
+	{
+		if (error)
+		{
+			*error = *isnull = true;
+			return (Datum) 0;
+		}
+
 		ereport(ERROR,
 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 				 errmsg("timestamp out of range")));
+	}
 
 	/*
 	 * Could also do this with time = (timestamp / USECS_PER_DAY *
@@ -1912,17 +2003,33 @@ timestamp_time(PG_FUNCTION_ARGS)
 	 */
 	result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *
 			  USECS_PER_SEC) + fsec;
+	*isnull = false;
 
-	PG_RETURN_TIMEADT(result);
+	return result;
 }
 
-/* timestamptz_time()
- * Convert timestamptz to time data type.
+/* timestamp_time()
+ * Convert timestamp to time data type.
  */
 Datum
-timestamptz_time(PG_FUNCTION_ARGS)
+timestamp_time(PG_FUNCTION_ARGS)
+{
+	bool		isnull;
+	TimeADT		time =
+		timestamp_time_opt_error(PG_GETARG_TIMESTAMP(0), &isnull, NULL);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_TIMEADT(time);
+}
+
+/* timestamptz_time_opt_error()
+ * Convert timestamptz to time data type.
+ */
+TimeADT
+timestamptz_time_opt_error(TimestampTz timestamp, bool *isnull, bool *error)
 {
-	TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
 	TimeADT		result;
 	struct pg_tm tt,
 			   *tm = &tt;
@@ -1930,12 +2037,23 @@ timestamptz_time(PG_FUNCTION_ARGS)
 	fsec_t		fsec;
 
 	if (TIMESTAMP_NOT_FINITE(timestamp))
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
 	if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
+	{
+		if (error)
+		{
+			*error = *isnull = true;
+			return 0;
+		}
+
 		ereport(ERROR,
 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 				 errmsg("timestamp out of range")));
+	}
 
 	/*
 	 * Could also do this with time = (timestamp / USECS_PER_DAY *
@@ -1943,8 +2061,25 @@ timestamptz_time(PG_FUNCTION_ARGS)
 	 */
 	result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *
 			  USECS_PER_SEC) + fsec;
+	*isnull = false;
 
-	PG_RETURN_TIMEADT(result);
+	return result;
+}
+
+/* timestamptz_time()
+ * Convert timestamptz to time data type.
+ */
+Datum
+timestamptz_time(PG_FUNCTION_ARGS)
+{
+	bool		isnull;
+	TimeADT		result =
+		timestamptz_time_opt_error(PG_GETARG_TIMESTAMP(0), &isnull, NULL);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_TIMEADT(result);
 }
 
 /* datetime_timestamp()
@@ -2249,15 +2384,9 @@ tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result)
 	return 0;
 }
 
-Datum
-timetz_in(PG_FUNCTION_ARGS)
+TimeTzADT *
+timetz_in_opt_error(char *str, int32 typmod, bool *error)
 {
-	char	   *str = PG_GETARG_CSTRING(0);
-
-#ifdef NOT_USED
-	Oid			typelem = PG_GETARG_OID(1);
-#endif
-	int32		typmod = PG_GETARG_INT32(2);
 	TimeTzADT  *result;
 	fsec_t		fsec;
 	struct pg_tm tt,
@@ -2273,17 +2402,38 @@ timetz_in(PG_FUNCTION_ARGS)
 	dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
 						  field, ftype, MAXDATEFIELDS, &nf);
 	if (dterr == 0)
-		dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz);
+		dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz,
+							   !error);
 	if (dterr != 0)
+	{
+		if (error)
+		{
+			*error = true;
+			return NULL;
+		}
+
 		DateTimeParseError(dterr, str, "time with time zone");
+	}
 
 	result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
 	tm2timetz(tm, fsec, tz, result);
 	AdjustTimeForTypmod(&(result->time), typmod);
 
-	PG_RETURN_TIMETZADT_P(result);
+	return result;
 }
 
+Datum
+timetz_in(PG_FUNCTION_ARGS)
+{
+	char	   *str = PG_GETARG_CSTRING(0);
+
+#ifdef NOT_USED
+	Oid			typelem = PG_GETARG_OID(1);
+#endif
+	int32		typmod = PG_GETARG_INT32(2);
+
+	PG_RETURN_TIMETZADT_P(timetz_in_opt_error(str, typmod, NULL));
+}
 Datum
 timetz_out(PG_FUNCTION_ARGS)
 {
@@ -2812,10 +2962,9 @@ time_timetz(PG_FUNCTION_ARGS)
 /* timestamptz_timetz()
  * Convert timestamp to timetz data type.
  */
-Datum
-timestamptz_timetz(PG_FUNCTION_ARGS)
+TimeTzADT *
+timestamptz_timetz_opt_error(TimestampTz timestamp, bool *error)
 {
-	TimestampTz timestamp = PG_GETARG_TIMESTAMP(0);
 	TimeTzADT  *result;
 	struct pg_tm tt,
 			   *tm = &tt;
@@ -2823,20 +2972,42 @@ timestamptz_timetz(PG_FUNCTION_ARGS)
 	fsec_t		fsec;
 
 	if (TIMESTAMP_NOT_FINITE(timestamp))
-		PG_RETURN_NULL();
+		return NULL;
 
 	if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
+	{
+		if (error)
+		{
+			*error = true;
+			return NULL;
+		}
+
 		ereport(ERROR,
 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 				 errmsg("timestamp out of range")));
+	}
 
 	result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
 
 	tm2timetz(tm, fsec, tz, result);
 
-	PG_RETURN_TIMETZADT_P(result);
+	return result;
 }
 
+/* timestamptz_timetz()
+ * Convert timestamp to timetz data type.
+ */
+Datum
+timestamptz_timetz(PG_FUNCTION_ARGS)
+{
+	TimeTzADT  *result =
+		timestamptz_timetz_opt_error(PG_GETARG_TIMESTAMP(0), NULL);
+
+	if (result)
+		PG_RETURN_TIMETZADT_P(result);
+	else
+		PG_RETURN_NULL();
+}
 
 /* datetimetz_timestamptz()
  * Convert date and timetz to timestamp with time zone data type.
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 43fff50d490..3275e5fd948 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -971,7 +971,8 @@ ParseDateTime(const char *timestr, char *workbuf, size_t buflen,
  */
 int
 DecodeDateTime(char **field, int *ftype, int nf,
-			   int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp)
+			   int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
+			   bool throw_errors)
 {
 	int			fmask = 0,
 				tmask,
@@ -1114,8 +1115,13 @@ DecodeDateTime(char **field, int *ftype, int nf,
 							/*
 							 * We should return an error code instead of
 							 * ereport'ing directly, but then there is no way
-							 * to report the bad time zone name.
+							 * to report the bad time zone name.  But
+							 * throwing errors is disallowed, simply
+							 * return error code.
 							 */
+							if (!throw_errors)
+								return DTERR_BAD_FORMAT;
+
 							ereport(ERROR,
 									(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 									 errmsg("time zone \"%s\" not recognized",
@@ -1921,7 +1927,8 @@ DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr, pg_tz *tzp,
  */
 int
 DecodeTimeOnly(char **field, int *ftype, int nf,
-			   int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp)
+			   int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
+			   bool throw_errors)
 {
 	int			fmask = 0,
 				tmask,
@@ -2022,6 +2029,9 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
 							 * ereport'ing directly, but then there is no way
 							 * to report the bad time zone name.
 							 */
+							if (!throw_errors)
+								return DTERR_BAD_FORMAT;
+
 							ereport(ERROR,
 									(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 									 errmsg("time zone \"%s\" not recognized",
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 49cdb290ac2..04a0e3eb063 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -139,18 +139,12 @@ anytimestamp_typmodout(bool istz, int32 typmod)
  *	 USER I/O ROUTINES														 *
  *****************************************************************************/
 
-/* timestamp_in()
- * Convert a string to internal form.
+/* timestamp_in_opt_error()
+ * Convert a string to internal form, returning error instead of throwing if needed.
  */
-Datum
-timestamp_in(PG_FUNCTION_ARGS)
+Timestamp
+timestamp_in_opt_error(char *str, int32 typmod, bool *error)
 {
-	char	   *str = PG_GETARG_CSTRING(0);
-
-#ifdef NOT_USED
-	Oid			typelem = PG_GETARG_OID(1);
-#endif
-	int32		typmod = PG_GETARG_INT32(2);
 	Timestamp	result;
 	fsec_t		fsec;
 	struct pg_tm tt,
@@ -166,17 +160,34 @@ timestamp_in(PG_FUNCTION_ARGS)
 	dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
 						  field, ftype, MAXDATEFIELDS, &nf);
 	if (dterr == 0)
-		dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz);
+		dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz,
+							   !error);
 	if (dterr != 0)
+	{
+		if (error)
+		{
+			*error = true;
+			return 0;
+		}
+
 		DateTimeParseError(dterr, str, "timestamp");
+	}
 
 	switch (dtype)
 	{
 		case DTK_DATE:
 			if (tm2timestamp(tm, fsec, NULL, &result) != 0)
+			{
+				if (error)
+				{
+					*error = true;
+					return 0;
+				}
+
 				ereport(ERROR,
 						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 						 errmsg("timestamp out of range: \"%s\"", str)));
+			}
 			break;
 
 		case DTK_EPOCH:
@@ -197,11 +208,27 @@ timestamp_in(PG_FUNCTION_ARGS)
 			TIMESTAMP_NOEND(result);
 	}
 
-	AdjustTimestampForTypmod(&result, typmod);
+	AdjustTimestampForTypmodError(&result, typmod, error);
 
 	PG_RETURN_TIMESTAMP(result);
 }
 
+/* timestamp_in()
+ * Convert a string to internal form.
+ */
+Datum
+timestamp_in(PG_FUNCTION_ARGS)
+{
+	char	   *str = PG_GETARG_CSTRING(0);
+
+#ifdef NOT_USED
+	Oid			typelem = PG_GETARG_OID(1);
+#endif
+	int32		typmod = PG_GETARG_INT32(2);
+
+	PG_RETURN_TIMESTAMP(timestamp_in_opt_error(str, typmod, NULL));
+}
+
 /* timestamp_out()
  * Convert a timestamp to external form.
  */
@@ -400,15 +427,9 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
 /* timestamptz_in()
  * Convert a string to internal form.
  */
-Datum
-timestamptz_in(PG_FUNCTION_ARGS)
+TimestampTz
+timestamptz_in_opt_error(char *str, int32 typmod, bool *error)
 {
-	char	   *str = PG_GETARG_CSTRING(0);
-
-#ifdef NOT_USED
-	Oid			typelem = PG_GETARG_OID(1);
-#endif
-	int32		typmod = PG_GETARG_INT32(2);
 	TimestampTz result;
 	fsec_t		fsec;
 	struct pg_tm tt,
@@ -424,17 +445,34 @@ timestamptz_in(PG_FUNCTION_ARGS)
 	dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
 						  field, ftype, MAXDATEFIELDS, &nf);
 	if (dterr == 0)
-		dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz);
+		dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz,
+							   !error);
 	if (dterr != 0)
+	{
+		if (error)
+		{
+			*error = true;
+			return 0;
+		}
+
 		DateTimeParseError(dterr, str, "timestamp with time zone");
+	}
 
 	switch (dtype)
 	{
 		case DTK_DATE:
 			if (tm2timestamp(tm, fsec, &tz, &result) != 0)
+			{
+				if (error)
+				{
+					*error = true;
+					return 0;
+				}
+
 				ereport(ERROR,
 						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 						 errmsg("timestamp out of range: \"%s\"", str)));
+			}
 			break;
 
 		case DTK_EPOCH:
@@ -455,9 +493,24 @@ timestamptz_in(PG_FUNCTION_ARGS)
 			TIMESTAMP_NOEND(result);
 	}
 
-	AdjustTimestampForTypmod(&result, typmod);
+	AdjustTimestampForTypmodError(&result, typmod, error);
 
-	PG_RETURN_TIMESTAMPTZ(result);
+	return result;
+}
+
+/* timestamptz_in()
+ * Convert a string to internal form.
+ */
+Datum
+timestamptz_in(PG_FUNCTION_ARGS)
+{
+	char	   *str = PG_GETARG_CSTRING(0);
+#ifdef NOT_USED
+	Oid			typelem = PG_GETARG_OID(1);
+#endif
+	int32		typmod = PG_GETARG_INT32(2);
+
+	PG_RETURN_TIMESTAMPTZ(timestamptz_in_opt_error(str, typmod, NULL));
 }
 
 /*
@@ -5614,8 +5667,8 @@ timestamptz_timestamp(PG_FUNCTION_ARGS)
 	PG_RETURN_TIMESTAMP(timestamptz2timestamp(timestamp));
 }
 
-static Timestamp
-timestamptz2timestamp(TimestampTz timestamp)
+Timestamp
+timestamptz2timestamp_opt_error(TimestampTz timestamp, bool *error)
 {
 	Timestamp	result;
 	struct pg_tm tt,
@@ -5628,17 +5681,40 @@ timestamptz2timestamp(TimestampTz timestamp)
 	else
 	{
 		if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, NULL) != 0)
+		{
+			if (error)
+			{
+				*error = true;
+				return 0;
+			}
+
 			ereport(ERROR,
 					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 					 errmsg("timestamp out of range")));
+		}
+
 		if (tm2timestamp(tm, fsec, NULL, &result) != 0)
+		{
+			if (error)
+			{
+				*error = true;
+				return 0;
+			}
+
 			ereport(ERROR,
 					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 					 errmsg("timestamp out of range")));
+		}
 	}
 	return result;
 }
 
+static Timestamp
+timestamptz2timestamp(TimestampTz timestamp)
+{
+	return timestamptz2timestamp_opt_error(timestamp, NULL);
+}
+
 /* timestamptz_zone()
  * Evaluate timestamp with time zone type at the specified time zone.
  * Returns a timestamp without time zone.
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 9fbbfb1be54..7877bce6043 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -12866,7 +12866,7 @@ check_recovery_target_time(char **newval, void **extra, GucSource source)
 			dterr = ParseDateTime(str, workbuf, sizeof(workbuf),
 								  field, ftype, MAXDATEFIELDS, &nf);
 			if (dterr == 0)
-				dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz);
+				dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz, true);
 			if (dterr != 0)
 				return false;
 			if (dtype != DTK_DATE)
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index 91ae24254df..f29168a025b 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -87,4 +87,17 @@ extern bool time_overflows(int hour, int min, int sec, fsec_t fsec);
 extern bool float_time_overflows(int hour, int min, double sec);
 extern void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
 
+extern DateADT timestamp_date_opt_error(Timestamp timestamp, bool *error);
+extern TimeADT timestamp_time_opt_error(Timestamp timestamp,
+										bool *isnull, bool *error);
+
+extern DateADT timestamptz_date_opt_error(Timestamp timestamp, bool *error);
+extern TimeADT timestamptz_time_opt_error(TimestampTz timestamp,
+										  bool *isnull, bool *error);
+extern TimeTzADT *timestamptz_timetz_opt_error(TimestampTz timestamp,
+											   bool *error);
+extern DateADT date_in_opt_error(char *str, bool *error);
+extern TimeADT time_in_opt_error(char *str, int32 typmod, bool *error);
+extern TimeTzADT *timetz_in_opt_error(char *str, int32 typmod, bool *error);
+
 #endif							/* DATE_H */
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index 4527e825177..68241d7c4cc 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -295,11 +295,13 @@ extern int	ParseDateTime(const char *timestr, char *workbuf, size_t buflen,
 						  int maxfields, int *numfields);
 extern int	DecodeDateTime(char **field, int *ftype,
 						   int nf, int *dtype,
-						   struct pg_tm *tm, fsec_t *fsec, int *tzp);
+						   struct pg_tm *tm, fsec_t *fsec, int *tzp,
+						   bool throw_errors);
 extern int	DecodeTimezone(char *str, int *tzp);
 extern int	DecodeTimeOnly(char **field, int *ftype,
 						   int nf, int *dtype,
-						   struct pg_tm *tm, fsec_t *fsec, int *tzp);
+						   struct pg_tm *tm, fsec_t *fsec, int *tzp,
+						   bool throw_errors);
 extern int	DecodeInterval(char **field, int *ftype, int nf, int range,
 						   int *dtype, struct pg_itm_in *itm_in);
 extern int	DecodeISO8601Interval(char *str,
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index edf3a973186..2685d14342d 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -102,8 +102,14 @@ extern int	timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
 
 extern TimestampTz timestamp2timestamptz_opt_overflow(Timestamp timestamp,
 													  int *overflow);
+extern Timestamp timestamptz2timestamp_opt_error(TimestampTz timestamp,
+												 bool *error);
 extern int32 timestamp_cmp_timestamptz_internal(Timestamp timestampVal,
 												TimestampTz dt2);
+extern Timestamp timestamp_in_opt_error(char *str, int32 typmod,
+										bool *error);
+extern TimestampTz timestamptz_in_opt_error(char *str, int32 typmod,
+											bool *error);
 
 extern int	isoweek2j(int year, int week);
 extern void isoweek2date(int woy, int *year, int *mon, int *mday);
-- 
2.17.1

